maka-agent 0.2.0-dev.46.20260922 → 0.2.0-dev.48.20260923
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/DISCLAIMER-WIP +6 -1
- package/README.md +6 -1
- package/dist/acp/maka-acp-agent.js +1 -1
- package/dist/acp/session-mcp.js +212 -0
- package/dist/acp/session-registry.js +52 -8
- package/dist/acp/stdio-server.js +3 -0
- package/dist/cli-core.js +1 -1
- package/dist/mcp-capability-provider.js +6 -1
- package/dist/mcp-capability-publication.js +139 -0
- package/dist/pi-tui-layout.js +46 -17
- package/dist/pi-tui-pickers.js +25 -14
- package/dist/pi-tui-runner.js +24 -15
- package/dist/runtime-host-capability-provider-command.js +25 -6
- package/dist/runtime-host-service-manager.js +26 -2
- package/dist/tui-context-refresh.js +15 -26
- package/dist/tui-mcp-control.js +66 -112
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
- package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
- package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
- package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
- package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
- package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
- package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
- package/node_modules/@maka/core/dist/client-capability-grant.js +2 -2
- package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
- package/node_modules/@maka/core/dist/mcp.js +4 -1
- package/node_modules/@maka/core/dist/model-metadata.js +28 -1
- package/node_modules/@maka/core/dist/model-thinking.js +36 -1
- package/node_modules/@maka/core/dist/provider-registry.js +0 -1
- package/node_modules/@maka/core/dist/refresh-read-coordinator.js +62 -0
- package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
- package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
- package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -0
- package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
- package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
- package/node_modules/@maka/core/dist/session.js +19 -1
- package/node_modules/@maka/core/dist/settings.js +3 -0
- package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
- package/node_modules/@maka/core/dist/turn-origin.js +14 -0
- package/node_modules/@maka/core/package.json +3 -0
- package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
- package/node_modules/@maka/mcp/dist/index.js +31 -5
- package/node_modules/@maka/mcp/dist/oauth.js +54 -6
- package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
- package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +15 -60
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +18 -0
- package/node_modules/@maka/runtime/dist/apply-patch-profile.js +64 -14
- package/node_modules/@maka/runtime/dist/builtin-tools.js +3 -2
- package/node_modules/@maka/runtime/dist/codex-v4a-patch.js +18 -0
- package/node_modules/@maka/runtime/dist/conversation-copy.js +56 -1
- package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
- package/node_modules/@maka/runtime/dist/model-adapter.js +80 -3
- package/node_modules/@maka/runtime/dist/model-runtime.js +6 -0
- package/node_modules/@maka/runtime/dist/openai-apply-patch.js +5 -0
- package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +122 -3
- package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
- package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
- package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +184 -126
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
- package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
- package/node_modules/@maka/runtime-host/dist/client/capability-provider-service.js +2 -0
- package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +59 -20
- package/node_modules/@maka/runtime-host/dist/client/connection.js +6 -4
- package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
- package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +4 -4
- package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
- package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
- package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +20 -3
- package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +8 -5
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +20 -2
- package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +75 -5
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
- package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
- package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
- package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +234 -70
- package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +14 -8
- package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
- package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +188 -21
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
- package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
- package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +5 -1
- package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
- package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
- package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
- package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +91 -12
- package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/session-bundle-coordinator.js +4 -1
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +128 -13
- package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +33 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +4 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
- package/node_modules/@maka/runtime-host/package.json +2 -0
- package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
- package/node_modules/@maka/storage/dist/execution-stores.js +15 -3
- package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
- package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
- package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
- package/node_modules/@maka/storage/dist/plan-store.js +30 -0
- package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
- package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
- package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
- package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
- package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
- package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
- package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +724 -84
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +16 -0
- package/package.json +1 -1
package/dist/tui-mcp-control.js
CHANGED
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { createHash, randomUUID } from 'node:crypto';
|
|
20
|
-
import { MCP_CONFIG_VERSION,
|
|
20
|
+
import { MCP_CONFIG_VERSION, resolveMcpProtocolPreference, } from '@maka/core/mcp';
|
|
21
21
|
import { createCredentialMcpOAuthStorage, McpClientManager } from '@maka/mcp';
|
|
22
22
|
import { createFileCredentialStore } from '@maka/storage/credential-store';
|
|
23
|
-
import { AtomicFileWriteCommitUnknownError, createMcpConfigStore,
|
|
23
|
+
import { AtomicFileWriteCommitUnknownError, createMcpConfigStore, updateMcpConfiguration, McpConfigurationValidationError, McpConfigSourceError, normalizeMcpConfig, normalizeMcpImport, } from '@maka/storage/mcp-config-store';
|
|
24
24
|
import { createMcpCapabilityProvider } from './mcp-capability-provider.js';
|
|
25
|
+
import { McpCapabilityPublication } from './mcp-capability-publication.js';
|
|
25
26
|
const RUNTIME_HOST_CREDENTIAL_ENV = 'MAKA_RUNTIME_HOST_ACCESS_CREDENTIAL';
|
|
26
27
|
export function createTuiMcpController(input, overrides = {}) {
|
|
27
28
|
const manager = overrides.manager ??
|
|
@@ -42,6 +43,7 @@ class TuiMcpControllerImpl {
|
|
|
42
43
|
#listeners = new Set();
|
|
43
44
|
#disposeManagerChange;
|
|
44
45
|
#disposeConnectionAvailability;
|
|
46
|
+
#disposeConfigChanges;
|
|
45
47
|
#initialization;
|
|
46
48
|
#availability = { kind: 'unavailable' };
|
|
47
49
|
#closed = false;
|
|
@@ -49,9 +51,7 @@ class TuiMcpControllerImpl {
|
|
|
49
51
|
#preparedImport;
|
|
50
52
|
#actionLane = Promise.resolve();
|
|
51
53
|
#publicationSuppressed = false;
|
|
52
|
-
#
|
|
53
|
-
#publicationTask;
|
|
54
|
-
#published;
|
|
54
|
+
#publication;
|
|
55
55
|
#snapshot = freezeSnapshot({
|
|
56
56
|
initialization: 'loading',
|
|
57
57
|
configuration: 'synchronizing',
|
|
@@ -67,6 +67,24 @@ class TuiMcpControllerImpl {
|
|
|
67
67
|
...this.#snapshot,
|
|
68
68
|
canManagePublicationCredential: Boolean(connection.setCredential && connection.removeCredential),
|
|
69
69
|
});
|
|
70
|
+
this.#publication = new McpCapabilityPublication({
|
|
71
|
+
connectionIdentity: () => this.#availability.kind === 'connected'
|
|
72
|
+
? connectionIdentity(this.#availability)
|
|
73
|
+
: undefined,
|
|
74
|
+
revision: () => this.#deps.manager.toolSnapshot().revision,
|
|
75
|
+
createProvider: () => this.#deps.createProvider(this.#deps.manager),
|
|
76
|
+
replace: (provider) => this.#connection.replaceClientCapabilities(provider),
|
|
77
|
+
unregister: () => this.#connection.unregisterClientCapabilities(),
|
|
78
|
+
onState: (state) => {
|
|
79
|
+
this.#updateSnapshot({
|
|
80
|
+
publication: state === 'unavailable'
|
|
81
|
+
? this.#availability.kind === 'unavailable'
|
|
82
|
+
? (this.#availability.reason ?? 'host_unavailable')
|
|
83
|
+
: 'waiting'
|
|
84
|
+
: state,
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
});
|
|
70
88
|
this.#disposeManagerChange = deps.manager.onChange(() => {
|
|
71
89
|
try {
|
|
72
90
|
this.#refreshManagerSnapshot();
|
|
@@ -81,7 +99,7 @@ class TuiMcpControllerImpl {
|
|
|
81
99
|
this.#disposeConnectionAvailability = connection.subscribeConnectionAvailability((availability) => {
|
|
82
100
|
this.#availability = availability;
|
|
83
101
|
if (availability.kind === 'unavailable') {
|
|
84
|
-
this.#
|
|
102
|
+
this.#publication.invalidate();
|
|
85
103
|
this.#updateSnapshot({
|
|
86
104
|
publication: availability.reason ?? 'host_unavailable',
|
|
87
105
|
...(availability.reason === 'provider_conflict'
|
|
@@ -95,6 +113,17 @@ class TuiMcpControllerImpl {
|
|
|
95
113
|
this.#requestPublication();
|
|
96
114
|
}
|
|
97
115
|
});
|
|
116
|
+
// Desktop edits the same file. A change during startup waits for it
|
|
117
|
+
// rather than being dropped, since startup may have read the file first.
|
|
118
|
+
// Failing to follow leaves the TUI's own edits working, so it stays quiet
|
|
119
|
+
// rather than print over the screen.
|
|
120
|
+
this.#disposeConfigChanges = deps.configStore.subscribeChanges((error) => {
|
|
121
|
+
if (error)
|
|
122
|
+
return;
|
|
123
|
+
void this.#initialization
|
|
124
|
+
.then(() => this.#serializeAction(() => this.#followConfigChange()))
|
|
125
|
+
.catch(() => undefined);
|
|
126
|
+
});
|
|
98
127
|
this.#initialization = this.#initialize();
|
|
99
128
|
}
|
|
100
129
|
snapshot() {
|
|
@@ -159,17 +188,14 @@ class TuiMcpControllerImpl {
|
|
|
159
188
|
this.#closed = true;
|
|
160
189
|
this.#disposeManagerChange();
|
|
161
190
|
this.#disposeConnectionAvailability();
|
|
191
|
+
this.#disposeConfigChanges();
|
|
162
192
|
this.#listeners.clear();
|
|
163
193
|
this.#preparedImport = undefined;
|
|
164
|
-
this.#
|
|
194
|
+
const publicationClosing = this.#publication.close().catch(() => undefined);
|
|
165
195
|
const managerClosing = this.#deps.manager.close();
|
|
166
196
|
await this.#actionLane.catch(() => undefined);
|
|
167
197
|
this.#config = undefined;
|
|
168
|
-
await
|
|
169
|
-
if (this.#availability.kind === 'connected') {
|
|
170
|
-
await this.#connection.unregisterClientCapabilities().catch(() => undefined);
|
|
171
|
-
}
|
|
172
|
-
this.#published = undefined;
|
|
198
|
+
await publicationClosing;
|
|
173
199
|
await this.#connection.closePublication?.().catch(() => undefined);
|
|
174
200
|
await managerClosing;
|
|
175
201
|
await this.#initialization.catch(() => undefined);
|
|
@@ -251,44 +277,32 @@ class TuiMcpControllerImpl {
|
|
|
251
277
|
async #commitMutation(action) {
|
|
252
278
|
let committed;
|
|
253
279
|
try {
|
|
254
|
-
committed = await this.#deps.configStore
|
|
255
|
-
if (this.#closed)
|
|
280
|
+
committed = await updateMcpConfiguration(this.#deps.configStore, (current) => {
|
|
281
|
+
if (this.#closed)
|
|
256
282
|
throw new TuiMcpMutationError({ status: 'failed', reason: 'closed' });
|
|
257
|
-
}
|
|
258
283
|
const prepared = this.#prepareMutation(current, action);
|
|
259
284
|
if ('status' in prepared)
|
|
260
285
|
throw new TuiMcpMutationError(prepared);
|
|
261
|
-
|
|
286
|
+
return prepared.next;
|
|
287
|
+
}, async (serverId, previous) => {
|
|
262
288
|
try {
|
|
263
|
-
|
|
289
|
+
await this.#deps.manager.forgetServerCredentials(serverId, previous);
|
|
264
290
|
}
|
|
265
291
|
catch {
|
|
266
|
-
throw new TuiMcpMutationError({ status: 'failed', reason: 'invalid-config' });
|
|
267
|
-
}
|
|
268
|
-
try {
|
|
269
|
-
for (const [serverId, previous] of Object.entries(current.mcpServers)) {
|
|
270
|
-
if (!mcpConfigChangeRetiresCredentials(previous, next.mcpServers[serverId]))
|
|
271
|
-
continue;
|
|
272
|
-
await this.#deps.manager.forgetServerCredentials(serverId, previous);
|
|
273
|
-
if (this.#closed) {
|
|
274
|
-
throw new TuiMcpMutationError({ status: 'failed', reason: 'closed' });
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
catch (error) {
|
|
279
|
-
if (error instanceof TuiMcpMutationError)
|
|
280
|
-
throw error;
|
|
281
292
|
throw new TuiMcpMutationError({
|
|
282
293
|
status: 'failed',
|
|
283
294
|
reason: 'credential-cleanup-failed',
|
|
284
295
|
});
|
|
285
296
|
}
|
|
286
|
-
|
|
297
|
+
if (this.#closed)
|
|
298
|
+
throw new TuiMcpMutationError({ status: 'failed', reason: 'closed' });
|
|
287
299
|
});
|
|
288
300
|
}
|
|
289
301
|
catch (error) {
|
|
290
302
|
if (error instanceof TuiMcpMutationError)
|
|
291
303
|
return error.result;
|
|
304
|
+
if (error instanceof McpConfigurationValidationError)
|
|
305
|
+
return { status: 'failed', reason: 'invalid-config' };
|
|
292
306
|
if (error instanceof AtomicFileWriteCommitUnknownError) {
|
|
293
307
|
// The transform has already published, including any credential
|
|
294
308
|
// retirement. Reload its authority; never replay those effects.
|
|
@@ -323,12 +337,24 @@ class TuiMcpControllerImpl {
|
|
|
323
337
|
}
|
|
324
338
|
return { status: 'failed', reason: 'persist-failed' };
|
|
325
339
|
}
|
|
340
|
+
this.#preparedImport = undefined;
|
|
326
341
|
return (await this.#synchronizeCommittedConfig(committed)).result;
|
|
327
342
|
}
|
|
343
|
+
async #followConfigChange() {
|
|
344
|
+
if (this.#closed)
|
|
345
|
+
return;
|
|
346
|
+
if (this.#snapshot.initialization === 'error')
|
|
347
|
+
return this.#initialize();
|
|
348
|
+
const latest = await this.#deps.configStore.get();
|
|
349
|
+
// An import preview survives: its commit re-checks each server it replaces.
|
|
350
|
+
if (this.#snapshot.configuration !== 'ready' ||
|
|
351
|
+
JSON.stringify(latest) !== JSON.stringify(this.#config)) {
|
|
352
|
+
await this.#synchronizeCommittedConfig(latest);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
328
355
|
async #synchronizeCommittedConfig(committed) {
|
|
329
356
|
if (this.#closed)
|
|
330
357
|
return { result: { status: 'failed', reason: 'closed' } };
|
|
331
|
-
this.#preparedImport = undefined;
|
|
332
358
|
this.#config = cloneConfig(committed);
|
|
333
359
|
this.#updateSnapshot({ configuration: 'synchronizing' });
|
|
334
360
|
this.#refreshManagerSnapshot();
|
|
@@ -400,9 +426,10 @@ class TuiMcpControllerImpl {
|
|
|
400
426
|
}
|
|
401
427
|
}
|
|
402
428
|
async #settlePublication() {
|
|
403
|
-
this.#
|
|
404
|
-
|
|
405
|
-
|
|
429
|
+
if (!this.#closed &&
|
|
430
|
+
this.#snapshot.initialization === 'ready' &&
|
|
431
|
+
!this.#publicationSuppressed) {
|
|
432
|
+
await this.#publication.settle();
|
|
406
433
|
}
|
|
407
434
|
if (this.#snapshot.publication === 'error' ||
|
|
408
435
|
this.#snapshot.publication === 'credential_rejected' ||
|
|
@@ -436,74 +463,9 @@ class TuiMcpControllerImpl {
|
|
|
436
463
|
this.#notify();
|
|
437
464
|
}
|
|
438
465
|
#requestPublication() {
|
|
439
|
-
if (this.#closed)
|
|
440
|
-
this.#publicationRequested = false;
|
|
466
|
+
if (this.#closed || this.#snapshot.initialization !== 'ready' || this.#publicationSuppressed)
|
|
441
467
|
return;
|
|
442
|
-
|
|
443
|
-
if (this.#snapshot.initialization !== 'ready' || this.#publicationSuppressed)
|
|
444
|
-
return;
|
|
445
|
-
this.#publicationRequested = true;
|
|
446
|
-
if (this.#publicationTask)
|
|
447
|
-
return;
|
|
448
|
-
this.#publicationTask = this.#runPublicationQueue().finally(() => {
|
|
449
|
-
this.#publicationTask = undefined;
|
|
450
|
-
if (this.#publicationRequested)
|
|
451
|
-
this.#requestPublication();
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
async #runPublicationQueue() {
|
|
455
|
-
while (this.#publicationRequested && !this.#closed) {
|
|
456
|
-
this.#publicationRequested = false;
|
|
457
|
-
await this.#publishCurrentSnapshot();
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
async #publishCurrentSnapshot() {
|
|
461
|
-
const availability = this.#availability;
|
|
462
|
-
if (availability.kind !== 'connected') {
|
|
463
|
-
this.#updateSnapshot({ publication: availability.reason ?? 'host_unavailable' });
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
const identity = connectionIdentity(availability);
|
|
467
|
-
const revision = this.#deps.manager.toolSnapshot().revision;
|
|
468
|
-
if (this.#published?.identity === identity && this.#published.revision === revision) {
|
|
469
|
-
this.#updateSnapshot({
|
|
470
|
-
publication: this.#snapshot.toolCount === 0 ? 'not_published' : 'published',
|
|
471
|
-
});
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
let provider;
|
|
475
|
-
this.#updateSnapshot({ publication: 'publishing' });
|
|
476
|
-
try {
|
|
477
|
-
provider = this.#deps.createProvider(this.#deps.manager);
|
|
478
|
-
if (provider) {
|
|
479
|
-
await this.#connection.replaceClientCapabilities(provider);
|
|
480
|
-
}
|
|
481
|
-
else if (this.#published?.identity === identity && this.#published.registered) {
|
|
482
|
-
await this.#connection.unregisterClientCapabilities();
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
catch {
|
|
486
|
-
await closeProvider(provider);
|
|
487
|
-
if (this.#isCurrent(identity, revision)) {
|
|
488
|
-
this.#updateSnapshot({ publication: 'error' });
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
491
|
-
this.#requestPublication();
|
|
492
|
-
}
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
if (!this.#isCurrent(identity, revision)) {
|
|
496
|
-
this.#requestPublication();
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
this.#published = { identity, revision, registered: provider !== undefined };
|
|
500
|
-
this.#updateSnapshot({ publication: provider ? 'published' : 'not_published' });
|
|
501
|
-
}
|
|
502
|
-
#isCurrent(identity, revision) {
|
|
503
|
-
return (!this.#closed &&
|
|
504
|
-
this.#availability.kind === 'connected' &&
|
|
505
|
-
connectionIdentity(this.#availability) === identity &&
|
|
506
|
-
this.#deps.manager.toolSnapshot().revision === revision);
|
|
468
|
+
this.#publication.request();
|
|
507
469
|
}
|
|
508
470
|
#updateSnapshot(update) {
|
|
509
471
|
this.#snapshot = freezeSnapshot({ ...this.#snapshot, ...update });
|
|
@@ -548,14 +510,6 @@ function freezeSnapshot(snapshot) {
|
|
|
548
510
|
function connectionIdentity(availability) {
|
|
549
511
|
return `${availability.hostEpoch}\0${availability.connectionId}`;
|
|
550
512
|
}
|
|
551
|
-
async function closeProvider(provider) {
|
|
552
|
-
try {
|
|
553
|
-
await provider?.close?.();
|
|
554
|
-
}
|
|
555
|
-
catch {
|
|
556
|
-
// A rejected provider never crossed into Host ownership.
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
513
|
function cloneConfig(config) {
|
|
560
514
|
return structuredClone(config);
|
|
561
515
|
}
|
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
export class AcpRuntimeError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
constructor(message, code) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = 'AcpRuntimeError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { constants } from 'node:fs';
|
|
20
|
+
import { lstat, open, realpath, stat } from 'node:fs/promises';
|
|
21
|
+
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
22
|
+
const MAX_TEXT_FILE_BYTES = 8 * 1024 * 1024;
|
|
23
|
+
export async function readWorkspaceTextFile(cwd, path, line, limit) {
|
|
24
|
+
if (line != null && (!Number.isSafeInteger(line) || line < 1))
|
|
25
|
+
throw new Error('ACP file line must be a positive integer');
|
|
26
|
+
if (limit != null && (!Number.isSafeInteger(limit) || limit < 0))
|
|
27
|
+
throw new Error('ACP file limit must be a non-negative integer');
|
|
28
|
+
const file = await openWorkspaceFile(cwd, path, false);
|
|
29
|
+
try {
|
|
30
|
+
const info = await file.stat();
|
|
31
|
+
if (info.size > MAX_TEXT_FILE_BYTES)
|
|
32
|
+
throw new Error('ACP text file is too large');
|
|
33
|
+
const text = await file.readFile('utf8');
|
|
34
|
+
const start = line == null ? 0 : line - 1;
|
|
35
|
+
return line != null || limit != null
|
|
36
|
+
? text
|
|
37
|
+
.split('\n')
|
|
38
|
+
.slice(start, limit == null ? undefined : start + limit)
|
|
39
|
+
.join('\n')
|
|
40
|
+
: text;
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await file.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function writeWorkspaceTextFile(cwd, path, content) {
|
|
47
|
+
if (Buffer.byteLength(content) > MAX_TEXT_FILE_BYTES)
|
|
48
|
+
throw new Error('ACP text file is too large');
|
|
49
|
+
const file = await openWorkspaceFile(cwd, path, true);
|
|
50
|
+
try {
|
|
51
|
+
// Validation happens against this exact open file descriptor before any
|
|
52
|
+
// existing content is truncated, so later path swaps cannot redirect data.
|
|
53
|
+
await file.truncate(0);
|
|
54
|
+
await file.writeFile(content, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await file.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function openWorkspaceFile(cwd, path, forWrite) {
|
|
61
|
+
if (!isAbsolute(path))
|
|
62
|
+
throw new Error('ACP file path must be absolute');
|
|
63
|
+
const root = await realpath(cwd);
|
|
64
|
+
const candidate = resolve(path);
|
|
65
|
+
if (forWrite) {
|
|
66
|
+
const parent = await realpath(dirname(candidate));
|
|
67
|
+
assertContained(root, parent);
|
|
68
|
+
const entry = await lstat(candidate).catch((error) => {
|
|
69
|
+
if (error.code !== 'ENOENT')
|
|
70
|
+
throw error;
|
|
71
|
+
return undefined;
|
|
72
|
+
});
|
|
73
|
+
if (entry?.isSymbolicLink())
|
|
74
|
+
throw new Error('ACP file path could not be resolved');
|
|
75
|
+
}
|
|
76
|
+
const flags = forWrite
|
|
77
|
+
? constants.O_WRONLY | constants.O_CREAT | (constants.O_NOFOLLOW ?? 0)
|
|
78
|
+
: constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0);
|
|
79
|
+
const file = await open(candidate, flags, 0o600);
|
|
80
|
+
try {
|
|
81
|
+
const opened = await file.stat();
|
|
82
|
+
if (!opened.isFile())
|
|
83
|
+
throw new Error('ACP file path is not a regular file');
|
|
84
|
+
// Resolve and compare after opening. If the path or an ancestor changed
|
|
85
|
+
// between lookup and open, either containment or file identity no longer
|
|
86
|
+
// matches. Operations after this point use only the verified descriptor.
|
|
87
|
+
const canonical = await realpath(candidate);
|
|
88
|
+
assertContained(root, canonical);
|
|
89
|
+
const current = await stat(canonical);
|
|
90
|
+
if (opened.dev !== current.dev || opened.ino !== current.ino)
|
|
91
|
+
throw new Error('ACP file path changed during validation');
|
|
92
|
+
return file;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
await file.close();
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function assertContained(root, candidate) {
|
|
100
|
+
const relation = relative(root, candidate);
|
|
101
|
+
if (relation === '..' || relation.startsWith('../') || isAbsolute(relation))
|
|
102
|
+
throw new Error('ACP file path leaves the workspace');
|
|
103
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { Readable, Writable } from 'node:stream';
|
|
21
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
22
|
+
import { client, ndJsonStream, } from '@agentclientprotocol/sdk';
|
|
23
|
+
import { terminateProcessTree } from '@maka/runtime/process-tree-terminator';
|
|
24
|
+
import { AcpRuntimeError } from './acp-errors.js';
|
|
25
|
+
const PROCESS_EXIT_TIMEOUT_MS = 2_000;
|
|
26
|
+
export function createAcpConnection(input) {
|
|
27
|
+
let child;
|
|
28
|
+
try {
|
|
29
|
+
child = spawn(input.executable, [...(input.args ?? [])], {
|
|
30
|
+
cwd: input.cwd,
|
|
31
|
+
env: input.env,
|
|
32
|
+
stdio: 'pipe',
|
|
33
|
+
detached: true,
|
|
34
|
+
shell: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new AcpRuntimeError('ACP executable is unavailable', 'acp_executable_unavailable');
|
|
39
|
+
}
|
|
40
|
+
let disposing = false;
|
|
41
|
+
let disposed = false;
|
|
42
|
+
let disposal;
|
|
43
|
+
let rejectFailure;
|
|
44
|
+
const failed = new Promise((_resolve, reject) => {
|
|
45
|
+
rejectFailure = reject;
|
|
46
|
+
});
|
|
47
|
+
void failed.catch(() => undefined);
|
|
48
|
+
const fail = () => {
|
|
49
|
+
if (!disposing)
|
|
50
|
+
rejectFailure(new Error('ACP connection failed'));
|
|
51
|
+
};
|
|
52
|
+
child.once('error', fail);
|
|
53
|
+
child.stdin.once('error', fail);
|
|
54
|
+
child.stderr.once('error', fail);
|
|
55
|
+
child.stderr.on('data', () => undefined);
|
|
56
|
+
child.once('close', fail);
|
|
57
|
+
const app = client({ name: input.clientName });
|
|
58
|
+
input.configureClient(app);
|
|
59
|
+
const connection = app.connect(ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout)));
|
|
60
|
+
void connection.closed.then(fail, fail);
|
|
61
|
+
return {
|
|
62
|
+
connection,
|
|
63
|
+
failed,
|
|
64
|
+
dispose() {
|
|
65
|
+
if (disposed)
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
if (disposal)
|
|
68
|
+
return disposal;
|
|
69
|
+
disposing = true;
|
|
70
|
+
disposal = terminate(child, connection).then(() => {
|
|
71
|
+
disposed = true;
|
|
72
|
+
}, (error) => {
|
|
73
|
+
disposal = undefined;
|
|
74
|
+
throw error;
|
|
75
|
+
});
|
|
76
|
+
return disposal;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function terminate(child, connection) {
|
|
81
|
+
const pid = child.pid;
|
|
82
|
+
const alive = () => {
|
|
83
|
+
if (!pid)
|
|
84
|
+
return false;
|
|
85
|
+
if (process.platform === 'win32')
|
|
86
|
+
return child.exitCode === null && child.signalCode === null;
|
|
87
|
+
try {
|
|
88
|
+
process.kill(-pid, 0);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return error.code !== 'ESRCH';
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
try {
|
|
96
|
+
if (pid) {
|
|
97
|
+
await terminateProcessTree({ pid, signal: 'SIGTERM', fallback: () => child.kill('SIGTERM') });
|
|
98
|
+
for (let elapsed = 0; elapsed < PROCESS_EXIT_TIMEOUT_MS && alive(); elapsed += 50)
|
|
99
|
+
await delay(50);
|
|
100
|
+
if (alive()) {
|
|
101
|
+
await terminateProcessTree({
|
|
102
|
+
pid,
|
|
103
|
+
signal: 'SIGKILL',
|
|
104
|
+
fallback: () => child.kill('SIGKILL'),
|
|
105
|
+
});
|
|
106
|
+
for (let elapsed = 0; elapsed < PROCESS_EXIT_TIMEOUT_MS && alive(); elapsed += 50)
|
|
107
|
+
await delay(50);
|
|
108
|
+
}
|
|
109
|
+
if (alive())
|
|
110
|
+
throw new Error('ACP process cleanup failed');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
connection.close();
|
|
115
|
+
child.stdin.destroy();
|
|
116
|
+
child.stdout.destroy();
|
|
117
|
+
child.stderr.destroy();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
const MAX_EVENT_TEXT = 8_192;
|
|
20
|
+
const MAX_TOOL_RESULT_DIFF = 1024 * 1024;
|
|
21
|
+
export function promptText(request) {
|
|
22
|
+
const sections = [request.text];
|
|
23
|
+
if (request.instructions)
|
|
24
|
+
sections.push(`Agent instructions:\n${request.instructions}`);
|
|
25
|
+
for (const quote of request.quotes ?? [])
|
|
26
|
+
sections.push(`Quoted context:\n${quote.text}`);
|
|
27
|
+
for (const reference of request.directoryReferences ?? [])
|
|
28
|
+
sections.push(`Project directory reference: ${reference.path}`);
|
|
29
|
+
return sections.filter(Boolean).join('\n\n');
|
|
30
|
+
}
|
|
31
|
+
export function emitText(context, type, text) {
|
|
32
|
+
const safeText = text.replaceAll('\r', '');
|
|
33
|
+
for (let offset = 0; offset < safeText.length; offset += MAX_EVENT_TEXT) {
|
|
34
|
+
const chunk = safeText.slice(offset, offset + MAX_EVENT_TEXT);
|
|
35
|
+
context.emit({ type, text: chunk });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function emitToolOutput(context, toolCallId, text) {
|
|
39
|
+
const safeText = text.replaceAll('\r', '');
|
|
40
|
+
for (let offset = 0; offset < safeText.length; offset += MAX_EVENT_TEXT) {
|
|
41
|
+
context.emit({
|
|
42
|
+
type: 'tool_output_delta',
|
|
43
|
+
toolCallId,
|
|
44
|
+
text: safeText.slice(offset, offset + MAX_EVENT_TEXT),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function projectToolResult(content, rawOutput) {
|
|
49
|
+
const diffs = content.flatMap((item) => item.type === 'diff'
|
|
50
|
+
? [
|
|
51
|
+
{
|
|
52
|
+
path: item.path,
|
|
53
|
+
diff: createWholeFileDiff(item.path, item.oldText ?? '', item.newText ?? ''),
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
: []);
|
|
57
|
+
const combinedDiff = diffs.map(({ diff }) => diff).join('');
|
|
58
|
+
if (diffs.length && diffs.length <= 64 && combinedDiff.length <= MAX_TOOL_RESULT_DIFF)
|
|
59
|
+
return {
|
|
60
|
+
kind: 'file_diff',
|
|
61
|
+
paths: diffs.map(({ path }) => path),
|
|
62
|
+
diff: combinedDiff,
|
|
63
|
+
};
|
|
64
|
+
if (diffs.length)
|
|
65
|
+
return {
|
|
66
|
+
kind: 'text',
|
|
67
|
+
text: boundedText(`${diffs.map(({ path }) => `Updated ${path}`).join('\n')}\nDiff omitted because it exceeds the executor event limit.`),
|
|
68
|
+
};
|
|
69
|
+
return { kind: 'text', text: boundedText(summarizeToolResult(content, rawOutput)) };
|
|
70
|
+
}
|
|
71
|
+
function createWholeFileDiff(path, oldText, newText) {
|
|
72
|
+
const oldFile = diffLines(oldText);
|
|
73
|
+
const newFile = diffLines(newText);
|
|
74
|
+
const oldLines = oldFile.lines;
|
|
75
|
+
const newLines = newFile.lines;
|
|
76
|
+
return ([
|
|
77
|
+
`--- a/${path}`,
|
|
78
|
+
`+++ b/${path}`,
|
|
79
|
+
`@@ -${oldLines.length ? 1 : 0},${oldLines.length} +${newLines.length ? 1 : 0},${newLines.length} @@`,
|
|
80
|
+
...diffSide(oldLines, '-', oldFile.endsWithNewline),
|
|
81
|
+
...diffSide(newLines, '+', newFile.endsWithNewline),
|
|
82
|
+
].join('\n') + '\n');
|
|
83
|
+
}
|
|
84
|
+
function diffLines(text) {
|
|
85
|
+
const normalized = text.replaceAll('\r', '');
|
|
86
|
+
if (!normalized)
|
|
87
|
+
return { lines: [], endsWithNewline: true };
|
|
88
|
+
const endsWithNewline = normalized.endsWith('\n');
|
|
89
|
+
const lines = normalized.split('\n');
|
|
90
|
+
if (endsWithNewline)
|
|
91
|
+
lines.pop();
|
|
92
|
+
return { lines, endsWithNewline };
|
|
93
|
+
}
|
|
94
|
+
function diffSide(lines, prefix, endsWithNewline) {
|
|
95
|
+
return lines.flatMap((line, index) => [
|
|
96
|
+
`${prefix}${line}`,
|
|
97
|
+
...(index === lines.length - 1 && !endsWithNewline ? ['\'] : []),
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
export function summarizeToolContent(content) {
|
|
101
|
+
return content
|
|
102
|
+
.map((item) => {
|
|
103
|
+
if (item.type === 'diff')
|
|
104
|
+
return `Updated ${item.path}`;
|
|
105
|
+
if (item.type === 'terminal')
|
|
106
|
+
return item.terminalId ? `Terminal ${item.terminalId}` : '';
|
|
107
|
+
return item.content.type === 'text' ? item.content.text : '';
|
|
108
|
+
})
|
|
109
|
+
.filter(Boolean)
|
|
110
|
+
.join('\n');
|
|
111
|
+
}
|
|
112
|
+
function summarizeToolResult(content, rawOutput) {
|
|
113
|
+
const summary = summarizeToolContent(content);
|
|
114
|
+
if (summary)
|
|
115
|
+
return summary;
|
|
116
|
+
if (rawOutput === undefined)
|
|
117
|
+
return '';
|
|
118
|
+
try {
|
|
119
|
+
return JSON.stringify(rawOutput);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return 'External tool completed';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function boundedText(value) {
|
|
126
|
+
const safe = value.replace(/[\0\r]/gu, '');
|
|
127
|
+
return safe.length <= MAX_EVENT_TEXT ? safe : `${safe.slice(0, MAX_EVENT_TEXT - 1)}…`;
|
|
128
|
+
}
|
|
129
|
+
export function toolName(value) {
|
|
130
|
+
return value.replace(/[\0\r\n]/gu, '').slice(0, 256) || 'external_tool';
|
|
131
|
+
}
|
|
132
|
+
export function activityKind(kind) {
|
|
133
|
+
if (kind === 'read')
|
|
134
|
+
return 'read';
|
|
135
|
+
if (kind === 'edit' || kind === 'delete' || kind === 'move')
|
|
136
|
+
return 'edit';
|
|
137
|
+
if (kind === 'search')
|
|
138
|
+
return 'search';
|
|
139
|
+
if (kind === 'fetch')
|
|
140
|
+
return 'webfetch';
|
|
141
|
+
if (kind === 'execute')
|
|
142
|
+
return 'command';
|
|
143
|
+
if (kind === 'think')
|
|
144
|
+
return 'explore';
|
|
145
|
+
return 'tool';
|
|
146
|
+
}
|