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