@agentshouse/kit 0.2.1-alpha.9 → 0.2.1-c14f8446ba284
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 +37 -20
- package/dist/abort.js +9 -0
- package/dist/acp.js +18 -75
- package/dist/agents.js +246 -52
- package/dist/api.js +36 -4
- package/dist/app-git.js +64 -0
- package/dist/bootstrap.json +10 -14
- package/dist/bridge.js +99 -70
- package/dist/clis-main.js +23 -0
- package/dist/clis.js +171 -71
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +324 -199
- package/dist/copies.js +70 -68
- package/dist/end.js +8 -0
- package/dist/events.js +521 -0
- package/dist/files.js +16 -1
- package/dist/git-main.js +2 -2
- package/dist/git.js +6 -2
- package/dist/home.js +32 -3
- package/dist/house-main.js +117 -44
- package/dist/instructions.js +13 -12
- package/dist/kit-main.js +7 -1
- package/dist/login.js +39 -94
- package/dist/logout.js +21 -0
- package/dist/mcp.js +39 -0
- package/dist/operation.js +7 -4
- package/dist/parts.js +64 -0
- package/dist/refusals.js +53 -0
- package/dist/relay.js +10 -0
- package/dist/resident.js +46 -21
- package/dist/scope.js +203 -0
- package/dist/secret-input.js +7 -2
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +48 -11
- package/dist/skills.js +5 -10
- package/dist/stream.js +31 -6
- package/dist/transcription.js +170 -0
- package/dist/update.js +119 -0
- package/dist/work.js +348 -0
- package/how-we-work.md +10 -12
- package/package.json +4 -7
- package/dist/blocks.js +0 -51
package/dist/conversations.js
CHANGED
|
@@ -1,16 +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
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { unlessAborted } from './abort.js';
|
|
6
|
+
import { startAdapter } from './acp.js';
|
|
7
7
|
import { openBridge } from './bridge.js';
|
|
8
8
|
import { CLIS } from './clis.js';
|
|
9
|
-
import {
|
|
9
|
+
import { readerFor } from './events.js';
|
|
10
|
+
import { placeFiles, uploadBytes } from './files.js';
|
|
11
|
+
import { agentBase } from './home.js';
|
|
10
12
|
import { instructions } from './instructions.js';
|
|
13
|
+
import { Parts } from './parts.js';
|
|
14
|
+
import { killMarked, Scope } from './scope.js';
|
|
11
15
|
import { holdSecretInput } from './secret-input.js';
|
|
12
16
|
import { createHowWeWork } from './skills.js';
|
|
13
|
-
|
|
17
|
+
import { transcribe } from './transcription.js';
|
|
18
|
+
import { workFor } from './work.js';
|
|
14
19
|
const KILLED = 'the conversation was killed';
|
|
15
20
|
function causeOf(error) {
|
|
16
21
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -21,42 +26,40 @@ function answerMessage(request, response) {
|
|
|
21
26
|
function logged(error) {
|
|
22
27
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
23
28
|
}
|
|
24
|
-
function
|
|
25
|
-
return
|
|
26
|
-
const killed = () => reject(signal.reason);
|
|
27
|
-
signal.addEventListener('abort', killed, { once: true });
|
|
28
|
-
if (signal.aborted)
|
|
29
|
-
killed();
|
|
30
|
-
work.then(resolve, reject).finally(() => signal.removeEventListener('abort', killed));
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
function stop(bridge) {
|
|
34
|
-
killMarked(`HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`);
|
|
29
|
+
function marker(bridge) {
|
|
30
|
+
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
35
31
|
}
|
|
36
32
|
class Turn {
|
|
37
33
|
id = randomUUID();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
unsentFrom = null;
|
|
41
|
-
draftSequence = 0;
|
|
42
|
-
planSequence = 0;
|
|
43
|
-
entries = null;
|
|
34
|
+
parts;
|
|
35
|
+
admitted = false;
|
|
44
36
|
questions = 0;
|
|
45
37
|
secrets = 0;
|
|
46
|
-
|
|
38
|
+
prompted = false;
|
|
47
39
|
ended = false;
|
|
40
|
+
images = 0;
|
|
41
|
+
uploads = [];
|
|
42
|
+
constructor(emit) {
|
|
43
|
+
this.parts = new Parts(emit);
|
|
44
|
+
}
|
|
48
45
|
}
|
|
49
46
|
class Conversation {
|
|
50
47
|
id;
|
|
51
48
|
running = null;
|
|
49
|
+
session = null;
|
|
52
50
|
turn = null;
|
|
53
51
|
commands = null;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
context = null;
|
|
53
|
+
leftovers = new Set();
|
|
54
|
+
waiting = [];
|
|
55
|
+
sending = null;
|
|
56
|
+
subagents = new Map();
|
|
57
|
+
held = [];
|
|
57
58
|
opening = null;
|
|
58
59
|
kills = 0;
|
|
59
60
|
killed = new AbortController();
|
|
61
|
+
interrupts = 0;
|
|
62
|
+
interrupted = new AbortController();
|
|
60
63
|
open = 0;
|
|
61
64
|
queue = Promise.resolve();
|
|
62
65
|
reports = Promise.resolve();
|
|
@@ -79,8 +82,11 @@ export class Conversations {
|
|
|
79
82
|
questions = new Map();
|
|
80
83
|
carrying = 0;
|
|
81
84
|
reporting = 0;
|
|
85
|
+
watched = false;
|
|
82
86
|
constructor(kit) {
|
|
83
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);
|
|
84
90
|
}
|
|
85
91
|
input(input) {
|
|
86
92
|
this.carrying++;
|
|
@@ -90,10 +96,17 @@ export class Conversations {
|
|
|
90
96
|
if (input.kind === 'kill') {
|
|
91
97
|
conversation.kills++;
|
|
92
98
|
conversation.killed.abort(new Error(KILLED));
|
|
99
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
100
|
+
waiting.settle({ refused: KILLED });
|
|
93
101
|
this.halt(conversation);
|
|
94
102
|
}
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
if (input.kind === 'interrupt') {
|
|
104
|
+
conversation.interrupts++;
|
|
105
|
+
conversation.interrupted.abort();
|
|
106
|
+
}
|
|
107
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
108
|
+
conversation.queue = handled.catch(() => undefined);
|
|
109
|
+
const work = handled.then(({ ack }) => ack);
|
|
97
110
|
this.acks.set(input.input_id, work);
|
|
98
111
|
return work;
|
|
99
112
|
})();
|
|
@@ -111,15 +124,36 @@ export class Conversations {
|
|
|
111
124
|
for (const conversation of this.conversations.values()) {
|
|
112
125
|
const turn = conversation.turn;
|
|
113
126
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
114
|
-
if (conversation.
|
|
127
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
115
128
|
return false;
|
|
116
129
|
}
|
|
117
|
-
|
|
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();
|
|
118
145
|
}
|
|
119
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;
|
|
120
154
|
for (const conversation of this.conversations.values()) {
|
|
121
155
|
if (conversation.turn !== null)
|
|
122
|
-
conversation.turn
|
|
156
|
+
this.replay(conversation, conversation.turn);
|
|
123
157
|
}
|
|
124
158
|
}
|
|
125
159
|
conversation(id) {
|
|
@@ -132,13 +166,17 @@ export class Conversations {
|
|
|
132
166
|
}
|
|
133
167
|
async carry(conversation, input) {
|
|
134
168
|
if (input.kind === 'open')
|
|
135
|
-
return this.open(conversation, input);
|
|
169
|
+
return { ack: Promise.resolve(await this.open(conversation, input)) };
|
|
136
170
|
if (input.kind === 'message')
|
|
137
171
|
return this.message(conversation, input);
|
|
138
|
-
if (input.kind === 'interrupt')
|
|
172
|
+
if (input.kind === 'interrupt') {
|
|
173
|
+
if (--conversation.interrupts === 0)
|
|
174
|
+
conversation.interrupted = new AbortController();
|
|
139
175
|
await this.interrupt(conversation, String(input.turn_id));
|
|
176
|
+
}
|
|
140
177
|
if (input.kind === 'kill') {
|
|
141
178
|
await this.kill(conversation);
|
|
179
|
+
await conversation.sending;
|
|
142
180
|
if (--conversation.kills === 0)
|
|
143
181
|
conversation.killed = new AbortController();
|
|
144
182
|
}
|
|
@@ -147,11 +185,12 @@ export class Conversations {
|
|
|
147
185
|
if (input.kind === 'answer') {
|
|
148
186
|
const question = this.questions.get(String(input.interaction_id));
|
|
149
187
|
if (question === undefined) {
|
|
150
|
-
|
|
188
|
+
const text = answerMessage(input.request, input.response);
|
|
189
|
+
return this.message(conversation, { ...input, text, files: [], first: false });
|
|
151
190
|
}
|
|
152
191
|
question.answer(input.response);
|
|
153
192
|
}
|
|
154
|
-
return {};
|
|
193
|
+
return { ack: Promise.resolve({}) };
|
|
155
194
|
}
|
|
156
195
|
async open(conversation, input) {
|
|
157
196
|
if (conversation.running !== null)
|
|
@@ -164,35 +203,88 @@ export class Conversations {
|
|
|
164
203
|
}
|
|
165
204
|
}
|
|
166
205
|
async message(conversation, input) {
|
|
206
|
+
const content = [{ type: 'text', text: String(input.text) }];
|
|
207
|
+
let route;
|
|
208
|
+
try {
|
|
209
|
+
route = await this.route(conversation, input.agent_id);
|
|
210
|
+
const files = input.files;
|
|
211
|
+
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
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;
|
|
167
241
|
let ack = {};
|
|
168
242
|
const prompt = [];
|
|
169
243
|
try {
|
|
170
|
-
const route = await this.route(conversation, input.agent_id);
|
|
171
|
-
const paths = await placeFiles(this.kit.house, route.working_directory, input.files, conversation.killed.signal);
|
|
172
244
|
if (conversation.running === null) {
|
|
173
|
-
const
|
|
174
|
-
|
|
245
|
+
const resumed = input.provider_session_id ?? conversation.session;
|
|
246
|
+
const opened = await this.start(conversation, waiting.route, resumed);
|
|
247
|
+
if (resumed === null)
|
|
175
248
|
ack = { provider_session_id: opened };
|
|
176
249
|
}
|
|
177
250
|
if (input.first === true) {
|
|
178
251
|
prompt.push({
|
|
179
252
|
type: 'text',
|
|
180
|
-
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()),
|
|
181
254
|
});
|
|
182
255
|
}
|
|
183
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
184
|
-
if (paths.length > 0)
|
|
185
|
-
prompt.push({ type: 'text', text: paths.join('\n') });
|
|
186
256
|
if (conversation.kills > 0)
|
|
187
257
|
throw new Error(KILLED);
|
|
188
258
|
}
|
|
189
259
|
catch (error) {
|
|
190
|
-
if ('provider_session_id' in ack)
|
|
260
|
+
if ('provider_session_id' in ack) {
|
|
261
|
+
conversation.session = null;
|
|
191
262
|
await this.kill(conversation);
|
|
192
|
-
|
|
263
|
+
}
|
|
264
|
+
waiting.settle({ refused: conversation.kills > 0 ? KILLED : causeOf(error) });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this.prompt(conversation, [...prompt, ...waiting.content]);
|
|
268
|
+
waiting.settle(ack);
|
|
269
|
+
}
|
|
270
|
+
async transcribed(conversation, files, paths) {
|
|
271
|
+
const lines = [];
|
|
272
|
+
for (const [index, path] of paths.entries()) {
|
|
273
|
+
lines.push(path);
|
|
274
|
+
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
275
|
+
continue;
|
|
276
|
+
try {
|
|
277
|
+
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
278
|
+
if (text !== null && text !== '')
|
|
279
|
+
lines.push(`Transcript: ${text}`);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (conversation.kills > 0)
|
|
283
|
+
throw error;
|
|
284
|
+
logged(new Error(`${files[index].name} was not transcribed: ${causeOf(error)}`));
|
|
285
|
+
}
|
|
193
286
|
}
|
|
194
|
-
|
|
195
|
-
return ack;
|
|
287
|
+
return lines;
|
|
196
288
|
}
|
|
197
289
|
async interrupt(conversation, turnId) {
|
|
198
290
|
if (conversation.running === null || conversation.turn?.id !== turnId)
|
|
@@ -203,11 +295,13 @@ export class Conversations {
|
|
|
203
295
|
}
|
|
204
296
|
halt(conversation) {
|
|
205
297
|
if (conversation.opening !== null)
|
|
206
|
-
|
|
298
|
+
killMarked(marker(conversation.opening));
|
|
299
|
+
for (const scope of conversation.leftovers)
|
|
300
|
+
scope.kill();
|
|
207
301
|
if (conversation.running === null)
|
|
208
302
|
return;
|
|
209
303
|
conversation.running.killed = true;
|
|
210
|
-
|
|
304
|
+
conversation.running.scope.kill();
|
|
211
305
|
}
|
|
212
306
|
async kill(conversation) {
|
|
213
307
|
const running = conversation.running;
|
|
@@ -219,6 +313,7 @@ export class Conversations {
|
|
|
219
313
|
if (running === null)
|
|
220
314
|
return;
|
|
221
315
|
try {
|
|
316
|
+
running.reader.requested(option, value);
|
|
222
317
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
223
318
|
sessionId: running.sessionId,
|
|
224
319
|
configId: option,
|
|
@@ -227,54 +322,62 @@ export class Conversations {
|
|
|
227
322
|
this.options(conversation, answered.configOptions);
|
|
228
323
|
}
|
|
229
324
|
catch (error) {
|
|
325
|
+
running.reader.refused(option);
|
|
230
326
|
logged(error);
|
|
231
327
|
}
|
|
232
328
|
}
|
|
233
329
|
async route(conversation, agentId) {
|
|
234
330
|
const signal = conversation.killed.signal;
|
|
235
|
-
await
|
|
331
|
+
await unlessAborted(this.kit.agents.read(), signal);
|
|
236
332
|
if (this.kit.agents.route(agentId) === undefined)
|
|
237
|
-
await
|
|
333
|
+
await unlessAborted(this.kit.agents.refresh(), signal);
|
|
238
334
|
const route = this.kit.agents.route(agentId);
|
|
239
335
|
if (route === undefined)
|
|
240
336
|
throw new Error(`this Environment hosts no Agent ${agentId}`);
|
|
241
|
-
if (route.working_directory === join(
|
|
337
|
+
if (route.working_directory === join(agentBase(), route.agent_id)) {
|
|
242
338
|
await mkdir(route.working_directory, { recursive: true });
|
|
243
339
|
}
|
|
244
340
|
return route;
|
|
245
341
|
}
|
|
246
342
|
async start(conversation, route, sessionId) {
|
|
343
|
+
const reader = readerFor(route.kind);
|
|
344
|
+
reader.session = sessionId;
|
|
247
345
|
const app = client({ name: '@agentshouse/kit' })
|
|
248
|
-
.onNotification('session/update', ({ params }) => this.update(conversation, params))
|
|
249
|
-
.onNotification(TURN_STARTED, (params) => params, () => void this.started(conversation))
|
|
250
|
-
.onNotification(TURN_ENDED, (params) => params, () => this.ended(conversation))
|
|
251
346
|
.onRequest('session/request_permission', ({ params, signal }) => this.ask(conversation, 'session/request_permission', params, false, signal))
|
|
252
347
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal));
|
|
253
|
-
await
|
|
348
|
+
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
254
349
|
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
255
350
|
if (conversation.kills > 0) {
|
|
256
351
|
bridge.close();
|
|
257
352
|
throw new Error(KILLED);
|
|
258
353
|
}
|
|
259
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);
|
|
260
357
|
try {
|
|
261
|
-
await
|
|
262
|
-
|
|
358
|
+
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
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);
|
|
263
364
|
void adapter.exited.then(() => bridge.close());
|
|
264
365
|
const agent = adapter.connection.agent;
|
|
265
366
|
const cwd = route.working_directory;
|
|
367
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
266
368
|
const opened = sessionId === null
|
|
267
|
-
? await agent.request('session/new', { cwd, mcpServers: [] })
|
|
268
|
-
: { ...(await agent.request('session/resume', { sessionId, cwd, mcpServers: [] })), sessionId };
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
sessionId: opened.sessionId,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
};
|
|
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 };
|
|
276
377
|
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
378
|
+
void work.settled().then(() => scope.record());
|
|
277
379
|
conversation.running = running;
|
|
380
|
+
conversation.session = running.sessionId;
|
|
278
381
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: true });
|
|
279
382
|
if (conversation.commands !== null)
|
|
280
383
|
this.commands(conversation, conversation.commands);
|
|
@@ -283,7 +386,9 @@ export class Conversations {
|
|
|
283
386
|
return running.sessionId;
|
|
284
387
|
}
|
|
285
388
|
catch (error) {
|
|
286
|
-
|
|
389
|
+
scope.kill();
|
|
390
|
+
work.close();
|
|
391
|
+
conversation.leftovers.add(scope);
|
|
287
392
|
bridge.close();
|
|
288
393
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
289
394
|
}
|
|
@@ -294,6 +399,7 @@ export class Conversations {
|
|
|
294
399
|
async launchSettings(running, route, offered) {
|
|
295
400
|
let options = offered;
|
|
296
401
|
const settings = [
|
|
402
|
+
['mode', route.mode ?? CLIS[route.kind].fullAccess],
|
|
297
403
|
['model', route.model],
|
|
298
404
|
['thought_level', route.effort],
|
|
299
405
|
];
|
|
@@ -301,6 +407,7 @@ export class Conversations {
|
|
|
301
407
|
if (value === null)
|
|
302
408
|
continue;
|
|
303
409
|
const option = options.find((candidate) => candidate.category === category);
|
|
410
|
+
running.reader.requested(option?.id ?? category, value);
|
|
304
411
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
305
412
|
sessionId: running.sessionId,
|
|
306
413
|
configId: option?.id ?? category,
|
|
@@ -314,19 +421,23 @@ export class Conversations {
|
|
|
314
421
|
const running = conversation.running;
|
|
315
422
|
if (running?.adapter !== adapter)
|
|
316
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);
|
|
317
434
|
conversation.running = null;
|
|
318
435
|
conversation.commands = null;
|
|
319
|
-
conversation.jobs.clear();
|
|
320
|
-
conversation.queued.length = 0;
|
|
321
436
|
for (const [id, question] of this.questions) {
|
|
322
437
|
if (question.conversation === conversation)
|
|
323
438
|
this.questions.delete(id);
|
|
324
439
|
}
|
|
325
440
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: false });
|
|
326
|
-
if (conversation.turn !== null) {
|
|
327
|
-
this.end(conversation, conversation.turn, { failed: running.killed ? KILLED : cause });
|
|
328
|
-
}
|
|
329
|
-
this.kit.changed();
|
|
330
441
|
}
|
|
331
442
|
commands(conversation, commands) {
|
|
332
443
|
conversation.commands = commands;
|
|
@@ -341,7 +452,7 @@ export class Conversations {
|
|
|
341
452
|
}
|
|
342
453
|
deliver(conversation, turn, event, body) {
|
|
343
454
|
this.reporting++;
|
|
344
|
-
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));
|
|
345
456
|
conversation.reports = delivered
|
|
346
457
|
.catch(() => undefined)
|
|
347
458
|
.finally(() => {
|
|
@@ -350,105 +461,82 @@ export class Conversations {
|
|
|
350
461
|
});
|
|
351
462
|
return delivered;
|
|
352
463
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
.then(() => this.admitted(conversation, turn))
|
|
358
|
-
.catch(logged);
|
|
359
|
-
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 });
|
|
360
468
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.draft(conversation, turn);
|
|
365
|
-
}
|
|
366
|
-
if (turn.entries !== null)
|
|
367
|
-
this.plan(conversation, turn);
|
|
469
|
+
replay(conversation, turn) {
|
|
470
|
+
for (const operation of turn.parts.operations())
|
|
471
|
+
this.write(conversation, turn, operation);
|
|
368
472
|
}
|
|
369
473
|
begin(conversation) {
|
|
370
|
-
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);
|
|
371
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);
|
|
372
487
|
this.kit.changed();
|
|
373
488
|
return turn;
|
|
374
489
|
}
|
|
375
|
-
end(conversation, turn,
|
|
490
|
+
end(conversation, turn, failed) {
|
|
376
491
|
if (turn.ended)
|
|
377
492
|
return;
|
|
493
|
+
if (conversation.turn === turn) {
|
|
494
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
495
|
+
this.apply(conversation, turn, event);
|
|
496
|
+
}
|
|
378
497
|
turn.ended = true;
|
|
379
498
|
conversation.open--;
|
|
380
499
|
if (conversation.turn === turn)
|
|
381
500
|
conversation.turn = null;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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);
|
|
386
508
|
this.kit.changed();
|
|
387
509
|
}
|
|
388
|
-
failed(conversation, cause) {
|
|
389
|
-
if (conversation.open > 0)
|
|
390
|
-
conversation.late.push(cause);
|
|
391
|
-
else
|
|
392
|
-
this.end(conversation, this.report(conversation), { failed: cause });
|
|
393
|
-
}
|
|
394
510
|
prompt(conversation, prompt) {
|
|
395
511
|
const running = conversation.running;
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
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) });
|
|
408
524
|
}
|
|
409
525
|
});
|
|
410
526
|
}
|
|
411
|
-
attach(turn, sent) {
|
|
412
|
-
turn.prompt = sent;
|
|
413
|
-
sent.turn = turn;
|
|
414
|
-
}
|
|
415
|
-
answered(conversation, sent, failed) {
|
|
416
|
-
const turn = sent.turn;
|
|
417
|
-
if (turn === null) {
|
|
418
|
-
const queued = conversation.queued.indexOf(sent);
|
|
419
|
-
if (queued < 0)
|
|
420
|
-
return;
|
|
421
|
-
conversation.queued.splice(queued, 1);
|
|
422
|
-
if (failed !== null)
|
|
423
|
-
this.failed(conversation, failed);
|
|
424
|
-
}
|
|
425
|
-
else if (failed !== null) {
|
|
426
|
-
if (turn.ended)
|
|
427
|
-
this.failed(conversation, failed);
|
|
428
|
-
else
|
|
429
|
-
this.end(conversation, turn, { failed });
|
|
430
|
-
}
|
|
431
|
-
else if (turn.prompt === sent) {
|
|
432
|
-
this.end(conversation, turn, { text: turn.text });
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
527
|
started(conversation) {
|
|
436
|
-
|
|
437
|
-
return conversation.turn;
|
|
438
|
-
const turn = this.begin(conversation);
|
|
439
|
-
const queued = conversation.queued.shift();
|
|
440
|
-
if (queued !== undefined)
|
|
441
|
-
this.attach(turn, queued);
|
|
442
|
-
return turn;
|
|
528
|
+
return conversation.turn ?? this.begin(conversation);
|
|
443
529
|
}
|
|
444
530
|
ended(conversation) {
|
|
445
531
|
const turn = conversation.turn;
|
|
446
532
|
if (turn === null)
|
|
447
533
|
return;
|
|
448
|
-
if (turn.
|
|
449
|
-
this.end(conversation, turn,
|
|
534
|
+
if (!turn.prompted) {
|
|
535
|
+
this.end(conversation, turn, null);
|
|
450
536
|
}
|
|
451
537
|
else {
|
|
538
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
539
|
+
this.apply(conversation, turn, event);
|
|
452
540
|
conversation.turn = null;
|
|
453
541
|
this.kit.changed();
|
|
454
542
|
}
|
|
@@ -497,65 +585,102 @@ export class Conversations {
|
|
|
497
585
|
if (content !== null)
|
|
498
586
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
499
587
|
}
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
588
|
+
read(conversation, reader, message) {
|
|
589
|
+
for (const event of reader.read(message))
|
|
590
|
+
this.event(conversation, event);
|
|
591
|
+
}
|
|
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;
|
|
517
620
|
}
|
|
518
|
-
|
|
519
|
-
|
|
621
|
+
this.apply(conversation, this.started(conversation), event);
|
|
622
|
+
}
|
|
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;
|
|
520
650
|
}
|
|
521
651
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
652
|
+
subagentDone(conversation, id, result) {
|
|
653
|
+
const subagent = conversation.subagents.get(id);
|
|
654
|
+
if (subagent === undefined)
|
|
655
|
+
return;
|
|
656
|
+
conversation.subagents.delete(id);
|
|
657
|
+
if (subagent.turn === conversation.turn) {
|
|
658
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
662
|
+
if (conversation.turn === null)
|
|
663
|
+
conversation.held.push(part);
|
|
525
664
|
else
|
|
526
|
-
conversation.
|
|
527
|
-
this.kit.changed();
|
|
665
|
+
conversation.turn.parts.start(part);
|
|
528
666
|
}
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
turn.
|
|
543
|
-
turn.unsentFrom = null;
|
|
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 });
|
|
544
681
|
}
|
|
545
|
-
|
|
546
|
-
turn.
|
|
682
|
+
catch (error) {
|
|
683
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${causeOf(error)}` });
|
|
547
684
|
}
|
|
548
685
|
}
|
|
549
|
-
plan(conversation, turn) {
|
|
550
|
-
const sent = this.kit.send({
|
|
551
|
-
type: 'plan',
|
|
552
|
-
conversation_id: conversation.id,
|
|
553
|
-
turn_id: turn.id,
|
|
554
|
-
sequence: turn.planSequence + 1,
|
|
555
|
-
from: 0,
|
|
556
|
-
steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
557
|
-
});
|
|
558
|
-
if (sent)
|
|
559
|
-
turn.planSequence++;
|
|
560
|
-
}
|
|
561
686
|
}
|