@agentshouse/kit 0.2.1-c23b83622c698 → 0.2.1-c23bd93c02c83
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 +32 -31
- package/dist/acp.js +16 -93
- package/dist/agents.js +125 -35
- package/dist/api.js +34 -2
- package/dist/app-git.js +48 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +35 -56
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +126 -69
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +350 -246
- package/dist/copies.js +157 -116
- package/dist/end.js +23 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +524 -0
- package/dist/files.js +21 -1
- package/dist/git.js +10 -8
- package/dist/home.js +96 -7
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -159
- package/dist/instructions.js +10 -32
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -19
- package/dist/login.js +118 -103
- package/dist/logout.js +14 -13
- package/dist/parts.js +82 -0
- package/dist/placement.js +30 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +12 -2
- package/dist/resident.js +84 -32
- 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 +346 -0
- package/how-we-work.md +13 -3
- package/package.json +3 -2
- package/skills/ask-matt/SKILL.md +17 -11
- package/skills/ask-matt/agents/openai.yaml +1 -0
- package/skills/code-review/SKILL.md +5 -5
- package/skills/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/skills/codebase-design/SKILL.md +0 -2
- package/skills/diagnosing-bugs/SKILL.md +2 -4
- package/skills/domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/skills/domain-modeling/SKILL.md +12 -14
- package/skills/grill-me/SKILL.md +1 -2
- package/skills/grill-me/agents/openai.yaml +1 -0
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grill-with-docs/agents/openai.yaml +1 -0
- package/skills/grilling/SKILL.md +2 -2
- package/skills/handoff/SKILL.md +2 -3
- package/skills/handoff/agents/openai.yaml +1 -0
- package/skills/implement/SKILL.md +5 -4
- package/skills/implement/agents/openai.yaml +1 -0
- package/skills/implement-spec/SKILL.md +40 -0
- package/skills/implement-spec/agents/openai.yaml +5 -0
- package/skills/improve-codebase-architecture/SKILL.md +6 -7
- package/skills/improve-codebase-architecture/agents/openai.yaml +1 -0
- package/skills/pr/CREDITS.md +3 -0
- package/skills/pr/LICENSE +21 -0
- package/skills/pr/SKILL.md +170 -0
- package/skills/pr/agents/openai.yaml +3 -0
- package/skills/prototype/SKILL.md +0 -2
- package/skills/research/SKILL.md +0 -2
- package/skills/retro/LICENSE +21 -0
- package/skills/retro/SKILL.md +44 -0
- package/skills/retro/agents/openai.yaml +5 -0
- package/skills/tdd/SKILL.md +2 -4
- package/skills/teach/SKILL.md +3 -4
- package/skills/teach/agents/openai.yaml +1 -0
- package/skills/to-questionnaire/SKILL.md +1 -2
- package/skills/to-questionnaire/agents/openai.yaml +1 -0
- package/skills/to-spec/SKILL.md +1 -2
- package/skills/to-spec/agents/openai.yaml +1 -0
- package/skills/to-tickets/SKILL.md +3 -4
- package/skills/to-tickets/agents/openai.yaml +1 -0
- package/skills/triage/AGENT-BRIEF.md +207 -0
- package/skills/triage/LICENSE +21 -0
- package/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/skills/triage/SKILL.md +112 -0
- package/skills/triage/agents/openai.yaml +5 -0
- package/skills/wait-what/SKILL.md +2 -3
- package/skills/wait-what/agents/openai.yaml +1 -0
- package/skills/wayfinder/SKILL.md +5 -6
- package/skills/wayfinder/agents/openai.yaml +1 -0
- package/skills/wizard/LICENSE +21 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/wizard/agents/openai.yaml +3 -0
- package/skills/wizard/template.sh +223 -0
- package/skills/writing-for-agents/SKILL.md +0 -2
- package/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,47 @@
|
|
|
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 { answeredFailure, 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 REFUSED = 'the CLI refused the prompt';
|
|
21
|
+
const NO_CONNECTION = "the Kit could not open this conversation's connection";
|
|
22
|
+
const NO_START = "the Agent's CLI did not start";
|
|
23
|
+
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
24
|
+
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
25
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
26
|
+
class NotStarted extends Error {
|
|
27
|
+
}
|
|
28
|
+
async function step(reason, work) {
|
|
29
|
+
try {
|
|
30
|
+
return await work;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new NotStarted(reason, { cause: error });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const EXIT_PLAN_MODE = {
|
|
37
|
+
parse(value) {
|
|
38
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
39
|
+
throw new Error('_x.ai/exit_plan_mode needs an object');
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export const CONVERSATION_INPUTS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
17
45
|
function causeOf(error) {
|
|
18
46
|
return error instanceof Error ? error.message : String(error);
|
|
19
47
|
}
|
|
@@ -23,34 +51,44 @@ function answerMessage(request, response) {
|
|
|
23
51
|
function logged(error) {
|
|
24
52
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
25
53
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
54
|
+
function refusal(conversation, error) {
|
|
55
|
+
if (conversation.kills > 0)
|
|
56
|
+
return KILLED;
|
|
57
|
+
const cause = error instanceof NotStarted ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
58
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
59
|
+
return causeOf(error);
|
|
60
|
+
}
|
|
61
|
+
function marker(bridge) {
|
|
62
|
+
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
28
63
|
}
|
|
29
64
|
class Turn {
|
|
30
65
|
id = randomUUID();
|
|
31
|
-
|
|
32
|
-
segment = null;
|
|
66
|
+
parts;
|
|
33
67
|
admitted = false;
|
|
34
|
-
notes = [];
|
|
35
|
-
sent = [];
|
|
36
|
-
unsentFrom = null;
|
|
37
|
-
draftSequence = 0;
|
|
38
|
-
planSequence = 0;
|
|
39
|
-
entries = null;
|
|
40
68
|
questions = 0;
|
|
41
69
|
secrets = 0;
|
|
42
|
-
|
|
70
|
+
prompted = false;
|
|
43
71
|
ended = false;
|
|
72
|
+
images = 0;
|
|
73
|
+
uploads = [];
|
|
74
|
+
constructor(emit) {
|
|
75
|
+
this.parts = new Parts(emit);
|
|
76
|
+
}
|
|
44
77
|
}
|
|
45
78
|
class Conversation {
|
|
46
79
|
id;
|
|
47
80
|
running = null;
|
|
81
|
+
session = null;
|
|
48
82
|
turn = null;
|
|
49
83
|
commands = null;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
84
|
+
context = null;
|
|
85
|
+
leftovers = new Set();
|
|
86
|
+
waiting = [];
|
|
87
|
+
sending = null;
|
|
88
|
+
subagents = new Map();
|
|
89
|
+
held = [];
|
|
53
90
|
opening = null;
|
|
91
|
+
prompted = null;
|
|
54
92
|
kills = 0;
|
|
55
93
|
killed = new AbortController();
|
|
56
94
|
interrupts = 0;
|
|
@@ -77,8 +115,11 @@ export class Conversations {
|
|
|
77
115
|
questions = new Map();
|
|
78
116
|
carrying = 0;
|
|
79
117
|
reporting = 0;
|
|
118
|
+
watched = false;
|
|
80
119
|
constructor(kit) {
|
|
81
120
|
this.kit = kit;
|
|
121
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
122
|
+
setInterval(() => this.look(), 2_000);
|
|
82
123
|
}
|
|
83
124
|
input(input) {
|
|
84
125
|
this.carrying++;
|
|
@@ -88,14 +129,17 @@ export class Conversations {
|
|
|
88
129
|
if (input.kind === 'kill') {
|
|
89
130
|
conversation.kills++;
|
|
90
131
|
conversation.killed.abort(new Error(KILLED));
|
|
132
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
133
|
+
waiting.settle({ refused: KILLED });
|
|
91
134
|
this.halt(conversation);
|
|
92
135
|
}
|
|
93
136
|
if (input.kind === 'interrupt') {
|
|
94
137
|
conversation.interrupts++;
|
|
95
138
|
conversation.interrupted.abort();
|
|
96
139
|
}
|
|
97
|
-
const
|
|
98
|
-
conversation.queue =
|
|
140
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
141
|
+
conversation.queue = handled.catch(() => undefined);
|
|
142
|
+
const work = handled.then(({ ack }) => ack);
|
|
99
143
|
this.acks.set(input.input_id, work);
|
|
100
144
|
return work;
|
|
101
145
|
})();
|
|
@@ -113,15 +157,39 @@ export class Conversations {
|
|
|
113
157
|
for (const conversation of this.conversations.values()) {
|
|
114
158
|
const turn = conversation.turn;
|
|
115
159
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
116
|
-
if (conversation.
|
|
160
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
117
161
|
return false;
|
|
118
162
|
}
|
|
119
|
-
|
|
163
|
+
const conversations = [...this.conversations.values()];
|
|
164
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
165
|
+
return false;
|
|
166
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
167
|
+
}
|
|
168
|
+
quiet() {
|
|
169
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
|
|
170
|
+
}
|
|
171
|
+
look() {
|
|
172
|
+
for (const conversation of this.conversations.values()) {
|
|
173
|
+
for (const scope of conversation.leftovers) {
|
|
174
|
+
if (!scope.empty())
|
|
175
|
+
continue;
|
|
176
|
+
scope.close();
|
|
177
|
+
conversation.leftovers.delete(scope);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.kit.changed();
|
|
120
181
|
}
|
|
121
182
|
opened() {
|
|
183
|
+
this.watched = false;
|
|
184
|
+
}
|
|
185
|
+
watching(watched) {
|
|
186
|
+
const resend = watched && !this.watched;
|
|
187
|
+
this.watched = watched;
|
|
188
|
+
if (!resend)
|
|
189
|
+
return;
|
|
122
190
|
for (const conversation of this.conversations.values()) {
|
|
123
191
|
if (conversation.turn !== null)
|
|
124
|
-
conversation.turn
|
|
192
|
+
this.replay(conversation, conversation.turn);
|
|
125
193
|
}
|
|
126
194
|
}
|
|
127
195
|
conversation(id) {
|
|
@@ -134,7 +202,7 @@ export class Conversations {
|
|
|
134
202
|
}
|
|
135
203
|
async carry(conversation, input) {
|
|
136
204
|
if (input.kind === 'open')
|
|
137
|
-
return this.open(conversation, input);
|
|
205
|
+
return { ack: Promise.resolve(await this.open(conversation, input)) };
|
|
138
206
|
if (input.kind === 'message')
|
|
139
207
|
return this.message(conversation, input);
|
|
140
208
|
if (input.kind === 'interrupt') {
|
|
@@ -144,6 +212,7 @@ export class Conversations {
|
|
|
144
212
|
}
|
|
145
213
|
if (input.kind === 'kill') {
|
|
146
214
|
await this.kill(conversation);
|
|
215
|
+
await conversation.sending;
|
|
147
216
|
if (--conversation.kills === 0)
|
|
148
217
|
conversation.killed = new AbortController();
|
|
149
218
|
}
|
|
@@ -152,54 +221,91 @@ export class Conversations {
|
|
|
152
221
|
if (input.kind === 'answer') {
|
|
153
222
|
const question = this.questions.get(String(input.interaction_id));
|
|
154
223
|
if (question === undefined) {
|
|
155
|
-
|
|
224
|
+
const text = answerMessage(input.request, input.response);
|
|
225
|
+
return this.message(conversation, { ...input, text, files: [], first: false });
|
|
156
226
|
}
|
|
157
227
|
question.answer(input.response);
|
|
158
228
|
}
|
|
159
|
-
return {};
|
|
229
|
+
return { ack: Promise.resolve({}) };
|
|
160
230
|
}
|
|
161
231
|
async open(conversation, input) {
|
|
162
232
|
if (conversation.running !== null)
|
|
163
233
|
return {};
|
|
164
234
|
try {
|
|
165
|
-
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null) };
|
|
235
|
+
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null, true) };
|
|
166
236
|
}
|
|
167
237
|
catch (error) {
|
|
168
|
-
return { refused:
|
|
238
|
+
return { refused: refusal(conversation, error) };
|
|
169
239
|
}
|
|
170
240
|
}
|
|
171
241
|
async message(conversation, input) {
|
|
172
|
-
|
|
173
|
-
|
|
242
|
+
const content = [{ type: 'text', text: String(input.text) }];
|
|
243
|
+
let route;
|
|
174
244
|
try {
|
|
175
|
-
|
|
245
|
+
route = await this.route(conversation, input.agent_id);
|
|
176
246
|
const files = input.files;
|
|
177
247
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
178
248
|
const lines = await this.transcribed(conversation, files, paths);
|
|
249
|
+
if (lines.length > 0)
|
|
250
|
+
content.push({ type: 'text', text: lines.join('\n') });
|
|
251
|
+
if (conversation.kills > 0)
|
|
252
|
+
throw new Error(KILLED);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
ack: new Promise((settle) => {
|
|
259
|
+
conversation.waiting.push({ input, route, content, settle });
|
|
260
|
+
this.next(conversation);
|
|
261
|
+
}),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
next(conversation) {
|
|
265
|
+
if (conversation.sending !== null || conversation.open > 0)
|
|
266
|
+
return;
|
|
267
|
+
const waiting = conversation.waiting.shift();
|
|
268
|
+
if (waiting === undefined)
|
|
269
|
+
return;
|
|
270
|
+
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
271
|
+
conversation.sending = null;
|
|
272
|
+
this.next(conversation);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async send(conversation, waiting) {
|
|
276
|
+
const { input } = waiting;
|
|
277
|
+
let ack = {};
|
|
278
|
+
let started = false;
|
|
279
|
+
const prompt = [];
|
|
280
|
+
try {
|
|
179
281
|
if (conversation.running === null) {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
282
|
+
const stored = conversation.session ?? input.provider_session_id;
|
|
283
|
+
const empty = input.first === true && conversation.prompted !== stored;
|
|
284
|
+
started = (await this.start(conversation, waiting.route, stored, empty)) !== stored;
|
|
285
|
+
}
|
|
286
|
+
const session = conversation.running.sessionId;
|
|
287
|
+
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
288
|
+
ack = { provider_session_id: session };
|
|
183
289
|
}
|
|
184
290
|
if (input.first === true) {
|
|
185
291
|
prompt.push({
|
|
186
292
|
type: 'text',
|
|
187
|
-
text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
|
|
293
|
+
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
188
294
|
});
|
|
189
295
|
}
|
|
190
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
191
|
-
if (lines.length > 0)
|
|
192
|
-
prompt.push({ type: 'text', text: lines.join('\n') });
|
|
193
296
|
if (conversation.kills > 0)
|
|
194
297
|
throw new Error(KILLED);
|
|
195
298
|
}
|
|
196
299
|
catch (error) {
|
|
197
|
-
if (
|
|
300
|
+
if (started) {
|
|
301
|
+
conversation.session = null;
|
|
198
302
|
await this.kill(conversation);
|
|
199
|
-
|
|
303
|
+
}
|
|
304
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
305
|
+
return;
|
|
200
306
|
}
|
|
201
|
-
this.prompt(conversation, prompt);
|
|
202
|
-
|
|
307
|
+
this.prompt(conversation, [...prompt, ...waiting.content]);
|
|
308
|
+
waiting.settle(ack);
|
|
203
309
|
}
|
|
204
310
|
async transcribed(conversation, files, paths) {
|
|
205
311
|
const lines = [];
|
|
@@ -209,7 +315,7 @@ export class Conversations {
|
|
|
209
315
|
continue;
|
|
210
316
|
try {
|
|
211
317
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
212
|
-
if (text !== '')
|
|
318
|
+
if (text !== null && text !== '')
|
|
213
319
|
lines.push(`Transcript: ${text}`);
|
|
214
320
|
}
|
|
215
321
|
catch (error) {
|
|
@@ -229,11 +335,13 @@ export class Conversations {
|
|
|
229
335
|
}
|
|
230
336
|
halt(conversation) {
|
|
231
337
|
if (conversation.opening !== null)
|
|
232
|
-
|
|
338
|
+
killMarked(marker(conversation.opening));
|
|
339
|
+
for (const scope of conversation.leftovers)
|
|
340
|
+
scope.kill();
|
|
233
341
|
if (conversation.running === null)
|
|
234
342
|
return;
|
|
235
343
|
conversation.running.killed = true;
|
|
236
|
-
|
|
344
|
+
conversation.running.scope.kill();
|
|
237
345
|
}
|
|
238
346
|
async kill(conversation) {
|
|
239
347
|
const running = conversation.running;
|
|
@@ -245,6 +353,7 @@ export class Conversations {
|
|
|
245
353
|
if (running === null)
|
|
246
354
|
return;
|
|
247
355
|
try {
|
|
356
|
+
running.reader.requested(option, value);
|
|
248
357
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
249
358
|
sessionId: running.sessionId,
|
|
250
359
|
configId: option,
|
|
@@ -253,6 +362,7 @@ export class Conversations {
|
|
|
253
362
|
this.options(conversation, answered.configOptions);
|
|
254
363
|
}
|
|
255
364
|
catch (error) {
|
|
365
|
+
running.reader.refused(option);
|
|
256
366
|
logged(error);
|
|
257
367
|
}
|
|
258
368
|
}
|
|
@@ -269,41 +379,46 @@ export class Conversations {
|
|
|
269
379
|
}
|
|
270
380
|
return route;
|
|
271
381
|
}
|
|
272
|
-
async start(conversation, route, sessionId) {
|
|
382
|
+
async start(conversation, route, sessionId, empty) {
|
|
383
|
+
const reader = readerFor(route.kind);
|
|
384
|
+
reader.session = sessionId;
|
|
273
385
|
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
386
|
.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))
|
|
387
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
388
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
279
389
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
280
|
-
const bridge = await openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
390
|
+
const bridge = await step(NO_CONNECTION, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
281
391
|
if (conversation.kills > 0) {
|
|
282
392
|
bridge.close();
|
|
283
393
|
throw new Error(KILLED);
|
|
284
394
|
}
|
|
285
395
|
conversation.opening = bridge;
|
|
396
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
397
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
286
398
|
try {
|
|
287
399
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
288
|
-
await CLIS[route.kind].allowHouse();
|
|
289
|
-
const
|
|
400
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
401
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
402
|
+
const adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
403
|
+
work.read(message);
|
|
404
|
+
this.read(conversation, reader, message);
|
|
405
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
290
406
|
void adapter.exited.then(() => bridge.close());
|
|
291
407
|
const agent = adapter.connection.agent;
|
|
292
408
|
const cwd = route.working_directory;
|
|
293
|
-
const _meta = CLIS[route.kind].sessionMeta;
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
};
|
|
305
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
409
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
410
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
411
|
+
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
412
|
+
reader.session = opened.sessionId;
|
|
413
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
414
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
415
|
+
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
416
|
+
}
|
|
417
|
+
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
418
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
419
|
+
void work.settled().then(() => scope.record());
|
|
306
420
|
conversation.running = running;
|
|
421
|
+
conversation.session = running.sessionId;
|
|
307
422
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: true });
|
|
308
423
|
if (conversation.commands !== null)
|
|
309
424
|
this.commands(conversation, conversation.commands);
|
|
@@ -312,7 +427,9 @@ export class Conversations {
|
|
|
312
427
|
return running.sessionId;
|
|
313
428
|
}
|
|
314
429
|
catch (error) {
|
|
315
|
-
|
|
430
|
+
scope.kill();
|
|
431
|
+
work.close();
|
|
432
|
+
conversation.leftovers.add(scope);
|
|
316
433
|
bridge.close();
|
|
317
434
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
318
435
|
}
|
|
@@ -320,6 +437,18 @@ export class Conversations {
|
|
|
320
437
|
conversation.opening = null;
|
|
321
438
|
}
|
|
322
439
|
}
|
|
440
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
441
|
+
try {
|
|
442
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
446
|
+
throw new NotStarted(NO_RESUME, { cause: error });
|
|
447
|
+
}
|
|
448
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
323
452
|
async launchSettings(running, route, offered) {
|
|
324
453
|
let options = offered;
|
|
325
454
|
const settings = [
|
|
@@ -331,6 +460,7 @@ export class Conversations {
|
|
|
331
460
|
if (value === null)
|
|
332
461
|
continue;
|
|
333
462
|
const option = options.find((candidate) => candidate.category === category);
|
|
463
|
+
running.reader.requested(option?.id ?? category, value);
|
|
334
464
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
335
465
|
sessionId: running.sessionId,
|
|
336
466
|
configId: option?.id ?? category,
|
|
@@ -344,19 +474,23 @@ export class Conversations {
|
|
|
344
474
|
const running = conversation.running;
|
|
345
475
|
if (running?.adapter !== adapter)
|
|
346
476
|
return;
|
|
477
|
+
this.detach(conversation, running);
|
|
478
|
+
if (conversation.turn !== null) {
|
|
479
|
+
this.end(conversation, conversation.turn, { failed: running.killed ? KILLED : cause });
|
|
480
|
+
}
|
|
481
|
+
this.next(conversation);
|
|
482
|
+
this.kit.changed();
|
|
483
|
+
}
|
|
484
|
+
detach(conversation, running) {
|
|
485
|
+
running.work.close();
|
|
486
|
+
conversation.leftovers.add(running.scope);
|
|
347
487
|
conversation.running = null;
|
|
348
488
|
conversation.commands = null;
|
|
349
|
-
conversation.jobs.clear();
|
|
350
|
-
conversation.queued.length = 0;
|
|
351
489
|
for (const [id, question] of this.questions) {
|
|
352
490
|
if (question.conversation === conversation)
|
|
353
491
|
this.questions.delete(id);
|
|
354
492
|
}
|
|
355
493
|
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
494
|
}
|
|
361
495
|
commands(conversation, commands) {
|
|
362
496
|
conversation.commands = commands;
|
|
@@ -371,7 +505,7 @@ export class Conversations {
|
|
|
371
505
|
}
|
|
372
506
|
deliver(conversation, turn, event, body) {
|
|
373
507
|
this.reporting++;
|
|
374
|
-
const delivered = conversation.reports.then(() => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, body));
|
|
508
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
375
509
|
conversation.reports = delivered
|
|
376
510
|
.catch(() => undefined)
|
|
377
511
|
.finally(() => {
|
|
@@ -380,120 +514,89 @@ export class Conversations {
|
|
|
380
514
|
});
|
|
381
515
|
return delivered;
|
|
382
516
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
.then(() => this.admitted(conversation, turn))
|
|
388
|
-
.catch(logged);
|
|
389
|
-
return turn;
|
|
517
|
+
write(conversation, turn, operation) {
|
|
518
|
+
if (!turn.admitted || turn.ended || !this.watched)
|
|
519
|
+
return;
|
|
520
|
+
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
390
521
|
}
|
|
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);
|
|
522
|
+
replay(conversation, turn) {
|
|
523
|
+
for (const operation of turn.parts.operations())
|
|
524
|
+
this.write(conversation, turn, operation);
|
|
399
525
|
}
|
|
400
526
|
begin(conversation) {
|
|
401
|
-
const turn = this.
|
|
527
|
+
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
528
|
+
conversation.open++;
|
|
529
|
+
void this.deliver(conversation, turn, 'started', {})
|
|
530
|
+
.then(() => {
|
|
531
|
+
turn.admitted = true;
|
|
532
|
+
this.replay(conversation, turn);
|
|
533
|
+
})
|
|
534
|
+
.catch(logged);
|
|
402
535
|
conversation.turn = turn;
|
|
536
|
+
if (conversation.context !== null)
|
|
537
|
+
turn.parts.measure(conversation.context);
|
|
538
|
+
for (const part of conversation.held.splice(0))
|
|
539
|
+
turn.parts.start(part);
|
|
403
540
|
this.kit.changed();
|
|
404
541
|
return turn;
|
|
405
542
|
}
|
|
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) {
|
|
543
|
+
end(conversation, turn, failed, notice = null) {
|
|
418
544
|
if (turn.ended)
|
|
419
545
|
return;
|
|
546
|
+
if (conversation.turn === turn) {
|
|
547
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
548
|
+
this.apply(conversation, turn, event);
|
|
549
|
+
}
|
|
550
|
+
if (notice !== null)
|
|
551
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
420
552
|
turn.ended = true;
|
|
421
553
|
conversation.open--;
|
|
422
554
|
if (conversation.turn === turn)
|
|
423
555
|
conversation.turn = null;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
556
|
+
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
557
|
+
parts: turn.parts.saved(),
|
|
558
|
+
context: turn.parts.context,
|
|
559
|
+
...failed,
|
|
560
|
+
}));
|
|
561
|
+
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
562
|
+
this.next(conversation);
|
|
428
563
|
this.kit.changed();
|
|
429
564
|
}
|
|
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
565
|
prompt(conversation, prompt) {
|
|
437
566
|
const running = conversation.running;
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (
|
|
447
|
-
this.
|
|
448
|
-
if (conversation.open === 0 && conversation.running === running)
|
|
449
|
-
stop(running.bridge);
|
|
567
|
+
const turn = this.begin(conversation);
|
|
568
|
+
turn.prompted = true;
|
|
569
|
+
conversation.prompted = running.sessionId;
|
|
570
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
571
|
+
const notice = answeredFailure(answer);
|
|
572
|
+
const failed = notice ?? (answer.stopReason === 'refusal' ? REFUSED : null);
|
|
573
|
+
this.end(conversation, turn, failed === null ? null : { failed }, notice);
|
|
574
|
+
}, async (error) => {
|
|
575
|
+
if (running.adapter.connection.signal.aborted) {
|
|
576
|
+
this.end(conversation, turn, { failed: running.killed ? KILLED : await running.adapter.exited });
|
|
450
577
|
}
|
|
451
|
-
else
|
|
452
|
-
|
|
578
|
+
else {
|
|
579
|
+
if (conversation.open === 1 && conversation.running === running) {
|
|
580
|
+
running.scope.kill();
|
|
581
|
+
this.detach(conversation, running);
|
|
582
|
+
}
|
|
583
|
+
this.end(conversation, turn, { failed: causeOf(error) });
|
|
453
584
|
}
|
|
454
585
|
});
|
|
455
586
|
}
|
|
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
587
|
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;
|
|
588
|
+
return conversation.turn ?? this.begin(conversation);
|
|
488
589
|
}
|
|
489
590
|
ended(conversation) {
|
|
490
591
|
const turn = conversation.turn;
|
|
491
592
|
if (turn === null)
|
|
492
593
|
return;
|
|
493
|
-
if (turn.
|
|
494
|
-
this.
|
|
594
|
+
if (!turn.prompted) {
|
|
595
|
+
this.end(conversation, turn, null);
|
|
495
596
|
}
|
|
496
597
|
else {
|
|
598
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
599
|
+
this.apply(conversation, turn, event);
|
|
497
600
|
conversation.turn = null;
|
|
498
601
|
this.kit.changed();
|
|
499
602
|
}
|
|
@@ -542,101 +645,102 @@ export class Conversations {
|
|
|
542
645
|
if (content !== null)
|
|
543
646
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
544
647
|
}
|
|
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
|
-
}
|
|
648
|
+
read(conversation, reader, message) {
|
|
649
|
+
for (const event of reader.read(message))
|
|
650
|
+
this.event(conversation, event);
|
|
570
651
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
652
|
+
event(conversation, event) {
|
|
653
|
+
switch (event.kind) {
|
|
654
|
+
case 'started':
|
|
655
|
+
this.started(conversation);
|
|
656
|
+
return;
|
|
657
|
+
case 'ended':
|
|
658
|
+
this.ended(conversation);
|
|
659
|
+
return;
|
|
660
|
+
case 'commands':
|
|
661
|
+
this.commands(conversation, event.commands);
|
|
662
|
+
return;
|
|
663
|
+
case 'options':
|
|
664
|
+
this.options(conversation, event.options);
|
|
665
|
+
return;
|
|
666
|
+
case 'context':
|
|
667
|
+
conversation.context = { used: event.used, window: event.window };
|
|
668
|
+
conversation.turn?.parts.measure(conversation.context);
|
|
669
|
+
return;
|
|
670
|
+
case 'subagent-done':
|
|
671
|
+
this.subagentDone(conversation, event.id, event.result);
|
|
672
|
+
return;
|
|
673
|
+
case 'marker':
|
|
674
|
+
case 'image':
|
|
675
|
+
if (conversation.turn !== null)
|
|
676
|
+
break;
|
|
677
|
+
if (event.kind === 'marker' && event.marker === 'job')
|
|
678
|
+
conversation.held.push({ type: 'marker', marker: 'job', text: event.text });
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
this.apply(conversation, this.started(conversation), event);
|
|
577
682
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
683
|
+
apply(conversation, turn, event) {
|
|
684
|
+
switch (event.kind) {
|
|
685
|
+
case 'text':
|
|
686
|
+
case 'reasoning':
|
|
687
|
+
turn.parts.write(event.kind, event.id, event.text);
|
|
688
|
+
return;
|
|
689
|
+
case 'command':
|
|
690
|
+
turn.parts.command();
|
|
691
|
+
return;
|
|
692
|
+
case 'subagent': {
|
|
693
|
+
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
694
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
case 'message':
|
|
698
|
+
turn.parts.start({ type: 'message', command: event.command, target: event.target });
|
|
699
|
+
return;
|
|
700
|
+
case 'marker':
|
|
701
|
+
turn.parts.start({ type: 'marker', marker: event.marker, text: event.text });
|
|
702
|
+
return;
|
|
703
|
+
case 'plan':
|
|
704
|
+
turn.parts.plan(event.entries);
|
|
705
|
+
return;
|
|
706
|
+
case 'image':
|
|
707
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
708
|
+
this.kit.changed();
|
|
709
|
+
return;
|
|
586
710
|
}
|
|
587
|
-
turn.segment ??= { messageId, phase, text: '' };
|
|
588
|
-
turn.segment.text += text;
|
|
589
711
|
}
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
if (
|
|
712
|
+
subagentDone(conversation, id, result) {
|
|
713
|
+
const subagent = conversation.subagents.get(id);
|
|
714
|
+
if (subagent === undefined)
|
|
593
715
|
return;
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
note(conversation, turn, text) {
|
|
598
|
-
if (text.trim() === '')
|
|
716
|
+
conversation.subagents.delete(id);
|
|
717
|
+
if (subagent.turn === conversation.turn) {
|
|
718
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
599
719
|
return;
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
720
|
+
}
|
|
721
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
722
|
+
if (conversation.turn === null)
|
|
723
|
+
conversation.held.push(part);
|
|
603
724
|
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;
|
|
725
|
+
conversation.turn.parts.start(part);
|
|
726
|
+
}
|
|
727
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
728
|
+
const bytes = Buffer.from(data, 'base64');
|
|
729
|
+
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
730
|
+
try {
|
|
731
|
+
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
732
|
+
name,
|
|
733
|
+
media_type: mediaType,
|
|
734
|
+
bytes: bytes.length,
|
|
735
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
736
|
+
}));
|
|
737
|
+
const saved = declared.upload === undefined ? declared : await uploadBytes(declared.upload, bytes);
|
|
738
|
+
if (saved.save.status !== 'saved')
|
|
739
|
+
throw new Error(saved.save.failure ?? saved.save.status);
|
|
740
|
+
turn.parts.arrive({ type: 'image', file: saved.version, name, media_type: mediaType }, reserved);
|
|
625
741
|
}
|
|
626
|
-
|
|
627
|
-
turn.
|
|
742
|
+
catch (error) {
|
|
743
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${causeOf(error)}` }, reserved);
|
|
628
744
|
}
|
|
629
745
|
}
|
|
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
746
|
}
|