@openchambery/web 1.18.4-beta.9 → 1.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AssistantView-CbCe7NJG.js → AssistantView-DM2dKfoR.js} +1 -1
- package/dist/assets/{ChatContainer-CSHDg3oS.js → ChatContainer-BVPeKR-K.js} +42 -42
- package/dist/assets/{DiagramView-DUciKofo.js → DiagramView-Cmf-1RrR.js} +1 -1
- package/dist/assets/{MarkdownRendererImpl-BjAOpViW.js → MarkdownRendererImpl-TCIXUwWr.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-EXZlPK6E.js → MobileDetailNavigation-BVwAAhoU.js} +1 -1
- package/dist/assets/{MobileShareBridge-CevrYlt3.js → MobileShareBridge-2J_Tfuji.js} +1 -1
- package/dist/assets/MobileSurface-CDiflo1b.js +2 -0
- package/dist/assets/{MultiRunWindow-CNWuOY4y.js → MultiRunWindow-UffGnrG2.js} +1 -1
- package/dist/assets/{OnboardingScreen-BhSzVmYo.js → OnboardingScreen-Cx79VlTN.js} +1 -1
- package/dist/assets/{SettingsView-YyQ-C-qq.js → SettingsView-Da1ATyc8.js} +6 -6
- package/dist/assets/{SettingsWindow-uUezVVwi.js → SettingsWindow-nVdVW9KM.js} +1 -1
- package/dist/assets/{TerminalView-DPto-DoO.js → TerminalView-BTY7HznA.js} +1 -1
- package/dist/assets/{ToolOutputDialog-CgPYRmDX.js → ToolOutputDialog-BJfWelnd.js} +1 -1
- package/dist/assets/appThemeRegistry-BQGUOtTV.js +2 -0
- package/dist/assets/{clipboard-DhOMUCwT.js → clipboard-BfgFef1i.js} +1 -1
- package/dist/assets/{context-menu-BPxTMlbo.js → context-menu-BwxsmiaI.js} +1 -1
- package/dist/assets/{dialog-BoAAoL5Z.js → dialog-GR7KoP1o.js} +1 -1
- package/dist/assets/{es-BXQb1dPR.js → es-Bi4m8Mm6.js} +1 -1
- package/dist/assets/{fr-BTyDIvxw.js → fr-B5UdepyS.js} +1 -1
- package/dist/assets/{gitApi-BktvSPdR.js → gitApi-BfOQScwC.js} +3 -3
- package/dist/assets/insertionBoundaries-Gk_rVlpX.js +1 -0
- package/dist/assets/{ja-fcQgtBFy.js → ja-KtFuRjvm.js} +1 -1
- package/dist/assets/{ko-CgoVt1eb.js → ko-CWpFVSCH.js} +1 -1
- package/dist/assets/{main-UJICKUVV.js → main-7rfc5WXp.js} +15 -15
- package/dist/assets/{main-DOGudNxM.js → main-CTzdOaGn.js} +2 -2
- package/dist/assets/{miniChat-C465ME90.js → miniChat-CAbceO-F.js} +2 -2
- package/dist/assets/{mobile-BHRw1m5c.js → mobile-BX6ehEeM.js} +2 -2
- package/dist/assets/multirun-C2J5Yp8V.js +4 -0
- package/dist/assets/{pl-DVDnk2fT.js → pl-30X8TD5i.js} +1 -1
- package/dist/assets/projectMeta-mqvgqR8a.js +3 -0
- package/dist/assets/{pt-BR-ofV_Qcd_.js → pt-BR-Cm-Klf0E.js} +1 -1
- package/dist/assets/{radio-CS9DITGQ.js → radio-nnW-6V1r.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-C2Ya6FdB.js +2 -0
- package/dist/assets/renderMobileApp-BgDGLev_.js +23 -0
- package/dist/assets/{runtimeConfig-Dofg9S27.js → runtimeConfig-CCrS3B1v.js} +1 -1
- package/dist/assets/runtimeEndpointReset-C1NE1NgZ.js +147 -0
- package/dist/assets/{sortable-tabs-strip-N3j5So4j.js → sortable-tabs-strip-BYmWSdS9.js} +1 -1
- package/dist/assets/{store-Hr-xvtDJ.js → store-Z1MxUaP4.js} +2 -2
- package/dist/assets/{uk-Dzob0yMx.js → uk-Cgc5wXrs.js} +1 -1
- package/dist/assets/useAppFontEffects-BoCsqVCV.js +7 -0
- package/dist/assets/{useDesktopWindowControlsLayout-CGYSjr_T.js → useDesktopWindowControlsLayout-C7JocspE.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-BtkZPQZ2.js → useEffectiveDirectory-DMWATHLS.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-BNnnW08z.js → useMobileNavigationStore-CLiqmN1d.js} +15 -15
- package/dist/assets/useSessionAutoCleanup-BPKBXJg4.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-Bo9meAHp.js → useWorkerHighlightedLines-NddS5-lr.js} +1 -1
- package/dist/assets/{zh-CN-Sndj2nhS.js → zh-CN-BdRwDJuj.js} +1 -1
- package/dist/assets/{zh-TW-S24a8EMD.js → zh-TW-C3LubKzZ.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/mini-chat.html +2 -2
- package/dist/mobile.html +2 -2
- package/package.json +1 -1
- package/server/lib/session-assist/DOCUMENTATION.md +4 -2
- package/server/lib/session-assist/runtime.js +67 -30
- package/server/lib/session-assist/runtime.test.js +114 -0
- package/dist/assets/MobileSurface-cYYd428z.js +0 -2
- package/dist/assets/appThemeRegistry-CPVFQqR9.js +0 -2
- package/dist/assets/insertionBoundaries-Cqk1aLwK.js +0 -1
- package/dist/assets/multirun-BCvIqAk-.js +0 -4
- package/dist/assets/projectMeta-Wxow1DQz.js +0 -3
- package/dist/assets/renderElectronMiniChatApp-DAIgdBk9.js +0 -2
- package/dist/assets/renderMobileApp-BXOtgQHy.js +0 -23
- package/dist/assets/runtimeEndpointReset-Cc3yypUQ.js +0 -147
- package/dist/assets/useAppFontEffects-BpT_sNyZ.js +0 -7
- package/dist/assets/useSessionAutoCleanup-C5V-GCiC.js +0 -1
package/dist/index.html
CHANGED
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-CTzdOaGn.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
@@ -569,7 +569,7 @@
|
|
|
569
569
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-KDUc5mh2.js">
|
|
570
570
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
571
571
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
572
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
572
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CCrS3B1v.js">
|
|
573
573
|
<link rel="stylesheet" crossorigin href="/assets/src-Dbdlm5x1.css">
|
|
574
574
|
</head>
|
|
575
575
|
<body class="h-full bg-background text-foreground">
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-CAbceO-F.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-KDUc5mh2.js">
|
|
92
92
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
93
93
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
94
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
94
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CCrS3B1v.js">
|
|
95
95
|
<link rel="stylesheet" crossorigin href="/assets/src-Dbdlm5x1.css">
|
|
96
96
|
</head>
|
|
97
97
|
<body class="h-full bg-background text-foreground">
|
package/dist/mobile.html
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-BX6ehEeM.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-KDUc5mh2.js">
|
|
81
81
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
82
82
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
83
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
83
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-CCrS3B1v.js">
|
|
84
84
|
<link rel="stylesheet" crossorigin href="/assets/src-Dbdlm5x1.css">
|
|
85
85
|
</head>
|
|
86
86
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
|
@@ -14,8 +14,10 @@ under `metadata.openchamber.assist`.
|
|
|
14
14
|
`retry` status or a user `message.updated` clears it (the "1 minute of
|
|
15
15
|
quiet" rule).
|
|
16
16
|
3. On fire: fetch the session (skip sub-agent sessions with `parentID`),
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
then build a trimmed excerpt via `buildAssistTranscript`: the LAST text
|
|
18
|
+
block of the last assistant message (earlier blocks, reasoning, and tool
|
|
19
|
+
output are excluded) plus the latest 3 real user messages (synthetic
|
|
20
|
+
hidden-instruction parts never count). The excerpt is sent to
|
|
19
21
|
`generateSmallModelText` with the
|
|
20
22
|
session's own provider/model taken from the last assistant message — so
|
|
21
23
|
the utility call spends the same subscription as the conversation.
|
|
@@ -33,6 +33,8 @@ const isSessionRecapEnabled = () => {
|
|
|
33
33
|
|
|
34
34
|
const IDLE_QUIET_MS = 60_000;
|
|
35
35
|
const TRANSCRIPT_MESSAGE_LIMIT = 12;
|
|
36
|
+
/** Latest real user messages fed to the recap prompt. */
|
|
37
|
+
const TRANSCRIPT_USER_MESSAGE_LIMIT = 3;
|
|
36
38
|
const TRANSCRIPT_PART_CHAR_LIMIT = 6_000;
|
|
37
39
|
const RECAP_CHAR_LIMIT = 320;
|
|
38
40
|
const FETCH_TIMEOUT_MS = 5_000;
|
|
@@ -92,13 +94,63 @@ const extractUserMessage = (payload) => {
|
|
|
92
94
|
};
|
|
93
95
|
};
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
/** Real (non-synthetic) text parts of a message — hidden instructions excluded. */
|
|
98
|
+
const realTextParts = (message) => {
|
|
96
99
|
const parts = Array.isArray(message?.parts) ? message.parts : [];
|
|
97
100
|
return parts
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
.filter((part) => part?.type === 'text'
|
|
102
|
+
&& typeof part.text === 'string'
|
|
103
|
+
&& part.text.trim().length > 0
|
|
104
|
+
&& part.synthetic !== true)
|
|
105
|
+
.map((part) => part.text);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Body text of a message: only its LAST text block, char-clamped. */
|
|
109
|
+
const lastMessageText = (message) => {
|
|
110
|
+
const texts = realTextParts(message);
|
|
111
|
+
return texts.length > 0 ? texts[texts.length - 1].slice(0, TRANSCRIPT_PART_CHAR_LIMIT) : '';
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Build the recap transcript from a bounded recent-message window.
|
|
116
|
+
* - Assistant: only the LAST text block of the last assistant message —
|
|
117
|
+
* earlier blocks, reasoning, and tool output are token waste for a
|
|
118
|
+
* one-line recap.
|
|
119
|
+
* - User: the latest real user messages (default 3), chronological.
|
|
120
|
+
*/
|
|
121
|
+
export const buildAssistTranscript = (
|
|
122
|
+
messages,
|
|
123
|
+
{ userLimit = TRANSCRIPT_USER_MESSAGE_LIMIT } = {},
|
|
124
|
+
) => {
|
|
125
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
126
|
+
return { transcript: '', assistantInfo: null, userTexts: [], assistantText: '' };
|
|
127
|
+
}
|
|
128
|
+
let lastAssistant = null;
|
|
129
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
130
|
+
if (messages[i]?.info?.role === 'assistant') {
|
|
131
|
+
lastAssistant = messages[i];
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const lastAssistantInfo = lastAssistant?.info ?? null;
|
|
136
|
+
if (!lastAssistantInfo?.id) {
|
|
137
|
+
return { transcript: '', assistantInfo: null, userTexts: [], assistantText: '' };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const userTexts = [];
|
|
141
|
+
for (let i = messages.length - 1; i >= 0 && userTexts.length < userLimit; i -= 1) {
|
|
142
|
+
const message = messages[i];
|
|
143
|
+
if (message?.info?.role !== 'user') continue;
|
|
144
|
+
const text = lastMessageText(message);
|
|
145
|
+
if (!text) continue;
|
|
146
|
+
userTexts.unshift(text);
|
|
147
|
+
}
|
|
148
|
+
const assistantText = lastMessageText(lastAssistant);
|
|
149
|
+
const transcript = [
|
|
150
|
+
...userTexts.map((text) => `User:\n${text}`),
|
|
151
|
+
assistantText ? `Assistant:\n${assistantText}` : '',
|
|
152
|
+
].filter(Boolean).join('\n\n');
|
|
153
|
+
return { transcript, assistantInfo: lastAssistantInfo, userTexts, assistantText };
|
|
102
154
|
};
|
|
103
155
|
|
|
104
156
|
export const createSessionAssistRuntime = ({
|
|
@@ -169,36 +221,21 @@ export const createSessionAssistRuntime = ({
|
|
|
169
221
|
return;
|
|
170
222
|
}
|
|
171
223
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
lastAssistant = messages[i];
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
const lastAssistantInfo = lastAssistant?.info;
|
|
224
|
+
// Trimmed extraction for a one-line recap: the last assistant text block
|
|
225
|
+
// plus the latest real user messages. Everything else (earlier blocks,
|
|
226
|
+
// reasoning, tool output, older turns) is token waste.
|
|
227
|
+
const { transcript, assistantInfo: lastAssistantInfo, userTexts, assistantText } = buildAssistTranscript(messages);
|
|
181
228
|
if (!lastAssistantInfo?.id) return;
|
|
182
|
-
|
|
183
|
-
// Only the last exchange: the assistant reply plus the user message it
|
|
184
|
-
// answered (assistant info.parentID → user info.id). Everything else is
|
|
185
|
-
// token waste for a one-line recap.
|
|
186
|
-
const parentUserMessage = typeof lastAssistantInfo.parentID === 'string' && lastAssistantInfo.parentID
|
|
187
|
-
? messages.find((message) => message?.info?.id === lastAssistantInfo.parentID && message?.info?.role === 'user')
|
|
188
|
-
: null;
|
|
189
|
-
const userText = parentUserMessage ? messagePartsToText(parentUserMessage) : '';
|
|
190
|
-
const assistantText = messagePartsToText(lastAssistant);
|
|
191
|
-
const transcript = [
|
|
192
|
-
userText ? `User:\n${userText}` : '',
|
|
193
|
-
assistantText ? `Assistant:\n${assistantText}` : '',
|
|
194
|
-
].filter(Boolean).join('\n\n');
|
|
195
229
|
if (!transcript) return;
|
|
196
230
|
|
|
197
231
|
const { generateSmallModelText } = await getSmallModelService();
|
|
198
232
|
// Instruct the language by example, not by description — account-side
|
|
199
233
|
// personalization (e.g. the ChatGPT backend knowing the user's locale)
|
|
200
234
|
// otherwise leaks a different language into the output.
|
|
201
|
-
const languageSample = (
|
|
235
|
+
const languageSample = (userTexts[userTexts.length - 1] || assistantText)
|
|
236
|
+
.slice(0, 200)
|
|
237
|
+
.replace(/\s+/g, ' ')
|
|
238
|
+
.trim();
|
|
202
239
|
let generated;
|
|
203
240
|
try {
|
|
204
241
|
generated = await generateSmallModelText({
|
|
@@ -206,7 +243,7 @@ export const createSessionAssistRuntime = ({
|
|
|
206
243
|
// session's own provider unless the user explicitly picked a small
|
|
207
244
|
// model (settings override / opencode config).
|
|
208
245
|
restrictToPreferredProvider: true,
|
|
209
|
-
prompt: `The latest
|
|
246
|
+
prompt: `The latest conversation excerpt:\n\n${transcript}\n\nWrite recap in the SAME language as this sample from the conversation: "${languageSample}"`,
|
|
210
247
|
system: ASSIST_SYSTEM_PROMPT,
|
|
211
248
|
directory,
|
|
212
249
|
preferredProviderID: typeof lastAssistantInfo.providerID === 'string' ? lastAssistantInfo.providerID : undefined,
|
|
@@ -228,7 +265,7 @@ export const createSessionAssistRuntime = ({
|
|
|
228
265
|
// no Cyrillic/CJK at all, the output must not either.
|
|
229
266
|
const hasCyrillic = (text) => /[\u0400-\u04FF]/.test(text);
|
|
230
267
|
const hasCjk = (text) => /[\u3040-\u30FF\u4E00-\u9FFF\uAC00-\uD7AF]/.test(text);
|
|
231
|
-
const inputText = `${
|
|
268
|
+
const inputText = `${userTexts.join('\n')}\n${assistantText}`;
|
|
232
269
|
const scriptMismatch = (text) => (hasCyrillic(text) && !hasCyrillic(inputText))
|
|
233
270
|
|| (hasCjk(text) && !hasCjk(inputText));
|
|
234
271
|
if (recap && scriptMismatch(recap)) {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { buildAssistTranscript } from './runtime.js';
|
|
3
|
+
|
|
4
|
+
const textPart = (text, { synthetic = false } = {}) => ({
|
|
5
|
+
type: 'text',
|
|
6
|
+
text,
|
|
7
|
+
...(synthetic ? { synthetic: true } : {}),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const userMessage = (id, parts) => ({ info: { id, role: 'user' }, parts });
|
|
11
|
+
const assistantMessage = (id, parts) => ({ info: { id, role: 'assistant' }, parts });
|
|
12
|
+
|
|
13
|
+
describe('buildAssistTranscript', () => {
|
|
14
|
+
test('assistant keeps only the LAST text block of the last assistant message', () => {
|
|
15
|
+
const result = buildAssistTranscript([
|
|
16
|
+
userMessage('u1', [textPart('first user message')]),
|
|
17
|
+
assistantMessage('a1', [textPart('old assistant reply')]),
|
|
18
|
+
userMessage('u2', [textPart('second user message')]),
|
|
19
|
+
assistantMessage('a2', [
|
|
20
|
+
textPart('intro before tools'),
|
|
21
|
+
{ type: 'tool', tool: 'bash' },
|
|
22
|
+
textPart('final body text'),
|
|
23
|
+
]),
|
|
24
|
+
]);
|
|
25
|
+
expect(result.assistantInfo.id).toBe('a2');
|
|
26
|
+
expect(result.assistantText).toBe('final body text');
|
|
27
|
+
expect(result.transcript).toBe(
|
|
28
|
+
'User:\nfirst user message\n\nUser:\nsecond user message\n\nAssistant:\nfinal body text',
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('reasoning parts never enter the transcript', () => {
|
|
33
|
+
const result = buildAssistTranscript([
|
|
34
|
+
userMessage('u1', [textPart('请帮我看看')]),
|
|
35
|
+
assistantMessage('a1', [
|
|
36
|
+
{ type: 'reasoning', text: 'thinking about the problem' },
|
|
37
|
+
textPart('最终答复'),
|
|
38
|
+
]),
|
|
39
|
+
]);
|
|
40
|
+
expect(result.transcript).toBe('User:\n请帮我看看\n\nAssistant:\n最终答复');
|
|
41
|
+
expect(result.transcript).not.toContain('thinking');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('user side keeps only the latest 3 real user messages, chronologically', () => {
|
|
45
|
+
const result = buildAssistTranscript([
|
|
46
|
+
userMessage('u1', [textPart('question 1')]),
|
|
47
|
+
assistantMessage('a1', [textPart('answer 1')]),
|
|
48
|
+
userMessage('u2', [textPart('question 2')]),
|
|
49
|
+
assistantMessage('a2', [textPart('answer 2')]),
|
|
50
|
+
userMessage('u3', [textPart('question 3')]),
|
|
51
|
+
userMessage('u4', [textPart('question 4')]),
|
|
52
|
+
assistantMessage('a3', [textPart('answer 3')]),
|
|
53
|
+
]);
|
|
54
|
+
expect(result.userTexts).toEqual(['question 2', 'question 3', 'question 4']);
|
|
55
|
+
expect(result.transcript).toBe(
|
|
56
|
+
'User:\nquestion 2\n\nUser:\nquestion 3\n\nUser:\nquestion 4\n\nAssistant:\nanswer 3',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('synthetic-only user messages do not count toward the user window', () => {
|
|
61
|
+
const result = buildAssistTranscript([
|
|
62
|
+
userMessage('u1', [textPart('visible request')]),
|
|
63
|
+
userMessage('u2', [textPart('hidden instruction', { synthetic: true })]),
|
|
64
|
+
userMessage('u3', [textPart('real follow-up')]),
|
|
65
|
+
assistantMessage('a1', [textPart('reply body')]),
|
|
66
|
+
]);
|
|
67
|
+
expect(result.userTexts).toEqual(['visible request', 'real follow-up']);
|
|
68
|
+
expect(result.transcript).not.toContain('hidden instruction');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('synthetic instruction part on a real user message is dropped', () => {
|
|
72
|
+
const result = buildAssistTranscript([
|
|
73
|
+
userMessage('u1', [
|
|
74
|
+
textPart('visible request'),
|
|
75
|
+
textPart('/summary hidden instructions', { synthetic: true }),
|
|
76
|
+
]),
|
|
77
|
+
assistantMessage('a1', [textPart('reply body')]),
|
|
78
|
+
]);
|
|
79
|
+
expect(result.userTexts).toEqual(['visible request']);
|
|
80
|
+
expect(result.transcript).not.toContain('/summary hidden instructions');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('fewer than 3 user messages keeps all of them', () => {
|
|
84
|
+
const result = buildAssistTranscript([
|
|
85
|
+
userMessage('u1', [textPart('only question')]),
|
|
86
|
+
assistantMessage('a1', [textPart('only answer')]),
|
|
87
|
+
]);
|
|
88
|
+
expect(result.userTexts).toEqual(['only question']);
|
|
89
|
+
expect(result.transcript).toBe('User:\nonly question\n\nAssistant:\nonly answer');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('no assistant message yields an empty transcript', () => {
|
|
93
|
+
const result = buildAssistTranscript([
|
|
94
|
+
userMessage('u1', [textPart('unanswered question')]),
|
|
95
|
+
]);
|
|
96
|
+
expect(result.transcript).toBe('');
|
|
97
|
+
expect(result.assistantInfo).toBeNull();
|
|
98
|
+
expect(result.userTexts).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('assistant with no text parts still summarizes the user side', () => {
|
|
102
|
+
const result = buildAssistTranscript([
|
|
103
|
+
userMessage('u1', [textPart('question')]),
|
|
104
|
+
assistantMessage('a1', [{ type: 'tool', tool: 'bash' }]),
|
|
105
|
+
]);
|
|
106
|
+
expect(result.assistantInfo.id).toBe('a1');
|
|
107
|
+
expect(result.transcript).toBe('User:\nquestion');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('empty input yields an empty transcript', () => {
|
|
111
|
+
expect(buildAssistTranscript([]).transcript).toBe('');
|
|
112
|
+
expect(buildAssistTranscript(null).transcript).toBe('');
|
|
113
|
+
});
|
|
114
|
+
});
|