@agentshouse/kit 0.2.1-caf8b8e5a324f → 0.2.1-cb345f4157685
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/dist/acp.js +34 -14
- package/dist/agents.js +1 -3
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +1 -1
- package/dist/conversations.js +58 -48
- package/dist/copies.js +17 -8
- package/dist/events.js +2 -1
- package/dist/files.js +1 -1
- package/dist/inputs.js +37 -0
- package/dist/resident.js +9 -5
- package/dist/service.js +22 -0
- package/dist/sign-in.js +5 -23
- package/dist/stream.js +4 -4
- package/dist/update.js +20 -23
- package/dist/wire.js +1 -1
- package/package.json +1 -1
package/dist/acp.js
CHANGED
|
@@ -3,6 +3,7 @@ import { spawn } from 'node:child_process';
|
|
|
3
3
|
import { Readable, Writable } from 'node:stream';
|
|
4
4
|
import { CLIS, KIT_VERSION, adapterCommand, withoutProxy } from './clis.js';
|
|
5
5
|
import { killTree } from './scope.js';
|
|
6
|
+
const RETIRED = 'the CLI was retired';
|
|
6
7
|
export async function startAdapter(kind, cli, cwd, app, events = () => undefined, added = {}, signal, scope = null) {
|
|
7
8
|
const adapter = CLIS[kind].adapter;
|
|
8
9
|
const started = [adapter === null ? cli : adapterCommand(kind), CLIS[kind].args];
|
|
@@ -13,34 +14,53 @@ export async function startAdapter(kind, cli, cwd, app, events = () => undefined
|
|
|
13
14
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
14
15
|
detached: true,
|
|
15
16
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
child.on('exit', () => signal?.removeEventListener('abort', stop));
|
|
17
|
+
let owned = true;
|
|
18
|
+
let retired = false;
|
|
19
19
|
let stderr = '';
|
|
20
20
|
child.stderr.on('data', (chunk) => {
|
|
21
21
|
stderr = (stderr + chunk.toString('utf8')).slice(-4000);
|
|
22
22
|
});
|
|
23
|
-
const exited = new Promise((resolve) => {
|
|
24
|
-
child.on('error', (error) => resolve(`the CLI did not start: ${error.message}`));
|
|
25
|
-
child.on('exit', (code, signal) => {
|
|
26
|
-
const last = stderr.trim().split('\n').at(-1);
|
|
27
|
-
resolve(`the CLI exited with ${code ?? signal}${last ? `: ${last}` : ''}`);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
23
|
const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
31
24
|
const connection = app.connect({
|
|
32
25
|
writable: stream.writable,
|
|
33
26
|
readable: stream.readable.pipeThrough(new TransformStream({
|
|
34
27
|
transform(message, controller) {
|
|
35
28
|
const read = message;
|
|
36
|
-
|
|
29
|
+
if (owned)
|
|
30
|
+
events(read);
|
|
37
31
|
if (read.id !== undefined || (read.method !== 'session/update' && !read.method.startsWith('_'))) {
|
|
38
32
|
controller.enqueue(message);
|
|
39
33
|
}
|
|
40
34
|
},
|
|
41
35
|
})),
|
|
42
36
|
});
|
|
43
|
-
|
|
37
|
+
const { promise: lost, resolve: lose } = Promise.withResolvers();
|
|
38
|
+
const exited = new Promise((resolve) => {
|
|
39
|
+
const end = (cause) => {
|
|
40
|
+
if (owned)
|
|
41
|
+
lose(cause);
|
|
42
|
+
owned = false;
|
|
43
|
+
connection.close(new Error(cause));
|
|
44
|
+
resolve(cause);
|
|
45
|
+
};
|
|
46
|
+
child.on('error', (error) => end(`the CLI did not start: ${error.message}`));
|
|
47
|
+
child.on('exit', (code, signal) => {
|
|
48
|
+
const last = stderr.trim().split('\n').at(-1);
|
|
49
|
+
end(`the CLI exited with ${code ?? signal}${last ? `: ${last}` : ''}`);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
const retire = () => {
|
|
53
|
+
owned = false;
|
|
54
|
+
if (retired)
|
|
55
|
+
return;
|
|
56
|
+
retired = true;
|
|
57
|
+
if (child.pid !== undefined)
|
|
58
|
+
killTree(child);
|
|
59
|
+
scope?.kill();
|
|
60
|
+
connection.close(new Error(RETIRED));
|
|
61
|
+
};
|
|
62
|
+
signal?.addEventListener('abort', retire, { once: true });
|
|
63
|
+
child.on('exit', () => signal?.removeEventListener('abort', retire));
|
|
44
64
|
try {
|
|
45
65
|
const initialized = await connection.agent.request('initialize', {
|
|
46
66
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -54,10 +74,10 @@ export async function startAdapter(kind, cli, cwd, app, events = () => undefined
|
|
|
54
74
|
clientInfo: { name: '@agentshouse/kit', version: KIT_VERSION },
|
|
55
75
|
});
|
|
56
76
|
scope?.entered(child.pid);
|
|
57
|
-
return {
|
|
77
|
+
return { connection, initialized, exited, lost, retire };
|
|
58
78
|
}
|
|
59
79
|
catch (error) {
|
|
60
|
-
|
|
80
|
+
retire();
|
|
61
81
|
throw error;
|
|
62
82
|
}
|
|
63
83
|
}
|
package/dist/agents.js
CHANGED
|
@@ -5,7 +5,6 @@ import { startAdapter } from './acp.js';
|
|
|
5
5
|
import { CLIS, KIT_VERSION, below, installAdapter, installCli, locate, run, versionOf } from './clis.js';
|
|
6
6
|
import { agentBase, kitHome } from './home.js';
|
|
7
7
|
import { updatesItself } from './placement.js';
|
|
8
|
-
import { killTree } from './scope.js';
|
|
9
8
|
import { holdTranscription, transcriptionReport } from './transcription.js';
|
|
10
9
|
import { KIT_WIRE } from './wire.js';
|
|
11
10
|
// Two quiet seconds let a fresh conversation start before background model discovery competes for its CPU.
|
|
@@ -109,8 +108,7 @@ async function offer(kind, cli, known, signal) {
|
|
|
109
108
|
};
|
|
110
109
|
}
|
|
111
110
|
finally {
|
|
112
|
-
|
|
113
|
-
killTree(adapter.child);
|
|
111
|
+
adapter?.retire();
|
|
114
112
|
}
|
|
115
113
|
}
|
|
116
114
|
function reported(kind, reading) {
|
package/dist/bootstrap.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-cb345f4157685",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"macos": {
|
|
6
6
|
"asset": {
|
|
7
7
|
"name": "connect-linux.sh",
|
|
8
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
8
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cb345f4157685/connect-linux.sh"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"linux": {
|
|
12
12
|
"asset": {
|
|
13
13
|
"name": "connect-linux.sh",
|
|
14
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
14
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cb345f4157685/connect-linux.sh"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"windows": {
|
|
18
18
|
"asset": {
|
|
19
19
|
"name": "connect-windows.ps1",
|
|
20
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cb345f4157685/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
package/dist/bridge.js
CHANGED
|
@@ -81,7 +81,7 @@ function editAnswer(forwarded) {
|
|
|
81
81
|
export async function openBridge(house, copies, conversationId, signal) {
|
|
82
82
|
const path = [join(kitHome(), 'shim'), dirname(process.execPath), await loginPath()].join(delimiter);
|
|
83
83
|
const origin = (await readEnrolment()).house;
|
|
84
|
-
const { credential } = await house.post(`/kit/conversations/${conversationId}/credential`, {}, signal);
|
|
84
|
+
const { credential } = await retrying(() => house.post(`/kit/conversations/${conversationId}/credential`, {}, signal), signal);
|
|
85
85
|
const closed = new AbortController();
|
|
86
86
|
const sent = (message, operation) => callHouse(origin, credential, message, operation, closed.signal);
|
|
87
87
|
const send = async (message, operation) => {
|
package/dist/conversations.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { client, } from '@agentclientprotocol/sdk';
|
|
1
|
+
import { client, RequestError, } 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
6
|
import { startAdapter } from './acp.js';
|
|
7
|
+
import { finalRefusal } from './api.js';
|
|
7
8
|
import { openBridge } from './bridge.js';
|
|
8
9
|
import { CLIS } from './clis.js';
|
|
9
10
|
import { answeredFailure, readerFor } from './events.js';
|
|
@@ -11,14 +12,22 @@ import { placeFiles, uploadBytes } from './files.js';
|
|
|
11
12
|
import { agentBase } from './home.js';
|
|
12
13
|
import { instructions } from './instructions.js';
|
|
13
14
|
import { Parts } from './parts.js';
|
|
15
|
+
import { refusalOf } from './refusals.js';
|
|
14
16
|
import { killMarked, Scope } from './scope.js';
|
|
15
17
|
import { holdSecretInput } from './secret-input.js';
|
|
16
18
|
import { createHowWeWork } from './skills.js';
|
|
17
19
|
import { transcribe } from './transcription.js';
|
|
18
20
|
import { workFor } from './work.js';
|
|
21
|
+
const SIGNED_OUT = RequestError.authRequired().code;
|
|
19
22
|
const KILLED = 'the conversation was killed';
|
|
20
23
|
const REFUSED = 'the CLI refused the prompt';
|
|
21
|
-
const NO_CONNECTION = "the
|
|
24
|
+
const NO_CONNECTION = "the Agent's computer could not open this conversation; try again";
|
|
25
|
+
const GONE = 'House no longer holds this conversation; start a new one';
|
|
26
|
+
const RETIRED = "this conversation's Agent is retired, so it cannot start";
|
|
27
|
+
const ENDED = {
|
|
28
|
+
conversation_not_found: GONE,
|
|
29
|
+
conversation_historical: RETIRED,
|
|
30
|
+
};
|
|
22
31
|
const NO_START = "the Agent's CLI did not start";
|
|
23
32
|
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
24
33
|
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
@@ -30,9 +39,12 @@ async function step(reason, work) {
|
|
|
30
39
|
return await work;
|
|
31
40
|
}
|
|
32
41
|
catch (error) {
|
|
33
|
-
throw new NotStarted(reason, { cause: error });
|
|
42
|
+
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
34
43
|
}
|
|
35
44
|
}
|
|
45
|
+
function unbridged(error) {
|
|
46
|
+
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
47
|
+
}
|
|
36
48
|
const EXIT_PLAN_MODE = {
|
|
37
49
|
parse(value) {
|
|
38
50
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
@@ -45,6 +57,13 @@ export const CONVERSATION_INPUTS = new Set(['open', 'message', 'interrupt', 'kil
|
|
|
45
57
|
function causeOf(error) {
|
|
46
58
|
return error instanceof Error ? error.message : String(error);
|
|
47
59
|
}
|
|
60
|
+
function answeredWith(error) {
|
|
61
|
+
const signedOut = typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
62
|
+
return { failure: signedOut ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
63
|
+
}
|
|
64
|
+
function exitedWith(running, cause) {
|
|
65
|
+
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
66
|
+
}
|
|
48
67
|
function answerMessage(request, response) {
|
|
49
68
|
return `The User answered a question you asked earlier.\nQuestion: ${JSON.stringify(request)}\nAnswer: ${JSON.stringify(response)}`;
|
|
50
69
|
}
|
|
@@ -111,9 +130,7 @@ function asksSecret(request) {
|
|
|
111
130
|
export class Conversations {
|
|
112
131
|
kit;
|
|
113
132
|
conversations = new Map();
|
|
114
|
-
acks = new Map();
|
|
115
133
|
questions = new Map();
|
|
116
|
-
carrying = 0;
|
|
117
134
|
reporting = 0;
|
|
118
135
|
watched = false;
|
|
119
136
|
constructor(kit) {
|
|
@@ -122,37 +139,26 @@ export class Conversations {
|
|
|
122
139
|
setInterval(() => this.look(), 2_000);
|
|
123
140
|
}
|
|
124
141
|
input(input) {
|
|
125
|
-
this.
|
|
126
|
-
|
|
127
|
-
(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
conversation.queue = handled.catch(() => undefined);
|
|
142
|
-
const work = handled.then(({ ack }) => ack);
|
|
143
|
-
this.acks.set(input.input_id, work);
|
|
144
|
-
return work;
|
|
145
|
-
})();
|
|
146
|
-
carried
|
|
147
|
-
.then((body) => this.kit.house.deliver(`/kit/inputs/${input.input_id}/ack`, body))
|
|
148
|
-
.catch(logged)
|
|
149
|
-
.finally(() => {
|
|
150
|
-
this.carrying--;
|
|
151
|
-
this.kit.changed();
|
|
142
|
+
void this.kit.inputs.carry(input.input_id, () => {
|
|
143
|
+
const conversation = this.conversation(input.conversation_id);
|
|
144
|
+
if (input.kind === 'kill') {
|
|
145
|
+
conversation.kills++;
|
|
146
|
+
conversation.killed.abort(new Error(KILLED));
|
|
147
|
+
for (const waiting of conversation.waiting.splice(0))
|
|
148
|
+
waiting.settle({ refused: KILLED });
|
|
149
|
+
this.halt(conversation);
|
|
150
|
+
}
|
|
151
|
+
if (input.kind === 'interrupt') {
|
|
152
|
+
conversation.interrupts++;
|
|
153
|
+
conversation.interrupted.abort();
|
|
154
|
+
}
|
|
155
|
+
const handled = conversation.queue.then(() => this.carry(conversation, input));
|
|
156
|
+
conversation.queue = handled.catch(() => undefined);
|
|
157
|
+
return handled.then(async ({ ack }) => ({ ack: await ack }));
|
|
152
158
|
});
|
|
153
159
|
}
|
|
154
160
|
idle() {
|
|
155
|
-
if (this.
|
|
161
|
+
if (this.reporting > 0)
|
|
156
162
|
return false;
|
|
157
163
|
for (const conversation of this.conversations.values()) {
|
|
158
164
|
const turn = conversation.turn;
|
|
@@ -387,7 +393,7 @@ export class Conversations {
|
|
|
387
393
|
.onRequest('elicitation/create', ({ params, signal }) => this.ask(conversation, 'elicitation/create', params, asksSecret(params), signal))
|
|
388
394
|
.onRequest('_x.ai/exit_plan_mode', EXIT_PLAN_MODE, ({ params, signal }) => this.ask(conversation, '_x.ai/exit_plan_mode', params, false, signal));
|
|
389
395
|
const cli = await unlessAborted(this.kit.agents.cli(route.kind), conversation.killed.signal);
|
|
390
|
-
const bridge = await step(
|
|
396
|
+
const bridge = await step(unbridged, openBridge(this.kit.house, this.kit.copies, conversation.id, conversation.killed.signal));
|
|
391
397
|
if (conversation.kills > 0) {
|
|
392
398
|
bridge.close();
|
|
393
399
|
throw new Error(KILLED);
|
|
@@ -395,11 +401,12 @@ export class Conversations {
|
|
|
395
401
|
conversation.opening = bridge;
|
|
396
402
|
const work = workFor(route.kind, route.working_directory, () => this.kit.changed());
|
|
397
403
|
const scope = new Scope(marker(bridge), randomUUID(), cli);
|
|
404
|
+
let adapter = null;
|
|
398
405
|
try {
|
|
399
406
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
400
407
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
401
408
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
402
|
-
|
|
409
|
+
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
403
410
|
work.read(message);
|
|
404
411
|
this.read(conversation, reader, message);
|
|
405
412
|
}, { ...bridge.env, ...streamed }, undefined, scope));
|
|
@@ -423,11 +430,11 @@ export class Conversations {
|
|
|
423
430
|
if (conversation.commands !== null)
|
|
424
431
|
this.commands(conversation, conversation.commands);
|
|
425
432
|
this.options(conversation, options);
|
|
426
|
-
void adapter.
|
|
433
|
+
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
427
434
|
return running.sessionId;
|
|
428
435
|
}
|
|
429
436
|
catch (error) {
|
|
430
|
-
|
|
437
|
+
adapter?.retire();
|
|
431
438
|
work.close();
|
|
432
439
|
conversation.leftovers.add(scope);
|
|
433
440
|
bridge.close();
|
|
@@ -470,13 +477,10 @@ export class Conversations {
|
|
|
470
477
|
}
|
|
471
478
|
return options;
|
|
472
479
|
}
|
|
473
|
-
exited(conversation,
|
|
474
|
-
const running = conversation.running;
|
|
475
|
-
if (running?.adapter !== adapter)
|
|
476
|
-
return;
|
|
480
|
+
exited(conversation, running, cause) {
|
|
477
481
|
this.detach(conversation, running);
|
|
478
482
|
if (conversation.turn !== null) {
|
|
479
|
-
this.end(conversation, conversation.turn,
|
|
483
|
+
this.end(conversation, conversation.turn, exitedWith(running, cause));
|
|
480
484
|
}
|
|
481
485
|
this.next(conversation);
|
|
482
486
|
this.kit.changed();
|
|
@@ -543,6 +547,8 @@ export class Conversations {
|
|
|
543
547
|
end(conversation, turn, failed, notice = null) {
|
|
544
548
|
if (turn.ended)
|
|
545
549
|
return;
|
|
550
|
+
if (failed !== null)
|
|
551
|
+
logged(new Error(`conversation ${conversation.id} turn ${turn.id} failed: ${failed.cause}`));
|
|
546
552
|
if (conversation.turn === turn) {
|
|
547
553
|
for (const event of conversation.running?.reader.closeTurn() ?? [])
|
|
548
554
|
this.apply(conversation, turn, event);
|
|
@@ -556,7 +562,7 @@ export class Conversations {
|
|
|
556
562
|
const body = Promise.allSettled(turn.uploads).then(() => ({
|
|
557
563
|
parts: turn.parts.saved(),
|
|
558
564
|
context: turn.parts.context,
|
|
559
|
-
...failed,
|
|
565
|
+
...(failed === null ? {} : { failed: failed.failure }),
|
|
560
566
|
}));
|
|
561
567
|
void this.deliver(conversation, turn, 'ended', body).catch(logged);
|
|
562
568
|
this.next(conversation);
|
|
@@ -569,18 +575,22 @@ export class Conversations {
|
|
|
569
575
|
conversation.prompted = running.sessionId;
|
|
570
576
|
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
571
577
|
const notice = answeredFailure(answer);
|
|
572
|
-
|
|
573
|
-
|
|
578
|
+
if (notice !== null) {
|
|
579
|
+
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
this.end(conversation, turn, answer.stopReason === 'refusal' ? { failure: 'refused', cause: REFUSED } : null);
|
|
583
|
+
}
|
|
574
584
|
}, async (error) => {
|
|
575
585
|
if (running.adapter.connection.signal.aborted) {
|
|
576
|
-
this.end(conversation, turn,
|
|
586
|
+
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
577
587
|
}
|
|
578
588
|
else {
|
|
579
589
|
if (conversation.open === 1 && conversation.running === running) {
|
|
580
|
-
running.
|
|
590
|
+
running.adapter.retire();
|
|
581
591
|
this.detach(conversation, running);
|
|
582
592
|
}
|
|
583
|
-
this.end(conversation, turn,
|
|
593
|
+
this.end(conversation, turn, answeredWith(error));
|
|
584
594
|
}
|
|
585
595
|
});
|
|
586
596
|
}
|
package/dist/copies.js
CHANGED
|
@@ -219,26 +219,35 @@ export class LocalCopies {
|
|
|
219
219
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
220
220
|
.sort();
|
|
221
221
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
connected() {
|
|
223
|
+
return this.selected();
|
|
224
|
+
}
|
|
225
|
+
async changed(rooms) {
|
|
226
|
+
if (rooms.length > 0)
|
|
227
|
+
await this.selected(rooms);
|
|
226
228
|
}
|
|
227
229
|
fetch() {
|
|
228
230
|
return this.answered(() => this.fetched(true));
|
|
229
231
|
}
|
|
232
|
+
async selected(rooms) {
|
|
233
|
+
const copied = await this.answered(() => this.fetched(false, rooms));
|
|
234
|
+
if (copied.refused)
|
|
235
|
+
logged(copied.text);
|
|
236
|
+
}
|
|
230
237
|
answered(work) {
|
|
231
238
|
const answered = this.answering.then(work);
|
|
232
239
|
this.answering = answered.catch(() => undefined);
|
|
233
240
|
return answered;
|
|
234
241
|
}
|
|
235
|
-
async fetched(refreshing) {
|
|
236
|
-
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', {});
|
|
242
|
+
async fetched(refreshing, rooms) {
|
|
243
|
+
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', rooms === undefined ? {} : { rooms });
|
|
237
244
|
const synced = new Map(answer.rooms.map((room) => [`rooms/${room.room_ref}`, room.room_handle]));
|
|
238
|
-
|
|
245
|
+
const named = rooms === undefined ? null : new Set(rooms.map((room) => `rooms/${room}`));
|
|
246
|
+
const held = () => [...this.copies.values()].filter((copy) => named === null || named.has(copy.key));
|
|
247
|
+
return this.holding([...new Set([...held().map((copy) => copy.key), ...synced.keys()])].sort(), async () => {
|
|
239
248
|
const said = [];
|
|
240
249
|
let refused = false;
|
|
241
|
-
for (const copy of
|
|
250
|
+
for (const copy of held()) {
|
|
242
251
|
const returned = synced.has(copy.key);
|
|
243
252
|
if (returned && copy.erasing === undefined)
|
|
244
253
|
continue;
|
package/dist/events.js
CHANGED
|
@@ -513,7 +513,8 @@ class GrokReader extends Reader {
|
|
|
513
513
|
}
|
|
514
514
|
}
|
|
515
515
|
export function answeredFailure(answer) {
|
|
516
|
-
|
|
516
|
+
const failure = answer._meta?.jetbrains?.air?.sessionFailure;
|
|
517
|
+
return typeof failure?.title === 'string' ? { title: failure.title, limit: failure.category === 'limit' } : null;
|
|
517
518
|
}
|
|
518
519
|
export function readerFor(kind) {
|
|
519
520
|
if (kind === 'claude-agent-acp')
|
package/dist/files.js
CHANGED
|
@@ -11,7 +11,7 @@ export function verified(bytes, sha256, name) {
|
|
|
11
11
|
export async function placeFiles(house, directory, files, signal) {
|
|
12
12
|
const paths = [];
|
|
13
13
|
for (const file of files) {
|
|
14
|
-
const original = await house.post('/kit/conversation/originals/get', { version: file.version, download: true }, signal);
|
|
14
|
+
const original = await retrying(() => house.post('/kit/conversation/originals/get', { version: file.version, download: true }, signal), signal);
|
|
15
15
|
const answer = await fetch(relayed(original.download.url), { signal });
|
|
16
16
|
if (!answer.ok)
|
|
17
17
|
throw new Error(`${file.name} did not download: ${answer.status}`);
|
package/dist/inputs.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function logged(error) {
|
|
2
|
+
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
3
|
+
}
|
|
4
|
+
export class Inputs {
|
|
5
|
+
house;
|
|
6
|
+
changed;
|
|
7
|
+
carried = new Map();
|
|
8
|
+
open = 0;
|
|
9
|
+
constructor(house, changed) {
|
|
10
|
+
this.house = house;
|
|
11
|
+
this.changed = changed;
|
|
12
|
+
}
|
|
13
|
+
idle() {
|
|
14
|
+
return this.open === 0;
|
|
15
|
+
}
|
|
16
|
+
carry(inputId, work) {
|
|
17
|
+
const known = this.carried.get(inputId);
|
|
18
|
+
const carried = known ?? work();
|
|
19
|
+
if (known === undefined) {
|
|
20
|
+
this.carried.set(inputId, carried);
|
|
21
|
+
carried.catch(() => this.carried.delete(inputId));
|
|
22
|
+
}
|
|
23
|
+
this.open++;
|
|
24
|
+
this.changed();
|
|
25
|
+
return carried
|
|
26
|
+
.then(async ({ ack, acknowledged }) => {
|
|
27
|
+
await this.house.deliver(`/kit/inputs/${inputId}/ack`, ack);
|
|
28
|
+
if (known === undefined)
|
|
29
|
+
await acknowledged?.();
|
|
30
|
+
})
|
|
31
|
+
.catch(logged)
|
|
32
|
+
.finally(() => {
|
|
33
|
+
this.open--;
|
|
34
|
+
this.changed();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/resident.js
CHANGED
|
@@ -12,6 +12,7 @@ import { CONVERSATION_INPUTS, Conversations } from './conversations.js';
|
|
|
12
12
|
import { LocalCopies } from './copies.js';
|
|
13
13
|
import { restart } from './end.js';
|
|
14
14
|
import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
|
|
15
|
+
import { Inputs } from './inputs.js';
|
|
15
16
|
import { killDescendants } from './scope.js';
|
|
16
17
|
import { quoted } from './shell.js';
|
|
17
18
|
import { SignIns } from './sign-in.js';
|
|
@@ -133,7 +134,7 @@ export async function resident() {
|
|
|
133
134
|
const agents = new Agents(house, () => changed());
|
|
134
135
|
let stream = null;
|
|
135
136
|
let reported = false;
|
|
136
|
-
const idle = () =>
|
|
137
|
+
const idle = () => inputs.idle() && conversations.idle() && agents.idle();
|
|
137
138
|
const changed = () => {
|
|
138
139
|
if (!idle()) {
|
|
139
140
|
reported = false;
|
|
@@ -146,15 +147,17 @@ export async function resident() {
|
|
|
146
147
|
logged(house.deliver('/kit/idle', {}, idle));
|
|
147
148
|
}
|
|
148
149
|
};
|
|
150
|
+
const inputs = new Inputs(house, changed);
|
|
149
151
|
const conversations = new Conversations({
|
|
150
152
|
house,
|
|
151
153
|
agents,
|
|
152
154
|
copies,
|
|
155
|
+
inputs,
|
|
153
156
|
send: (frame) => stream?.send(frame) ?? false,
|
|
154
157
|
changed,
|
|
155
158
|
});
|
|
156
|
-
const signIns = new SignIns(house, agents,
|
|
157
|
-
const updates = new Updates(
|
|
159
|
+
const signIns = new SignIns(house, agents, inputs);
|
|
160
|
+
const updates = new Updates(inputs);
|
|
158
161
|
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
159
162
|
process.once(signal, () => {
|
|
160
163
|
killDescendants();
|
|
@@ -173,10 +176,11 @@ export async function resident() {
|
|
|
173
176
|
frame: (arrived) => {
|
|
174
177
|
if (arrived.type === 'watching')
|
|
175
178
|
conversations.watching(arrived.watching === true);
|
|
179
|
+
if (arrived.type === 'authority_changed')
|
|
180
|
+
logged(copies.changed(arrived.rooms));
|
|
176
181
|
if (arrived.type === 'work_available' && arrived.subject === 'agents')
|
|
177
182
|
logged(agents.refresh());
|
|
178
183
|
if (arrived.type === 'input') {
|
|
179
|
-
reported = false;
|
|
180
184
|
if (arrived.kind === 'sign_in')
|
|
181
185
|
signIns.start(arrived);
|
|
182
186
|
else if (arrived.kind === 'update')
|
|
@@ -185,7 +189,7 @@ export async function resident() {
|
|
|
185
189
|
conversations.input(arrived);
|
|
186
190
|
else {
|
|
187
191
|
const refused = { refused: `This Kit does not know ${arrived.kind}; update the Kit` };
|
|
188
|
-
|
|
192
|
+
void inputs.carry(String(arrived.input_id), async () => ({ ack: refused }));
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
195
|
},
|
package/dist/service.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { kitHome } from './home.js';
|
|
2
5
|
const UNIT = 'house-kit.service';
|
|
3
6
|
// launchctl and systemctl answer at once, so ten seconds only bounds a stuck service manager.
|
|
4
7
|
const MANAGER_MS = 10_000;
|
|
@@ -12,18 +15,37 @@ function start(command, args) {
|
|
|
12
15
|
const cause = started.error?.message ?? (started.stderr.trim() || `${command} exited with ${started.status ?? 'a signal'}`);
|
|
13
16
|
throw new Error(`the House Kit service could not be started: ${cause}`);
|
|
14
17
|
}
|
|
18
|
+
function physical(path) {
|
|
19
|
+
return existsSync(path) ? realpathSync(path) : resolve(path);
|
|
20
|
+
}
|
|
21
|
+
function systemdHome(environment) {
|
|
22
|
+
for (const [, quoted, bare] of environment.matchAll(/"((?:[^"\\]|\\.)*)"|(\S+)/g)) {
|
|
23
|
+
const assignment = bare ?? quoted.replace(/\\(.)/g, '$1');
|
|
24
|
+
if (assignment.startsWith('HOUSE_KIT_HOME='))
|
|
25
|
+
return assignment.slice('HOUSE_KIT_HOME='.length);
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function runsThisHome(served) {
|
|
30
|
+
return served !== undefined && physical(served) === physical(kitHome());
|
|
31
|
+
}
|
|
15
32
|
export function startService() {
|
|
16
33
|
if (process.platform === 'darwin') {
|
|
17
34
|
const service = `gui/${process.getuid()}/house-kit`;
|
|
18
35
|
const printed = manage('launchctl', ['print', service]);
|
|
19
36
|
if (printed.status !== 0 || /^\tstate = running$/m.test(printed.stdout))
|
|
20
37
|
return false;
|
|
38
|
+
if (!runsThisHome(/^\t+HOUSE_KIT_HOME => (.+)$/m.exec(printed.stdout)?.[1]))
|
|
39
|
+
return false;
|
|
21
40
|
start('launchctl', ['kickstart', service]);
|
|
22
41
|
return true;
|
|
23
42
|
}
|
|
24
43
|
for (const scope of [['--user'], []]) {
|
|
25
44
|
if (manage('systemctl', [...scope, 'is-enabled', '--quiet', UNIT]).status !== 0)
|
|
26
45
|
continue;
|
|
46
|
+
const environment = manage('systemctl', [...scope, 'show', '--property=Environment', '--value', UNIT]).stdout;
|
|
47
|
+
if (!runsThisHome(systemdHome(environment)))
|
|
48
|
+
continue;
|
|
27
49
|
if (manage('systemctl', [...scope, 'is-active', '--quiet', UNIT]).status === 0)
|
|
28
50
|
return false;
|
|
29
51
|
start('systemctl', [...scope, 'start', UNIT]);
|
package/dist/sign-in.js
CHANGED
|
@@ -9,36 +9,20 @@ const CODE = /^[A-Z0-9]+-[A-Z0-9]+$/;
|
|
|
9
9
|
function causeOf(error) {
|
|
10
10
|
return error instanceof Error ? error.message : String(error);
|
|
11
11
|
}
|
|
12
|
-
function logged(error) {
|
|
13
|
-
process.stderr.write(`kit: ${causeOf(error)}\n`);
|
|
14
|
-
}
|
|
15
12
|
function endOf(signal) {
|
|
16
13
|
return new Promise((resolve) => signal.addEventListener('abort', () => resolve(true), { once: true }));
|
|
17
14
|
}
|
|
18
15
|
export class SignIns {
|
|
19
16
|
house;
|
|
20
17
|
agents;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
constructor(house, agents, changed) {
|
|
18
|
+
inputs;
|
|
19
|
+
constructor(house, agents, inputs) {
|
|
24
20
|
this.house = house;
|
|
25
21
|
this.agents = agents;
|
|
26
|
-
this.
|
|
27
|
-
}
|
|
28
|
-
idle() {
|
|
29
|
-
return this.running.size === 0;
|
|
22
|
+
this.inputs = inputs;
|
|
30
23
|
}
|
|
31
24
|
start(input) {
|
|
32
|
-
|
|
33
|
-
return;
|
|
34
|
-
this.running.add(input.input_id);
|
|
35
|
-
this.changed();
|
|
36
|
-
this.signIn(input)
|
|
37
|
-
.catch(logged)
|
|
38
|
-
.finally(() => {
|
|
39
|
-
this.running.delete(input.input_id);
|
|
40
|
-
this.changed();
|
|
41
|
-
});
|
|
25
|
+
void this.inputs.carry(input.input_id, () => this.signIn(input));
|
|
42
26
|
}
|
|
43
27
|
async signIn(input) {
|
|
44
28
|
const ending = new AbortController();
|
|
@@ -48,9 +32,7 @@ export class SignIns {
|
|
|
48
32
|
clearTimeout(timer);
|
|
49
33
|
if (ended)
|
|
50
34
|
process.stderr.write(`kit: the ${input.cli} sign-in reached the end House set for it\n`);
|
|
51
|
-
|
|
52
|
-
if (!ended)
|
|
53
|
-
await this.agents.reread(input.cli);
|
|
35
|
+
return ended ? { ack: {} } : { ack: {}, acknowledged: () => this.agents.reread(input.cli) };
|
|
54
36
|
}
|
|
55
37
|
async signedIn(input, signal) {
|
|
56
38
|
await this.login(input, signal);
|
package/dist/stream.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { subscribe } from 'node:diagnostics_channel';
|
|
2
|
-
import {
|
|
2
|
+
import { HouseRefusal, retryDelay } from './api.js';
|
|
3
3
|
import { end, REPLACED, signInAgain, updateRequired } from './end.js';
|
|
4
4
|
import { readEnrolment } from './home.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
@@ -82,9 +82,9 @@ export function holdStream(handlers) {
|
|
|
82
82
|
}
|
|
83
83
|
if (refused !== null && tooOld(refused.text))
|
|
84
84
|
updateRequired();
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
// A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; an opening House refused
|
|
85
|
+
if (!opened && refused !== null)
|
|
86
|
+
process.stderr.write(`kit: ${refused.message}\n`);
|
|
87
|
+
// A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; an opening House refused or left unanswered backs off on the jittered curve, retryable or not, since a resident outlives a House deploy.
|
|
88
88
|
setTimeout(() => void connect(), opened ? 1000 + Math.random() * 2000 : retryDelay(failures++));
|
|
89
89
|
};
|
|
90
90
|
opening.onopen = () => {
|
package/dist/update.js
CHANGED
|
@@ -6,9 +6,6 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { run } from './clis.js';
|
|
7
7
|
import { kitHome, workspaceRoot } from './home.js';
|
|
8
8
|
import { service, throughSudo } from './placement.js';
|
|
9
|
-
function logged(error) {
|
|
10
|
-
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
11
|
-
}
|
|
12
9
|
function directory() {
|
|
13
10
|
return join(kitHome(), 'update');
|
|
14
11
|
}
|
|
@@ -50,6 +47,12 @@ async function recorded() {
|
|
|
50
47
|
return null;
|
|
51
48
|
return status.trim() === '0' ? {} : { refused: `the Kit bootstrap exited with ${status.trim()}` };
|
|
52
49
|
}
|
|
50
|
+
async function forget(inputId) {
|
|
51
|
+
if ((await pendingInput()) !== inputId)
|
|
52
|
+
return;
|
|
53
|
+
await rm(pending(), { force: true });
|
|
54
|
+
await rm(outcome(), { force: true });
|
|
55
|
+
}
|
|
53
56
|
async function finished(inputId) {
|
|
54
57
|
for (;;) {
|
|
55
58
|
const ack = await recorded();
|
|
@@ -90,27 +93,28 @@ function rerun(script, inputId, flags) {
|
|
|
90
93
|
});
|
|
91
94
|
}
|
|
92
95
|
export class Updates {
|
|
93
|
-
|
|
94
|
-
held = new Set();
|
|
96
|
+
inputs;
|
|
95
97
|
queue = Promise.resolve();
|
|
96
|
-
constructor(
|
|
97
|
-
this.
|
|
98
|
+
constructor(inputs) {
|
|
99
|
+
this.inputs = inputs;
|
|
98
100
|
}
|
|
99
101
|
async resumed() {
|
|
100
102
|
const inputId = await pendingInput();
|
|
101
103
|
if (inputId === null)
|
|
102
104
|
return;
|
|
103
|
-
await this.
|
|
105
|
+
await this.enqueue(inputId, () => finished(inputId));
|
|
104
106
|
}
|
|
105
107
|
start(input) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
void this.enqueue(input.input_id, () => this.carry(input));
|
|
109
|
+
}
|
|
110
|
+
enqueue(inputId, work) {
|
|
111
|
+
const before = this.queue;
|
|
112
|
+
const carried = this.inputs.carry(inputId, async () => {
|
|
113
|
+
await before;
|
|
114
|
+
return { ack: await work(), acknowledged: () => forget(inputId) };
|
|
115
|
+
});
|
|
116
|
+
this.queue = before.then(() => carried);
|
|
117
|
+
return carried;
|
|
114
118
|
}
|
|
115
119
|
async carry(input) {
|
|
116
120
|
if ((await pendingInput()) === input.input_id)
|
|
@@ -131,11 +135,4 @@ export class Updates {
|
|
|
131
135
|
refused: 'the Kit bootstrap did not finish',
|
|
132
136
|
};
|
|
133
137
|
}
|
|
134
|
-
async acknowledge(inputId, ack) {
|
|
135
|
-
await this.house.deliver(`/kit/inputs/${inputId}/ack`, ack);
|
|
136
|
-
if ((await pendingInput()) !== inputId)
|
|
137
|
-
return;
|
|
138
|
-
await rm(pending(), { force: true });
|
|
139
|
-
await rm(outcome(), { force: true });
|
|
140
|
-
}
|
|
141
138
|
}
|
package/dist/wire.js
CHANGED