@agentshouse/kit 0.2.1-cb345f4157685 → 0.2.1-cb8dc89b78e07
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 +2 -2
- package/dist/agents.js +33 -11
- package/dist/api.js +4 -4
- package/dist/bootstrap.json +4 -4
- package/dist/conversations.js +158 -38
- package/dist/copies.js +32 -39
- package/dist/end.js +5 -1
- package/dist/events.js +2 -2
- package/dist/inputs.js +28 -2
- package/dist/instructions.js +2 -2
- package/dist/placement.js +11 -2
- package/dist/scope.js +3 -0
- package/dist/stream.js +4 -4
- package/dist/translate.js +2 -8
- package/dist/wire.js +18 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,8 +46,8 @@ this computer as a new one when House no longer holds it.
|
|
|
46
46
|
In a container you do not control, such as ChatGPT's, `npm i -g @agentshouse/kit` and `kit chatgpt --key <key>` with a key made on Connections let `house <tool>` act as you, starting and configuring nothing else.
|
|
47
47
|
|
|
48
48
|
`kit resident` keeps the Environment connected and, as it connects, copies the text of each Room you chose for it as a
|
|
49
|
-
Git repository in the workspace's `local-copies`, and
|
|
50
|
-
may no longer be copied here. `house fetch` does the same and records House's changes on each copy's
|
|
49
|
+
Git repository in the workspace's `local-copies`, and keeps a Room's copy as it is, no longer refreshing it, once the
|
|
50
|
+
Room may no longer be copied here. `house fetch` does the same and records House's changes on each copy's
|
|
51
51
|
`refs/house/received`; rebase onto it, commit and run `house push --owner` in the copy, through the install command in
|
|
52
52
|
the container. An Agent runs both, without `--owner`. `house upload` and `house download` move a Room's other files.
|
|
53
53
|
|
package/dist/agents.js
CHANGED
|
@@ -10,6 +10,7 @@ import { KIT_WIRE } from './wire.js';
|
|
|
10
10
|
// Two quiet seconds let a fresh conversation start before background model discovery competes for its CPU.
|
|
11
11
|
const MODEL_PROBE_QUIET_MS = 2_000;
|
|
12
12
|
const SIGNED_OUT = { signed_in: false, models: [], modes: [] };
|
|
13
|
+
const INSTALLING = { release: null, failure: null, installing: true, ...SIGNED_OUT };
|
|
13
14
|
function select(options, category) {
|
|
14
15
|
const option = options.find((candidate) => candidate.category === category);
|
|
15
16
|
return option?.type === 'select' ? option : undefined;
|
|
@@ -117,6 +118,7 @@ function reported(kind, reading) {
|
|
|
117
118
|
release: reading.release,
|
|
118
119
|
...(reading.minimum === undefined ? {} : { minimum: reading.minimum }),
|
|
119
120
|
failure: reading.failure,
|
|
121
|
+
...(reading.installing === undefined ? {} : { installing: reading.installing }),
|
|
120
122
|
signed_in: reading.signed_in,
|
|
121
123
|
models: reading.models,
|
|
122
124
|
modes: reading.modes,
|
|
@@ -162,6 +164,7 @@ export class Agents {
|
|
|
162
164
|
reading = Promise.resolve();
|
|
163
165
|
queues = new Map();
|
|
164
166
|
installing = Promise.resolve();
|
|
167
|
+
installs = new Map();
|
|
165
168
|
readings = new Map();
|
|
166
169
|
reads = new Map();
|
|
167
170
|
starts = 0;
|
|
@@ -217,7 +220,7 @@ export class Agents {
|
|
|
217
220
|
const { cli, release } = resolved;
|
|
218
221
|
const known = this.readings.get(kind);
|
|
219
222
|
const kept = known?.release === null || known?.minimum !== undefined ? SIGNED_OUT : (known ?? SIGNED_OUT);
|
|
220
|
-
if (known?.release !== release || known.minimum !== undefined) {
|
|
223
|
+
if (known?.release !== release || known.minimum !== undefined || this.installs.has(kind)) {
|
|
221
224
|
this.update(kind, { release, failure: null, signed_in: kept.signed_in, models: kept.models, modes: kept.modes });
|
|
222
225
|
}
|
|
223
226
|
const read = this.nextRead(kind);
|
|
@@ -232,7 +235,8 @@ export class Agents {
|
|
|
232
235
|
});
|
|
233
236
|
}
|
|
234
237
|
located(kind, signal) {
|
|
235
|
-
|
|
238
|
+
const owner = {};
|
|
239
|
+
return this.queue(kind, () => this.ensure(kind, signal, owner).finally(() => this.settle(kind, owner)), signal);
|
|
236
240
|
}
|
|
237
241
|
async reread(kind) {
|
|
238
242
|
await this.examine(kind, this.readings.get(kind) ?? SIGNED_OUT, offer);
|
|
@@ -240,7 +244,13 @@ export class Agents {
|
|
|
240
244
|
}
|
|
241
245
|
async recheck(kind, signal) {
|
|
242
246
|
const known = this.readings.get(kind) ?? SIGNED_OUT;
|
|
243
|
-
|
|
247
|
+
const owner = {};
|
|
248
|
+
try {
|
|
249
|
+
await this.examine(kind, known, async (kind, cli) => ((await signedIn(kind, cli, known, signal)) ? { ...known, signed_in: true } : SIGNED_OUT), signal, owner);
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
this.settle(kind, owner);
|
|
253
|
+
}
|
|
244
254
|
if (!signal.aborted)
|
|
245
255
|
await this.report();
|
|
246
256
|
}
|
|
@@ -254,8 +264,18 @@ export class Agents {
|
|
|
254
264
|
this.reads.set(kind, read);
|
|
255
265
|
return read;
|
|
256
266
|
}
|
|
257
|
-
|
|
267
|
+
hold(kind, reading) {
|
|
258
268
|
this.readings.set(kind, reading);
|
|
269
|
+
this.installs.delete(kind);
|
|
270
|
+
}
|
|
271
|
+
update(kind, reading) {
|
|
272
|
+
this.hold(kind, reading);
|
|
273
|
+
this.report().catch(logged);
|
|
274
|
+
}
|
|
275
|
+
settle(kind, owner) {
|
|
276
|
+
if (this.installs.get(kind) !== owner)
|
|
277
|
+
return;
|
|
278
|
+
this.installs.delete(kind);
|
|
259
279
|
this.report().catch(logged);
|
|
260
280
|
}
|
|
261
281
|
queue(kind, work, signal) {
|
|
@@ -266,13 +286,15 @@ export class Agents {
|
|
|
266
286
|
this.queues.set(kind, queued.catch(() => undefined));
|
|
267
287
|
return signal === undefined ? queued : Promise.race([queued, ended(signal)]);
|
|
268
288
|
}
|
|
269
|
-
async ensure(kind, signal) {
|
|
289
|
+
async ensure(kind, signal, owner = {}) {
|
|
270
290
|
const failed = await installAdapter(kind, signal);
|
|
271
291
|
if (failed !== null)
|
|
272
292
|
throw new Error(failed);
|
|
273
293
|
const found = await locate(kind, signal);
|
|
274
294
|
if (found !== null)
|
|
275
295
|
return found;
|
|
296
|
+
this.installs.set(kind, owner);
|
|
297
|
+
this.report().catch(logged);
|
|
276
298
|
const installing = this.installing.catch(() => undefined).then(() => installCli(kind, signal));
|
|
277
299
|
this.installing = installing;
|
|
278
300
|
const failure = await installing;
|
|
@@ -283,10 +305,10 @@ export class Agents {
|
|
|
283
305
|
throw new Error(`the login shell does not find ${CLIS[kind].bin} after its install`);
|
|
284
306
|
return installed;
|
|
285
307
|
}
|
|
286
|
-
async resolve(kind, signal) {
|
|
308
|
+
async resolve(kind, signal, owner) {
|
|
287
309
|
let cli;
|
|
288
310
|
try {
|
|
289
|
-
cli = await realpath(await this.ensure(kind, signal));
|
|
311
|
+
cli = await realpath(await this.ensure(kind, signal, owner));
|
|
290
312
|
}
|
|
291
313
|
catch (error) {
|
|
292
314
|
const failure = causeOf(error);
|
|
@@ -301,12 +323,12 @@ export class Agents {
|
|
|
301
323
|
return { release, minimum, failure: null, ...SIGNED_OUT };
|
|
302
324
|
return { cli, release };
|
|
303
325
|
}
|
|
304
|
-
async examine(kind, known, read, signal) {
|
|
326
|
+
async examine(kind, known, read, signal, owner) {
|
|
305
327
|
const current = this.nextRead(kind);
|
|
306
|
-
const resolved = await this.queue(kind, () => this.resolve(kind, signal), signal);
|
|
328
|
+
const resolved = await this.queue(kind, () => this.resolve(kind, signal, owner), signal);
|
|
307
329
|
const reading = 'cli' in resolved ? { release: resolved.release, failure: null, ...(await read(kind, resolved.cli, known)) } : resolved;
|
|
308
330
|
if (this.reads.get(kind) === current && !signal?.aborted)
|
|
309
|
-
this.
|
|
331
|
+
this.hold(kind, reading);
|
|
310
332
|
}
|
|
311
333
|
async quietOffer(kind, cli, known) {
|
|
312
334
|
for (let seen = -1; seen !== this.starts;) {
|
|
@@ -324,7 +346,7 @@ export class Agents {
|
|
|
324
346
|
}
|
|
325
347
|
async send() {
|
|
326
348
|
const agents = this.desired.agents.flatMap((kind) => {
|
|
327
|
-
const reading = this.readings.get(kind);
|
|
349
|
+
const reading = this.installs.has(kind) ? INSTALLING : this.readings.get(kind);
|
|
328
350
|
return reading === undefined ? [] : [reported(kind, reading)];
|
|
329
351
|
});
|
|
330
352
|
const hostKey = await sshHostKey();
|
package/dist/api.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
-
import { signInAgain
|
|
2
|
+
import { signInAgain } from './end.js';
|
|
3
3
|
import { readEnrolment } from './home.js';
|
|
4
4
|
import { retryable } from './refusals.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
6
|
-
import { KIT_WIRE, KIT_WIRE_HEADER
|
|
6
|
+
import { endUnserved, KIT_WIRE, KIT_WIRE_HEADER } from './wire.js';
|
|
7
7
|
export class HouseRefusal extends Error {
|
|
8
8
|
status;
|
|
9
9
|
text;
|
|
@@ -45,8 +45,8 @@ export async function post(house, path, body, credential, signal) {
|
|
|
45
45
|
signal,
|
|
46
46
|
});
|
|
47
47
|
const text = await answer.text();
|
|
48
|
-
if (!answer.ok
|
|
49
|
-
|
|
48
|
+
if (!answer.ok)
|
|
49
|
+
endUnserved(text);
|
|
50
50
|
if (!answer.ok)
|
|
51
51
|
throw new HouseRefusal(path, answer.status, text);
|
|
52
52
|
return JSON.parse(text);
|
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-cb8dc89b78e07",
|
|
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-cb8dc89b78e07/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-cb8dc89b78e07/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-cb8dc89b78e07/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
package/dist/conversations.js
CHANGED
|
@@ -31,6 +31,7 @@ const ENDED = {
|
|
|
31
31
|
const NO_START = "the Agent's CLI did not start";
|
|
32
32
|
const NO_SESSION = "the Agent's CLI did not open a session";
|
|
33
33
|
const NO_RESUME = "the Agent's CLI could not resume this conversation";
|
|
34
|
+
const NOT_SIGNED_IN = "the Agent's CLI is not signed in; sign it in on the Agent's machine";
|
|
34
35
|
const NO_SETTINGS = "the Agent's CLI refused the Agent's model, mode or effort";
|
|
35
36
|
class NotStarted extends Error {
|
|
36
37
|
}
|
|
@@ -42,6 +43,14 @@ async function step(reason, work) {
|
|
|
42
43
|
throw new NotStarted(typeof reason === 'string' ? reason : reason(error), { cause: error });
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
function signedOut(error) {
|
|
47
|
+
return typeof error === 'object' && error !== null && error.code === SIGNED_OUT;
|
|
48
|
+
}
|
|
49
|
+
function unopened(reason) {
|
|
50
|
+
return (error) => (signedOut(error) ? NOT_SIGNED_IN : reason);
|
|
51
|
+
}
|
|
52
|
+
// A start-up step is shown only once it has lasted a second, so a quick start shows none of them.
|
|
53
|
+
const STARTING_SHOWN_AFTER_MS = 1_000;
|
|
45
54
|
function unbridged(error) {
|
|
46
55
|
return (finalRefusal(error) ? ENDED[refusalOf(error.text)?.code ?? ''] : undefined) ?? NO_CONNECTION;
|
|
47
56
|
}
|
|
@@ -58,8 +67,7 @@ function causeOf(error) {
|
|
|
58
67
|
return error instanceof Error ? error.message : String(error);
|
|
59
68
|
}
|
|
60
69
|
function answeredWith(error) {
|
|
61
|
-
|
|
62
|
-
return { failure: signedOut ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
70
|
+
return { failure: signedOut(error) ? 'signed_out' : 'error', cause: causeOf(error) };
|
|
63
71
|
}
|
|
64
72
|
function exitedWith(running, cause) {
|
|
65
73
|
return running.killed ? { failure: 'killed', cause: KILLED } : { failure: 'exited', cause };
|
|
@@ -83,10 +91,10 @@ function marker(bridge) {
|
|
|
83
91
|
class Turn {
|
|
84
92
|
id = randomUUID();
|
|
85
93
|
parts;
|
|
86
|
-
admitted = false;
|
|
87
94
|
questions = 0;
|
|
88
95
|
secrets = 0;
|
|
89
96
|
prompted = false;
|
|
97
|
+
taken = null;
|
|
90
98
|
ended = false;
|
|
91
99
|
images = 0;
|
|
92
100
|
uploads = [];
|
|
@@ -100,6 +108,8 @@ class Conversation {
|
|
|
100
108
|
session = null;
|
|
101
109
|
turn = null;
|
|
102
110
|
commands = null;
|
|
111
|
+
options = null;
|
|
112
|
+
exitUntold = false;
|
|
103
113
|
context = null;
|
|
104
114
|
leftovers = new Set();
|
|
105
115
|
waiting = [];
|
|
@@ -107,6 +117,7 @@ class Conversation {
|
|
|
107
117
|
subagents = new Map();
|
|
108
118
|
held = [];
|
|
109
119
|
opening = null;
|
|
120
|
+
launch = null;
|
|
110
121
|
prompted = null;
|
|
111
122
|
kills = 0;
|
|
112
123
|
killed = new AbortController();
|
|
@@ -115,6 +126,11 @@ class Conversation {
|
|
|
115
126
|
open = 0;
|
|
116
127
|
queue = Promise.resolve();
|
|
117
128
|
reports = Promise.resolve();
|
|
129
|
+
starting = null;
|
|
130
|
+
preparing = null;
|
|
131
|
+
told = null;
|
|
132
|
+
untold = false;
|
|
133
|
+
shown = null;
|
|
118
134
|
constructor(id) {
|
|
119
135
|
this.id = id;
|
|
120
136
|
}
|
|
@@ -187,6 +203,14 @@ export class Conversations {
|
|
|
187
203
|
}
|
|
188
204
|
opened() {
|
|
189
205
|
this.watched = false;
|
|
206
|
+
for (const conversation of this.conversations.values()) {
|
|
207
|
+
if (conversation.running !== null)
|
|
208
|
+
this.facts(conversation);
|
|
209
|
+
else if (conversation.exitUntold)
|
|
210
|
+
this.process(conversation, false);
|
|
211
|
+
if (conversation.untold || conversation.told !== null)
|
|
212
|
+
this.retell(conversation);
|
|
213
|
+
}
|
|
190
214
|
}
|
|
191
215
|
watching(watched) {
|
|
192
216
|
const resend = watched && !this.watched;
|
|
@@ -208,9 +232,10 @@ export class Conversations {
|
|
|
208
232
|
}
|
|
209
233
|
async carry(conversation, input) {
|
|
210
234
|
if (input.kind === 'open')
|
|
211
|
-
return { ack:
|
|
235
|
+
return { ack: this.open(conversation, input) };
|
|
212
236
|
if (input.kind === 'message')
|
|
213
237
|
return this.message(conversation, input);
|
|
238
|
+
await this.launched(conversation);
|
|
214
239
|
if (input.kind === 'interrupt') {
|
|
215
240
|
if (--conversation.interrupts === 0)
|
|
216
241
|
conversation.interrupted = new AbortController();
|
|
@@ -234,22 +259,58 @@ export class Conversations {
|
|
|
234
259
|
}
|
|
235
260
|
return { ack: Promise.resolve({}) };
|
|
236
261
|
}
|
|
237
|
-
|
|
262
|
+
open(conversation, input) {
|
|
238
263
|
if (conversation.running !== null)
|
|
239
|
-
return {};
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
264
|
+
return Promise.resolve({});
|
|
265
|
+
const launch = conversation.launch ?? this.launch(conversation, this.route(conversation, input.agent_id), null, true);
|
|
266
|
+
return launch.session.then((session) => ({ provider_session_id: session }), (error) => ({ refused: refusal(conversation, error) }));
|
|
267
|
+
}
|
|
268
|
+
launch(conversation, route, sessionId, empty) {
|
|
269
|
+
let opened;
|
|
270
|
+
let failed;
|
|
271
|
+
const bridge = new Promise((resolve, reject) => {
|
|
272
|
+
opened = resolve;
|
|
273
|
+
failed = reject;
|
|
274
|
+
});
|
|
275
|
+
bridge.catch(() => undefined);
|
|
276
|
+
const session = this.start(conversation, route, sessionId, empty, opened);
|
|
277
|
+
session.catch(failed);
|
|
278
|
+
const launch = { bridge, session };
|
|
279
|
+
conversation.launch = launch;
|
|
280
|
+
void session
|
|
281
|
+
.catch(() => undefined)
|
|
282
|
+
.then(() => {
|
|
283
|
+
if (conversation.launch !== launch)
|
|
284
|
+
return;
|
|
285
|
+
conversation.launch = null;
|
|
286
|
+
this.next(conversation);
|
|
287
|
+
});
|
|
288
|
+
this.next(conversation);
|
|
289
|
+
return launch;
|
|
290
|
+
}
|
|
291
|
+
async launched(conversation) {
|
|
292
|
+
while (conversation.launch !== null)
|
|
293
|
+
await conversation.launch.session.catch(() => undefined);
|
|
294
|
+
}
|
|
295
|
+
instructed(conversation, route) {
|
|
296
|
+
const bridge = conversation.running === null ? conversation.launch?.bridge : Promise.resolve(conversation.running.bridge);
|
|
297
|
+
if (bridge === undefined)
|
|
298
|
+
return null;
|
|
299
|
+
const read = Promise.all([bridge, this.kit.copies.mapping()]).then(([opened, copies]) => instructions(opened, route.base_instructions, copies));
|
|
300
|
+
read.catch(() => undefined);
|
|
301
|
+
return read;
|
|
246
302
|
}
|
|
247
303
|
async message(conversation, input) {
|
|
248
304
|
const content = [{ type: 'text', text: String(input.text) }];
|
|
249
305
|
let route;
|
|
306
|
+
let instructing = null;
|
|
250
307
|
try {
|
|
251
308
|
route = await this.route(conversation, input.agent_id);
|
|
309
|
+
if (input.first === true)
|
|
310
|
+
instructing = this.instructed(conversation, route);
|
|
252
311
|
const files = input.files;
|
|
312
|
+
if (files.length > 0)
|
|
313
|
+
this.prepares(conversation, 'files');
|
|
253
314
|
const paths = await placeFiles(this.kit.house, route.working_directory, files, conversation.killed.signal);
|
|
254
315
|
const lines = await this.transcribed(conversation, files, paths);
|
|
255
316
|
if (lines.length > 0)
|
|
@@ -258,11 +319,13 @@ export class Conversations {
|
|
|
258
319
|
throw new Error(KILLED);
|
|
259
320
|
}
|
|
260
321
|
catch (error) {
|
|
322
|
+
this.prepares(conversation, null);
|
|
261
323
|
return { ack: Promise.resolve({ refused: refusal(conversation, error) }) };
|
|
262
324
|
}
|
|
325
|
+
conversation.preparing = null;
|
|
263
326
|
return {
|
|
264
327
|
ack: new Promise((settle) => {
|
|
265
|
-
conversation.waiting.push({ input, route, content, settle });
|
|
328
|
+
conversation.waiting.push({ input, route, content, instructing, settle });
|
|
266
329
|
this.next(conversation);
|
|
267
330
|
}),
|
|
268
331
|
};
|
|
@@ -271,8 +334,12 @@ export class Conversations {
|
|
|
271
334
|
if (conversation.sending !== null || conversation.open > 0)
|
|
272
335
|
return;
|
|
273
336
|
const waiting = conversation.waiting.shift();
|
|
274
|
-
if (waiting === undefined)
|
|
337
|
+
if (waiting === undefined) {
|
|
338
|
+
const step = conversation.preparing ?? (conversation.launch === null ? null : 'session');
|
|
339
|
+
if (conversation.starting !== step)
|
|
340
|
+
this.starts(conversation, step);
|
|
275
341
|
return;
|
|
342
|
+
}
|
|
276
343
|
conversation.sending = this.send(conversation, waiting).finally(() => {
|
|
277
344
|
conversation.sending = null;
|
|
278
345
|
this.next(conversation);
|
|
@@ -282,27 +349,33 @@ export class Conversations {
|
|
|
282
349
|
const { input } = waiting;
|
|
283
350
|
let ack = {};
|
|
284
351
|
let started = false;
|
|
352
|
+
let instructing = waiting.instructing;
|
|
285
353
|
const prompt = [];
|
|
286
354
|
try {
|
|
355
|
+
if (conversation.running === null) {
|
|
356
|
+
this.starts(conversation, 'session');
|
|
357
|
+
await this.launched(conversation);
|
|
358
|
+
}
|
|
287
359
|
if (conversation.running === null) {
|
|
288
360
|
const stored = conversation.session ?? input.provider_session_id;
|
|
289
361
|
const empty = input.first === true && conversation.prompted !== stored;
|
|
290
|
-
|
|
362
|
+
const launch = this.launch(conversation, Promise.resolve(waiting.route), stored, empty);
|
|
363
|
+
if (input.first === true)
|
|
364
|
+
instructing = this.instructed(conversation, waiting.route);
|
|
365
|
+
started = (await launch.session) !== stored;
|
|
291
366
|
}
|
|
292
367
|
const session = conversation.running.sessionId;
|
|
293
368
|
if (started || (input.first === true && session !== input.provider_session_id)) {
|
|
294
369
|
ack = { provider_session_id: session };
|
|
295
370
|
}
|
|
296
371
|
if (input.first === true) {
|
|
297
|
-
prompt.push({
|
|
298
|
-
type: 'text',
|
|
299
|
-
text: await instructions(conversation.running.bridge, waiting.route.base_instructions, await this.kit.copies.mapping()),
|
|
300
|
-
});
|
|
372
|
+
prompt.push({ type: 'text', text: await (instructing ?? this.instructed(conversation, waiting.route)) });
|
|
301
373
|
}
|
|
302
374
|
if (conversation.kills > 0)
|
|
303
375
|
throw new Error(KILLED);
|
|
304
376
|
}
|
|
305
377
|
catch (error) {
|
|
378
|
+
this.starts(conversation, null);
|
|
306
379
|
if (started) {
|
|
307
380
|
conversation.session = null;
|
|
308
381
|
await this.kill(conversation);
|
|
@@ -310,15 +383,45 @@ export class Conversations {
|
|
|
310
383
|
waiting.settle({ refused: refusal(conversation, error) });
|
|
311
384
|
return;
|
|
312
385
|
}
|
|
313
|
-
this.
|
|
386
|
+
this.starts(conversation, null);
|
|
387
|
+
this.prompt(conversation, [...prompt, ...waiting.content], () => this.kit.inputs.hands(String(input.input_id), ack));
|
|
314
388
|
waiting.settle(ack);
|
|
315
389
|
}
|
|
390
|
+
starts(conversation, step) {
|
|
391
|
+
conversation.starting = step;
|
|
392
|
+
if (conversation.shown !== null)
|
|
393
|
+
clearTimeout(conversation.shown);
|
|
394
|
+
conversation.shown = null;
|
|
395
|
+
if (step === null || conversation.told !== null) {
|
|
396
|
+
this.tell(conversation, step);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
conversation.shown = setTimeout(() => {
|
|
400
|
+
conversation.shown = null;
|
|
401
|
+
if (conversation.starting === step)
|
|
402
|
+
this.tell(conversation, step);
|
|
403
|
+
}, STARTING_SHOWN_AFTER_MS);
|
|
404
|
+
}
|
|
405
|
+
prepares(conversation, step) {
|
|
406
|
+
conversation.preparing = step;
|
|
407
|
+
this.next(conversation);
|
|
408
|
+
}
|
|
409
|
+
tell(conversation, step) {
|
|
410
|
+
if (conversation.told === step && !conversation.untold)
|
|
411
|
+
return;
|
|
412
|
+
conversation.told = step;
|
|
413
|
+
this.retell(conversation);
|
|
414
|
+
}
|
|
415
|
+
retell(conversation) {
|
|
416
|
+
conversation.untold = !this.kit.send({ type: 'starting', conversation_id: conversation.id, step: conversation.told });
|
|
417
|
+
}
|
|
316
418
|
async transcribed(conversation, files, paths) {
|
|
317
419
|
const lines = [];
|
|
318
420
|
for (const [index, path] of paths.entries()) {
|
|
319
421
|
lines.push(path);
|
|
320
422
|
if (files[index].media_type?.startsWith('audio/') !== true)
|
|
321
423
|
continue;
|
|
424
|
+
this.prepares(conversation, 'transcription');
|
|
322
425
|
try {
|
|
323
426
|
const text = await transcribe(path, AbortSignal.any([conversation.killed.signal, conversation.interrupted.signal]));
|
|
324
427
|
if (text !== null && text !== '')
|
|
@@ -385,7 +488,8 @@ export class Conversations {
|
|
|
385
488
|
}
|
|
386
489
|
return route;
|
|
387
490
|
}
|
|
388
|
-
async start(conversation,
|
|
491
|
+
async start(conversation, routed, sessionId, empty, bridged) {
|
|
492
|
+
const route = await routed;
|
|
389
493
|
const reader = readerFor(route.kind);
|
|
390
494
|
reader.session = sessionId;
|
|
391
495
|
const app = client({ name: '@agentshouse/kit' })
|
|
@@ -404,6 +508,7 @@ export class Conversations {
|
|
|
404
508
|
let adapter = null;
|
|
405
509
|
try {
|
|
406
510
|
await unlessAborted(createHowWeWork(bridge), conversation.killed.signal);
|
|
511
|
+
bridged(bridge);
|
|
407
512
|
await CLIS[route.kind].allowHouse(bridge.env.PATH);
|
|
408
513
|
const streamed = await step(NO_CONNECTION, work.open(cli));
|
|
409
514
|
adapter = await step(NO_START, startAdapter(route.kind, cli, route.working_directory, app, (message) => {
|
|
@@ -415,21 +520,19 @@ export class Conversations {
|
|
|
415
520
|
const cwd = route.working_directory;
|
|
416
521
|
const _meta = CLIS[route.kind].sessionMeta(bridge.env.HOUSE_BRIDGE, conversation.id);
|
|
417
522
|
const resumed = sessionId === null ? null : await this.resume(conversation, route.kind, adapter, { sessionId, cwd, mcpServers: [], _meta }, empty);
|
|
418
|
-
const opened = resumed ?? (await step(NO_SESSION, agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
523
|
+
const opened = resumed ?? (await step(unopened(NO_SESSION), agent.request('session/new', { cwd, mcpServers: [], _meta })));
|
|
419
524
|
reader.session = opened.sessionId;
|
|
420
525
|
if (opened.sessionId !== sessionId && CLIS[route.kind].renames) {
|
|
421
526
|
const prompt = [{ type: 'text', text: `/rename ${conversation.id}` }];
|
|
422
|
-
await step(NO_SESSION, agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
527
|
+
await step(unopened(NO_SESSION), agent.request('session/prompt', { sessionId: opened.sessionId, prompt }));
|
|
423
528
|
}
|
|
424
529
|
const running = { adapter, bridge, sessionId: opened.sessionId, killed: false, reader, work, scope };
|
|
425
530
|
const options = await step(NO_SETTINGS, this.launchSettings(running, route, opened.configOptions ?? []));
|
|
426
531
|
void work.settled().then(() => scope.record());
|
|
427
532
|
conversation.running = running;
|
|
428
533
|
conversation.session = running.sessionId;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
this.commands(conversation, conversation.commands);
|
|
432
|
-
this.options(conversation, options);
|
|
534
|
+
conversation.options = options;
|
|
535
|
+
this.facts(conversation);
|
|
433
536
|
void adapter.lost.then((cause) => this.exited(conversation, running, cause));
|
|
434
537
|
return running.sessionId;
|
|
435
538
|
}
|
|
@@ -450,7 +553,7 @@ export class Conversations {
|
|
|
450
553
|
}
|
|
451
554
|
catch (error) {
|
|
452
555
|
if (!empty || conversation.kills > 0 || !CLIS[kind].unknownSession(error)) {
|
|
453
|
-
throw new NotStarted(NO_RESUME, { cause: error });
|
|
556
|
+
throw new NotStarted(unopened(NO_RESUME)(error), { cause: error });
|
|
454
557
|
}
|
|
455
558
|
logged(new Error(`conversation ${conversation.id} opens a new session: ${NO_RESUME}: ${causeOf(error)}`));
|
|
456
559
|
return null;
|
|
@@ -490,11 +593,22 @@ export class Conversations {
|
|
|
490
593
|
conversation.leftovers.add(running.scope);
|
|
491
594
|
conversation.running = null;
|
|
492
595
|
conversation.commands = null;
|
|
596
|
+
conversation.options = null;
|
|
493
597
|
for (const [id, question] of this.questions) {
|
|
494
598
|
if (question.conversation === conversation)
|
|
495
599
|
this.questions.delete(id);
|
|
496
600
|
}
|
|
497
|
-
this.
|
|
601
|
+
this.process(conversation, false);
|
|
602
|
+
}
|
|
603
|
+
process(conversation, running) {
|
|
604
|
+
conversation.exitUntold = !this.kit.send({ type: 'process', conversation_id: conversation.id, running }) && !running;
|
|
605
|
+
}
|
|
606
|
+
facts(conversation) {
|
|
607
|
+
this.process(conversation, true);
|
|
608
|
+
if (conversation.commands !== null)
|
|
609
|
+
this.commands(conversation, conversation.commands);
|
|
610
|
+
if (conversation.options !== null)
|
|
611
|
+
this.options(conversation, conversation.options);
|
|
498
612
|
}
|
|
499
613
|
commands(conversation, commands) {
|
|
500
614
|
conversation.commands = commands;
|
|
@@ -505,6 +619,7 @@ export class Conversations {
|
|
|
505
619
|
options(conversation, options) {
|
|
506
620
|
if (conversation.running === null)
|
|
507
621
|
return;
|
|
622
|
+
conversation.options = options;
|
|
508
623
|
this.kit.send({ type: 'options', conversation_id: conversation.id, options });
|
|
509
624
|
}
|
|
510
625
|
deliver(conversation, turn, event, body) {
|
|
@@ -519,7 +634,7 @@ export class Conversations {
|
|
|
519
634
|
return delivered;
|
|
520
635
|
}
|
|
521
636
|
write(conversation, turn, operation) {
|
|
522
|
-
if (
|
|
637
|
+
if (turn.ended || !this.watched)
|
|
523
638
|
return;
|
|
524
639
|
this.kit.send({ type: 'turn', conversation_id: conversation.id, turn_id: turn.id, ...operation });
|
|
525
640
|
}
|
|
@@ -530,12 +645,7 @@ export class Conversations {
|
|
|
530
645
|
begin(conversation) {
|
|
531
646
|
const turn = new Turn((operation) => this.write(conversation, turn, operation));
|
|
532
647
|
conversation.open++;
|
|
533
|
-
void this.deliver(conversation, turn, 'started', {})
|
|
534
|
-
.then(() => {
|
|
535
|
-
turn.admitted = true;
|
|
536
|
-
this.replay(conversation, turn);
|
|
537
|
-
})
|
|
538
|
-
.catch(logged);
|
|
648
|
+
void this.deliver(conversation, turn, 'started', {}).catch(logged);
|
|
539
649
|
conversation.turn = turn;
|
|
540
650
|
if (conversation.context !== null)
|
|
541
651
|
turn.parts.measure(conversation.context);
|
|
@@ -568,12 +678,14 @@ export class Conversations {
|
|
|
568
678
|
this.next(conversation);
|
|
569
679
|
this.kit.changed();
|
|
570
680
|
}
|
|
571
|
-
prompt(conversation, prompt) {
|
|
681
|
+
prompt(conversation, prompt, taken) {
|
|
572
682
|
const running = conversation.running;
|
|
573
683
|
const turn = this.begin(conversation);
|
|
574
684
|
turn.prompted = true;
|
|
685
|
+
turn.taken = taken;
|
|
575
686
|
conversation.prompted = running.sessionId;
|
|
576
687
|
running.adapter.connection.agent.request('session/prompt', { sessionId: running.sessionId, prompt }).then((answer) => {
|
|
688
|
+
this.took(turn);
|
|
577
689
|
const notice = answeredFailure(answer);
|
|
578
690
|
if (notice !== null) {
|
|
579
691
|
this.end(conversation, turn, { failure: notice.limit ? 'limit' : 'error', cause: notice.title }, notice.title);
|
|
@@ -586,6 +698,7 @@ export class Conversations {
|
|
|
586
698
|
this.end(conversation, turn, exitedWith(running, await running.adapter.exited));
|
|
587
699
|
}
|
|
588
700
|
else {
|
|
701
|
+
this.took(turn);
|
|
589
702
|
if (conversation.open === 1 && conversation.running === running) {
|
|
590
703
|
running.adapter.retire();
|
|
591
704
|
this.detach(conversation, running);
|
|
@@ -595,7 +708,9 @@ export class Conversations {
|
|
|
595
708
|
});
|
|
596
709
|
}
|
|
597
710
|
started(conversation) {
|
|
598
|
-
|
|
711
|
+
const turn = conversation.turn ?? this.begin(conversation);
|
|
712
|
+
this.took(turn);
|
|
713
|
+
return turn;
|
|
599
714
|
}
|
|
600
715
|
ended(conversation) {
|
|
601
716
|
const turn = conversation.turn;
|
|
@@ -690,6 +805,11 @@ export class Conversations {
|
|
|
690
805
|
}
|
|
691
806
|
this.apply(conversation, this.started(conversation), event);
|
|
692
807
|
}
|
|
808
|
+
took(turn) {
|
|
809
|
+
const taken = turn.taken;
|
|
810
|
+
turn.taken = null;
|
|
811
|
+
taken?.();
|
|
812
|
+
}
|
|
693
813
|
apply(conversation, turn, event) {
|
|
694
814
|
switch (event.kind) {
|
|
695
815
|
case 'text':
|
package/dist/copies.js
CHANGED
|
@@ -58,6 +58,9 @@ function expiredAuthority(error) {
|
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
function grouped(items, size) {
|
|
62
|
+
return items.flatMap((_, index) => (index % size === 0 ? [items.slice(index, index + size)] : []));
|
|
63
|
+
}
|
|
61
64
|
function covers(path, prefix) {
|
|
62
65
|
return prefix === '' || path === prefix || path.startsWith(`${prefix}/`);
|
|
63
66
|
}
|
|
@@ -215,7 +218,7 @@ export class LocalCopies {
|
|
|
215
218
|
async mapping() {
|
|
216
219
|
await this.answering;
|
|
217
220
|
return [...this.copies.values()]
|
|
218
|
-
.filter((copy) => copy.
|
|
221
|
+
.filter((copy) => copy.unfollowed === undefined)
|
|
219
222
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
220
223
|
.sort();
|
|
221
224
|
}
|
|
@@ -247,30 +250,24 @@ export class LocalCopies {
|
|
|
247
250
|
return this.holding([...new Set([...held().map((copy) => copy.key), ...synced.keys()])].sort(), async () => {
|
|
248
251
|
const said = [];
|
|
249
252
|
let refused = false;
|
|
253
|
+
const mark = refreshing ? 'said' : 'unsaid';
|
|
250
254
|
for (const copy of held()) {
|
|
251
|
-
|
|
252
|
-
if (returned && copy.erasing === undefined)
|
|
255
|
+
if (synced.has(copy.key) || copy.unfollowed === 'said' || copy.unfollowed === mark)
|
|
253
256
|
continue;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
said.push(`${rootOf(copy.room_handle)}:
|
|
257
|
-
}
|
|
258
|
-
catch (error) {
|
|
259
|
-
refused = true;
|
|
260
|
-
said.push(`${rootOf(copy.room_handle)}: ${causeOf(error)}`);
|
|
261
|
-
}
|
|
257
|
+
await this.save({ ...copy, unfollowed: mark });
|
|
258
|
+
if (refreshing)
|
|
259
|
+
said.push(`${rootOf(copy.room_handle)}: no longer followed; kept as it is`);
|
|
262
260
|
}
|
|
263
261
|
const current = [];
|
|
264
262
|
for (const [key, handle] of synced) {
|
|
265
263
|
try {
|
|
266
264
|
const held = this.copies.get(key);
|
|
267
|
-
if (held?.erasing)
|
|
268
|
-
continue;
|
|
269
265
|
if (held === undefined || !(await exists(held.repository))) {
|
|
270
266
|
said.push(await this.materialize(key, handle));
|
|
271
267
|
continue;
|
|
272
268
|
}
|
|
273
|
-
|
|
269
|
+
const { unfollowed, ...followed } = held;
|
|
270
|
+
current.push(unfollowed === undefined && held.room_handle === handle ? held : await this.save({ ...followed, room_handle: handle }));
|
|
274
271
|
}
|
|
275
272
|
catch (error) {
|
|
276
273
|
refused = true;
|
|
@@ -278,13 +275,12 @@ export class LocalCopies {
|
|
|
278
275
|
}
|
|
279
276
|
}
|
|
280
277
|
if (refreshing && current.length > 0) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
catch (error) {
|
|
278
|
+
const failed = await this.refreshed(current).then(() => null, houseCause);
|
|
279
|
+
for (const copy of current)
|
|
280
|
+
said.push(...moved(copy, this.copies.get(copy.key)));
|
|
281
|
+
if (failed !== null) {
|
|
286
282
|
refused = true;
|
|
287
|
-
said.push(
|
|
283
|
+
said.push(failed);
|
|
288
284
|
}
|
|
289
285
|
}
|
|
290
286
|
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
@@ -296,8 +292,6 @@ export class LocalCopies {
|
|
|
296
292
|
return;
|
|
297
293
|
await this.exclusive(key, async () => {
|
|
298
294
|
const copy = this.copies.get(key);
|
|
299
|
-
if (copy === undefined || copy.erasing)
|
|
300
|
-
return;
|
|
301
295
|
let moves = copy.moves;
|
|
302
296
|
for (const { from, to } of report.moves) {
|
|
303
297
|
const held = moves.find((move) => move.worktree === report.worktree && move.to === from);
|
|
@@ -353,12 +347,6 @@ export class LocalCopies {
|
|
|
353
347
|
this.copies.set(copy.key, copy);
|
|
354
348
|
return copy;
|
|
355
349
|
}
|
|
356
|
-
async erase(copy) {
|
|
357
|
-
await this.save({ ...copy, erasing: true });
|
|
358
|
-
await rm(copy.repository, { recursive: true, force: true });
|
|
359
|
-
await rm(recordOf(copy.key), { force: true });
|
|
360
|
-
this.copies.delete(copy.key);
|
|
361
|
-
}
|
|
362
350
|
async read(handle) {
|
|
363
351
|
const prefixes = [rootOf(handle)];
|
|
364
352
|
const discovery = await this.house.post('/kit/door/discover', { prefixes });
|
|
@@ -459,19 +447,24 @@ export class LocalCopies {
|
|
|
459
447
|
return next;
|
|
460
448
|
}
|
|
461
449
|
async refreshed(copies) {
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
});
|
|
465
|
-
const
|
|
450
|
+
const discover = (group) => this.house.post('/kit/door/discover', { prefixes: group.map((copy) => rootOf(copy.room_handle)) });
|
|
451
|
+
const [first, ...rest] = copies;
|
|
452
|
+
const { bounds, authorities } = await discover([first]);
|
|
453
|
+
for (const group of grouped(rest, bounds.max_prefixes))
|
|
454
|
+
authorities.push(...(await discover(group)).authorities);
|
|
455
|
+
const unchanged = [];
|
|
466
456
|
for (const copy of copies) {
|
|
467
|
-
const authority =
|
|
457
|
+
const authority = authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
468
458
|
if (authority !== undefined &&
|
|
469
459
|
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
470
|
-
|
|
460
|
+
await this.save(await this.received(copy));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
unchanged.push(copy);
|
|
471
464
|
}
|
|
472
465
|
}
|
|
473
|
-
const
|
|
474
|
-
|
|
466
|
+
for (const group of grouped(unchanged, bounds.max_prefixes))
|
|
467
|
+
await this.caughtUp(group, false);
|
|
475
468
|
}
|
|
476
469
|
async caughtUp(copies, whole) {
|
|
477
470
|
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
@@ -553,8 +546,6 @@ export class LocalCopies {
|
|
|
553
546
|
}
|
|
554
547
|
async pushed(key, worktree, selector, caller) {
|
|
555
548
|
let copy = this.copies.get(key);
|
|
556
|
-
if (copy === undefined || copy.erasing)
|
|
557
|
-
return outside(worktree);
|
|
558
549
|
if (copy.push?.state === 'submitted')
|
|
559
550
|
return this.settle(copy, caller);
|
|
560
551
|
const selected = await run(worktree, ['rev-parse', '--verify', '--quiet', '--end-of-options', `${selector}^{commit}`]);
|
|
@@ -669,8 +660,10 @@ export class LocalCopies {
|
|
|
669
660
|
}
|
|
670
661
|
if (outcome.refused.code === 'expired')
|
|
671
662
|
return this.recovered(copy);
|
|
672
|
-
const rebased = rebaseFixes(outcome.refused);
|
|
673
663
|
let settled = await this.save(without(copy));
|
|
664
|
+
if (settled.unfollowed !== undefined)
|
|
665
|
+
return { refused: true, text: outcome.refused.text };
|
|
666
|
+
const rebased = rebaseFixes(outcome.refused);
|
|
674
667
|
try {
|
|
675
668
|
settled = (await this.caughtUp([settled], true))[0];
|
|
676
669
|
}
|
package/dist/end.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KIT_VERSION } from './clis.js';
|
|
1
2
|
import { forgetRejected } from './home.js';
|
|
2
3
|
import { installCommand } from './placement.js';
|
|
3
4
|
import { killDescendants } from './scope.js';
|
|
@@ -8,7 +9,7 @@ function exit(reason, status) {
|
|
|
8
9
|
process.exit(status);
|
|
9
10
|
}
|
|
10
11
|
export function end(reason) {
|
|
11
|
-
exit(reason, 0);
|
|
12
|
+
exit(reason, process.env.HOUSE_KIT_RESIDENT === String(process.pid) ? 0 : 1);
|
|
12
13
|
}
|
|
13
14
|
export function restart(reason) {
|
|
14
15
|
exit(reason, 1);
|
|
@@ -17,6 +18,9 @@ export function updateRequired() {
|
|
|
17
18
|
const command = installCommand();
|
|
18
19
|
end(command === null ? 'This Kit is too old for House; install it again.' : `This Kit is too old for House. Update it: ${command}`);
|
|
19
20
|
}
|
|
21
|
+
export function newerThanHouse(release, command) {
|
|
22
|
+
end(`Kit ${KIT_VERSION} is newer than this Building supports (Kit ${release}). Install Kit ${release}: ${command}`);
|
|
23
|
+
}
|
|
20
24
|
export async function signInAgain(rejected) {
|
|
21
25
|
await forgetRejected(rejected);
|
|
22
26
|
restart('sign in again');
|
package/dist/events.js
CHANGED
|
@@ -2,7 +2,7 @@ const COMMAND = { shown: 'command' };
|
|
|
2
2
|
const HIDDEN = { shown: 'hidden' };
|
|
3
3
|
const FINISHED = new Set(['completed', 'failed']);
|
|
4
4
|
export const RUNNING_JOB = new Set(['running', 'paused']);
|
|
5
|
-
const
|
|
5
|
+
const MESSAGE_TARGETS = { send_routine_request: 'access', cancel_routine_request: 'request', send_mail: 'request' };
|
|
6
6
|
function words(line) {
|
|
7
7
|
const found = [];
|
|
8
8
|
let word = null;
|
|
@@ -48,7 +48,7 @@ export function houseMessage(line) {
|
|
|
48
48
|
catch {
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
-
const argument =
|
|
51
|
+
const argument = MESSAGE_TARGETS[call.command];
|
|
52
52
|
if (argument === undefined)
|
|
53
53
|
return null;
|
|
54
54
|
const target = call.arguments?.[argument];
|
package/dist/inputs.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { kitHome } from './home.js';
|
|
1
4
|
function logged(error) {
|
|
2
5
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
3
6
|
}
|
|
@@ -5,17 +8,28 @@ export class Inputs {
|
|
|
5
8
|
house;
|
|
6
9
|
changed;
|
|
7
10
|
carried = new Map();
|
|
11
|
+
path = join(kitHome(), 'handed-inputs.json');
|
|
12
|
+
handed;
|
|
13
|
+
answered = new Set();
|
|
8
14
|
open = 0;
|
|
9
15
|
constructor(house, changed) {
|
|
10
16
|
this.house = house;
|
|
11
17
|
this.changed = changed;
|
|
18
|
+
this.handed = new Map(existsSync(this.path) ? Object.entries(JSON.parse(readFileSync(this.path, 'utf8'))) : []);
|
|
19
|
+
}
|
|
20
|
+
hands(inputId, ack) {
|
|
21
|
+
if (this.answered.has(inputId))
|
|
22
|
+
return;
|
|
23
|
+
this.handed.set(inputId, ack);
|
|
24
|
+
this.save();
|
|
12
25
|
}
|
|
13
26
|
idle() {
|
|
14
27
|
return this.open === 0;
|
|
15
28
|
}
|
|
16
29
|
carry(inputId, work) {
|
|
17
30
|
const known = this.carried.get(inputId);
|
|
18
|
-
const
|
|
31
|
+
const handed = this.handed.get(inputId);
|
|
32
|
+
const carried = known ?? (handed === undefined ? work() : Promise.resolve({ ack: handed }));
|
|
19
33
|
if (known === undefined) {
|
|
20
34
|
this.carried.set(inputId, carried);
|
|
21
35
|
carried.catch(() => this.carried.delete(inputId));
|
|
@@ -24,7 +38,14 @@ export class Inputs {
|
|
|
24
38
|
this.changed();
|
|
25
39
|
return carried
|
|
26
40
|
.then(async ({ ack, acknowledged }) => {
|
|
27
|
-
|
|
41
|
+
try {
|
|
42
|
+
await this.house.deliver(`/kit/inputs/${inputId}/ack`, ack);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
this.answered.add(inputId);
|
|
46
|
+
if (this.handed.delete(inputId))
|
|
47
|
+
this.save();
|
|
48
|
+
}
|
|
28
49
|
if (known === undefined)
|
|
29
50
|
await acknowledged?.();
|
|
30
51
|
})
|
|
@@ -34,4 +55,9 @@ export class Inputs {
|
|
|
34
55
|
this.changed();
|
|
35
56
|
});
|
|
36
57
|
}
|
|
58
|
+
save() {
|
|
59
|
+
const temporary = `${this.path}.${process.pid}`;
|
|
60
|
+
writeFileSync(temporary, JSON.stringify(Object.fromEntries(this.handed)), { mode: 0o600 });
|
|
61
|
+
renameSync(temporary, this.path);
|
|
62
|
+
}
|
|
37
63
|
}
|
package/dist/instructions.js
CHANGED
|
@@ -4,9 +4,9 @@ const SOURCE_LINE = "From House:";
|
|
|
4
4
|
const HOUSE_LINE = "Work with House through the `house` CLI: run `house --help`, and `house find_command` for House's guides and formats.";
|
|
5
5
|
const FILE_LINE = 'To give the User a file, upload it with `house upload` and link it; for an App, a Site or a website, follow `house find_command \'{"query":"app"}\'`. Never start a server.';
|
|
6
6
|
const READ_LINE = "Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`.";
|
|
7
|
-
const
|
|
7
|
+
const REQUEST_LINE = "For another Agent's help, `send_routine_request`; its mail comes back into this conversation. If an Agent of your User has no Routine to call, write one in the Private Room and `create_routine_access` with your User as its only caller.";
|
|
8
8
|
const COPIES_LINE = `House Local copies; before work run \`house fetch\` and rebase onto \`${HOUSE_REF}\`; after it commit, then \`house push\`:`;
|
|
9
9
|
export async function instructions(bridge, base, copies) {
|
|
10
|
-
const lines = [SOURCE_LINE, HOUSE_LINE, FILE_LINE, READ_LINE,
|
|
10
|
+
const lines = [SOURCE_LINE, HOUSE_LINE, FILE_LINE, READ_LINE, REQUEST_LINE, ...(copies.length === 0 ? [] : [COPIES_LINE, ...copies])];
|
|
11
11
|
return [lines.join('\n'), base, (await readHowWeWork(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
|
|
12
12
|
}
|
package/dist/placement.js
CHANGED
|
@@ -18,9 +18,9 @@ export async function updatesItself() {
|
|
|
18
18
|
// sudo answers at once, so ten seconds only bounds a stuck sudo.
|
|
19
19
|
return (await run('sudo', ['-n', 'true'], 10_000)).status === 0;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
function recorded(name) {
|
|
22
22
|
try {
|
|
23
|
-
return readFileSync(join(kitHome(),
|
|
23
|
+
return readFileSync(join(kitHome(), name), 'utf8').trim();
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
26
|
if (error.code === 'ENOENT')
|
|
@@ -28,3 +28,12 @@ export function installCommand() {
|
|
|
28
28
|
throw error;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
export function installCommand() {
|
|
32
|
+
return recorded('install-command');
|
|
33
|
+
}
|
|
34
|
+
export function machine() {
|
|
35
|
+
const placed = recorded('placement');
|
|
36
|
+
if (placed === 'container' || placed === 'server' || placed === 'windows')
|
|
37
|
+
return placed;
|
|
38
|
+
return process.platform === 'darwin' ? 'macos' : 'linux';
|
|
39
|
+
}
|
package/dist/scope.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
const LISTING_BYTES = 1 << 28;
|
|
5
5
|
const CGROUPS = '/sys/fs/cgroup';
|
|
6
6
|
const GONE = new Set(['ENOENT', 'ESRCH']);
|
|
7
|
+
const EXITED = new Set(['Z', 'X']);
|
|
7
8
|
function failure(error) {
|
|
8
9
|
return error.code ?? '';
|
|
9
10
|
}
|
|
@@ -24,6 +25,8 @@ function read(pid) {
|
|
|
24
25
|
try {
|
|
25
26
|
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
26
27
|
const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
28
|
+
if (EXITED.has(fields[0]))
|
|
29
|
+
return null;
|
|
27
30
|
const command = readFileSync(`/proc/${pid}/cmdline`, 'utf8').replaceAll('\0', ' ');
|
|
28
31
|
return { parent: Number(fields[1]), session: fields[3], command };
|
|
29
32
|
}
|
package/dist/stream.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { subscribe } from 'node:diagnostics_channel';
|
|
2
2
|
import { HouseRefusal, retryDelay } from './api.js';
|
|
3
|
-
import { end, REPLACED, signInAgain
|
|
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 { endUnserved, KIT_WIRE } from './wire.js';
|
|
7
7
|
const PATH = '/kit/stream';
|
|
8
8
|
const SUBPROTOCOL = `house.kit.stream.${KIT_WIRE}`;
|
|
9
9
|
const REFUSAL_HEADER = 'x-house-refusal';
|
|
@@ -80,8 +80,8 @@ export function holdStream(handlers) {
|
|
|
80
80
|
void signInAgain(credential);
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
-
if (refused !== null
|
|
84
|
-
|
|
83
|
+
if (refused !== null)
|
|
84
|
+
endUnserved(refused.text);
|
|
85
85
|
if (!opened && refused !== null)
|
|
86
86
|
process.stderr.write(`kit: ${refused.message}\n`);
|
|
87
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.
|
package/dist/translate.js
CHANGED
|
@@ -3,14 +3,8 @@ function same(left, right) {
|
|
|
3
3
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
4
4
|
}
|
|
5
5
|
function applied(document, changes) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const result = apply(current, change);
|
|
9
|
-
if (result.outcome !== 'applied')
|
|
10
|
-
return null;
|
|
11
|
-
current = result.document;
|
|
12
|
-
}
|
|
13
|
-
return serialize(current);
|
|
6
|
+
const result = apply(document, changes);
|
|
7
|
+
return result.outcome === 'applied' ? serialize(result.document) : null;
|
|
14
8
|
}
|
|
15
9
|
function components(path, before, after, text) {
|
|
16
10
|
const edits = [];
|
package/dist/wire.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
import { newerThanHouse, updateRequired } from './end.js';
|
|
2
|
+
import { machine } from './placement.js';
|
|
1
3
|
import { refusalOf } from './refusals.js';
|
|
2
4
|
export const KIT_WIRE = 2;
|
|
3
5
|
export const KIT_WIRE_HEADER = 'x-house-kit-wire';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
function newer(text) {
|
|
7
|
+
let body;
|
|
8
|
+
try {
|
|
9
|
+
body = JSON.parse(text);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return body?.error?.code === 'kit_newer_than_house' ? body.error : null;
|
|
15
|
+
}
|
|
16
|
+
export function endUnserved(text) {
|
|
17
|
+
if (refusalOf(text)?.code === 'kit_update_required')
|
|
18
|
+
updateRequired();
|
|
19
|
+
const refused = newer(text);
|
|
20
|
+
if (refused !== null)
|
|
21
|
+
newerThanHouse(refused.release, refused.commands[machine()]);
|
|
6
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentshouse/kit",
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-cb8dc89b78e07",
|
|
4
4
|
"description": "House Kit connects an Environment to agents.house.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@agentclientprotocol/sdk": "1.7.0",
|
|
31
|
-
"@agentshouse/mdmodel": "0.1.0-
|
|
31
|
+
"@agentshouse/mdmodel": "0.1.0-cabd61587647f",
|
|
32
32
|
"yaml": "2.9.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|