@agentshouse/kit 0.2.1-c7f53ff9eaf98 → 0.2.1-c845e172df1af
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 +17 -17
- package/dist/acp.js +36 -16
- package/dist/agents.js +36 -16
- package/dist/api.js +5 -5
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +1 -1
- package/dist/calls.js +63 -0
- package/dist/clis.js +1 -1
- package/dist/{stream.js → connection.js} +32 -30
- package/dist/conversations.js +242 -116
- package/dist/copies.js +53 -51
- package/dist/end.js +6 -2
- package/dist/events.js +2 -2
- package/dist/files.js +116 -27
- package/dist/git.js +1 -1
- package/dist/house-cli.js +95 -59
- package/dist/instructions.js +2 -2
- package/dist/kit-cli.js +2 -2
- package/dist/mcp.js +4 -2
- package/dist/placement.js +11 -2
- package/dist/resident.js +32 -30
- package/dist/scope.js +3 -0
- package/dist/service.js +23 -1
- package/dist/sign-in.js +19 -37
- package/dist/skills.js +3 -3
- package/dist/translate.js +2 -8
- package/dist/update.js +42 -45
- package/dist/wire.js +19 -3
- package/package.json +3 -3
package/dist/conversations.js
CHANGED
|
@@ -4,13 +4,16 @@ import { mkdir } from 'node:fs/promises';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
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
10
|
import { answeredFailure, readerFor } from './events.js';
|
|
10
|
-
import { placeFiles,
|
|
11
|
+
import { placeFiles, storeRefusal, uploaded } from './files.js';
|
|
11
12
|
import { agentBase } from './home.js';
|
|
12
13
|
import { instructions } from './instructions.js';
|
|
13
14
|
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
14
17
|
import { killMarked, Scope } from './scope.js';
|
|
15
18
|
import { holdSecretInput } from './secret-input.js';
|
|
16
19
|
import { createHowWeWork } from './skills.js';
|
|
@@ -19,10 +22,17 @@ import { workFor } from './work.js';
|
|
|
19
22
|
const SIGNED_OUT = RequestError.authRequired().code;
|
|
20
23
|
const KILLED = 'the conversation was killed';
|
|
21
24
|
const REFUSED = 'the CLI refused the prompt';
|
|
22
|
-
const NO_CONNECTION = "the
|
|
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
|
+
};
|
|
23
32
|
const NO_START = "the Agent's CLI did not start";
|
|
24
33
|
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
25
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";
|
|
26
36
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
27
37
|
class NotStarted extends Error {
|
|
28
38
|
}
|
|
@@ -31,9 +41,20 @@ async function step(reason, work) {
|
|
|
31
41
|
return await work;
|
|
32
42
|
}
|
|
33
43
|
catch (error) {
|
|
34
|
-
throw new NotStarted(reason, { cause: error });
|
|
44
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
35
45
|
}
|
|
36
46
|
}
|
|
47
|
+
function signedOut(error) {
|
|
48
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
49
|
+
}
|
|
50
|
+
function unopened(reason) {
|
|
51
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
52
|
+
}
|
|
53
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
54
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
55
|
+
function unbridged(error) {
|
|
56
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
57
|
+
}
|
|
37
58
|
const EXIT_PLAN_MODE = {
|
|
38
59
|
parse(value) {
|
|
39
60
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
@@ -42,13 +63,12 @@ const EXIT_PLAN_MODE = {
|
|
|
42
63
|
return value;
|
|
43
64
|
},
|
|
44
65
|
};
|
|
45
|
-
export const
|
|
66
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
46
67
|
function causeOf(error) {
|
|
47
68
|
return error instanceof Error ? error.message : String(error);
|
|
48
69
|
}
|
|
49
70
|
function answeredWith(error) {
|
|
50
|
-
|
|
51
|
-
return { failure: signedOut ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
71
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
52
72
|
}
|
|
53
73
|
function exitedWith(running, cause) {
|
|
54
74
|
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
@@ -56,6 +76,12 @@ function exitedWith(running, cause) {
|
|
|
56
76
|
function answerMessage(request, response) {
|
|
57
77
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
58
78
|
}
|
|
79
|
+
async function sentPart(url, bytes) {
|
|
80
|
+
const answer = await fetch(relayed(url), { method: 'PUT', body: bytes });
|
|
81
|
+
await answer.arrayBuffer();
|
|
82
|
+
if (!answer.ok)
|
|
83
|
+
throw storeRefusal('upload', answer.status);
|
|
84
|
+
}
|
|
59
85
|
function logged(error) {
|
|
60
86
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
61
87
|
}
|
|
@@ -72,10 +98,10 @@ function marker(bridge) {
|
|
|
72
98
|
class Turn {
|
|
73
99
|
id = randomUUID();
|
|
74
100
|
parts;
|
|
75
|
-
admitted = false;
|
|
76
101
|
questions = 0;
|
|
77
102
|
secrets = 0;
|
|
78
103
|
prompted = false;
|
|
104
|
+
taken = null;
|
|
79
105
|
ended = false;
|
|
80
106
|
images = 0;
|
|
81
107
|
uploads = [];
|
|
@@ -89,6 +115,8 @@ class Conversation {
|
|
|
89
115
|
session = null;
|
|
90
116
|
turn = null;
|
|
91
117
|
commands = null;
|
|
118
|
+
options = null;
|
|
119
|
+
exitUntold = false;
|
|
92
120
|
context = null;
|
|
93
121
|
leftovers = new Set();
|
|
94
122
|
waiting = [];
|
|
@@ -96,6 +124,7 @@ class Conversation {
|
|
|
96
124
|
subagents = new Map();
|
|
97
125
|
held = [];
|
|
98
126
|
opening = null;
|
|
127
|
+
launch = null;
|
|
99
128
|
prompted = null;
|
|
100
129
|
kills = 0;
|
|
101
130
|
killed = new AbortController();
|
|
@@ -104,6 +133,11 @@ class Conversation {
|
|
|
104
133
|
open = 0;
|
|
105
134
|
queue = Promise.resolve();
|
|
106
135
|
reports = Promise.resolve();
|
|
136
|
+
starting = null;
|
|
137
|
+
preparing = null;
|
|
138
|
+
told = null;
|
|
139
|
+
untold = false;
|
|
140
|
+
shown = null;
|
|
107
141
|
constructor(id) {
|
|
108
142
|
this.id = id;
|
|
109
143
|
}
|
|
@@ -119,9 +153,7 @@ function asksSecret(request) {
|
|
|
119
153
|
export class Conversations {
|
|
120
154
|
kit;
|
|
121
155
|
conversations = new Map();
|
|
122
|
-
acks = new Map();
|
|
123
156
|
questions = new Map();
|
|
124
|
-
carrying = 0;
|
|
125
157
|
reporting = 0;
|
|
126
158
|
watched = false;
|
|
127
159
|
constructor(kit) {
|
|
@@ -129,38 +161,27 @@ export class Conversations {
|
|
|
129
161
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
130
162
|
setInterval(() => this.look(), 2_000);
|
|
131
163
|
}
|
|
132
|
-
|
|
133
|
-
this.
|
|
134
|
-
|
|
135
|
-
(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
conversation.queue = handled.catch(() => undefined);
|
|
150
|
-
const work = handled.then(({ ack }) => ack);
|
|
151
|
-
this.acks.set(input.input_id, work);
|
|
152
|
-
return work;
|
|
153
|
-
})();
|
|
154
|
-
carried
|
|
155
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
156
|
-
.catch(logged)
|
|
157
|
-
.finally(() => {
|
|
158
|
-
this.carrying--;
|
|
159
|
-
this.kit.changed();
|
|
164
|
+
call(call) {
|
|
165
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
166
|
+
const conversation = this.conversation(call.conversation_id);
|
|
167
|
+
if (call.kind === 'kill') {
|
|
168
|
+
conversation.kills++;
|
|
169
|
+
conversation.killed.abort(new Error(KILLED));
|
|
170
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
171
|
+
waiting.settle({ refused: KILLED });
|
|
172
|
+
this.halt(conversation);
|
|
173
|
+
}
|
|
174
|
+
if (call.kind === 'interrupt') {
|
|
175
|
+
conversation.interrupts++;
|
|
176
|
+
conversation.interrupted.abort();
|
|
177
|
+
}
|
|
178
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
179
|
+
conversation.queue = handled.catch(() => undefined);
|
|
180
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
160
181
|
});
|
|
161
182
|
}
|
|
162
183
|
idle() {
|
|
163
|
-
if (this.
|
|
184
|
+
if (this.reporting > 0)
|
|
164
185
|
return false;
|
|
165
186
|
for (const conversation of this.conversations.values()) {
|
|
166
187
|
const turn = conversation.turn;
|
|
@@ -189,6 +210,14 @@ export class Conversations {
|
|
|
189
210
|
}
|
|
190
211
|
opened() {
|
|
191
212
|
this.watched = false;
|
|
213
|
+
for (const conversation of this.conversations.values()) {
|
|
214
|
+
if (conversation.running !== null)
|
|
215
|
+
this.facts(conversation);
|
|
216
|
+
else if (conversation.exitUntold)
|
|
217
|
+
this.process(conversation, false);
|
|
218
|
+
if (conversation.untold || conversation.told !== null)
|
|
219
|
+
this.retell(conversation);
|
|
220
|
+
}
|
|
192
221
|
}
|
|
193
222
|
watching(watched) {
|
|
194
223
|
const resend = watched && !this.watched;
|
|
@@ -208,50 +237,87 @@ export class Conversations {
|
|
|
208
237
|
}
|
|
209
238
|
return conversation;
|
|
210
239
|
}
|
|
211
|
-
async carry(conversation,
|
|
212
|
-
if (
|
|
213
|
-
return {
|
|
214
|
-
if (
|
|
215
|
-
return this.message(conversation,
|
|
216
|
-
|
|
240
|
+
async carry(conversation, call) {
|
|
241
|
+
if (call.kind === 'open')
|
|
242
|
+
return { result: this.open(conversation, call) };
|
|
243
|
+
if (call.kind === 'message')
|
|
244
|
+
return this.message(conversation, call);
|
|
245
|
+
await this.launched(conversation);
|
|
246
|
+
if (call.kind === 'interrupt') {
|
|
217
247
|
if (--conversation.interrupts === 0)
|
|
218
248
|
conversation.interrupted = new AbortController();
|
|
219
|
-
await this.interrupt(conversation, String(
|
|
249
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
220
250
|
}
|
|
221
|
-
if (
|
|
251
|
+
if (call.kind === 'kill') {
|
|
222
252
|
await this.kill(conversation);
|
|
223
253
|
await conversation.sending;
|
|
224
254
|
if (--conversation.kills === 0)
|
|
225
255
|
conversation.killed = new AbortController();
|
|
226
256
|
}
|
|
227
|
-
if (
|
|
228
|
-
await this.option(conversation, String(
|
|
229
|
-
if (
|
|
230
|
-
const question = this.questions.get(String(
|
|
257
|
+
if (call.kind === 'option')
|
|
258
|
+
await this.option(conversation, String(call.option), call.value);
|
|
259
|
+
if (call.kind === 'answer') {
|
|
260
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
231
261
|
if (question === undefined) {
|
|
232
|
-
const text = answerMessage(
|
|
233
|
-
return this.message(conversation, { ...
|
|
262
|
+
const text = answerMessage(call.request, call.response);
|
|
263
|
+
return this.message(conversation, { ...call, text, files: [], first: false });
|
|
234
264
|
}
|
|
235
|
-
question.answer(
|
|
265
|
+
question.answer(call.response);
|
|
236
266
|
}
|
|
237
|
-
return {
|
|
267
|
+
return { result: Promise.resolve({}) };
|
|
238
268
|
}
|
|
239
|
-
|
|
269
|
+
open(conversation, call) {
|
|
240
270
|
if (conversation.running !== null)
|
|
241
|
-
return {};
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
271
|
+
return Promise.resolve({});
|
|
272
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
273
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
274
|
+
}
|
|
275
|
+
launch(conversation, route, sessionId, empty) {
|
|
276
|
+
let opened;
|
|
277
|
+
let failed;
|
|
278
|
+
const bridge = new Promise((resolve, reject) => {
|
|
279
|
+
opened = resolve;
|
|
280
|
+
failed = reject;
|
|
281
|
+
});
|
|
282
|
+
bridge.catch(() => undefined);
|
|
283
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
284
|
+
session.catch(failed);
|
|
285
|
+
const launch = { bridge, session };
|
|
286
|
+
conversation.launch = launch;
|
|
287
|
+
void session
|
|
288
|
+
.catch(() => undefined)
|
|
289
|
+
.then(() => {
|
|
290
|
+
if (conversation.launch !== launch)
|
|
291
|
+
return;
|
|
292
|
+
conversation.launch = null;
|
|
293
|
+
this.next(conversation);
|
|
294
|
+
});
|
|
295
|
+
this.next(conversation);
|
|
296
|
+
return launch;
|
|
248
297
|
}
|
|
249
|
-
async
|
|
250
|
-
|
|
298
|
+
async launched(conversation) {
|
|
299
|
+
while (conversation.launch !== null)
|
|
300
|
+
await conversation.launch.session.catch(() => undefined);
|
|
301
|
+
}
|
|
302
|
+
instructed(conversation, route) {
|
|
303
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
304
|
+
if (bridge === undefined)
|
|
305
|
+
return null;
|
|
306
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
307
|
+
read.catch(() => undefined);
|
|
308
|
+
return read;
|
|
309
|
+
}
|
|
310
|
+
async message(conversation, call) {
|
|
311
|
+
const content = [{ type: 'text', text: String(call.text) }];
|
|
251
312
|
let route;
|
|
313
|
+
let instructing = null;
|
|
252
314
|
try {
|
|
253
|
-
route = await this.route(conversation,
|
|
254
|
-
|
|
315
|
+
route = await this.route(conversation, call.agent_id);
|
|
316
|
+
if (call.first === true)
|
|
317
|
+
instructing = this.instructed(conversation, route);
|
|
318
|
+
const files = call.files;
|
|
319
|
+
if (files.length > 0)
|
|
320
|
+
this.prepares(conversation, 'files');
|
|
255
321
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
256
322
|
const lines = await this.transcribed(conversation, files, paths);
|
|
257
323
|
if (lines.length > 0)
|
|
@@ -260,11 +326,13 @@ export class Conversations {
|
|
|
260
326
|
throw new Error(KILLED);
|
|
261
327
|
}
|
|
262
328
|
catch (error) {
|
|
263
|
-
|
|
329
|
+
this.prepares(conversation, null);
|
|
330
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
264
331
|
}
|
|
332
|
+
conversation.preparing = null;
|
|
265
333
|
return {
|
|
266
|
-
|
|
267
|
-
conversation.waiting.push({
|
|
334
|
+
result: new Promise((settle) => {
|
|
335
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
268
336
|
this.next(conversation);
|
|
269
337
|
}),
|
|
270
338
|
};
|
|
@@ -273,38 +341,48 @@ export class Conversations {
|
|
|
273
341
|
if (conversation.sending !== null || conversation.open > 0)
|
|
274
342
|
return;
|
|
275
343
|
const waiting = conversation.waiting.shift();
|
|
276
|
-
if (waiting === undefined)
|
|
344
|
+
if (waiting === undefined) {
|
|
345
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
346
|
+
if (conversation.starting !== step)
|
|
347
|
+
this.starts(conversation, step);
|
|
277
348
|
return;
|
|
349
|
+
}
|
|
278
350
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
279
351
|
conversation.sending = null;
|
|
280
352
|
this.next(conversation);
|
|
281
353
|
});
|
|
282
354
|
}
|
|
283
355
|
async send(conversation, waiting) {
|
|
284
|
-
const {
|
|
285
|
-
let
|
|
356
|
+
const { call } = waiting;
|
|
357
|
+
let result = {};
|
|
286
358
|
let started = false;
|
|
359
|
+
let instructing = waiting.instructing;
|
|
287
360
|
const prompt = [];
|
|
288
361
|
try {
|
|
289
362
|
if (conversation.running === null) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
363
|
+
this.starts(conversation, 'session');
|
|
364
|
+
await this.launched(conversation);
|
|
365
|
+
}
|
|
366
|
+
if (conversation.running === null) {
|
|
367
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
368
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
369
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
370
|
+
if (call.first === true)
|
|
371
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
372
|
+
started = (await launch.session) !== stored;
|
|
293
373
|
}
|
|
294
374
|
const session = conversation.running.sessionId;
|
|
295
|
-
if (started || (
|
|
296
|
-
|
|
375
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
376
|
+
result = { provider_session_id: session };
|
|
297
377
|
}
|
|
298
|
-
if (
|
|
299
|
-
prompt.push({
|
|
300
|
-
type: 'text',
|
|
301
|
-
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
302
|
-
});
|
|
378
|
+
if (call.first === true) {
|
|
379
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
303
380
|
}
|
|
304
381
|
if (conversation.kills > 0)
|
|
305
382
|
throw new Error(KILLED);
|
|
306
383
|
}
|
|
307
384
|
catch (error) {
|
|
385
|
+
this.starts(conversation, null);
|
|
308
386
|
if (started) {
|
|
309
387
|
conversation.session = null;
|
|
310
388
|
await this.kill(conversation);
|
|
@@ -312,8 +390,37 @@ export class Conversations {
|
|
|
312
390
|
waiting.settle({ refused: refusal(conversation, error) });
|
|
313
391
|
return;
|
|
314
392
|
}
|
|
315
|
-
this.
|
|
316
|
-
waiting.
|
|
393
|
+
this.starts(conversation, null);
|
|
394
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
395
|
+
waiting.settle(result);
|
|
396
|
+
}
|
|
397
|
+
starts(conversation, step) {
|
|
398
|
+
conversation.starting = step;
|
|
399
|
+
if (conversation.shown !== null)
|
|
400
|
+
clearTimeout(conversation.shown);
|
|
401
|
+
conversation.shown = null;
|
|
402
|
+
if (step === null || conversation.told !== null) {
|
|
403
|
+
this.tell(conversation, step);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
conversation.shown = setTimeout(() => {
|
|
407
|
+
conversation.shown = null;
|
|
408
|
+
if (conversation.starting === step)
|
|
409
|
+
this.tell(conversation, step);
|
|
410
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
411
|
+
}
|
|
412
|
+
prepares(conversation, step) {
|
|
413
|
+
conversation.preparing = step;
|
|
414
|
+
this.next(conversation);
|
|
415
|
+
}
|
|
416
|
+
tell(conversation, step) {
|
|
417
|
+
if (conversation.told === step && !conversation.untold)
|
|
418
|
+
return;
|
|
419
|
+
conversation.told = step;
|
|
420
|
+
this.retell(conversation);
|
|
421
|
+
}
|
|
422
|
+
retell(conversation) {
|
|
423
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
317
424
|
}
|
|
318
425
|
async transcribed(conversation, files, paths) {
|
|
319
426
|
const lines = [];
|
|
@@ -321,6 +428,7 @@ export class Conversations {
|
|
|
321
428
|
lines.push(path);
|
|
322
429
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
323
430
|
continue;
|
|
431
|
+
this.prepares(conversation, 'transcription');
|
|
324
432
|
try {
|
|
325
433
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
326
434
|
if (text !== null && text !== '')
|
|
@@ -387,7 +495,8 @@ export class Conversations {
|
|
|
387
495
|
}
|
|
388
496
|
return route;
|
|
389
497
|
}
|
|
390
|
-
async start(conversation,
|
|
498
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
499
|
+
const route = await routed;
|
|
391
500
|
const reader = readerFor(route.kind);
|
|
392
501
|
reader.session = sessionId;
|
|
393
502
|
const app = client({ name: '@agentshouse/kit' })
|
|
@@ -395,7 +504,7 @@ export class Conversations {
|
|
|
395
504
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
396
505
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
397
506
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
398
|
-
const bridge = await step(
|
|
507
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
399
508
|
if (conversation.kills > 0) {
|
|
400
509
|
bridge.close();
|
|
401
510
|
throw new Error(KILLED);
|
|
@@ -403,11 +512,13 @@ export class Conversations {
|
|
|
403
512
|
conversation.opening = bridge;
|
|
404
513
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
405
514
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
515
|
+
let adapter = null;
|
|
406
516
|
try {
|
|
407
517
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
518
|
+
bridged(bridge);
|
|
408
519
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
409
520
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
410
|
-
|
|
521
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
411
522
|
work.read(message);
|
|
412
523
|
this.read(conversation, reader, message);
|
|
413
524
|
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
@@ -416,26 +527,24 @@ export class Conversations {
|
|
|
416
527
|
const cwd = route.working_directory;
|
|
417
528
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
418
529
|
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
419
|
-
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
530
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
420
531
|
reader.session = opened.sessionId;
|
|
421
532
|
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
422
533
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
423
|
-
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
534
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
424
535
|
}
|
|
425
536
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
426
537
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
427
538
|
void work.settled().then(() => scope.record());
|
|
428
539
|
conversation.running = running;
|
|
429
540
|
conversation.session = running.sessionId;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.options(conversation, options);
|
|
434
|
-
void adapter.exited.then((cause) => this.exited(conversation, adapter, cause));
|
|
541
|
+
conversation.options = options;
|
|
542
|
+
this.facts(conversation);
|
|
543
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
435
544
|
return running.sessionId;
|
|
436
545
|
}
|
|
437
546
|
catch (error) {
|
|
438
|
-
|
|
547
|
+
adapter?.retire();
|
|
439
548
|
work.close();
|
|
440
549
|
conversation.leftovers.add(scope);
|
|
441
550
|
bridge.close();
|
|
@@ -451,7 +560,7 @@ export class Conversations {
|
|
|
451
560
|
}
|
|
452
561
|
catch (error) {
|
|
453
562
|
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
454
|
-
throw new NotStarted(NO_RESUME, { cause: error });
|
|
563
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
455
564
|
}
|
|
456
565
|
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
457
566
|
return null;
|
|
@@ -478,10 +587,7 @@ export class Conversations {
|
|
|
478
587
|
}
|
|
479
588
|
return options;
|
|
480
589
|
}
|
|
481
|
-
exited(conversation,
|
|
482
|
-
const running = conversation.running;
|
|
483
|
-
if (running?.adapter !== adapter)
|
|
484
|
-
return;
|
|
590
|
+
exited(conversation, running, cause) {
|
|
485
591
|
this.detach(conversation, running);
|
|
486
592
|
if (conversation.turn !== null) {
|
|
487
593
|
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
@@ -494,11 +600,22 @@ export class Conversations {
|
|
|
494
600
|
conversation.leftovers.add(running.scope);
|
|
495
601
|
conversation.running = null;
|
|
496
602
|
conversation.commands = null;
|
|
603
|
+
conversation.options = null;
|
|
497
604
|
for (const [id, question] of this.questions) {
|
|
498
605
|
if (question.conversation === conversation)
|
|
499
606
|
this.questions.delete(id);
|
|
500
607
|
}
|
|
501
|
-
this.
|
|
608
|
+
this.process(conversation, false);
|
|
609
|
+
}
|
|
610
|
+
process(conversation, running) {
|
|
611
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
612
|
+
}
|
|
613
|
+
facts(conversation) {
|
|
614
|
+
this.process(conversation, true);
|
|
615
|
+
if (conversation.commands !== null)
|
|
616
|
+
this.commands(conversation, conversation.commands);
|
|
617
|
+
if (conversation.options !== null)
|
|
618
|
+
this.options(conversation, conversation.options);
|
|
502
619
|
}
|
|
503
620
|
commands(conversation, commands) {
|
|
504
621
|
conversation.commands = commands;
|
|
@@ -509,6 +626,7 @@ export class Conversations {
|
|
|
509
626
|
options(conversation, options) {
|
|
510
627
|
if (conversation.running === null)
|
|
511
628
|
return;
|
|
629
|
+
conversation.options = options;
|
|
512
630
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
513
631
|
}
|
|
514
632
|
deliver(conversation, turn, event, body) {
|
|
@@ -523,7 +641,7 @@ export class Conversations {
|
|
|
523
641
|
return delivered;
|
|
524
642
|
}
|
|
525
643
|
write(conversation, turn, operation) {
|
|
526
|
-
if (
|
|
644
|
+
if (turn.ended || !this.watched)
|
|
527
645
|
return;
|
|
528
646
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
529
647
|
}
|
|
@@ -534,12 +652,7 @@ export class Conversations {
|
|
|
534
652
|
begin(conversation) {
|
|
535
653
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
536
654
|
conversation.open++;
|
|
537
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
538
|
-
.then(() => {
|
|
539
|
-
turn.admitted = true;
|
|
540
|
-
this.replay(conversation, turn);
|
|
541
|
-
})
|
|
542
|
-
.catch(logged);
|
|
655
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
543
656
|
conversation.turn = turn;
|
|
544
657
|
if (conversation.context !== null)
|
|
545
658
|
turn.parts.measure(conversation.context);
|
|
@@ -572,12 +685,14 @@ export class Conversations {
|
|
|
572
685
|
this.next(conversation);
|
|
573
686
|
this.kit.changed();
|
|
574
687
|
}
|
|
575
|
-
prompt(conversation, prompt) {
|
|
688
|
+
prompt(conversation, prompt, taken) {
|
|
576
689
|
const running = conversation.running;
|
|
577
690
|
const turn = this.begin(conversation);
|
|
578
691
|
turn.prompted = true;
|
|
692
|
+
turn.taken = taken;
|
|
579
693
|
conversation.prompted = running.sessionId;
|
|
580
694
|
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
695
|
+
this.took(turn);
|
|
581
696
|
const notice = answeredFailure(answer);
|
|
582
697
|
if (notice !== null) {
|
|
583
698
|
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
@@ -590,8 +705,9 @@ export class Conversations {
|
|
|
590
705
|
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
591
706
|
}
|
|
592
707
|
else {
|
|
708
|
+
this.took(turn);
|
|
593
709
|
if (conversation.open === 1 && conversation.running === running) {
|
|
594
|
-
running.
|
|
710
|
+
running.adapter.retire();
|
|
595
711
|
this.detach(conversation, running);
|
|
596
712
|
}
|
|
597
713
|
this.end(conversation, turn, answeredWith(error));
|
|
@@ -599,7 +715,9 @@ export class Conversations {
|
|
|
599
715
|
});
|
|
600
716
|
}
|
|
601
717
|
started(conversation) {
|
|
602
|
-
|
|
718
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
719
|
+
this.took(turn);
|
|
720
|
+
return turn;
|
|
603
721
|
}
|
|
604
722
|
ended(conversation) {
|
|
605
723
|
const turn = conversation.turn;
|
|
@@ -694,6 +812,11 @@ export class Conversations {
|
|
|
694
812
|
}
|
|
695
813
|
this.apply(conversation, this.started(conversation), event);
|
|
696
814
|
}
|
|
815
|
+
took(turn) {
|
|
816
|
+
const taken = turn.taken;
|
|
817
|
+
turn.taken = null;
|
|
818
|
+
taken?.();
|
|
819
|
+
}
|
|
697
820
|
apply(conversation, turn, event) {
|
|
698
821
|
switch (event.kind) {
|
|
699
822
|
case 'text':
|
|
@@ -748,13 +871,16 @@ export class Conversations {
|
|
|
748
871
|
bytes: bytes.length,
|
|
749
872
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
750
873
|
}));
|
|
751
|
-
const saved =
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
874
|
+
const saved = await uploaded(declared.upload, {
|
|
875
|
+
bytes: bytes.length,
|
|
876
|
+
send: (url, start, end) => sentPart(url, bytes.subarray(start, end)),
|
|
877
|
+
finish: (upload) => this.kit.house.deliver('/kit/uploads/finish', { upload }),
|
|
878
|
+
});
|
|
879
|
+
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
755
880
|
}
|
|
756
881
|
catch (error) {
|
|
757
|
-
|
|
882
|
+
const cause = error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
883
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${cause}` }, reserved);
|
|
758
884
|
}
|
|
759
885
|
}
|
|
760
886
|
}
|