@agentshouse/kit 0.2.1-c0b2838a220ac → 0.2.1-c0eea939ba749
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -15
- package/dist/acp.js +34 -14
- package/dist/agents.js +85 -17
- package/dist/api.js +1 -1
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +3 -6
- package/dist/chatgpt.js +9 -2
- package/dist/clis.js +11 -1
- package/dist/conversations.js +133 -73
- package/dist/copies.js +86 -45
- package/dist/end.js +3 -2
- package/dist/events.js +4 -0
- package/dist/files.js +1 -1
- package/dist/git.js +2 -3
- package/dist/home.js +31 -13
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -262
- package/dist/inputs.js +37 -0
- package/dist/instructions.js +4 -28
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -67
- package/dist/login.js +39 -10
- package/dist/logout.js +0 -2
- package/dist/placement.js +2 -20
- package/dist/resident.js +60 -15
- package/dist/service.js +22 -0
- package/dist/sign-in.js +5 -23
- package/dist/skills.js +71 -12
- package/dist/stream.js +62 -16
- package/dist/transcription.js +3 -3
- package/dist/update.js +20 -23
- package/dist/wire.js +1 -1
- package/dist/work.js +3 -5
- package/how-we-work.md +13 -3
- package/package.json +2 -1
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/skills/resolving-merge-conflicts/SKILL.md +0 -16
- package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- /package/skills/{resolving-merge-conflicts → implement-spec}/LICENSE +0 -0
package/dist/conversations.js
CHANGED
|
@@ -1,39 +1,88 @@
|
|
|
1
|
-
import { client, } from '@agentclientprotocol/sdk';
|
|
1
|
+
import { client, RequestError, } from '@agentclientprotocol/sdk';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
6
|
import { startAdapter } from './acp.js';
|
|
7
|
+
import { finalRefusal } from './api.js';
|
|
7
8
|
import { openBridge } from './bridge.js';
|
|
8
9
|
import { CLIS } from './clis.js';
|
|
9
|
-
import { readerFor } from './events.js';
|
|
10
|
+
import { answeredFailure, readerFor } from './events.js';
|
|
10
11
|
import { placeFiles, uploadBytes } from './files.js';
|
|
11
12
|
import { agentBase } from './home.js';
|
|
12
13
|
import { instructions } from './instructions.js';
|
|
13
14
|
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
14
16
|
import { killMarked, Scope } from './scope.js';
|
|
15
17
|
import { holdSecretInput } from './secret-input.js';
|
|
16
18
|
import { createHowWeWork } from './skills.js';
|
|
17
19
|
import { transcribe } from './transcription.js';
|
|
18
20
|
import { workFor } from './work.js';
|
|
21
|
+
const SIGNED_OUT = RequestError.authRequired().code;
|
|
19
22
|
const KILLED = 'the conversation was killed';
|
|
23
|
+
const REFUSED = 'the CLI refused the prompt';
|
|
24
|
+
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
|
+
const GONE = 'House no longer holds this conversation; start a new one';
|
|
26
|
+
const RETIRED = "this conversation's Agent is retired, so it cannot start";
|
|
27
|
+
const ENDED = {
|
|
28
|
+
conversation_not_found: GONE,
|
|
29
|
+
conversation_historical: RETIRED,
|
|
30
|
+
};
|
|
31
|
+
const NO_START = "the Agent's CLI did not start";
|
|
32
|
+
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
33
|
+
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
34
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
35
|
+
class NotStarted extends Error {
|
|
36
|
+
}
|
|
37
|
+
async function step(reason, work) {
|
|
38
|
+
try {
|
|
39
|
+
return await work;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function unbridged(error) {
|
|
46
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
47
|
+
}
|
|
48
|
+
const EXIT_PLAN_MODE = {
|
|
49
|
+
parse(value) {
|
|
50
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
51
|
+
throw new Error('_x.ai/exit_plan_mode needs an object');
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
20
56
|
export const CONVERSATION_INPUTS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
21
57
|
function causeOf(error) {
|
|
22
58
|
return error instanceof Error ? error.message : String(error);
|
|
23
59
|
}
|
|
60
|
+
function answeredWith(error) {
|
|
61
|
+
const signedOut = typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
62
|
+
return { failure: signedOut ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
63
|
+
}
|
|
64
|
+
function exitedWith(running, cause) {
|
|
65
|
+
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
66
|
+
}
|
|
24
67
|
function answerMessage(request, response) {
|
|
25
68
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
26
69
|
}
|
|
27
70
|
function logged(error) {
|
|
28
71
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
29
72
|
}
|
|
73
|
+
function refusal(conversation, error) {
|
|
74
|
+
if (conversation.kills > 0)
|
|
75
|
+
return KILLED;
|
|
76
|
+
const cause = error instanceof NotStarted ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
77
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
78
|
+
return causeOf(error);
|
|
79
|
+
}
|
|
30
80
|
function marker(bridge) {
|
|
31
81
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
32
82
|
}
|
|
33
83
|
class Turn {
|
|
34
84
|
id = randomUUID();
|
|
35
85
|
parts;
|
|
36
|
-
admitted = false;
|
|
37
86
|
questions = 0;
|
|
38
87
|
secrets = 0;
|
|
39
88
|
prompted = false;
|
|
@@ -57,6 +106,7 @@ class Conversation {
|
|
|
57
106
|
subagents = new Map();
|
|
58
107
|
held = [];
|
|
59
108
|
opening = null;
|
|
109
|
+
prompted = null;
|
|
60
110
|
kills = 0;
|
|
61
111
|
killed = new AbortController();
|
|
62
112
|
interrupts = 0;
|
|
@@ -79,9 +129,7 @@ function asksSecret(request) {
|
|
|
79
129
|
export class Conversations {
|
|
80
130
|
kit;
|
|
81
131
|
conversations = new Map();
|
|
82
|
-
acks = new Map();
|
|
83
132
|
questions = new Map();
|
|
84
|
-
carrying = 0;
|
|
85
133
|
reporting = 0;
|
|
86
134
|
watched = false;
|
|
87
135
|
constructor(kit) {
|
|
@@ -90,37 +138,26 @@ export class Conversations {
|
|
|
90
138
|
setInterval(() => this.look(), 2_000);
|
|
91
139
|
}
|
|
92
140
|
input(input) {
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
conversation.queue = handled.catch(() => undefined);
|
|
110
|
-
const work = handled.then(({ ack }) => ack);
|
|
111
|
-
this.acks.set(input.input_id, work);
|
|
112
|
-
return work;
|
|
113
|
-
})();
|
|
114
|
-
carried
|
|
115
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
116
|
-
.catch(logged)
|
|
117
|
-
.finally(() => {
|
|
118
|
-
this.carrying--;
|
|
119
|
-
this.kit.changed();
|
|
141
|
+
void this.kit.inputs.carry(input.input_id, () => {
|
|
142
|
+
const conversation = this.conversation(input.conversation_id);
|
|
143
|
+
if (input.kind === 'kill') {
|
|
144
|
+
conversation.kills++;
|
|
145
|
+
conversation.killed.abort(new Error(KILLED));
|
|
146
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
147
|
+
waiting.settle({ refused: KILLED });
|
|
148
|
+
this.halt(conversation);
|
|
149
|
+
}
|
|
150
|
+
if (input.kind === 'interrupt') {
|
|
151
|
+
conversation.interrupts++;
|
|
152
|
+
conversation.interrupted.abort();
|
|
153
|
+
}
|
|
154
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
155
|
+
conversation.queue = handled.catch(() => undefined);
|
|
156
|
+
return handled.then(async ({ ack }) => ({ ack: await ack }));
|
|
120
157
|
});
|
|
121
158
|
}
|
|
122
159
|
idle() {
|
|
123
|
-
if (this.
|
|
160
|
+
if (this.reporting > 0)
|
|
124
161
|
return false;
|
|
125
162
|
for (const conversation of this.conversations.values()) {
|
|
126
163
|
const turn = conversation.turn;
|
|
@@ -200,10 +237,10 @@ export class Conversations {
|
|
|
200
237
|
if (conversation.running !== null)
|
|
201
238
|
return {};
|
|
202
239
|
try {
|
|
203
|
-
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null) };
|
|
240
|
+
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null, true) };
|
|
204
241
|
}
|
|
205
242
|
catch (error) {
|
|
206
|
-
return { refused:
|
|
243
|
+
return { refused: refusal(conversation, error) };
|
|
207
244
|
}
|
|
208
245
|
}
|
|
209
246
|
async message(conversation, input) {
|
|
@@ -220,7 +257,7 @@ export class Conversations {
|
|
|
220
257
|
throw new Error(KILLED);
|
|
221
258
|
}
|
|
222
259
|
catch (error) {
|
|
223
|
-
return { ack: Promise.resolve({ refused: conversation
|
|
260
|
+
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
224
261
|
}
|
|
225
262
|
return {
|
|
226
263
|
ack: new Promise((settle) => {
|
|
@@ -243,13 +280,17 @@ export class Conversations {
|
|
|
243
280
|
async send(conversation, waiting) {
|
|
244
281
|
const { input } = waiting;
|
|
245
282
|
let ack = {};
|
|
283
|
+
let started = false;
|
|
246
284
|
const prompt = [];
|
|
247
285
|
try {
|
|
248
286
|
if (conversation.running === null) {
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
287
|
+
const stored = conversation.session ?? input.provider_session_id;
|
|
288
|
+
const empty = input.first === true && conversation.prompted !== stored;
|
|
289
|
+
started = (await this.start(conversation, waiting.route, stored, empty)) !== stored;
|
|
290
|
+
}
|
|
291
|
+
const session = conversation.running.sessionId;
|
|
292
|
+
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
293
|
+
ack = { provider_session_id: session };
|
|
253
294
|
}
|
|
254
295
|
if (input.first === true) {
|
|
255
296
|
prompt.push({
|
|
@@ -261,11 +302,11 @@ export class Conversations {
|
|
|
261
302
|
throw new Error(KILLED);
|
|
262
303
|
}
|
|
263
304
|
catch (error) {
|
|
264
|
-
if (
|
|
305
|
+
if (started) {
|
|
265
306
|
conversation.session = null;
|
|
266
307
|
await this.kill(conversation);
|
|
267
308
|
}
|
|
268
|
-
waiting.settle({ refused: conversation
|
|
309
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
269
310
|
return;
|
|
270
311
|
}
|
|
271
312
|
this.prompt(conversation, [...prompt, ...waiting.content]);
|
|
@@ -343,14 +384,15 @@ export class Conversations {
|
|
|
343
384
|
}
|
|
344
385
|
return route;
|
|
345
386
|
}
|
|
346
|
-
async start(conversation, route, sessionId) {
|
|
387
|
+
async start(conversation, route, sessionId, empty) {
|
|
347
388
|
const reader = readerFor(route.kind);
|
|
348
389
|
reader.session = sessionId;
|
|
349
390
|
const app = client({ name: '@agentshouse/kit' })
|
|
350
391
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
351
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
392
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
393
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
352
394
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
353
|
-
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
395
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
354
396
|
if (conversation.kills > 0) {
|
|
355
397
|
bridge.close();
|
|
356
398
|
throw new Error(KILLED);
|
|
@@ -358,27 +400,28 @@ export class Conversations {
|
|
|
358
400
|
conversation.opening = bridge;
|
|
359
401
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
360
402
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
403
|
+
let adapter = null;
|
|
361
404
|
try {
|
|
362
405
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
363
406
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
364
|
-
const
|
|
407
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
408
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
365
409
|
work.read(message);
|
|
366
410
|
this.read(conversation, reader, message);
|
|
367
|
-
}, { ...bridge.env, ...
|
|
411
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
368
412
|
void adapter.exited.then(() => bridge.close());
|
|
369
413
|
const agent = adapter.connection.agent;
|
|
370
414
|
const cwd = route.working_directory;
|
|
371
415
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
416
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
417
|
+
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
375
418
|
reader.session = opened.sessionId;
|
|
376
|
-
if (sessionId
|
|
419
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
377
420
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
378
|
-
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
421
|
+
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
379
422
|
}
|
|
380
423
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
381
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
424
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
382
425
|
void work.settled().then(() => scope.record());
|
|
383
426
|
conversation.running = running;
|
|
384
427
|
conversation.session = running.sessionId;
|
|
@@ -386,11 +429,11 @@ export class Conversations {
|
|
|
386
429
|
if (conversation.commands !== null)
|
|
387
430
|
this.commands(conversation, conversation.commands);
|
|
388
431
|
this.options(conversation, options);
|
|
389
|
-
void adapter.
|
|
432
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
390
433
|
return running.sessionId;
|
|
391
434
|
}
|
|
392
435
|
catch (error) {
|
|
393
|
-
|
|
436
|
+
adapter?.retire();
|
|
394
437
|
work.close();
|
|
395
438
|
conversation.leftovers.add(scope);
|
|
396
439
|
bridge.close();
|
|
@@ -400,6 +443,18 @@ export class Conversations {
|
|
|
400
443
|
conversation.opening = null;
|
|
401
444
|
}
|
|
402
445
|
}
|
|
446
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
447
|
+
try {
|
|
448
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
452
|
+
throw new NotStarted(NO_RESUME, { cause: error });
|
|
453
|
+
}
|
|
454
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
403
458
|
async launchSettings(running, route, offered) {
|
|
404
459
|
let options = offered;
|
|
405
460
|
const settings = [
|
|
@@ -421,13 +476,10 @@ export class Conversations {
|
|
|
421
476
|
}
|
|
422
477
|
return options;
|
|
423
478
|
}
|
|
424
|
-
exited(conversation,
|
|
425
|
-
const running = conversation.running;
|
|
426
|
-
if (running?.adapter !== adapter)
|
|
427
|
-
return;
|
|
479
|
+
exited(conversation, running, cause) {
|
|
428
480
|
this.detach(conversation, running);
|
|
429
481
|
if (conversation.turn !== null) {
|
|
430
|
-
this.end(conversation, conversation.turn,
|
|
482
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
431
483
|
}
|
|
432
484
|
this.next(conversation);
|
|
433
485
|
this.kit.changed();
|
|
@@ -466,7 +518,7 @@ export class Conversations {
|
|
|
466
518
|
return delivered;
|
|
467
519
|
}
|
|
468
520
|
write(conversation, turn, operation) {
|
|
469
|
-
if (
|
|
521
|
+
if (turn.ended || !this.watched)
|
|
470
522
|
return;
|
|
471
523
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
472
524
|
}
|
|
@@ -477,12 +529,7 @@ export class Conversations {
|
|
|
477
529
|
begin(conversation) {
|
|
478
530
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
479
531
|
conversation.open++;
|
|
480
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
481
|
-
.then(() => {
|
|
482
|
-
turn.admitted = true;
|
|
483
|
-
this.replay(conversation, turn);
|
|
484
|
-
})
|
|
485
|
-
.catch(logged);
|
|
532
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
486
533
|
conversation.turn = turn;
|
|
487
534
|
if (conversation.context !== null)
|
|
488
535
|
turn.parts.measure(conversation.context);
|
|
@@ -491,13 +538,17 @@ export class Conversations {
|
|
|
491
538
|
this.kit.changed();
|
|
492
539
|
return turn;
|
|
493
540
|
}
|
|
494
|
-
end(conversation, turn, failed) {
|
|
541
|
+
end(conversation, turn, failed, notice = null) {
|
|
495
542
|
if (turn.ended)
|
|
496
543
|
return;
|
|
544
|
+
if (failed !== null)
|
|
545
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
497
546
|
if (conversation.turn === turn) {
|
|
498
547
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
499
548
|
this.apply(conversation, turn, event);
|
|
500
549
|
}
|
|
550
|
+
if (notice !== null)
|
|
551
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
501
552
|
turn.ended = true;
|
|
502
553
|
conversation.open--;
|
|
503
554
|
if (conversation.turn === turn)
|
|
@@ -505,7 +556,7 @@ export class Conversations {
|
|
|
505
556
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
506
557
|
parts: turn.parts.saved(),
|
|
507
558
|
context: turn.parts.context,
|
|
508
|
-
...failed,
|
|
559
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
509
560
|
}));
|
|
510
561
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
511
562
|
this.next(conversation);
|
|
@@ -515,16 +566,25 @@ export class Conversations {
|
|
|
515
566
|
const running = conversation.running;
|
|
516
567
|
const turn = this.begin(conversation);
|
|
517
568
|
turn.prompted = true;
|
|
518
|
-
|
|
569
|
+
conversation.prompted = running.sessionId;
|
|
570
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
571
|
+
const notice = answeredFailure(answer);
|
|
572
|
+
if (notice !== null) {
|
|
573
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
577
|
+
}
|
|
578
|
+
}, async (error) => {
|
|
519
579
|
if (running.adapter.connection.signal.aborted) {
|
|
520
|
-
this.end(conversation, turn,
|
|
580
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
521
581
|
}
|
|
522
582
|
else {
|
|
523
583
|
if (conversation.open === 1 && conversation.running === running) {
|
|
524
|
-
running.
|
|
584
|
+
running.adapter.retire();
|
|
525
585
|
this.detach(conversation, running);
|
|
526
586
|
}
|
|
527
|
-
this.end(conversation, turn,
|
|
587
|
+
this.end(conversation, turn, answeredWith(error));
|
|
528
588
|
}
|
|
529
589
|
});
|
|
530
590
|
}
|