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