@adecore/agents 0.38.0 → 0.40.0
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/async.d.ts +5 -0
- package/dist/async.js +13 -5
- package/dist/chat/acp-protocol.js +3 -5
- package/dist/chat/chat-core.d.ts +1 -0
- package/dist/chat/chat-core.js +9 -2
- package/dist/chat/chat-process.d.ts +1 -0
- package/dist/chat/chat-process.js +1 -1
- package/dist/chat/chat-session.d.ts +2 -0
- package/dist/chat/chat-session.js +9 -10
- package/dist/chat/claude-protocol.js +1 -6
- package/dist/chat/claude-transcript.js +1 -3
- package/dist/chat/codex-protocol.js +1 -6
- package/dist/chat/projector.js +1 -3
- package/dist/chat/request-summary.js +1 -3
- package/dist/coded-error.js +1 -5
- package/dist/context/argv.js +1 -2
- package/dist/context/verb.js +1 -3
- package/dist/error-text.d.ts +1 -0
- package/dist/error-text.js +4 -0
- package/dist/fields.d.ts +3 -0
- package/dist/fields.js +12 -0
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +8 -4
- package/dist/lineage.js +11 -5
- package/dist/messages/notice-store.js +3 -4
- package/dist/providers/accounts/acp-account.js +3 -20
- package/dist/providers/accounts/service.d.ts +1 -2
- package/dist/providers/accounts/service.js +5 -9
- package/dist/providers/accounts/status.js +1 -3
- package/dist/providers/acp-agent-state.js +1 -3
- package/dist/providers/claude.js +0 -1
- package/dist/providers/codex.js +0 -1
- package/dist/providers/detect.js +1 -1
- package/dist/providers/shell-commands.js +1 -2
- package/dist/run-process.js +1 -3
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -8
- package/dist/tasks/background-limit.js +3 -2
- package/dist/tasks/task-coordinator.js +11 -17
- package/dist/testing/watch.js +0 -1
- package/dist/usage/limits/monitor.js +1 -2
- package/dist/usage/readers/claude.js +3 -10
- package/dist/usage/readers/codex.js +2 -9
- package/dist/usage/readers/grok.js +3 -10
- package/dist/usage/readers/host-log.js +3 -10
- package/dist/usage/record.d.ts +2 -0
- package/dist/usage/record.js +13 -0
- package/package.json +6 -6
- package/src/async.ts +23 -5
- package/src/chat/acp-backend.ts +121 -0
- package/src/chat/acp-protocol.ts +172 -6
- package/src/chat/attachment-store.ts +21 -0
- package/src/chat/backend.ts +0 -2
- package/src/chat/bookmark-store.ts +23 -0
- package/src/chat/chat-core.ts +218 -7
- package/src/chat/chat-log.ts +19 -0
- package/src/chat/chat-process.ts +52 -1
- package/src/chat/chat-session.ts +414 -13
- package/src/chat/chat-store.ts +50 -0
- package/src/chat/chat-title.ts +9 -0
- package/src/chat/claude-backend.ts +50 -0
- package/src/chat/claude-protocol.ts +155 -8
- package/src/chat/claude-title.ts +10 -0
- package/src/chat/claude-transcript.ts +47 -4
- package/src/chat/codex-backend.ts +131 -0
- package/src/chat/codex-protocol.ts +180 -8
- package/src/chat/codex-thread.ts +23 -0
- package/src/chat/composer-preferences.ts +6 -0
- package/src/chat/context-breakdown.ts +11 -0
- package/src/chat/delta-coalescer.ts +8 -0
- package/src/chat/external-sessions.ts +28 -0
- package/src/chat/generated-image.ts +25 -0
- package/src/chat/index.ts +5 -0
- package/src/chat/input.ts +13 -0
- package/src/chat/json-rpc-transport.ts +28 -0
- package/src/chat/limit-resume.ts +13 -0
- package/src/chat/projector.ts +160 -4
- package/src/chat/readable-text.ts +3 -0
- package/src/chat/request-summary.ts +18 -4
- package/src/chat/subagent-projection.ts +3 -0
- package/src/chat/subagent-reader.ts +76 -0
- package/src/chat/subagent-settlement.ts +24 -0
- package/src/chat/thread-search.ts +13 -0
- package/src/chat/thread.ts +31 -0
- package/src/chat/ui-queries.ts +122 -0
- package/src/chat/ui-replies.ts +34 -0
- package/src/chat/visual-store.ts +49 -0
- package/src/chat/wake-chat.ts +3 -0
- package/src/client-sinks.ts +2 -0
- package/src/coded-error.ts +1 -5
- package/src/context/argv.ts +18 -2
- package/src/context/index.ts +1 -0
- package/src/context/refusal.ts +3 -0
- package/src/context/verb.ts +32 -4
- package/src/coordination/index.ts +10 -0
- package/src/error-text.ts +7 -0
- package/src/fields.ts +14 -0
- package/src/fs.ts +20 -4
- package/src/host/agent-host.ts +20 -0
- package/src/host/environment.ts +6 -0
- package/src/host/handlers.ts +18 -0
- package/src/host/memory-port.ts +4 -0
- package/src/host/wiring.ts +10 -0
- package/src/index.ts +1 -0
- package/src/lineage.ts +24 -5
- package/src/messages/deliver-message.ts +6 -0
- package/src/messages/deliver-notice.ts +13 -0
- package/src/messages/notice-store.ts +16 -4
- package/src/modes.ts +1 -0
- package/src/outbox/outbox-worker.ts +31 -0
- package/src/outbox/outbox.ts +5 -0
- package/src/providers/accounts/accounts.ts +36 -0
- package/src/providers/accounts/acp-account.ts +22 -27
- package/src/providers/accounts/launch.ts +4 -0
- package/src/providers/accounts/service.ts +110 -12
- package/src/providers/accounts/shadow-home.ts +10 -0
- package/src/providers/accounts/status.ts +13 -4
- package/src/providers/accounts/store.ts +25 -0
- package/src/providers/accounts/variables.ts +11 -1
- package/src/providers/acp-agent-state.ts +24 -3
- package/src/providers/acp-provider.ts +5 -0
- package/src/providers/acp-usage-log.ts +2 -0
- package/src/providers/catalog.ts +17 -0
- package/src/providers/claude.ts +10 -1
- package/src/providers/codex-rules.ts +3 -0
- package/src/providers/codex.ts +2 -1
- package/src/providers/detect.ts +6 -1
- package/src/providers/provider.ts +1 -1
- package/src/providers/registry.ts +18 -2
- package/src/providers/shell-commands.ts +37 -2
- package/src/record-directory.ts +19 -0
- package/src/run-process.ts +24 -3
- package/src/serializer.ts +3 -0
- package/src/skills.ts +73 -10
- package/src/tasks/background-limit.ts +9 -2
- package/src/tasks/end-children.ts +9 -0
- package/src/tasks/give-task.ts +8 -0
- package/src/tasks/task-coordinator.ts +81 -17
- package/src/tasks/task-store.ts +28 -0
- package/src/tasks/waiting-child.ts +21 -0
- package/src/tasks/wake-parent.ts +26 -0
- package/src/tasks/wiring.ts +22 -0
- package/src/testing/accounts.ts +2 -0
- package/src/testing/fake-acp-capture.ts +16 -0
- package/src/testing/fake-acp.ts +66 -0
- package/src/testing/fake-claude.ts +177 -0
- package/src/testing/fake-cli.ts +29 -0
- package/src/testing/fake-codex.ts +122 -0
- package/src/testing/manual-clock.ts +2 -0
- package/src/testing/watch.ts +7 -1
- package/src/title-file.ts +12 -0
- package/src/usage/accounts.ts +6 -0
- package/src/usage/aggregate.ts +19 -0
- package/src/usage/exchange.ts +12 -0
- package/src/usage/index-file.ts +27 -0
- package/src/usage/limits/monitor.ts +31 -3
- package/src/usage/limits/normalize.ts +30 -0
- package/src/usage/limits/probe.ts +17 -0
- package/src/usage/pricing.ts +35 -0
- package/src/usage/projects.ts +8 -0
- package/src/usage/readers/claude.ts +12 -9
- package/src/usage/readers/codex.ts +40 -8
- package/src/usage/readers/grok.ts +17 -9
- package/src/usage/readers/host-log.ts +9 -9
- package/src/usage/record.ts +22 -0
- package/src/usage/roots.ts +6 -0
- package/src/usage/scanner.ts +61 -0
- package/src/usage/usage-service.ts +14 -0
- package/src/watch-seam.ts +8 -0
- package/src/windows-command.ts +12 -0
package/src/chat/acp-backend.ts
CHANGED
|
@@ -92,6 +92,7 @@ const MAX_EMBEDDED_BYTES = 1024 * 1024;
|
|
|
92
92
|
|
|
93
93
|
function inside(folder: string, path: string): boolean {
|
|
94
94
|
const rest = relative(folder, path);
|
|
95
|
+
|
|
95
96
|
return rest === '' || (!rest.startsWith('..') && !isAbsolute(rest));
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -144,6 +145,7 @@ export class AcpBackend implements ChatBackend {
|
|
|
144
145
|
if (this.transport) {
|
|
145
146
|
return;
|
|
146
147
|
}
|
|
148
|
+
|
|
147
149
|
const transport = new JsonRpcTransport({
|
|
148
150
|
command: this.launch.command,
|
|
149
151
|
cwd: this.launch.cwd,
|
|
@@ -157,7 +159,9 @@ export class AcpBackend implements ChatBackend {
|
|
|
157
159
|
onFrame: (frame) => this.handleFrame(transport, frame),
|
|
158
160
|
onExit: (exitCode, stderr) => this.handleExit(transport, exitCode, stderr)
|
|
159
161
|
});
|
|
162
|
+
|
|
160
163
|
this.transport = transport;
|
|
164
|
+
|
|
161
165
|
try {
|
|
162
166
|
await this.handshake(transport);
|
|
163
167
|
} catch (error) {
|
|
@@ -166,6 +170,7 @@ export class AcpBackend implements ChatBackend {
|
|
|
166
170
|
this.transport = null;
|
|
167
171
|
void transport.dispose();
|
|
168
172
|
}
|
|
173
|
+
|
|
169
174
|
throw error;
|
|
170
175
|
}
|
|
171
176
|
}
|
|
@@ -178,9 +183,11 @@ export class AcpBackend implements ChatBackend {
|
|
|
178
183
|
clientInfo: { name: this.client.name, title: nameOf(this.client), version: this.client.version }
|
|
179
184
|
})
|
|
180
185
|
);
|
|
186
|
+
|
|
181
187
|
if (capabilities.protocolVersion !== ACP_PROTOCOL_VERSION) {
|
|
182
188
|
throw new Error(`${this.spec.name} speaks ACP version ${capabilities.protocolVersion}; only version ${ACP_PROTOCOL_VERSION} is supported`);
|
|
183
189
|
}
|
|
190
|
+
|
|
184
191
|
this.capabilities = capabilities;
|
|
185
192
|
const info = await this.openSession(transport, capabilities);
|
|
186
193
|
const modeId = await this.applyMode(transport, info);
|
|
@@ -188,6 +195,7 @@ export class AcpBackend implements ChatBackend {
|
|
|
188
195
|
this.model = model;
|
|
189
196
|
await this.applyThoughtLevel(transport, thoughtLevel);
|
|
190
197
|
this.spec.onSession?.(info);
|
|
198
|
+
|
|
191
199
|
this.emit({
|
|
192
200
|
type: 'session',
|
|
193
201
|
agentSessionId: this.sessionId,
|
|
@@ -195,6 +203,7 @@ export class AcpBackend implements ChatBackend {
|
|
|
195
203
|
effectiveRuntimeMode: this.launch.runtimeMode,
|
|
196
204
|
...(modeId === null ? {} : { permissionMode: modeId })
|
|
197
205
|
});
|
|
206
|
+
|
|
198
207
|
for (const params of this.early.splice(0)) {
|
|
199
208
|
this.emitAll(this.protocol.update(this.sessionId, params));
|
|
200
209
|
}
|
|
@@ -202,6 +211,7 @@ export class AcpBackend implements ChatBackend {
|
|
|
202
211
|
|
|
203
212
|
private sessionParams(): Record<string, unknown> {
|
|
204
213
|
const folders = this.launch.folders ?? [];
|
|
214
|
+
|
|
205
215
|
return {
|
|
206
216
|
cwd: this.launch.cwd,
|
|
207
217
|
mcpServers: [],
|
|
@@ -211,8 +221,10 @@ export class AcpBackend implements ChatBackend {
|
|
|
211
221
|
|
|
212
222
|
private async openSession(transport: JsonRpcTransport, capabilities: AcpAgentCapabilities): Promise<AcpSessionInfo> {
|
|
213
223
|
const resume = this.launch.resume;
|
|
224
|
+
|
|
214
225
|
if (resume) {
|
|
215
226
|
const method = capabilities.resumeSession ? 'session/resume' : capabilities.loadSession ? 'session/load' : null;
|
|
227
|
+
|
|
216
228
|
if (method === null) {
|
|
217
229
|
this.emit({ type: 'note', level: 'warning', text: `${this.spec.name} cannot continue a session it ran before. Started a new one.` });
|
|
218
230
|
} else {
|
|
@@ -221,12 +233,14 @@ export class AcpBackend implements ChatBackend {
|
|
|
221
233
|
const result = await this.authenticated(transport, method, { sessionId: resume, ...this.sessionParams() });
|
|
222
234
|
this.sessionId = resume;
|
|
223
235
|
this.contextPending = [this.launch.resumeInstructions ?? null, this.launch.resumeNote].filter((part) => part !== null).join('\n\n') || null;
|
|
236
|
+
|
|
224
237
|
return parseSessionInfo(result, resume, capabilities.agent);
|
|
225
238
|
} catch (error) {
|
|
226
239
|
// A refusal means the agent no longer has the session and a fresh one keeps the chat usable; a silence or an exit says nothing about it.
|
|
227
240
|
if (!(error instanceof JsonRpcError)) {
|
|
228
241
|
throw error;
|
|
229
242
|
}
|
|
243
|
+
|
|
230
244
|
this.emit({
|
|
231
245
|
type: 'note',
|
|
232
246
|
level: 'warning',
|
|
@@ -237,13 +251,17 @@ export class AcpBackend implements ChatBackend {
|
|
|
237
251
|
}
|
|
238
252
|
}
|
|
239
253
|
}
|
|
254
|
+
|
|
240
255
|
const result = await this.authenticated(transport, 'session/new', this.sessionParams());
|
|
241
256
|
const sessionId = sessionIdOf(result);
|
|
257
|
+
|
|
242
258
|
if (sessionId === null) {
|
|
243
259
|
throw new Error(`session/new failed: ${this.spec.name} answered without a session id`);
|
|
244
260
|
}
|
|
261
|
+
|
|
245
262
|
this.sessionId = sessionId;
|
|
246
263
|
this.contextPending = this.launch.instructions;
|
|
264
|
+
|
|
247
265
|
return parseSessionInfo(result, sessionId, capabilities.agent);
|
|
248
266
|
}
|
|
249
267
|
|
|
@@ -255,12 +273,16 @@ export class AcpBackend implements ChatBackend {
|
|
|
255
273
|
if (!(error instanceof JsonRpcError) || error.code !== ACP_AUTH_REQUIRED) {
|
|
256
274
|
throw error;
|
|
257
275
|
}
|
|
276
|
+
|
|
258
277
|
const methodId = this.spec.authMethod ?? null;
|
|
259
278
|
const offered = this.capabilities?.authMethods ?? [];
|
|
279
|
+
|
|
260
280
|
if (methodId === null || (offered.length > 0 && !offered.includes(methodId))) {
|
|
261
281
|
throw new Error(`${this.spec.name} is not signed in.${this.spec.loginHint ? ` ${this.spec.loginHint}` : ''}`, { cause: error });
|
|
262
282
|
}
|
|
283
|
+
|
|
263
284
|
await transport.request('authenticate', { methodId }, { timeoutMs: null });
|
|
285
|
+
|
|
264
286
|
return transport.request(method, params);
|
|
265
287
|
}
|
|
266
288
|
}
|
|
@@ -268,31 +290,40 @@ export class AcpBackend implements ChatBackend {
|
|
|
268
290
|
/* Puts the session in the agent mode the runtime mode maps to; answers the mode it runs in, or null for an agent without modes. */
|
|
269
291
|
private async applyMode(transport: JsonRpcTransport, info: AcpSessionInfo): Promise<string | null> {
|
|
270
292
|
const modes = info.modes;
|
|
293
|
+
|
|
271
294
|
if (modes === null || modes.modes.length === 0) {
|
|
272
295
|
return null;
|
|
273
296
|
}
|
|
297
|
+
|
|
274
298
|
const map = this.spec.modes ?? {};
|
|
275
299
|
const requested = this.launch.runtimeMode;
|
|
300
|
+
|
|
276
301
|
const resolved = resolveAcpMode(
|
|
277
302
|
requested,
|
|
278
303
|
map,
|
|
279
304
|
modes.modes.map((mode) => mode.id)
|
|
280
305
|
);
|
|
306
|
+
|
|
281
307
|
if (resolved === null) {
|
|
282
308
|
const current = runtimeModeOf(modes.current, map);
|
|
309
|
+
|
|
283
310
|
if (current !== null && MODE_ORDER.indexOf(current) > MODE_ORDER.indexOf(requested)) {
|
|
284
311
|
throw new Error(`${this.spec.name} runs in ${modes.current}, which allows more than ${requested}, and offers no narrower mode`);
|
|
285
312
|
}
|
|
313
|
+
|
|
286
314
|
return modes.current;
|
|
287
315
|
}
|
|
316
|
+
|
|
288
317
|
if (resolved.modeId === modes.current) {
|
|
289
318
|
return modes.current;
|
|
290
319
|
}
|
|
320
|
+
|
|
291
321
|
if (modes.configId === null) {
|
|
292
322
|
await transport.request('session/set_mode', { sessionId: this.sessionId, modeId: resolved.modeId });
|
|
293
323
|
} else {
|
|
294
324
|
await transport.request('session/set_config_option', { sessionId: this.sessionId, configId: modes.configId, value: resolved.modeId });
|
|
295
325
|
}
|
|
326
|
+
|
|
296
327
|
return resolved.modeId;
|
|
297
328
|
}
|
|
298
329
|
|
|
@@ -304,30 +335,39 @@ export class AcpBackend implements ChatBackend {
|
|
|
304
335
|
const models = info.models;
|
|
305
336
|
const thoughtLevel = info.thoughtLevel;
|
|
306
337
|
const wanted = this.spec.model ? this.spec.model(this.launch.selection) : this.launch.selection.model;
|
|
338
|
+
|
|
307
339
|
if (models === null || wanted === null || wanted === models.current) {
|
|
308
340
|
return { model: models?.current ?? wanted, thoughtLevel };
|
|
309
341
|
}
|
|
342
|
+
|
|
310
343
|
if (!models.models.some((model) => model.id === wanted)) {
|
|
311
344
|
this.emit({ type: 'note', level: 'warning', text: `${this.spec.name} does not offer ${this.launch.modelName}. It runs ${models.current}.` });
|
|
345
|
+
|
|
312
346
|
return { model: models.current, thoughtLevel };
|
|
313
347
|
}
|
|
348
|
+
|
|
314
349
|
try {
|
|
315
350
|
if (models.configId === null) {
|
|
316
351
|
await transport.request('session/set_model', { sessionId: this.sessionId, modelId: wanted });
|
|
352
|
+
|
|
317
353
|
return { model: wanted, thoughtLevel };
|
|
318
354
|
}
|
|
355
|
+
|
|
319
356
|
const result = await transport.request('session/set_config_option', { sessionId: this.sessionId, configId: models.configId, value: wanted });
|
|
320
357
|
const after = thoughtLevelOf(result);
|
|
358
|
+
|
|
321
359
|
return { model: wanted, thoughtLevel: after === undefined ? thoughtLevel : after };
|
|
322
360
|
} catch (error) {
|
|
323
361
|
if (!(error instanceof JsonRpcError)) {
|
|
324
362
|
throw error;
|
|
325
363
|
}
|
|
364
|
+
|
|
326
365
|
this.emit({
|
|
327
366
|
type: 'note',
|
|
328
367
|
level: 'warning',
|
|
329
368
|
text: `${this.spec.name} did not switch to ${this.launch.modelName} (${errorText(error)}). It runs ${models.current}.`
|
|
330
369
|
});
|
|
370
|
+
|
|
331
371
|
return { model: models.current, thoughtLevel };
|
|
332
372
|
}
|
|
333
373
|
}
|
|
@@ -336,15 +376,18 @@ export class AcpBackend implements ChatBackend {
|
|
|
336
376
|
private async applyThoughtLevel(transport: JsonRpcTransport, level: AcpThoughtLevel | null): Promise<void> {
|
|
337
377
|
const effort = this.launch.selection.options.effort;
|
|
338
378
|
const choice = level?.choices.find((entry) => entry.id === effort);
|
|
379
|
+
|
|
339
380
|
if (level === null || choice === undefined || effort === level.current) {
|
|
340
381
|
return;
|
|
341
382
|
}
|
|
383
|
+
|
|
342
384
|
try {
|
|
343
385
|
await transport.request('session/set_config_option', { sessionId: this.sessionId, configId: level.configId, value: choice.id });
|
|
344
386
|
} catch (error) {
|
|
345
387
|
if (!(error instanceof JsonRpcError)) {
|
|
346
388
|
throw error;
|
|
347
389
|
}
|
|
390
|
+
|
|
348
391
|
const current = level.choices.find((entry) => entry.id === level.current)?.name ?? level.current;
|
|
349
392
|
this.emit({ type: 'note', level: 'warning', text: `${this.spec.name} kept its reasoning at ${current} instead of ${choice.name}.` });
|
|
350
393
|
}
|
|
@@ -353,20 +396,25 @@ export class AcpBackend implements ChatBackend {
|
|
|
353
396
|
private promptBlocks(input: TurnInput): unknown[] {
|
|
354
397
|
const sendsImages = this.capabilities?.images === true;
|
|
355
398
|
const images = sendsImages ? input.attachments.filter((attachment) => attachmentImageMime(attachment) !== null) : [];
|
|
399
|
+
|
|
356
400
|
const blocks = images.map((attachment) => {
|
|
357
401
|
let data: string;
|
|
402
|
+
|
|
358
403
|
try {
|
|
359
404
|
data = readFileSync(attachment.path).toString('base64');
|
|
360
405
|
} catch {
|
|
361
406
|
throw new Error(`Could not read attached image "${attachment.name}". Attach it again and retry.`);
|
|
362
407
|
}
|
|
408
|
+
|
|
363
409
|
return { type: 'image', mimeType: attachmentImageMime(attachment), data };
|
|
364
410
|
});
|
|
411
|
+
|
|
365
412
|
const named: ChatAttachment[] = input.attachments.filter((attachment) => !images.includes(attachment));
|
|
366
413
|
const parts = [this.contextPending, input.preamble, input.text, attachmentNote(named)].filter((part): part is string => part !== null && part !== '');
|
|
367
414
|
this.contextPending = null;
|
|
368
415
|
const text = parts.join('\n\n');
|
|
369
416
|
this.protocol.prompted(text);
|
|
417
|
+
|
|
370
418
|
return [{ type: 'text', text }, ...blocks, ...this.mentionBlocks(input.mentions)];
|
|
371
419
|
}
|
|
372
420
|
|
|
@@ -379,6 +427,7 @@ export class AcpBackend implements ChatBackend {
|
|
|
379
427
|
if (this.capabilities?.embeddedContext !== true || mentions.length === 0) {
|
|
380
428
|
return [];
|
|
381
429
|
}
|
|
430
|
+
|
|
382
431
|
const real = (path: string): string | null => {
|
|
383
432
|
try {
|
|
384
433
|
return realpathSync(path);
|
|
@@ -386,42 +435,56 @@ export class AcpBackend implements ChatBackend {
|
|
|
386
435
|
return null;
|
|
387
436
|
}
|
|
388
437
|
};
|
|
438
|
+
|
|
389
439
|
const folders = [this.launch.cwd, ...(this.launch.folders ?? [])].flatMap((folder) => real(folder) ?? []);
|
|
390
440
|
const blocks: unknown[] = [];
|
|
391
441
|
let budget = MAX_EMBEDDED_BYTES;
|
|
442
|
+
|
|
392
443
|
for (const mention of new Set(mentions)) {
|
|
393
444
|
const path = real(resolve(this.launch.cwd, mention));
|
|
445
|
+
|
|
394
446
|
if (path === null || !folders.some((folder) => inside(folder, path))) {
|
|
395
447
|
continue;
|
|
396
448
|
}
|
|
449
|
+
|
|
397
450
|
let text: string;
|
|
451
|
+
|
|
398
452
|
try {
|
|
399
453
|
const stat = statSync(path);
|
|
454
|
+
|
|
400
455
|
if (!stat.isFile() || stat.size > MAX_EMBEDDED_FILE_BYTES || stat.size > budget) {
|
|
401
456
|
continue;
|
|
402
457
|
}
|
|
458
|
+
|
|
403
459
|
text = readFileSync(path, 'utf8');
|
|
404
460
|
} catch {
|
|
405
461
|
continue;
|
|
406
462
|
}
|
|
463
|
+
|
|
407
464
|
if (text.includes('\u0000')) {
|
|
408
465
|
continue;
|
|
409
466
|
}
|
|
467
|
+
|
|
410
468
|
budget -= Buffer.byteLength(text);
|
|
411
469
|
blocks.push({ type: 'resource', resource: { uri: pathToFileURL(path).href, text } });
|
|
412
470
|
}
|
|
471
|
+
|
|
413
472
|
return blocks;
|
|
414
473
|
}
|
|
415
474
|
|
|
416
475
|
sendTurn(input: TurnInput): void {
|
|
417
476
|
const transport = this.transport;
|
|
477
|
+
|
|
418
478
|
if (!transport) {
|
|
419
479
|
this.emit({ type: 'failed', message: `${this.spec.name} is not running` });
|
|
480
|
+
|
|
420
481
|
return;
|
|
421
482
|
}
|
|
483
|
+
|
|
422
484
|
const prompt = this.promptBlocks(input);
|
|
423
485
|
const turn = { promptId: input.promptId, cancelled: false };
|
|
424
486
|
this.prompt = turn;
|
|
487
|
+
|
|
425
488
|
transport
|
|
426
489
|
.request('session/prompt', { sessionId: this.sessionId, prompt }, { timeoutMs: null })
|
|
427
490
|
.then((result) => {
|
|
@@ -445,8 +508,10 @@ export class AcpBackend implements ChatBackend {
|
|
|
445
508
|
compact(): void {
|
|
446
509
|
if (this.commands.includes('compact')) {
|
|
447
510
|
this.sendTurn({ text: '/compact', preamble: null, attachments: [], mentions: [], skills: [] });
|
|
511
|
+
|
|
448
512
|
return;
|
|
449
513
|
}
|
|
514
|
+
|
|
450
515
|
this.emit({ type: 'note', level: 'warning', text: `${this.spec.name} cannot compact its context.` });
|
|
451
516
|
this.emit({ type: 'turn.done', state: 'done', costUsd: 0 });
|
|
452
517
|
}
|
|
@@ -454,19 +519,25 @@ export class AcpBackend implements ChatBackend {
|
|
|
454
519
|
/* Cancels the turn: every permission request still open is answered as cancelled, and the turn ends on the prompt's response. */
|
|
455
520
|
interrupt(): void {
|
|
456
521
|
const transport = this.transport;
|
|
522
|
+
|
|
457
523
|
if (!transport) {
|
|
458
524
|
return;
|
|
459
525
|
}
|
|
526
|
+
|
|
460
527
|
if (this.prompt === null) {
|
|
461
528
|
this.emit({ type: 'turn.done', state: 'aborted', costUsd: 0 });
|
|
529
|
+
|
|
462
530
|
return;
|
|
463
531
|
}
|
|
532
|
+
|
|
464
533
|
this.prompt.cancelled = true;
|
|
534
|
+
|
|
465
535
|
try {
|
|
466
536
|
for (const cancelled of this.protocol.cancelRequests()) {
|
|
467
537
|
transport.respond(cancelled.rpcId, cancelled.result);
|
|
468
538
|
this.emit({ type: 'request.withdrawn', requestId: cancelled.requestId });
|
|
469
539
|
}
|
|
540
|
+
|
|
470
541
|
transport.notify('session/cancel', { sessionId: this.sessionId });
|
|
471
542
|
} catch (error) {
|
|
472
543
|
this.fail(error);
|
|
@@ -475,33 +546,43 @@ export class AcpBackend implements ChatBackend {
|
|
|
475
546
|
|
|
476
547
|
respondApproval(requestId: string, decision: ApprovalDecision): boolean {
|
|
477
548
|
const answer = this.protocol.answer(requestId, decision);
|
|
549
|
+
|
|
478
550
|
if (!answer) {
|
|
479
551
|
return false;
|
|
480
552
|
}
|
|
553
|
+
|
|
481
554
|
if (!this.respond(requestId, answer.rpcId, answer.result)) {
|
|
482
555
|
return false;
|
|
483
556
|
}
|
|
557
|
+
|
|
484
558
|
this.emitAll(answer.events);
|
|
559
|
+
|
|
485
560
|
return true;
|
|
486
561
|
}
|
|
487
562
|
|
|
488
563
|
respondQuestion(requestId: string, answers: Record<string, string>): boolean {
|
|
489
564
|
const answer = this.protocol.answerQuestion(requestId, answers);
|
|
565
|
+
|
|
490
566
|
if (!answer) {
|
|
491
567
|
return false;
|
|
492
568
|
}
|
|
569
|
+
|
|
493
570
|
if (!this.respond(requestId, answer.rpcId, answer.result)) {
|
|
494
571
|
return false;
|
|
495
572
|
}
|
|
573
|
+
|
|
496
574
|
this.emitAll(answer.events);
|
|
575
|
+
|
|
497
576
|
return true;
|
|
498
577
|
}
|
|
499
578
|
|
|
500
579
|
declineRequest(requestId: string): boolean {
|
|
501
580
|
if (this.protocol.hasQuestion(requestId)) {
|
|
502
581
|
const cancelled = this.protocol.cancelQuestion(requestId);
|
|
582
|
+
|
|
503
583
|
return cancelled !== null && this.respond(requestId, cancelled.rpcId, cancelled.result);
|
|
504
584
|
}
|
|
585
|
+
|
|
505
586
|
return this.protocol.hasPermission(requestId) && this.respondApproval(requestId, 'deny');
|
|
506
587
|
}
|
|
507
588
|
|
|
@@ -512,6 +593,7 @@ export class AcpBackend implements ChatBackend {
|
|
|
512
593
|
dispose(): Promise<void> {
|
|
513
594
|
const transport = this.transport;
|
|
514
595
|
this.transport = null;
|
|
596
|
+
|
|
515
597
|
return transport?.dispose() ?? Promise.resolve();
|
|
516
598
|
}
|
|
517
599
|
|
|
@@ -520,11 +602,14 @@ export class AcpBackend implements ChatBackend {
|
|
|
520
602
|
if (!this.transport) {
|
|
521
603
|
throw new Error(`${this.spec.name} is not running`);
|
|
522
604
|
}
|
|
605
|
+
|
|
523
606
|
this.transport.respond(rpcId, result);
|
|
607
|
+
|
|
524
608
|
return true;
|
|
525
609
|
} catch (error) {
|
|
526
610
|
this.emit({ type: 'request.withdrawn', requestId });
|
|
527
611
|
this.fail(error);
|
|
612
|
+
|
|
528
613
|
return false;
|
|
529
614
|
}
|
|
530
615
|
}
|
|
@@ -537,58 +622,84 @@ export class AcpBackend implements ChatBackend {
|
|
|
537
622
|
if (this.transport !== transport) {
|
|
538
623
|
return;
|
|
539
624
|
}
|
|
625
|
+
|
|
540
626
|
const id = typeof frame.id === 'number' || typeof frame.id === 'string' ? frame.id : null;
|
|
627
|
+
|
|
541
628
|
if (frame.method === 'session/update' && id === null) {
|
|
542
629
|
if (this.sessionId === '' || this.replaying) {
|
|
543
630
|
this.holdEarly(frame.params);
|
|
631
|
+
|
|
544
632
|
return;
|
|
545
633
|
}
|
|
634
|
+
|
|
546
635
|
this.emitAll(this.protocol.update(this.sessionId, frame.params));
|
|
636
|
+
|
|
547
637
|
return;
|
|
548
638
|
}
|
|
639
|
+
|
|
549
640
|
if (id === null) {
|
|
550
641
|
return;
|
|
551
642
|
}
|
|
643
|
+
|
|
552
644
|
if (typeof frame.method === 'string' && GROK_QUESTION_METHODS.includes(frame.method)) {
|
|
553
645
|
const question = this.protocol.question(id, this.sessionId, frame.params);
|
|
646
|
+
|
|
554
647
|
if (question === null) {
|
|
555
648
|
transport.respondError(id, -32602, 'Not a question of this session');
|
|
649
|
+
|
|
556
650
|
return;
|
|
557
651
|
}
|
|
652
|
+
|
|
558
653
|
this.ask(transport, question);
|
|
654
|
+
|
|
559
655
|
return;
|
|
560
656
|
}
|
|
657
|
+
|
|
561
658
|
if (frame.method !== 'session/request_permission') {
|
|
562
659
|
transport.respondError(id, -32601, `${nameOf(this.client)} does not handle ${String(frame.method)}`);
|
|
660
|
+
|
|
563
661
|
return;
|
|
564
662
|
}
|
|
663
|
+
|
|
565
664
|
// Before any runtime mode answers it: a question is never the agent's to allow.
|
|
566
665
|
const interaction = this.protocol.interaction(id, this.sessionId, frame.params);
|
|
666
|
+
|
|
567
667
|
if (interaction !== null) {
|
|
568
668
|
this.ask(transport, interaction);
|
|
669
|
+
|
|
569
670
|
return;
|
|
570
671
|
}
|
|
672
|
+
|
|
571
673
|
const permission = this.protocol.permission(id, this.sessionId, frame.params);
|
|
674
|
+
|
|
572
675
|
if (permission === null) {
|
|
573
676
|
transport.respondError(id, -32602, 'Not a permission request of this session');
|
|
677
|
+
|
|
574
678
|
return;
|
|
575
679
|
}
|
|
680
|
+
|
|
576
681
|
if (this.prompt?.cancelled) {
|
|
577
682
|
this.emitAll(permission.events.filter((event) => event.type !== 'approval.requested'));
|
|
578
683
|
const answer = this.protocol.cancelRequests().find((cancelled) => cancelled.requestId === permission.requestId);
|
|
684
|
+
|
|
579
685
|
if (answer) {
|
|
580
686
|
transport.respond(answer.rpcId, answer.result);
|
|
581
687
|
}
|
|
688
|
+
|
|
582
689
|
return;
|
|
583
690
|
}
|
|
691
|
+
|
|
584
692
|
if (autoAllows(this.launch.runtimeMode, permission.kind) || this.hostAllows(permission.request)) {
|
|
585
693
|
this.emitAll(permission.events.filter((event) => event.type !== 'approval.requested'));
|
|
586
694
|
const answer = this.protocol.answer(permission.requestId, 'allow');
|
|
695
|
+
|
|
587
696
|
if (answer) {
|
|
588
697
|
this.respond(permission.requestId, answer.rpcId, answer.result);
|
|
589
698
|
}
|
|
699
|
+
|
|
590
700
|
return;
|
|
591
701
|
}
|
|
702
|
+
|
|
592
703
|
this.emitAll(permission.events);
|
|
593
704
|
}
|
|
594
705
|
|
|
@@ -597,17 +708,21 @@ export class AcpBackend implements ChatBackend {
|
|
|
597
708
|
if (this.prompt?.cancelled) {
|
|
598
709
|
this.emitAll(question.events.filter((event) => event.type !== 'question.requested'));
|
|
599
710
|
const cancelled = this.protocol.cancelQuestion(question.requestId);
|
|
711
|
+
|
|
600
712
|
if (cancelled) {
|
|
601
713
|
transport.respond(cancelled.rpcId, cancelled.result);
|
|
602
714
|
}
|
|
715
|
+
|
|
603
716
|
return;
|
|
604
717
|
}
|
|
718
|
+
|
|
605
719
|
this.emitAll(question.events);
|
|
606
720
|
}
|
|
607
721
|
|
|
608
722
|
/* Holds an update until the session is known; of a history a load replays, only the commands, since the thread has the rest. */
|
|
609
723
|
private holdEarly(params: unknown): void {
|
|
610
724
|
const update = sessionUpdateOf(params);
|
|
725
|
+
|
|
611
726
|
if (update !== null && (!this.replaying || update.commands !== null)) {
|
|
612
727
|
this.early.push(params);
|
|
613
728
|
}
|
|
@@ -625,6 +740,7 @@ export class AcpBackend implements ChatBackend {
|
|
|
625
740
|
if (this.transport !== transport) {
|
|
626
741
|
return;
|
|
627
742
|
}
|
|
743
|
+
|
|
628
744
|
this.transport = null;
|
|
629
745
|
this.prompt = null;
|
|
630
746
|
this.protocol.cancelRequests();
|
|
@@ -634,9 +750,11 @@ export class AcpBackend implements ChatBackend {
|
|
|
634
750
|
/* A cancelled turn still spent what it reports. A host that fails to keep it costs the record, never the end of the turn. */
|
|
635
751
|
private report(result: unknown): void {
|
|
636
752
|
const usage = this.spec.onUsage === undefined ? null : promptUsageOf(result);
|
|
753
|
+
|
|
637
754
|
if (usage === null) {
|
|
638
755
|
return;
|
|
639
756
|
}
|
|
757
|
+
|
|
640
758
|
try {
|
|
641
759
|
this.spec.onUsage?.({ ...usage, sessionId: this.sessionId, cwd: this.launch.cwd, model: this.model ?? this.launch.selection.model });
|
|
642
760
|
} catch (error) {
|
|
@@ -654,14 +772,17 @@ export class AcpBackend implements ChatBackend {
|
|
|
654
772
|
if (event.type === 'model') {
|
|
655
773
|
this.model = event.model;
|
|
656
774
|
}
|
|
775
|
+
|
|
657
776
|
if (event.type === 'session' && event.slashCommands !== undefined) {
|
|
658
777
|
this.commands = event.slashCommands;
|
|
659
778
|
this.spec.onCommands?.(event.slashCommands);
|
|
660
779
|
}
|
|
780
|
+
|
|
661
781
|
if (event.type === 'usage' && event.contextWindow !== undefined && !this.contextReported) {
|
|
662
782
|
this.contextReported = true;
|
|
663
783
|
this.spec.onContextReport?.();
|
|
664
784
|
}
|
|
785
|
+
|
|
665
786
|
this.host.onEvent(event);
|
|
666
787
|
}
|
|
667
788
|
}
|