@agentshouse/kit 0.2.1-ce145e9aadbf6 → 0.2.1-cf6326dfa5c3e
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 +151 -41
- package/dist/api.js +34 -2
- package/dist/app-git.js +48 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +36 -57
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +126 -69
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +457 -277
- package/dist/copies.js +198 -124
- 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 +63 -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 +3 -2
- 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 -28
- 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,121 @@
|
|
|
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 NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
35
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
36
|
+
class NotStarted extends Error {
|
|
37
|
+
}
|
|
38
|
+
async function step(reason, work) {
|
|
39
|
+
try {
|
|
40
|
+
return await work;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function signedOut(error) {
|
|
47
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
48
|
+
}
|
|
49
|
+
function unopened(reason) {
|
|
50
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
51
|
+
}
|
|
52
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
53
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
54
|
+
function unbridged(error) {
|
|
55
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
56
|
+
}
|
|
57
|
+
const EXIT_PLAN_MODE = {
|
|
58
|
+
parse(value) {
|
|
59
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
60
|
+
throw new Error('_x.ai/exit_plan_mode needs an object');
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
export const CONVERSATION_INPUTS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
17
66
|
function causeOf(error) {
|
|
18
67
|
return error instanceof Error ? error.message : String(error);
|
|
19
68
|
}
|
|
69
|
+
function answeredWith(error) {
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
71
|
+
}
|
|
72
|
+
function exitedWith(running, cause) {
|
|
73
|
+
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
74
|
+
}
|
|
20
75
|
function answerMessage(request, response) {
|
|
21
76
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
22
77
|
}
|
|
23
78
|
function logged(error) {
|
|
24
79
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
25
80
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
81
|
+
function refusal(conversation, error) {
|
|
82
|
+
if (conversation.kills > 0)
|
|
83
|
+
return KILLED;
|
|
84
|
+
const cause = error instanceof NotStarted ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
85
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
86
|
+
return causeOf(error);
|
|
87
|
+
}
|
|
88
|
+
function marker(bridge) {
|
|
89
|
+
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
28
90
|
}
|
|
29
91
|
class Turn {
|
|
30
92
|
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;
|
|
93
|
+
parts;
|
|
40
94
|
questions = 0;
|
|
41
95
|
secrets = 0;
|
|
42
|
-
|
|
96
|
+
prompted = false;
|
|
97
|
+
taken = null;
|
|
43
98
|
ended = false;
|
|
99
|
+
images = 0;
|
|
100
|
+
uploads = [];
|
|
101
|
+
constructor(emit) {
|
|
102
|
+
this.parts = new Parts(emit);
|
|
103
|
+
}
|
|
44
104
|
}
|
|
45
105
|
class Conversation {
|
|
46
106
|
id;
|
|
47
107
|
running = null;
|
|
108
|
+
session = null;
|
|
48
109
|
turn = null;
|
|
49
110
|
commands = null;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
111
|
+
context = null;
|
|
112
|
+
leftovers = new Set();
|
|
113
|
+
waiting = [];
|
|
114
|
+
sending = null;
|
|
115
|
+
subagents = new Map();
|
|
116
|
+
held = [];
|
|
53
117
|
opening = null;
|
|
118
|
+
prompted = null;
|
|
54
119
|
kills = 0;
|
|
55
120
|
killed = new AbortController();
|
|
56
121
|
interrupts = 0;
|
|
@@ -58,6 +123,11 @@ class Conversation {
|
|
|
58
123
|
open = 0;
|
|
59
124
|
queue = Promise.resolve();
|
|
60
125
|
reports = Promise.resolve();
|
|
126
|
+
starting = null;
|
|
127
|
+
preparing = null;
|
|
128
|
+
told = null;
|
|
129
|
+
untold = false;
|
|
130
|
+
shown = null;
|
|
61
131
|
constructor(id) {
|
|
62
132
|
this.id = id;
|
|
63
133
|
}
|
|
@@ -73,55 +143,76 @@ function asksSecret(request) {
|
|
|
73
143
|
export class Conversations {
|
|
74
144
|
kit;
|
|
75
145
|
conversations = new Map();
|
|
76
|
-
acks = new Map();
|
|
77
146
|
questions = new Map();
|
|
78
|
-
carrying = 0;
|
|
79
147
|
reporting = 0;
|
|
148
|
+
watched = false;
|
|
80
149
|
constructor(kit) {
|
|
81
150
|
this.kit = kit;
|
|
151
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
152
|
+
setInterval(() => this.look(), 2_000);
|
|
82
153
|
}
|
|
83
154
|
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();
|
|
155
|
+
void this.kit.inputs.carry(input.input_id, () => {
|
|
156
|
+
const conversation = this.conversation(input.conversation_id);
|
|
157
|
+
if (input.kind === 'kill') {
|
|
158
|
+
conversation.kills++;
|
|
159
|
+
conversation.killed.abort(new Error(KILLED));
|
|
160
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
161
|
+
waiting.settle({ refused: KILLED });
|
|
162
|
+
this.halt(conversation);
|
|
163
|
+
}
|
|
164
|
+
if (input.kind === 'interrupt') {
|
|
165
|
+
conversation.interrupts++;
|
|
166
|
+
conversation.interrupted.abort();
|
|
167
|
+
}
|
|
168
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
169
|
+
conversation.queue = handled.catch(() => undefined);
|
|
170
|
+
return handled.then(async ({ ack }) => ({ ack: await ack }));
|
|
108
171
|
});
|
|
109
172
|
}
|
|
110
173
|
idle() {
|
|
111
|
-
if (this.
|
|
174
|
+
if (this.reporting > 0)
|
|
112
175
|
return false;
|
|
113
176
|
for (const conversation of this.conversations.values()) {
|
|
114
177
|
const turn = conversation.turn;
|
|
115
178
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
116
|
-
if (conversation.
|
|
179
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
117
180
|
return false;
|
|
118
181
|
}
|
|
119
|
-
|
|
182
|
+
const conversations = [...this.conversations.values()];
|
|
183
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
184
|
+
return false;
|
|
185
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
186
|
+
}
|
|
187
|
+
quiet() {
|
|
188
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
|
|
189
|
+
}
|
|
190
|
+
look() {
|
|
191
|
+
for (const conversation of this.conversations.values()) {
|
|
192
|
+
for (const scope of conversation.leftovers) {
|
|
193
|
+
if (!scope.empty())
|
|
194
|
+
continue;
|
|
195
|
+
scope.close();
|
|
196
|
+
conversation.leftovers.delete(scope);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
this.kit.changed();
|
|
120
200
|
}
|
|
121
201
|
opened() {
|
|
202
|
+
this.watched = false;
|
|
203
|
+
for (const conversation of this.conversations.values()) {
|
|
204
|
+
if (conversation.untold || conversation.told !== null)
|
|
205
|
+
this.retell(conversation);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
watching(watched) {
|
|
209
|
+
const resend = watched && !this.watched;
|
|
210
|
+
this.watched = watched;
|
|
211
|
+
if (!resend)
|
|
212
|
+
return;
|
|
122
213
|
for (const conversation of this.conversations.values()) {
|
|
123
214
|
if (conversation.turn !== null)
|
|
124
|
-
conversation.turn
|
|
215
|
+
this.replay(conversation, conversation.turn);
|
|
125
216
|
}
|
|
126
217
|
}
|
|
127
218
|
conversation(id) {
|
|
@@ -134,7 +225,7 @@ export class Conversations {
|
|
|
134
225
|
}
|
|
135
226
|
async carry(conversation, input) {
|
|
136
227
|
if (input.kind === 'open')
|
|
137
|
-
return this.open(conversation, input);
|
|
228
|
+
return { ack: Promise.resolve(await this.open(conversation, input)) };
|
|
138
229
|
if (input.kind === 'message')
|
|
139
230
|
return this.message(conversation, input);
|
|
140
231
|
if (input.kind === 'interrupt') {
|
|
@@ -144,6 +235,7 @@ export class Conversations {
|
|
|
144
235
|
}
|
|
145
236
|
if (input.kind === 'kill') {
|
|
146
237
|
await this.kill(conversation);
|
|
238
|
+
await conversation.sending;
|
|
147
239
|
if (--conversation.kills === 0)
|
|
148
240
|
conversation.killed = new AbortController();
|
|
149
241
|
}
|
|
@@ -152,54 +244,133 @@ export class Conversations {
|
|
|
152
244
|
if (input.kind === 'answer') {
|
|
153
245
|
const question = this.questions.get(String(input.interaction_id));
|
|
154
246
|
if (question === undefined) {
|
|
155
|
-
|
|
247
|
+
const text = answerMessage(input.request, input.response);
|
|
248
|
+
return this.message(conversation, { ...input, text, files: [], first: false });
|
|
156
249
|
}
|
|
157
250
|
question.answer(input.response);
|
|
158
251
|
}
|
|
159
|
-
return {};
|
|
252
|
+
return { ack: Promise.resolve({}) };
|
|
160
253
|
}
|
|
161
254
|
async open(conversation, input) {
|
|
162
255
|
if (conversation.running !== null)
|
|
163
256
|
return {};
|
|
257
|
+
this.prepares(conversation, 'session');
|
|
164
258
|
try {
|
|
165
|
-
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null) };
|
|
259
|
+
return { provider_session_id: await this.start(conversation, await this.route(conversation, input.agent_id), null, true) };
|
|
166
260
|
}
|
|
167
261
|
catch (error) {
|
|
168
|
-
return { refused:
|
|
262
|
+
return { refused: refusal(conversation, error) };
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
this.prepares(conversation, null);
|
|
169
266
|
}
|
|
170
267
|
}
|
|
171
268
|
async message(conversation, input) {
|
|
172
|
-
|
|
173
|
-
|
|
269
|
+
const content = [{ type: 'text', text: String(input.text) }];
|
|
270
|
+
let route;
|
|
174
271
|
try {
|
|
175
|
-
|
|
272
|
+
route = await this.route(conversation, input.agent_id);
|
|
176
273
|
const files = input.files;
|
|
274
|
+
if (files.length > 0)
|
|
275
|
+
this.prepares(conversation, 'files');
|
|
177
276
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
178
277
|
const lines = await this.transcribed(conversation, files, paths);
|
|
278
|
+
if (lines.length > 0)
|
|
279
|
+
content.push({ type: 'text', text: lines.join('\n') });
|
|
280
|
+
if (conversation.kills > 0)
|
|
281
|
+
throw new Error(KILLED);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
this.prepares(conversation, null);
|
|
285
|
+
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
286
|
+
}
|
|
287
|
+
conversation.preparing = null;
|
|
288
|
+
return {
|
|
289
|
+
ack: new Promise((settle) => {
|
|
290
|
+
conversation.waiting.push({ input, route, content, settle });
|
|
291
|
+
this.next(conversation);
|
|
292
|
+
}),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
next(conversation) {
|
|
296
|
+
if (conversation.sending !== null || conversation.open > 0)
|
|
297
|
+
return;
|
|
298
|
+
const waiting = conversation.waiting.shift();
|
|
299
|
+
if (waiting === undefined) {
|
|
300
|
+
if (conversation.starting !== conversation.preparing)
|
|
301
|
+
this.starts(conversation, conversation.preparing);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
305
|
+
conversation.sending = null;
|
|
306
|
+
this.next(conversation);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
async send(conversation, waiting) {
|
|
310
|
+
const { input } = waiting;
|
|
311
|
+
let ack = {};
|
|
312
|
+
let started = false;
|
|
313
|
+
const prompt = [];
|
|
314
|
+
try {
|
|
179
315
|
if (conversation.running === null) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
316
|
+
this.starts(conversation, 'session');
|
|
317
|
+
const stored = conversation.session ?? input.provider_session_id;
|
|
318
|
+
const empty = input.first === true && conversation.prompted !== stored;
|
|
319
|
+
started = (await this.start(conversation, waiting.route, stored, empty)) !== stored;
|
|
320
|
+
}
|
|
321
|
+
const session = conversation.running.sessionId;
|
|
322
|
+
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
323
|
+
ack = { provider_session_id: session };
|
|
183
324
|
}
|
|
184
325
|
if (input.first === true) {
|
|
185
326
|
prompt.push({
|
|
186
327
|
type: 'text',
|
|
187
|
-
text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
|
|
328
|
+
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
188
329
|
});
|
|
189
330
|
}
|
|
190
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
191
|
-
if (lines.length > 0)
|
|
192
|
-
prompt.push({ type: 'text', text: lines.join('\n') });
|
|
193
331
|
if (conversation.kills > 0)
|
|
194
332
|
throw new Error(KILLED);
|
|
195
333
|
}
|
|
196
334
|
catch (error) {
|
|
197
|
-
|
|
335
|
+
this.starts(conversation, null);
|
|
336
|
+
if (started) {
|
|
337
|
+
conversation.session = null;
|
|
198
338
|
await this.kill(conversation);
|
|
199
|
-
|
|
339
|
+
}
|
|
340
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
this.starts(conversation, null);
|
|
344
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.inputs.hands(String(input.input_id), ack));
|
|
345
|
+
waiting.settle(ack);
|
|
346
|
+
}
|
|
347
|
+
starts(conversation, step) {
|
|
348
|
+
conversation.starting = step;
|
|
349
|
+
if (conversation.shown !== null)
|
|
350
|
+
clearTimeout(conversation.shown);
|
|
351
|
+
conversation.shown = null;
|
|
352
|
+
if (step === null || conversation.told !== null) {
|
|
353
|
+
this.tell(conversation, step);
|
|
354
|
+
return;
|
|
200
355
|
}
|
|
201
|
-
|
|
202
|
-
|
|
356
|
+
conversation.shown = setTimeout(() => {
|
|
357
|
+
conversation.shown = null;
|
|
358
|
+
if (conversation.starting === step)
|
|
359
|
+
this.tell(conversation, step);
|
|
360
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
361
|
+
}
|
|
362
|
+
prepares(conversation, step) {
|
|
363
|
+
conversation.preparing = step;
|
|
364
|
+
this.next(conversation);
|
|
365
|
+
}
|
|
366
|
+
tell(conversation, step) {
|
|
367
|
+
if (conversation.told === step && !conversation.untold)
|
|
368
|
+
return;
|
|
369
|
+
conversation.told = step;
|
|
370
|
+
this.retell(conversation);
|
|
371
|
+
}
|
|
372
|
+
retell(conversation) {
|
|
373
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
203
374
|
}
|
|
204
375
|
async transcribed(conversation, files, paths) {
|
|
205
376
|
const lines = [];
|
|
@@ -207,9 +378,10 @@ export class Conversations {
|
|
|
207
378
|
lines.push(path);
|
|
208
379
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
209
380
|
continue;
|
|
381
|
+
this.prepares(conversation, 'transcription');
|
|
210
382
|
try {
|
|
211
383
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
212
|
-
if (text !== '')
|
|
384
|
+
if (text !== null && text !== '')
|
|
213
385
|
lines.push(`Transcript: ${text}`);
|
|
214
386
|
}
|
|
215
387
|
catch (error) {
|
|
@@ -229,11 +401,13 @@ export class Conversations {
|
|
|
229
401
|
}
|
|
230
402
|
halt(conversation) {
|
|
231
403
|
if (conversation.opening !== null)
|
|
232
|
-
|
|
404
|
+
killMarked(marker(conversation.opening));
|
|
405
|
+
for (const scope of conversation.leftovers)
|
|
406
|
+
scope.kill();
|
|
233
407
|
if (conversation.running === null)
|
|
234
408
|
return;
|
|
235
409
|
conversation.running.killed = true;
|
|
236
|
-
|
|
410
|
+
conversation.running.scope.kill();
|
|
237
411
|
}
|
|
238
412
|
async kill(conversation) {
|
|
239
413
|
const running = conversation.running;
|
|
@@ -245,6 +419,7 @@ export class Conversations {
|
|
|
245
419
|
if (running === null)
|
|
246
420
|
return;
|
|
247
421
|
try {
|
|
422
|
+
running.reader.requested(option, value);
|
|
248
423
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
249
424
|
sessionId: running.sessionId,
|
|
250
425
|
configId: option,
|
|
@@ -253,6 +428,7 @@ export class Conversations {
|
|
|
253
428
|
this.options(conversation, answered.configOptions);
|
|
254
429
|
}
|
|
255
430
|
catch (error) {
|
|
431
|
+
running.reader.refused(option);
|
|
256
432
|
logged(error);
|
|
257
433
|
}
|
|
258
434
|
}
|
|
@@ -269,50 +445,58 @@ export class Conversations {
|
|
|
269
445
|
}
|
|
270
446
|
return route;
|
|
271
447
|
}
|
|
272
|
-
async start(conversation, route, sessionId) {
|
|
448
|
+
async start(conversation, route, sessionId, empty) {
|
|
449
|
+
const reader = readerFor(route.kind);
|
|
450
|
+
reader.session = sessionId;
|
|
273
451
|
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
452
|
.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))
|
|
453
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
454
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
279
455
|
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);
|
|
456
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
281
457
|
if (conversation.kills > 0) {
|
|
282
458
|
bridge.close();
|
|
283
459
|
throw new Error(KILLED);
|
|
284
460
|
}
|
|
285
461
|
conversation.opening = bridge;
|
|
462
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
463
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
464
|
+
let adapter = null;
|
|
286
465
|
try {
|
|
287
466
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
288
|
-
await CLIS[route.kind].allowHouse();
|
|
289
|
-
const
|
|
467
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
468
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
469
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
470
|
+
work.read(message);
|
|
471
|
+
this.read(conversation, reader, message);
|
|
472
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
290
473
|
void adapter.exited.then(() => bridge.close());
|
|
291
474
|
const agent = adapter.connection.agent;
|
|
292
475
|
const cwd = route.working_directory;
|
|
293
|
-
const _meta = CLIS[route.kind].sessionMeta;
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
};
|
|
305
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
476
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
477
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
478
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
479
|
+
reader.session = opened.sessionId;
|
|
480
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
481
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
482
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
483
|
+
}
|
|
484
|
+
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
485
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
486
|
+
void work.settled().then(() => scope.record());
|
|
306
487
|
conversation.running = running;
|
|
488
|
+
conversation.session = running.sessionId;
|
|
307
489
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: true });
|
|
308
490
|
if (conversation.commands !== null)
|
|
309
491
|
this.commands(conversation, conversation.commands);
|
|
310
492
|
this.options(conversation, options);
|
|
311
|
-
void adapter.
|
|
493
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
312
494
|
return running.sessionId;
|
|
313
495
|
}
|
|
314
496
|
catch (error) {
|
|
315
|
-
|
|
497
|
+
adapter?.retire();
|
|
498
|
+
work.close();
|
|
499
|
+
conversation.leftovers.add(scope);
|
|
316
500
|
bridge.close();
|
|
317
501
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
318
502
|
}
|
|
@@ -320,6 +504,18 @@ export class Conversations {
|
|
|
320
504
|
conversation.opening = null;
|
|
321
505
|
}
|
|
322
506
|
}
|
|
507
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
508
|
+
try {
|
|
509
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
513
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
514
|
+
}
|
|
515
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
323
519
|
async launchSettings(running, route, offered) {
|
|
324
520
|
let options = offered;
|
|
325
521
|
const settings = [
|
|
@@ -331,6 +527,7 @@ export class Conversations {
|
|
|
331
527
|
if (value === null)
|
|
332
528
|
continue;
|
|
333
529
|
const option = options.find((candidate) => candidate.category === category);
|
|
530
|
+
running.reader.requested(option?.id ?? category, value);
|
|
334
531
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
335
532
|
sessionId: running.sessionId,
|
|
336
533
|
configId: option?.id ?? category,
|
|
@@ -340,23 +537,24 @@ export class Conversations {
|
|
|
340
537
|
}
|
|
341
538
|
return options;
|
|
342
539
|
}
|
|
343
|
-
exited(conversation,
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
|
|
540
|
+
exited(conversation, running, cause) {
|
|
541
|
+
this.detach(conversation, running);
|
|
542
|
+
if (conversation.turn !== null) {
|
|
543
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
544
|
+
}
|
|
545
|
+
this.next(conversation);
|
|
546
|
+
this.kit.changed();
|
|
547
|
+
}
|
|
548
|
+
detach(conversation, running) {
|
|
549
|
+
running.work.close();
|
|
550
|
+
conversation.leftovers.add(running.scope);
|
|
347
551
|
conversation.running = null;
|
|
348
552
|
conversation.commands = null;
|
|
349
|
-
conversation.jobs.clear();
|
|
350
|
-
conversation.queued.length = 0;
|
|
351
553
|
for (const [id, question] of this.questions) {
|
|
352
554
|
if (question.conversation === conversation)
|
|
353
555
|
this.questions.delete(id);
|
|
354
556
|
}
|
|
355
557
|
this.kit.send({ type: 'process', conversation_id: conversation.id, running: false });
|
|
356
|
-
if (conversation.turn !== null) {
|
|
357
|
-
this.end(conversation, conversation.turn, { failed: running.killed ? KILLED : cause });
|
|
358
|
-
}
|
|
359
|
-
this.kit.changed();
|
|
360
558
|
}
|
|
361
559
|
commands(conversation, commands) {
|
|
362
560
|
conversation.commands = commands;
|
|
@@ -371,7 +569,7 @@ export class Conversations {
|
|
|
371
569
|
}
|
|
372
570
|
deliver(conversation, turn, event, body) {
|
|
373
571
|
this.reporting++;
|
|
374
|
-
const delivered = conversation.reports.then(() => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, body));
|
|
572
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
375
573
|
conversation.reports = delivered
|
|
376
574
|
.catch(() => undefined)
|
|
377
575
|
.finally(() => {
|
|
@@ -380,119 +578,95 @@ export class Conversations {
|
|
|
380
578
|
});
|
|
381
579
|
return delivered;
|
|
382
580
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
.then(() => this.admitted(conversation, turn))
|
|
388
|
-
.catch(logged);
|
|
389
|
-
return turn;
|
|
581
|
+
write(conversation, turn, operation) {
|
|
582
|
+
if (turn.ended || !this.watched)
|
|
583
|
+
return;
|
|
584
|
+
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
390
585
|
}
|
|
391
|
-
|
|
392
|
-
turn.
|
|
393
|
-
|
|
394
|
-
this.kit.send(note);
|
|
395
|
-
if (turn.text !== '') {
|
|
396
|
-
turn.unsentFrom = 0;
|
|
397
|
-
this.draft(conversation, turn);
|
|
398
|
-
}
|
|
399
|
-
if (turn.entries !== null)
|
|
400
|
-
this.plan(conversation, turn);
|
|
586
|
+
replay(conversation, turn) {
|
|
587
|
+
for (const operation of turn.parts.operations())
|
|
588
|
+
this.write(conversation, turn, operation);
|
|
401
589
|
}
|
|
402
590
|
begin(conversation) {
|
|
403
|
-
const turn = this.
|
|
591
|
+
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
592
|
+
conversation.open++;
|
|
593
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
404
594
|
conversation.turn = turn;
|
|
595
|
+
if (conversation.context !== null)
|
|
596
|
+
turn.parts.measure(conversation.context);
|
|
597
|
+
for (const part of conversation.held.splice(0))
|
|
598
|
+
turn.parts.start(part);
|
|
405
599
|
this.kit.changed();
|
|
406
600
|
return turn;
|
|
407
601
|
}
|
|
408
|
-
|
|
409
|
-
if (turn.ended)
|
|
410
|
-
return;
|
|
411
|
-
const segment = turn.segment;
|
|
412
|
-
turn.segment = null;
|
|
413
|
-
if (segment?.phase === 'note')
|
|
414
|
-
this.note(conversation, turn, segment.text);
|
|
415
|
-
else if (segment !== null)
|
|
416
|
-
this.answer(conversation, turn, segment.text);
|
|
417
|
-
this.end(conversation, turn, { text: turn.text });
|
|
418
|
-
}
|
|
419
|
-
end(conversation, turn, outcome) {
|
|
602
|
+
end(conversation, turn, failed, notice = null) {
|
|
420
603
|
if (turn.ended)
|
|
421
604
|
return;
|
|
605
|
+
if (failed !== null)
|
|
606
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
607
|
+
if (conversation.turn === turn) {
|
|
608
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
609
|
+
this.apply(conversation, turn, event);
|
|
610
|
+
}
|
|
611
|
+
if (notice !== null)
|
|
612
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
422
613
|
turn.ended = true;
|
|
423
614
|
conversation.open--;
|
|
424
615
|
if (conversation.turn === turn)
|
|
425
616
|
conversation.turn = null;
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
617
|
+
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
618
|
+
parts: turn.parts.saved(),
|
|
619
|
+
context: turn.parts.context,
|
|
620
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
621
|
+
}));
|
|
622
|
+
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
623
|
+
this.next(conversation);
|
|
430
624
|
this.kit.changed();
|
|
431
625
|
}
|
|
432
|
-
|
|
433
|
-
if (conversation.open > 0)
|
|
434
|
-
conversation.late.push(cause);
|
|
435
|
-
else
|
|
436
|
-
this.end(conversation, this.report(conversation), { failed: cause });
|
|
437
|
-
}
|
|
438
|
-
prompt(conversation, prompt) {
|
|
626
|
+
prompt(conversation, prompt, taken) {
|
|
439
627
|
const running = conversation.running;
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
this.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
else
|
|
451
|
-
this.end(conversation,
|
|
628
|
+
const turn = this.begin(conversation);
|
|
629
|
+
turn.prompted = true;
|
|
630
|
+
turn.taken = taken;
|
|
631
|
+
conversation.prompted = running.sessionId;
|
|
632
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
633
|
+
this.took(turn);
|
|
634
|
+
const notice = answeredFailure(answer);
|
|
635
|
+
if (notice !== null) {
|
|
636
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
640
|
+
}
|
|
641
|
+
}, async (error) => {
|
|
642
|
+
if (running.adapter.connection.signal.aborted) {
|
|
643
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
this.took(turn);
|
|
647
|
+
if (conversation.open === 1 && conversation.running === running) {
|
|
648
|
+
running.adapter.retire();
|
|
649
|
+
this.detach(conversation, running);
|
|
650
|
+
}
|
|
651
|
+
this.end(conversation, turn, answeredWith(error));
|
|
452
652
|
}
|
|
453
653
|
});
|
|
454
654
|
}
|
|
455
|
-
attach(turn, sent) {
|
|
456
|
-
turn.prompt = sent;
|
|
457
|
-
sent.turn = turn;
|
|
458
|
-
}
|
|
459
|
-
answered(conversation, sent, failed) {
|
|
460
|
-
const turn = sent.turn;
|
|
461
|
-
if (turn === null) {
|
|
462
|
-
const queued = conversation.queued.indexOf(sent);
|
|
463
|
-
if (queued < 0)
|
|
464
|
-
return;
|
|
465
|
-
conversation.queued.splice(queued, 1);
|
|
466
|
-
if (failed !== null)
|
|
467
|
-
this.failed(conversation, failed);
|
|
468
|
-
}
|
|
469
|
-
else if (failed !== null) {
|
|
470
|
-
if (turn.ended)
|
|
471
|
-
this.failed(conversation, failed);
|
|
472
|
-
else
|
|
473
|
-
this.end(conversation, turn, { failed });
|
|
474
|
-
}
|
|
475
|
-
else if (turn.prompt === sent) {
|
|
476
|
-
this.finish(conversation, turn);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
655
|
started(conversation) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const turn = this.begin(conversation);
|
|
483
|
-
const queued = conversation.queued.shift();
|
|
484
|
-
if (queued !== undefined)
|
|
485
|
-
this.attach(turn, queued);
|
|
656
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
657
|
+
this.took(turn);
|
|
486
658
|
return turn;
|
|
487
659
|
}
|
|
488
660
|
ended(conversation) {
|
|
489
661
|
const turn = conversation.turn;
|
|
490
662
|
if (turn === null)
|
|
491
663
|
return;
|
|
492
|
-
if (turn.
|
|
493
|
-
this.
|
|
664
|
+
if (!turn.prompted) {
|
|
665
|
+
this.end(conversation, turn, null);
|
|
494
666
|
}
|
|
495
667
|
else {
|
|
668
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
669
|
+
this.apply(conversation, turn, event);
|
|
496
670
|
conversation.turn = null;
|
|
497
671
|
this.kit.changed();
|
|
498
672
|
}
|
|
@@ -541,101 +715,107 @@ export class Conversations {
|
|
|
541
715
|
if (content !== null)
|
|
542
716
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
543
717
|
}
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
718
|
+
read(conversation, reader, message) {
|
|
719
|
+
for (const event of reader.read(message))
|
|
720
|
+
this.event(conversation, event);
|
|
721
|
+
}
|
|
722
|
+
event(conversation, event) {
|
|
723
|
+
switch (event.kind) {
|
|
724
|
+
case 'started':
|
|
725
|
+
this.started(conversation);
|
|
726
|
+
return;
|
|
727
|
+
case 'ended':
|
|
728
|
+
this.ended(conversation);
|
|
729
|
+
return;
|
|
730
|
+
case 'commands':
|
|
731
|
+
this.commands(conversation, event.commands);
|
|
732
|
+
return;
|
|
733
|
+
case 'options':
|
|
734
|
+
this.options(conversation, event.options);
|
|
735
|
+
return;
|
|
736
|
+
case 'context':
|
|
737
|
+
conversation.context = { used: event.used, window: event.window };
|
|
738
|
+
conversation.turn?.parts.measure(conversation.context);
|
|
739
|
+
return;
|
|
740
|
+
case 'subagent-done':
|
|
741
|
+
this.subagentDone(conversation, event.id, event.result);
|
|
742
|
+
return;
|
|
743
|
+
case 'marker':
|
|
744
|
+
case 'image':
|
|
745
|
+
if (conversation.turn !== null)
|
|
746
|
+
break;
|
|
747
|
+
if (event.kind === 'marker' && event.marker === 'job')
|
|
748
|
+
conversation.held.push({ type: 'marker', marker: 'job', text: event.text });
|
|
749
|
+
return;
|
|
568
750
|
}
|
|
751
|
+
this.apply(conversation, this.started(conversation), event);
|
|
569
752
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
conversation.jobs.delete(job.id);
|
|
575
|
-
this.kit.changed();
|
|
753
|
+
took(turn) {
|
|
754
|
+
const taken = turn.taken;
|
|
755
|
+
turn.taken = null;
|
|
756
|
+
taken?.();
|
|
576
757
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
758
|
+
apply(conversation, turn, event) {
|
|
759
|
+
switch (event.kind) {
|
|
760
|
+
case 'text':
|
|
761
|
+
case 'reasoning':
|
|
762
|
+
turn.parts.write(event.kind, event.id, event.text);
|
|
763
|
+
return;
|
|
764
|
+
case 'command':
|
|
765
|
+
turn.parts.command();
|
|
766
|
+
return;
|
|
767
|
+
case 'subagent': {
|
|
768
|
+
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
769
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
case 'message':
|
|
773
|
+
turn.parts.start({ type: 'message', command: event.command, target: event.target });
|
|
774
|
+
return;
|
|
775
|
+
case 'marker':
|
|
776
|
+
turn.parts.start({ type: 'marker', marker: event.marker, text: event.text });
|
|
777
|
+
return;
|
|
778
|
+
case 'plan':
|
|
779
|
+
turn.parts.plan(event.entries);
|
|
780
|
+
return;
|
|
781
|
+
case 'image':
|
|
782
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
783
|
+
this.kit.changed();
|
|
784
|
+
return;
|
|
585
785
|
}
|
|
586
|
-
turn.segment ??= { messageId, phase, text: '' };
|
|
587
|
-
turn.segment.text += text;
|
|
588
786
|
}
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
if (
|
|
787
|
+
subagentDone(conversation, id, result) {
|
|
788
|
+
const subagent = conversation.subagents.get(id);
|
|
789
|
+
if (subagent === undefined)
|
|
592
790
|
return;
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
note(conversation, turn, text) {
|
|
597
|
-
if (text.trim() === '')
|
|
791
|
+
conversation.subagents.delete(id);
|
|
792
|
+
if (subagent.turn === conversation.turn) {
|
|
793
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
598
794
|
return;
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
795
|
+
}
|
|
796
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
797
|
+
if (conversation.turn === null)
|
|
798
|
+
conversation.held.push(part);
|
|
602
799
|
else
|
|
603
|
-
turn.
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
});
|
|
620
|
-
if (sent) {
|
|
621
|
-
turn.draftSequence++;
|
|
622
|
-
turn.sent = blocks;
|
|
623
|
-
turn.unsentFrom = null;
|
|
800
|
+
conversation.turn.parts.start(part);
|
|
801
|
+
}
|
|
802
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
803
|
+
const bytes = Buffer.from(data, 'base64');
|
|
804
|
+
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
805
|
+
try {
|
|
806
|
+
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
807
|
+
name,
|
|
808
|
+
media_type: mediaType,
|
|
809
|
+
bytes: bytes.length,
|
|
810
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
811
|
+
}));
|
|
812
|
+
const saved = declared.upload === undefined ? declared : await uploadBytes(declared.upload, bytes);
|
|
813
|
+
if (saved.save.status !== 'saved')
|
|
814
|
+
throw new Error(saved.save.failure ?? saved.save.status);
|
|
815
|
+
turn.parts.arrive({ type: 'image', file: saved.version, name, media_type: mediaType }, reserved);
|
|
624
816
|
}
|
|
625
|
-
|
|
626
|
-
turn.
|
|
817
|
+
catch (error) {
|
|
818
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${causeOf(error)}` }, reserved);
|
|
627
819
|
}
|
|
628
820
|
}
|
|
629
|
-
plan(conversation, turn) {
|
|
630
|
-
const sent = this.kit.send({
|
|
631
|
-
type: 'plan',
|
|
632
|
-
conversation_id: conversation.id,
|
|
633
|
-
turn_id: turn.id,
|
|
634
|
-
sequence: turn.planSequence + 1,
|
|
635
|
-
from: 0,
|
|
636
|
-
steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
637
|
-
});
|
|
638
|
-
if (sent)
|
|
639
|
-
turn.planSequence++;
|
|
640
|
-
}
|
|
641
821
|
}
|