@agentshouse/kit 0.2.1-cde47fd72fe59 → 0.2.1-ce3d2bb67177c
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 +18 -18
- package/dist/acp.js +2 -2
- package/dist/agents.js +2 -2
- package/dist/api.js +5 -5
- package/dist/bootstrap.json +4 -4
- package/dist/calls.js +63 -0
- package/dist/clis.js +1 -1
- package/dist/{stream.js → connection.js} +29 -27
- package/dist/conversations.js +289 -101
- package/dist/copies.js +15 -29
- package/dist/end.js +6 -2
- package/dist/events.js +2 -2
- package/dist/files.js +123 -37
- 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/login.js +1 -3
- package/dist/mcp.js +4 -2
- package/dist/placement.js +11 -2
- package/dist/resident.js +32 -32
- package/dist/scope.js +3 -0
- package/dist/service.js +1 -1
- package/dist/sign-in.js +19 -19
- package/dist/skills.js +3 -3
- package/dist/transcription.js +51 -20
- package/dist/update.js +32 -32
- package/dist/wire.js +19 -3
- package/package.json +2 -2
- package/dist/inputs.js +0 -37
package/dist/conversations.js
CHANGED
|
@@ -4,15 +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 } from './api.js';
|
|
7
|
+
import { finalRefusal, HouseRefusal } from './api.js';
|
|
8
8
|
import { openBridge } from './bridge.js';
|
|
9
9
|
import { CLIS } from './clis.js';
|
|
10
10
|
import { answeredFailure, readerFor } from './events.js';
|
|
11
|
-
import {
|
|
11
|
+
import { inspectedFile, linkedPaths, storeRefusal, uploaded } from './files.js';
|
|
12
12
|
import { agentBase } from './home.js';
|
|
13
13
|
import { instructions } from './instructions.js';
|
|
14
14
|
import { Parts } from './parts.js';
|
|
15
15
|
import { refusalOf } from './refusals.js';
|
|
16
|
+
import { relayed } from './relay.js';
|
|
16
17
|
import { killMarked, Scope } from './scope.js';
|
|
17
18
|
import { holdSecretInput } from './secret-input.js';
|
|
18
19
|
import { createHowWeWork } from './skills.js';
|
|
@@ -20,6 +21,7 @@ import { transcribe } from './transcription.js';
|
|
|
20
21
|
import { workFor } from './work.js';
|
|
21
22
|
const SIGNED_OUT = RequestError.authRequired().code;
|
|
22
23
|
const KILLED = 'the conversation was killed';
|
|
24
|
+
const INTERRUPTED = 'the conversation was interrupted';
|
|
23
25
|
const REFUSED = 'the CLI refused the prompt';
|
|
24
26
|
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
27
|
const GONE = 'House no longer holds this conversation; start a new one';
|
|
@@ -31,9 +33,12 @@ const ENDED = {
|
|
|
31
33
|
const NO_START = "the Agent's CLI did not start";
|
|
32
34
|
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
33
35
|
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
36
|
+
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
34
37
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
35
38
|
class NotStarted extends Error {
|
|
36
39
|
}
|
|
40
|
+
class ResumeRefused extends NotStarted {
|
|
41
|
+
}
|
|
37
42
|
async function step(reason, work) {
|
|
38
43
|
try {
|
|
39
44
|
return await work;
|
|
@@ -42,6 +47,14 @@ async function step(reason, work) {
|
|
|
42
47
|
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
43
48
|
}
|
|
44
49
|
}
|
|
50
|
+
function signedOut(error) {
|
|
51
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
52
|
+
}
|
|
53
|
+
function unopened(reason) {
|
|
54
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
55
|
+
}
|
|
56
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
57
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
45
58
|
function unbridged(error) {
|
|
46
59
|
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
47
60
|
}
|
|
@@ -53,13 +66,12 @@ const EXIT_PLAN_MODE = {
|
|
|
53
66
|
return value;
|
|
54
67
|
},
|
|
55
68
|
};
|
|
56
|
-
export const
|
|
69
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
57
70
|
function causeOf(error) {
|
|
58
71
|
return error instanceof Error ? error.message : String(error);
|
|
59
72
|
}
|
|
60
73
|
function answeredWith(error) {
|
|
61
|
-
|
|
62
|
-
return { failure: signedOut ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
74
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
63
75
|
}
|
|
64
76
|
function exitedWith(running, cause) {
|
|
65
77
|
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
@@ -67,16 +79,28 @@ function exitedWith(running, cause) {
|
|
|
67
79
|
function answerMessage(request, response) {
|
|
68
80
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
69
81
|
}
|
|
82
|
+
async function sentPart(url, bytes) {
|
|
83
|
+
const answer = await fetch(relayed(url), { method: 'PUT', body: bytes });
|
|
84
|
+
await answer.arrayBuffer();
|
|
85
|
+
if (!answer.ok)
|
|
86
|
+
throw storeRefusal('upload', answer.status);
|
|
87
|
+
}
|
|
70
88
|
function logged(error) {
|
|
71
89
|
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
72
90
|
}
|
|
73
91
|
function refusal(conversation, error) {
|
|
74
92
|
if (conversation.kills > 0)
|
|
75
93
|
return KILLED;
|
|
76
|
-
const cause = error instanceof
|
|
94
|
+
const cause = error instanceof Error && error.cause !== undefined ? `${error.message}: ${causeOf(error.cause)}` : causeOf(error);
|
|
77
95
|
logged(new Error(`conversation ${conversation.id} did not start: ${cause}`));
|
|
78
96
|
return causeOf(error);
|
|
79
97
|
}
|
|
98
|
+
function audioOf(result) {
|
|
99
|
+
if (result.isError === true)
|
|
100
|
+
return null;
|
|
101
|
+
const file = inspectedFile(result.content.map((content) => content.text).join('\n'));
|
|
102
|
+
return file?.media_type?.startsWith('audio/') === true ? file : null;
|
|
103
|
+
}
|
|
80
104
|
function marker(bridge) {
|
|
81
105
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
82
106
|
}
|
|
@@ -86,6 +110,7 @@ class Turn {
|
|
|
86
110
|
questions = 0;
|
|
87
111
|
secrets = 0;
|
|
88
112
|
prompted = false;
|
|
113
|
+
taken = null;
|
|
89
114
|
ended = false;
|
|
90
115
|
images = 0;
|
|
91
116
|
uploads = [];
|
|
@@ -99,6 +124,8 @@ class Conversation {
|
|
|
99
124
|
session = null;
|
|
100
125
|
turn = null;
|
|
101
126
|
commands = null;
|
|
127
|
+
options = null;
|
|
128
|
+
exitUntold = false;
|
|
102
129
|
context = null;
|
|
103
130
|
leftovers = new Set();
|
|
104
131
|
waiting = [];
|
|
@@ -106,6 +133,8 @@ class Conversation {
|
|
|
106
133
|
subagents = new Map();
|
|
107
134
|
held = [];
|
|
108
135
|
opening = null;
|
|
136
|
+
bridge = null;
|
|
137
|
+
launch = null;
|
|
109
138
|
prompted = null;
|
|
110
139
|
kills = 0;
|
|
111
140
|
killed = new AbortController();
|
|
@@ -114,6 +143,11 @@ class Conversation {
|
|
|
114
143
|
open = 0;
|
|
115
144
|
queue = Promise.resolve();
|
|
116
145
|
reports = Promise.resolve();
|
|
146
|
+
starting = null;
|
|
147
|
+
preparing = null;
|
|
148
|
+
told = null;
|
|
149
|
+
untold = false;
|
|
150
|
+
shown = null;
|
|
117
151
|
constructor(id) {
|
|
118
152
|
this.id = id;
|
|
119
153
|
}
|
|
@@ -137,23 +171,23 @@ export class Conversations {
|
|
|
137
171
|
// A process or a durable schedule ends without a report, so the Kit looks at both again every two seconds.
|
|
138
172
|
setInterval(() => this.look(), 2_000);
|
|
139
173
|
}
|
|
140
|
-
|
|
141
|
-
void this.kit.
|
|
142
|
-
const conversation = this.conversation(
|
|
143
|
-
if (
|
|
174
|
+
call(call) {
|
|
175
|
+
void this.kit.calls.carry(call.input_id, () => {
|
|
176
|
+
const conversation = this.conversation(call.conversation_id);
|
|
177
|
+
if (call.kind === 'kill') {
|
|
144
178
|
conversation.kills++;
|
|
145
179
|
conversation.killed.abort(new Error(KILLED));
|
|
146
180
|
for (const waiting of conversation.waiting.splice(0))
|
|
147
181
|
waiting.settle({ refused: KILLED });
|
|
148
182
|
this.halt(conversation);
|
|
149
183
|
}
|
|
150
|
-
if (
|
|
184
|
+
if (call.kind === 'interrupt') {
|
|
151
185
|
conversation.interrupts++;
|
|
152
186
|
conversation.interrupted.abort();
|
|
153
187
|
}
|
|
154
|
-
const handled = conversation.queue.then(() => this.carry(conversation,
|
|
188
|
+
const handled = conversation.queue.then(() => this.carry(conversation, call));
|
|
155
189
|
conversation.queue = handled.catch(() => undefined);
|
|
156
|
-
return handled.then(async ({
|
|
190
|
+
return handled.then(async ({ result }) => ({ result: await result }));
|
|
157
191
|
});
|
|
158
192
|
}
|
|
159
193
|
idle() {
|
|
@@ -186,6 +220,14 @@ export class Conversations {
|
|
|
186
220
|
}
|
|
187
221
|
opened() {
|
|
188
222
|
this.watched = false;
|
|
223
|
+
for (const conversation of this.conversations.values()) {
|
|
224
|
+
if (conversation.running !== null)
|
|
225
|
+
this.facts(conversation);
|
|
226
|
+
else if (conversation.exitUntold)
|
|
227
|
+
this.process(conversation, false);
|
|
228
|
+
if (conversation.untold || conversation.told !== null)
|
|
229
|
+
this.retell(conversation);
|
|
230
|
+
}
|
|
189
231
|
}
|
|
190
232
|
watching(watched) {
|
|
191
233
|
const resend = watched && !this.watched;
|
|
@@ -205,63 +247,99 @@ export class Conversations {
|
|
|
205
247
|
}
|
|
206
248
|
return conversation;
|
|
207
249
|
}
|
|
208
|
-
async carry(conversation,
|
|
209
|
-
if (
|
|
210
|
-
return {
|
|
211
|
-
if (
|
|
212
|
-
return this.message(conversation,
|
|
213
|
-
|
|
250
|
+
async carry(conversation, call) {
|
|
251
|
+
if (call.kind === 'open')
|
|
252
|
+
return { result: this.open(conversation, call) };
|
|
253
|
+
if (call.kind === 'message')
|
|
254
|
+
return this.message(conversation, call);
|
|
255
|
+
await this.launched(conversation);
|
|
256
|
+
if (call.kind === 'interrupt') {
|
|
214
257
|
if (--conversation.interrupts === 0)
|
|
215
258
|
conversation.interrupted = new AbortController();
|
|
216
|
-
await this.interrupt(conversation, String(
|
|
259
|
+
await this.interrupt(conversation, String(call.turn_id));
|
|
217
260
|
}
|
|
218
|
-
if (
|
|
261
|
+
if (call.kind === 'kill') {
|
|
219
262
|
await this.kill(conversation);
|
|
220
263
|
await conversation.sending;
|
|
221
264
|
if (--conversation.kills === 0)
|
|
222
265
|
conversation.killed = new AbortController();
|
|
223
266
|
}
|
|
224
|
-
if (
|
|
225
|
-
await this.option(conversation, String(
|
|
226
|
-
if (
|
|
227
|
-
const question = this.questions.get(String(
|
|
267
|
+
if (call.kind === 'option')
|
|
268
|
+
await this.option(conversation, String(call.option), call.value);
|
|
269
|
+
if (call.kind === 'answer') {
|
|
270
|
+
const question = this.questions.get(String(call.interaction_id));
|
|
228
271
|
if (question === undefined) {
|
|
229
|
-
const text = answerMessage(
|
|
230
|
-
return this.message(conversation, { ...
|
|
272
|
+
const text = answerMessage(call.request, call.response);
|
|
273
|
+
return this.message(conversation, { ...call, text, first: false, message_id: null });
|
|
231
274
|
}
|
|
232
|
-
question.answer(
|
|
275
|
+
question.answer(call.response);
|
|
233
276
|
}
|
|
234
|
-
return {
|
|
277
|
+
return { result: Promise.resolve({}) };
|
|
235
278
|
}
|
|
236
|
-
|
|
279
|
+
open(conversation, call) {
|
|
237
280
|
if (conversation.running !== null)
|
|
238
|
-
return {};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
281
|
+
return Promise.resolve({});
|
|
282
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
283
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
284
|
+
}
|
|
285
|
+
launch(conversation, route, sessionId, empty) {
|
|
286
|
+
let opened;
|
|
287
|
+
let failed;
|
|
288
|
+
const bridge = new Promise((resolve, reject) => {
|
|
289
|
+
opened = resolve;
|
|
290
|
+
failed = reject;
|
|
291
|
+
});
|
|
292
|
+
bridge.catch(() => undefined);
|
|
293
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
294
|
+
session.catch(failed);
|
|
295
|
+
const launch = { bridge, session };
|
|
296
|
+
conversation.launch = launch;
|
|
297
|
+
void session
|
|
298
|
+
.catch(() => undefined)
|
|
299
|
+
.then(() => {
|
|
300
|
+
if (conversation.launch !== launch)
|
|
301
|
+
return;
|
|
302
|
+
conversation.launch = null;
|
|
303
|
+
this.closeIdleBridge(conversation);
|
|
304
|
+
this.next(conversation);
|
|
305
|
+
});
|
|
306
|
+
this.next(conversation);
|
|
307
|
+
return launch;
|
|
245
308
|
}
|
|
246
|
-
async
|
|
247
|
-
|
|
309
|
+
async launched(conversation) {
|
|
310
|
+
while (conversation.launch !== null)
|
|
311
|
+
await conversation.launch.session.catch(() => undefined);
|
|
312
|
+
}
|
|
313
|
+
instructed(conversation, route) {
|
|
314
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
315
|
+
if (bridge === undefined)
|
|
316
|
+
return null;
|
|
317
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
318
|
+
read.catch(() => undefined);
|
|
319
|
+
return read;
|
|
320
|
+
}
|
|
321
|
+
async message(conversation, call) {
|
|
322
|
+
const content = [{ type: 'text', text: String(call.text) }];
|
|
248
323
|
let route;
|
|
324
|
+
let instructing = null;
|
|
249
325
|
try {
|
|
250
|
-
route = await this.route(conversation,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
content.push({ type: 'text', text: lines.join('\n') });
|
|
326
|
+
route = await this.route(conversation, call.agent_id);
|
|
327
|
+
if (call.first === true)
|
|
328
|
+
instructing = this.instructed(conversation, route);
|
|
329
|
+
if (call.message_id !== null)
|
|
330
|
+
content.push(...(await this.transcribed(conversation, String(call.message_id), String(call.text))));
|
|
256
331
|
if (conversation.kills > 0)
|
|
257
332
|
throw new Error(KILLED);
|
|
258
333
|
}
|
|
259
334
|
catch (error) {
|
|
260
|
-
|
|
335
|
+
this.closeIdleBridge(conversation);
|
|
336
|
+
this.prepares(conversation, null);
|
|
337
|
+
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
261
338
|
}
|
|
339
|
+
conversation.preparing = null;
|
|
262
340
|
return {
|
|
263
|
-
|
|
264
|
-
conversation.waiting.push({
|
|
341
|
+
result: new Promise((settle) => {
|
|
342
|
+
conversation.waiting.push({ call, route, content, instructing, settle });
|
|
265
343
|
this.next(conversation);
|
|
266
344
|
}),
|
|
267
345
|
};
|
|
@@ -270,66 +348,148 @@ export class Conversations {
|
|
|
270
348
|
if (conversation.sending !== null || conversation.open > 0)
|
|
271
349
|
return;
|
|
272
350
|
const waiting = conversation.waiting.shift();
|
|
273
|
-
if (waiting === undefined)
|
|
351
|
+
if (waiting === undefined) {
|
|
352
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
353
|
+
if (conversation.starting !== step)
|
|
354
|
+
this.starts(conversation, step);
|
|
274
355
|
return;
|
|
356
|
+
}
|
|
275
357
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
276
358
|
conversation.sending = null;
|
|
277
359
|
this.next(conversation);
|
|
278
360
|
});
|
|
279
361
|
}
|
|
280
362
|
async send(conversation, waiting) {
|
|
281
|
-
const {
|
|
282
|
-
let
|
|
363
|
+
const { call } = waiting;
|
|
364
|
+
let result = {};
|
|
283
365
|
let started = false;
|
|
366
|
+
let instructing = waiting.instructing;
|
|
284
367
|
const prompt = [];
|
|
285
368
|
try {
|
|
286
369
|
if (conversation.running === null) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
370
|
+
this.starts(conversation, 'session');
|
|
371
|
+
await this.launched(conversation);
|
|
372
|
+
}
|
|
373
|
+
if (conversation.running === null) {
|
|
374
|
+
const stored = conversation.session ?? call.provider_session_id;
|
|
375
|
+
const empty = call.first === true && conversation.prompted !== stored;
|
|
376
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
377
|
+
if (call.first === true)
|
|
378
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
379
|
+
started = (await launch.session) !== stored;
|
|
290
380
|
}
|
|
291
381
|
const session = conversation.running.sessionId;
|
|
292
|
-
if (started || (
|
|
293
|
-
|
|
382
|
+
if (started || (call.first === true && session !== call.provider_session_id)) {
|
|
383
|
+
result = { provider_session_id: session };
|
|
294
384
|
}
|
|
295
|
-
if (
|
|
296
|
-
prompt.push({
|
|
297
|
-
type: 'text',
|
|
298
|
-
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
299
|
-
});
|
|
385
|
+
if (call.first === true) {
|
|
386
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
300
387
|
}
|
|
301
388
|
if (conversation.kills > 0)
|
|
302
389
|
throw new Error(KILLED);
|
|
303
390
|
}
|
|
304
391
|
catch (error) {
|
|
392
|
+
this.starts(conversation, null);
|
|
305
393
|
if (started) {
|
|
306
394
|
conversation.session = null;
|
|
307
395
|
await this.kill(conversation);
|
|
308
396
|
}
|
|
309
|
-
|
|
397
|
+
const refused = refusal(conversation, error);
|
|
398
|
+
const resumeRefused = call.kind === 'message' && conversation.kills === 0 && error instanceof ResumeRefused;
|
|
399
|
+
waiting.settle(resumeRefused ? { refused, resume_refused: true } : { refused });
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
this.starts(conversation, null);
|
|
403
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.calls.keepResult(String(call.input_id), result));
|
|
404
|
+
waiting.settle(result);
|
|
405
|
+
}
|
|
406
|
+
starts(conversation, step) {
|
|
407
|
+
conversation.starting = step;
|
|
408
|
+
if (conversation.shown !== null)
|
|
409
|
+
clearTimeout(conversation.shown);
|
|
410
|
+
conversation.shown = null;
|
|
411
|
+
if (step === null || conversation.told !== null) {
|
|
412
|
+
this.tell(conversation, step);
|
|
310
413
|
return;
|
|
311
414
|
}
|
|
312
|
-
|
|
313
|
-
|
|
415
|
+
conversation.shown = setTimeout(() => {
|
|
416
|
+
conversation.shown = null;
|
|
417
|
+
if (conversation.starting === step)
|
|
418
|
+
this.tell(conversation, step);
|
|
419
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
314
420
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
421
|
+
prepares(conversation, step) {
|
|
422
|
+
conversation.preparing = step;
|
|
423
|
+
this.next(conversation);
|
|
424
|
+
}
|
|
425
|
+
tell(conversation, step) {
|
|
426
|
+
if (conversation.told === step && !conversation.untold)
|
|
427
|
+
return;
|
|
428
|
+
conversation.told = step;
|
|
429
|
+
this.retell(conversation);
|
|
430
|
+
}
|
|
431
|
+
retell(conversation) {
|
|
432
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
433
|
+
}
|
|
434
|
+
async transcribed(conversation, messageId, text) {
|
|
435
|
+
const paths = linkedPaths(text);
|
|
436
|
+
if (paths.length === 0)
|
|
437
|
+
return [];
|
|
438
|
+
const killed = conversation.killed.signal;
|
|
439
|
+
const interrupted = conversation.interrupted.signal;
|
|
440
|
+
const transcribing = AbortSignal.any([killed, interrupted]);
|
|
441
|
+
let signal = killed;
|
|
442
|
+
const blocks = [];
|
|
443
|
+
try {
|
|
444
|
+
const bridge = await step(unbridged, unlessAborted(this.bridged(conversation), killed));
|
|
445
|
+
const read = async (path) => audioOf(await step(unbridged, unlessAborted(bridge.tool('inspect', { path }), signal)));
|
|
446
|
+
for (const path of paths) {
|
|
447
|
+
let answered = await read(path);
|
|
448
|
+
if (answered === null)
|
|
449
|
+
continue;
|
|
450
|
+
signal = transcribing;
|
|
451
|
+
this.prepares(conversation, 'transcription');
|
|
452
|
+
const said = await transcribe(async () => {
|
|
453
|
+
const address = answered ?? (await read(path));
|
|
454
|
+
answered = null;
|
|
455
|
+
if (address === null)
|
|
456
|
+
throw new Error(`${path} is no longer an audio file`);
|
|
457
|
+
return address;
|
|
458
|
+
}, signal);
|
|
459
|
+
const transcript = `/kit/conversations/${conversation.id}/messages/${messageId}/transcripts`;
|
|
460
|
+
await step(unbridged, unlessAborted(this.kit.house.deliver(transcript, { path, text: said }, () => !signal.aborted), signal));
|
|
461
|
+
blocks.push({ type: 'text', text: `${path}\nTranscript: ${said}` });
|
|
330
462
|
}
|
|
331
463
|
}
|
|
332
|
-
|
|
464
|
+
catch (error) {
|
|
465
|
+
if (signal === transcribing && interrupted.aborted && conversation.kills === 0)
|
|
466
|
+
throw new NotStarted(INTERRUPTED, { cause: error });
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
return blocks;
|
|
470
|
+
}
|
|
471
|
+
bridged(conversation) {
|
|
472
|
+
if (conversation.bridge === null) {
|
|
473
|
+
const bridging = openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal);
|
|
474
|
+
conversation.bridge = bridging;
|
|
475
|
+
bridging.catch(() => {
|
|
476
|
+
if (conversation.bridge === bridging)
|
|
477
|
+
conversation.bridge = null;
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
return conversation.bridge;
|
|
481
|
+
}
|
|
482
|
+
closeBridge(conversation, bridging, bridge) {
|
|
483
|
+
bridge.close();
|
|
484
|
+
if (conversation.bridge === bridging)
|
|
485
|
+
conversation.bridge = null;
|
|
486
|
+
}
|
|
487
|
+
closeIdleBridge(conversation) {
|
|
488
|
+
const bridging = conversation.bridge;
|
|
489
|
+
if (bridging === null || conversation.running !== null || conversation.launch !== null)
|
|
490
|
+
return;
|
|
491
|
+
conversation.bridge = null;
|
|
492
|
+
void bridging.then((bridge) => bridge.close(), () => undefined);
|
|
333
493
|
}
|
|
334
494
|
async interrupt(conversation, turnId) {
|
|
335
495
|
if (conversation.running === null || conversation.turn?.id !== turnId)
|
|
@@ -384,7 +544,8 @@ export class Conversations {
|
|
|
384
544
|
}
|
|
385
545
|
return route;
|
|
386
546
|
}
|
|
387
|
-
async start(conversation,
|
|
547
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
548
|
+
const route = await routed;
|
|
388
549
|
const reader = readerFor(route.kind);
|
|
389
550
|
reader.session = sessionId;
|
|
390
551
|
const app = client({ name: '@agentshouse/kit' })
|
|
@@ -392,9 +553,10 @@ export class Conversations {
|
|
|
392
553
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
393
554
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
394
555
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
395
|
-
const
|
|
556
|
+
const bridging = this.bridged(conversation);
|
|
557
|
+
const bridge = await step(unbridged, bridging);
|
|
396
558
|
if (conversation.kills > 0) {
|
|
397
|
-
|
|
559
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
398
560
|
throw new Error(KILLED);
|
|
399
561
|
}
|
|
400
562
|
conversation.opening = bridge;
|
|
@@ -403,32 +565,31 @@ export class Conversations {
|
|
|
403
565
|
let adapter = null;
|
|
404
566
|
try {
|
|
405
567
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
568
|
+
bridged(bridge);
|
|
406
569
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
407
570
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
408
571
|
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
409
572
|
work.read(message);
|
|
410
573
|
this.read(conversation, reader, message);
|
|
411
574
|
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
412
|
-
void adapter.exited.then(() =>
|
|
575
|
+
void adapter.exited.then(() => this.closeBridge(conversation, bridging, bridge));
|
|
413
576
|
const agent = adapter.connection.agent;
|
|
414
577
|
const cwd = route.working_directory;
|
|
415
578
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
416
579
|
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
417
|
-
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
580
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
418
581
|
reader.session = opened.sessionId;
|
|
419
582
|
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
420
583
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
421
|
-
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
584
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
422
585
|
}
|
|
423
|
-
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
586
|
+
const running = { adapter, bridging, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
424
587
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
425
588
|
void work.settled().then(() => scope.record());
|
|
426
589
|
conversation.running = running;
|
|
427
590
|
conversation.session = running.sessionId;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
this.commands(conversation, conversation.commands);
|
|
431
|
-
this.options(conversation, options);
|
|
591
|
+
conversation.options = options;
|
|
592
|
+
this.facts(conversation);
|
|
432
593
|
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
433
594
|
return running.sessionId;
|
|
434
595
|
}
|
|
@@ -436,7 +597,7 @@ export class Conversations {
|
|
|
436
597
|
adapter?.retire();
|
|
437
598
|
work.close();
|
|
438
599
|
conversation.leftovers.add(scope);
|
|
439
|
-
|
|
600
|
+
this.closeBridge(conversation, bridging, bridge);
|
|
440
601
|
throw conversation.kills > 0 ? new Error(KILLED) : error;
|
|
441
602
|
}
|
|
442
603
|
finally {
|
|
@@ -449,7 +610,7 @@ export class Conversations {
|
|
|
449
610
|
}
|
|
450
611
|
catch (error) {
|
|
451
612
|
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
452
|
-
throw new NotStarted(NO_RESUME, { cause: error });
|
|
613
|
+
throw signedOut(error) ? new NotStarted(NOT_SIGNED_IN, { cause: error }) : new ResumeRefused(NO_RESUME, { cause: error });
|
|
453
614
|
}
|
|
454
615
|
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
455
616
|
return null;
|
|
@@ -485,15 +646,28 @@ export class Conversations {
|
|
|
485
646
|
this.kit.changed();
|
|
486
647
|
}
|
|
487
648
|
detach(conversation, running) {
|
|
649
|
+
if (conversation.bridge === running.bridging)
|
|
650
|
+
conversation.bridge = null;
|
|
488
651
|
running.work.close();
|
|
489
652
|
conversation.leftovers.add(running.scope);
|
|
490
653
|
conversation.running = null;
|
|
491
654
|
conversation.commands = null;
|
|
655
|
+
conversation.options = null;
|
|
492
656
|
for (const [id, question] of this.questions) {
|
|
493
657
|
if (question.conversation === conversation)
|
|
494
658
|
this.questions.delete(id);
|
|
495
659
|
}
|
|
496
|
-
this.
|
|
660
|
+
this.process(conversation, false);
|
|
661
|
+
}
|
|
662
|
+
process(conversation, running) {
|
|
663
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
664
|
+
}
|
|
665
|
+
facts(conversation) {
|
|
666
|
+
this.process(conversation, true);
|
|
667
|
+
if (conversation.commands !== null)
|
|
668
|
+
this.commands(conversation, conversation.commands);
|
|
669
|
+
if (conversation.options !== null)
|
|
670
|
+
this.options(conversation, conversation.options);
|
|
497
671
|
}
|
|
498
672
|
commands(conversation, commands) {
|
|
499
673
|
conversation.commands = commands;
|
|
@@ -504,6 +678,7 @@ export class Conversations {
|
|
|
504
678
|
options(conversation, options) {
|
|
505
679
|
if (conversation.running === null)
|
|
506
680
|
return;
|
|
681
|
+
conversation.options = options;
|
|
507
682
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
508
683
|
}
|
|
509
684
|
deliver(conversation, turn, event, body) {
|
|
@@ -562,12 +737,14 @@ export class Conversations {
|
|
|
562
737
|
this.next(conversation);
|
|
563
738
|
this.kit.changed();
|
|
564
739
|
}
|
|
565
|
-
prompt(conversation, prompt) {
|
|
740
|
+
prompt(conversation, prompt, taken) {
|
|
566
741
|
const running = conversation.running;
|
|
567
742
|
const turn = this.begin(conversation);
|
|
568
743
|
turn.prompted = true;
|
|
744
|
+
turn.taken = taken;
|
|
569
745
|
conversation.prompted = running.sessionId;
|
|
570
746
|
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
747
|
+
this.took(turn);
|
|
571
748
|
const notice = answeredFailure(answer);
|
|
572
749
|
if (notice !== null) {
|
|
573
750
|
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
@@ -580,6 +757,7 @@ export class Conversations {
|
|
|
580
757
|
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
581
758
|
}
|
|
582
759
|
else {
|
|
760
|
+
this.took(turn);
|
|
583
761
|
if (conversation.open === 1 && conversation.running === running) {
|
|
584
762
|
running.adapter.retire();
|
|
585
763
|
this.detach(conversation, running);
|
|
@@ -589,7 +767,9 @@ export class Conversations {
|
|
|
589
767
|
});
|
|
590
768
|
}
|
|
591
769
|
started(conversation) {
|
|
592
|
-
|
|
770
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
771
|
+
this.took(turn);
|
|
772
|
+
return turn;
|
|
593
773
|
}
|
|
594
774
|
ended(conversation) {
|
|
595
775
|
const turn = conversation.turn;
|
|
@@ -684,6 +864,11 @@ export class Conversations {
|
|
|
684
864
|
}
|
|
685
865
|
this.apply(conversation, this.started(conversation), event);
|
|
686
866
|
}
|
|
867
|
+
took(turn) {
|
|
868
|
+
const taken = turn.taken;
|
|
869
|
+
turn.taken = null;
|
|
870
|
+
taken?.();
|
|
871
|
+
}
|
|
687
872
|
apply(conversation, turn, event) {
|
|
688
873
|
switch (event.kind) {
|
|
689
874
|
case 'text':
|
|
@@ -738,13 +923,16 @@ export class Conversations {
|
|
|
738
923
|
bytes: bytes.length,
|
|
739
924
|
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
740
925
|
}));
|
|
741
|
-
const saved =
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
926
|
+
const saved = await uploaded(declared.upload, {
|
|
927
|
+
bytes: bytes.length,
|
|
928
|
+
send: (url, start, end) => sentPart(url, bytes.subarray(start, end)),
|
|
929
|
+
finish: (upload) => this.kit.house.deliver('/kit/uploads/finish', { upload }),
|
|
930
|
+
});
|
|
931
|
+
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
745
932
|
}
|
|
746
933
|
catch (error) {
|
|
747
|
-
|
|
934
|
+
const cause = error instanceof HouseRefusal ? (refusalOf(error.text)?.text ?? causeOf(error)) : causeOf(error);
|
|
935
|
+
turn.parts.arrive({ type: 'marker', marker: 'retry', text: `${name} was not saved: ${cause}` }, reserved);
|
|
748
936
|
}
|
|
749
937
|
}
|
|
750
938
|
}
|