@kafca/agentdock 0.1.28 → 0.1.30
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/README.md +5 -0
- package/dist/renderer/assets/{Badge-B-Ayib4h.js → Badge-DlVJI1Ys.js} +1 -1
- package/dist/renderer/assets/{Chat-CNEOxtyz.js → Chat-oa8OjaHQ.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-BTd1QBj9.js → ChatMarkdown-C-N8KS5C.js} +1 -1
- package/dist/renderer/assets/{Config-ByKhcLa6.js → Config-D9sL_d4D.js} +1 -1
- package/dist/renderer/assets/{CronList-0mi8dcC5.js → CronList-Z5JU3DHk.js} +1 -1
- package/dist/renderer/assets/{Dashboard-C8-tB_qg.js → Dashboard-D3BwbG43.js} +2 -2
- package/dist/renderer/assets/{EmptyState-TR-9nr5e.js → EmptyState-QOj765Ul.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-DKAxFOXc.js → KnowledgeDetail-J7n01mPu.js} +1 -1
- package/dist/renderer/assets/{KnowledgeHome-CM-1IDr4.js → KnowledgeHome-BFnH0NVk.js} +1 -1
- package/dist/renderer/assets/{Logs-DI5RFPri.js → Logs-DfSUifXc.js} +1 -1
- package/dist/renderer/assets/{Modal-CQpa4NPP.js → Modal-QjEGhyru.js} +1 -1
- package/dist/renderer/assets/{Page-BHXEAwzQ.js → Page-qdHFSw7x.js} +1 -1
- package/dist/renderer/assets/{ProjectDetail-Dn5elnHx.js → ProjectDetail-DS_rfvhh.js} +1 -1
- package/dist/renderer/assets/{ProjectList-D6yvEgb5.js → ProjectList-COtyZd7H.js} +1 -1
- package/dist/renderer/assets/{Select-BbkcXnXL.js → Select-BQ8mncVu.js} +1 -1
- package/dist/renderer/assets/{SessionChat-Vkf-7gvX.js → SessionChat-DjVnjuQy.js} +1 -1
- package/dist/renderer/assets/{SessionList-c5encjWd.js → SessionList-88dgGZJ8.js} +1 -1
- package/dist/renderer/assets/ThreadChat-BCdbTxBo.js +23 -0
- package/dist/renderer/assets/{Workspace-BC7hWqhP.js → Workspace-D_hzSJjX.js} +1 -1
- package/dist/renderer/assets/{arrow-left-I_Y1kdN9.js → arrow-left-BMpknulW.js} +1 -1
- package/dist/renderer/assets/{book-open-B3anUYvp.js → book-open-CiaOeQbU.js} +1 -1
- package/dist/renderer/assets/{check-BPNCDKgS.js → check-CgwYH4GF.js} +1 -1
- package/dist/renderer/assets/{chevron-down-BaxUcOIV.js → chevron-down-BzD2S1OO.js} +1 -1
- package/dist/renderer/assets/{circle-DJQiypFS.js → circle-BDHD7g7l.js} +1 -1
- package/dist/renderer/assets/{desktop-Cv51clcN.js → desktop-BiR5LPL5.js} +1 -1
- package/dist/renderer/assets/{funnel-DqKBtiqE.js → funnel-Bruf5FYA.js} +1 -1
- package/dist/renderer/assets/{heart-CmoNjvMO.js → heart-D7Mz04B0.js} +1 -1
- package/dist/renderer/assets/{index-DXJl2HOK.js → index-CL1e8m3U.js} +2 -2
- package/dist/renderer/assets/{index-BfXqhQ1Z.js → index-gZIRr4_o.js} +1 -1
- package/dist/renderer/assets/{pencil-CLUBszbw.js → pencil-C6gzDMse.js} +1 -1
- package/dist/renderer/assets/{play-l2RPZuAi.js → play-DqTXX-Pr.js} +1 -1
- package/dist/renderer/assets/{plug-CeUPIjRt.js → plug-ykmbeaO3.js} +1 -1
- package/dist/renderer/assets/{plus-BjkefS2P.js → plus-DHE60vMX.js} +1 -1
- package/dist/renderer/assets/projects-Bx5p10th.js +1 -0
- package/dist/renderer/assets/{save-D0yYt9tz.js → save-CGIvrdQI.js} +1 -1
- package/dist/renderer/assets/{search-B7s473Y4.js → search-D7IFAwyE.js} +1 -1
- package/dist/renderer/assets/{server-Dz_h8X_8.js → server-aOsirp7I.js} +1 -1
- package/dist/renderer/assets/{session-utils-BvNRTRXA.js → session-utils-Dxfp4ohQ.js} +1 -1
- package/dist/renderer/assets/{sessions-KPJsgKbQ.js → sessions-Db1XPbG2.js} +1 -1
- package/dist/renderer/assets/{shield-check-Wc5wfUUs.js → shield-check-ChXeQK-d.js} +1 -1
- package/dist/renderer/assets/{trash-2-7INdaB9w.js → trash-2-CfN-c1zw.js} +1 -1
- package/dist/renderer/assets/{user-CLbCgWNR.js → user-C7OHaBp4.js} +1 -1
- package/dist/renderer/assets/{wifi-off-BfDVPXbO.js → wifi-off-CpaJigye.js} +1 -1
- package/dist/renderer/index.html +1 -1
- package/dist-electron/electron/agent-runtime-detector.test.js +20 -0
- package/dist-electron/electron/lac-cli.test.js +2 -13
- package/dist-electron/electron/local-core-refactor.test.js +424 -64
- package/dist-electron/electron/workspace-task-store.test.js +118 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +17 -10
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-progress.js +245 -9
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +24 -11
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +36 -11
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +2 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +90 -7
- package/dist-electron/services/local-ai-core/src/channel/lark/runtime-state.js +35 -40
- package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +43 -18
- package/dist-electron/services/local-ai-core/src/cli/lac.js +1 -17
- package/dist-electron/services/local-ai-core/src/router/workspace-router.js +2 -2
- package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +1 -1
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +31 -1
- package/dist-electron/services/local-ai-core/src/scheduler/local-schedule-adapter.js +4 -1
- package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +4 -1
- package/dist-electron/src/pages/Threads/thread-chat-message-blocks.js +4 -114
- package/dist-electron/src/pages/Threads/thread-chat-model.js +14 -23
- package/dist-electron/src/pages/Threads/thread-chat-page-state.js +1 -1
- package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +58 -31
- package/package.json +1 -1
- package/dist/renderer/assets/ThreadChat-DY4wzomi.js +0 -23
- package/dist/renderer/assets/projects-De73QY_o.js +0 -1
|
@@ -11,6 +11,8 @@ const node_os_1 = require("node:os");
|
|
|
11
11
|
const local_core_acp_store_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-store.js");
|
|
12
12
|
const local_core_acp_session_coordinator_js_1 = require("../services/local-ai-core/src/acp/local-core-acp-session-coordinator.js");
|
|
13
13
|
const scheduler_service_js_1 = require("../services/local-ai-core/src/scheduler/scheduler-service.js");
|
|
14
|
+
const bootstrap_js_1 = require("../services/local-ai-core/src/kernel/bootstrap.js");
|
|
15
|
+
const local_core_controller_js_1 = require("../services/local-ai-core/src/runtime/local-core-controller.js");
|
|
14
16
|
(0, node_test_1.default)('workspace registry entries persist in LocalCoreAcpStore', () => {
|
|
15
17
|
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'workspace-registry-'));
|
|
16
18
|
try {
|
|
@@ -36,6 +38,50 @@ const scheduler_service_js_1 = require("../services/local-ai-core/src/scheduler/
|
|
|
36
38
|
(0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
|
|
37
39
|
}
|
|
38
40
|
});
|
|
41
|
+
(0, node_test_1.default)('scheduler create resolves a Lark route from the current thread binding', async () => {
|
|
42
|
+
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-binding-route-'));
|
|
43
|
+
try {
|
|
44
|
+
const runtime = (0, bootstrap_js_1.bootstrapLocalCoreRuntime)({
|
|
45
|
+
userDataPath,
|
|
46
|
+
enableKnowledge: false,
|
|
47
|
+
log: () => { },
|
|
48
|
+
});
|
|
49
|
+
const controller = new local_core_controller_js_1.LocalCoreController(userDataPath, runtime);
|
|
50
|
+
const thread = runtime.store.createThread('workspace-a', 'Lark thread');
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
runtime.store.upsertPlatformThreadBinding({
|
|
53
|
+
workspace_id: 'workspace-a',
|
|
54
|
+
platform: 'lark',
|
|
55
|
+
chat_id: 'chat-1',
|
|
56
|
+
platform_user_id: 'user-1',
|
|
57
|
+
thread_id: thread.id,
|
|
58
|
+
last_platform_message_id: null,
|
|
59
|
+
created_at: now,
|
|
60
|
+
updated_at: now,
|
|
61
|
+
});
|
|
62
|
+
const job = await controller.createScheduledJob({
|
|
63
|
+
workspaceId: 'workspace-a',
|
|
64
|
+
threadId: thread.id,
|
|
65
|
+
executionMode: 'side-thread',
|
|
66
|
+
triggerType: 'cron',
|
|
67
|
+
cronExpr: '30 18 * * *',
|
|
68
|
+
promptTemplate: 'ping',
|
|
69
|
+
description: 'bound lark task',
|
|
70
|
+
enabled: true,
|
|
71
|
+
});
|
|
72
|
+
strict_1.default.equal(job.platform, 'lark');
|
|
73
|
+
strict_1.default.deepEqual(job.route, {
|
|
74
|
+
type: 'channel.chat',
|
|
75
|
+
channelId: 'chat-1',
|
|
76
|
+
participantId: 'user-1',
|
|
77
|
+
threadId: thread.id,
|
|
78
|
+
});
|
|
79
|
+
await controller.close();
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
(0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
39
85
|
(0, node_test_1.default)('ACP runtime env includes the current workspace path for file returns', async () => {
|
|
40
86
|
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'workspace-env-'));
|
|
41
87
|
try {
|
|
@@ -92,6 +138,78 @@ const scheduler_service_js_1 = require("../services/local-ai-core/src/scheduler/
|
|
|
92
138
|
(0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
|
|
93
139
|
}
|
|
94
140
|
});
|
|
141
|
+
(0, node_test_1.default)('ACP scheduled session can override permission mode without changing thread mode', async () => {
|
|
142
|
+
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-permission-mode-'));
|
|
143
|
+
try {
|
|
144
|
+
const store = new local_core_acp_store_js_1.LocalCoreAcpStore(userDataPath);
|
|
145
|
+
const thread = store.createThread('workspace-a', 'Thread');
|
|
146
|
+
const capturedSessionNewParams = [];
|
|
147
|
+
let closeCount = 0;
|
|
148
|
+
const coordinator = new local_core_acp_session_coordinator_js_1.LocalCoreAcpSessionCoordinator({
|
|
149
|
+
store,
|
|
150
|
+
transport: {
|
|
151
|
+
spawnSession(input) {
|
|
152
|
+
return {
|
|
153
|
+
threadId: input.threadId,
|
|
154
|
+
bridgeSessionKey: input.bridgeSessionKey,
|
|
155
|
+
closed: false,
|
|
156
|
+
sessionId: '',
|
|
157
|
+
supportsLoad: false,
|
|
158
|
+
pendingPermissionByRun: new Map(),
|
|
159
|
+
schedulerJobCreatedByRun: new Map(),
|
|
160
|
+
launchPermissionMode: '',
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
initializeSession: async () => { },
|
|
164
|
+
request: async (_session, method, params) => {
|
|
165
|
+
if (method === 'session/new') {
|
|
166
|
+
capturedSessionNewParams.push(params);
|
|
167
|
+
return { sessionId: 'session-1' };
|
|
168
|
+
}
|
|
169
|
+
return {};
|
|
170
|
+
},
|
|
171
|
+
closeSession: () => {
|
|
172
|
+
closeCount += 1;
|
|
173
|
+
},
|
|
174
|
+
closeSessionWithError: () => { },
|
|
175
|
+
sendRaw: () => true,
|
|
176
|
+
},
|
|
177
|
+
runThreadMap: new Map(),
|
|
178
|
+
emitBridge: () => { },
|
|
179
|
+
});
|
|
180
|
+
await coordinator.ensureSession(thread.id, 'session:thread-1', {
|
|
181
|
+
workspaceId: 'workspace-a',
|
|
182
|
+
agentType: 'claudecode',
|
|
183
|
+
command: 'claude',
|
|
184
|
+
args: [],
|
|
185
|
+
env: {},
|
|
186
|
+
workDir: '/tmp/workspace-a',
|
|
187
|
+
model: '',
|
|
188
|
+
}, { permissionMode: 'bypassPermissions' });
|
|
189
|
+
strict_1.default.equal(capturedSessionNewParams[0]?._meta?.claudeCode?.options?.permissionMode, 'bypassPermissions');
|
|
190
|
+
strict_1.default.equal(store.getThreadRow(thread.id)?.agent_mode, 'default');
|
|
191
|
+
await coordinator.ensureSession(thread.id, 'session:thread-1', {
|
|
192
|
+
workspaceId: 'workspace-a',
|
|
193
|
+
agentType: 'claudecode',
|
|
194
|
+
command: 'claude',
|
|
195
|
+
args: [],
|
|
196
|
+
env: {},
|
|
197
|
+
workDir: '/tmp/workspace-a',
|
|
198
|
+
model: '',
|
|
199
|
+
});
|
|
200
|
+
strict_1.default.equal(closeCount, 1);
|
|
201
|
+
strict_1.default.deepEqual(capturedSessionNewParams[1]?._meta, {
|
|
202
|
+
claudeCode: {
|
|
203
|
+
emitRawSDKMessages: [
|
|
204
|
+
{ type: 'system', subtype: 'local_command_output' },
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
(0, node_fs_1.rmSync)(userDataPath, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
95
213
|
(0, node_test_1.default)('scheduler uses short ids for new jobs and resolves legacy full ids by short id', () => {
|
|
96
214
|
const userDataPath = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'scheduler-id-store-'));
|
|
97
215
|
try {
|
|
@@ -10,6 +10,7 @@ const local_core_acp_response_processor_js_1 = require("./local-core-acp-respons
|
|
|
10
10
|
const local_core_acp_content_js_1 = require("./local-core-acp-content.js");
|
|
11
11
|
const command_risk_js_1 = require("../security/command-risk.js");
|
|
12
12
|
const local_core_slash_commands_js_1 = require("./local-core-slash-commands.js");
|
|
13
|
+
const local_core_acp_progress_js_1 = require("./local-core-acp-progress.js");
|
|
13
14
|
const ACP_PROMPT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
14
15
|
class LocalCoreAcpBackend {
|
|
15
16
|
options;
|
|
@@ -27,11 +28,11 @@ class LocalCoreAcpBackend {
|
|
|
27
28
|
});
|
|
28
29
|
this.turnCoordinator = new local_core_acp_turn_coordinator_js_1.LocalCoreAcpTurnCoordinator({
|
|
29
30
|
emitBridge: (event) => this.emitBridgeEvent(event),
|
|
30
|
-
appendMessage: (threadId, role, content, kind, toolCall) => {
|
|
31
|
-
this.options.store.appendMessage(threadId, role, content, kind, toolCall);
|
|
31
|
+
appendMessage: (threadId, role, content, kind, toolCall, bridgeKind, bridgeStatus) => {
|
|
32
|
+
this.options.store.appendMessage(threadId, role, content, kind, toolCall, bridgeKind, bridgeStatus);
|
|
32
33
|
},
|
|
33
|
-
upsertMessage: (threadId, id, role, content, kind, toolCall) => {
|
|
34
|
-
this.options.store.upsertMessage(threadId, id, role, content, kind, toolCall);
|
|
34
|
+
upsertMessage: (threadId, id, role, content, kind, toolCall, bridgeKind, bridgeStatus) => {
|
|
35
|
+
this.options.store.upsertMessage(threadId, id, role, content, kind, toolCall, bridgeKind, bridgeStatus);
|
|
35
36
|
},
|
|
36
37
|
updateRunStatus: (runId, threadId, status) => {
|
|
37
38
|
this.options.store.updateRun(runId, threadId, status);
|
|
@@ -129,7 +130,7 @@ class LocalCoreAcpBackend {
|
|
|
129
130
|
this.options.store.deleteThread(threadId);
|
|
130
131
|
return { deleted: true };
|
|
131
132
|
}
|
|
132
|
-
async sendThreadMessage(threadId, input, config) {
|
|
133
|
+
async sendThreadMessage(threadId, input, config, options = {}) {
|
|
133
134
|
if (!config) {
|
|
134
135
|
throw new Error('localcore-acp message send requires a workspace config.');
|
|
135
136
|
}
|
|
@@ -199,7 +200,7 @@ class LocalCoreAcpBackend {
|
|
|
199
200
|
sessionKey: row.bridge_session_key,
|
|
200
201
|
},
|
|
201
202
|
});
|
|
202
|
-
void this.runPrompt(threadId, runId, row.bridge_session_key, config, message).catch((error) => {
|
|
203
|
+
void this.runPrompt(threadId, runId, row.bridge_session_key, config, message, options).catch((error) => {
|
|
203
204
|
this.options.log?.(`localcore-acp prompt failed for ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
204
205
|
});
|
|
205
206
|
return { runId };
|
|
@@ -283,7 +284,7 @@ class LocalCoreAcpBackend {
|
|
|
283
284
|
async interruptRun(runId) {
|
|
284
285
|
return this.sessionCoordinator.interruptRun(runId);
|
|
285
286
|
}
|
|
286
|
-
async runPrompt(threadId, runId, bridgeSessionKey, config, input) {
|
|
287
|
+
async runPrompt(threadId, runId, bridgeSessionKey, config, input, options = {}) {
|
|
287
288
|
const row = this.options.store.getThreadRow(threadId);
|
|
288
289
|
if (!row) {
|
|
289
290
|
throw new Error(`Thread not found: ${threadId}`);
|
|
@@ -297,16 +298,19 @@ class LocalCoreAcpBackend {
|
|
|
297
298
|
const content = message.displayText;
|
|
298
299
|
let session = null;
|
|
299
300
|
try {
|
|
300
|
-
session = await this.sessionCoordinator.ensureSession(threadId, bridgeSessionKey, config
|
|
301
|
+
session = await this.sessionCoordinator.ensureSession(threadId, bridgeSessionKey, config, {
|
|
302
|
+
permissionMode: options.permissionMode,
|
|
303
|
+
});
|
|
301
304
|
session.currentRunId = runId;
|
|
302
305
|
session.currentTurn = {
|
|
303
306
|
runId,
|
|
304
307
|
replyCtx: runId,
|
|
305
308
|
previewHandle: (0, node_crypto_1.randomUUID)(),
|
|
306
309
|
thoughtPreviewHandle: (0, node_crypto_1.randomUUID)(),
|
|
307
|
-
thoughtMessageId: `${runId}-thought`,
|
|
310
|
+
thoughtMessageId: `${runId}-thought-1`,
|
|
308
311
|
assistantText: '',
|
|
309
312
|
thoughtText: '',
|
|
313
|
+
thoughtSequence: 1,
|
|
310
314
|
typingStarted: true,
|
|
311
315
|
previewStarted: false,
|
|
312
316
|
thoughtPreviewStarted: false,
|
|
@@ -317,6 +321,7 @@ class LocalCoreAcpBackend {
|
|
|
317
321
|
pendingToolCalls: {},
|
|
318
322
|
pendingToolCallOrder: [],
|
|
319
323
|
toolCallSequence: 0,
|
|
324
|
+
toolObservations: [],
|
|
320
325
|
permission: null,
|
|
321
326
|
};
|
|
322
327
|
const promptPromise = this.transport.request(session, 'session/prompt', {
|
|
@@ -330,9 +335,11 @@ class LocalCoreAcpBackend {
|
|
|
330
335
|
if (!currentTurn || currentTurn.runId !== runId) {
|
|
331
336
|
return;
|
|
332
337
|
}
|
|
338
|
+
this.turnCoordinator.closePendingThoughtSegment(session);
|
|
333
339
|
this.turnCoordinator.flushPendingToolCall(session);
|
|
334
340
|
if (currentTurn.assistantText) {
|
|
335
|
-
const
|
|
341
|
+
const assistantText = (0, local_core_acp_progress_js_1.stripObservedToolTranscriptsFromAssistantText)(currentTurn.assistantText, currentTurn.toolObservations);
|
|
342
|
+
const processed = await this.responseProcessor.processAssistantResponse(threadId, assistantText);
|
|
336
343
|
if (processed.displayContent) {
|
|
337
344
|
this.options.store.appendMessage(threadId, 'assistant', processed.displayContent, 'final');
|
|
338
345
|
this.options.eventBus.emit({
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.applyAssistantMessageChunk = applyAssistantMessageChunk;
|
|
4
4
|
exports.applyThoughtChunk = applyThoughtChunk;
|
|
5
|
+
exports.closeThoughtSegment = closeThoughtSegment;
|
|
5
6
|
exports.registerPendingToolCall = registerPendingToolCall;
|
|
6
7
|
exports.resolveToolCallForUpdate = resolveToolCallForUpdate;
|
|
7
8
|
exports.resolveFallbackToolCall = resolveFallbackToolCall;
|
|
@@ -16,6 +17,8 @@ exports.isTerminalToolStatus = isTerminalToolStatus;
|
|
|
16
17
|
exports.formatPlanProgress = formatPlanProgress;
|
|
17
18
|
exports.extractToolCallKey = extractToolCallKey;
|
|
18
19
|
exports.extractToolCallInput = extractToolCallInput;
|
|
20
|
+
exports.recordToolObservation = recordToolObservation;
|
|
21
|
+
exports.stripObservedToolTranscriptsFromAssistantText = stripObservedToolTranscriptsFromAssistantText;
|
|
19
22
|
function applyAssistantMessageChunk(currentTurn, text) {
|
|
20
23
|
if (!text) {
|
|
21
24
|
return null;
|
|
@@ -27,20 +30,26 @@ function applyAssistantMessageChunk(currentTurn, text) {
|
|
|
27
30
|
bridgeType: 'preview_start',
|
|
28
31
|
previewHandle: currentTurn.previewHandle,
|
|
29
32
|
content: currentTurn.assistantText,
|
|
33
|
+
bridgeKind: 'assistant',
|
|
30
34
|
};
|
|
31
35
|
}
|
|
32
36
|
return {
|
|
33
37
|
bridgeType: 'update_message',
|
|
34
38
|
previewHandle: currentTurn.previewHandle,
|
|
35
39
|
content: currentTurn.assistantText,
|
|
40
|
+
bridgeKind: 'assistant',
|
|
36
41
|
};
|
|
37
42
|
}
|
|
38
43
|
function applyThoughtChunk(currentTurn, text) {
|
|
39
44
|
if (!text) {
|
|
40
45
|
return null;
|
|
41
46
|
}
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
const projection = mergeThoughtSegment(currentTurn.thoughtText, text);
|
|
48
|
+
currentTurn.thoughtText = projection.fullText;
|
|
49
|
+
const content = currentTurn.thoughtText.trim();
|
|
50
|
+
if (!content) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
44
53
|
if (!currentTurn.thoughtPreviewStarted) {
|
|
45
54
|
currentTurn.thoughtPreviewStarted = true;
|
|
46
55
|
return {
|
|
@@ -48,6 +57,7 @@ function applyThoughtChunk(currentTurn, text) {
|
|
|
48
57
|
previewHandle: currentTurn.thoughtPreviewHandle,
|
|
49
58
|
messageId: currentTurn.thoughtMessageId,
|
|
50
59
|
content,
|
|
60
|
+
bridgeKind: 'thought',
|
|
51
61
|
};
|
|
52
62
|
}
|
|
53
63
|
return {
|
|
@@ -55,25 +65,70 @@ function applyThoughtChunk(currentTurn, text) {
|
|
|
55
65
|
previewHandle: currentTurn.thoughtPreviewHandle,
|
|
56
66
|
messageId: currentTurn.thoughtMessageId,
|
|
57
67
|
content,
|
|
68
|
+
bridgeKind: 'thought',
|
|
58
69
|
};
|
|
59
70
|
}
|
|
60
|
-
function
|
|
71
|
+
function closeThoughtSegment(currentTurn) {
|
|
72
|
+
const thoughtText = currentTurn.thoughtText || '';
|
|
73
|
+
if (!thoughtText.trim()) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const nextSequence = (currentTurn.thoughtSequence || 1) + 1;
|
|
77
|
+
const runId = currentTurn.runId || 'thought';
|
|
78
|
+
currentTurn.thoughtSequence = nextSequence;
|
|
79
|
+
currentTurn.thoughtText = '';
|
|
80
|
+
currentTurn.thoughtPreviewStarted = false;
|
|
81
|
+
currentTurn.thoughtMessageId = `${runId}-thought-${nextSequence}`;
|
|
82
|
+
currentTurn.thoughtPreviewHandle = `${runId}-thought-preview-${nextSequence}`;
|
|
83
|
+
}
|
|
84
|
+
function mergeThoughtSegment(current, next) {
|
|
61
85
|
if (!current) {
|
|
62
|
-
return next;
|
|
86
|
+
return { fullText: next, segmentText: next };
|
|
63
87
|
}
|
|
64
88
|
if (next.startsWith(current)) {
|
|
65
|
-
return
|
|
89
|
+
return {
|
|
90
|
+
fullText: next,
|
|
91
|
+
segmentText: next.slice(current.length),
|
|
92
|
+
};
|
|
66
93
|
}
|
|
67
94
|
if (current.endsWith(next)) {
|
|
68
|
-
return
|
|
95
|
+
return {
|
|
96
|
+
fullText: current,
|
|
97
|
+
segmentText: '',
|
|
98
|
+
};
|
|
69
99
|
}
|
|
70
100
|
const maxOverlap = Math.min(current.length, next.length);
|
|
71
101
|
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
72
102
|
if (current.endsWith(next.slice(0, size))) {
|
|
73
|
-
|
|
103
|
+
const suffix = next.slice(size);
|
|
104
|
+
return {
|
|
105
|
+
fullText: current + suffix,
|
|
106
|
+
segmentText: suffix,
|
|
107
|
+
};
|
|
74
108
|
}
|
|
75
109
|
}
|
|
76
|
-
return
|
|
110
|
+
return {
|
|
111
|
+
fullText: appendThoughtPiece(current, next),
|
|
112
|
+
segmentText: next,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function appendThoughtPiece(current, next) {
|
|
116
|
+
if (!current.trim()) {
|
|
117
|
+
return next;
|
|
118
|
+
}
|
|
119
|
+
if (!next.trim()) {
|
|
120
|
+
return current;
|
|
121
|
+
}
|
|
122
|
+
if (/^[,.;:!?,。;:!?、)】》]/.test(next.trimStart())) {
|
|
123
|
+
return current.trimEnd() + next.trimStart();
|
|
124
|
+
}
|
|
125
|
+
if (/^[-/]/.test(next.trimStart()) || /[-/][A-Za-z0-9]*$/.test(current.trimEnd())) {
|
|
126
|
+
return current.trimEnd() + next.trimStart();
|
|
127
|
+
}
|
|
128
|
+
if (/\s$/.test(current) || /^\s/.test(next)) {
|
|
129
|
+
return current + next;
|
|
130
|
+
}
|
|
131
|
+
return `${current.trimEnd()} ${next.trimStart()}`;
|
|
77
132
|
}
|
|
78
133
|
function registerPendingToolCall(input) {
|
|
79
134
|
const title = String(input.update.title || 'Running tool').trim();
|
|
@@ -177,7 +232,7 @@ function formatPlanProgress(entries) {
|
|
|
177
232
|
.map((entry) => String(entry?.content || '').trim())
|
|
178
233
|
.filter(Boolean)
|
|
179
234
|
.join(' | ');
|
|
180
|
-
return summary
|
|
235
|
+
return summary;
|
|
181
236
|
}
|
|
182
237
|
function extractToolCallKey(update) {
|
|
183
238
|
for (const key of ['toolCallId', 'tool_call_id', 'callId', 'call_id', 'invocationId', 'invocation_id', 'id']) {
|
|
@@ -199,3 +254,184 @@ function extractToolCallInput(update) {
|
|
|
199
254
|
}
|
|
200
255
|
return undefined;
|
|
201
256
|
}
|
|
257
|
+
function recordToolObservation(currentTurn, observation) {
|
|
258
|
+
const normalized = {
|
|
259
|
+
...observation,
|
|
260
|
+
name: observation.name?.trim() || undefined,
|
|
261
|
+
title: observation.title?.trim() || undefined,
|
|
262
|
+
status: observation.status?.trim() || undefined,
|
|
263
|
+
outputText: observation.outputText?.trim() || undefined,
|
|
264
|
+
};
|
|
265
|
+
if (!normalized.name && !normalized.title && normalized.input === undefined && !normalized.outputText) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
currentTurn.toolObservations = [
|
|
269
|
+
...(currentTurn.toolObservations || []),
|
|
270
|
+
normalized,
|
|
271
|
+
];
|
|
272
|
+
}
|
|
273
|
+
function stripObservedToolTranscriptsFromAssistantText(text, observations) {
|
|
274
|
+
let remaining = String(text || '');
|
|
275
|
+
const knownObservations = observations || [];
|
|
276
|
+
for (let guard = 0; guard < knownObservations.length; guard += 1) {
|
|
277
|
+
const stripped = stripOneObservedToolTranscriptPrefix(remaining, knownObservations);
|
|
278
|
+
if (stripped === remaining) {
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
remaining = stripped;
|
|
282
|
+
}
|
|
283
|
+
return remaining.trim();
|
|
284
|
+
}
|
|
285
|
+
function stripOneObservedToolTranscriptPrefix(text, observations) {
|
|
286
|
+
const trimmedStart = text.replace(/^\s+/, '');
|
|
287
|
+
if (!trimmedStart) {
|
|
288
|
+
return text;
|
|
289
|
+
}
|
|
290
|
+
const matchingEvidence = observations
|
|
291
|
+
.map((observation) => createToolObservationEvidence(observation))
|
|
292
|
+
.find((evidence) => matchesObservedToolTranscriptPrefix(trimmedStart, evidence));
|
|
293
|
+
if (!matchingEvidence) {
|
|
294
|
+
return text;
|
|
295
|
+
}
|
|
296
|
+
const boundary = findToolTranscriptPrefixBoundary(trimmedStart, matchingEvidence);
|
|
297
|
+
if (boundary <= 0) {
|
|
298
|
+
return text;
|
|
299
|
+
}
|
|
300
|
+
return trimmedStart.slice(boundary);
|
|
301
|
+
}
|
|
302
|
+
function createToolObservationEvidence(observation) {
|
|
303
|
+
const serializedInput = serializeObservationValue(observation.input);
|
|
304
|
+
const serializedOutput = observation.outputText || '';
|
|
305
|
+
const structuredValues = collectStructuredStringValues(serializedOutput);
|
|
306
|
+
return {
|
|
307
|
+
toolNames: uniqueNonEmpty([
|
|
308
|
+
observation.name,
|
|
309
|
+
observation.title,
|
|
310
|
+
].map((value) => normalizeEvidencePhrase(value || ''))),
|
|
311
|
+
urls: uniqueNonEmpty([
|
|
312
|
+
...extractUrls(serializedInput),
|
|
313
|
+
...extractUrls(serializedOutput),
|
|
314
|
+
]),
|
|
315
|
+
phrases: uniqueNonEmpty([
|
|
316
|
+
...structuredValues,
|
|
317
|
+
observation.title,
|
|
318
|
+
serializedInput.length <= 300 ? serializedInput : '',
|
|
319
|
+
].map((value) => normalizeEvidencePhrase(value || '')).filter((value) => value.length >= 12)),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function matchesObservedToolTranscriptPrefix(text, evidence) {
|
|
323
|
+
const prefix = text.slice(0, 3000);
|
|
324
|
+
const normalizedPrefix = normalizeEvidencePhrase(prefix);
|
|
325
|
+
const hasToolName = evidence.toolNames.some((name) => name && normalizedPrefix.includes(name));
|
|
326
|
+
const urlMatches = evidence.urls.filter((url) => prefix.includes(url)).length;
|
|
327
|
+
const phraseMatches = evidence.phrases.filter((phrase) => normalizedPrefix.includes(phrase)).length;
|
|
328
|
+
if (hasToolName && (urlMatches > 0 || phraseMatches > 0)) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
return urlMatches > 0 && phraseMatches > 0;
|
|
332
|
+
}
|
|
333
|
+
function findToolTranscriptPrefixBoundary(text, evidence) {
|
|
334
|
+
const lines = text.split(/\n/);
|
|
335
|
+
const firstContentLine = lines.find((line) => line.trim());
|
|
336
|
+
if (!firstContentLine || !isTranscriptStartLine(firstContentLine, evidence)) {
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
let offset = 0;
|
|
340
|
+
let seenEvidence = false;
|
|
341
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
342
|
+
const line = lines[index] || '';
|
|
343
|
+
const lineWithNewlineLength = line.length + (index < lines.length - 1 ? 1 : 0);
|
|
344
|
+
if (lineContainsObservationEvidence(line, evidence)) {
|
|
345
|
+
seenEvidence = true;
|
|
346
|
+
}
|
|
347
|
+
if (seenEvidence && index > 0 && line.trim() && !isStructuredTranscriptLine(line, evidence)) {
|
|
348
|
+
return offset;
|
|
349
|
+
}
|
|
350
|
+
offset += lineWithNewlineLength;
|
|
351
|
+
}
|
|
352
|
+
return 0;
|
|
353
|
+
}
|
|
354
|
+
function isTranscriptStartLine(line, evidence) {
|
|
355
|
+
const trimmed = line.trim();
|
|
356
|
+
return lineContainsObservationEvidence(line, evidence) &&
|
|
357
|
+
(/^(```|\{|\[|\*\*[^*]+\*\*|#+\s)/.test(trimmed) || /^[A-Za-z0-9_.-]+:/.test(trimmed));
|
|
358
|
+
}
|
|
359
|
+
function isStructuredTranscriptLine(line, evidence) {
|
|
360
|
+
const trimmed = line.trim();
|
|
361
|
+
if (!trimmed) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
if (lineContainsObservationEvidence(line, evidence)) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
return /^(```|\{|\}|\[|\]|".+":|\*\*[^*]+:\*\*|\*[^*]+\*|#+\s)/.test(trimmed) ||
|
|
368
|
+
/[::]\s*$/.test(trimmed) ||
|
|
369
|
+
/[{}\[\]"]/g.test(trimmed) ||
|
|
370
|
+
/^[A-Za-z0-9_.-]+:/.test(trimmed);
|
|
371
|
+
}
|
|
372
|
+
function lineContainsObservationEvidence(line, evidence) {
|
|
373
|
+
const normalizedLine = normalizeEvidencePhrase(line);
|
|
374
|
+
return evidence.toolNames.some((name) => name && normalizedLine.includes(name)) ||
|
|
375
|
+
evidence.urls.some((url) => line.includes(url)) ||
|
|
376
|
+
evidence.phrases.some((phrase) => phrase && normalizedLine.includes(phrase));
|
|
377
|
+
}
|
|
378
|
+
function serializeObservationValue(value) {
|
|
379
|
+
if (value == null) {
|
|
380
|
+
return '';
|
|
381
|
+
}
|
|
382
|
+
if (typeof value === 'string') {
|
|
383
|
+
return value;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
return JSON.stringify(value);
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return String(value);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function collectStructuredStringValues(text) {
|
|
393
|
+
const values = [];
|
|
394
|
+
try {
|
|
395
|
+
collectStringValues(JSON.parse(text), values);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
const matches = text.matchAll(/"(title|description|url|name|summary)"\s*:\s*"([^"]{12,})"/gi);
|
|
399
|
+
for (const match of matches) {
|
|
400
|
+
values.push(match[2] || '');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return values;
|
|
404
|
+
}
|
|
405
|
+
function collectStringValues(value, values) {
|
|
406
|
+
if (typeof value === 'string') {
|
|
407
|
+
values.push(value);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (Array.isArray(value)) {
|
|
411
|
+
for (const item of value) {
|
|
412
|
+
collectStringValues(item, values);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!value || typeof value !== 'object') {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
for (const entry of Object.values(value)) {
|
|
420
|
+
collectStringValues(entry, values);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function extractUrls(text) {
|
|
424
|
+
return [...text.matchAll(/https?:\/\/[^\s"'`<>),\]}]+/g)]
|
|
425
|
+
.map((match) => match[0])
|
|
426
|
+
.filter(Boolean);
|
|
427
|
+
}
|
|
428
|
+
function normalizeEvidencePhrase(value) {
|
|
429
|
+
return String(value || '')
|
|
430
|
+
.replace(/\\[nrt]/g, ' ')
|
|
431
|
+
.replace(/\s+/g, ' ')
|
|
432
|
+
.trim()
|
|
433
|
+
.toLowerCase();
|
|
434
|
+
}
|
|
435
|
+
function uniqueNonEmpty(values) {
|
|
436
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
437
|
+
}
|
|
@@ -25,9 +25,13 @@ class LocalCoreAcpSessionCoordinator {
|
|
|
25
25
|
this.options.transport.closeSession(session);
|
|
26
26
|
this.sessions.delete(threadId);
|
|
27
27
|
}
|
|
28
|
-
async ensureSession(threadId, bridgeSessionKey, config) {
|
|
28
|
+
async ensureSession(threadId, bridgeSessionKey, config, options = {}) {
|
|
29
29
|
const existing = this.sessions.get(threadId);
|
|
30
|
-
|
|
30
|
+
const permissionMode = this.resolveLaunchPermissionMode(threadId, options.permissionMode);
|
|
31
|
+
if (existing
|
|
32
|
+
&& !existing.closed
|
|
33
|
+
&& existing.sessionId
|
|
34
|
+
&& existing.launchPermissionMode === permissionMode) {
|
|
31
35
|
return existing;
|
|
32
36
|
}
|
|
33
37
|
if (existing) {
|
|
@@ -43,6 +47,7 @@ class LocalCoreAcpSessionCoordinator {
|
|
|
43
47
|
config,
|
|
44
48
|
runtimeEnv: this.buildAgentRuntimeEnv(threadId, String(baseEnv.PATH || '')),
|
|
45
49
|
});
|
|
50
|
+
session.launchPermissionMode = permissionMode;
|
|
46
51
|
this.sessions.set(threadId, session);
|
|
47
52
|
await this.options.transport.initializeSession(session);
|
|
48
53
|
const row = this.options.store.getThreadRow(threadId);
|
|
@@ -68,7 +73,7 @@ class LocalCoreAcpSessionCoordinator {
|
|
|
68
73
|
const created = await this.options.transport.request(session, 'session/new', {
|
|
69
74
|
cwd: config.workDir,
|
|
70
75
|
mcpServers: [],
|
|
71
|
-
_meta: this.buildSessionMeta(threadId),
|
|
76
|
+
_meta: this.buildSessionMeta(threadId, permissionMode),
|
|
72
77
|
}, 30000);
|
|
73
78
|
session.sessionId = String(created.sessionId || created.session_id || created.id || created.session?.sessionId || created.session?.session_id || created.session?.id || '').trim();
|
|
74
79
|
if (!session.sessionId) {
|
|
@@ -182,17 +187,25 @@ class LocalCoreAcpSessionCoordinator {
|
|
|
182
187
|
}
|
|
183
188
|
return env;
|
|
184
189
|
}
|
|
185
|
-
|
|
190
|
+
resolveLaunchPermissionMode(threadId, permissionModeOverride = '') {
|
|
186
191
|
const row = this.options.store.getThreadRow(threadId);
|
|
187
|
-
const mode = String(row?.agent_mode || '').trim();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
const mode = String(permissionModeOverride || row?.agent_mode || '').trim();
|
|
193
|
+
return !mode || mode === 'default' ? '' : mode;
|
|
194
|
+
}
|
|
195
|
+
buildSessionMeta(threadId, permissionModeOverride = '') {
|
|
196
|
+
const mode = this.resolveLaunchPermissionMode(threadId, permissionModeOverride);
|
|
191
197
|
return {
|
|
192
198
|
claudeCode: {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
emitRawSDKMessages: [
|
|
200
|
+
{ type: 'system', subtype: 'local_command_output' },
|
|
201
|
+
],
|
|
202
|
+
...(mode
|
|
203
|
+
? {
|
|
204
|
+
options: {
|
|
205
|
+
permissionMode: mode,
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
: {}),
|
|
196
209
|
},
|
|
197
210
|
};
|
|
198
211
|
}
|