@agentshouse/kit 0.2.1-cca9e1f43de44 → 0.2.1-ccf188e02985d
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 +30 -28
- package/dist/acp.js +41 -111
- package/dist/agents.js +110 -22
- package/dist/api.js +9 -5
- package/dist/app-git.js +10 -26
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +10 -24
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +95 -14
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +181 -114
- package/dist/copies.js +188 -119
- package/dist/end.js +19 -4
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +14 -76
- package/dist/files.js +8 -2
- package/dist/git.js +10 -8
- package/dist/home.js +96 -7
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -177
- package/dist/inputs.js +37 -0
- package/dist/instructions.js +10 -32
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -22
- package/dist/login.js +117 -106
- package/dist/logout.js +14 -16
- package/dist/parts.js +34 -16
- package/dist/placement.js +30 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +89 -33
- package/dist/scope.js +203 -0
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/sign-in.js +6 -24
- package/dist/skills.js +81 -12
- package/dist/stream.js +68 -17
- package/dist/transcription.js +3 -3
- package/dist/update.js +48 -29
- package/dist/wire.js +6 -0
- package/dist/work.js +346 -0
- package/how-we-work.md +13 -3
- package/package.json +3 -2
- 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,37 +1,85 @@
|
|
|
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
|
-
import {
|
|
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';
|
|
16
|
+
import { killMarked, Scope } from './scope.js';
|
|
14
17
|
import { holdSecretInput } from './secret-input.js';
|
|
15
18
|
import { createHowWeWork } from './skills.js';
|
|
16
19
|
import { transcribe } from './transcription.js';
|
|
20
|
+
import { workFor } from './work.js';
|
|
21
|
+
const SIGNED_OUT = RequestError.authRequired().code;
|
|
17
22
|
const KILLED = 'the conversation was killed';
|
|
18
|
-
|
|
19
|
-
const
|
|
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
|
+
};
|
|
56
|
+
export const CONVERSATION_INPUTS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
20
57
|
function causeOf(error) {
|
|
21
58
|
return error instanceof Error ? error.message : String(error);
|
|
22
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
|
+
}
|
|
23
67
|
function answerMessage(request, response) {
|
|
24
68
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
25
69
|
}
|
|
26
70
|
function logged(error) {
|
|
27
71
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
28
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
|
+
}
|
|
29
80
|
function marker(bridge) {
|
|
30
81
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
31
82
|
}
|
|
32
|
-
function stop(bridge) {
|
|
33
|
-
killMarked(marker(bridge));
|
|
34
|
-
}
|
|
35
83
|
class Turn {
|
|
36
84
|
id = randomUUID();
|
|
37
85
|
parts;
|
|
@@ -53,12 +101,13 @@ class Conversation {
|
|
|
53
101
|
turn = null;
|
|
54
102
|
commands = null;
|
|
55
103
|
context = null;
|
|
56
|
-
|
|
104
|
+
leftovers = new Set();
|
|
57
105
|
waiting = [];
|
|
58
106
|
sending = null;
|
|
59
107
|
subagents = new Map();
|
|
60
108
|
held = [];
|
|
61
109
|
opening = null;
|
|
110
|
+
prompted = null;
|
|
62
111
|
kills = 0;
|
|
63
112
|
killed = new AbortController();
|
|
64
113
|
interrupts = 0;
|
|
@@ -81,67 +130,60 @@ function asksSecret(request) {
|
|
|
81
130
|
export class Conversations {
|
|
82
131
|
kit;
|
|
83
132
|
conversations = new Map();
|
|
84
|
-
acks = new Map();
|
|
85
133
|
questions = new Map();
|
|
86
|
-
carrying = 0;
|
|
87
134
|
reporting = 0;
|
|
88
135
|
watched = false;
|
|
89
|
-
recheck = null;
|
|
90
136
|
constructor(kit) {
|
|
91
137
|
this.kit = kit;
|
|
138
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
139
|
+
setInterval(() => this.look(), 2_000);
|
|
92
140
|
}
|
|
93
141
|
input(input) {
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
conversation.queue = handled.catch(() => undefined);
|
|
111
|
-
const work = handled.then(({ ack }) => ack);
|
|
112
|
-
this.acks.set(input.input_id, work);
|
|
113
|
-
return work;
|
|
114
|
-
})();
|
|
115
|
-
carried
|
|
116
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
117
|
-
.catch(logged)
|
|
118
|
-
.finally(() => {
|
|
119
|
-
this.carrying--;
|
|
120
|
-
this.kit.changed();
|
|
142
|
+
void this.kit.inputs.carry(input.input_id, () => {
|
|
143
|
+
const conversation = this.conversation(input.conversation_id);
|
|
144
|
+
if (input.kind === 'kill') {
|
|
145
|
+
conversation.kills++;
|
|
146
|
+
conversation.killed.abort(new Error(KILLED));
|
|
147
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
148
|
+
waiting.settle({ refused: KILLED });
|
|
149
|
+
this.halt(conversation);
|
|
150
|
+
}
|
|
151
|
+
if (input.kind === 'interrupt') {
|
|
152
|
+
conversation.interrupts++;
|
|
153
|
+
conversation.interrupted.abort();
|
|
154
|
+
}
|
|
155
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
156
|
+
conversation.queue = handled.catch(() => undefined);
|
|
157
|
+
return handled.then(async ({ ack }) => ({ ack: await ack }));
|
|
121
158
|
});
|
|
122
159
|
}
|
|
123
160
|
idle() {
|
|
124
|
-
if (this.
|
|
161
|
+
if (this.reporting > 0)
|
|
125
162
|
return false;
|
|
126
163
|
for (const conversation of this.conversations.values()) {
|
|
127
164
|
const turn = conversation.turn;
|
|
128
165
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
129
|
-
if (conversation.
|
|
166
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
130
167
|
return false;
|
|
131
168
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (running === null || running.baseline === null)
|
|
135
|
-
continue;
|
|
136
|
-
if ([...processes(marker(running.bridge))].every((found) => running.baseline.has(found)))
|
|
137
|
-
continue;
|
|
138
|
-
this.recheck ??= setTimeout(() => {
|
|
139
|
-
this.recheck = null;
|
|
140
|
-
this.kit.changed();
|
|
141
|
-
}, PROCESS_RECHECK_MS);
|
|
169
|
+
const conversations = [...this.conversations.values()];
|
|
170
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
142
171
|
return false;
|
|
172
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
173
|
+
}
|
|
174
|
+
quiet() {
|
|
175
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
|
|
176
|
+
}
|
|
177
|
+
look() {
|
|
178
|
+
for (const conversation of this.conversations.values()) {
|
|
179
|
+
for (const scope of conversation.leftovers) {
|
|
180
|
+
if (!scope.empty())
|
|
181
|
+
continue;
|
|
182
|
+
scope.close();
|
|
183
|
+
conversation.leftovers.delete(scope);
|
|
184
|
+
}
|
|
143
185
|
}
|
|
144
|
-
|
|
186
|
+
this.kit.changed();
|
|
145
187
|
}
|
|
146
188
|
opened() {
|
|
147
189
|
this.watched = false;
|
|
@@ -196,10 +238,10 @@ export class Conversations {
|
|
|
196
238
|
if (conversation.running !== null)
|
|
197
239
|
return {};
|
|
198
240
|
try {
|
|
199
|
-
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null) };
|
|
241
|
+
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null, true) };
|
|
200
242
|
}
|
|
201
243
|
catch (error) {
|
|
202
|
-
return { refused:
|
|
244
|
+
return { refused: refusal(conversation, error) };
|
|
203
245
|
}
|
|
204
246
|
}
|
|
205
247
|
async message(conversation, input) {
|
|
@@ -216,7 +258,7 @@ export class Conversations {
|
|
|
216
258
|
throw new Error(KILLED);
|
|
217
259
|
}
|
|
218
260
|
catch (error) {
|
|
219
|
-
return { ack: Promise.resolve({ refused: conversation
|
|
261
|
+
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
220
262
|
}
|
|
221
263
|
return {
|
|
222
264
|
ack: new Promise((settle) => {
|
|
@@ -239,13 +281,17 @@ export class Conversations {
|
|
|
239
281
|
async send(conversation, waiting) {
|
|
240
282
|
const { input } = waiting;
|
|
241
283
|
let ack = {};
|
|
284
|
+
let started = false;
|
|
242
285
|
const prompt = [];
|
|
243
286
|
try {
|
|
244
287
|
if (conversation.running === null) {
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
288
|
+
const stored = conversation.session ?? input.provider_session_id;
|
|
289
|
+
const empty = input.first === true && conversation.prompted !== stored;
|
|
290
|
+
started = (await this.start(conversation, waiting.route, stored, empty)) !== stored;
|
|
291
|
+
}
|
|
292
|
+
const session = conversation.running.sessionId;
|
|
293
|
+
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
294
|
+
ack = { provider_session_id: session };
|
|
249
295
|
}
|
|
250
296
|
if (input.first === true) {
|
|
251
297
|
prompt.push({
|
|
@@ -257,11 +303,11 @@ export class Conversations {
|
|
|
257
303
|
throw new Error(KILLED);
|
|
258
304
|
}
|
|
259
305
|
catch (error) {
|
|
260
|
-
if (
|
|
306
|
+
if (started) {
|
|
261
307
|
conversation.session = null;
|
|
262
308
|
await this.kill(conversation);
|
|
263
309
|
}
|
|
264
|
-
waiting.settle({ refused: conversation
|
|
310
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
265
311
|
return;
|
|
266
312
|
}
|
|
267
313
|
this.prompt(conversation, [...prompt, ...waiting.content]);
|
|
@@ -295,11 +341,13 @@ export class Conversations {
|
|
|
295
341
|
}
|
|
296
342
|
halt(conversation) {
|
|
297
343
|
if (conversation.opening !== null)
|
|
298
|
-
|
|
344
|
+
killMarked(marker(conversation.opening));
|
|
345
|
+
for (const scope of conversation.leftovers)
|
|
346
|
+
scope.kill();
|
|
299
347
|
if (conversation.running === null)
|
|
300
348
|
return;
|
|
301
349
|
conversation.running.killed = true;
|
|
302
|
-
|
|
350
|
+
conversation.running.scope.kill();
|
|
303
351
|
}
|
|
304
352
|
async kill(conversation) {
|
|
305
353
|
const running = conversation.running;
|
|
@@ -337,46 +385,58 @@ export class Conversations {
|
|
|
337
385
|
}
|
|
338
386
|
return route;
|
|
339
387
|
}
|
|
340
|
-
async start(conversation, route, sessionId) {
|
|
388
|
+
async start(conversation, route, sessionId, empty) {
|
|
341
389
|
const reader = readerFor(route.kind);
|
|
342
390
|
reader.session = sessionId;
|
|
343
391
|
const app = client({ name: '@agentshouse/kit' })
|
|
344
392
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
345
|
-
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
393
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
394
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
346
395
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
347
|
-
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
396
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
348
397
|
if (conversation.kills > 0) {
|
|
349
398
|
bridge.close();
|
|
350
399
|
throw new Error(KILLED);
|
|
351
400
|
}
|
|
352
401
|
conversation.opening = bridge;
|
|
402
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
403
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
404
|
+
let adapter = null;
|
|
353
405
|
try {
|
|
354
406
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
355
|
-
await CLIS[route.kind].allowHouse();
|
|
356
|
-
|
|
357
|
-
|
|
407
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
408
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
409
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
410
|
+
work.read(message);
|
|
411
|
+
this.read(conversation, reader, message);
|
|
412
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
358
413
|
void adapter.exited.then(() => bridge.close());
|
|
359
414
|
const agent = adapter.connection.agent;
|
|
360
415
|
const cwd = route.working_directory;
|
|
361
|
-
const _meta = CLIS[route.kind].sessionMeta;
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
416
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
417
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
418
|
+
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
365
419
|
reader.session = opened.sessionId;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
420
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
421
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
422
|
+
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
423
|
+
}
|
|
424
|
+
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
425
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
426
|
+
void work.settled().then(() => scope.record());
|
|
369
427
|
conversation.running = running;
|
|
370
428
|
conversation.session = running.sessionId;
|
|
371
429
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: true });
|
|
372
430
|
if (conversation.commands !== null)
|
|
373
431
|
this.commands(conversation, conversation.commands);
|
|
374
432
|
this.options(conversation, options);
|
|
375
|
-
void adapter.
|
|
433
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
376
434
|
return running.sessionId;
|
|
377
435
|
}
|
|
378
436
|
catch (error) {
|
|
379
|
-
|
|
437
|
+
adapter?.retire();
|
|
438
|
+
work.close();
|
|
439
|
+
conversation.leftovers.add(scope);
|
|
380
440
|
bridge.close();
|
|
381
441
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
382
442
|
}
|
|
@@ -384,6 +444,18 @@ export class Conversations {
|
|
|
384
444
|
conversation.opening = null;
|
|
385
445
|
}
|
|
386
446
|
}
|
|
447
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
448
|
+
try {
|
|
449
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
453
|
+
throw new NotStarted(NO_RESUME, { cause: error });
|
|
454
|
+
}
|
|
455
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
387
459
|
async launchSettings(running, route, offered) {
|
|
388
460
|
let options = offered;
|
|
389
461
|
const settings = [
|
|
@@ -405,21 +477,19 @@ export class Conversations {
|
|
|
405
477
|
}
|
|
406
478
|
return options;
|
|
407
479
|
}
|
|
408
|
-
exited(conversation,
|
|
409
|
-
|
|
410
|
-
if (running?.adapter !== adapter)
|
|
411
|
-
return;
|
|
412
|
-
this.detach(conversation);
|
|
480
|
+
exited(conversation, running, cause) {
|
|
481
|
+
this.detach(conversation, running);
|
|
413
482
|
if (conversation.turn !== null) {
|
|
414
|
-
this.end(conversation, conversation.turn,
|
|
483
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
415
484
|
}
|
|
416
485
|
this.next(conversation);
|
|
417
486
|
this.kit.changed();
|
|
418
487
|
}
|
|
419
|
-
detach(conversation) {
|
|
488
|
+
detach(conversation, running) {
|
|
489
|
+
running.work.close();
|
|
490
|
+
conversation.leftovers.add(running.scope);
|
|
420
491
|
conversation.running = null;
|
|
421
492
|
conversation.commands = null;
|
|
422
|
-
conversation.busy.clear();
|
|
423
493
|
for (const [id, question] of this.questions) {
|
|
424
494
|
if (question.conversation === conversation)
|
|
425
495
|
this.questions.delete(id);
|
|
@@ -471,26 +541,28 @@ export class Conversations {
|
|
|
471
541
|
turn.parts.measure(conversation.context);
|
|
472
542
|
for (const part of conversation.held.splice(0))
|
|
473
543
|
turn.parts.start(part);
|
|
474
|
-
for (const event of conversation.running?.reader.turnStarted() ?? [])
|
|
475
|
-
this.event(conversation, event);
|
|
476
544
|
this.kit.changed();
|
|
477
545
|
return turn;
|
|
478
546
|
}
|
|
479
|
-
end(conversation, turn, failed) {
|
|
547
|
+
end(conversation, turn, failed, notice = null) {
|
|
480
548
|
if (turn.ended)
|
|
481
549
|
return;
|
|
550
|
+
if (failed !== null)
|
|
551
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
482
552
|
if (conversation.turn === turn) {
|
|
483
553
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
484
554
|
this.apply(conversation, turn, event);
|
|
485
555
|
}
|
|
556
|
+
if (notice !== null)
|
|
557
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
486
558
|
turn.ended = true;
|
|
487
559
|
conversation.open--;
|
|
488
560
|
if (conversation.turn === turn)
|
|
489
561
|
conversation.turn = null;
|
|
490
562
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
491
|
-
parts: turn.parts.
|
|
563
|
+
parts: turn.parts.saved(),
|
|
492
564
|
context: turn.parts.context,
|
|
493
|
-
...failed,
|
|
565
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
494
566
|
}));
|
|
495
567
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
496
568
|
this.next(conversation);
|
|
@@ -500,16 +572,25 @@ export class Conversations {
|
|
|
500
572
|
const running = conversation.running;
|
|
501
573
|
const turn = this.begin(conversation);
|
|
502
574
|
turn.prompted = true;
|
|
503
|
-
|
|
575
|
+
conversation.prompted = running.sessionId;
|
|
576
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
577
|
+
const notice = answeredFailure(answer);
|
|
578
|
+
if (notice !== null) {
|
|
579
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
583
|
+
}
|
|
584
|
+
}, async (error) => {
|
|
504
585
|
if (running.adapter.connection.signal.aborted) {
|
|
505
|
-
this.end(conversation, turn,
|
|
586
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
506
587
|
}
|
|
507
588
|
else {
|
|
508
589
|
if (conversation.open === 1 && conversation.running === running) {
|
|
509
|
-
|
|
510
|
-
this.detach(conversation);
|
|
590
|
+
running.adapter.retire();
|
|
591
|
+
this.detach(conversation, running);
|
|
511
592
|
}
|
|
512
|
-
this.end(conversation, turn,
|
|
593
|
+
this.end(conversation, turn, answeredWith(error));
|
|
513
594
|
}
|
|
514
595
|
});
|
|
515
596
|
}
|
|
@@ -592,20 +673,6 @@ export class Conversations {
|
|
|
592
673
|
case 'options':
|
|
593
674
|
this.options(conversation, event.options);
|
|
594
675
|
return;
|
|
595
|
-
case 'busy':
|
|
596
|
-
if (event.running)
|
|
597
|
-
conversation.busy.add(event.key);
|
|
598
|
-
else
|
|
599
|
-
conversation.busy.delete(event.key);
|
|
600
|
-
this.kit.changed();
|
|
601
|
-
return;
|
|
602
|
-
case 'servers': {
|
|
603
|
-
const running = conversation.running;
|
|
604
|
-
if (running?.baseline)
|
|
605
|
-
for (const found of processes(marker(running.bridge)))
|
|
606
|
-
running.baseline.add(found);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
676
|
case 'context':
|
|
610
677
|
conversation.context = { used: event.used, window: event.window };
|
|
611
678
|
conversation.turn?.parts.measure(conversation.context);
|
|
@@ -647,7 +714,7 @@ export class Conversations {
|
|
|
647
714
|
turn.parts.plan(event.entries);
|
|
648
715
|
return;
|
|
649
716
|
case 'image':
|
|
650
|
-
turn.uploads.push(this.image(conversation, turn, event.data, event.mediaType));
|
|
717
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
651
718
|
this.kit.changed();
|
|
652
719
|
return;
|
|
653
720
|
}
|
|
@@ -667,7 +734,7 @@ export class Conversations {
|
|
|
667
734
|
else
|
|
668
735
|
conversation.turn.parts.start(part);
|
|
669
736
|
}
|
|
670
|
-
async image(conversation, turn, data, mediaType) {
|
|
737
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
671
738
|
const bytes = Buffer.from(data, 'base64');
|
|
672
739
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
673
740
|
try {
|
|
@@ -680,10 +747,10 @@ export class Conversations {
|
|
|
680
747
|
const saved = declared.upload === undefined ? declared : await uploadBytes(declared.upload, bytes);
|
|
681
748
|
if (saved.save.status !== 'saved')
|
|
682
749
|
throw new Error(saved.save.failure ?? saved.save.status);
|
|
683
|
-
turn.parts.
|
|
750
|
+
turn.parts.arrive({ type: 'image', file: saved.version, name, media_type: mediaType }, reserved);
|
|
684
751
|
}
|
|
685
752
|
catch (error) {
|
|
686
|
-
turn.parts.
|
|
753
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${causeOf(error)}` }, reserved);
|
|
687
754
|
}
|
|
688
755
|
}
|
|
689
756
|
}
|