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