Skip to content

Commit 644621a

Browse files
committed
Persist session color to transcript for resume support
Patch saveAgentColor to expose it via globalThis, then call it from the INJECTION via queueMicrotask so the env-based color is written to the session transcript. On resume, computeStandaloneAgentContext reads agentColor from the log and restores the prompt bar color automatically.
1 parent 46f3e6a commit 644621a

2 files changed

Lines changed: 101 additions & 11 deletions

File tree

src/patches/sessionColor.test.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { describe, it, expect } from 'vitest';
2-
import { writeSessionColor } from './sessionColor';
2+
import { writeSessionColor, patchSaveAgentColor } from './sessionColor';
3+
4+
const makeSaveAgentColor = () =>
5+
';async function Mr$(H,$,q){let K=q??sT(H);' +
6+
'if(Hv(K,{type:"agent-color",agentColor:$,sessionId:H}),H===V$())' +
7+
'WA().currentSessionAgentColor=$;c("tengu_agent_color_set",{})}';
38

49
const makeCLIState = () =>
510
'effortValue:oR(w.effort),' +
@@ -12,37 +17,41 @@ const makeDefaultState = () =>
1217
const makeBoth = () =>
1318
'first{' + makeDefaultState() + 'second{' + makeCLIState();
1419

20+
const makeFullFile = () => makeBoth() + makeSaveAgentColor();
21+
1522
describe('sessionColor', () => {
1623
describe('writeSessionColor', () => {
17-
it('should inject into CLI initialState', () => {
18-
const result = writeSessionColor(makeCLIState());
24+
it('should inject into CLI initialState and patch saveAgentColor', () => {
25+
const result = writeSessionColor(makeFullFile());
1926
expect(result).not.toBeNull();
2027
expect(result).toContain('TWEAKCC_SESSION_COLOR');
2128
expect(result).toContain('{name:"",color:__c}');
29+
expect(result).toContain('__tweakccSaveAgentColor');
2230
});
2331

2432
it('should inject into default app state', () => {
25-
const result = writeSessionColor(makeDefaultState());
33+
const input = makeDefaultState() + makeSaveAgentColor();
34+
const result = writeSessionColor(input);
2635
expect(result).not.toBeNull();
2736
expect(result).toContain('TWEAKCC_SESSION_COLOR');
2837
});
2938

30-
it('should patch both locations when both present', () => {
31-
const result = writeSessionColor(makeBoth())!;
39+
it('should patch both initialState locations', () => {
40+
const result = writeSessionColor(makeFullFile())!;
3241
expect(result).not.toBeNull();
3342
const count = (result.match(/TWEAKCC_SESSION_COLOR/g) || []).length;
3443
expect(count).toBe(2);
3544
});
3645

3746
it('should validate color against allowed list', () => {
38-
const result = writeSessionColor(makeCLIState())!;
47+
const result = writeSessionColor(makeFullFile())!;
3948
expect(result).toContain('.includes(__c)');
4049
expect(result).toContain('"green"');
4150
expect(result).toContain('"cyan"');
4251
});
4352

4453
it('should be idempotent', () => {
45-
const first = writeSessionColor(makeBoth())!;
54+
const first = writeSessionColor(makeFullFile())!;
4655
const second = writeSessionColor(first)!;
4756
expect(second).toBe(first);
4857
});
@@ -51,5 +60,37 @@ describe('sessionColor', () => {
5160
const result = writeSessionColor('not a valid file');
5261
expect(result).toBeNull();
5362
});
63+
64+
it('should schedule color save via queueMicrotask', () => {
65+
const result = writeSessionColor(makeFullFile())!;
66+
expect(result).toContain('queueMicrotask');
67+
expect(result).toContain('__tweakccSaveAgentColor');
68+
});
69+
70+
it('should expose saveAgentColor on globalThis', () => {
71+
const result = writeSessionColor(makeFullFile())!;
72+
expect(result).toContain(
73+
'globalThis.__tweakccSaveAgentColor=(c)=>Mr$(V$(),c)'
74+
);
75+
});
76+
});
77+
78+
describe('patchSaveAgentColor', () => {
79+
it('should find and patch saveAgentColor', () => {
80+
const result = patchSaveAgentColor(makeSaveAgentColor());
81+
expect(result).not.toBeNull();
82+
expect(result).toContain('globalThis.__tweakccSaveAgentColor');
83+
});
84+
85+
it('should return null when pattern not found', () => {
86+
const result = patchSaveAgentColor('no match here');
87+
expect(result).toBeNull();
88+
});
89+
90+
it('should preserve original function', () => {
91+
const result = patchSaveAgentColor(makeSaveAgentColor())!;
92+
expect(result).toContain('async function Mr$');
93+
expect(result).toContain('type:"agent-color"');
94+
});
5495
});
5596
});

src/patches/sessionColor.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ const INJECTION =
1616
`,standaloneAgentContext:` +
1717
`(()=>{` +
1818
`let __c=process.env.TWEAKCC_SESSION_COLOR;` +
19-
`return __c&&${JSON.stringify(VALID_COLORS)}.includes(__c)` +
20-
`?{name:"",color:__c}` +
21-
`:void 0` +
19+
`if(!__c||!${JSON.stringify(VALID_COLORS)}.includes(__c))return void 0;` +
20+
`queueMicrotask(()=>{` +
21+
`if(globalThis.__tweakccSaveAgentColor)globalThis.__tweakccSaveAgentColor(__c)` +
22+
`});` +
23+
`return{name:"",color:__c}` +
2224
`})()`;
2325

2426
export const writeSessionColor = (oldFile: string): string | null => {
@@ -64,5 +66,52 @@ export const writeSessionColor = (oldFile: string): string | null => {
6466
return null;
6567
}
6668

69+
const saveColorResult = patchSaveAgentColor(result);
70+
if (!saveColorResult) {
71+
debug('patch: sessionColor: failed to patch saveAgentColor');
72+
return null;
73+
}
74+
75+
return saveColorResult;
76+
};
77+
78+
export const patchSaveAgentColor = (oldFile: string): string | null => {
79+
const pattern = new RegExp(
80+
'([,;{}])' +
81+
'(async function ([$\\w]+)' +
82+
'\\(([$\\w]+),([$\\w]+),([$\\w]+)\\)' +
83+
'\\{let [$\\w]+=\\6\\?\\?[$\\w]+\\(\\4\\);' +
84+
'if\\([$\\w]+\\([$\\w]+,' +
85+
'\\{type:"agent-color",agentColor:\\5,sessionId:\\4\\}\\),' +
86+
'\\4===([$\\w]+)\\(\\)\\))'
87+
);
88+
const match = oldFile.match(pattern);
89+
if (!match || match.index === undefined) {
90+
return null;
91+
}
92+
93+
const delimiter = match[1];
94+
const funcBody = match[2];
95+
const funcName = match[3];
96+
const getSessionIdName = match[7];
97+
98+
const replacement =
99+
`${delimiter}globalThis.__tweakccSaveAgentColor=` +
100+
`(c)=>${funcName}(${getSessionIdName}(),c);` +
101+
funcBody;
102+
103+
const result =
104+
oldFile.slice(0, match.index) +
105+
replacement +
106+
oldFile.slice(match.index + match[0].length);
107+
108+
showDiff(
109+
oldFile,
110+
result,
111+
replacement,
112+
match.index,
113+
match.index + match[0].length
114+
);
115+
67116
return result;
68117
};

0 commit comments

Comments
 (0)