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