@agentshouse/kit 0.2.1-alpha.8 → 0.2.1-c031c7457a661
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 +49 -24
- package/dist/abort.js +9 -0
- package/dist/acp.js +49 -86
- package/dist/agents.js +333 -52
- package/dist/api.js +40 -4
- package/dist/app-git.js +48 -0
- package/dist/bootstrap.json +10 -14
- package/dist/bridge.js +93 -78
- package/dist/calls.js +63 -0
- package/dist/chatgpt.js +32 -0
- package/dist/clis-main.js +23 -0
- package/dist/clis.js +217 -72
- package/dist/codex-main.js +58 -0
- package/dist/configure-main.js +6 -0
- package/dist/connection.js +126 -0
- package/dist/conversations.js +642 -261
- package/dist/copies.js +237 -173
- package/dist/end.js +27 -0
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +525 -0
- package/dist/files.js +140 -17
- package/dist/git-main.js +2 -2
- package/dist/git.js +16 -10
- package/dist/home.js +141 -10
- package/dist/house-cli.js +300 -0
- package/dist/house-main.js +5 -102
- package/dist/instructions.js +16 -17
- package/dist/kit-cli.js +68 -0
- package/dist/kit-main.js +8 -16
- package/dist/login.js +128 -95
- package/dist/logout.js +19 -0
- package/dist/mcp.js +41 -0
- package/dist/operation.js +7 -4
- package/dist/parts.js +82 -0
- package/dist/placement.js +39 -0
- package/dist/refusals.js +53 -0
- package/dist/relay.js +20 -0
- package/dist/resident.js +140 -51
- package/dist/scope.js +206 -0
- package/dist/secret-input.js +7 -2
- package/dist/service-main.js +9 -0
- package/dist/service.js +55 -0
- package/dist/shell.js +3 -0
- package/dist/sign-in.js +50 -31
- package/dist/skills.js +66 -0
- package/dist/transcription.js +201 -0
- package/dist/translate.js +2 -8
- package/dist/update.js +138 -0
- package/dist/web-wizard.js +100 -0
- package/dist/wire.js +22 -0
- package/dist/work.js +346 -0
- package/package.json +8 -8
- package/dist/blocks.js +0 -51
- package/dist/stream.js +0 -47
package/dist/conversations.js
CHANGED
|
@@ -1,64 +1,152 @@
|
|
|
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, HouseRefusal } 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 { inspectedFile, linkedPaths, storeRefusal, uploaded } 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 { relayed } from './relay.js';
|
|
17
|
+
import { killMarked, Scope } from './scope.js';
|
|
11
18
|
import { holdSecretInput } from './secret-input.js';
|
|
12
|
-
|
|
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 INTERRUPTED = 'the conversation was interrupted';
|
|
24
|
+
const REFUSED = 'the CLI refused the prompt';
|
|
25
|
+
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
26
|
+
const GONE = 'House no longer holds this conversation; start a new one';
|
|
27
|
+
const RETIRED = "this conversation's Agent is retired, so it cannot start";
|
|
28
|
+
const ENDED = {
|
|
29
|
+
conversation_not_found: GONE,
|
|
30
|
+
conversation_historical: RETIRED,
|
|
31
|
+
};
|
|
32
|
+
const NO_START = "the Agent's CLI did not start";
|
|
33
|
+
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
34
|
+
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
35
|
+
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
36
|
+
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
37
|
+
class NotStarted extends Error {
|
|
38
|
+
}
|
|
39
|
+
class ResumeRefused extends NotStarted {
|
|
40
|
+
}
|
|
41
|
+
async function step(reason, work) {
|
|
42
|
+
try {
|
|
43
|
+
return await work;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function signedOut(error) {
|
|
50
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
51
|
+
}
|
|
52
|
+
function unopened(reason) {
|
|
53
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
54
|
+
}
|
|
55
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
56
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
57
|
+
function unbridged(error) {
|
|
58
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
59
|
+
}
|
|
60
|
+
const EXIT_PLAN_MODE = {
|
|
61
|
+
parse(value) {
|
|
62
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
63
|
+
throw new Error('_x.ai/exit_plan_mode needs an object');
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
14
69
|
function causeOf(error) {
|
|
15
70
|
return error instanceof Error ? error.message : String(error);
|
|
16
71
|
}
|
|
72
|
+
function answeredWith(error) {
|
|
73
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
74
|
+
}
|
|
75
|
+
function exitedWith(running, cause) {
|
|
76
|
+
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
77
|
+
}
|
|
17
78
|
function answerMessage(request, response) {
|
|
18
79
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
19
80
|
}
|
|
81
|
+
async function sentPart(url, bytes, headers) {
|
|
82
|
+
const answer = await fetch(relayed(url), { method: 'PUT', headers, body: bytes });
|
|
83
|
+
await answer.arrayBuffer();
|
|
84
|
+
if (!answer.ok)
|
|
85
|
+
throw storeRefusal('upload', answer.status);
|
|
86
|
+
}
|
|
20
87
|
function logged(error) {
|
|
21
88
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
22
89
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
work.then(resolve, reject).finally(() => signal.removeEventListener('abort', killed));
|
|
30
|
-
});
|
|
90
|
+
function refusal(conversation, error) {
|
|
91
|
+
if (conversation.kills > 0)
|
|
92
|
+
return KILLED;
|
|
93
|
+
const cause = error instanceof Error && error.cause !== undefined ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
94
|
+
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
95
|
+
return causeOf(error);
|
|
31
96
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
97
|
+
function audioOf(result) {
|
|
98
|
+
if (result.isError === true)
|
|
99
|
+
return null;
|
|
100
|
+
const file = inspectedFile(result.content.map((content) => content.text).join('\n'));
|
|
101
|
+
return file?.media_type?.startsWith('audio/') === true ? file : null;
|
|
102
|
+
}
|
|
103
|
+
function marker(bridge) {
|
|
104
|
+
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
34
105
|
}
|
|
35
106
|
class Turn {
|
|
36
107
|
id = randomUUID();
|
|
37
|
-
|
|
38
|
-
sent = [];
|
|
39
|
-
unsentFrom = null;
|
|
40
|
-
draftSequence = 0;
|
|
41
|
-
planSequence = 0;
|
|
42
|
-
entries = null;
|
|
108
|
+
parts;
|
|
43
109
|
questions = 0;
|
|
44
110
|
secrets = 0;
|
|
45
|
-
|
|
111
|
+
prompted = false;
|
|
112
|
+
taken = null;
|
|
46
113
|
ended = false;
|
|
114
|
+
images = 0;
|
|
115
|
+
uploads = [];
|
|
116
|
+
constructor(emit) {
|
|
117
|
+
this.parts = new Parts(emit);
|
|
118
|
+
}
|
|
47
119
|
}
|
|
48
120
|
class Conversation {
|
|
49
121
|
id;
|
|
50
122
|
running = null;
|
|
123
|
+
session = null;
|
|
51
124
|
turn = null;
|
|
52
125
|
commands = null;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
126
|
+
options = null;
|
|
127
|
+
exitUntold = false;
|
|
128
|
+
context = null;
|
|
129
|
+
leftovers = new Set();
|
|
130
|
+
waiting = [];
|
|
131
|
+
sending = null;
|
|
132
|
+
subagents = new Map();
|
|
133
|
+
held = [];
|
|
56
134
|
opening = null;
|
|
135
|
+
bridge = null;
|
|
136
|
+
launch = null;
|
|
137
|
+
prompted = null;
|
|
57
138
|
kills = 0;
|
|
58
139
|
killed = new AbortController();
|
|
140
|
+
interrupts = 0;
|
|
141
|
+
interrupted = new AbortController();
|
|
59
142
|
open = 0;
|
|
60
143
|
queue = Promise.resolve();
|
|
61
144
|
reports = Promise.resolve();
|
|
145
|
+
starting = null;
|
|
146
|
+
preparing = null;
|
|
147
|
+
told = null;
|
|
148
|
+
untold = false;
|
|
149
|
+
shown = null;
|
|
62
150
|
constructor(id) {
|
|
63
151
|
this.id = id;
|
|
64
152
|
}
|
|
@@ -74,51 +162,80 @@ function asksSecret(request) {
|
|
|
74
162
|
export class Conversations {
|
|
75
163
|
kit;
|
|
76
164
|
conversations = new Map();
|
|
77
|
-
acks = new Map();
|
|
78
165
|
questions = new Map();
|
|
79
|
-
carrying = 0;
|
|
80
166
|
reporting = 0;
|
|
167
|
+
watched = false;
|
|
81
168
|
constructor(kit) {
|
|
82
169
|
this.kit = kit;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.then((
|
|
101
|
-
.catch(
|
|
102
|
-
.
|
|
103
|
-
this.carrying--;
|
|
104
|
-
this.kit.changed();
|
|
170
|
+
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
171
|
+
setInterval(() => this.look(), 2_000);
|
|
172
|
+
}
|
|
173
|
+
call(call) {
|
|
174
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
175
|
+
const conversation = this.conversation(call.conversation_id);
|
|
176
|
+
if (call.kind === 'kill') {
|
|
177
|
+
conversation.kills++;
|
|
178
|
+
conversation.killed.abort(new Error(KILLED));
|
|
179
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
180
|
+
waiting.settle({ refused: KILLED });
|
|
181
|
+
this.halt(conversation);
|
|
182
|
+
}
|
|
183
|
+
if (call.kind === 'interrupt') {
|
|
184
|
+
conversation.interrupts++;
|
|
185
|
+
conversation.interrupted.abort();
|
|
186
|
+
}
|
|
187
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
188
|
+
conversation.queue = handled.catch(() => undefined);
|
|
189
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
105
190
|
});
|
|
106
191
|
}
|
|
107
192
|
idle() {
|
|
108
|
-
if (this.
|
|
193
|
+
if (this.reporting > 0)
|
|
109
194
|
return false;
|
|
110
195
|
for (const conversation of this.conversations.values()) {
|
|
111
196
|
const turn = conversation.turn;
|
|
112
197
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
113
|
-
if (conversation.
|
|
198
|
+
if (conversation.open > (asking ? 1 : 0))
|
|
114
199
|
return false;
|
|
115
200
|
}
|
|
116
|
-
|
|
201
|
+
const conversations = [...this.conversations.values()];
|
|
202
|
+
if (conversations.some((conversation) => conversation.running?.work.busy()))
|
|
203
|
+
return false;
|
|
204
|
+
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
205
|
+
}
|
|
206
|
+
quiet() {
|
|
207
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.open === 0);
|
|
208
|
+
}
|
|
209
|
+
look() {
|
|
210
|
+
for (const conversation of this.conversations.values()) {
|
|
211
|
+
for (const scope of conversation.leftovers) {
|
|
212
|
+
if (!scope.empty())
|
|
213
|
+
continue;
|
|
214
|
+
scope.close();
|
|
215
|
+
conversation.leftovers.delete(scope);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
this.kit.changed();
|
|
117
219
|
}
|
|
118
220
|
opened() {
|
|
221
|
+
this.watched = false;
|
|
222
|
+
for (const conversation of this.conversations.values()) {
|
|
223
|
+
if (conversation.running !== null)
|
|
224
|
+
this.facts(conversation);
|
|
225
|
+
else if (conversation.exitUntold)
|
|
226
|
+
this.process(conversation, false);
|
|
227
|
+
if (conversation.untold || conversation.told !== null)
|
|
228
|
+
this.retell(conversation);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
watching(watched) {
|
|
232
|
+
const resend = watched && !this.watched;
|
|
233
|
+
this.watched = watched;
|
|
234
|
+
if (!resend)
|
|
235
|
+
return;
|
|
119
236
|
for (const conversation of this.conversations.values()) {
|
|
120
237
|
if (conversation.turn !== null)
|
|
121
|
-
conversation.turn
|
|
238
|
+
this.replay(conversation, conversation.turn);
|
|
122
239
|
}
|
|
123
240
|
}
|
|
124
241
|
conversation(id) {
|
|
@@ -129,69 +246,249 @@ export class Conversations {
|
|
|
129
246
|
}
|
|
130
247
|
return conversation;
|
|
131
248
|
}
|
|
132
|
-
async carry(conversation,
|
|
133
|
-
if (
|
|
134
|
-
return this.open(conversation,
|
|
135
|
-
if (
|
|
136
|
-
return this.message(conversation,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
249
|
+
async carry(conversation, call) {
|
|
250
|
+
if (call.kind === 'open')
|
|
251
|
+
return { result: this.open(conversation, call) };
|
|
252
|
+
if (call.kind === 'message')
|
|
253
|
+
return this.message(conversation, call);
|
|
254
|
+
await this.launched(conversation);
|
|
255
|
+
if (call.kind === 'interrupt') {
|
|
256
|
+
if (--conversation.interrupts === 0)
|
|
257
|
+
conversation.interrupted = new AbortController();
|
|
258
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
259
|
+
}
|
|
260
|
+
if (call.kind === 'kill') {
|
|
140
261
|
await this.kill(conversation);
|
|
262
|
+
await conversation.sending;
|
|
141
263
|
if (--conversation.kills === 0)
|
|
142
264
|
conversation.killed = new AbortController();
|
|
143
265
|
}
|
|
144
|
-
if (
|
|
145
|
-
await this.option(conversation, String(
|
|
146
|
-
if (
|
|
147
|
-
const question = this.questions.get(String(
|
|
266
|
+
if (call.kind === 'option')
|
|
267
|
+
await this.option(conversation, String(call.option), call.value);
|
|
268
|
+
if (call.kind === 'answer') {
|
|
269
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
148
270
|
if (question === undefined) {
|
|
149
|
-
|
|
271
|
+
const text = answerMessage(call.request, call.response);
|
|
272
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
150
273
|
}
|
|
151
|
-
question.answer(
|
|
274
|
+
question.answer(call.response);
|
|
152
275
|
}
|
|
153
|
-
return {};
|
|
276
|
+
return { result: Promise.resolve({}) };
|
|
154
277
|
}
|
|
155
|
-
|
|
278
|
+
open(conversation, call) {
|
|
156
279
|
if (conversation.running !== null)
|
|
157
|
-
return {};
|
|
280
|
+
return Promise.resolve({});
|
|
281
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
282
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
283
|
+
}
|
|
284
|
+
launch(conversation, route, sessionId, empty) {
|
|
285
|
+
let opened;
|
|
286
|
+
let failed;
|
|
287
|
+
const bridge = new Promise((resolve, reject) => {
|
|
288
|
+
opened = resolve;
|
|
289
|
+
failed = reject;
|
|
290
|
+
});
|
|
291
|
+
bridge.catch(() => undefined);
|
|
292
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
293
|
+
session.catch(failed);
|
|
294
|
+
const launch = { bridge, session };
|
|
295
|
+
conversation.launch = launch;
|
|
296
|
+
void session
|
|
297
|
+
.catch(() => undefined)
|
|
298
|
+
.then(() => {
|
|
299
|
+
if (conversation.launch !== launch)
|
|
300
|
+
return;
|
|
301
|
+
conversation.launch = null;
|
|
302
|
+
this.closeIdleBridge(conversation);
|
|
303
|
+
this.next(conversation);
|
|
304
|
+
});
|
|
305
|
+
this.next(conversation);
|
|
306
|
+
return launch;
|
|
307
|
+
}
|
|
308
|
+
async launched(conversation) {
|
|
309
|
+
while (conversation.launch !== null)
|
|
310
|
+
await conversation.launch.session.catch(() => undefined);
|
|
311
|
+
}
|
|
312
|
+
instructed(conversation, route) {
|
|
313
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
314
|
+
if (bridge === undefined)
|
|
315
|
+
return null;
|
|
316
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
317
|
+
read.catch(() => undefined);
|
|
318
|
+
return read;
|
|
319
|
+
}
|
|
320
|
+
async message(conversation, call) {
|
|
321
|
+
const content = [{ type: 'text', text: String(call.text) }];
|
|
322
|
+
let route;
|
|
323
|
+
let instructing = null;
|
|
158
324
|
try {
|
|
159
|
-
|
|
325
|
+
route = await this.route(conversation, call.agent_id);
|
|
326
|
+
if (call.first === true)
|
|
327
|
+
instructing = this.instructed(conversation, route);
|
|
328
|
+
if (call.message_id !== null)
|
|
329
|
+
content.push(...(await this.transcribed(conversation, String(call.message_id), String(call.text))));
|
|
330
|
+
if (conversation.kills > 0)
|
|
331
|
+
throw new Error(KILLED);
|
|
160
332
|
}
|
|
161
333
|
catch (error) {
|
|
162
|
-
|
|
334
|
+
this.closeIdleBridge(conversation);
|
|
335
|
+
this.prepares(conversation, null);
|
|
336
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
337
|
+
}
|
|
338
|
+
conversation.preparing = null;
|
|
339
|
+
return {
|
|
340
|
+
result: new Promise((settle) => {
|
|
341
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
342
|
+
this.next(conversation);
|
|
343
|
+
}),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
next(conversation) {
|
|
347
|
+
if (conversation.sending !== null || conversation.open > 0)
|
|
348
|
+
return;
|
|
349
|
+
const waiting = conversation.waiting.shift();
|
|
350
|
+
if (waiting === undefined) {
|
|
351
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
352
|
+
if (conversation.starting !== step)
|
|
353
|
+
this.starts(conversation, step);
|
|
354
|
+
return;
|
|
163
355
|
}
|
|
356
|
+
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
357
|
+
conversation.sending = null;
|
|
358
|
+
this.next(conversation);
|
|
359
|
+
});
|
|
164
360
|
}
|
|
165
|
-
async
|
|
166
|
-
|
|
361
|
+
async send(conversation, waiting) {
|
|
362
|
+
const { call } = waiting;
|
|
363
|
+
let result = {};
|
|
364
|
+
let started = false;
|
|
365
|
+
let instructing = waiting.instructing;
|
|
167
366
|
const prompt = [];
|
|
168
367
|
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
368
|
if (conversation.running === null) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
369
|
+
this.starts(conversation, 'session');
|
|
370
|
+
await this.launched(conversation);
|
|
371
|
+
}
|
|
372
|
+
if (conversation.running === null) {
|
|
373
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
374
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
375
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
376
|
+
if (call.first === true)
|
|
377
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
378
|
+
started = (await launch.session) !== stored;
|
|
379
|
+
}
|
|
380
|
+
const session = conversation.running.sessionId;
|
|
381
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
382
|
+
result = { provider_session_id: session };
|
|
175
383
|
}
|
|
176
|
-
if (
|
|
177
|
-
prompt.push({
|
|
178
|
-
type: 'text',
|
|
179
|
-
text: await instructions(conversation.running.bridge, route.base_instructions, await this.kit.copies.mapping()),
|
|
180
|
-
});
|
|
384
|
+
if (call.first === true) {
|
|
385
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
181
386
|
}
|
|
182
|
-
prompt.push({ type: 'text', text: String(input.text) });
|
|
183
|
-
if (paths.length > 0)
|
|
184
|
-
prompt.push({ type: 'text', text: paths.join('\n') });
|
|
185
387
|
if (conversation.kills > 0)
|
|
186
388
|
throw new Error(KILLED);
|
|
187
389
|
}
|
|
188
390
|
catch (error) {
|
|
189
|
-
|
|
391
|
+
this.starts(conversation, null);
|
|
392
|
+
if (started) {
|
|
393
|
+
conversation.session = null;
|
|
190
394
|
await this.kill(conversation);
|
|
191
|
-
|
|
395
|
+
}
|
|
396
|
+
const refused = refusal(conversation, error);
|
|
397
|
+
const resumeRefused = call.kind === 'message' && conversation.kills === 0 && error instanceof ResumeRefused;
|
|
398
|
+
waiting.settle(resumeRefused ? { refused, resume_refused: true } : { refused });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
this.starts(conversation, null);
|
|
402
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
403
|
+
waiting.settle(result);
|
|
404
|
+
}
|
|
405
|
+
starts(conversation, step) {
|
|
406
|
+
conversation.starting = step;
|
|
407
|
+
if (conversation.shown !== null)
|
|
408
|
+
clearTimeout(conversation.shown);
|
|
409
|
+
conversation.shown = null;
|
|
410
|
+
if (step === null || conversation.told !== null) {
|
|
411
|
+
this.tell(conversation, step);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
conversation.shown = setTimeout(() => {
|
|
415
|
+
conversation.shown = null;
|
|
416
|
+
if (conversation.starting === step)
|
|
417
|
+
this.tell(conversation, step);
|
|
418
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
419
|
+
}
|
|
420
|
+
prepares(conversation, step) {
|
|
421
|
+
conversation.preparing = step;
|
|
422
|
+
this.next(conversation);
|
|
423
|
+
}
|
|
424
|
+
tell(conversation, step) {
|
|
425
|
+
if (conversation.told === step && !conversation.untold)
|
|
426
|
+
return;
|
|
427
|
+
conversation.told = step;
|
|
428
|
+
this.retell(conversation);
|
|
429
|
+
}
|
|
430
|
+
retell(conversation) {
|
|
431
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
432
|
+
}
|
|
433
|
+
async transcribed(conversation, messageId, text) {
|
|
434
|
+
const paths = linkedPaths(text);
|
|
435
|
+
if (paths.length === 0)
|
|
436
|
+
return [];
|
|
437
|
+
const killed = conversation.killed.signal;
|
|
438
|
+
const interrupted = conversation.interrupted.signal;
|
|
439
|
+
const transcribing = AbortSignal.any([killed, interrupted]);
|
|
440
|
+
let signal = killed;
|
|
441
|
+
const blocks = [];
|
|
442
|
+
try {
|
|
443
|
+
const bridge = await step(unbridged, unlessAborted(this.bridged(conversation), killed));
|
|
444
|
+
const read = async (path) => audioOf(await step(unbridged, unlessAborted(bridge.tool('inspect', { path }), signal)));
|
|
445
|
+
for (const path of paths) {
|
|
446
|
+
let answered = await read(path);
|
|
447
|
+
if (answered === null)
|
|
448
|
+
continue;
|
|
449
|
+
signal = transcribing;
|
|
450
|
+
this.prepares(conversation, 'transcription');
|
|
451
|
+
const said = await transcribe(async () => {
|
|
452
|
+
const address = answered ?? (await read(path));
|
|
453
|
+
answered = null;
|
|
454
|
+
if (address === null)
|
|
455
|
+
throw new Error(`${path} is no longer an audio file`);
|
|
456
|
+
return address;
|
|
457
|
+
}, signal);
|
|
458
|
+
const transcript = `/kit/conversations/${conversation.id}/messages/${messageId}/transcripts`;
|
|
459
|
+
await step(unbridged, unlessAborted(this.kit.house.deliver(transcript, { path, text: said }, () => !signal.aborted), signal));
|
|
460
|
+
blocks.push({ type: 'text', text: `${path}\nTranscript: ${said}` });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
if (signal === transcribing && interrupted.aborted && conversation.kills === 0)
|
|
465
|
+
throw new NotStarted(INTERRUPTED, { cause: error });
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
return blocks;
|
|
469
|
+
}
|
|
470
|
+
bridged(conversation) {
|
|
471
|
+
if (conversation.bridge === null) {
|
|
472
|
+
const bridging = openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
473
|
+
conversation.bridge = bridging;
|
|
474
|
+
bridging.catch(() => {
|
|
475
|
+
if (conversation.bridge === bridging)
|
|
476
|
+
conversation.bridge = null;
|
|
477
|
+
});
|
|
192
478
|
}
|
|
193
|
-
|
|
194
|
-
|
|
479
|
+
return conversation.bridge;
|
|
480
|
+
}
|
|
481
|
+
closeBridge(conversation, bridging, bridge) {
|
|
482
|
+
bridge.close();
|
|
483
|
+
if (conversation.bridge === bridging)
|
|
484
|
+
conversation.bridge = null;
|
|
485
|
+
}
|
|
486
|
+
closeIdleBridge(conversation) {
|
|
487
|
+
const bridging = conversation.bridge;
|
|
488
|
+
if (bridging === null || conversation.running !== null || conversation.launch !== null)
|
|
489
|
+
return;
|
|
490
|
+
conversation.bridge = null;
|
|
491
|
+
void bridging.then((bridge) => bridge.close(), () => undefined);
|
|
195
492
|
}
|
|
196
493
|
async interrupt(conversation, turnId) {
|
|
197
494
|
if (conversation.running === null || conversation.turn?.id !== turnId)
|
|
@@ -202,11 +499,13 @@ export class Conversations {
|
|
|
202
499
|
}
|
|
203
500
|
halt(conversation) {
|
|
204
501
|
if (conversation.opening !== null)
|
|
205
|
-
|
|
502
|
+
killMarked(marker(conversation.opening));
|
|
503
|
+
for (const scope of conversation.leftovers)
|
|
504
|
+
scope.kill();
|
|
206
505
|
if (conversation.running === null)
|
|
207
506
|
return;
|
|
208
507
|
conversation.running.killed = true;
|
|
209
|
-
|
|
508
|
+
conversation.running.scope.kill();
|
|
210
509
|
}
|
|
211
510
|
async kill(conversation) {
|
|
212
511
|
const running = conversation.running;
|
|
@@ -218,6 +517,7 @@ export class Conversations {
|
|
|
218
517
|
if (running === null)
|
|
219
518
|
return;
|
|
220
519
|
try {
|
|
520
|
+
running.reader.requested(option, value);
|
|
221
521
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
222
522
|
sessionId: running.sessionId,
|
|
223
523
|
configId: option,
|
|
@@ -226,72 +526,98 @@ export class Conversations {
|
|
|
226
526
|
this.options(conversation, answered.configOptions);
|
|
227
527
|
}
|
|
228
528
|
catch (error) {
|
|
529
|
+
running.reader.refused(option);
|
|
229
530
|
logged(error);
|
|
230
531
|
}
|
|
231
532
|
}
|
|
232
533
|
async route(conversation, agentId) {
|
|
233
534
|
const signal = conversation.killed.signal;
|
|
234
|
-
await
|
|
535
|
+
await unlessAborted(this.kit.agents.read(), signal);
|
|
235
536
|
if (this.kit.agents.route(agentId) === undefined)
|
|
236
|
-
await
|
|
537
|
+
await unlessAborted(this.kit.agents.refresh(), signal);
|
|
237
538
|
const route = this.kit.agents.route(agentId);
|
|
238
539
|
if (route === undefined)
|
|
239
540
|
throw new Error(`this Environment hosts no Agent ${agentId}`);
|
|
240
|
-
if (route.working_directory === join(
|
|
541
|
+
if (route.working_directory === join(agentBase(), route.agent_id)) {
|
|
241
542
|
await mkdir(route.working_directory, { recursive: true });
|
|
242
543
|
}
|
|
243
544
|
return route;
|
|
244
545
|
}
|
|
245
|
-
async start(conversation,
|
|
546
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
547
|
+
const route = await routed;
|
|
548
|
+
const reader = readerFor(route.kind);
|
|
549
|
+
reader.session = sessionId;
|
|
246
550
|
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
551
|
.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
|
|
552
|
+
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
553
|
+
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
554
|
+
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
555
|
+
const bridging = this.bridged(conversation);
|
|
556
|
+
const bridge = await step(unbridged, bridging);
|
|
254
557
|
if (conversation.kills > 0) {
|
|
255
|
-
|
|
558
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
256
559
|
throw new Error(KILLED);
|
|
257
560
|
}
|
|
258
561
|
conversation.opening = bridge;
|
|
562
|
+
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
563
|
+
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
564
|
+
let adapter = null;
|
|
259
565
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
566
|
+
bridged(bridge);
|
|
567
|
+
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
568
|
+
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
569
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
570
|
+
work.read(message);
|
|
571
|
+
this.read(conversation, reader, message);
|
|
572
|
+
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
573
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
262
574
|
const agent = adapter.connection.agent;
|
|
263
575
|
const cwd = route.working_directory;
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
sessionId: opened.sessionId,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
576
|
+
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
577
|
+
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
578
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
579
|
+
reader.session = opened.sessionId;
|
|
580
|
+
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
581
|
+
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
582
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
583
|
+
}
|
|
584
|
+
const running = { adapter, bridging, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
585
|
+
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
586
|
+
void work.settled().then(() => scope.record());
|
|
275
587
|
conversation.running = running;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.
|
|
280
|
-
void adapter.exited.then((cause) => this.exited(conversation, adapter, cause));
|
|
588
|
+
conversation.session = running.sessionId;
|
|
589
|
+
conversation.options = options;
|
|
590
|
+
this.facts(conversation);
|
|
591
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
281
592
|
return running.sessionId;
|
|
282
593
|
}
|
|
283
594
|
catch (error) {
|
|
284
|
-
|
|
285
|
-
|
|
595
|
+
adapter?.retire();
|
|
596
|
+
work.close();
|
|
597
|
+
conversation.leftovers.add(scope);
|
|
598
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
286
599
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
287
600
|
}
|
|
288
601
|
finally {
|
|
289
602
|
conversation.opening = null;
|
|
290
603
|
}
|
|
291
604
|
}
|
|
605
|
+
async resume(conversation, kind, adapter, request, empty) {
|
|
606
|
+
try {
|
|
607
|
+
return { ...(await adapter.connection.agent.request('session/resume', request)), sessionId: request.sessionId };
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
611
|
+
throw signedOut(error) ? new NotStarted(NOT_SIGNED_IN, { cause: error }) : new ResumeRefused(NO_RESUME, { cause: error });
|
|
612
|
+
}
|
|
613
|
+
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
292
617
|
async launchSettings(running, route, offered) {
|
|
293
618
|
let options = offered;
|
|
294
619
|
const settings = [
|
|
620
|
+
['mode', route.mode ?? CLIS[route.kind].fullAccess],
|
|
295
621
|
['model', route.model],
|
|
296
622
|
['thought_level', route.effort],
|
|
297
623
|
];
|
|
@@ -299,6 +625,7 @@ export class Conversations {
|
|
|
299
625
|
if (value === null)
|
|
300
626
|
continue;
|
|
301
627
|
const option = options.find((candidate) => candidate.category === category);
|
|
628
|
+
running.reader.requested(option?.id ?? category, value);
|
|
302
629
|
const answered = await running.adapter.connection.agent.request('session/set_config_option', {
|
|
303
630
|
sessionId: running.sessionId,
|
|
304
631
|
configId: option?.id ?? category,
|
|
@@ -308,23 +635,37 @@ export class Conversations {
|
|
|
308
635
|
}
|
|
309
636
|
return options;
|
|
310
637
|
}
|
|
311
|
-
exited(conversation,
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
|
|
638
|
+
exited(conversation, running, cause) {
|
|
639
|
+
this.detach(conversation, running);
|
|
640
|
+
if (conversation.turn !== null) {
|
|
641
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
642
|
+
}
|
|
643
|
+
this.next(conversation);
|
|
644
|
+
this.kit.changed();
|
|
645
|
+
}
|
|
646
|
+
detach(conversation, running) {
|
|
647
|
+
if (conversation.bridge === running.bridging)
|
|
648
|
+
conversation.bridge = null;
|
|
649
|
+
running.work.close();
|
|
650
|
+
conversation.leftovers.add(running.scope);
|
|
315
651
|
conversation.running = null;
|
|
316
652
|
conversation.commands = null;
|
|
317
|
-
conversation.
|
|
318
|
-
conversation.queued.length = 0;
|
|
653
|
+
conversation.options = null;
|
|
319
654
|
for (const [id, question] of this.questions) {
|
|
320
655
|
if (question.conversation === conversation)
|
|
321
656
|
this.questions.delete(id);
|
|
322
657
|
}
|
|
323
|
-
this.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
658
|
+
this.process(conversation, false);
|
|
659
|
+
}
|
|
660
|
+
process(conversation, running) {
|
|
661
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
662
|
+
}
|
|
663
|
+
facts(conversation) {
|
|
664
|
+
this.process(conversation, true);
|
|
665
|
+
if (conversation.commands !== null)
|
|
666
|
+
this.commands(conversation, conversation.commands);
|
|
667
|
+
if (conversation.options !== null)
|
|
668
|
+
this.options(conversation, conversation.options);
|
|
328
669
|
}
|
|
329
670
|
commands(conversation, commands) {
|
|
330
671
|
conversation.commands = commands;
|
|
@@ -335,11 +676,12 @@ export class Conversations {
|
|
|
335
676
|
options(conversation, options) {
|
|
336
677
|
if (conversation.running === null)
|
|
337
678
|
return;
|
|
679
|
+
conversation.options = options;
|
|
338
680
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
339
681
|
}
|
|
340
682
|
deliver(conversation, turn, event, body) {
|
|
341
683
|
this.reporting++;
|
|
342
|
-
const delivered = conversation.reports.then(() => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, body));
|
|
684
|
+
const delivered = conversation.reports.then(async () => this.kit.house.deliver(`/kit/conversations/${conversation.id}/turns/${turn.id}/${event}`, await body));
|
|
343
685
|
conversation.reports = delivered
|
|
344
686
|
.catch(() => undefined)
|
|
345
687
|
.finally(() => {
|
|
@@ -348,105 +690,95 @@ export class Conversations {
|
|
|
348
690
|
});
|
|
349
691
|
return delivered;
|
|
350
692
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
.then(() => this.admitted(conversation, turn))
|
|
356
|
-
.catch(logged);
|
|
357
|
-
return turn;
|
|
693
|
+
write(conversation, turn, operation) {
|
|
694
|
+
if (turn.ended || !this.watched)
|
|
695
|
+
return;
|
|
696
|
+
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
358
697
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
this.draft(conversation, turn);
|
|
363
|
-
}
|
|
364
|
-
if (turn.entries !== null)
|
|
365
|
-
this.plan(conversation, turn);
|
|
698
|
+
replay(conversation, turn) {
|
|
699
|
+
for (const operation of turn.parts.operations())
|
|
700
|
+
this.write(conversation, turn, operation);
|
|
366
701
|
}
|
|
367
702
|
begin(conversation) {
|
|
368
|
-
const turn = this.
|
|
703
|
+
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
704
|
+
conversation.open++;
|
|
705
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
369
706
|
conversation.turn = turn;
|
|
707
|
+
if (conversation.context !== null)
|
|
708
|
+
turn.parts.measure(conversation.context);
|
|
709
|
+
for (const part of conversation.held.splice(0))
|
|
710
|
+
turn.parts.start(part);
|
|
370
711
|
this.kit.changed();
|
|
371
712
|
return turn;
|
|
372
713
|
}
|
|
373
|
-
end(conversation, turn,
|
|
714
|
+
end(conversation, turn, failed, notice = null) {
|
|
374
715
|
if (turn.ended)
|
|
375
716
|
return;
|
|
717
|
+
if (failed !== null)
|
|
718
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
719
|
+
if (conversation.turn === turn) {
|
|
720
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
721
|
+
this.apply(conversation, turn, event);
|
|
722
|
+
}
|
|
723
|
+
if (notice !== null)
|
|
724
|
+
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
376
725
|
turn.ended = true;
|
|
377
726
|
conversation.open--;
|
|
378
727
|
if (conversation.turn === turn)
|
|
379
728
|
conversation.turn = null;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
729
|
+
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
730
|
+
parts: turn.parts.saved(),
|
|
731
|
+
context: turn.parts.context,
|
|
732
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
733
|
+
}));
|
|
734
|
+
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
735
|
+
this.next(conversation);
|
|
384
736
|
this.kit.changed();
|
|
385
737
|
}
|
|
386
|
-
|
|
387
|
-
if (conversation.open > 0)
|
|
388
|
-
conversation.late.push(cause);
|
|
389
|
-
else
|
|
390
|
-
this.end(conversation, this.report(conversation), { failed: cause });
|
|
391
|
-
}
|
|
392
|
-
prompt(conversation, prompt) {
|
|
738
|
+
prompt(conversation, prompt, taken) {
|
|
393
739
|
const running = conversation.running;
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
this.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
else
|
|
405
|
-
this.end(conversation,
|
|
740
|
+
const turn = this.begin(conversation);
|
|
741
|
+
turn.prompted = true;
|
|
742
|
+
turn.taken = taken;
|
|
743
|
+
conversation.prompted = running.sessionId;
|
|
744
|
+
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
745
|
+
this.took(turn);
|
|
746
|
+
const notice = answeredFailure(answer);
|
|
747
|
+
if (notice !== null) {
|
|
748
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
752
|
+
}
|
|
753
|
+
}, async (error) => {
|
|
754
|
+
if (running.adapter.connection.signal.aborted) {
|
|
755
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
this.took(turn);
|
|
759
|
+
if (conversation.open === 1 && conversation.running === running) {
|
|
760
|
+
running.adapter.retire();
|
|
761
|
+
this.detach(conversation, running);
|
|
762
|
+
}
|
|
763
|
+
this.end(conversation, turn, answeredWith(error));
|
|
406
764
|
}
|
|
407
765
|
});
|
|
408
766
|
}
|
|
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
767
|
started(conversation) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const turn = this.begin(conversation);
|
|
437
|
-
const queued = conversation.queued.shift();
|
|
438
|
-
if (queued !== undefined)
|
|
439
|
-
this.attach(turn, queued);
|
|
768
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
769
|
+
this.took(turn);
|
|
440
770
|
return turn;
|
|
441
771
|
}
|
|
442
772
|
ended(conversation) {
|
|
443
773
|
const turn = conversation.turn;
|
|
444
774
|
if (turn === null)
|
|
445
775
|
return;
|
|
446
|
-
if (turn.
|
|
447
|
-
this.end(conversation, turn,
|
|
776
|
+
if (!turn.prompted) {
|
|
777
|
+
this.end(conversation, turn, null);
|
|
448
778
|
}
|
|
449
779
|
else {
|
|
780
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
781
|
+
this.apply(conversation, turn, event);
|
|
450
782
|
conversation.turn = null;
|
|
451
783
|
this.kit.changed();
|
|
452
784
|
}
|
|
@@ -495,65 +827,114 @@ export class Conversations {
|
|
|
495
827
|
if (content !== null)
|
|
496
828
|
this.questions.get(interactionId)?.answer({ action: 'accept', content });
|
|
497
829
|
}
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
830
|
+
read(conversation, reader, message) {
|
|
831
|
+
for (const event of reader.read(message))
|
|
832
|
+
this.event(conversation, event);
|
|
833
|
+
}
|
|
834
|
+
event(conversation, event) {
|
|
835
|
+
switch (event.kind) {
|
|
836
|
+
case 'started':
|
|
837
|
+
this.started(conversation);
|
|
838
|
+
return;
|
|
839
|
+
case 'ended':
|
|
840
|
+
this.ended(conversation);
|
|
841
|
+
return;
|
|
842
|
+
case 'commands':
|
|
843
|
+
this.commands(conversation, event.commands);
|
|
844
|
+
return;
|
|
845
|
+
case 'options':
|
|
846
|
+
this.options(conversation, event.options);
|
|
847
|
+
return;
|
|
848
|
+
case 'context':
|
|
849
|
+
conversation.context = { used: event.used, window: event.window };
|
|
850
|
+
conversation.turn?.parts.measure(conversation.context);
|
|
851
|
+
return;
|
|
852
|
+
case 'subagent-done':
|
|
853
|
+
this.subagentDone(conversation, event.id, event.result);
|
|
854
|
+
return;
|
|
855
|
+
case 'marker':
|
|
856
|
+
case 'image':
|
|
857
|
+
if (conversation.turn !== null)
|
|
858
|
+
break;
|
|
859
|
+
if (event.kind === 'marker' && event.marker === 'job')
|
|
860
|
+
conversation.held.push({ type: 'marker', marker: 'job', text: event.text });
|
|
861
|
+
return;
|
|
515
862
|
}
|
|
516
|
-
|
|
517
|
-
|
|
863
|
+
this.apply(conversation, this.started(conversation), event);
|
|
864
|
+
}
|
|
865
|
+
took(turn) {
|
|
866
|
+
const taken = turn.taken;
|
|
867
|
+
turn.taken = null;
|
|
868
|
+
taken?.();
|
|
869
|
+
}
|
|
870
|
+
apply(conversation, turn, event) {
|
|
871
|
+
switch (event.kind) {
|
|
872
|
+
case 'text':
|
|
873
|
+
case 'reasoning':
|
|
874
|
+
turn.parts.write(event.kind, event.id, event.text);
|
|
875
|
+
return;
|
|
876
|
+
case 'command':
|
|
877
|
+
turn.parts.command();
|
|
878
|
+
return;
|
|
879
|
+
case 'subagent': {
|
|
880
|
+
const index = turn.parts.start({ type: 'subagent', description: event.description, status: 'running', result: null });
|
|
881
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index });
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
case 'message':
|
|
885
|
+
turn.parts.start({ type: 'message', command: event.command, target: event.target });
|
|
886
|
+
return;
|
|
887
|
+
case 'marker':
|
|
888
|
+
turn.parts.start({ type: 'marker', marker: event.marker, text: event.text });
|
|
889
|
+
return;
|
|
890
|
+
case 'plan':
|
|
891
|
+
turn.parts.plan(event.entries);
|
|
892
|
+
return;
|
|
893
|
+
case 'image':
|
|
894
|
+
turn.uploads.push(this.image(conversation, turn, turn.parts.reserve(), event.data, event.mediaType));
|
|
895
|
+
this.kit.changed();
|
|
896
|
+
return;
|
|
518
897
|
}
|
|
519
898
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
899
|
+
subagentDone(conversation, id, result) {
|
|
900
|
+
const subagent = conversation.subagents.get(id);
|
|
901
|
+
if (subagent === undefined)
|
|
902
|
+
return;
|
|
903
|
+
conversation.subagents.delete(id);
|
|
904
|
+
if (subagent.turn === conversation.turn) {
|
|
905
|
+
subagent.turn.parts.done(subagent.index, result);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const part = { type: 'subagent', description: subagent.description, status: 'done', result };
|
|
909
|
+
if (conversation.turn === null)
|
|
910
|
+
conversation.held.push(part);
|
|
523
911
|
else
|
|
524
|
-
conversation.
|
|
525
|
-
this.kit.changed();
|
|
912
|
+
conversation.turn.parts.start(part);
|
|
526
913
|
}
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
914
|
+
async image(conversation, turn, reserved, data, mediaType) {
|
|
915
|
+
const bytes = Buffer.from(data, 'base64');
|
|
916
|
+
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
917
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
918
|
+
try {
|
|
919
|
+
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
920
|
+
name,
|
|
921
|
+
media_type: mediaType,
|
|
922
|
+
bytes: bytes.length,
|
|
923
|
+
sha256,
|
|
924
|
+
}));
|
|
925
|
+
const saved = await uploaded(declared.upload, {
|
|
926
|
+
name,
|
|
927
|
+
bytes: bytes.length,
|
|
928
|
+
sha256,
|
|
929
|
+
read: (start, end) => [bytes.subarray(start, end)],
|
|
930
|
+
send: (url, start, end, headers) => sentPart(url, bytes.subarray(start, end), headers),
|
|
931
|
+
finish: (upload, partSha256) => this.kit.house.deliver('/kit/uploads/finish', { upload, part_sha256: partSha256 }),
|
|
932
|
+
});
|
|
933
|
+
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
542
934
|
}
|
|
543
|
-
|
|
544
|
-
|
|
935
|
+
catch (error) {
|
|
936
|
+
const cause = error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
937
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${cause}` }, reserved);
|
|
545
938
|
}
|
|
546
939
|
}
|
|
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
940
|
}
|