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