@agentshouse/kit 0.2.1-cd9cbd8b6428c → 0.2.1-cfe564e35b4a5
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 +19 -19
- package/dist/acp.js +16 -93
- package/dist/agents.js +81 -39
- package/dist/api.js +30 -2
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +35 -52
- package/dist/clis.js +95 -65
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +284 -236
- package/dist/copies.js +146 -129
- package/dist/end.js +8 -0
- package/dist/events.js +521 -0
- package/dist/files.js +14 -0
- package/dist/house-main.js +41 -12
- package/dist/instructions.js +4 -3
- package/dist/kit-main.js +3 -0
- package/dist/login.js +23 -98
- package/dist/logout.js +16 -13
- package/dist/parts.js +64 -0
- package/dist/refusals.js +12 -0
- package/dist/resident.js +24 -22
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +4 -1
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +34 -9
- package/dist/skills.js +1 -1
- package/dist/stream.js +29 -4
- package/dist/transcription.js +54 -6
- package/dist/update.js +2 -0
- package/dist/work.js +348 -0
- package/package.json +2 -2
- package/dist/blocks.js +0 -51
package/dist/conversations.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { client, } from '@agentclientprotocol/sdk';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
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 {
|
|
7
|
-
import { blocksOf, firstChange } from './blocks.js';
|
|
6
|
+
import { startAdapter } from './acp.js';
|
|
8
7
|
import { openBridge } from './bridge.js';
|
|
9
8
|
import { CLIS } from './clis.js';
|
|
10
|
-
import {
|
|
9
|
+
import { readerFor } from './events.js';
|
|
10
|
+
import { placeFiles, uploadBytes } from './files.js';
|
|
11
11
|
import { agentBase } from './home.js';
|
|
12
12
|
import { instructions } from './instructions.js';
|
|
13
|
+
import { Parts } from './parts.js';
|
|
14
|
+
import { killMarked, Scope } from './scope.js';
|
|
13
15
|
import { holdSecretInput } from './secret-input.js';
|
|
14
16
|
import { createHowWeWork } from './skills.js';
|
|
15
17
|
import { transcribe } from './transcription.js';
|
|
18
|
+
import { workFor } from './work.js';
|
|
16
19
|
const KILLED = 'the conversation was killed';
|
|
17
20
|
function causeOf(error) {
|
|
18
21
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -23,33 +26,35 @@ function answerMessage(request, response) {
|
|
|
23
26
|
function logged(error) {
|
|
24
27
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
25
28
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
29
|
+
function marker(bridge) {
|
|
30
|
+
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
28
31
|
}
|
|
29
32
|
class Turn {
|
|
30
33
|
id = randomUUID();
|
|
31
|
-
|
|
32
|
-
segment = null;
|
|
34
|
+
parts;
|
|
33
35
|
admitted = false;
|
|
34
|
-
notes = [];
|
|
35
|
-
sent = [];
|
|
36
|
-
unsentFrom = null;
|
|
37
|
-
draftSequence = 0;
|
|
38
|
-
planSequence = 0;
|
|
39
|
-
entries = null;
|
|
40
36
|
questions = 0;
|
|
41
37
|
secrets = 0;
|
|
42
|
-
|
|
38
|
+
prompted = false;
|
|
43
39
|
ended = false;
|
|
40
|
+
images = 0;
|
|
41
|
+
uploads = [];
|
|
42
|
+
constructor(emit) {
|
|
43
|
+
this.parts = new Parts(emit);
|
|
44
|
+
}
|
|
44
45
|
}
|
|
45
46
|
class Conversation {
|
|
46
47
|
id;
|
|
47
48
|
running = null;
|
|
49
|
+
session = null;
|
|
48
50
|
turn = null;
|
|
49
51
|
commands = null;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
context = null;
|
|
53
|
+
leftovers = new Set();
|
|
54
|
+
waiting = [];
|
|
55
|
+
sending = null;
|
|
56
|
+
subagents = new Map();
|
|
57
|
+
held = [];
|
|
53
58
|
opening = null;
|
|
54
59
|
kills = 0;
|
|
55
60
|
killed = new AbortController();
|
|
@@ -77,8 +82,11 @@ export class Conversations {
|
|
|
77
82
|
questions = new Map();
|
|
78
83
|
carrying = 0;
|
|
79
84
|
reporting = 0;
|
|
85
|
+
watched = false;
|
|
80
86
|
constructor(kit) {
|
|
81
87
|
this.kit = kit;
|
|
88
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
89
|
+
setInterval(() => this.look(), 2_000);
|
|
82
90
|
}
|
|
83
91
|
input(input) {
|
|
84
92
|
this.carrying++;
|
|
@@ -88,14 +96,17 @@ export class Conversations {
|
|
|
88
96
|
if (input.kind === 'kill') {
|
|
89
97
|
conversation.kills++;
|
|
90
98
|
conversation.killed.abort(new Error(KILLED));
|
|
99
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
100
|
+
waiting.settle({ refused: KILLED });
|
|
91
101
|
this.halt(conversation);
|
|
92
102
|
}
|
|
93
103
|
if (input.kind === 'interrupt') {
|
|
94
104
|
conversation.interrupts++;
|
|
95
105
|
conversation.interrupted.abort();
|
|
96
106
|
}
|
|
97
|
-
const
|
|
98
|
-
conversation.queue =
|
|
107
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
108
|
+
conversation.queue = handled.catch(() => undefined);
|
|
109
|
+
const work = handled.then(({ ack }) => ack);
|
|
99
110
|
this.acks.set(input.input_id, work);
|
|
100
111
|
return work;
|
|
101
112
|
})();
|
|
@@ -113,15 +124,36 @@ export class Conversations {
|
|
|
113
124
|
for (const conversation of this.conversations.values()) {
|
|
114
125
|
const turn = conversation.turn;
|
|
115
126
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
116
|
-
if (conversation.
|
|
127
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
117
128
|
return false;
|
|
118
129
|
}
|
|
119
|
-
|
|
130
|
+
const conversations = [...this.conversations.values()];
|
|
131
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
132
|
+
return false;
|
|
133
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
134
|
+
}
|
|
135
|
+
look() {
|
|
136
|
+
for (const conversation of this.conversations.values()) {
|
|
137
|
+
for (const scope of conversation.leftovers) {
|
|
138
|
+
if (!scope.empty())
|
|
139
|
+
continue;
|
|
140
|
+
scope.close();
|
|
141
|
+
conversation.leftovers.delete(scope);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.kit.changed();
|
|
120
145
|
}
|
|
121
146
|
opened() {
|
|
147
|
+
this.watched = false;
|
|
148
|
+
}
|
|
149
|
+
watching(watched) {
|
|
150
|
+
const resend = watched && !this.watched;
|
|
151
|
+
this.watched = watched;
|
|
152
|
+
if (!resend)
|
|
153
|
+
return;
|
|
122
154
|
for (const conversation of this.conversations.values()) {
|
|
123
155
|
if (conversation.turn !== null)
|
|
124
|
-
conversation.turn
|
|
156
|
+
this.replay(conversation, conversation.turn);
|
|
125
157
|
}
|
|
126
158
|
}
|
|
127
159
|
conversation(id) {
|
|
@@ -134,7 +166,7 @@ export class Conversations {
|
|
|
134
166
|
}
|
|
135
167
|
async carry(conversation, input) {
|
|
136
168
|
if (input.kind === 'open')
|
|
137
|
-
return this.open(conversation, input);
|
|
169
|
+
return { ack: Promise.resolve(await this.open(conversation, input)) };
|
|
138
170
|
if (input.kind === 'message')
|
|
139
171
|
return this.message(conversation, input);
|
|
140
172
|
if (input.kind === 'interrupt') {
|
|
@@ -144,6 +176,7 @@ export class Conversations {
|
|
|
144
176
|
}
|
|
145
177
|
if (input.kind === 'kill') {
|
|
146
178
|
await this.kill(conversation);
|
|
179
|
+
await conversation.sending;
|
|
147
180
|
if (--conversation.kills === 0)
|
|
148
181
|
conversation.killed = new AbortController();
|
|
149
182
|
}
|
|
@@ -152,11 +185,12 @@ export class Conversations {
|
|
|
152
185
|
if (input.kind === 'answer') {
|
|
153
186
|
const question = this.questions.get(String(input.interaction_id));
|
|
154
187
|
if (question === undefined) {
|
|
155
|
-
|
|
188
|
+
const text = answerMessage(input.request, input.response);
|
|
189
|
+
return this.message(conversation, { ...input, text, files: [], first: false });
|
|
156
190
|
}
|
|
157
191
|
question.answer(input.response);
|
|
158
192
|
}
|
|
159
|
-
return {};
|
|
193
|
+
return { ack: Promise.resolve({}) };
|
|
160
194
|
}
|
|
161
195
|
async open(conversation, input) {
|
|
162
196
|
if (conversation.running !== null)
|
|
@@ -169,37 +203,69 @@ export class Conversations {
|
|
|
169
203
|
}
|
|
170
204
|
}
|
|
171
205
|
async message(conversation, input) {
|
|
172
|
-
|
|
173
|
-
|
|
206
|
+
const content = [{ type: 'text', text: String(input.text) }];
|
|
207
|
+
let route;
|
|
174
208
|
try {
|
|
175
|
-
|
|
209
|
+
route = await this.route(conversation, input.agent_id);
|
|
176
210
|
const files = input.files;
|
|
177
211
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
178
212
|
const lines = await this.transcribed(conversation, files, paths);
|
|
213
|
+
if (lines.length > 0)
|
|
214
|
+
content.push({ type: 'text', text: lines.join('\n') });
|
|
215
|
+
if (conversation.kills > 0)
|
|
216
|
+
throw new Error(KILLED);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
return { ack: Promise.resolve({ refused: conversation.kills > 0 ? KILLED : causeOf(error) }) };
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
ack: new Promise((settle) => {
|
|
223
|
+
conversation.waiting.push({ input, route, content, settle });
|
|
224
|
+
this.next(conversation);
|
|
225
|
+
}),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
next(conversation) {
|
|
229
|
+
if (conversation.sending !== null || conversation.open > 0)
|
|
230
|
+
return;
|
|
231
|
+
const waiting = conversation.waiting.shift();
|
|
232
|
+
if (waiting === undefined)
|
|
233
|
+
return;
|
|
234
|
+
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
235
|
+
conversation.sending = null;
|
|
236
|
+
this.next(conversation);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async send(conversation, waiting) {
|
|
240
|
+
const { input } = waiting;
|
|
241
|
+
let ack = {};
|
|
242
|
+
const prompt = [];
|
|
243
|
+
try {
|
|
179
244
|
if (conversation.running === null) {
|
|
180
|
-
const
|
|
181
|
-
|
|
245
|
+
const resumed = input.provider_session_id ?? conversation.session;
|
|
246
|
+
const opened = await this.start(conversation, waiting.route, resumed);
|
|
247
|
+
if (resumed === null)
|
|
182
248
|
ack = { provider_session_id: opened };
|
|
183
249
|
}
|
|
184
250
|
if (input.first === true) {
|
|
185
251
|
prompt.push({
|
|
186
252
|
type: 'text',
|
|
187
|
-
text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
|
|
253
|
+
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
188
254
|
});
|
|
189
255
|
}
|
|
190
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
191
|
-
if (lines.length > 0)
|
|
192
|
-
prompt.push({ type: 'text', text: lines.join('\n') });
|
|
193
256
|
if (conversation.kills > 0)
|
|
194
257
|
throw new Error(KILLED);
|
|
195
258
|
}
|
|
196
259
|
catch (error) {
|
|
197
|
-
if ('provider_session_id' in ack)
|
|
260
|
+
if ('provider_session_id' in ack) {
|
|
261
|
+
conversation.session = null;
|
|
198
262
|
await this.kill(conversation);
|
|
199
|
-
|
|
263
|
+
}
|
|
264
|
+
waiting.settle({ refused: conversation.kills > 0 ? KILLED : causeOf(error) });
|
|
265
|
+
return;
|
|
200
266
|
}
|
|
201
|
-
this.prompt(conversation, prompt);
|
|
202
|
-
|
|
267
|
+
this.prompt(conversation, [...prompt, ...waiting.content]);
|
|
268
|
+
waiting.settle(ack);
|
|
203
269
|
}
|
|
204
270
|
async transcribed(conversation, files, paths) {
|
|
205
271
|
const lines = [];
|
|
@@ -209,7 +275,7 @@ export class Conversations {
|
|
|
209
275
|
continue;
|
|
210
276
|
try {
|
|
211
277
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
212
|
-
if (text !== '')
|
|
278
|
+
if (text !== null && text !== '')
|
|
213
279
|
lines.push(`Transcript: ${text}`);
|
|
214
280
|
}
|
|
215
281
|
catch (error) {
|
|
@@ -229,11 +295,13 @@ export class Conversations {
|
|
|
229
295
|
}
|
|
230
296
|
halt(conversation) {
|
|
231
297
|
if (conversation.opening !== null)
|
|
232
|
-
|
|
298
|
+
killMarked(marker(conversation.opening));
|
|
299
|
+
for (const scope of conversation.leftovers)
|
|
300
|
+
scope.kill();
|
|
233
301
|
if (conversation.running === null)
|
|
234
302
|
return;
|
|
235
303
|
conversation.running.killed = true;
|
|
236
|
-
|
|
304
|
+
conversation.running.scope.kill();
|
|
237
305
|
}
|
|
238
306
|
async kill(conversation) {
|
|
239
307
|
const running = conversation.running;
|
|
@@ -245,6 +313,7 @@ export class Conversations {
|
|
|
245
313
|
if (running === null)
|
|
246
314
|
return;
|
|
247
315
|
try {
|
|
316
|
+
running.reader.requested(option, value);
|
|
248
317
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
249
318
|
sessionId: running.sessionId,
|
|
250
319
|
configId: option,
|
|
@@ -253,6 +322,7 @@ export class Conversations {
|
|
|
253
322
|
this.options(conversation, answered.configOptions);
|
|
254
323
|
}
|
|
255
324
|
catch (error) {
|
|
325
|
+
running.reader.refused(option);
|
|
256
326
|
logged(error);
|
|
257
327
|
}
|
|
258
328
|
}
|
|
@@ -270,10 +340,9 @@ export class Conversations {
|
|
|
270
340
|
return route;
|
|
271
341
|
}
|
|
272
342
|
async start(conversation, route, sessionId) {
|
|
343
|
+
const reader = readerFor(route.kind);
|
|
344
|
+
reader.session = sessionId;
|
|
273
345
|
const app = client({ name: '@agentshouse/kit' })
|
|
274
|
-
.onNotification('session/update', ({ params }) => this.update(conversation, params))
|
|
275
|
-
.onNotification(TURN_STARTED, (params) => params, () => void this.started(conversation))
|
|
276
|
-
.onNotification(TURN_ENDED, (params) => params, () => this.ended(conversation))
|
|
277
346
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
278
347
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal));
|
|
279
348
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
@@ -283,25 +352,32 @@ export class Conversations {
|
|
|
283
352
|
throw new Error(KILLED);
|
|
284
353
|
}
|
|
285
354
|
conversation.opening = bridge;
|
|
355
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
356
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
286
357
|
try {
|
|
287
358
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
288
|
-
|
|
359
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
360
|
+
const adapter = await startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
361
|
+
work.read(message);
|
|
362
|
+
this.read(conversation, reader, message);
|
|
363
|
+
}, { ...bridge.env, ...(await work.open(cli)) }, undefined, scope);
|
|
289
364
|
void adapter.exited.then(() => bridge.close());
|
|
290
365
|
const agent = adapter.connection.agent;
|
|
291
366
|
const cwd = route.working_directory;
|
|
367
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
292
368
|
const opened = sessionId === null
|
|
293
|
-
? await agent.request('session/new', { cwd, mcpServers: [] })
|
|
294
|
-
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [] })), sessionId };
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
sessionId: opened.sessionId,
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
phase: CLIS[route.kind].phase,
|
|
302
|
-
};
|
|
369
|
+
? await agent.request('session/new', { cwd, mcpServers: [], _meta })
|
|
370
|
+
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [], _meta })), sessionId };
|
|
371
|
+
reader.session = opened.sessionId;
|
|
372
|
+
if (sessionId === null && CLIS[route.kind].renames) {
|
|
373
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
374
|
+
await agent.request('session/prompt', { sessionId: opened.sessionId, prompt });
|
|
375
|
+
}
|
|
376
|
+
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
303
377
|
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
378
|
+
void work.settled().then(() => scope.record());
|
|
304
379
|
conversation.running = running;
|
|
380
|
+
conversation.session = running.sessionId;
|
|
305
381
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: true });
|
|
306
382
|
if (conversation.commands !== null)
|
|
307
383
|
this.commands(conversation, conversation.commands);
|
|
@@ -310,7 +386,9 @@ export class Conversations {
|
|
|
310
386
|
return running.sessionId;
|
|
311
387
|
}
|
|
312
388
|
catch (error) {
|
|
313
|
-
|
|
389
|
+
scope.kill();
|
|
390
|
+
work.close();
|
|
391
|
+
conversation.leftovers.add(scope);
|
|
314
392
|
bridge.close();
|
|
315
393
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
316
394
|
}
|
|
@@ -321,6 +399,7 @@ export class Conversations {
|
|
|
321
399
|
async launchSettings(running, route, offered) {
|
|
322
400
|
let options = offered;
|
|
323
401
|
const settings = [
|
|
402
|
+
['mode', route.mode ?? CLIS[route.kind].fullAccess],
|
|
324
403
|
['model', route.model],
|
|
325
404
|
['thought_level', route.effort],
|
|
326
405
|
];
|
|
@@ -328,6 +407,7 @@ export class Conversations {
|
|
|
328
407
|
if (value === null)
|
|
329
408
|
continue;
|
|
330
409
|
const option = options.find((candidate) => candidate.category === category);
|
|
410
|
+
running.reader.requested(option?.id ?? category, value);
|
|
331
411
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
332
412
|
sessionId: running.sessionId,
|
|
333
413
|
configId: option?.id ?? category,
|
|
@@ -341,19 +421,23 @@ export class Conversations {
|
|
|
341
421
|
const running = conversation.running;
|
|
342
422
|
if (running?.adapter !== adapter)
|
|
343
423
|
return;
|
|
424
|
+
this.detach(conversation, running);
|
|
425
|
+
if (conversation.turn !== null) {
|
|
426
|
+
this.end(conversation, conversation.turn, { failed: running.killed ? KILLED : cause });
|
|
427
|
+
}
|
|
428
|
+
this.next(conversation);
|
|
429
|
+
this.kit.changed();
|
|
430
|
+
}
|
|
431
|
+
detach(conversation, running) {
|
|
432
|
+
running.work.close();
|
|
433
|
+
conversation.leftovers.add(running.scope);
|
|
344
434
|
conversation.running = null;
|
|
345
435
|
conversation.commands = null;
|
|
346
|
-
conversation.jobs.clear();
|
|
347
|
-
conversation.queued.length = 0;
|
|
348
436
|
for (const [id, question] of this.questions) {
|
|
349
437
|
if (question.conversation === conversation)
|
|
350
438
|
this.questions.delete(id);
|
|
351
439
|
}
|
|
352
440
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: false });
|
|
353
|
-
if (conversation.turn !== null) {
|
|
354
|
-
this.end(conversation, conversation.turn, { failed: running.killed ? KILLED : cause });
|
|
355
|
-
}
|
|
356
|
-
this.kit.changed();
|
|
357
441
|
}
|
|
358
442
|
commands(conversation, commands) {
|
|
359
443
|
conversation.commands = commands;
|
|
@@ -368,7 +452,7 @@ export class Conversations {
|
|
|
368
452
|
}
|
|
369
453
|
deliver(conversation, turn, event, body) {
|
|
370
454
|
this.reporting++;
|
|
371
|
-
const delivered = conversation.reports.then(() => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, body));
|
|
455
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
372
456
|
conversation.reports = delivered
|
|
373
457
|
.catch(() => undefined)
|
|
374
458
|
.finally(() => {
|
|
@@ -377,119 +461,82 @@ export class Conversations {
|
|
|
377
461
|
});
|
|
378
462
|
return delivered;
|
|
379
463
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
.then(() => this.admitted(conversation, turn))
|
|
385
|
-
.catch(logged);
|
|
386
|
-
return turn;
|
|
464
|
+
write(conversation, turn, operation) {
|
|
465
|
+
if (!turn.admitted || turn.ended || !this.watched)
|
|
466
|
+
return;
|
|
467
|
+
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
387
468
|
}
|
|
388
|
-
|
|
389
|
-
turn.
|
|
390
|
-
|
|
391
|
-
this.kit.send(note);
|
|
392
|
-
if (turn.text !== '') {
|
|
393
|
-
turn.unsentFrom = 0;
|
|
394
|
-
this.draft(conversation, turn);
|
|
395
|
-
}
|
|
396
|
-
if (turn.entries !== null)
|
|
397
|
-
this.plan(conversation, turn);
|
|
469
|
+
replay(conversation, turn) {
|
|
470
|
+
for (const operation of turn.parts.operations())
|
|
471
|
+
this.write(conversation, turn, operation);
|
|
398
472
|
}
|
|
399
473
|
begin(conversation) {
|
|
400
|
-
const turn = this.
|
|
474
|
+
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
475
|
+
conversation.open++;
|
|
476
|
+
void this.deliver(conversation, turn, 'started', {})
|
|
477
|
+
.then(() => {
|
|
478
|
+
turn.admitted = true;
|
|
479
|
+
this.replay(conversation, turn);
|
|
480
|
+
})
|
|
481
|
+
.catch(logged);
|
|
401
482
|
conversation.turn = turn;
|
|
483
|
+
if (conversation.context !== null)
|
|
484
|
+
turn.parts.measure(conversation.context);
|
|
485
|
+
for (const part of conversation.held.splice(0))
|
|
486
|
+
turn.parts.start(part);
|
|
402
487
|
this.kit.changed();
|
|
403
488
|
return turn;
|
|
404
489
|
}
|
|
405
|
-
|
|
406
|
-
if (turn.ended)
|
|
407
|
-
return;
|
|
408
|
-
const segment = turn.segment;
|
|
409
|
-
turn.segment = null;
|
|
410
|
-
if (segment?.phase === 'note')
|
|
411
|
-
this.note(conversation, turn, segment.text);
|
|
412
|
-
else if (segment !== null)
|
|
413
|
-
this.answer(conversation, turn, segment.text);
|
|
414
|
-
this.end(conversation, turn, { text: turn.text });
|
|
415
|
-
}
|
|
416
|
-
end(conversation, turn, outcome) {
|
|
490
|
+
end(conversation, turn, failed) {
|
|
417
491
|
if (turn.ended)
|
|
418
492
|
return;
|
|
493
|
+
if (conversation.turn === turn) {
|
|
494
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
495
|
+
this.apply(conversation, turn, event);
|
|
496
|
+
}
|
|
419
497
|
turn.ended = true;
|
|
420
498
|
conversation.open--;
|
|
421
499
|
if (conversation.turn === turn)
|
|
422
500
|
conversation.turn = null;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
501
|
+
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
502
|
+
parts: turn.parts.parts,
|
|
503
|
+
context: turn.parts.context,
|
|
504
|
+
...failed,
|
|
505
|
+
}));
|
|
506
|
+
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
507
|
+
this.next(conversation);
|
|
427
508
|
this.kit.changed();
|
|
428
509
|
}
|
|
429
|
-
failed(conversation, cause) {
|
|
430
|
-
if (conversation.open > 0)
|
|
431
|
-
conversation.late.push(cause);
|
|
432
|
-
else
|
|
433
|
-
this.end(conversation, this.report(conversation), { failed: cause });
|
|
434
|
-
}
|
|
435
510
|
prompt(conversation, prompt) {
|
|
436
511
|
const running = conversation.running;
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
this.end(conversation,
|
|
512
|
+
const turn = this.begin(conversation);
|
|
513
|
+
turn.prompted = true;
|
|
514
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then(() => this.end(conversation, turn, null), async (error) => {
|
|
515
|
+
if (running.adapter.connection.signal.aborted) {
|
|
516
|
+
this.end(conversation, turn, { failed: running.killed ? KILLED : await running.adapter.exited });
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
if (conversation.open === 1 && conversation.running === running) {
|
|
520
|
+
running.scope.kill();
|
|
521
|
+
this.detach(conversation, running);
|
|
522
|
+
}
|
|
523
|
+
this.end(conversation, turn, { failed: causeOf(error) });
|
|
449
524
|
}
|
|
450
525
|
});
|
|
451
526
|
}
|
|
452
|
-
attach(turn, sent) {
|
|
453
|
-
turn.prompt = sent;
|
|
454
|
-
sent.turn = turn;
|
|
455
|
-
}
|
|
456
|
-
answered(conversation, sent, failed) {
|
|
457
|
-
const turn = sent.turn;
|
|
458
|
-
if (turn === null) {
|
|
459
|
-
const queued = conversation.queued.indexOf(sent);
|
|
460
|
-
if (queued < 0)
|
|
461
|
-
return;
|
|
462
|
-
conversation.queued.splice(queued, 1);
|
|
463
|
-
if (failed !== null)
|
|
464
|
-
this.failed(conversation, failed);
|
|
465
|
-
}
|
|
466
|
-
else if (failed !== null) {
|
|
467
|
-
if (turn.ended)
|
|
468
|
-
this.failed(conversation, failed);
|
|
469
|
-
else
|
|
470
|
-
this.end(conversation, turn, { failed });
|
|
471
|
-
}
|
|
472
|
-
else if (turn.prompt === sent) {
|
|
473
|
-
this.finish(conversation, turn);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
527
|
started(conversation) {
|
|
477
|
-
|
|
478
|
-
return conversation.turn;
|
|
479
|
-
const turn = this.begin(conversation);
|
|
480
|
-
const queued = conversation.queued.shift();
|
|
481
|
-
if (queued !== undefined)
|
|
482
|
-
this.attach(turn, queued);
|
|
483
|
-
return turn;
|
|
528
|
+
return conversation.turn ?? this.begin(conversation);
|
|
484
529
|
}
|
|
485
530
|
ended(conversation) {
|
|
486
531
|
const turn = conversation.turn;
|
|
487
532
|
if (turn === null)
|
|
488
533
|
return;
|
|
489
|
-
if (turn.
|
|
490
|
-
this.
|
|
534
|
+
if (!turn.prompted) {
|
|
535
|
+
this.end(conversation, turn, null);
|
|
491
536
|
}
|
|
492
537
|
else {
|
|
538
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
539
|
+
this.apply(conversation, turn, event);
|
|
493
540
|
conversation.turn = null;
|
|
494
541
|
this.kit.changed();
|
|
495
542
|
}
|
|
@@ -538,101 +585,102 @@ export class Conversations {
|
|
|
538
585
|
if (content !== null)
|
|
539
586
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
540
587
|
}
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
this.chunk(conversation, this.started(conversation), update, update.content.text);
|
|
545
|
-
}
|
|
546
|
-
else if (update.sessionUpdate === 'plan') {
|
|
547
|
-
const turn = this.started(conversation);
|
|
548
|
-
this.close(conversation, turn);
|
|
549
|
-
turn.entries = update.entries;
|
|
550
|
-
this.plan(conversation, turn);
|
|
551
|
-
}
|
|
552
|
-
else if (update.sessionUpdate === 'tool_call') {
|
|
553
|
-
this.close(conversation, this.started(conversation));
|
|
554
|
-
}
|
|
555
|
-
else if (update.sessionUpdate === 'agent_thought_chunk') {
|
|
556
|
-
const turn = this.started(conversation);
|
|
557
|
-
if (turn.segment?.messageId !== (update.messageId ?? null))
|
|
558
|
-
this.close(conversation, turn);
|
|
559
|
-
}
|
|
560
|
-
else if (update.sessionUpdate === 'available_commands_update') {
|
|
561
|
-
this.commands(conversation, update.availableCommands);
|
|
562
|
-
}
|
|
563
|
-
else if (update.sessionUpdate === 'config_option_update') {
|
|
564
|
-
this.options(conversation, update.configOptions);
|
|
565
|
-
}
|
|
588
|
+
read(conversation, reader, message) {
|
|
589
|
+
for (const event of reader.read(message))
|
|
590
|
+
this.event(conversation, event);
|
|
566
591
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
592
|
+
event(conversation, event) {
|
|
593
|
+
switch (event.kind) {
|
|
594
|
+
case 'started':
|
|
595
|
+
this.started(conversation);
|
|
596
|
+
return;
|
|
597
|
+
case 'ended':
|
|
598
|
+
this.ended(conversation);
|
|
599
|
+
return;
|
|
600
|
+
case 'commands':
|
|
601
|
+
this.commands(conversation, event.commands);
|
|
602
|
+
return;
|
|
603
|
+
case 'options':
|
|
604
|
+
this.options(conversation, event.options);
|
|
605
|
+
return;
|
|
606
|
+
case 'context':
|
|
607
|
+
conversation.context = { used: event.used, window: event.window };
|
|
608
|
+
conversation.turn?.parts.measure(conversation.context);
|
|
609
|
+
return;
|
|
610
|
+
case 'subagent-done':
|
|
611
|
+
this.subagentDone(conversation, event.id, event.result);
|
|
612
|
+
return;
|
|
613
|
+
case 'marker':
|
|
614
|
+
case 'image':
|
|
615
|
+
if (conversation.turn !== null)
|
|
616
|
+
break;
|
|
617
|
+
if (event.kind === 'marker' && event.marker === 'job')
|
|
618
|
+
conversation.held.push({ type: 'marker', marker: 'job', text: event.text });
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
this.apply(conversation, this.started(conversation), event);
|
|
573
622
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
623
|
+
apply(conversation, turn, event) {
|
|
624
|
+
switch (event.kind) {
|
|
625
|
+
case 'text':
|
|
626
|
+
case 'reasoning':
|
|
627
|
+
turn.parts.write(event.kind, event.id, event.text);
|
|
628
|
+
return;
|
|
629
|
+
case 'command':
|
|
630
|
+
turn.parts.command();
|
|
631
|
+
return;
|
|
632
|
+
case 'subagent': {
|
|
633
|
+
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
634
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
case 'message':
|
|
638
|
+
turn.parts.start({ type: 'message', command: event.command, target: event.target });
|
|
639
|
+
return;
|
|
640
|
+
case 'marker':
|
|
641
|
+
turn.parts.start({ type: 'marker', marker: event.marker, text: event.text });
|
|
642
|
+
return;
|
|
643
|
+
case 'plan':
|
|
644
|
+
turn.parts.plan(event.entries);
|
|
645
|
+
return;
|
|
646
|
+
case 'image':
|
|
647
|
+
turn.uploads.push(this.image(conversation, turn, event.data, event.mediaType));
|
|
648
|
+
this.kit.changed();
|
|
649
|
+
return;
|
|
582
650
|
}
|
|
583
|
-
turn.segment ??= { messageId, phase, text: '' };
|
|
584
|
-
turn.segment.text += text;
|
|
585
651
|
}
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
652
|
+
subagentDone(conversation, id, result) {
|
|
653
|
+
const subagent = conversation.subagents.get(id);
|
|
654
|
+
if (subagent === undefined)
|
|
589
655
|
return;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
note(conversation, turn, text) {
|
|
594
|
-
if (text.trim() === '')
|
|
656
|
+
conversation.subagents.delete(id);
|
|
657
|
+
if (subagent.turn === conversation.turn) {
|
|
658
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
595
659
|
return;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
660
|
+
}
|
|
661
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
662
|
+
if (conversation.turn === null)
|
|
663
|
+
conversation.held.push(part);
|
|
599
664
|
else
|
|
600
|
-
turn.
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
});
|
|
617
|
-
if (sent) {
|
|
618
|
-
turn.draftSequence++;
|
|
619
|
-
turn.sent = blocks;
|
|
620
|
-
turn.unsentFrom = null;
|
|
665
|
+
conversation.turn.parts.start(part);
|
|
666
|
+
}
|
|
667
|
+
async image(conversation, turn, data, mediaType) {
|
|
668
|
+
const bytes = Buffer.from(data, 'base64');
|
|
669
|
+
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
670
|
+
try {
|
|
671
|
+
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
672
|
+
name,
|
|
673
|
+
media_type: mediaType,
|
|
674
|
+
bytes: bytes.length,
|
|
675
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
676
|
+
}));
|
|
677
|
+
const saved = declared.upload === undefined ? declared : await uploadBytes(declared.upload, bytes);
|
|
678
|
+
if (saved.save.status !== 'saved')
|
|
679
|
+
throw new Error(saved.save.failure ?? saved.save.status);
|
|
680
|
+
turn.parts.start({ type: 'image', file: saved.version, name, media_type: mediaType });
|
|
621
681
|
}
|
|
622
|
-
|
|
623
|
-
turn.
|
|
682
|
+
catch (error) {
|
|
683
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${causeOf(error)}` });
|
|
624
684
|
}
|
|
625
685
|
}
|
|
626
|
-
plan(conversation, turn) {
|
|
627
|
-
const sent = this.kit.send({
|
|
628
|
-
type: 'plan',
|
|
629
|
-
conversation_id: conversation.id,
|
|
630
|
-
turn_id: turn.id,
|
|
631
|
-
sequence: turn.planSequence + 1,
|
|
632
|
-
from: 0,
|
|
633
|
-
steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
634
|
-
});
|
|
635
|
-
if (sent)
|
|
636
|
-
turn.planSequence++;
|
|
637
|
-
}
|
|
638
686
|
}
|