@agentshouse/kit 0.2.1-ca0e1517cce79 → 0.2.1-ca1f38641026d
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 +6 -6
- package/dist/acp.js +57 -6
- package/dist/agents.js +12 -21
- package/dist/authority-main.js +2 -2
- package/dist/building.js +46 -0
- package/dist/calls.js +11 -3
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +37 -13
- package/dist/connection.js +19 -29
- package/dist/conversations.js +140 -40
- package/dist/end.js +8 -2
- package/dist/events.js +29 -8
- package/dist/git.js +2 -2
- package/dist/house-cli.js +3 -4
- package/dist/instructions.js +4 -4
- package/dist/kit-cli.js +2 -2
- package/dist/login.js +3 -1
- package/dist/mcp.js +2 -0
- package/dist/relay.js +21 -0
- package/dist/resident.js +17 -12
- 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 +51 -8
- package/package.json +11 -12
- package/dist/bootstrap.json +0 -24
- package/dist/web-wizard.js +0 -100
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,7 +70,9 @@ 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)}`;
|
|
@@ -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,6 +1004,13 @@ 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]}`;
|
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
|
-
|
|
17
|
+
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_routine_request', ['routine', 'agent']],
|
|
8
|
+
['cancel_routine_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);
|
package/dist/git.js
CHANGED
|
@@ -2,8 +2,8 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { isUtf8 } from 'node:buffer';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { withoutProxy } from './clis.js';
|
|
6
5
|
import { workspaceRoot } from './home.js';
|
|
6
|
+
import { childEnvironment } from './relay.js';
|
|
7
7
|
export function localCopies() {
|
|
8
8
|
return join(workspaceRoot(), 'local-copies');
|
|
9
9
|
}
|
|
@@ -11,7 +11,7 @@ export const HOUSE_REF = 'refs/house/received';
|
|
|
11
11
|
const COMMITTER = 'Kit <house-kit@localhost>';
|
|
12
12
|
export function run(cwd, args, input, env = {}) {
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
14
|
-
const child = spawn('git', args, { cwd, env: { ...
|
|
14
|
+
const child = spawn('git', args, { cwd, env: { ...childEnvironment(), ...env } });
|
|
15
15
|
const stdout = [];
|
|
16
16
|
let stderr = '';
|
|
17
17
|
child.stdout.on('data', (chunk) => stdout.push(chunk));
|
package/dist/house-cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
-
import { request
|
|
3
|
+
import { request } from 'node:http';
|
|
4
4
|
import { request as httpsRequest } from 'node:https';
|
|
5
5
|
import { extname, join, resolve } from 'node:path';
|
|
6
6
|
import { Readable } from 'node:stream';
|
|
@@ -10,7 +10,7 @@ import { HOUSE_REF } from './git.js';
|
|
|
10
10
|
import { ownerSocket, readOwnAgent } from './home.js';
|
|
11
11
|
import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
|
|
12
12
|
import { refusalOf, retryable } from './refusals.js';
|
|
13
|
-
import { relayed, selectRelay } from './relay.js';
|
|
13
|
+
import { proxyUnlessRelayed, relayed, selectRelay } from './relay.js';
|
|
14
14
|
import { parse } from 'yaml';
|
|
15
15
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
16
16
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
@@ -89,7 +89,6 @@ async function direct(message) {
|
|
|
89
89
|
if (ownAgent === null)
|
|
90
90
|
throw new Error(UNCONNECTED);
|
|
91
91
|
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
92
|
-
setGlobalProxyFromEnv();
|
|
93
92
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
94
93
|
throw new Unsettled(UNREACHABLE);
|
|
95
94
|
});
|
|
@@ -120,7 +119,7 @@ function opened(url, method, headers, body) {
|
|
|
120
119
|
const local = socketPath !== undefined;
|
|
121
120
|
const target = local ? null : relayed(url);
|
|
122
121
|
if (!local)
|
|
123
|
-
|
|
122
|
+
proxyUnlessRelayed();
|
|
124
123
|
const sent = target === null
|
|
125
124
|
? request({ socketPath, path: url, method, headers }, answered)
|
|
126
125
|
: (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
|
package/dist/instructions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agentInstructions } from '@agentshouse/skills';
|
|
2
2
|
import { HOUSE_REF } from './git.js';
|
|
3
|
-
const
|
|
3
|
+
const OWNER_RULES = '/rooms/private/owner-rules.md';
|
|
4
4
|
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
5
5
|
const FAILED = /^exit: [1-9]\d*$/;
|
|
6
6
|
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
@@ -8,9 +8,9 @@ function cutOf(lines) {
|
|
|
8
8
|
const cut = CUT.exec(lines[0] ?? '');
|
|
9
9
|
return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
|
|
10
10
|
}
|
|
11
|
-
async function
|
|
11
|
+
async function readOwnerRules(bridge) {
|
|
12
12
|
let document = '';
|
|
13
|
-
for (let command = `cat ${
|
|
13
|
+
for (let command = `cat ${OWNER_RULES}`; command !== undefined;) {
|
|
14
14
|
const read = await bridge.tool('shell', { command });
|
|
15
15
|
const text = read.content.map((content) => content.text).join('\n');
|
|
16
16
|
const lines = text.split('\n');
|
|
@@ -28,5 +28,5 @@ async function readHowWeWork(bridge) {
|
|
|
28
28
|
}
|
|
29
29
|
export async function instructions(bridge, base, copies) {
|
|
30
30
|
const lines = agentInstructions(HOUSE_REF, copies);
|
|
31
|
-
return [lines.join('\n'), base, (await
|
|
31
|
+
return [lines.join('\n'), base, (await readOwnerRules(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
|
|
32
32
|
}
|
package/dist/kit-cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { parseArgs } from 'node:util';
|
|
3
2
|
import { answerGitCredential } from './app-git.js';
|
|
4
3
|
import { chatgpt } from './chatgpt.js';
|
|
5
4
|
import { login } from './login.js';
|
|
6
5
|
import { logout } from './logout.js';
|
|
6
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
7
7
|
import { resident } from './resident.js';
|
|
8
8
|
const USAGE = 'usage: kit <login|logout|resident|chatgpt>\n';
|
|
9
9
|
const COMMANDS = new Map([
|
|
@@ -36,7 +36,7 @@ const COMMANDS = new Map([
|
|
|
36
36
|
],
|
|
37
37
|
]);
|
|
38
38
|
const [command, ...argv] = process.argv.slice(2);
|
|
39
|
-
|
|
39
|
+
proxyUnlessRelayed();
|
|
40
40
|
async function run() {
|
|
41
41
|
if (command === 'login')
|
|
42
42
|
return login(argv);
|
package/dist/login.js
CHANGED
|
@@ -10,6 +10,7 @@ import { refusalOf } from './refusals.js';
|
|
|
10
10
|
import { selectRelay } from './relay.js';
|
|
11
11
|
import { startService } from './service.js';
|
|
12
12
|
import { placeSkillSet } from './skills.js';
|
|
13
|
+
import { endUnserved } from './wire.js';
|
|
13
14
|
export const DEFAULT_HOUSE = 'https://agents.house';
|
|
14
15
|
const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
15
16
|
// Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
|
|
@@ -25,6 +26,7 @@ export async function accepted(house, credential) {
|
|
|
25
26
|
return true;
|
|
26
27
|
if (checked.status === 401)
|
|
27
28
|
return false;
|
|
29
|
+
endUnserved(checked.text);
|
|
28
30
|
throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
|
|
29
31
|
}
|
|
30
32
|
const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private]
|
|
@@ -119,7 +121,7 @@ export async function login(argv) {
|
|
|
119
121
|
open(started.start_url);
|
|
120
122
|
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
121
123
|
}
|
|
122
|
-
await placeSkillSet();
|
|
124
|
+
await placeSkillSet(house);
|
|
123
125
|
await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
124
126
|
const relayRecord = relay === undefined ? {} : { relay };
|
|
125
127
|
await writeEnrolment({
|
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { KIT_RELEASE } from './clis.js';
|
|
3
3
|
import { relayed } from './relay.js';
|
|
4
|
+
import { KIT_WIRE, KIT_WIRE_HEADER } from './wire.js';
|
|
4
5
|
const MCP_PROTOCOL_VERSION = '2026-07-28';
|
|
5
6
|
export const UNREACHABLE = 'House is unreachable; call again in a minute';
|
|
6
7
|
export class Unsettled extends Error {
|
|
@@ -33,6 +34,7 @@ export async function callHouse(origin, credential, message, operation = null, s
|
|
|
33
34
|
'mcp-protocol-version': MCP_PROTOCOL_VERSION,
|
|
34
35
|
'mcp-method': message.method,
|
|
35
36
|
...(tool === null ? {} : { 'mcp-name': tool }),
|
|
37
|
+
[KIT_WIRE_HEADER]: String(KIT_WIRE),
|
|
36
38
|
},
|
|
37
39
|
body: mcpBody(message, operation),
|
|
38
40
|
signal,
|