-
Notifications
You must be signed in to change notification settings - Fork 159
feat: add /clear-screen slash command #647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
VitalyOstanin
wants to merge
5
commits into
Piebald-AI:main
Choose a base branch
from
VitalyOstanin:feature/clear-screen
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
f669a19
feat: add /clear-screen slash command
VitalyOstanin 683c0af
Address review feedback: fallback for assistant without usage
VitalyOstanin 4eea753
fix: preserve conversation context in /clear-screen
VitalyOstanin 8458f26
Add delimiter prefix to patchRenderFilter regex for V8 performance
VitalyOstanin b58ff14
Tie render filter regex to first param via backreference
VitalyOstanin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import { describe, expect, it } from 'vitest'; | ||
|
|
||
| import { writeClearScreen, patchRenderFilter } from './clearScreen'; | ||
|
|
||
| const cmds = Array.from({ length: 31 }, (_, i) => `c${i}`).join(','); | ||
| const slashCommandArray = `=>[${cmds}]`; | ||
|
|
||
| const renderFilter = | ||
| 'function g97(H,$){if(H.type!=="user")return!0;if(H.isMeta){if(H.origin?.kind==="channel")return!0;return!1}if(H.isVisibleInTranscriptOnly&&!$)return!1;return!0}'; | ||
|
|
||
| const makeInput = (delimiter = ';') => | ||
| 'const x=1;' + | ||
| renderFilter + | ||
| slashCommandArray + | ||
| `${delimiter}let Z=G_H.useCallback(()=>{Nw.get(process.stdout)?.forceRedraw()})`; | ||
|
|
||
| describe('clearScreen', () => { | ||
| it('exposes forceRedraw and registers /clear-screen command', () => { | ||
| const result = writeClearScreen(makeInput()); | ||
|
|
||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain( | ||
| 'globalThis.__tweakccForceRedraw=()=>Nw.get(process.stdout)?.forceRedraw()' | ||
| ); | ||
| expect(result).toContain('name:"clear-screen"'); | ||
| expect(result).toContain('__tweakccHiddenUUIDs'); | ||
| expect(result).toContain('globalThis.__tweakccForceRedraw?.()'); | ||
| }); | ||
|
|
||
| it('preserves all messages for API context (hides via UUID set, does not remove)', () => { | ||
| const result = writeClearScreen(makeInput()); | ||
|
|
||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain('__tweakccHiddenUUIDs=new Set('); | ||
| expect(result).toContain('return[...m]'); | ||
| expect(result).not.toContain('content:[]'); | ||
| expect(result).not.toContain('return k?['); | ||
| expect(result).not.toContain('return[]'); | ||
| }); | ||
|
|
||
| it('patches render filter to check __tweakccHiddenUUIDs', () => { | ||
| const result = writeClearScreen(makeInput()); | ||
|
|
||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain( | ||
| 'globalThis.__tweakccHiddenUUIDs?.has(H.uuid?.slice(0,24)))return!1;if(H.type!=="user")' | ||
| ); | ||
| }); | ||
|
|
||
| it('preserves original app:redraw callback', () => { | ||
| const result = writeClearScreen(makeInput()); | ||
|
|
||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain( | ||
| 'let Z=G_H.useCallback(()=>{Nw.get(process.stdout)?.forceRedraw()})' | ||
| ); | ||
| }); | ||
|
|
||
| it('returns oldFile when already patched', () => { | ||
| const input = makeInput() + ',{name:"clear-screen"}'; | ||
| const result = writeClearScreen(input); | ||
|
|
||
| expect(result).toBe(input); | ||
| }); | ||
|
|
||
| it('returns null when app:redraw callback not found', () => { | ||
| const result = writeClearScreen('const x=1;'); | ||
|
|
||
| expect(result).toBeNull(); | ||
| }); | ||
|
|
||
| it('returns null when render filter not found', () => { | ||
| const input = | ||
| 'const x=1' + | ||
| slashCommandArray + | ||
| ';let Z=G_H.useCallback(()=>{Nw.get(process.stdout)?.forceRedraw()})'; | ||
| const result = writeClearScreen(input); | ||
|
|
||
| expect(result).toBeNull(); | ||
| }); | ||
|
|
||
| it('works with different delimiters before useCallback', () => { | ||
| for (const d of [',', ';', '}', '{']) { | ||
| const result = writeClearScreen(makeInput(d)); | ||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain('globalThis.__tweakccForceRedraw'); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('patchRenderFilter', () => { | ||
| it('adds __tweakccHiddenUUIDs check at the start of the function', () => { | ||
| const result = patchRenderFilter(';' + renderFilter); | ||
|
|
||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain( | ||
| ';function g97(H,$){if(globalThis.__tweakccHiddenUUIDs?.has(H.uuid?.slice(0,24)))return!1;if(H.type!=="user")' | ||
| ); | ||
| }); | ||
|
|
||
| it('preserves the rest of the function', () => { | ||
| const result = patchRenderFilter(';' + renderFilter); | ||
|
|
||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain('if(H.isMeta)'); | ||
| expect(result).toContain('if(H.isVisibleInTranscriptOnly&&!$)return!1'); | ||
| }); | ||
|
|
||
| it('returns null when pattern not found', () => { | ||
| const result = patchRenderFilter('const x=1;'); | ||
|
|
||
| expect(result).toBeNull(); | ||
| }); | ||
|
|
||
| it('works with different delimiters before function', () => { | ||
| for (const d of [',', ';', '}', '{']) { | ||
| const result = patchRenderFilter(d + renderFilter); | ||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain( | ||
| 'if(globalThis.__tweakccHiddenUUIDs?.has(H.uuid?.slice(0,24)))return!1;' | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| it('works with different function and argument names', () => { | ||
| const input = | ||
| ';function abc(X$,Y$){if(X$.type!=="user")return!0;if(X$.isMeta){if(X$.origin?.kind==="channel")return!0;return!1}return!0}'; | ||
| const result = patchRenderFilter(input); | ||
|
|
||
| expect(result).not.toBeNull(); | ||
| expect(result).toContain( | ||
| 'if(globalThis.__tweakccHiddenUUIDs?.has(X$.uuid?.slice(0,24)))return!1;' | ||
| ); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| // Please see the note about writing patches in ./index | ||
|
|
||
| import { debug } from '../utils'; | ||
| import { showDiff } from './index'; | ||
| import { writeSlashCommandDefinition } from './slashCommands'; | ||
|
|
||
| export const writeClearScreen = (oldFile: string): string | null => { | ||
| const alreadyPatchedPattern = /name:"clear-screen"/; | ||
| if (alreadyPatchedPattern.test(oldFile)) { | ||
| return oldFile; | ||
| } | ||
|
|
||
| const redrawPattern = | ||
| /([,;{}])(let [$\w]+=[$\w]+\.useCallback\(\(\)=>\{)([$\w]+)\.get\(process\.stdout\)\?\.forceRedraw\(\)\}/; | ||
| const redrawMatch = oldFile.match(redrawPattern); | ||
| if (!redrawMatch || redrawMatch.index === undefined) { | ||
| debug('patch: clearScreen: failed to find app:redraw callback'); | ||
| return null; | ||
| } | ||
|
|
||
| const delimiter = redrawMatch[1]; | ||
| const mapVar = redrawMatch[3]; | ||
| const redrawReplacement = | ||
| `${delimiter}globalThis.__tweakccForceRedraw=()=>${mapVar}.get(process.stdout)?.forceRedraw();` + | ||
| redrawMatch[0].slice(1); | ||
|
|
||
| let file = | ||
| oldFile.slice(0, redrawMatch.index) + | ||
| redrawReplacement + | ||
| oldFile.slice(redrawMatch.index + redrawMatch[0].length); | ||
|
|
||
| showDiff( | ||
| oldFile, | ||
| file, | ||
| redrawReplacement, | ||
| redrawMatch.index, | ||
| redrawMatch.index + redrawMatch[0].length | ||
| ); | ||
|
|
||
| const renderFilterResult = patchRenderFilter(file); | ||
| if (!renderFilterResult) { | ||
| debug('patch: clearScreen: failed to patch render filter g97'); | ||
| return null; | ||
| } | ||
| file = renderFilterResult; | ||
|
|
||
| const commandDef = | ||
| ',{type:"local",name:"clear-screen",' + | ||
| 'description:"Clear screen without resetting conversation context",' + | ||
| 'supportsNonInteractive:!1,' + | ||
| 'load:()=>Promise.resolve().then(()=>({call:(H,$)=>{' + | ||
| '$.setMessages(m=>{' + | ||
| 'globalThis.__tweakccHiddenUUIDs=new Set(m.map(x=>x.uuid?.slice(0,24)).filter(Boolean));' + | ||
| 'return[...m]});' + | ||
| 'process.stdout.write("\\x1b[2J\\x1b[H\\x1b[3J");' + | ||
| 'globalThis.__tweakccForceRedraw?.();' + | ||
| 'return{type:"skip"}}}))}'; | ||
|
|
||
| const result = writeSlashCommandDefinition(file, commandDef); | ||
| if (!result) { | ||
| debug('patch: clearScreen: failed to register slash command'); | ||
| return null; | ||
| } | ||
|
|
||
| return result; | ||
| }; | ||
|
|
||
| export const patchRenderFilter = (oldFile: string): string | null => { | ||
| const pattern = | ||
| /([,;{}])(function [$\w]+\(([$\w]+),[$\w]+\)\{)if\(\3\.type!=="user"\)return!0;if\(\3\.isMeta\)/; | ||
| const match = oldFile.match(pattern); | ||
| if (!match || match.index === undefined) { | ||
| return null; | ||
| } | ||
|
|
||
| const delimiter = match[1]; | ||
| const funcPrefix = match[2]; | ||
| const firstArg = match[3]; | ||
|
|
||
| const replacement = | ||
| `${delimiter}${funcPrefix}if(globalThis.__tweakccHiddenUUIDs?.has(${firstArg}.uuid?.slice(0,24)))return!1;` + | ||
| match[0].slice(delimiter.length + funcPrefix.length); | ||
|
|
||
| const result = | ||
| oldFile.slice(0, match.index) + | ||
| replacement + | ||
| oldFile.slice(match.index + match[0].length); | ||
|
|
||
| showDiff( | ||
| oldFile, | ||
| result, | ||
| replacement, | ||
| match.index, | ||
| match.index + match[0].length | ||
| ); | ||
|
|
||
| return result; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.