@agentshouse/kit 0.2.1-c441d29eb63f4 → 0.2.1-c4613862fad84
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 +14 -14
- package/dist/acp.js +57 -6
- package/dist/agents.js +54 -31
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +50 -0
- package/dist/calls.js +11 -3
- package/dist/clis.js +28 -37
- package/dist/connection.js +9 -30
- package/dist/conversations.js +149 -45
- package/dist/end.js +8 -2
- package/dist/events.js +29 -8
- package/dist/files.js +33 -17
- package/dist/git.js +2 -2
- package/dist/home.js +0 -7
- package/dist/house-cli.js +24 -11
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +4 -4
- package/dist/kit-cli.js +2 -2
- package/dist/kit-main.js +3 -3
- package/dist/login.js +3 -1
- package/dist/mcp.js +2 -0
- package/dist/placement.js +10 -18
- package/dist/relay.js +21 -0
- package/dist/resident.js +19 -15
- package/dist/scope.js +12 -4
- package/dist/scripts.js +255 -0
- package/dist/sign-in.js +3 -2
- package/dist/silence.js +28 -0
- package/dist/skills.js +38 -12
- package/dist/transcription.js +32 -23
- package/dist/update.js +109 -120
- package/dist/wire.js +23 -5
- package/package.json +16 -17
- package/dist/bootstrap.json +0 -24
- package/dist/clis-main.js +0 -23
- package/dist/web-wizard.js +0 -100
package/dist/connection.js
CHANGED
|
@@ -3,40 +3,13 @@ import { HouseRefusal, retryDelay } from './api.js';
|
|
|
3
3
|
import { end, REPLACED, signInAgain } from './end.js';
|
|
4
4
|
import { readEnrolment } from './home.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
6
|
-
import {
|
|
6
|
+
import { Silence } from './silence.js';
|
|
7
|
+
import { endUnserved, KIT_WIRE, unserved } from './wire.js';
|
|
7
8
|
const PATH = '/kit/stream';
|
|
8
9
|
const SUBPROTOCOL = `house.kit.stream.${KIT_WIRE}`;
|
|
9
10
|
const REFUSAL_HEADER = 'x-house-refusal';
|
|
10
11
|
// House beats each Kit connection every thirty seconds, so two silent beats mean this Kit process cannot hear House.
|
|
11
12
|
export const CONNECTION_SILENCE_MS = 60_000;
|
|
12
|
-
const SYSTEM_CONNECTION_CLOCK = {
|
|
13
|
-
later: (delay, callback) => setTimeout(callback, delay),
|
|
14
|
-
clear: (timer) => clearTimeout(timer),
|
|
15
|
-
};
|
|
16
|
-
export class ConnectionSilence {
|
|
17
|
-
timer = null;
|
|
18
|
-
silent;
|
|
19
|
-
clock;
|
|
20
|
-
interval;
|
|
21
|
-
constructor(silent, clock = SYSTEM_CONNECTION_CLOCK, interval = CONNECTION_SILENCE_MS) {
|
|
22
|
-
this.silent = silent;
|
|
23
|
-
this.clock = clock;
|
|
24
|
-
this.interval = interval;
|
|
25
|
-
}
|
|
26
|
-
heard() {
|
|
27
|
-
this.stop();
|
|
28
|
-
this.timer = this.clock.later(this.interval, () => {
|
|
29
|
-
this.timer = null;
|
|
30
|
-
this.silent();
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
stop() {
|
|
34
|
-
if (this.timer === null)
|
|
35
|
-
return;
|
|
36
|
-
this.clock.clear(this.timer);
|
|
37
|
-
this.timer = null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
13
|
function connectionUrl(house) {
|
|
41
14
|
const url = new URL(PATH, house);
|
|
42
15
|
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
@@ -58,19 +31,25 @@ subscribe('undici:request:headers', (message) => {
|
|
|
58
31
|
});
|
|
59
32
|
export function holdConnection(handlers) {
|
|
60
33
|
let connection = null;
|
|
34
|
+
let socket = null;
|
|
61
35
|
let failures = 0;
|
|
62
36
|
const connect = async () => {
|
|
63
37
|
const { house, credential } = (await readEnrolment());
|
|
38
|
+
if (socket !== null || unserved())
|
|
39
|
+
return;
|
|
64
40
|
refused = null;
|
|
65
41
|
const opening = new WebSocket(relayed(connectionUrl(house)), {
|
|
66
42
|
protocols: [SUBPROTOCOL],
|
|
67
43
|
headers: { authorization: `Bearer ${credential}` },
|
|
68
44
|
});
|
|
45
|
+
socket = opening;
|
|
69
46
|
let opened = false;
|
|
70
47
|
let silence = null;
|
|
71
48
|
const closed = (event) => {
|
|
72
49
|
silence?.stop();
|
|
73
50
|
silence = null;
|
|
51
|
+
if (socket === opening)
|
|
52
|
+
socket = null;
|
|
74
53
|
if (connection === opening)
|
|
75
54
|
connection = null;
|
|
76
55
|
handlers.closed();
|
|
@@ -92,7 +71,7 @@ export function holdConnection(handlers) {
|
|
|
92
71
|
opened = true;
|
|
93
72
|
failures = 0;
|
|
94
73
|
connection = opening;
|
|
95
|
-
silence = new
|
|
74
|
+
silence = new Silence(CONNECTION_SILENCE_MS, () => {
|
|
96
75
|
if (connection !== opening)
|
|
97
76
|
return;
|
|
98
77
|
connection = null;
|
package/dist/conversations.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { client,
|
|
1
|
+
import { client, } from '@agentclientprotocol/sdk';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { unlessAborted } from './abort.js';
|
|
6
|
-
import { startAdapter } from './acp.js';
|
|
6
|
+
import { signedOut, startAdapter } from './acp.js';
|
|
7
7
|
import { finalRefusal, HouseRefusal } from './api.js';
|
|
8
8
|
import { openBridge } from './bridge.js';
|
|
9
9
|
import { CLIS } from './clis.js';
|
|
@@ -18,7 +18,6 @@ import { killMarked, Scope } from './scope.js';
|
|
|
18
18
|
import { holdSecretInput } from './secret-input.js';
|
|
19
19
|
import { transcribe } from './transcription.js';
|
|
20
20
|
import { workFor } from './work.js';
|
|
21
|
-
const SIGNED_OUT = RequestError.authRequired().code;
|
|
22
21
|
const KILLED = 'the conversation was killed';
|
|
23
22
|
const INTERRUPTED = 'the conversation was interrupted';
|
|
24
23
|
const REFUSED = 'the CLI refused the prompt';
|
|
@@ -34,6 +33,7 @@ const NO_SESSION = "the Agent's CLI did not open a session";
|
|
|
34
33
|
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
35
34
|
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
36
35
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
36
|
+
const UNSTEERED = "the Agent's CLI did not take the message into its running turn";
|
|
37
37
|
class NotStarted extends Error {
|
|
38
38
|
}
|
|
39
39
|
class ResumeRefused extends NotStarted {
|
|
@@ -46,9 +46,6 @@ async function step(reason, work) {
|
|
|
46
46
|
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
function signedOut(error) {
|
|
50
|
-
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
51
|
-
}
|
|
52
49
|
function unopened(reason) {
|
|
53
50
|
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
54
51
|
}
|
|
@@ -65,7 +62,7 @@ const EXIT_PLAN_MODE = {
|
|
|
65
62
|
return value;
|
|
66
63
|
},
|
|
67
64
|
};
|
|
68
|
-
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
65
|
+
export const CONVERSATION_METHODS = new Set(['open', 'message', 'interrupt', 'kill', 'done', 'option', 'answer']);
|
|
69
66
|
function causeOf(error) {
|
|
70
67
|
return error instanceof Error ? error.message : String(error);
|
|
71
68
|
}
|
|
@@ -73,13 +70,15 @@ function answeredWith(error) {
|
|
|
73
70
|
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
74
71
|
}
|
|
75
72
|
function exitedWith(running, cause) {
|
|
76
|
-
|
|
73
|
+
if (running.killed)
|
|
74
|
+
return { failure: 'killed', cause: KILLED };
|
|
75
|
+
return { failure: running.adapter.silent() ? 'silent' : 'exited', cause };
|
|
77
76
|
}
|
|
78
77
|
function answerMessage(request, response) {
|
|
79
78
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
80
79
|
}
|
|
81
|
-
async function sentPart(url, bytes) {
|
|
82
|
-
const answer = await fetch(relayed(url), { method: 'PUT', body: bytes });
|
|
80
|
+
async function sentPart(url, bytes, headers) {
|
|
81
|
+
const answer = await fetch(relayed(url), { method: 'PUT', headers, body: bytes });
|
|
83
82
|
await answer.arrayBuffer();
|
|
84
83
|
if (!answer.ok)
|
|
85
84
|
throw storeRefusal('upload', answer.status);
|
|
@@ -103,12 +102,16 @@ function audioOf(result) {
|
|
|
103
102
|
function marker(bridge) {
|
|
104
103
|
return `HOUSE_BRIDGE=${bridge.env.HOUSE_BRIDGE}`;
|
|
105
104
|
}
|
|
105
|
+
function steers(adapter) {
|
|
106
|
+
return adapter.initialized._meta?.steering?.supported === true;
|
|
107
|
+
}
|
|
106
108
|
class Turn {
|
|
107
109
|
id = randomUUID();
|
|
108
110
|
parts;
|
|
109
111
|
questions = 0;
|
|
110
112
|
secrets = 0;
|
|
111
113
|
prompted = false;
|
|
114
|
+
steered = false;
|
|
112
115
|
taken = null;
|
|
113
116
|
ended = false;
|
|
114
117
|
images = 0;
|
|
@@ -128,6 +131,7 @@ class Conversation {
|
|
|
128
131
|
context = null;
|
|
129
132
|
leftovers = new Set();
|
|
130
133
|
waiting = [];
|
|
134
|
+
done = false;
|
|
131
135
|
sending = null;
|
|
132
136
|
subagents = new Map();
|
|
133
137
|
held = [];
|
|
@@ -139,7 +143,7 @@ class Conversation {
|
|
|
139
143
|
killed = new AbortController();
|
|
140
144
|
interrupts = 0;
|
|
141
145
|
interrupted = new AbortController();
|
|
142
|
-
|
|
146
|
+
turns = new Set();
|
|
143
147
|
queue = Promise.resolve();
|
|
144
148
|
reports = Promise.resolve();
|
|
145
149
|
starting = null;
|
|
@@ -195,7 +199,7 @@ export class Conversations {
|
|
|
195
199
|
for (const conversation of this.conversations.values()) {
|
|
196
200
|
const turn = conversation.turn;
|
|
197
201
|
const asking = turn !== null && turn.questions > 0 && turn.secrets === 0;
|
|
198
|
-
if (conversation.
|
|
202
|
+
if (conversation.turns.size > (asking ? 1 : 0))
|
|
199
203
|
return false;
|
|
200
204
|
}
|
|
201
205
|
const conversations = [...this.conversations.values()];
|
|
@@ -204,7 +208,7 @@ export class Conversations {
|
|
|
204
208
|
return !conversations.some((conversation) => conversation.running?.scope.working() || [...conversation.leftovers].some((scope) => scope.working()));
|
|
205
209
|
}
|
|
206
210
|
quiet() {
|
|
207
|
-
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.
|
|
211
|
+
return this.idle() && [...this.conversations.values()].every((conversation) => conversation.turns.size === 0);
|
|
208
212
|
}
|
|
209
213
|
look() {
|
|
210
214
|
for (const conversation of this.conversations.values()) {
|
|
@@ -220,8 +224,10 @@ export class Conversations {
|
|
|
220
224
|
opened() {
|
|
221
225
|
this.watched = false;
|
|
222
226
|
for (const conversation of this.conversations.values()) {
|
|
223
|
-
if (conversation.running !== null)
|
|
227
|
+
if (conversation.running !== null) {
|
|
224
228
|
this.facts(conversation);
|
|
229
|
+
this.subagentsChanged(conversation);
|
|
230
|
+
}
|
|
225
231
|
else if (conversation.exitUntold)
|
|
226
232
|
this.process(conversation, false);
|
|
227
233
|
if (conversation.untold || conversation.told !== null)
|
|
@@ -248,9 +254,14 @@ export class Conversations {
|
|
|
248
254
|
}
|
|
249
255
|
async carry(conversation, call) {
|
|
250
256
|
if (call.kind === 'open')
|
|
251
|
-
return
|
|
257
|
+
return this.open(conversation, call);
|
|
252
258
|
if (call.kind === 'message')
|
|
253
259
|
return this.message(conversation, call);
|
|
260
|
+
if (call.kind === 'done') {
|
|
261
|
+
conversation.done = true;
|
|
262
|
+
this.next(conversation);
|
|
263
|
+
return { result: Promise.resolve({}) };
|
|
264
|
+
}
|
|
254
265
|
await this.launched(conversation);
|
|
255
266
|
if (call.kind === 'interrupt') {
|
|
256
267
|
if (--conversation.interrupts === 0)
|
|
@@ -275,11 +286,17 @@ export class Conversations {
|
|
|
275
286
|
}
|
|
276
287
|
return { result: Promise.resolve({}) };
|
|
277
288
|
}
|
|
278
|
-
open(conversation, call) {
|
|
279
|
-
|
|
280
|
-
return Promise.resolve({});
|
|
289
|
+
async open(conversation, call) {
|
|
290
|
+
await this.stop(conversation);
|
|
281
291
|
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, call.agent_id), null, true);
|
|
282
|
-
return
|
|
292
|
+
return {
|
|
293
|
+
result: launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) })),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async stop(conversation) {
|
|
297
|
+
const running = conversation.running;
|
|
298
|
+
running?.scope.kill();
|
|
299
|
+
await running?.adapter.exited;
|
|
283
300
|
}
|
|
284
301
|
launch(conversation, route, sessionId, empty) {
|
|
285
302
|
let opened;
|
|
@@ -318,7 +335,10 @@ export class Conversations {
|
|
|
318
335
|
return read;
|
|
319
336
|
}
|
|
320
337
|
async message(conversation, call) {
|
|
321
|
-
const content = [
|
|
338
|
+
const content = [
|
|
339
|
+
...(typeof call.recent === 'string' ? [{ type: 'text', text: call.recent }] : []),
|
|
340
|
+
{ type: 'text', text: String(call.text) },
|
|
341
|
+
];
|
|
322
342
|
let route;
|
|
323
343
|
let instructing = null;
|
|
324
344
|
try {
|
|
@@ -336,21 +356,71 @@ export class Conversations {
|
|
|
336
356
|
return { result: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
337
357
|
}
|
|
338
358
|
conversation.preparing = null;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
359
|
+
const { promise, resolve: settle } = Promise.withResolvers();
|
|
360
|
+
const waiting = { call, route, content, instructing, settle };
|
|
361
|
+
if (call.at_once === true && conversation.turns.size > 0)
|
|
362
|
+
await this.atOnce(conversation, waiting);
|
|
363
|
+
else
|
|
364
|
+
conversation.waiting.push(waiting);
|
|
365
|
+
this.next(conversation);
|
|
366
|
+
return { result: promise };
|
|
367
|
+
}
|
|
368
|
+
async atOnce(conversation, waiting) {
|
|
369
|
+
while (conversation.sending !== null)
|
|
370
|
+
await conversation.sending;
|
|
371
|
+
const running = conversation.running;
|
|
372
|
+
if (running !== null && conversation.turns.size > 0 && steers(running.adapter)) {
|
|
373
|
+
conversation.sending = this.steer(conversation, running, waiting);
|
|
374
|
+
await conversation.sending;
|
|
375
|
+
conversation.sending = null;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.ahead(conversation, waiting);
|
|
379
|
+
if (conversation.turns.size > 0)
|
|
380
|
+
await this.cancel(conversation);
|
|
381
|
+
}
|
|
382
|
+
ahead(conversation, waiting) {
|
|
383
|
+
const behind = conversation.waiting.findIndex((held) => held.call.at_once !== true);
|
|
384
|
+
conversation.waiting.splice(behind === -1 ? conversation.waiting.length : behind, 0, waiting);
|
|
385
|
+
}
|
|
386
|
+
async steer(conversation, running, waiting) {
|
|
387
|
+
let outcome;
|
|
388
|
+
try {
|
|
389
|
+
({ outcome } = await running.adapter.connection.agent.request('_session/steering', {
|
|
390
|
+
sessionId: running.sessionId,
|
|
391
|
+
prompt: waiting.content,
|
|
392
|
+
_meta: { steering: { idleBehavior: 'promptRequired' } },
|
|
393
|
+
}));
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
outcome = error;
|
|
397
|
+
}
|
|
398
|
+
if (outcome === 'promptRequired') {
|
|
399
|
+
this.ahead(conversation, waiting);
|
|
400
|
+
}
|
|
401
|
+
else if (outcome === 'injected' || outcome === 'startedNewTurn') {
|
|
402
|
+
if (outcome === 'injected') {
|
|
403
|
+
for (const turn of conversation.turns)
|
|
404
|
+
turn.steered ||= turn.prompted;
|
|
405
|
+
}
|
|
406
|
+
this.kit.calls.keepResult(String(waiting.call.input_id), {});
|
|
407
|
+
waiting.settle({});
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const cause = outcome instanceof Error ? outcome : new Error(`the CLI answered ${JSON.stringify(outcome)}`);
|
|
411
|
+
waiting.settle({ refused: refusal(conversation, new NotStarted(UNSTEERED, { cause })) });
|
|
412
|
+
}
|
|
345
413
|
}
|
|
346
414
|
next(conversation) {
|
|
347
|
-
if (conversation.sending !== null || conversation.
|
|
415
|
+
if (conversation.sending !== null || conversation.turns.size > 0)
|
|
348
416
|
return;
|
|
349
417
|
const waiting = conversation.waiting.shift();
|
|
350
418
|
if (waiting === undefined) {
|
|
351
419
|
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
352
420
|
if (conversation.starting !== step)
|
|
353
421
|
this.starts(conversation, step);
|
|
422
|
+
if (conversation.done && conversation.launch === null)
|
|
423
|
+
void this.stop(conversation).catch(logged);
|
|
354
424
|
return;
|
|
355
425
|
}
|
|
356
426
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
@@ -491,7 +561,11 @@ export class Conversations {
|
|
|
491
561
|
void bridging.then((bridge) => bridge.close(), () => undefined);
|
|
492
562
|
}
|
|
493
563
|
async interrupt(conversation, turnId) {
|
|
494
|
-
if (conversation.
|
|
564
|
+
if ([...conversation.turns].some((turn) => turn.id === turnId))
|
|
565
|
+
await this.cancel(conversation);
|
|
566
|
+
}
|
|
567
|
+
async cancel(conversation) {
|
|
568
|
+
if (conversation.running === null)
|
|
495
569
|
return;
|
|
496
570
|
await conversation.running.adapter.connection.agent.notify('session/cancel', {
|
|
497
571
|
sessionId: conversation.running.sessionId,
|
|
@@ -585,6 +659,7 @@ export class Conversations {
|
|
|
585
659
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
586
660
|
void work.settled().then(() => scope.record());
|
|
587
661
|
conversation.running = running;
|
|
662
|
+
adapter.turn(conversation.turns.size > 0);
|
|
588
663
|
conversation.session = running.sessionId;
|
|
589
664
|
conversation.options = options;
|
|
590
665
|
this.facts(conversation);
|
|
@@ -651,6 +726,10 @@ export class Conversations {
|
|
|
651
726
|
conversation.running = null;
|
|
652
727
|
conversation.commands = null;
|
|
653
728
|
conversation.options = null;
|
|
729
|
+
if (conversation.subagents.size > 0) {
|
|
730
|
+
conversation.subagents.clear();
|
|
731
|
+
this.subagentsChanged(conversation);
|
|
732
|
+
}
|
|
654
733
|
for (const [id, question] of this.questions) {
|
|
655
734
|
if (question.conversation === conversation)
|
|
656
735
|
this.questions.delete(id);
|
|
@@ -667,6 +746,10 @@ export class Conversations {
|
|
|
667
746
|
if (conversation.options !== null)
|
|
668
747
|
this.options(conversation, conversation.options);
|
|
669
748
|
}
|
|
749
|
+
subagentsChanged(conversation) {
|
|
750
|
+
const subagents = [...conversation.subagents.values()].map(({ description, activity }) => ({ description, activity }));
|
|
751
|
+
this.kit.send({ type: 'subagents', conversation_id: conversation.id, subagents });
|
|
752
|
+
}
|
|
670
753
|
commands(conversation, commands) {
|
|
671
754
|
conversation.commands = commands;
|
|
672
755
|
if (conversation.running === null)
|
|
@@ -701,7 +784,8 @@ export class Conversations {
|
|
|
701
784
|
}
|
|
702
785
|
begin(conversation) {
|
|
703
786
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
704
|
-
conversation.
|
|
787
|
+
conversation.turns.add(turn);
|
|
788
|
+
conversation.running?.adapter.turn(true);
|
|
705
789
|
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
706
790
|
conversation.turn = turn;
|
|
707
791
|
if (conversation.context !== null)
|
|
@@ -723,7 +807,8 @@ export class Conversations {
|
|
|
723
807
|
if (notice !== null)
|
|
724
808
|
turn.parts.start({ type: 'marker', marker: 'retry', text: notice });
|
|
725
809
|
turn.ended = true;
|
|
726
|
-
conversation.
|
|
810
|
+
conversation.turns.delete(turn);
|
|
811
|
+
conversation.running?.adapter.turn(conversation.turns.size > 0);
|
|
727
812
|
if (conversation.turn === turn)
|
|
728
813
|
conversation.turn = null;
|
|
729
814
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
@@ -756,7 +841,7 @@ export class Conversations {
|
|
|
756
841
|
}
|
|
757
842
|
else {
|
|
758
843
|
this.took(turn);
|
|
759
|
-
if (conversation.
|
|
844
|
+
if (conversation.turns.size === 1 && conversation.turns.has(turn) && conversation.running === running) {
|
|
760
845
|
running.adapter.retire();
|
|
761
846
|
this.detach(conversation, running);
|
|
762
847
|
}
|
|
@@ -769,19 +854,22 @@ export class Conversations {
|
|
|
769
854
|
this.took(turn);
|
|
770
855
|
return turn;
|
|
771
856
|
}
|
|
772
|
-
ended(conversation) {
|
|
857
|
+
ended(conversation, stop) {
|
|
773
858
|
const turn = conversation.turn;
|
|
774
859
|
if (turn === null)
|
|
775
860
|
return;
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
this.apply(conversation, turn, event);
|
|
782
|
-
conversation.turn = null;
|
|
783
|
-
this.kit.changed();
|
|
861
|
+
const held = stop !== null && !turn.steered && [...conversation.subagents.values()].some((subagent) => subagent.turn === turn);
|
|
862
|
+
if (!turn.prompted || held) {
|
|
863
|
+
this.took(turn);
|
|
864
|
+
this.end(conversation, turn, stop === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
865
|
+
return;
|
|
784
866
|
}
|
|
867
|
+
if (stop !== null)
|
|
868
|
+
return;
|
|
869
|
+
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
870
|
+
this.apply(conversation, turn, event);
|
|
871
|
+
conversation.turn = null;
|
|
872
|
+
this.kit.changed();
|
|
785
873
|
}
|
|
786
874
|
async ask(conversation, method, params, secret, signal) {
|
|
787
875
|
const turn = this.started(conversation);
|
|
@@ -837,7 +925,7 @@ export class Conversations {
|
|
|
837
925
|
this.started(conversation);
|
|
838
926
|
return;
|
|
839
927
|
case 'ended':
|
|
840
|
-
this.ended(conversation);
|
|
928
|
+
this.ended(conversation, event.stop);
|
|
841
929
|
return;
|
|
842
930
|
case 'commands':
|
|
843
931
|
this.commands(conversation, event.commands);
|
|
@@ -852,6 +940,9 @@ export class Conversations {
|
|
|
852
940
|
case 'subagent-done':
|
|
853
941
|
this.subagentDone(conversation, event.id, event.result);
|
|
854
942
|
return;
|
|
943
|
+
case 'subagent-activity':
|
|
944
|
+
this.subagentActivity(conversation, event.id, event.activity);
|
|
945
|
+
return;
|
|
855
946
|
case 'marker':
|
|
856
947
|
case 'image':
|
|
857
948
|
if (conversation.turn !== null)
|
|
@@ -878,7 +969,8 @@ export class Conversations {
|
|
|
878
969
|
return;
|
|
879
970
|
case 'subagent': {
|
|
880
971
|
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 });
|
|
972
|
+
conversation.subagents.set(event.id, { description: event.description, turn, index, activity: null });
|
|
973
|
+
this.subagentsChanged(conversation);
|
|
882
974
|
return;
|
|
883
975
|
}
|
|
884
976
|
case 'message':
|
|
@@ -901,6 +993,7 @@ export class Conversations {
|
|
|
901
993
|
if (subagent === undefined)
|
|
902
994
|
return;
|
|
903
995
|
conversation.subagents.delete(id);
|
|
996
|
+
this.subagentsChanged(conversation);
|
|
904
997
|
if (subagent.turn === conversation.turn) {
|
|
905
998
|
subagent.turn.parts.done(subagent.index, result);
|
|
906
999
|
return;
|
|
@@ -911,20 +1004,31 @@ export class Conversations {
|
|
|
911
1004
|
else
|
|
912
1005
|
conversation.turn.parts.start(part);
|
|
913
1006
|
}
|
|
1007
|
+
subagentActivity(conversation, id, activity) {
|
|
1008
|
+
const subagent = conversation.subagents.get(id);
|
|
1009
|
+
if (subagent === undefined || subagent.activity === activity)
|
|
1010
|
+
return;
|
|
1011
|
+
subagent.activity = activity;
|
|
1012
|
+
this.subagentsChanged(conversation);
|
|
1013
|
+
}
|
|
914
1014
|
async image(conversation, turn, reserved, data, mediaType) {
|
|
915
1015
|
const bytes = Buffer.from(data, 'base64');
|
|
916
1016
|
const name = `image-${++turn.images}.${mediaType.split('/')[1]}`;
|
|
1017
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
917
1018
|
try {
|
|
918
1019
|
const declared = (await this.deliver(conversation, turn, 'images', {
|
|
919
1020
|
name,
|
|
920
1021
|
media_type: mediaType,
|
|
921
1022
|
bytes: bytes.length,
|
|
922
|
-
sha256
|
|
1023
|
+
sha256,
|
|
923
1024
|
}));
|
|
924
1025
|
const saved = await uploaded(declared.upload, {
|
|
1026
|
+
name,
|
|
925
1027
|
bytes: bytes.length,
|
|
926
|
-
|
|
927
|
-
|
|
1028
|
+
sha256,
|
|
1029
|
+
read: (start, end) => [bytes.subarray(start, end)],
|
|
1030
|
+
send: (url, start, end, headers) => sentPart(url, bytes.subarray(start, end), headers),
|
|
1031
|
+
finish: (upload, partSha256) => this.kit.house.deliver('/kit/uploads/finish', { upload, part_sha256: partSha256 }),
|
|
928
1032
|
});
|
|
929
1033
|
turn.parts.arrive({ type: 'image', file: saved.path, name, media_type: mediaType }, reserved);
|
|
930
1034
|
}
|
package/dist/end.js
CHANGED
|
@@ -14,9 +14,15 @@ export function end(reason) {
|
|
|
14
14
|
export function restart(reason) {
|
|
15
15
|
exit(reason, 1);
|
|
16
16
|
}
|
|
17
|
-
export function
|
|
17
|
+
export function updateLine(lead) {
|
|
18
18
|
const command = installCommand();
|
|
19
|
-
|
|
19
|
+
return command === null ? `${lead}; install it again.` : `${lead}. Update it: ${command}`;
|
|
20
|
+
}
|
|
21
|
+
export function updateRequired() {
|
|
22
|
+
end(updateLine('This Kit is too old for House'));
|
|
23
|
+
}
|
|
24
|
+
export function unknownMethod(kind) {
|
|
25
|
+
return updateLine(`This Kit does not know ${kind}`);
|
|
20
26
|
}
|
|
21
27
|
export function newerThanHouse(release, command) {
|
|
22
28
|
end(`Kit ${KIT_RELEASE} is newer than this Building supports (Kit ${release}). Install Kit ${release}: ${command}`);
|
package/dist/events.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
const AUTONOMOUS_ORIGINS = new Set(['task-notification', 'peer', 'coordinator', 'observer', 'observer-activity']);
|
|
1
2
|
const COMMAND = { shown: 'command' };
|
|
2
3
|
const HIDDEN = { shown: 'hidden' };
|
|
3
4
|
const FINISHED = new Set(['completed', 'failed']);
|
|
4
5
|
export const RUNNING_JOB = new Set(['running', 'paused']);
|
|
5
|
-
const MESSAGE_TARGETS =
|
|
6
|
+
const MESSAGE_TARGETS = new Map([
|
|
7
|
+
['send_agent_function_request', ['agent_function', 'agent']],
|
|
8
|
+
['cancel_agent_function_request', ['request']],
|
|
9
|
+
['send_mail', ['conversation', 'request']],
|
|
10
|
+
]);
|
|
6
11
|
function words(line) {
|
|
7
12
|
const found = [];
|
|
8
13
|
let word = null;
|
|
@@ -48,11 +53,13 @@ export function houseMessage(line) {
|
|
|
48
53
|
catch {
|
|
49
54
|
return null;
|
|
50
55
|
}
|
|
51
|
-
|
|
52
|
-
if (argument === undefined)
|
|
56
|
+
if (call === null)
|
|
53
57
|
return null;
|
|
54
|
-
const
|
|
55
|
-
|
|
58
|
+
const targets = MESSAGE_TARGETS.get(call.command);
|
|
59
|
+
if (targets === undefined)
|
|
60
|
+
return null;
|
|
61
|
+
const target = targets.map((argument) => call.arguments?.[argument]).find((value) => typeof value === 'string');
|
|
62
|
+
return { shown: 'message', command: call.command, target: target ?? null };
|
|
56
63
|
}
|
|
57
64
|
function decidedByLine(command, final) {
|
|
58
65
|
const line = typeof command === 'string' ? command : Array.isArray(command) ? command.at(-1) : undefined;
|
|
@@ -269,6 +276,7 @@ class Reader {
|
|
|
269
276
|
class ClaudeReader extends Reader {
|
|
270
277
|
watchedOptions = { model: 'model' };
|
|
271
278
|
tasks = new Map();
|
|
279
|
+
stop = null;
|
|
272
280
|
own(message) {
|
|
273
281
|
if (message.params?.update?._meta?.claudeCode?.parentToolUseId !== undefined)
|
|
274
282
|
return [];
|
|
@@ -276,7 +284,9 @@ class ClaudeReader extends Reader {
|
|
|
276
284
|
}
|
|
277
285
|
update(update) {
|
|
278
286
|
if (update.sessionUpdate === 'usage_update' && update._meta?.['_claude/origin'] !== undefined) {
|
|
279
|
-
|
|
287
|
+
const stop = this.stop;
|
|
288
|
+
this.stop = null;
|
|
289
|
+
return [...super.update(update), { kind: 'ended', stop }];
|
|
280
290
|
}
|
|
281
291
|
if (update.sessionUpdate === 'session_info_update' && update._meta?.jetbrains?.air?.sessionFailure !== undefined) {
|
|
282
292
|
return [{ kind: 'marker', marker: 'retry', text: update._meta.jetbrains.air.sessionFailure.title }];
|
|
@@ -287,6 +297,13 @@ class ClaudeReader extends Reader {
|
|
|
287
297
|
if (message.method !== '_claude/sdkMessage')
|
|
288
298
|
return [];
|
|
289
299
|
const sdk = message.params.message;
|
|
300
|
+
if (sdk.type === 'result') {
|
|
301
|
+
if (AUTONOMOUS_ORIGINS.has(sdk.origin?.kind))
|
|
302
|
+
this.stop = null;
|
|
303
|
+
else
|
|
304
|
+
this.stop = sdk.stop_reason === 'refusal' ? 'refusal' : sdk.is_error === true ? null : 'end_turn';
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
290
307
|
switch (sdk.subtype) {
|
|
291
308
|
case 'task_started':
|
|
292
309
|
if (sdk.tool_use_id !== undefined)
|
|
@@ -294,6 +311,10 @@ class ClaudeReader extends Reader {
|
|
|
294
311
|
return [];
|
|
295
312
|
case 'task_notification':
|
|
296
313
|
return sdk.tool_use_id === undefined ? [] : this.finish(sdk.tool_use_id, sdk.summary ?? null);
|
|
314
|
+
case 'task_progress':
|
|
315
|
+
if (!this.subagents.has(sdk.tool_use_id) || this.done.has(sdk.tool_use_id) || typeof sdk.description !== 'string')
|
|
316
|
+
return [];
|
|
317
|
+
return [{ kind: 'subagent-activity', id: sdk.tool_use_id, activity: sdk.description }];
|
|
297
318
|
default:
|
|
298
319
|
return [];
|
|
299
320
|
}
|
|
@@ -356,7 +377,7 @@ class CodexReader extends Reader {
|
|
|
356
377
|
if (codex?.threadStatus?.type === 'active')
|
|
357
378
|
return [{ kind: 'started' }];
|
|
358
379
|
if (codex?.threadStatus?.type === 'idle')
|
|
359
|
-
return [{ kind: 'ended' }];
|
|
380
|
+
return [{ kind: 'ended', stop: null }];
|
|
360
381
|
if (codex?.error !== undefined)
|
|
361
382
|
return [{ kind: 'marker', marker: 'retry', text: codex.error.message ?? '' }];
|
|
362
383
|
return [];
|
|
@@ -429,7 +450,7 @@ class GrokReader extends Reader {
|
|
|
429
450
|
return (message.params.runningPromptId ?? null) !== null && session === this.session ? [{ kind: 'started' }] : [];
|
|
430
451
|
}
|
|
431
452
|
if (message.params?.update?.sessionUpdate === 'turn_completed') {
|
|
432
|
-
return session === this.session ? [{ kind: 'ended' }] : [];
|
|
453
|
+
return session === this.session ? [{ kind: 'ended', stop: null }] : [];
|
|
433
454
|
}
|
|
434
455
|
if (message.method === '_x.ai/models/update') {
|
|
435
456
|
this.offered(message.params);
|