@kafca/agentdock 0.1.10 → 0.1.11
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/renderer/assets/index-BM0Jl5gB.js +487 -0
- package/dist/renderer/index.html +1 -1
- package/dist-electron/electron/local-core-refactor.test.js +88 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +7 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +39 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +34 -0
- package/dist-electron/src/pages/Threads/thread-chat-model.js +18 -25
- package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +49 -89
- package/dist-electron/src/pages/Threads/thread-chat-task-state.js +0 -39
- package/package.json +1 -1
- package/dist/renderer/assets/index-B67E3Ah_.js +0 -487
package/dist/renderer/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>AgentDock</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-BM0Jl5gB.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="./assets/index-DrLbJPYN.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body class="bg-[#f5f5f7] dark:bg-[#151516]">
|
|
@@ -14,6 +14,7 @@ const scheduler_run_lifecycle_js_1 = require("../services/local-ai-core/src/sche
|
|
|
14
14
|
const lark_execution_policies_js_1 = require("../services/local-ai-core/src/scheduler/lark-execution-policies.js");
|
|
15
15
|
const local_core_weixin_gateway_js_1 = require("../services/local-ai-core/src/gateway/local-core-weixin-gateway.js");
|
|
16
16
|
const local_core_acp_turn_coordinator_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js");
|
|
17
|
+
const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-store.js");
|
|
17
18
|
(0, node_test_1.default)('ACP tool call update is emitted with its pending tool name', () => {
|
|
18
19
|
const appended = [];
|
|
19
20
|
const emitted = [];
|
|
@@ -161,6 +162,93 @@ const local_core_acp_turn_coordinator_js_1 = require("../services/local-ai-core/
|
|
|
161
162
|
strict_1.default.equal(emitted[0]?.type, 'reply');
|
|
162
163
|
strict_1.default.equal(emitted[0]?.content, appended[0]?.content);
|
|
163
164
|
});
|
|
165
|
+
(0, node_test_1.default)('ACP thought chunks are streamed as thinking preview updates', () => {
|
|
166
|
+
const appended = [];
|
|
167
|
+
const upserted = [];
|
|
168
|
+
const emitted = [];
|
|
169
|
+
const coordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
|
|
170
|
+
appendMessage: (_threadId, _role, content, kind) => appended.push({ content, kind }),
|
|
171
|
+
upsertMessage: (_threadId, id, _role, content, kind) => upserted.push({ id, content, kind }),
|
|
172
|
+
emitBridge: (event) => emitted.push(event),
|
|
173
|
+
updateRunStatus: () => { },
|
|
174
|
+
sendRaw: () => true,
|
|
175
|
+
});
|
|
176
|
+
const session = {
|
|
177
|
+
threadId: 'thread-1',
|
|
178
|
+
bridgeSessionKey: 'session:thread-1',
|
|
179
|
+
currentRunId: 'run-1',
|
|
180
|
+
currentTurn: {
|
|
181
|
+
runId: 'run-1',
|
|
182
|
+
replyCtx: 'run-1',
|
|
183
|
+
previewHandle: 'preview-1',
|
|
184
|
+
thoughtPreviewHandle: 'thought-preview-1',
|
|
185
|
+
thoughtMessageId: 'run-1-thought',
|
|
186
|
+
assistantText: '',
|
|
187
|
+
thoughtText: '',
|
|
188
|
+
typingStarted: true,
|
|
189
|
+
previewStarted: false,
|
|
190
|
+
thoughtPreviewStarted: false,
|
|
191
|
+
permission: null,
|
|
192
|
+
},
|
|
193
|
+
loadReplayMode: false,
|
|
194
|
+
schedulerJobCreatedByRun: new Map(),
|
|
195
|
+
};
|
|
196
|
+
coordinator.handleAgentNotification(session, {
|
|
197
|
+
method: 'session/update',
|
|
198
|
+
params: {
|
|
199
|
+
update: {
|
|
200
|
+
sessionUpdate: 'agent_thought_chunk',
|
|
201
|
+
content: { type: 'text', text: '先理解问题' },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
coordinator.handleAgentNotification(session, {
|
|
206
|
+
method: 'session/update',
|
|
207
|
+
params: {
|
|
208
|
+
update: {
|
|
209
|
+
sessionUpdate: 'agent_thought_chunk',
|
|
210
|
+
content: { type: 'text', text: ',再检查代码' },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
strict_1.default.deepEqual(appended, []);
|
|
215
|
+
strict_1.default.deepEqual(upserted, [
|
|
216
|
+
{
|
|
217
|
+
id: 'run-1-thought',
|
|
218
|
+
content: '💭 先理解问题',
|
|
219
|
+
kind: 'progress',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'run-1-thought',
|
|
223
|
+
content: '💭 先理解问题,再检查代码',
|
|
224
|
+
kind: 'progress',
|
|
225
|
+
},
|
|
226
|
+
]);
|
|
227
|
+
strict_1.default.deepEqual(emitted.map((event) => event.type), ['preview_start', 'update_message']);
|
|
228
|
+
strict_1.default.equal(emitted[0]?.previewHandle, 'thought-preview-1');
|
|
229
|
+
strict_1.default.equal(emitted[0]?.content, '💭 先理解问题');
|
|
230
|
+
strict_1.default.equal(emitted[1]?.content, '💭 先理解问题,再检查代码');
|
|
231
|
+
strict_1.default.equal(session.currentTurn.thoughtText, '先理解问题,再检查代码');
|
|
232
|
+
});
|
|
233
|
+
(0, node_test_1.default)('ACP store upserts thought progress as one durable message', () => {
|
|
234
|
+
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'agentdock-thought-store-'));
|
|
235
|
+
const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
|
|
236
|
+
try {
|
|
237
|
+
const thread = store.createThread('project-1', 'Thread');
|
|
238
|
+
store.upsertMessage(thread.id, 'run-1-thought', 'assistant', '💭 先理解问题', 'progress');
|
|
239
|
+
store.upsertMessage(thread.id, 'run-1-thought', 'assistant', '💭 先理解问题,再检查代码', 'progress');
|
|
240
|
+
const detail = store.getThread(thread.id, []);
|
|
241
|
+
strict_1.default.equal(detail.messages.length, 1);
|
|
242
|
+
strict_1.default.equal(detail.messages[0]?.id, 'run-1-thought');
|
|
243
|
+
strict_1.default.equal(detail.messages[0]?.kind, 'progress');
|
|
244
|
+
strict_1.default.equal(detail.messages[0]?.content, '💭 先理解问题,再检查代码');
|
|
245
|
+
strict_1.default.equal(detail.historyCount, 1);
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
store.close();
|
|
249
|
+
(0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
164
252
|
(0, node_test_1.default)('ACP skips empty generic running tool updates', () => {
|
|
165
253
|
const appended = [];
|
|
166
254
|
const emitted = [];
|
|
@@ -27,6 +27,9 @@ class LocalCoreAcpBackend {
|
|
|
27
27
|
appendMessage: (threadId, role, content, kind) => {
|
|
28
28
|
this.options.store.appendMessage(threadId, role, content, kind);
|
|
29
29
|
},
|
|
30
|
+
upsertMessage: (threadId, id, role, content, kind) => {
|
|
31
|
+
this.options.store.upsertMessage(threadId, id, role, content, kind);
|
|
32
|
+
},
|
|
30
33
|
updateRunStatus: (runId, threadId, status) => {
|
|
31
34
|
this.options.store.updateRun(runId, threadId, status);
|
|
32
35
|
},
|
|
@@ -180,9 +183,13 @@ class LocalCoreAcpBackend {
|
|
|
180
183
|
runId,
|
|
181
184
|
replyCtx: runId,
|
|
182
185
|
previewHandle: (0, node_crypto_1.randomUUID)(),
|
|
186
|
+
thoughtPreviewHandle: (0, node_crypto_1.randomUUID)(),
|
|
187
|
+
thoughtMessageId: `${runId}-thought`,
|
|
183
188
|
assistantText: '',
|
|
189
|
+
thoughtText: '',
|
|
184
190
|
typingStarted: true,
|
|
185
191
|
previewStarted: false,
|
|
192
|
+
thoughtPreviewStarted: false,
|
|
186
193
|
pendingToolCallTitle: undefined,
|
|
187
194
|
permission: null,
|
|
188
195
|
};
|
|
@@ -244,6 +244,45 @@ class LocalCoreAcpStore {
|
|
|
244
244
|
}
|
|
245
245
|
return { id, timestamp };
|
|
246
246
|
}
|
|
247
|
+
upsertMessage(threadId, id, role, content, kind) {
|
|
248
|
+
const timestamp = new Date().toISOString();
|
|
249
|
+
const excerpt = (0, workspace_thread_mappers_js_1.normalizeMessageContent)(content);
|
|
250
|
+
const existing = this.db.prepare('SELECT id FROM messages WHERE id = ? AND thread_id = ?').get(id, threadId);
|
|
251
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
252
|
+
try {
|
|
253
|
+
if (existing) {
|
|
254
|
+
this.db.prepare(`
|
|
255
|
+
UPDATE messages
|
|
256
|
+
SET content = ?, timestamp = ?, kind = ?
|
|
257
|
+
WHERE id = ? AND thread_id = ?
|
|
258
|
+
`).run(content, timestamp, kind, id, threadId);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const nextSequenceRow = this.db.prepare('SELECT COALESCE(MAX(seq), -1) + 1 AS next_seq FROM messages WHERE thread_id = ?').get(threadId);
|
|
262
|
+
const nextSeq = Number(nextSequenceRow?.next_seq || 0);
|
|
263
|
+
this.db.prepare(`
|
|
264
|
+
INSERT INTO messages (id, thread_id, role, content, timestamp, kind, seq)
|
|
265
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
266
|
+
`).run(id, threadId, role, content, timestamp, kind, nextSeq);
|
|
267
|
+
this.db.prepare(`
|
|
268
|
+
UPDATE threads
|
|
269
|
+
SET history_count = history_count + 1
|
|
270
|
+
WHERE id = ?
|
|
271
|
+
`).run(threadId);
|
|
272
|
+
}
|
|
273
|
+
this.db.prepare(`
|
|
274
|
+
UPDATE threads
|
|
275
|
+
SET updated_at = ?, excerpt = ?
|
|
276
|
+
WHERE id = ?
|
|
277
|
+
`).run(timestamp, excerpt, threadId);
|
|
278
|
+
this.db.exec('COMMIT');
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
this.db.exec('ROLLBACK');
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
return { id, timestamp };
|
|
285
|
+
}
|
|
247
286
|
updateRun(runId, threadId, status) {
|
|
248
287
|
const now = new Date().toISOString();
|
|
249
288
|
const existing = this.db.prepare('SELECT id FROM runs WHERE id = ?').get(runId);
|
|
@@ -163,6 +163,40 @@ class LocalCoreAcpTurnCoordinator {
|
|
|
163
163
|
});
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
|
+
case 'agent_thought_chunk': {
|
|
167
|
+
this.flushPendingToolCall(session);
|
|
168
|
+
if (update.content?.type !== 'text') {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const thoughtChunk = String(update.content.text || '');
|
|
172
|
+
if (!thoughtChunk) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
currentTurn.thoughtText += thoughtChunk;
|
|
176
|
+
const content = `💭 ${currentTurn.thoughtText.trim()}`;
|
|
177
|
+
if (this.options.upsertMessage) {
|
|
178
|
+
this.options.upsertMessage(session.threadId, currentTurn.thoughtMessageId, 'assistant', content, 'progress');
|
|
179
|
+
}
|
|
180
|
+
if (!currentTurn.thoughtPreviewStarted) {
|
|
181
|
+
currentTurn.thoughtPreviewStarted = true;
|
|
182
|
+
this.options.emitBridge({
|
|
183
|
+
type: 'preview_start',
|
|
184
|
+
sessionKey: session.bridgeSessionKey,
|
|
185
|
+
replyCtx: currentRunId,
|
|
186
|
+
previewHandle: currentTurn.thoughtPreviewHandle,
|
|
187
|
+
content,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
this.options.emitBridge({
|
|
192
|
+
type: 'update_message',
|
|
193
|
+
sessionKey: session.bridgeSessionKey,
|
|
194
|
+
replyCtx: currentRunId,
|
|
195
|
+
previewHandle: currentTurn.thoughtPreviewHandle,
|
|
196
|
+
content,
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
166
200
|
case 'tool_call': {
|
|
167
201
|
const title = String(update.title || 'Running tool').trim();
|
|
168
202
|
this.flushPendingToolCall(session);
|
|
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.upsertSessionGroup = exports.ASSISTANT_REPLY_TIMEOUT_MS = void 0;
|
|
4
4
|
exports.isInternalProgressMessage = isInternalProgressMessage;
|
|
5
5
|
exports.isAwaitingInputMessage = isAwaitingInputMessage;
|
|
6
|
+
exports.findStreamingPreviewMessage = findStreamingPreviewMessage;
|
|
7
|
+
exports.shouldReplacePreviewWithReply = shouldReplacePreviewWithReply;
|
|
6
8
|
exports.extractVisibleMessageContent = extractVisibleMessageContent;
|
|
7
9
|
exports.sessionMatchesDesktop = sessionMatchesDesktop;
|
|
8
10
|
exports.toMessages = toMessages;
|
|
9
11
|
exports.toMessagesFromThread = toMessagesFromThread;
|
|
10
|
-
exports.mergePolledThreadMessages = mergePolledThreadMessages;
|
|
11
12
|
exports.toChatThreadSummary = toChatThreadSummary;
|
|
12
13
|
exports.toCoreChatThreadSummary = toCoreChatThreadSummary;
|
|
13
14
|
exports.sortChatThreadsByLiveAndUpdated = sortChatThreadsByLiveAndUpdated;
|
|
@@ -48,6 +49,22 @@ function isAwaitingInputMessage(content) {
|
|
|
48
49
|
normalized.includes('请直接回复') ||
|
|
49
50
|
normalized.includes('请输入你的回答'));
|
|
50
51
|
}
|
|
52
|
+
function findStreamingPreviewMessage(messages, previewHandle, replyCtx) {
|
|
53
|
+
if (previewHandle) {
|
|
54
|
+
return messages.find((message) => message.id === previewHandle);
|
|
55
|
+
}
|
|
56
|
+
if (!replyCtx) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
return messages.find((message) => message.preview && message.turnKey === replyCtx);
|
|
60
|
+
}
|
|
61
|
+
function shouldReplacePreviewWithReply(message, replyContent, replyCtx) {
|
|
62
|
+
if (!message.preview || !replyCtx || message.turnKey !== replyCtx) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const content = message.streamTargetContent ?? message.content;
|
|
66
|
+
return Boolean(replyContent && content === replyContent && !isInternalProgressMessage(content));
|
|
67
|
+
}
|
|
51
68
|
function extractVisibleMessageContent(content) {
|
|
52
69
|
if (!content) {
|
|
53
70
|
return '';
|
|
@@ -85,30 +102,6 @@ function toMessagesFromThread(history) {
|
|
|
85
102
|
timestamp: message.timestamp,
|
|
86
103
|
}));
|
|
87
104
|
}
|
|
88
|
-
function mergePolledThreadMessages(current, polled) {
|
|
89
|
-
const polledIds = new Set(polled.map((message) => message.id));
|
|
90
|
-
const polledSignatures = new Set(polled.map((message) => `${message.role}:${message.kind || 'final'}:${message.content}`));
|
|
91
|
-
const polledFinalAssistantContent = new Set(polled
|
|
92
|
-
.filter((message) => message.role === 'assistant' && (message.kind || 'final') === 'final')
|
|
93
|
-
.map((message) => message.content));
|
|
94
|
-
const retained = current.filter((message) => {
|
|
95
|
-
if (polledIds.has(message.id)) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
if (!message.preview && message.kind !== 'progress') {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
const content = message.streamTargetContent ?? message.content;
|
|
102
|
-
if (polledFinalAssistantContent.has(content)) {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
if (message.preview) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
return !polledSignatures.has(`${message.role}:${message.kind || 'final'}:${message.content}`);
|
|
109
|
-
});
|
|
110
|
-
return sortChatMessages([...polled, ...retained]);
|
|
111
|
-
}
|
|
112
105
|
function toChatThreadSummary(project, session) {
|
|
113
106
|
return {
|
|
114
107
|
id: session.id,
|
|
@@ -51,114 +51,74 @@ function createMessage(overrides) {
|
|
|
51
51
|
strict_1.default.equal((0, thread_chat_task_state_1.taskStateForBridgeButtons)(false, false), 'idle');
|
|
52
52
|
strict_1.default.equal((0, thread_chat_task_state_1.taskStateReasonForBridgeButtons)(false, false), 'bridge-buttons-idle');
|
|
53
53
|
});
|
|
54
|
-
(0, node_test_1.default)('
|
|
55
|
-
const pendingPermission = (0, thread_chat_task_state_1.deriveTaskStateFromThreadDetail)({
|
|
56
|
-
id: 'thread-1',
|
|
57
|
-
workspaceId: 'default',
|
|
58
|
-
title: 'Thread',
|
|
59
|
-
live: false,
|
|
60
|
-
updatedAt: '2026-04-22T00:00:00.000Z',
|
|
61
|
-
createdAt: '2026-04-22T00:00:00.000Z',
|
|
62
|
-
historyCount: 1,
|
|
63
|
-
excerpt: '',
|
|
64
|
-
messages: [],
|
|
65
|
-
selectedKnowledgeBaseIds: [],
|
|
66
|
-
pendingPermissionRequest: {
|
|
67
|
-
id: 'permission-1',
|
|
68
|
-
content: 'Permission required',
|
|
69
|
-
actions: createPermissionActions(),
|
|
70
|
-
actionMode: 'permission',
|
|
71
|
-
actionInteractive: true,
|
|
72
|
-
},
|
|
73
|
-
}, 0, 0);
|
|
74
|
-
strict_1.default.deepEqual(pendingPermission, {
|
|
75
|
-
state: 'awaiting_permission',
|
|
76
|
-
reason: 'local-core-poll-awaiting-permission',
|
|
77
|
-
});
|
|
78
|
-
const awaitingInput = (0, thread_chat_task_state_1.deriveTaskStateFromThreadDetail)({
|
|
79
|
-
id: 'thread-1',
|
|
80
|
-
workspaceId: 'default',
|
|
81
|
-
title: 'Thread',
|
|
82
|
-
live: false,
|
|
83
|
-
updatedAt: '2026-04-22T00:00:00.000Z',
|
|
84
|
-
createdAt: '2026-04-22T00:00:00.000Z',
|
|
85
|
-
historyCount: 1,
|
|
86
|
-
excerpt: '',
|
|
87
|
-
messages: [
|
|
88
|
-
{
|
|
89
|
-
id: 'assistant-1',
|
|
90
|
-
role: 'assistant',
|
|
91
|
-
content: '等待你的回复,请直接回复选项编号',
|
|
92
|
-
timestamp: '2026-04-22T00:00:00.000Z',
|
|
93
|
-
kind: 'progress',
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
selectedKnowledgeBaseIds: [],
|
|
97
|
-
pendingPermissionRequest: null,
|
|
98
|
-
}, 0, 0);
|
|
99
|
-
strict_1.default.deepEqual(awaitingInput, {
|
|
100
|
-
state: 'awaiting_input',
|
|
101
|
-
reason: 'local-core-poll-awaiting-input',
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
(0, node_test_1.default)('mergePolledThreadMessages keeps active streaming previews during polling', () => {
|
|
54
|
+
(0, node_test_1.default)('reply replacement removes only the matching answer preview from a multi-message turn', () => {
|
|
105
55
|
const current = [
|
|
106
56
|
{
|
|
107
|
-
id: '
|
|
108
|
-
role: 'user',
|
|
109
|
-
content: 'hello',
|
|
110
|
-
kind: 'final',
|
|
111
|
-
order: 0,
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
id: 'run-1-preview',
|
|
57
|
+
id: 'thought-preview',
|
|
115
58
|
role: 'assistant',
|
|
116
|
-
content: '
|
|
117
|
-
streamTargetContent: '
|
|
59
|
+
content: '💭 checking',
|
|
60
|
+
streamTargetContent: '💭 checking the request',
|
|
118
61
|
kind: 'progress',
|
|
119
62
|
order: 1,
|
|
120
63
|
turnKey: 'run-1',
|
|
121
64
|
preview: true,
|
|
122
65
|
previewPlainText: true,
|
|
123
66
|
},
|
|
124
|
-
];
|
|
125
|
-
const polled = [
|
|
126
67
|
{
|
|
127
|
-
id: '
|
|
128
|
-
role: 'user',
|
|
129
|
-
content: 'hello',
|
|
130
|
-
kind: 'final',
|
|
131
|
-
order: 0,
|
|
132
|
-
},
|
|
133
|
-
];
|
|
134
|
-
const merged = (0, thread_chat_model_1.mergePolledThreadMessages)(current, polled);
|
|
135
|
-
strict_1.default.equal(merged.length, 2);
|
|
136
|
-
strict_1.default.equal(merged[1]?.id, 'run-1-preview');
|
|
137
|
-
strict_1.default.equal(merged[1]?.content, 'thinking');
|
|
138
|
-
});
|
|
139
|
-
(0, node_test_1.default)('mergePolledThreadMessages drops previews once the final answer is persisted', () => {
|
|
140
|
-
const current = [
|
|
141
|
-
{
|
|
142
|
-
id: 'run-1-preview',
|
|
68
|
+
id: 'answer-preview',
|
|
143
69
|
role: 'assistant',
|
|
144
|
-
content: '
|
|
145
|
-
streamTargetContent: '
|
|
70
|
+
content: 'Hi',
|
|
71
|
+
streamTargetContent: 'Hi! How can I help you today?',
|
|
146
72
|
kind: 'progress',
|
|
147
|
-
order:
|
|
73
|
+
order: 2,
|
|
148
74
|
turnKey: 'run-1',
|
|
149
75
|
preview: true,
|
|
150
76
|
previewPlainText: true,
|
|
151
77
|
},
|
|
152
78
|
];
|
|
153
|
-
const
|
|
79
|
+
const retained = current.filter((message) => !(0, thread_chat_model_1.shouldReplacePreviewWithReply)(message, 'Hi! How can I help you today?', 'run-1'));
|
|
80
|
+
strict_1.default.deepEqual(retained.map((message) => message.id), ['thought-preview']);
|
|
81
|
+
});
|
|
82
|
+
(0, node_test_1.default)('findStreamingPreviewMessage keeps distinct preview handles in the same turn separate', () => {
|
|
83
|
+
const messages = [
|
|
154
84
|
{
|
|
155
|
-
id: '
|
|
85
|
+
id: 'thought-preview',
|
|
156
86
|
role: 'assistant',
|
|
157
|
-
content: '
|
|
158
|
-
kind: '
|
|
159
|
-
order:
|
|
87
|
+
content: '💭 checking',
|
|
88
|
+
kind: 'progress',
|
|
89
|
+
order: 0,
|
|
90
|
+
turnKey: 'run-1',
|
|
91
|
+
preview: true,
|
|
92
|
+
previewPlainText: true,
|
|
160
93
|
},
|
|
161
94
|
];
|
|
162
|
-
|
|
163
|
-
strict_1.default.
|
|
95
|
+
strict_1.default.equal((0, thread_chat_model_1.findStreamingPreviewMessage)(messages, 'assistant-preview', 'run-1'), undefined);
|
|
96
|
+
strict_1.default.equal((0, thread_chat_model_1.findStreamingPreviewMessage)(messages, 'thought-preview', 'run-1')?.id, 'thought-preview');
|
|
97
|
+
strict_1.default.equal((0, thread_chat_model_1.findStreamingPreviewMessage)(messages, undefined, 'run-1')?.id, 'thought-preview');
|
|
98
|
+
});
|
|
99
|
+
(0, node_test_1.default)('shouldReplacePreviewWithReply keeps thought previews when final answer arrives', () => {
|
|
100
|
+
const thoughtPreview = {
|
|
101
|
+
id: 'thought-preview',
|
|
102
|
+
role: 'assistant',
|
|
103
|
+
content: '💭 checking',
|
|
104
|
+
streamTargetContent: '💭 checking the request',
|
|
105
|
+
kind: 'progress',
|
|
106
|
+
order: 0,
|
|
107
|
+
turnKey: 'run-1',
|
|
108
|
+
preview: true,
|
|
109
|
+
previewPlainText: true,
|
|
110
|
+
};
|
|
111
|
+
const answerPreview = {
|
|
112
|
+
id: 'answer-preview',
|
|
113
|
+
role: 'assistant',
|
|
114
|
+
content: 'Hi',
|
|
115
|
+
streamTargetContent: 'Hi! How can I help you today?',
|
|
116
|
+
kind: 'progress',
|
|
117
|
+
order: 1,
|
|
118
|
+
turnKey: 'run-1',
|
|
119
|
+
preview: true,
|
|
120
|
+
previewPlainText: true,
|
|
121
|
+
};
|
|
122
|
+
strict_1.default.equal((0, thread_chat_model_1.shouldReplacePreviewWithReply)(thoughtPreview, 'Hi! How can I help you today?', 'run-1'), false);
|
|
123
|
+
strict_1.default.equal((0, thread_chat_model_1.shouldReplacePreviewWithReply)(answerPreview, 'Hi! How can I help you today?', 'run-1'), true);
|
|
164
124
|
});
|
|
@@ -3,19 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.taskStateAfterTypingStop = taskStateAfterTypingStop;
|
|
4
4
|
exports.taskStateForBridgeButtons = taskStateForBridgeButtons;
|
|
5
5
|
exports.taskStateReasonForBridgeButtons = taskStateReasonForBridgeButtons;
|
|
6
|
-
exports.deriveTaskStateFromThreadDetail = deriveTaskStateFromThreadDetail;
|
|
7
|
-
function isAwaitingInputMessage(content) {
|
|
8
|
-
if (!content) {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
const normalized = content.replace(/\s+/g, ' ').trim();
|
|
12
|
-
return (/^Agent 提问(?:\s*\(\d+\/\d+\))?/i.test(normalized) ||
|
|
13
|
-
normalized.includes('请回复选项编号') ||
|
|
14
|
-
normalized.includes('直接输入你的回答') ||
|
|
15
|
-
normalized.includes('等待你的回复') ||
|
|
16
|
-
normalized.includes('请直接回复') ||
|
|
17
|
-
normalized.includes('请输入你的回答'));
|
|
18
|
-
}
|
|
19
6
|
function taskStateAfterTypingStop(taskState) {
|
|
20
7
|
return taskState === 'awaiting_permission' ? 'awaiting_permission' : 'idle';
|
|
21
8
|
}
|
|
@@ -37,29 +24,3 @@ function taskStateReasonForBridgeButtons(hasActions, hasInteractivePermission) {
|
|
|
37
24
|
}
|
|
38
25
|
return 'bridge-buttons-idle';
|
|
39
26
|
}
|
|
40
|
-
function deriveTaskStateFromThreadDetail(detail, baselineResponseCount, unchangedPolls) {
|
|
41
|
-
if (detail.pendingPermissionRequest) {
|
|
42
|
-
return {
|
|
43
|
-
state: 'awaiting_permission',
|
|
44
|
-
reason: 'local-core-poll-awaiting-permission',
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const assistantMessages = detail.messages.filter((message) => message.role === 'assistant');
|
|
48
|
-
const latestAssistantMessage = assistantMessages[assistantMessages.length - 1];
|
|
49
|
-
if (latestAssistantMessage && isAwaitingInputMessage(latestAssistantMessage.content)) {
|
|
50
|
-
return {
|
|
51
|
-
state: 'awaiting_input',
|
|
52
|
-
reason: 'local-core-poll-awaiting-input',
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
if (latestAssistantMessage &&
|
|
56
|
-
latestAssistantMessage.kind !== 'progress' &&
|
|
57
|
-
detail.messages.filter((message) => message.role !== 'user' && message.kind !== 'progress').length > baselineResponseCount &&
|
|
58
|
-
unchangedPolls >= 1) {
|
|
59
|
-
return {
|
|
60
|
-
state: 'idle',
|
|
61
|
-
reason: 'local-core-poll-complete',
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
}
|