@agentshouse/kit 0.2.1-cb6679d048e60 → 0.2.1-cbdf76b9a90a3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -31
- package/dist/acp.js +46 -103
- package/dist/agents.js +178 -49
- package/dist/api.js +36 -3
- package/dist/app-git.js +48 -0
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +39 -58
- package/dist/chatgpt.js +32 -0
- package/dist/clis.js +137 -65
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +525 -287
- package/dist/copies.js +215 -146
- package/dist/end.js +27 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +525 -0
- package/dist/files.js +24 -3
- package/dist/git.js +10 -8
- package/dist/home.js +96 -7
- package/dist/house-cli.js +291 -0
- package/dist/house-main.js +7 -159
- package/dist/inputs.js +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/mcp.js +2 -1
- package/dist/parts.js +82 -0
- package/dist/placement.js +39 -0
- package/dist/refusals.js +12 -0
- package/dist/relay.js +20 -0
- package/dist/resident.js +91 -35
- package/dist/scope.js +206 -0
- package/dist/secret-input.js +4 -1
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +34 -27
- package/dist/skills.js +81 -12
- package/dist/stream.js +89 -12
- package/dist/transcription.js +57 -9
- package/dist/translate.js +2 -8
- package/dist/update.js +50 -29
- package/dist/wire.js +22 -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,124 @@
|
|
|
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
|
+
options = null;
|
|
112
|
+
exitUntold = false;
|
|
113
|
+
context = null;
|
|
114
|
+
leftovers = new Set();
|
|
115
|
+
waiting = [];
|
|
116
|
+
sending = null;
|
|
117
|
+
subagents = new Map();
|
|
118
|
+
held = [];
|
|
53
119
|
opening = null;
|
|
120
|
+
launch = null;
|
|
121
|
+
prompted = null;
|
|
54
122
|
kills = 0;
|
|
55
123
|
killed = new AbortController();
|
|
56
124
|
interrupts = 0;
|
|
@@ -58,6 +126,11 @@ class Conversation {
|
|
|
58
126
|
open = 0;
|
|
59
127
|
queue = Promise.resolve();
|
|
60
128
|
reports = Promise.resolve();
|
|
129
|
+
starting = null;
|
|
130
|
+
preparing = null;
|
|
131
|
+
told = null;
|
|
132
|
+
untold = false;
|
|
133
|
+
shown = null;
|
|
61
134
|
constructor(id) {
|
|
62
135
|
this.id = id;
|
|
63
136
|
}
|
|
@@ -73,55 +146,80 @@ function asksSecret(request) {
|
|
|
73
146
|
export class Conversations {
|
|
74
147
|
kit;
|
|
75
148
|
conversations = new Map();
|
|
76
|
-
acks = new Map();
|
|
77
149
|
questions = new Map();
|
|
78
|
-
carrying = 0;
|
|
79
150
|
reporting = 0;
|
|
151
|
+
watched = false;
|
|
80
152
|
constructor(kit) {
|
|
81
153
|
this.kit = kit;
|
|
154
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
155
|
+
setInterval(() => this.look(), 2_000);
|
|
82
156
|
}
|
|
83
157
|
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();
|
|
158
|
+
void this.kit.inputs.carry(input.input_id, () => {
|
|
159
|
+
const conversation = this.conversation(input.conversation_id);
|
|
160
|
+
if (input.kind === 'kill') {
|
|
161
|
+
conversation.kills++;
|
|
162
|
+
conversation.killed.abort(new Error(KILLED));
|
|
163
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
164
|
+
waiting.settle({ refused: KILLED });
|
|
165
|
+
this.halt(conversation);
|
|
166
|
+
}
|
|
167
|
+
if (input.kind === 'interrupt') {
|
|
168
|
+
conversation.interrupts++;
|
|
169
|
+
conversation.interrupted.abort();
|
|
170
|
+
}
|
|
171
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
172
|
+
conversation.queue = handled.catch(() => undefined);
|
|
173
|
+
return handled.then(async ({ ack }) => ({ ack: await ack }));
|
|
108
174
|
});
|
|
109
175
|
}
|
|
110
176
|
idle() {
|
|
111
|
-
if (this.
|
|
177
|
+
if (this.reporting > 0)
|
|
112
178
|
return false;
|
|
113
179
|
for (const conversation of this.conversations.values()) {
|
|
114
180
|
const turn = conversation.turn;
|
|
115
181
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
116
|
-
if (conversation.
|
|
182
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
117
183
|
return false;
|
|
118
184
|
}
|
|
119
|
-
|
|
185
|
+
const conversations = [...this.conversations.values()];
|
|
186
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
187
|
+
return false;
|
|
188
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
189
|
+
}
|
|
190
|
+
quiet() {
|
|
191
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
|
|
192
|
+
}
|
|
193
|
+
look() {
|
|
194
|
+
for (const conversation of this.conversations.values()) {
|
|
195
|
+
for (const scope of conversation.leftovers) {
|
|
196
|
+
if (!scope.empty())
|
|
197
|
+
continue;
|
|
198
|
+
scope.close();
|
|
199
|
+
conversation.leftovers.delete(scope);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
this.kit.changed();
|
|
120
203
|
}
|
|
121
204
|
opened() {
|
|
205
|
+
this.watched = false;
|
|
206
|
+
for (const conversation of this.conversations.values()) {
|
|
207
|
+
if (conversation.running !== null)
|
|
208
|
+
this.facts(conversation);
|
|
209
|
+
else if (conversation.exitUntold)
|
|
210
|
+
this.process(conversation, false);
|
|
211
|
+
if (conversation.untold || conversation.told !== null)
|
|
212
|
+
this.retell(conversation);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
watching(watched) {
|
|
216
|
+
const resend = watched && !this.watched;
|
|
217
|
+
this.watched = watched;
|
|
218
|
+
if (!resend)
|
|
219
|
+
return;
|
|
122
220
|
for (const conversation of this.conversations.values()) {
|
|
123
221
|
if (conversation.turn !== null)
|
|
124
|
-
conversation.turn
|
|
222
|
+
this.replay(conversation, conversation.turn);
|
|
125
223
|
}
|
|
126
224
|
}
|
|
127
225
|
conversation(id) {
|
|
@@ -134,9 +232,10 @@ export class Conversations {
|
|
|
134
232
|
}
|
|
135
233
|
async carry(conversation, input) {
|
|
136
234
|
if (input.kind === 'open')
|
|
137
|
-
return this.open(conversation, input);
|
|
235
|
+
return { ack: this.open(conversation, input) };
|
|
138
236
|
if (input.kind === 'message')
|
|
139
237
|
return this.message(conversation, input);
|
|
238
|
+
await this.launched(conversation);
|
|
140
239
|
if (input.kind === 'interrupt') {
|
|
141
240
|
if (--conversation.interrupts === 0)
|
|
142
241
|
conversation.interrupted = new AbortController();
|
|
@@ -144,6 +243,7 @@ export class Conversations {
|
|
|
144
243
|
}
|
|
145
244
|
if (input.kind === 'kill') {
|
|
146
245
|
await this.kill(conversation);
|
|
246
|
+
await conversation.sending;
|
|
147
247
|
if (--conversation.kills === 0)
|
|
148
248
|
conversation.killed = new AbortController();
|
|
149
249
|
}
|
|
@@ -152,54 +252,168 @@ export class Conversations {
|
|
|
152
252
|
if (input.kind === 'answer') {
|
|
153
253
|
const question = this.questions.get(String(input.interaction_id));
|
|
154
254
|
if (question === undefined) {
|
|
155
|
-
|
|
255
|
+
const text = answerMessage(input.request, input.response);
|
|
256
|
+
return this.message(conversation, { ...input, text, files: [], first: false });
|
|
156
257
|
}
|
|
157
258
|
question.answer(input.response);
|
|
158
259
|
}
|
|
159
|
-
return {};
|
|
260
|
+
return { ack: Promise.resolve({}) };
|
|
160
261
|
}
|
|
161
|
-
|
|
262
|
+
open(conversation, input) {
|
|
162
263
|
if (conversation.running !== null)
|
|
163
|
-
return {};
|
|
264
|
+
return Promise.resolve({});
|
|
265
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, input.agent_id), null, true);
|
|
266
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
267
|
+
}
|
|
268
|
+
launch(conversation, route, sessionId, empty) {
|
|
269
|
+
let opened;
|
|
270
|
+
let failed;
|
|
271
|
+
const bridge = new Promise((resolve, reject) => {
|
|
272
|
+
opened = resolve;
|
|
273
|
+
failed = reject;
|
|
274
|
+
});
|
|
275
|
+
bridge.catch(() => undefined);
|
|
276
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
277
|
+
session.catch(failed);
|
|
278
|
+
const launch = { bridge, session };
|
|
279
|
+
conversation.launch = launch;
|
|
280
|
+
void session
|
|
281
|
+
.catch(() => undefined)
|
|
282
|
+
.then(() => {
|
|
283
|
+
if (conversation.launch !== launch)
|
|
284
|
+
return;
|
|
285
|
+
conversation.launch = null;
|
|
286
|
+
this.next(conversation);
|
|
287
|
+
});
|
|
288
|
+
this.next(conversation);
|
|
289
|
+
return launch;
|
|
290
|
+
}
|
|
291
|
+
async launched(conversation) {
|
|
292
|
+
while (conversation.launch !== null)
|
|
293
|
+
await conversation.launch.session.catch(() => undefined);
|
|
294
|
+
}
|
|
295
|
+
instructed(conversation, route) {
|
|
296
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
297
|
+
if (bridge === undefined)
|
|
298
|
+
return null;
|
|
299
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
300
|
+
read.catch(() => undefined);
|
|
301
|
+
return read;
|
|
302
|
+
}
|
|
303
|
+
async message(conversation, input) {
|
|
304
|
+
const content = [{ type: 'text', text: String(input.text) }];
|
|
305
|
+
let route;
|
|
306
|
+
let instructing = null;
|
|
164
307
|
try {
|
|
165
|
-
|
|
308
|
+
route = await this.route(conversation, input.agent_id);
|
|
309
|
+
if (input.first === true)
|
|
310
|
+
instructing = this.instructed(conversation, route);
|
|
311
|
+
const files = input.files;
|
|
312
|
+
if (files.length > 0)
|
|
313
|
+
this.prepares(conversation, 'files');
|
|
314
|
+
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
315
|
+
const lines = await this.transcribed(conversation, files, paths);
|
|
316
|
+
if (lines.length > 0)
|
|
317
|
+
content.push({ type: 'text', text: lines.join('\n') });
|
|
318
|
+
if (conversation.kills > 0)
|
|
319
|
+
throw new Error(KILLED);
|
|
166
320
|
}
|
|
167
321
|
catch (error) {
|
|
168
|
-
|
|
322
|
+
this.prepares(conversation, null);
|
|
323
|
+
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
324
|
+
}
|
|
325
|
+
conversation.preparing = null;
|
|
326
|
+
return {
|
|
327
|
+
ack: new Promise((settle) => {
|
|
328
|
+
conversation.waiting.push({ input, route, content, instructing, settle });
|
|
329
|
+
this.next(conversation);
|
|
330
|
+
}),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
next(conversation) {
|
|
334
|
+
if (conversation.sending !== null || conversation.open > 0)
|
|
335
|
+
return;
|
|
336
|
+
const waiting = conversation.waiting.shift();
|
|
337
|
+
if (waiting === undefined) {
|
|
338
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
339
|
+
if (conversation.starting !== step)
|
|
340
|
+
this.starts(conversation, step);
|
|
341
|
+
return;
|
|
169
342
|
}
|
|
343
|
+
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
344
|
+
conversation.sending = null;
|
|
345
|
+
this.next(conversation);
|
|
346
|
+
});
|
|
170
347
|
}
|
|
171
|
-
async
|
|
348
|
+
async send(conversation, waiting) {
|
|
349
|
+
const { input } = waiting;
|
|
172
350
|
let ack = {};
|
|
351
|
+
let started = false;
|
|
352
|
+
let instructing = waiting.instructing;
|
|
173
353
|
const prompt = [];
|
|
174
354
|
try {
|
|
175
|
-
const route = await this.route(conversation, input.agent_id);
|
|
176
|
-
const files = input.files;
|
|
177
|
-
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
178
|
-
const lines = await this.transcribed(conversation, files, paths);
|
|
179
355
|
if (conversation.running === null) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
356
|
+
this.starts(conversation, 'session');
|
|
357
|
+
await this.launched(conversation);
|
|
358
|
+
}
|
|
359
|
+
if (conversation.running === null) {
|
|
360
|
+
const stored = conversation.session ?? input.provider_session_id;
|
|
361
|
+
const empty = input.first === true && conversation.prompted !== stored;
|
|
362
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
363
|
+
if (input.first === true)
|
|
364
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
365
|
+
started = (await launch.session) !== stored;
|
|
366
|
+
}
|
|
367
|
+
const session = conversation.running.sessionId;
|
|
368
|
+
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
369
|
+
ack = { provider_session_id: session };
|
|
183
370
|
}
|
|
184
371
|
if (input.first === true) {
|
|
185
|
-
prompt.push({
|
|
186
|
-
type: 'text',
|
|
187
|
-
text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
|
|
188
|
-
});
|
|
372
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
189
373
|
}
|
|
190
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
191
|
-
if (lines.length > 0)
|
|
192
|
-
prompt.push({ type: 'text', text: lines.join('\n') });
|
|
193
374
|
if (conversation.kills > 0)
|
|
194
375
|
throw new Error(KILLED);
|
|
195
376
|
}
|
|
196
377
|
catch (error) {
|
|
197
|
-
|
|
378
|
+
this.starts(conversation, null);
|
|
379
|
+
if (started) {
|
|
380
|
+
conversation.session = null;
|
|
198
381
|
await this.kill(conversation);
|
|
199
|
-
|
|
382
|
+
}
|
|
383
|
+
waiting.settle({ refused: refusal(conversation, error) });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.starts(conversation, null);
|
|
387
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.inputs.hands(String(input.input_id), ack));
|
|
388
|
+
waiting.settle(ack);
|
|
389
|
+
}
|
|
390
|
+
starts(conversation, step) {
|
|
391
|
+
conversation.starting = step;
|
|
392
|
+
if (conversation.shown !== null)
|
|
393
|
+
clearTimeout(conversation.shown);
|
|
394
|
+
conversation.shown = null;
|
|
395
|
+
if (step === null || conversation.told !== null) {
|
|
396
|
+
this.tell(conversation, step);
|
|
397
|
+
return;
|
|
200
398
|
}
|
|
201
|
-
|
|
202
|
-
|
|
399
|
+
conversation.shown = setTimeout(() => {
|
|
400
|
+
conversation.shown = null;
|
|
401
|
+
if (conversation.starting === step)
|
|
402
|
+
this.tell(conversation, step);
|
|
403
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
404
|
+
}
|
|
405
|
+
prepares(conversation, step) {
|
|
406
|
+
conversation.preparing = step;
|
|
407
|
+
this.next(conversation);
|
|
408
|
+
}
|
|
409
|
+
tell(conversation, step) {
|
|
410
|
+
if (conversation.told === step && !conversation.untold)
|
|
411
|
+
return;
|
|
412
|
+
conversation.told = step;
|
|
413
|
+
this.retell(conversation);
|
|
414
|
+
}
|
|
415
|
+
retell(conversation) {
|
|
416
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
203
417
|
}
|
|
204
418
|
async transcribed(conversation, files, paths) {
|
|
205
419
|
const lines = [];
|
|
@@ -207,9 +421,10 @@ export class Conversations {
|
|
|
207
421
|
lines.push(path);
|
|
208
422
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
209
423
|
continue;
|
|
424
|
+
this.prepares(conversation, 'transcription');
|
|
210
425
|
try {
|
|
211
426
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
212
|
-
if (text !== '')
|
|
427
|
+
if (text !== null && text !== '')
|
|
213
428
|
lines.push(`Transcript: ${text}`);
|
|
214
429
|
}
|
|
215
430
|
catch (error) {
|
|
@@ -229,11 +444,13 @@ export class Conversations {
|
|
|
229
444
|
}
|
|
230
445
|
halt(conversation) {
|
|
231
446
|
if (conversation.opening !== null)
|
|
232
|
-
|
|
447
|
+
killMarked(marker(conversation.opening));
|
|
448
|
+
for (const scope of conversation.leftovers)
|
|
449
|
+
scope.kill();
|
|
233
450
|
if (conversation.running === null)
|
|
234
451
|
return;
|
|
235
452
|
conversation.running.killed = true;
|
|
236
|
-
|
|
453
|
+
conversation.running.scope.kill();
|
|
237
454
|
}
|
|
238
455
|
async kill(conversation) {
|
|
239
456
|
const running = conversation.running;
|
|
@@ -245,6 +462,7 @@ export class Conversations {
|
|
|
245
462
|
if (running === null)
|
|
246
463
|
return;
|
|
247
464
|
try {
|
|
465
|
+
running.reader.requested(option, value);
|
|
248
466
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
249
467
|
sessionId: running.sessionId,
|
|
250
468
|
configId: option,
|
|
@@ -253,6 +471,7 @@ export class Conversations {
|
|
|
253
471
|
this.options(conversation, answered.configOptions);
|
|
254
472
|
}
|
|
255
473
|
catch (error) {
|
|
474
|
+
running.reader.refused(option);
|
|
256
475
|
logged(error);
|
|
257
476
|
}
|
|
258
477
|
}
|
|
@@ -269,48 +488,58 @@ export class Conversations {
|
|
|
269
488
|
}
|
|
270
489
|
return route;
|
|
271
490
|
}
|
|
272
|
-
async start(conversation,
|
|
491
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
492
|
+
const route = await routed;
|
|
493
|
+
const reader = readerFor(route.kind);
|
|
494
|
+
reader.session = sessionId;
|
|
273
495
|
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
496
|
.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))
|
|
497
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
498
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
279
499
|
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);
|
|
500
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
281
501
|
if (conversation.kills > 0) {
|
|
282
502
|
bridge.close();
|
|
283
503
|
throw new Error(KILLED);
|
|
284
504
|
}
|
|
285
505
|
conversation.opening = bridge;
|
|
506
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
507
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
508
|
+
let adapter = null;
|
|
286
509
|
try {
|
|
287
510
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
288
|
-
|
|
511
|
+
bridged(bridge);
|
|
512
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
513
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
514
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
515
|
+
work.read(message);
|
|
516
|
+
this.read(conversation, reader, message);
|
|
517
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
289
518
|
void adapter.exited.then(() => bridge.close());
|
|
290
519
|
const agent = adapter.connection.agent;
|
|
291
520
|
const cwd = route.working_directory;
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
sessionId: opened.sessionId,
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const options = await this.launchSettings(running, route, opened.configOptions ?? []);
|
|
521
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
522
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
523
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
524
|
+
reader.session = opened.sessionId;
|
|
525
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
526
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
527
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
528
|
+
}
|
|
529
|
+
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
530
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
531
|
+
void work.settled().then(() => scope.record());
|
|
304
532
|
conversation.running = running;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
this.
|
|
309
|
-
void adapter.exited.then((cause) => this.exited(conversation, adapter, cause));
|
|
533
|
+
conversation.session = running.sessionId;
|
|
534
|
+
conversation.options = options;
|
|
535
|
+
this.facts(conversation);
|
|
536
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
310
537
|
return running.sessionId;
|
|
311
538
|
}
|
|
312
539
|
catch (error) {
|
|
313
|
-
|
|
540
|
+
adapter?.retire();
|
|
541
|
+
work.close();
|
|
542
|
+
conversation.leftovers.add(scope);
|
|
314
543
|
bridge.close();
|
|
315
544
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
316
545
|
}
|
|
@@ -318,9 +547,22 @@ export class Conversations {
|
|
|
318
547
|
conversation.opening = null;
|
|
319
548
|
}
|
|
320
549
|
}
|
|
550
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
551
|
+
try {
|
|
552
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
556
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
557
|
+
}
|
|
558
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
321
562
|
async launchSettings(running, route, offered) {
|
|
322
563
|
let options = offered;
|
|
323
564
|
const settings = [
|
|
565
|
+
['mode', route.mode ?? CLIS[route.kind].fullAccess],
|
|
324
566
|
['model', route.model],
|
|
325
567
|
['thought_level', route.effort],
|
|
326
568
|
];
|
|
@@ -328,6 +570,7 @@ export class Conversations {
|
|
|
328
570
|
if (value === null)
|
|
329
571
|
continue;
|
|
330
572
|
const option = options.find((candidate) => candidate.category === category);
|
|
573
|
+
running.reader.requested(option?.id ?? category, value);
|
|
331
574
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
332
575
|
sessionId: running.sessionId,
|
|
333
576
|
configId: option?.id ?? category,
|
|
@@ -337,23 +580,35 @@ export class Conversations {
|
|
|
337
580
|
}
|
|
338
581
|
return options;
|
|
339
582
|
}
|
|
340
|
-
exited(conversation,
|
|
341
|
-
|
|
342
|
-
if (
|
|
343
|
-
|
|
583
|
+
exited(conversation, running, cause) {
|
|
584
|
+
this.detach(conversation, running);
|
|
585
|
+
if (conversation.turn !== null) {
|
|
586
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
587
|
+
}
|
|
588
|
+
this.next(conversation);
|
|
589
|
+
this.kit.changed();
|
|
590
|
+
}
|
|
591
|
+
detach(conversation, running) {
|
|
592
|
+
running.work.close();
|
|
593
|
+
conversation.leftovers.add(running.scope);
|
|
344
594
|
conversation.running = null;
|
|
345
595
|
conversation.commands = null;
|
|
346
|
-
conversation.
|
|
347
|
-
conversation.queued.length = 0;
|
|
596
|
+
conversation.options = null;
|
|
348
597
|
for (const [id, question] of this.questions) {
|
|
349
598
|
if (question.conversation === conversation)
|
|
350
599
|
this.questions.delete(id);
|
|
351
600
|
}
|
|
352
|
-
this.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
601
|
+
this.process(conversation, false);
|
|
602
|
+
}
|
|
603
|
+
process(conversation, running) {
|
|
604
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
605
|
+
}
|
|
606
|
+
facts(conversation) {
|
|
607
|
+
this.process(conversation, true);
|
|
608
|
+
if (conversation.commands !== null)
|
|
609
|
+
this.commands(conversation, conversation.commands);
|
|
610
|
+
if (conversation.options !== null)
|
|
611
|
+
this.options(conversation, conversation.options);
|
|
357
612
|
}
|
|
358
613
|
commands(conversation, commands) {
|
|
359
614
|
conversation.commands = commands;
|
|
@@ -364,11 +619,12 @@ export class Conversations {
|
|
|
364
619
|
options(conversation, options) {
|
|
365
620
|
if (conversation.running === null)
|
|
366
621
|
return;
|
|
622
|
+
conversation.options = options;
|
|
367
623
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
368
624
|
}
|
|
369
625
|
deliver(conversation, turn, event, body) {
|
|
370
626
|
this.reporting++;
|
|
371
|
-
const delivered = conversation.reports.then(() => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, body));
|
|
627
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
372
628
|
conversation.reports = delivered
|
|
373
629
|
.catch(() => undefined)
|
|
374
630
|
.finally(() => {
|
|
@@ -377,119 +633,95 @@ export class Conversations {
|
|
|
377
633
|
});
|
|
378
634
|
return delivered;
|
|
379
635
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
.then(() => this.admitted(conversation, turn))
|
|
385
|
-
.catch(logged);
|
|
386
|
-
return turn;
|
|
636
|
+
write(conversation, turn, operation) {
|
|
637
|
+
if (turn.ended || !this.watched)
|
|
638
|
+
return;
|
|
639
|
+
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
387
640
|
}
|
|
388
|
-
|
|
389
|
-
turn.
|
|
390
|
-
|
|
391
|
-
this.kit.send(note);
|
|
392
|
-
if (turn.text !== '') {
|
|
393
|
-
turn.unsentFrom = 0;
|
|
394
|
-
this.draft(conversation, turn);
|
|
395
|
-
}
|
|
396
|
-
if (turn.entries !== null)
|
|
397
|
-
this.plan(conversation, turn);
|
|
641
|
+
replay(conversation, turn) {
|
|
642
|
+
for (const operation of turn.parts.operations())
|
|
643
|
+
this.write(conversation, turn, operation);
|
|
398
644
|
}
|
|
399
645
|
begin(conversation) {
|
|
400
|
-
const turn = this.
|
|
646
|
+
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
647
|
+
conversation.open++;
|
|
648
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
401
649
|
conversation.turn = turn;
|
|
650
|
+
if (conversation.context !== null)
|
|
651
|
+
turn.parts.measure(conversation.context);
|
|
652
|
+
for (const part of conversation.held.splice(0))
|
|
653
|
+
turn.parts.start(part);
|
|
402
654
|
this.kit.changed();
|
|
403
655
|
return turn;
|
|
404
656
|
}
|
|
405
|
-
|
|
406
|
-
if (turn.ended)
|
|
407
|
-
return;
|
|
408
|
-
const segment = turn.segment;
|
|
409
|
-
turn.segment = null;
|
|
410
|
-
if (segment?.phase === 'note')
|
|
411
|
-
this.note(conversation, turn, segment.text);
|
|
412
|
-
else if (segment !== null)
|
|
413
|
-
this.answer(conversation, turn, segment.text);
|
|
414
|
-
this.end(conversation, turn, { text: turn.text });
|
|
415
|
-
}
|
|
416
|
-
end(conversation, turn, outcome) {
|
|
657
|
+
end(conversation, turn, failed, notice = null) {
|
|
417
658
|
if (turn.ended)
|
|
418
659
|
return;
|
|
660
|
+
if (failed !== null)
|
|
661
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
662
|
+
if (conversation.turn === turn) {
|
|
663
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
664
|
+
this.apply(conversation, turn, event);
|
|
665
|
+
}
|
|
666
|
+
if (notice !== null)
|
|
667
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
419
668
|
turn.ended = true;
|
|
420
669
|
conversation.open--;
|
|
421
670
|
if (conversation.turn === turn)
|
|
422
671
|
conversation.turn = null;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
672
|
+
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
673
|
+
parts: turn.parts.saved(),
|
|
674
|
+
context: turn.parts.context,
|
|
675
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
676
|
+
}));
|
|
677
|
+
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
678
|
+
this.next(conversation);
|
|
427
679
|
this.kit.changed();
|
|
428
680
|
}
|
|
429
|
-
|
|
430
|
-
if (conversation.open > 0)
|
|
431
|
-
conversation.late.push(cause);
|
|
432
|
-
else
|
|
433
|
-
this.end(conversation, this.report(conversation), { failed: cause });
|
|
434
|
-
}
|
|
435
|
-
prompt(conversation, prompt) {
|
|
681
|
+
prompt(conversation, prompt, taken) {
|
|
436
682
|
const running = conversation.running;
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
this.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
else
|
|
448
|
-
this.end(conversation,
|
|
683
|
+
const turn = this.begin(conversation);
|
|
684
|
+
turn.prompted = true;
|
|
685
|
+
turn.taken = taken;
|
|
686
|
+
conversation.prompted = running.sessionId;
|
|
687
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
688
|
+
this.took(turn);
|
|
689
|
+
const notice = answeredFailure(answer);
|
|
690
|
+
if (notice !== null) {
|
|
691
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
695
|
+
}
|
|
696
|
+
}, async (error) => {
|
|
697
|
+
if (running.adapter.connection.signal.aborted) {
|
|
698
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
this.took(turn);
|
|
702
|
+
if (conversation.open === 1 && conversation.running === running) {
|
|
703
|
+
running.adapter.retire();
|
|
704
|
+
this.detach(conversation, running);
|
|
705
|
+
}
|
|
706
|
+
this.end(conversation, turn, answeredWith(error));
|
|
449
707
|
}
|
|
450
708
|
});
|
|
451
709
|
}
|
|
452
|
-
attach(turn, sent) {
|
|
453
|
-
turn.prompt = sent;
|
|
454
|
-
sent.turn = turn;
|
|
455
|
-
}
|
|
456
|
-
answered(conversation, sent, failed) {
|
|
457
|
-
const turn = sent.turn;
|
|
458
|
-
if (turn === null) {
|
|
459
|
-
const queued = conversation.queued.indexOf(sent);
|
|
460
|
-
if (queued < 0)
|
|
461
|
-
return;
|
|
462
|
-
conversation.queued.splice(queued, 1);
|
|
463
|
-
if (failed !== null)
|
|
464
|
-
this.failed(conversation, failed);
|
|
465
|
-
}
|
|
466
|
-
else if (failed !== null) {
|
|
467
|
-
if (turn.ended)
|
|
468
|
-
this.failed(conversation, failed);
|
|
469
|
-
else
|
|
470
|
-
this.end(conversation, turn, { failed });
|
|
471
|
-
}
|
|
472
|
-
else if (turn.prompt === sent) {
|
|
473
|
-
this.finish(conversation, turn);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
710
|
started(conversation) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const turn = this.begin(conversation);
|
|
480
|
-
const queued = conversation.queued.shift();
|
|
481
|
-
if (queued !== undefined)
|
|
482
|
-
this.attach(turn, queued);
|
|
711
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
712
|
+
this.took(turn);
|
|
483
713
|
return turn;
|
|
484
714
|
}
|
|
485
715
|
ended(conversation) {
|
|
486
716
|
const turn = conversation.turn;
|
|
487
717
|
if (turn === null)
|
|
488
718
|
return;
|
|
489
|
-
if (turn.
|
|
490
|
-
this.
|
|
719
|
+
if (!turn.prompted) {
|
|
720
|
+
this.end(conversation, turn, null);
|
|
491
721
|
}
|
|
492
722
|
else {
|
|
723
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
724
|
+
this.apply(conversation, turn, event);
|
|
493
725
|
conversation.turn = null;
|
|
494
726
|
this.kit.changed();
|
|
495
727
|
}
|
|
@@ -538,101 +770,107 @@ export class Conversations {
|
|
|
538
770
|
if (content !== null)
|
|
539
771
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
540
772
|
}
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
773
|
+
read(conversation, reader, message) {
|
|
774
|
+
for (const event of reader.read(message))
|
|
775
|
+
this.event(conversation, event);
|
|
776
|
+
}
|
|
777
|
+
event(conversation, event) {
|
|
778
|
+
switch (event.kind) {
|
|
779
|
+
case 'started':
|
|
780
|
+
this.started(conversation);
|
|
781
|
+
return;
|
|
782
|
+
case 'ended':
|
|
783
|
+
this.ended(conversation);
|
|
784
|
+
return;
|
|
785
|
+
case 'commands':
|
|
786
|
+
this.commands(conversation, event.commands);
|
|
787
|
+
return;
|
|
788
|
+
case 'options':
|
|
789
|
+
this.options(conversation, event.options);
|
|
790
|
+
return;
|
|
791
|
+
case 'context':
|
|
792
|
+
conversation.context = { used: event.used, window: event.window };
|
|
793
|
+
conversation.turn?.parts.measure(conversation.context);
|
|
794
|
+
return;
|
|
795
|
+
case 'subagent-done':
|
|
796
|
+
this.subagentDone(conversation, event.id, event.result);
|
|
797
|
+
return;
|
|
798
|
+
case 'marker':
|
|
799
|
+
case 'image':
|
|
800
|
+
if (conversation.turn !== null)
|
|
801
|
+
break;
|
|
802
|
+
if (event.kind === 'marker' && event.marker === 'job')
|
|
803
|
+
conversation.held.push({ type: 'marker', marker: 'job', text: event.text });
|
|
804
|
+
return;
|
|
565
805
|
}
|
|
806
|
+
this.apply(conversation, this.started(conversation), event);
|
|
566
807
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
conversation.jobs.delete(job.id);
|
|
572
|
-
this.kit.changed();
|
|
808
|
+
took(turn) {
|
|
809
|
+
const taken = turn.taken;
|
|
810
|
+
turn.taken = null;
|
|
811
|
+
taken?.();
|
|
573
812
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
813
|
+
apply(conversation, turn, event) {
|
|
814
|
+
switch (event.kind) {
|
|
815
|
+
case 'text':
|
|
816
|
+
case 'reasoning':
|
|
817
|
+
turn.parts.write(event.kind, event.id, event.text);
|
|
818
|
+
return;
|
|
819
|
+
case 'command':
|
|
820
|
+
turn.parts.command();
|
|
821
|
+
return;
|
|
822
|
+
case 'subagent': {
|
|
823
|
+
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
824
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
case 'message':
|
|
828
|
+
turn.parts.start({ type: 'message', command: event.command, target: event.target });
|
|
829
|
+
return;
|
|
830
|
+
case 'marker':
|
|
831
|
+
turn.parts.start({ type: 'marker', marker: event.marker, text: event.text });
|
|
832
|
+
return;
|
|
833
|
+
case 'plan':
|
|
834
|
+
turn.parts.plan(event.entries);
|
|
835
|
+
return;
|
|
836
|
+
case 'image':
|
|
837
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
838
|
+
this.kit.changed();
|
|
839
|
+
return;
|
|
582
840
|
}
|
|
583
|
-
turn.segment ??= { messageId, phase, text: '' };
|
|
584
|
-
turn.segment.text += text;
|
|
585
841
|
}
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
842
|
+
subagentDone(conversation, id, result) {
|
|
843
|
+
const subagent = conversation.subagents.get(id);
|
|
844
|
+
if (subagent === undefined)
|
|
589
845
|
return;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
note(conversation, turn, text) {
|
|
594
|
-
if (text.trim() === '')
|
|
846
|
+
conversation.subagents.delete(id);
|
|
847
|
+
if (subagent.turn === conversation.turn) {
|
|
848
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
595
849
|
return;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
850
|
+
}
|
|
851
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
852
|
+
if (conversation.turn === null)
|
|
853
|
+
conversation.held.push(part);
|
|
599
854
|
else
|
|
600
|
-
turn.
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
});
|
|
617
|
-
if (sent) {
|
|
618
|
-
turn.draftSequence++;
|
|
619
|
-
turn.sent = blocks;
|
|
620
|
-
turn.unsentFrom = null;
|
|
855
|
+
conversation.turn.parts.start(part);
|
|
856
|
+
}
|
|
857
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
858
|
+
const bytes = Buffer.from(data, 'base64');
|
|
859
|
+
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
860
|
+
try {
|
|
861
|
+
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
862
|
+
name,
|
|
863
|
+
media_type: mediaType,
|
|
864
|
+
bytes: bytes.length,
|
|
865
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
866
|
+
}));
|
|
867
|
+
const saved = declared.upload === undefined ? declared : await uploadBytes(declared.upload, bytes);
|
|
868
|
+
if (saved.save.status !== 'saved')
|
|
869
|
+
throw new Error(saved.save.failure ?? saved.save.status);
|
|
870
|
+
turn.parts.arrive({ type: 'image', file: saved.version, name, media_type: mediaType }, reserved);
|
|
621
871
|
}
|
|
622
|
-
|
|
623
|
-
turn.
|
|
872
|
+
catch (error) {
|
|
873
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${causeOf(error)}` }, reserved);
|
|
624
874
|
}
|
|
625
875
|
}
|
|
626
|
-
plan(conversation, turn) {
|
|
627
|
-
const sent = this.kit.send({
|
|
628
|
-
type: 'plan',
|
|
629
|
-
conversation_id: conversation.id,
|
|
630
|
-
turn_id: turn.id,
|
|
631
|
-
sequence: turn.planSequence + 1,
|
|
632
|
-
from: 0,
|
|
633
|
-
steps: turn.entries.map((entry) => ({ label: entry.content, priority: entry.priority, status: entry.status })),
|
|
634
|
-
});
|
|
635
|
-
if (sent)
|
|
636
|
-
turn.planSequence++;
|
|
637
|
-
}
|
|
638
876
|
}
|