awb-agent-manager 1.6.205 → 1.6.207
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/lib/agent-session-runner.d.ts +20 -2
- package/dist/lib/agent-session-runner.js +329 -28
- package/dist/lib/agent-session-runner.js.map +1 -1
- package/dist/lib/rest.d.ts +20 -0
- package/dist/lib/rest.js.map +1 -1
- package/dist/lib/runtime/acp/acp-client.d.ts +8 -3
- package/dist/lib/runtime/acp/acp-client.js +45 -10
- package/dist/lib/runtime/acp/acp-client.js.map +1 -1
- package/dist/lib/runtime/acp/acp-types.d.ts +60 -1
- package/dist/lib/runtime/collaboration-governor.js +1 -1
- package/dist/lib/runtime/collaboration-governor.js.map +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AgentSessionStore } from './agent-session-store.js';
|
|
2
|
-
import { type AwbConfig } from './rest.js';
|
|
2
|
+
import { type AgentSessionConfigOptionPatch, type AwbConfig } from './rest.js';
|
|
3
3
|
import type { AcpMcpServer } from './runtime/acp/acp-types.js';
|
|
4
4
|
export interface AgentSessionRequest {
|
|
5
5
|
manager_id: string;
|
|
6
6
|
workspace_id?: string;
|
|
7
7
|
cli: string;
|
|
8
|
-
op: 'list' | 'history' | 'open' | 'prompt' | 'permission' | 'cancel' | 'set_mode' | 'close';
|
|
8
|
+
op: 'list' | 'history' | 'open' | 'prompt' | 'permission' | 'elicitation' | 'cancel' | 'set_mode' | 'set_config_option' | 'close';
|
|
9
9
|
request_id?: string;
|
|
10
10
|
session_id?: string | null;
|
|
11
11
|
cwd?: string;
|
|
@@ -15,6 +15,11 @@ export interface AgentSessionRequest {
|
|
|
15
15
|
permission_request_id?: string;
|
|
16
16
|
option_id?: string | null;
|
|
17
17
|
mode_id?: string;
|
|
18
|
+
config_id?: string;
|
|
19
|
+
config_value?: string | boolean;
|
|
20
|
+
elicitation_id?: string;
|
|
21
|
+
elicitation_action?: 'accept' | 'decline' | 'cancel';
|
|
22
|
+
elicitation_content?: Record<string, unknown> | null;
|
|
18
23
|
credential_id?: string | null;
|
|
19
24
|
driver_user_id: string;
|
|
20
25
|
issued_at: string;
|
|
@@ -45,7 +50,19 @@ export interface SessionCredential {
|
|
|
45
50
|
}
|
|
46
51
|
export declare const SESSION_CLI_CREDENTIAL_PREFIX: Record<string, string>;
|
|
47
52
|
export declare function redactSecrets(text: string): string;
|
|
53
|
+
type CommandPatch = {
|
|
54
|
+
name: string;
|
|
55
|
+
description: string;
|
|
56
|
+
input_hint?: string;
|
|
57
|
+
};
|
|
48
58
|
export declare const ACP_SESSION_CLIS: readonly ["claude", "codex", "hermes"];
|
|
59
|
+
export declare function parseConfigOptions(raw: unknown): AgentSessionConfigOptionPatch[];
|
|
60
|
+
export declare function parseCommands(raw: unknown): CommandPatch[];
|
|
61
|
+
export declare function parsePlanEntries(raw: unknown): Array<{
|
|
62
|
+
content: string;
|
|
63
|
+
priority: string;
|
|
64
|
+
status: string;
|
|
65
|
+
}> | null;
|
|
49
66
|
export declare function findOnPath(name: string): Promise<string | null>;
|
|
50
67
|
export declare function resolveAcpCommandForCli(cli: string): Promise<ResolvedAcpCommand>;
|
|
51
68
|
export declare function detectAcpSessionClis(env?: NodeJS.ProcessEnv): Promise<string[]>;
|
|
@@ -63,3 +80,4 @@ export declare class AgentSessionRunner {
|
|
|
63
80
|
handle(request: AgentSessionRequest): Promise<void>;
|
|
64
81
|
stopAll(reason?: string): Promise<void>;
|
|
65
82
|
}
|
|
83
|
+
export {};
|
|
@@ -68,6 +68,89 @@ function boundedPayload(payload) {
|
|
|
68
68
|
return bounded;
|
|
69
69
|
return { truncated: true, preview: serialized.slice(0, 4_000) };
|
|
70
70
|
}
|
|
71
|
+
export function parseConfigOptions(raw) {
|
|
72
|
+
if (!Array.isArray(raw))
|
|
73
|
+
return [];
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const entry of raw) {
|
|
76
|
+
if (!entry || typeof entry !== 'object')
|
|
77
|
+
continue;
|
|
78
|
+
const o = entry;
|
|
79
|
+
const configId = typeof o.configId === 'string' ? o.configId : typeof o.config_id === 'string' ? o.config_id : '';
|
|
80
|
+
if (!configId)
|
|
81
|
+
continue;
|
|
82
|
+
const type = typeof o.type === 'string' ? o.type : (typeof o.currentValue === 'boolean' ? 'boolean' : 'select');
|
|
83
|
+
const options = [];
|
|
84
|
+
const pushOption = (v, group) => {
|
|
85
|
+
if (!v || typeof v !== 'object')
|
|
86
|
+
return;
|
|
87
|
+
const opt = v;
|
|
88
|
+
const value = typeof opt.value === 'string' ? opt.value : typeof opt.id === 'string' ? opt.id : '';
|
|
89
|
+
if (!value)
|
|
90
|
+
return;
|
|
91
|
+
options.push({
|
|
92
|
+
value,
|
|
93
|
+
name: typeof opt.name === 'string' && opt.name ? opt.name : value,
|
|
94
|
+
...(typeof opt.description === 'string' && opt.description ? { description: opt.description } : {}),
|
|
95
|
+
...(group ? { group } : {}),
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
if (Array.isArray(o.options)) {
|
|
99
|
+
for (const v of o.options) {
|
|
100
|
+
const g = v;
|
|
101
|
+
if (g && typeof g === 'object' && Array.isArray(g.options)) {
|
|
102
|
+
const label = typeof g.name === 'string' ? g.name : typeof g.group === 'string' ? g.group : '';
|
|
103
|
+
for (const inner of g.options)
|
|
104
|
+
pushOption(inner, label || undefined);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
pushOption(v);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const current = o.currentValue ?? o.current_value;
|
|
112
|
+
out.push({
|
|
113
|
+
config_id: configId,
|
|
114
|
+
name: typeof o.name === 'string' && o.name ? o.name : configId,
|
|
115
|
+
...(typeof o.description === 'string' && o.description ? { description: o.description } : {}),
|
|
116
|
+
category: typeof o.category === 'string' && o.category ? o.category : 'unknown',
|
|
117
|
+
type,
|
|
118
|
+
current_value: typeof current === 'boolean' ? current : typeof current === 'string' ? current : null,
|
|
119
|
+
options,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
export function parseCommands(raw) {
|
|
125
|
+
if (!Array.isArray(raw))
|
|
126
|
+
return [];
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const entry of raw) {
|
|
129
|
+
if (!entry || typeof entry !== 'object')
|
|
130
|
+
continue;
|
|
131
|
+
const c = entry;
|
|
132
|
+
const name = typeof c.name === 'string' ? c.name.trim().replace(/^\//, '') : '';
|
|
133
|
+
if (!name)
|
|
134
|
+
continue;
|
|
135
|
+
const input = c.input && typeof c.input === 'object' ? c.input : null;
|
|
136
|
+
const hint = input && typeof input.hint === 'string' ? input.hint : '';
|
|
137
|
+
out.push({ name, description: typeof c.description === 'string' ? c.description : '', ...(hint ? { input_hint: hint } : {}) });
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
export function parsePlanEntries(raw) {
|
|
142
|
+
if (!Array.isArray(raw))
|
|
143
|
+
return null;
|
|
144
|
+
const entries = raw
|
|
145
|
+
.filter((e) => !!e && typeof e === 'object')
|
|
146
|
+
.map((e) => ({
|
|
147
|
+
content: typeof e.content === 'string' ? e.content.slice(0, 2_000) : '',
|
|
148
|
+
priority: typeof e.priority === 'string' ? e.priority : 'medium',
|
|
149
|
+
status: typeof e.status === 'string' ? e.status : 'pending',
|
|
150
|
+
}))
|
|
151
|
+
.filter((e) => e.content);
|
|
152
|
+
return entries;
|
|
153
|
+
}
|
|
71
154
|
export async function findOnPath(name) {
|
|
72
155
|
const candidates = process.platform === 'win32' ? [`${name}.cmd`, `${name}.exe`, name] : [name];
|
|
73
156
|
for (const dir of (process.env.PATH || '').split(delimiter)) {
|
|
@@ -100,7 +183,7 @@ export async function resolveAcpCommandForCli(cli) {
|
|
|
100
183
|
}
|
|
101
184
|
case 'codex': {
|
|
102
185
|
const found = await findOnPath('codex-acp');
|
|
103
|
-
return found ? { command: found, args: [] } : { command: 'npx', args: ['--yes', '@
|
|
186
|
+
return found ? { command: found, args: [] } : { command: 'npx', args: ['--yes', '@agentclientprotocol/codex-acp'] };
|
|
104
187
|
}
|
|
105
188
|
case 'hermes': {
|
|
106
189
|
const resolved = await resolveHermesAcpCommand();
|
|
@@ -126,6 +209,7 @@ export class AgentSessionRunner {
|
|
|
126
209
|
#store;
|
|
127
210
|
#live = new Map();
|
|
128
211
|
#opening = new Map();
|
|
212
|
+
#exited = [];
|
|
129
213
|
constructor(config, options) {
|
|
130
214
|
this.#config = config;
|
|
131
215
|
this.#store = options.store ?? new AgentSessionStore();
|
|
@@ -151,7 +235,7 @@ export class AgentSessionRunner {
|
|
|
151
235
|
return Array.from(this.#live.values()).map((live) => ({
|
|
152
236
|
cli: live.cli,
|
|
153
237
|
session_id: live.sessionId,
|
|
154
|
-
busy: live.turn !== null || live.pendingPermissions.size > 0,
|
|
238
|
+
busy: live.turn !== null || live.pendingPermissions.size > 0 || live.pendingElicitations.size > 0,
|
|
155
239
|
pid: live.client.process.pid ?? null,
|
|
156
240
|
}));
|
|
157
241
|
}
|
|
@@ -185,6 +269,16 @@ export class AgentSessionRunner {
|
|
|
185
269
|
case 'permission':
|
|
186
270
|
this.#resolvePermission(cli, sessionId, request.permission_request_id || '', request.option_id ?? null);
|
|
187
271
|
return;
|
|
272
|
+
case 'elicitation':
|
|
273
|
+
this.#resolveElicitation(cli, sessionId, request.elicitation_id || '', request.elicitation_action || 'cancel', request.elicitation_content ?? null);
|
|
274
|
+
return;
|
|
275
|
+
case 'set_config_option': {
|
|
276
|
+
const live = this.#live.get(this.#key(cli, sessionId));
|
|
277
|
+
if (!live || !request.config_id || request.config_value === undefined)
|
|
278
|
+
return;
|
|
279
|
+
await this.#setConfigOption(live, request.config_id, request.config_value);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
188
282
|
case 'cancel': {
|
|
189
283
|
const live = this.#live.get(this.#key(cli, sessionId));
|
|
190
284
|
if (live)
|
|
@@ -197,6 +291,7 @@ export class AgentSessionRunner {
|
|
|
197
291
|
if (!live || !modeId)
|
|
198
292
|
return;
|
|
199
293
|
await live.client.request('session/set_mode', { sessionId: live.sessionId, modeId }, { timeoutMs: this.#options.requestTimeoutMs });
|
|
294
|
+
live.currentMode = modeId;
|
|
200
295
|
this.#enqueue(live, [{ type: 'system', payload: { text: `Mode set to ${modeId}.` } }], { current_mode: modeId, reason: 'mode' });
|
|
201
296
|
return;
|
|
202
297
|
}
|
|
@@ -245,11 +340,17 @@ export class AgentSessionRunner {
|
|
|
245
340
|
await postAgentSessionRpcResponse(this.#config, managerId, requestId, { ok: false, error: 'Session not found on this Runtime Host.', code: 'not_found' });
|
|
246
341
|
return;
|
|
247
342
|
}
|
|
343
|
+
const pending = live
|
|
344
|
+
? [...Array.from(live.pendingPermissions.values()), ...Array.from(live.pendingElicitations.values())].map((p) => p.event)
|
|
345
|
+
: [];
|
|
346
|
+
const events = pending.length
|
|
347
|
+
? [...history.events, ...pending.map((e, i) => ({ ...e, seq: history.events.length + i + 1 }))]
|
|
348
|
+
: history.events;
|
|
248
349
|
await postAgentSessionRpcResponse(this.#config, managerId, requestId, {
|
|
249
350
|
ok: true,
|
|
250
351
|
result: {
|
|
251
352
|
session: history.session ?? (live ? this.#summaryOf(live) : null),
|
|
252
|
-
events
|
|
353
|
+
events,
|
|
253
354
|
truncated: history.truncated,
|
|
254
355
|
live: live ? this.#stateOf(live) : null,
|
|
255
356
|
},
|
|
@@ -274,6 +375,10 @@ export class AgentSessionRunner {
|
|
|
274
375
|
async stopAll(reason = 'manager_shutdown') {
|
|
275
376
|
const keys = Array.from(this.#live.values()).map((l) => [l.cli, l.sessionId]);
|
|
276
377
|
await Promise.all(keys.map(([cli, id]) => this.#closeLive(cli, id, 'idle', reason).catch(() => undefined)));
|
|
378
|
+
const drains = this.#exited.splice(0).map((l) => l.postChain);
|
|
379
|
+
if (drains.length) {
|
|
380
|
+
await Promise.race([Promise.all(drains), new Promise((resolve) => setTimeout(resolve, 3_000).unref?.())]);
|
|
381
|
+
}
|
|
277
382
|
}
|
|
278
383
|
async #ensureLive(cli, sessionId, cwd, title, request) {
|
|
279
384
|
if (sessionId) {
|
|
@@ -316,25 +421,34 @@ export class AgentSessionRunner {
|
|
|
316
421
|
const auth = await this.#prepareAuth(cli, cwd, request);
|
|
317
422
|
log(`${tag} spawning ACP adapter cmd=${command} ${args.join(' ')} cwd=${cwd} auth=${auth.label}`);
|
|
318
423
|
let live = null;
|
|
424
|
+
const env = this.#buildEnv(cli, requestedSessionId || 'new', auth);
|
|
319
425
|
const client = await AcpClient.spawn({
|
|
320
426
|
command,
|
|
321
427
|
args,
|
|
322
428
|
cwd,
|
|
323
|
-
env
|
|
429
|
+
env,
|
|
324
430
|
requestTimeoutMs: this.#options.requestTimeoutMs,
|
|
325
431
|
onEvent: (event) => { if (live)
|
|
326
432
|
this.#onEvent(live, event); },
|
|
327
433
|
onPermissionRequest: (permission) => (live ? this.#onPermission(live, permission) : Promise.resolve({ outcome: 'cancelled' })),
|
|
434
|
+
onElicitation: (elicitation) => (live ? this.#onElicitation(live, elicitation) : Promise.resolve({ action: 'cancel' })),
|
|
328
435
|
onStderr: (line) => log(`${tag} stderr: ${redactSecrets(line)}`),
|
|
329
436
|
spawnOptions: { detached: process.platform !== 'win32' },
|
|
330
437
|
});
|
|
331
438
|
try {
|
|
332
439
|
const initialized = await client.initialize({
|
|
333
440
|
clientInfo: { name: 'awb-agent-session', version: this.#options.clientVersion || '1' },
|
|
334
|
-
clientCapabilities: {
|
|
441
|
+
clientCapabilities: {
|
|
442
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
443
|
+
terminal: false,
|
|
444
|
+
elicitation: { form: {}, url: {} },
|
|
445
|
+
session: { configOptions: { boolean: {} } },
|
|
446
|
+
plan: {},
|
|
447
|
+
},
|
|
335
448
|
});
|
|
336
449
|
const caps = (initialized?.agentCapabilities ?? {});
|
|
337
450
|
const loadSupported = caps.loadSession === true;
|
|
451
|
+
const authMethods = Array.isArray(initialized?.authMethods) ? initialized.authMethods : [];
|
|
338
452
|
const sessionIdForMcp = requestedSessionId || 'new';
|
|
339
453
|
const mcpServers = this.#options.mcpServers ? this.#options.mcpServers(sessionIdForMcp) : this.#defaultMcpServers(sessionIdForMcp);
|
|
340
454
|
live = {
|
|
@@ -346,6 +460,11 @@ export class AgentSessionRunner {
|
|
|
346
460
|
loadSupported,
|
|
347
461
|
loading: false,
|
|
348
462
|
pendingPermissions: new Map(),
|
|
463
|
+
pendingElicitations: new Map(),
|
|
464
|
+
configOptions: [],
|
|
465
|
+
availableCommands: [],
|
|
466
|
+
currentMode: null,
|
|
467
|
+
availableModes: [],
|
|
349
468
|
turn: null,
|
|
350
469
|
textBuffer: '',
|
|
351
470
|
reasoningBuffer: '',
|
|
@@ -359,6 +478,7 @@ export class AgentSessionRunner {
|
|
|
359
478
|
exited: false,
|
|
360
479
|
};
|
|
361
480
|
let modes;
|
|
481
|
+
let configOptions;
|
|
362
482
|
let resumed = false;
|
|
363
483
|
if (requestedSessionId) {
|
|
364
484
|
if (!loadSupported) {
|
|
@@ -366,8 +486,9 @@ export class AgentSessionRunner {
|
|
|
366
486
|
}
|
|
367
487
|
live.loading = true;
|
|
368
488
|
try {
|
|
369
|
-
const loaded = await client.loadSession({ sessionId: requestedSessionId, cwd, mcpServers });
|
|
489
|
+
const loaded = await this.#withAuthRetry(client, cli, authMethods, env, () => client.loadSession({ sessionId: requestedSessionId, cwd, mcpServers }));
|
|
370
490
|
modes = loaded?.modes;
|
|
491
|
+
configOptions = loaded?.configOptions;
|
|
371
492
|
resumed = true;
|
|
372
493
|
}
|
|
373
494
|
finally {
|
|
@@ -375,9 +496,10 @@ export class AgentSessionRunner {
|
|
|
375
496
|
}
|
|
376
497
|
}
|
|
377
498
|
else {
|
|
378
|
-
const created = await client.newSession({ cwd, mcpServers });
|
|
499
|
+
const created = await this.#withAuthRetry(client, cli, authMethods, env, () => client.newSession({ cwd, mcpServers }));
|
|
379
500
|
live.sessionId = created.sessionId;
|
|
380
501
|
modes = created.modes;
|
|
502
|
+
configOptions = created.configOptions;
|
|
381
503
|
await this.#store.recordAwbSession({ cli, session_id: live.sessionId, cwd, title }).catch(() => undefined);
|
|
382
504
|
}
|
|
383
505
|
if (!live.sessionId)
|
|
@@ -386,6 +508,9 @@ export class AgentSessionRunner {
|
|
|
386
508
|
this.#live.set(key, live);
|
|
387
509
|
client.process.once('exit', (code, signal) => this.#onProcessExit(key, code, signal));
|
|
388
510
|
const modeInfo = this.#parseModes(modes);
|
|
511
|
+
live.currentMode = modeInfo.current;
|
|
512
|
+
live.availableModes = modeInfo.available;
|
|
513
|
+
live.configOptions = parseConfigOptions(configOptions);
|
|
389
514
|
this.#enqueue(live, [{
|
|
390
515
|
type: 'system',
|
|
391
516
|
payload: {
|
|
@@ -398,6 +523,8 @@ export class AgentSessionRunner {
|
|
|
398
523
|
...(title ? { title } : {}),
|
|
399
524
|
current_mode: modeInfo.current,
|
|
400
525
|
available_modes: modeInfo.available,
|
|
526
|
+
config_options: live.configOptions,
|
|
527
|
+
available_commands: live.availableCommands,
|
|
401
528
|
resume_supported: loadSupported,
|
|
402
529
|
last_error: null,
|
|
403
530
|
reason: resumed ? 'resumed' : 'opened',
|
|
@@ -422,8 +549,52 @@ export class AgentSessionRunner {
|
|
|
422
549
|
env.AWB_MANAGER_ID = this.#options.getManagerId();
|
|
423
550
|
env.AWB_SESSION_CLI = cli;
|
|
424
551
|
env.AWB_SESSION_ID = sessionId;
|
|
552
|
+
if (cli === 'codex' && env.NO_BROWSER === undefined)
|
|
553
|
+
env.NO_BROWSER = '1';
|
|
425
554
|
return env;
|
|
426
555
|
}
|
|
556
|
+
async #withAuthRetry(client, cli, authMethods, env, run) {
|
|
557
|
+
try {
|
|
558
|
+
return await run();
|
|
559
|
+
}
|
|
560
|
+
catch (err) {
|
|
561
|
+
const rpcCode = err?.rpcCode ?? err?.code;
|
|
562
|
+
const message = String(err?.message ?? '');
|
|
563
|
+
const authRequired = rpcCode === -32000 || /auth(entication)? required|not (logged|signed) in|unauthenticated/i.test(message);
|
|
564
|
+
if (!authRequired)
|
|
565
|
+
throw err;
|
|
566
|
+
const methods = authMethods
|
|
567
|
+
.filter((m) => !!m && typeof m === 'object')
|
|
568
|
+
.map((m) => ({ id: String(m.id ?? ''), name: String(m.name ?? m.id ?? ''), type: String(m.type ?? '') }))
|
|
569
|
+
.filter((m) => m.id);
|
|
570
|
+
const hasApiKey = !!(env.CODEX_API_KEY || env.OPENAI_API_KEY || env.ANTHROPIC_API_KEY || env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
571
|
+
const apiKeyMethod = methods.find((m) => /api[-_]?key|token/i.test(m.id) || /api[-_]?key|token/i.test(m.name));
|
|
572
|
+
if (hasApiKey && apiKeyMethod) {
|
|
573
|
+
log(`[agent-session ${cli}] authentication required — trying ACP auth method ${apiKeyMethod.id}`);
|
|
574
|
+
await client.authenticate(apiKeyMethod.id, { timeoutMs: this.#options.requestTimeoutMs });
|
|
575
|
+
return await run();
|
|
576
|
+
}
|
|
577
|
+
const listed = methods.length ? ` Available methods: ${methods.map((m) => m.name || m.id).join(', ')}.` : '';
|
|
578
|
+
throw Object.assign(new Error(`Authentication required for ${cli} on this Runtime Host — run \`${cli} login\` there, or bind a credential in CLI settings.${listed}`), { code: 'auth_required' });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async #setConfigOption(live, configId, value) {
|
|
582
|
+
const before = live.configOptions.find((o) => o.config_id === configId);
|
|
583
|
+
const response = await live.client.setConfigOption(typeof value === 'boolean'
|
|
584
|
+
? { sessionId: live.sessionId, configId, type: 'boolean', value }
|
|
585
|
+
: { sessionId: live.sessionId, configId, type: 'id', value }, { timeoutMs: this.#options.requestTimeoutMs });
|
|
586
|
+
if (Array.isArray(response?.configOptions))
|
|
587
|
+
live.configOptions = parseConfigOptions(response.configOptions);
|
|
588
|
+
else if (before) {
|
|
589
|
+
before.current_value = value;
|
|
590
|
+
}
|
|
591
|
+
const after = live.configOptions.find((o) => o.config_id === configId);
|
|
592
|
+
const label = after?.name || before?.name || configId;
|
|
593
|
+
const chosen = typeof value === 'boolean'
|
|
594
|
+
? (value ? 'on' : 'off')
|
|
595
|
+
: (after?.options.find((o) => o.value === value)?.name || String(value));
|
|
596
|
+
this.#enqueue(live, [{ type: 'system', payload: { text: `${label} set to ${chosen}.` } }], { config_options: live.configOptions, reason: 'config_option' });
|
|
597
|
+
}
|
|
427
598
|
async #prepareAuth(cli, cwd, request) {
|
|
428
599
|
const none = { label: 'operator-login', env: {}, stripEnvKeys: [], cliHome: null };
|
|
429
600
|
const credentialId = request.credential_id || '';
|
|
@@ -511,6 +682,8 @@ export class AgentSessionRunner {
|
|
|
511
682
|
return 'idle';
|
|
512
683
|
if (live.pendingPermissions.size > 0)
|
|
513
684
|
return 'awaiting_permission';
|
|
685
|
+
if (live.pendingElicitations.size > 0)
|
|
686
|
+
return 'awaiting_input';
|
|
514
687
|
if (live.turn)
|
|
515
688
|
return 'busy';
|
|
516
689
|
return 'ready';
|
|
@@ -526,6 +699,10 @@ export class AgentSessionRunner {
|
|
|
526
699
|
title: live.title,
|
|
527
700
|
status: this.#statusOf(live),
|
|
528
701
|
resume_supported: live.loadSupported,
|
|
702
|
+
current_mode: live.currentMode,
|
|
703
|
+
available_modes: live.availableModes,
|
|
704
|
+
config_options: live.configOptions,
|
|
705
|
+
available_commands: live.availableCommands,
|
|
529
706
|
};
|
|
530
707
|
}
|
|
531
708
|
async #runPrompt(live, turnId, text) {
|
|
@@ -622,10 +799,53 @@ export class AgentSessionRunner {
|
|
|
622
799
|
case 'diagnostic': {
|
|
623
800
|
const data = (event.data ?? {});
|
|
624
801
|
const kind = String(data.sessionUpdate ?? data.session_update ?? '');
|
|
625
|
-
if (event.method === 'session/update'
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
802
|
+
if (event.method === 'session/update') {
|
|
803
|
+
switch (kind) {
|
|
804
|
+
case 'current_mode_update': {
|
|
805
|
+
const modeId = String(data.currentModeId ?? data.current_mode_id ?? '');
|
|
806
|
+
if (modeId) {
|
|
807
|
+
live.currentMode = modeId;
|
|
808
|
+
this.#enqueue(live, [], { current_mode: modeId, reason: 'mode' });
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
case 'config_option_update':
|
|
813
|
+
live.configOptions = parseConfigOptions(data.configOptions ?? data.config_options);
|
|
814
|
+
this.#enqueue(live, [], { config_options: live.configOptions, reason: 'config_option' });
|
|
815
|
+
return;
|
|
816
|
+
case 'available_commands_update':
|
|
817
|
+
live.availableCommands = parseCommands(data.availableCommands ?? data.available_commands);
|
|
818
|
+
this.#enqueue(live, [], { available_commands: live.availableCommands, reason: 'commands' });
|
|
819
|
+
return;
|
|
820
|
+
case 'plan':
|
|
821
|
+
case 'plan_update': {
|
|
822
|
+
const entries = parsePlanEntries(kind === 'plan' ? data.entries : data.plan?.entries ?? data.entries);
|
|
823
|
+
if (entries) {
|
|
824
|
+
this.#flushBuffers(live, turnId);
|
|
825
|
+
this.#enqueue(live, [{ type: 'plan', payload: { entries }, turn_id: turnId }]);
|
|
826
|
+
}
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
case 'plan_removed':
|
|
830
|
+
return;
|
|
831
|
+
case 'session_info_update': {
|
|
832
|
+
const title = typeof data.title === 'string' ? data.title.trim().slice(0, 200) : '';
|
|
833
|
+
if (title && title !== live.title) {
|
|
834
|
+
live.title = title;
|
|
835
|
+
this.#enqueue(live, [], { title, reason: 'title' });
|
|
836
|
+
void this.#store.touchAwbSession(live.cli, live.sessionId, { title }).catch(() => undefined);
|
|
837
|
+
}
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
default:
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (event.method === 'elicitation/complete') {
|
|
845
|
+
const elicitationId = String(data.elicitationId ?? data.elicitation_id ?? '');
|
|
846
|
+
if (elicitationId) {
|
|
847
|
+
this.#enqueue(live, [{ type: 'elicitation_decision', payload: { elicitation_id: elicitationId, action: 'accept', decided_by: 'agent' }, turn_id: turnId }]);
|
|
848
|
+
}
|
|
629
849
|
return;
|
|
630
850
|
}
|
|
631
851
|
log(`[agent-session ${live.cli} ${live.sessionId.slice(0, 8)}] diagnostic ${event.method}${kind ? ` (${kind})` : ''}`);
|
|
@@ -642,12 +862,16 @@ export class AgentSessionRunner {
|
|
|
642
862
|
this.#flushBuffers(live, turnId);
|
|
643
863
|
const requestId = randomUUID();
|
|
644
864
|
const options = (permission.options ?? []).map((o) => ({ option_id: o.optionId, name: o.name, kind: o.kind }));
|
|
645
|
-
|
|
865
|
+
const meta = (permission._meta?.permission ?? null);
|
|
866
|
+
const title = permission.title || permission.toolCall?.title || (typeof meta?.title === 'string' ? meta.title : '');
|
|
867
|
+
const description = permission.description || (typeof meta?.description === 'string' ? meta.description : '');
|
|
868
|
+
const [requestEvent] = this.#enqueue(live, [{
|
|
646
869
|
type: 'permission_request',
|
|
647
870
|
payload: {
|
|
648
871
|
request_id: requestId,
|
|
649
|
-
tool_call_id: permission.toolCall?.toolCallId ?? '',
|
|
650
|
-
title
|
|
872
|
+
tool_call_id: permission.toolCall?.toolCallId ?? permission.subject?.toolCallId ?? '',
|
|
873
|
+
title,
|
|
874
|
+
...(description ? { description } : {}),
|
|
651
875
|
kind: permission.toolCall?.kind ?? '',
|
|
652
876
|
options,
|
|
653
877
|
raw_input: boundedValue(permission.toolCall?.rawInput ?? permission.toolCall?.raw_input),
|
|
@@ -665,9 +889,88 @@ export class AgentSessionRunner {
|
|
|
665
889
|
resolve({ outcome: 'cancelled' });
|
|
666
890
|
}, this.#options.permissionTimeoutMs);
|
|
667
891
|
timer.unref?.();
|
|
668
|
-
live.pendingPermissions.set(requestId, { resolve, timer });
|
|
892
|
+
live.pendingPermissions.set(requestId, { resolve, timer, event: requestEvent });
|
|
669
893
|
});
|
|
670
894
|
}
|
|
895
|
+
#cancelPendingPermissions(live) {
|
|
896
|
+
const events = [];
|
|
897
|
+
for (const [requestId, pending] of live.pendingPermissions) {
|
|
898
|
+
clearTimeout(pending.timer);
|
|
899
|
+
live.pendingPermissions.delete(requestId);
|
|
900
|
+
events.push({
|
|
901
|
+
type: 'permission_decision',
|
|
902
|
+
payload: { request_id: requestId, outcome: 'cancelled', option_id: null, decided_by: 'system' },
|
|
903
|
+
turn_id: live.turn?.turnId,
|
|
904
|
+
});
|
|
905
|
+
pending.resolve({ outcome: 'cancelled' });
|
|
906
|
+
}
|
|
907
|
+
for (const [elicitationId, pending] of live.pendingElicitations) {
|
|
908
|
+
clearTimeout(pending.timer);
|
|
909
|
+
live.pendingElicitations.delete(elicitationId);
|
|
910
|
+
events.push({
|
|
911
|
+
type: 'elicitation_decision',
|
|
912
|
+
payload: { elicitation_id: elicitationId, action: 'cancel', decided_by: 'system' },
|
|
913
|
+
turn_id: live.turn?.turnId,
|
|
914
|
+
});
|
|
915
|
+
pending.resolve({ action: 'cancel' });
|
|
916
|
+
}
|
|
917
|
+
if (events.length)
|
|
918
|
+
this.#enqueue(live, events);
|
|
919
|
+
}
|
|
920
|
+
async #onElicitation(live, request) {
|
|
921
|
+
if (live.loading)
|
|
922
|
+
return { action: 'cancel' };
|
|
923
|
+
const turnId = live.turn?.turnId;
|
|
924
|
+
this.#flushBuffers(live, turnId);
|
|
925
|
+
const mode = request.mode === 'url' ? 'url' : 'form';
|
|
926
|
+
const elicitationId = (mode === 'url' && typeof request.elicitationId === 'string' && request.elicitationId) ? request.elicitationId : randomUUID();
|
|
927
|
+
const payload = {
|
|
928
|
+
elicitation_id: elicitationId,
|
|
929
|
+
mode,
|
|
930
|
+
message: typeof request.message === 'string' ? request.message.slice(0, 8_000) : '',
|
|
931
|
+
tool_call_id: typeof request.toolCallId === 'string' ? request.toolCallId : '',
|
|
932
|
+
};
|
|
933
|
+
if (mode === 'form')
|
|
934
|
+
payload.schema = boundedValue(request.requestedSchema ?? {});
|
|
935
|
+
if (mode === 'url')
|
|
936
|
+
payload.url = typeof request.url === 'string' ? request.url.slice(0, 2_048) : '';
|
|
937
|
+
if (mode === 'url') {
|
|
938
|
+
this.#enqueue(live, [{ type: 'elicitation_request', payload, turn_id: turnId }]);
|
|
939
|
+
return { action: 'accept' };
|
|
940
|
+
}
|
|
941
|
+
const [requestEvent] = this.#enqueue(live, [{ type: 'elicitation_request', payload, turn_id: turnId }], { status: 'awaiting_input', reason: 'elicitation' });
|
|
942
|
+
return new Promise((resolve) => {
|
|
943
|
+
const timer = setTimeout(() => {
|
|
944
|
+
live.pendingElicitations.delete(elicitationId);
|
|
945
|
+
this.#enqueue(live, [{
|
|
946
|
+
type: 'elicitation_decision',
|
|
947
|
+
payload: { elicitation_id: elicitationId, action: 'cancel', decided_by: 'timeout' },
|
|
948
|
+
turn_id: live.turn?.turnId,
|
|
949
|
+
}], { status: this.#statusOf(live) === 'awaiting_input' ? 'busy' : this.#statusOf(live), reason: 'elicitation_timeout' });
|
|
950
|
+
resolve({ action: 'cancel' });
|
|
951
|
+
}, this.#options.permissionTimeoutMs);
|
|
952
|
+
timer.unref?.();
|
|
953
|
+
live.pendingElicitations.set(elicitationId, { resolve, timer, event: requestEvent });
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
#resolveElicitation(cli, sessionId, elicitationId, action, content) {
|
|
957
|
+
const live = this.#live.get(this.#key(cli, sessionId));
|
|
958
|
+
if (!live)
|
|
959
|
+
return;
|
|
960
|
+
const pending = live.pendingElicitations.get(elicitationId);
|
|
961
|
+
if (!pending) {
|
|
962
|
+
log(`[agent-session ${cli} ${sessionId.slice(0, 8)}] elicitation ${elicitationId.slice(0, 8)} not pending (late or duplicate answer)`);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
clearTimeout(pending.timer);
|
|
966
|
+
live.pendingElicitations.delete(elicitationId);
|
|
967
|
+
this.#enqueue(live, [{
|
|
968
|
+
type: 'elicitation_decision',
|
|
969
|
+
payload: { elicitation_id: elicitationId, action, ...(action === 'accept' ? { content: boundedValue(content ?? {}) } : {}), decided_by: 'user' },
|
|
970
|
+
turn_id: live.turn?.turnId,
|
|
971
|
+
}], { status: live.pendingPermissions.size > 0 ? 'awaiting_permission' : 'busy', reason: `elicitation_${action}` });
|
|
972
|
+
pending.resolve(action === 'accept' ? { action: 'accept', content: content ?? {} } : { action });
|
|
973
|
+
}
|
|
671
974
|
#resolvePermission(cli, sessionId, requestId, optionId) {
|
|
672
975
|
const live = this.#live.get(this.#key(cli, sessionId));
|
|
673
976
|
if (!live)
|
|
@@ -724,6 +1027,7 @@ export class AgentSessionRunner {
|
|
|
724
1027
|
? postAgentSessionEvents(this.#config, ref, stamped, state ?? null)
|
|
725
1028
|
: Promise.resolve({ ok: true, status: 204 })))
|
|
726
1029
|
.then(() => undefined, () => undefined);
|
|
1030
|
+
return stamped;
|
|
727
1031
|
}
|
|
728
1032
|
#touch(live) {
|
|
729
1033
|
this.#clearIdle(live);
|
|
@@ -733,7 +1037,7 @@ export class AgentSessionRunner {
|
|
|
733
1037
|
if (ms <= 0)
|
|
734
1038
|
return;
|
|
735
1039
|
live.idleTimer = setTimeout(() => {
|
|
736
|
-
if (live.turn || live.pendingPermissions.size > 0) {
|
|
1040
|
+
if (live.turn || live.pendingPermissions.size > 0 || live.pendingElicitations.size > 0) {
|
|
737
1041
|
this.#touch(live);
|
|
738
1042
|
return;
|
|
739
1043
|
}
|
|
@@ -752,19 +1056,20 @@ export class AgentSessionRunner {
|
|
|
752
1056
|
if (!live)
|
|
753
1057
|
return;
|
|
754
1058
|
live.exited = true;
|
|
755
|
-
for (const [id, pending] of live.pendingPermissions) {
|
|
756
|
-
clearTimeout(pending.timer);
|
|
757
|
-
pending.resolve({ outcome: 'cancelled' });
|
|
758
|
-
live.pendingPermissions.delete(id);
|
|
759
|
-
}
|
|
760
1059
|
this.#clearIdle(live);
|
|
761
|
-
if (live.closing)
|
|
1060
|
+
if (live.closing) {
|
|
1061
|
+
this.#cancelPendingPermissions(live);
|
|
762
1062
|
return;
|
|
1063
|
+
}
|
|
763
1064
|
this.#live.delete(key);
|
|
764
1065
|
const detail = `Agent process exited (${signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`}).`;
|
|
765
1066
|
log(`[agent-session ${live.cli} ${live.sessionId.slice(0, 8)}] ${detail}`);
|
|
766
1067
|
this.#flushBuffers(live, live.turn?.turnId);
|
|
767
|
-
this.#
|
|
1068
|
+
this.#cancelPendingPermissions(live);
|
|
1069
|
+
this.#enqueue(live, [{ type: 'system', payload: { text: `${detail} The next prompt reopens the session.` } }], { status: 'idle', reason: 'process_exit' });
|
|
1070
|
+
this.#exited.push(live);
|
|
1071
|
+
if (this.#exited.length > 50)
|
|
1072
|
+
this.#exited.splice(0, this.#exited.length - 50);
|
|
768
1073
|
}
|
|
769
1074
|
async #closeLive(cli, sessionId, finalStatus, reason = finalStatus) {
|
|
770
1075
|
const key = this.#key(cli, sessionId);
|
|
@@ -773,11 +1078,7 @@ export class AgentSessionRunner {
|
|
|
773
1078
|
return;
|
|
774
1079
|
live.closing = true;
|
|
775
1080
|
this.#clearIdle(live);
|
|
776
|
-
|
|
777
|
-
clearTimeout(pending.timer);
|
|
778
|
-
pending.resolve({ outcome: 'cancelled' });
|
|
779
|
-
live.pendingPermissions.delete(id);
|
|
780
|
-
}
|
|
1081
|
+
this.#cancelPendingPermissions(live);
|
|
781
1082
|
if (live.turn)
|
|
782
1083
|
await live.client.cancel(live.sessionId).catch(() => undefined);
|
|
783
1084
|
this.#flushBuffers(live, live.turn?.turnId);
|