@agentshouse/kit 0.0.1 → 0.1.0-alpha.2

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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1696 -2
  3. package/bin/connect-linux.sh +127 -0
  4. package/bin/kit.js +1023 -0
  5. package/package.json +33 -3
  6. package/src/acp.js +99 -0
  7. package/src/adapter-mcp.js +44 -0
  8. package/src/adapter.js +252 -0
  9. package/src/agent-logins.js +65 -0
  10. package/src/agents.js +449 -0
  11. package/src/apply.js +29 -0
  12. package/src/attach.js +52 -0
  13. package/src/binding.js +90 -0
  14. package/src/blocks.js +156 -0
  15. package/src/bridge.js +449 -0
  16. package/src/bytes.js +89 -0
  17. package/src/channel.js +37 -0
  18. package/src/command.js +109 -0
  19. package/src/configuration.js +465 -0
  20. package/src/connector-login.js +91 -0
  21. package/src/connector.js +107 -0
  22. package/src/continuation.js +61 -0
  23. package/src/contract.js +363 -0
  24. package/src/conversation-originals.js +143 -0
  25. package/src/custom/authorization.js +63 -0
  26. package/src/declaration.js +35 -0
  27. package/src/delivery.js +191 -0
  28. package/src/directory.js +102 -0
  29. package/src/draft.js +89 -0
  30. package/src/google/calendar.js +177 -0
  31. package/src/google/declared.js +146 -0
  32. package/src/google/drive.js +435 -0
  33. package/src/google/gateway.js +166 -0
  34. package/src/google/gmail.js +249 -0
  35. package/src/google/id.js +1 -0
  36. package/src/google/oauth.js +273 -0
  37. package/src/google/source.js +25 -0
  38. package/src/hook.js +376 -0
  39. package/src/host.js +121 -0
  40. package/src/house-main.js +79 -0
  41. package/src/house.js +1085 -0
  42. package/src/inbound.js +175 -0
  43. package/src/instance/google.js +1 -0
  44. package/src/launcher-main.js +19 -0
  45. package/src/launcher.js +276 -0
  46. package/src/local-files.js +113 -0
  47. package/src/login.js +274 -0
  48. package/src/mcp.js +71 -0
  49. package/src/migrator.js +248 -0
  50. package/src/mount.js +582 -0
  51. package/src/operation.js +20 -0
  52. package/src/original.js +631 -0
  53. package/src/peer.js +124 -0
  54. package/src/plan.js +57 -0
  55. package/src/question.js +316 -0
  56. package/src/refusal.js +79 -0
  57. package/src/repository.js +521 -0
  58. package/src/session-bin/house +2 -0
  59. package/src/settlement.js +57 -0
  60. package/src/setup.js +185 -0
  61. package/src/source-originals.js +116 -0
  62. package/src/source.js +286 -0
  63. package/src/starter-set/grilling/LICENSE +21 -0
  64. package/src/starter-set/grilling/SKILL.md +30 -0
  65. package/src/starter-set/grilling/agents/openai.yaml +3 -0
  66. package/src/starter-set.js +17 -0
  67. package/src/state.js +157 -0
  68. package/src/sync-library.js +293 -0
  69. package/src/sync-state.js +116 -0
  70. package/src/sync.js +477 -0
  71. package/src/telegram.js +1045 -0
  72. package/src/terminal.js +57 -0
  73. package/src/tmux.js +119 -0
  74. package/src/worker.js +756 -0
  75. package/src/workspace.js +500 -0
package/bin/kit.js ADDED
@@ -0,0 +1,1023 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ import { connectHouse, storedEnvironment, storedHouse } from "../src/login.js";
6
+ import { apply } from "../src/apply.js";
7
+ import { captureSource, resetSource, retrySourceOriginals, sourceStatus } from "../src/source.js";
8
+ import { setupEnvironment } from "../src/setup.js";
9
+ import { signInConnector } from "../src/connector-login.js";
10
+ import { attachSession } from "../src/attach.js";
11
+ import { runResident, runWorkers } from "../src/worker.js";
12
+ import { openSession, releaseControl } from "../src/house.js";
13
+ import { GOOGLE_OAUTH_CLIENT } from "../src/instance/google.js";
14
+ import { reportHost } from "../src/host.js";
15
+ import { signInAgent, updateAgents } from "../src/agents.js";
16
+ import { serveWorkspaces } from "../src/workspace.js";
17
+ import { captureRepository } from "../src/repository.js";
18
+ import { bootstrap, pull, rebootstrap, submit } from "../src/sync.js";
19
+ import { formatMount, formatUnmount, mountPackage, unmountSubtree } from "../src/mount.js";
20
+ import { emitMigratorPackage, readMigratorPackage } from "../src/migrator.js";
21
+ import { declaredMigrator, readConfiguration } from "../src/configuration.js";
22
+ import { resolveEnvironment } from "../src/command.js";
23
+ import { formatAttachmentGet, formatAttachmentUpload, getAttachment, uploadAttachment } from "../src/original.js";
24
+ import {
25
+ connectPersonalBot,
26
+ disconnectPersonalBot,
27
+ serveTelegramGateway,
28
+ } from "../src/telegram.js";
29
+ import { formatCommandError, KitRefusal } from "../src/refusal.js";
30
+
31
+ const USAGE =
32
+ "usage: kit <login|resident|credential|setup|apply|agents|source|repository|worker|sync|migrate|attachment|host|workspace|telegram> ...";
33
+ const LOGIN_USAGE =
34
+ "usage: kit login [--house <url>] [--environment <uuid>] [--manual|--host-browser]";
35
+ const HOST_USAGE = "usage: kit host report [--house <url>] [--config <path>]";
36
+ const AGENTS_USAGE =
37
+ "usage: kit agents update [--house <url>] [--config <path>] | kit agents sign-in <kind> [--house <url>] [--config <path>]";
38
+ const WORKSPACE_USAGE = "usage: kit workspace host [--house <url>] [--config <path>] [--passes <count>]";
39
+ const SOURCE_USAGE =
40
+ "usage: kit source <capture|reset|retry-files> <instance> [service] [--house <url>] [--config <path>]\n" +
41
+ " kit source status <instance> [service] [--config <path>]\n" +
42
+ " kit source sign-in <instance> [--config <path>]";
43
+ const SETUP_USAGE = "usage: kit setup [--house <url>] [--config <path>]";
44
+ const REPOSITORY_USAGE =
45
+ "usage: kit repository capture <checkout> --room <room> --source-handle <handle> [--house <url>]";
46
+ const WORKER_USAGE =
47
+ "usage: kit worker run [--house <url>] [--config <path>] | kit worker attach [<session>]";
48
+ const SYNC_USAGE =
49
+ "usage: kit sync <bootstrap|pull|rebootstrap|submit> ...";
50
+ const SYNC_BOOTSTRAP_USAGE =
51
+ "usage: kit sync bootstrap <directory> [--house <url>] --prefix <path> [--prefix <path> ...]";
52
+ const SYNC_PULL_USAGE = "usage: kit sync pull <directory> [--house <url>]";
53
+ const SYNC_REBOOTSTRAP_USAGE = "usage: kit sync rebootstrap <directory> [--house <url>]";
54
+ const SYNC_SUBMIT_USAGE =
55
+ "usage: kit sync submit <directory> [--house <url>] [--changes <path>]";
56
+ const MIGRATE_USAGE = "usage: kit migrate <emit|mount|unmount|run> ...";
57
+ const MIGRATE_EMIT_USAGE =
58
+ "usage: kit migrate emit <migrator> --package <directory> [--config <path>]";
59
+ const MIGRATE_MOUNT_USAGE =
60
+ "usage: kit migrate mount <package> [--house <url>] --subtree <path>";
61
+ const MIGRATE_UNMOUNT_USAGE =
62
+ "usage: kit migrate unmount <subtree> [--house <url>] [--confirm <subtree>]";
63
+ const MIGRATE_RUN_USAGE =
64
+ "usage: kit migrate run <migrator> [--house <url>] --subtree <path> --package <directory> [--config <path>]";
65
+ const ATTACHMENT_USAGE = "usage: kit attachment <upload|get> ...";
66
+ const ATTACHMENT_UPLOAD_USAGE =
67
+ "usage: kit attachment upload <file> [--house <url>] (--room <ref> | --private) [--name <name>] [--media-type <type>]";
68
+ const ATTACHMENT_GET_USAGE =
69
+ "usage: kit attachment get [--house <url>] --attachment <at_ref> (--room <ref> | --private) [--output <path>]";
70
+ const TELEGRAM_USAGE =
71
+ "usage: kit telegram <connect|disconnect> [--house <url>]\n" +
72
+ " kit telegram serve [--house <url>] [--passes <count>]";
73
+
74
+ function declared(argv, accepted, positionals, options = {}) {
75
+ const repeated = options.repeated ?? [];
76
+ const exact = options.exact === true;
77
+ const values = {};
78
+ const lists = {};
79
+ for (const name of repeated) lists[name] = [];
80
+ const rest = [];
81
+ for (let index = 0; index < argv.length; index += 1) {
82
+ const argument = argv[index];
83
+ if (!argument.startsWith("--")) {
84
+ rest.push(argument);
85
+ continue;
86
+ }
87
+ if (!accepted.includes(argument)) throw new Error(`unknown argument: ${argument}`);
88
+ if (argument === "--manual" || (options.flags ?? []).includes(argument)) {
89
+ values[argument] = true;
90
+ continue;
91
+ }
92
+ index += 1;
93
+ if (argv[index] === undefined) throw new Error(`${argument} expects a value`);
94
+ if (repeated.includes(argument)) lists[argument].push(argv[index]);
95
+ else values[argument] = argv[index];
96
+ }
97
+ if (exact) {
98
+ if (rest.length !== positionals) {
99
+ throw new Error(`unknown argument: ${rest[positionals] ?? "missing directory"}`);
100
+ }
101
+ } else if (rest.length > positionals) {
102
+ throw new Error(`unknown argument: ${rest[positionals]}`);
103
+ }
104
+ return { values, lists, rest };
105
+ }
106
+
107
+ function write(line) {
108
+ process.stdout.write(`${line}\n`);
109
+ }
110
+
111
+ function builtGoogleOAuthClient() {
112
+ if (GOOGLE_OAUTH_CLIENT === null) {
113
+ throw new KitRefusal("google_oauth_client_unconfigured", "run npm run build");
114
+ }
115
+ return GOOGLE_OAUTH_CLIENT;
116
+ }
117
+
118
+ async function selectedHouse(values, directory) {
119
+ return storedHouse(directory, false, values["--house"]);
120
+ }
121
+
122
+ async function runLogin(args, directory) {
123
+ const { values } = declared(args, ["--house", "--manual", "--host-browser", "--environment"], 0, { flags: ["--host-browser"] });
124
+ if (values["--manual"] && values["--host-browser"]) throw new Error(LOGIN_USAGE);
125
+ const house = await storedHouse(directory, true, values["--house"]);
126
+ const presentation = values["--manual"] === true ? "manual" : "loopback";
127
+ await connectHouse({
128
+ house,
129
+ presentation,
130
+ directory,
131
+ hostBrowser: values["--host-browser"] === true,
132
+ hostname: process.env.HOUSE_KIT_HOSTNAME,
133
+ ...(values["--environment"] === undefined ? {} : { environment: values["--environment"] }),
134
+ });
135
+ }
136
+
137
+ async function runCredentialCheck(args, directory) {
138
+ const { values } = declared(args, ["--house"], 0, { exact: true });
139
+ if (await storedEnvironment(directory) === null) throw new KitRefusal("kit_environment_invalid", "no enrolled Environment id");
140
+ const house = await selectedHouse(values, directory);
141
+ let session;
142
+ try {
143
+ session = await openSession({ house, directory });
144
+ } catch (error) {
145
+ if (error instanceof KitRefusal && error.code === "environment_control_lease_active") {
146
+ write("Stored Kit authority is accepted; its Environment already has an active control lease.");
147
+ return;
148
+ }
149
+ throw error;
150
+ }
151
+ await releaseControl(session, { required: true });
152
+ write("Stored Kit authority is accepted.");
153
+ }
154
+
155
+ async function runResidentCommand(args, directory) {
156
+ const { values } = declared(args, ["--house", "--config"], 0, { exact: true });
157
+ const house = await selectedHouse(values, directory);
158
+ const cancellation = new AbortController();
159
+ const stop = () => cancellation.abort();
160
+ process.once("SIGINT", stop);
161
+ process.once("SIGTERM", stop);
162
+ try {
163
+ await runResident({ house, directory, configuration: values["--config"] ?? join(directory, "kit.json"), signal: cancellation.signal });
164
+ } finally {
165
+ process.removeListener("SIGINT", stop);
166
+ process.removeListener("SIGTERM", stop);
167
+ }
168
+ }
169
+
170
+ async function runSetup(args, directory) {
171
+ const { values } = declared(args, ["--house", "--config"], 0, { exact: true });
172
+ const house = await selectedHouse(values, directory);
173
+ if (!house) throw new Error(SETUP_USAGE);
174
+ const cancellation = new AbortController();
175
+ const cancel = () => cancellation.abort();
176
+ process.once("SIGINT", cancel);
177
+ let setup;
178
+ try {
179
+ setup = await setupEnvironment(
180
+ {
181
+ house,
182
+ directory,
183
+ configuration: values["--config"] ?? join(directory, "kit.json"),
184
+ signal: cancellation.signal,
185
+ },
186
+ { googleOAuthClient: builtGoogleOAuthClient },
187
+ );
188
+ } finally {
189
+ process.removeListener("SIGINT", cancel);
190
+ }
191
+ if (setup === null) {
192
+ write("House requests no setup for this Environment.");
193
+ return;
194
+ }
195
+ for (const module of setup.modules) {
196
+ write(
197
+ `${module.category} ${module.name}: ${module.status}${module.reason === null ? "" : ` (${module.reason})`}`,
198
+ );
199
+ }
200
+ if (setup.modules.some((module) => module.status === "needs_attention")) process.exitCode = 1;
201
+ }
202
+
203
+ async function runSourceSignIn(args, directory) {
204
+ const { values, rest } = declared(args, ["--config"], 1);
205
+ const [instance] = rest;
206
+ if (!instance) throw new Error(SOURCE_USAGE);
207
+ const cancellation = new AbortController();
208
+ const cancel = () => cancellation.abort();
209
+ process.once("SIGINT", cancel);
210
+ let status;
211
+ try {
212
+ status = await signInConnector({
213
+ directory,
214
+ instance,
215
+ configuration: values["--config"] ?? join(directory, "kit.json"),
216
+ signal: cancellation.signal,
217
+ });
218
+ } finally {
219
+ process.removeListener("SIGINT", cancel);
220
+ }
221
+ write(`${instance}: ${status}`);
222
+ if (status === "needs_attention") process.exitCode = 1;
223
+ }
224
+
225
+ async function runApply(args, directory) {
226
+ const { values } = declared(args, ["--house", "--config"], 0);
227
+ const house = await selectedHouse(values, directory);
228
+ if (!house) throw new Error("usage: kit apply --house <url> [--config <path>]");
229
+ const cancellation = new AbortController();
230
+ const cancel = () => cancellation.abort();
231
+ process.once("SIGINT", cancel);
232
+ let result;
233
+ try {
234
+ result = await apply({
235
+ house,
236
+ configuration: values["--config"] ?? join(directory, "kit.json"),
237
+ directory,
238
+ signal: cancellation.signal,
239
+ });
240
+ } finally {
241
+ process.removeListener("SIGINT", cancel);
242
+ }
243
+ write(`Applied the Environment configuration with ${result.schedules} schedules.`);
244
+ for (const hook of result.hooks) {
245
+ write(
246
+ `Installed the ${hook.id} SessionStart hook ${hook.version} for ${hook.kind} in ${hook.settings}.`,
247
+ );
248
+ }
249
+ for (const settings of result.removed) {
250
+ write(`Removed this installation's SessionStart hook from ${settings}.`);
251
+ }
252
+ }
253
+
254
+ async function runSourceCapture(args, directory) {
255
+ const { values, rest } = declared(args, ["--house", "--config"], 2);
256
+ const house = await selectedHouse(values, directory);
257
+ if (!house || rest.length === 0) throw new Error(SOURCE_USAGE);
258
+ const cancellation = new AbortController();
259
+ process.once("SIGINT", () => cancellation.abort());
260
+ const result = await captureSource(
261
+ {
262
+ house,
263
+ directory,
264
+ configuration: values["--config"] ?? join(directory, "kit.json"),
265
+ instance: rest[0],
266
+ service: rest[1] ?? null,
267
+ signal: cancellation.signal,
268
+ },
269
+ { googleOAuthClient: builtGoogleOAuthClient },
270
+ );
271
+ for (const delivered of result.services) {
272
+ const category =
273
+ delivered.health.category === null ? "" : ` (${delivered.health.category})`;
274
+ write(
275
+ `Delivered ${delivered.delivered} items for ${result.instance}/${delivered.service} in ${delivered.batches} batches; health ${delivered.health.state}${category}.`,
276
+ );
277
+ if (delivered.refused !== null) {
278
+ write(
279
+ `House refused ${result.instance}/${delivered.service} with ${delivered.refused.code}: ${JSON.stringify(delivered.refused.payload)}`,
280
+ );
281
+ }
282
+ writeSourceOriginals(result.instance, delivered);
283
+ }
284
+ if (result.services.some((delivered) => delivered.health.state !== "ok")) process.exitCode = 1;
285
+ }
286
+
287
+ function writeSourceOriginals(instance, service) {
288
+ for (const reference of service.originals) {
289
+ const failure = reference.error ?? reference.save.failure;
290
+ write(
291
+ `Original ${instance}/${service.service} ${reference.native_object_id} attachment ${reference.position} in ${reference.room} record ${reference.record}: ` +
292
+ `${reference.save.status}${failure === null ? "" : ` (${failure})`}` +
293
+ `${reference.version === null ? "" : ` version ${reference.version}`}.`,
294
+ );
295
+ if (reference.save.status !== "saved") process.exitCode = 1;
296
+ }
297
+ }
298
+
299
+ async function runSourceRetryFiles(args, directory) {
300
+ const { values, rest } = declared(args, ["--house", "--config"], 2);
301
+ const house = await selectedHouse(values, directory);
302
+ if (!house || rest.length === 0) throw new Error(SOURCE_USAGE);
303
+ const cancellation = new AbortController();
304
+ process.once("SIGINT", () => cancellation.abort());
305
+ const result = await retrySourceOriginals({
306
+ house,
307
+ directory,
308
+ configuration: values["--config"] ?? join(directory, "kit.json"),
309
+ instance: rest[0],
310
+ service: rest[1] ?? null,
311
+ signal: cancellation.signal,
312
+ });
313
+ for (const retried of result.services) {
314
+ if (retried.originals.length === 0) {
315
+ write(`No unsaved originals for ${result.instance}/${retried.service}.`);
316
+ }
317
+ writeSourceOriginals(result.instance, retried);
318
+ }
319
+ }
320
+
321
+ async function runSourceStatus(args, directory) {
322
+ const { values, rest } = declared(args, ["--config"], 2);
323
+ if (rest.length === 0) throw new Error(SOURCE_USAGE);
324
+ const result = await sourceStatus({
325
+ directory,
326
+ configuration: values["--config"] ?? join(directory, "kit.json"),
327
+ instance: rest[0],
328
+ service: rest[1] ?? null,
329
+ });
330
+ for (const reported of result.services) {
331
+ const category = reported.health.category === null ? "" : ` (${reported.health.category})`;
332
+ const observed =
333
+ reported.health.observed_at === null ? "never observed" : reported.health.observed_at;
334
+ write(
335
+ `${result.instance}/${reported.service} -> ${reported.source_ref} in ${reported.rooms.join(", ")}: ` +
336
+ `${reported.health.state}${category} at ${observed}; ` +
337
+ `cursor ${reported.checkpoint?.cursor ?? "none"}, ${reported.gaps.length} gaps, ` +
338
+ `${reported.dropped.length} dropped items, ${reported.originals.length} unsaved originals.`,
339
+ );
340
+ }
341
+ if (result.services.some((reported) => reported.health.state !== "ok")) process.exitCode = 1;
342
+ }
343
+
344
+ async function runSourceReset(args, directory) {
345
+ const { values, rest } = declared(args, ["--house", "--config"], 2);
346
+ const house = await selectedHouse(values, directory);
347
+ if (!house || rest.length === 0) throw new Error(SOURCE_USAGE);
348
+ const result = await resetSource({
349
+ house,
350
+ directory,
351
+ configuration: values["--config"] ?? join(directory, "kit.json"),
352
+ instance: rest[0],
353
+ service: rest[1] ?? null,
354
+ });
355
+ write(
356
+ result.services.length === 0
357
+ ? `No checkpoint to reset for ${result.instance}.`
358
+ : `Reset ${result.services.join(", ")} for ${result.instance}; the next capture restarts at the configured backfill boundary.`,
359
+ );
360
+ }
361
+
362
+ async function runRepositoryCapture(args, directory) {
363
+ const { values, rest } = declared(args, ["--house", "--room", "--source-handle"], 1);
364
+ const house = await selectedHouse(values, directory);
365
+ const room = values["--room"];
366
+ const sourceHandle = values["--source-handle"];
367
+ if (!house || !room || !sourceHandle || rest.length !== 1) throw new Error(REPOSITORY_USAGE);
368
+ const cancellation = new AbortController();
369
+ const cancel = () => cancellation.abort();
370
+ process.once("SIGINT", cancel);
371
+ let result;
372
+ try {
373
+ result = await captureRepository({
374
+ house,
375
+ directory,
376
+ checkout: rest[0],
377
+ room,
378
+ sourceHandle,
379
+ signal: cancellation.signal,
380
+ });
381
+ } finally {
382
+ process.removeListener("SIGINT", cancel);
383
+ }
384
+ const refused =
385
+ result.refused.length === 0
386
+ ? ""
387
+ : ` ${result.refused.length} refused (${result.refused
388
+ .map((item) => `${item.path}: ${item.outcome}`)
389
+ .join(", ")}).`;
390
+ write(
391
+ `Publication ${result.publication} of ${result.sourceRef} is ${result.ending};` +
392
+ ` delivered ${result.delivered} Repository paths.${refused}`,
393
+ );
394
+ }
395
+
396
+ function writeConversationFiles(supervised) {
397
+ const originals = [...(supervised.inbound?.originals ?? []), ...supervised.files.originals];
398
+ for (const original of originals) {
399
+ const failure = original.error ?? original.save.failure;
400
+ write(
401
+ `Conversation original for attachment ${original.attachment}: ${original.save.status}` +
402
+ `${failure === null ? "" : ` (${failure})`}${original.version === null ? "" : ` version ${original.version}`}.`,
403
+ );
404
+ }
405
+ for (const failure of supervised.files.failures) {
406
+ write(`Conversation originals could not be retried (${failure}).`);
407
+ }
408
+ const settled = new Map(originals.map((original) => [original.attachment, original.save.status]));
409
+ if ([...settled.values()].some((status) => status !== "saved") || supervised.files.failures.length > 0) {
410
+ process.exitCode = 1;
411
+ }
412
+ }
413
+
414
+ async function runWorkerRun(args, directory) {
415
+ const { values } = declared(args, ["--house", "--config"], 0);
416
+ const house = await selectedHouse(values, directory);
417
+ if (!house) throw new Error(WORKER_USAGE);
418
+ const supervised = await runWorkers(
419
+ {
420
+ house,
421
+ directory,
422
+ configuration: values["--config"] ?? join(directory, "kit.json"),
423
+ },
424
+ { googleOAuthClient: builtGoogleOAuthClient },
425
+ );
426
+ write(`Worker runtime ready on tmux ${supervised.tmux} in namespace ${supervised.namespace}.`);
427
+ if (supervised.epochReport !== null) {
428
+ write(`House started a new log epoch; reported ${supervised.epochReport.length} held Runs and House settled each.`);
429
+ }
430
+ writeConversationFiles(supervised);
431
+ for (const stop of supervised.stopped) {
432
+ write(`Run ${stop.run} stopped at its owner's request; House settled it.`);
433
+ }
434
+ for (const bound of supervised.unresolved) {
435
+ write(
436
+ bound.refused === null
437
+ ? `Run ${bound.run} stays bound to this installation and House did not answer.`
438
+ : `Run ${bound.run} stays bound to this installation; House refused its reported outcome (${bound.refused.code}).`,
439
+ );
440
+ }
441
+ if (supervised.unresolved.length > 0) {
442
+ write("Nothing was acquired while a bound WorkerSession stays unresolved.");
443
+ process.exitCode = 1;
444
+ }
445
+ const run = supervised.run;
446
+ if (run === null) {
447
+ if (supervised.stopped.length === 0 && supervised.unresolved.length === 0) {
448
+ write("No WorkerRun was waiting for this Environment.");
449
+ }
450
+ return;
451
+ }
452
+ if (run.stopped !== null) {
453
+ write(`Run ${run.run} stopped at its owner's request; House settled it.`);
454
+ return;
455
+ }
456
+ const reported = run.reported;
457
+ const outcome =
458
+ reported === null
459
+ ? "stopped with its Provider continuation retained for owner resumption"
460
+ : reported.outcome === "completed"
461
+ ? "reported completed"
462
+ : `reported failed (${reported.cause})`;
463
+ write(`Run ${run.run} ${outcome} after ${run.assistant.length} User-visible assistant messages.`);
464
+ if (!run.settled) {
465
+ write(
466
+ `Run ${run.run} stays bound to this installation until House accepts that report` +
467
+ `${run.refused === null ? "" : ` (${run.refused.code})`}.`,
468
+ );
469
+ }
470
+ if (!run.settled || reported?.outcome === "failed") process.exitCode = 1;
471
+ }
472
+
473
+ function syncCancellation() {
474
+ const cancellation = new AbortController();
475
+ const cancel = () => cancellation.abort();
476
+ process.once("SIGINT", cancel);
477
+ return {
478
+ signal: cancellation.signal,
479
+ done() {
480
+ process.removeListener("SIGINT", cancel);
481
+ },
482
+ };
483
+ }
484
+
485
+ async function runSyncBootstrap(args, directory) {
486
+ const { values, lists, rest } = declared(args, ["--house", "--prefix"], 1, {
487
+ repeated: ["--prefix"],
488
+ exact: true,
489
+ });
490
+ const house = await selectedHouse(values, directory);
491
+ if (!house || lists["--prefix"].length === 0) throw new Error(SYNC_BOOTSTRAP_USAGE);
492
+ const run = syncCancellation();
493
+ try {
494
+ const result = await bootstrap({
495
+ house,
496
+ directory,
497
+ replica: resolve(rest[0]),
498
+ prefixes: lists["--prefix"],
499
+ signal: run.signal,
500
+ });
501
+ write(
502
+ `Bootstrapped ${rest[0]} at ${result.positions.map((entry) => `${entry.authority}=${entry.position}`).join(", ")}.`,
503
+ );
504
+ } finally {
505
+ run.done();
506
+ }
507
+ }
508
+
509
+ async function runSyncPull(args, directory) {
510
+ const { values, rest } = declared(args, ["--house"], 1, { exact: true });
511
+ const house = await selectedHouse(values, directory);
512
+ if (!house) throw new Error(SYNC_PULL_USAGE);
513
+ const run = syncCancellation();
514
+ try {
515
+ const result = await pull({
516
+ house,
517
+ directory,
518
+ replica: resolve(rest[0]),
519
+ signal: run.signal,
520
+ });
521
+ write(
522
+ `Pulled ${rest[0]} at ${result.positions.map((entry) => `${entry.authority}=${entry.position}`).join(", ")}.`,
523
+ );
524
+ } finally {
525
+ run.done();
526
+ }
527
+ }
528
+
529
+ async function runSyncRebootstrap(args, directory) {
530
+ const { values, rest } = declared(args, ["--house"], 1, { exact: true });
531
+ const house = await selectedHouse(values, directory);
532
+ if (!house) throw new Error(SYNC_REBOOTSTRAP_USAGE);
533
+ const run = syncCancellation();
534
+ try {
535
+ const result = await rebootstrap({
536
+ house,
537
+ directory,
538
+ replica: resolve(rest[0]),
539
+ signal: run.signal,
540
+ });
541
+ write(
542
+ `Rebootstrapped ${rest[0]} at ${result.positions.map((entry) => `${entry.authority}=${entry.position}`).join(", ")}.`,
543
+ );
544
+ } finally {
545
+ run.done();
546
+ }
547
+ }
548
+
549
+ async function readChanges(path) {
550
+ const parsed = JSON.parse(await readFile(path, "utf8"));
551
+ if (Array.isArray(parsed)) return parsed;
552
+ if (Array.isArray(parsed?.changes)) return parsed.changes;
553
+ throw new KitRefusal("kit_sync_changes_invalid", path);
554
+ }
555
+
556
+ async function runSyncSubmit(args, directory) {
557
+ const { values, rest } = declared(args, ["--house", "--changes"], 1, { exact: true });
558
+ const house = await selectedHouse(values, directory);
559
+ if (!house) throw new Error(SYNC_SUBMIT_USAGE);
560
+ const run = syncCancellation();
561
+ try {
562
+ const result = await submit({
563
+ house,
564
+ directory,
565
+ replica: resolve(rest[0]),
566
+ changes: values["--changes"] === undefined ? undefined : await readChanges(values["--changes"]),
567
+ signal: run.signal,
568
+ });
569
+ write(
570
+ result?.submitted === false
571
+ ? `Submitted no changes for ${rest[0]}.`
572
+ : `Submitted to ${result.authority} at position ${result.position}.`,
573
+ );
574
+ } finally {
575
+ run.done();
576
+ }
577
+ }
578
+
579
+ function migrateCancellation() {
580
+ const cancellation = new AbortController();
581
+ const cancel = () => cancellation.abort();
582
+ process.once("SIGINT", cancel);
583
+ return {
584
+ signal: cancellation.signal,
585
+ done() {
586
+ process.removeListener("SIGINT", cancel);
587
+ },
588
+ };
589
+ }
590
+
591
+ async function emitPackage(migrator, packagePath, configuration, directory, signal) {
592
+ const declaration = declaredMigrator(
593
+ await readConfiguration(configuration ?? join(directory, "kit.json")),
594
+ migrator,
595
+ );
596
+ return emitMigratorPackage(declaration, {
597
+ package: resolve(packagePath),
598
+ environment: await resolveEnvironment(declaration),
599
+ signal,
600
+ });
601
+ }
602
+
603
+ async function runMigrateEmit(args, directory) {
604
+ const { values, rest } = declared(args, ["--package", "--config"], 1, { exact: true });
605
+ if (rest.length !== 1 || values["--package"] === undefined) throw new Error(MIGRATE_EMIT_USAGE);
606
+ const run = migrateCancellation();
607
+ try {
608
+ const emitted = await emitPackage(
609
+ rest[0],
610
+ values["--package"],
611
+ values["--config"],
612
+ directory,
613
+ run.signal,
614
+ );
615
+ const held = await readMigratorPackage(emitted);
616
+ write(
617
+ `Emitted ${Object.keys(held.content).length} documents and ${held.attachments.size} attachments for ${held.manifest.migrator} into ${emitted}.`,
618
+ );
619
+ } finally {
620
+ run.done();
621
+ }
622
+ }
623
+
624
+ async function mountEmitted(packagePath, options, run) {
625
+ const held = await readMigratorPackage(resolve(packagePath));
626
+ const result = await mountPackage(held, { ...options, signal: run.signal });
627
+ for (const line of formatMount(result)) write(line);
628
+ if (!result.reportSaved || result.transfers.some((transfer) => transfer.save.status !== "saved")) process.exitCode = 1;
629
+ }
630
+
631
+ async function runMigrateMount(args, directory) {
632
+ const { values, rest } = declared(args, ["--house", "--subtree"], 1, { exact: true });
633
+ const house = await selectedHouse(values, directory);
634
+ if (!house || rest.length !== 1 || values["--subtree"] === undefined) {
635
+ throw new Error(MIGRATE_MOUNT_USAGE);
636
+ }
637
+ const run = migrateCancellation();
638
+ try {
639
+ await mountEmitted(rest[0], { house, directory, subtree: values["--subtree"] }, run);
640
+ } finally {
641
+ run.done();
642
+ }
643
+ }
644
+
645
+ async function runMigrateRun(args, directory) {
646
+ const { values, rest } = declared(args, ["--house", "--subtree", "--package", "--config"], 1, {
647
+ exact: true,
648
+ });
649
+ const house = await selectedHouse(values, directory);
650
+ if (
651
+ !house ||
652
+ rest.length !== 1 ||
653
+ values["--subtree"] === undefined ||
654
+ values["--package"] === undefined
655
+ ) {
656
+ throw new Error(MIGRATE_RUN_USAGE);
657
+ }
658
+ const run = migrateCancellation();
659
+ try {
660
+ const emitted = await emitPackage(
661
+ rest[0],
662
+ values["--package"],
663
+ values["--config"],
664
+ directory,
665
+ run.signal,
666
+ );
667
+ await mountEmitted(emitted, { house, directory, subtree: values["--subtree"] }, run);
668
+ } finally {
669
+ run.done();
670
+ }
671
+ }
672
+
673
+ async function runMigrateUnmount(args, directory) {
674
+ const { values, rest } = declared(args, ["--house", "--confirm"], 1, { exact: true });
675
+ const house = await selectedHouse(values, directory);
676
+ if (!house || rest.length !== 1) throw new Error(MIGRATE_UNMOUNT_USAGE);
677
+ const run = migrateCancellation();
678
+ try {
679
+ const result = await unmountSubtree({
680
+ house,
681
+ directory,
682
+ subtree: rest[0],
683
+ signal: run.signal,
684
+ ...(values["--confirm"] === undefined ? {} : { confirm: values["--confirm"] }),
685
+ });
686
+ for (const line of formatUnmount(result)) write(line);
687
+ } finally {
688
+ run.done();
689
+ }
690
+ }
691
+
692
+ async function runMigrate(args, directory) {
693
+ const [action, ...rest] = args;
694
+ if (action === "emit") return runMigrateEmit(rest, directory);
695
+ if (action === "mount") return runMigrateMount(rest, directory);
696
+ if (action === "unmount") return runMigrateUnmount(rest, directory);
697
+ if (action === "run") return runMigrateRun(rest, directory);
698
+ throw new Error(MIGRATE_USAGE);
699
+ }
700
+
701
+ async function runSync(args, directory) {
702
+ const [action, ...rest] = args;
703
+ if (action === "bootstrap") return runSyncBootstrap(rest, directory);
704
+ if (action === "pull") return runSyncPull(rest, directory);
705
+ if (action === "rebootstrap") return runSyncRebootstrap(rest, directory);
706
+ if (action === "submit") return runSyncSubmit(rest, directory);
707
+ throw new Error(SYNC_USAGE);
708
+ }
709
+
710
+ async function runWorkerAttach(args, directory) {
711
+ const { rest } = declared(args, [], 1);
712
+ const attached = await attachSession({ directory, session: rest[0] ?? null });
713
+ write(`Detached from the supervised session ${attached.session}.`);
714
+ if (attached.status !== 0) process.exitCode = attached.status;
715
+ }
716
+
717
+ function attachmentCancellation() {
718
+ const cancellation = new AbortController();
719
+ const cancel = () => cancellation.abort();
720
+ process.once("SIGINT", cancel);
721
+ return {
722
+ signal: cancellation.signal,
723
+ done() {
724
+ process.removeListener("SIGINT", cancel);
725
+ },
726
+ };
727
+ }
728
+
729
+ async function runAttachmentUpload(args, directory) {
730
+ const { values, rest } = declared(
731
+ args,
732
+ ["--house", "--room", "--private", "--name", "--media-type"],
733
+ 1,
734
+ { flags: ["--private"] },
735
+ );
736
+ const house = await selectedHouse(values, directory);
737
+ if (!house || rest.length !== 1) throw new Error(ATTACHMENT_UPLOAD_USAGE);
738
+ const run = attachmentCancellation();
739
+ try {
740
+ const result = await uploadAttachment({
741
+ house,
742
+ directory,
743
+ file: rest[0],
744
+ room: values["--room"],
745
+ private: values["--private"] === true,
746
+ name: values["--name"],
747
+ mediaType: values["--media-type"],
748
+ signal: run.signal,
749
+ });
750
+ for (const line of formatAttachmentUpload(result)) write(line);
751
+ } finally {
752
+ run.done();
753
+ }
754
+ }
755
+
756
+ async function runAttachmentGet(args, directory) {
757
+ const { values } = declared(
758
+ args,
759
+ ["--house", "--attachment", "--room", "--private", "--output"],
760
+ 0,
761
+ { flags: ["--private"] },
762
+ );
763
+ const house = await selectedHouse(values, directory);
764
+ const attachment = values["--attachment"];
765
+ if (!house || !attachment) throw new Error(ATTACHMENT_GET_USAGE);
766
+ const run = attachmentCancellation();
767
+ try {
768
+ const result = await getAttachment({
769
+ house,
770
+ directory,
771
+ attachment,
772
+ room: values["--room"],
773
+ private: values["--private"] === true,
774
+ output: values["--output"],
775
+ signal: run.signal,
776
+ });
777
+ for (const line of formatAttachmentGet(result)) write(line);
778
+ } finally {
779
+ run.done();
780
+ }
781
+ }
782
+
783
+ async function runAttachment(args, directory) {
784
+ const [action, ...rest] = args;
785
+ if (action === "upload") return runAttachmentUpload(rest, directory);
786
+ if (action === "get") return runAttachmentGet(rest, directory);
787
+ throw new Error(ATTACHMENT_USAGE);
788
+ }
789
+
790
+ async function runHostReport(args, directory) {
791
+ const { values } = declared(args, ["--house", "--config"], 0, { exact: true });
792
+ const house = await selectedHouse(values, directory);
793
+ if (!house) throw new Error(HOST_USAGE);
794
+ const observed = await reportHost({
795
+ house,
796
+ directory,
797
+ configuration: values["--config"] ?? join(directory, "kit.json"),
798
+ });
799
+ write(
800
+ `Reported ${observed.cpu.available} of ${observed.cpu.total} millicores, ${observed.memory.available} of ${observed.memory.total} memory bytes, and ${observed.disk.available} of ${observed.disk.total} bytes on ${observed.disk.path}, measured at ${observed.observed_at}.`,
801
+ );
802
+ }
803
+
804
+ async function runAgentsUpdate(args, directory) {
805
+ const { values } = declared(args, ["--house", "--config"], 0, { exact: true });
806
+ const house = await selectedHouse(values, directory);
807
+ if (!house) throw new Error(AGENTS_USAGE);
808
+ const { updated } = await updateAgents({
809
+ house,
810
+ directory,
811
+ configuration: values["--config"] ?? join(directory, "kit.json"),
812
+ });
813
+ if (updated.length === 0) write("This Environment declares no agent CLI.");
814
+ for (const agent of updated) {
815
+ if (agent.acted === "installed-release") {
816
+ write(`Installed ${agent.kind} release ${agent.release} and reported it to House.`);
817
+ } else if (agent.acted === "update-failed") {
818
+ write(
819
+ `Could not update ${agent.kind} (${agent.failure}); kept it at release ${agent.release ?? "none"}.`,
820
+ );
821
+ process.exitCode = 1;
822
+ } else if (agent.acted === "held-candidate") {
823
+ write(
824
+ `Held ${agent.kind} at ${agent.release ?? "no installed release"}: release ${agent.held} did not pass the contract's conformance check.`,
825
+ );
826
+ } else {
827
+ write(`Kept ${agent.kind} at release ${agent.release ?? "none"}.`);
828
+ }
829
+ }
830
+ }
831
+
832
+ async function runAgentsSignIn(args, directory) {
833
+ const { values, rest } = declared(args, ["--house", "--config"], 1, { exact: true });
834
+ const house = await selectedHouse(values, directory);
835
+ const [kind] = rest;
836
+ if (!house || !kind) throw new Error(AGENTS_USAGE);
837
+ const { status, reported } = await signInAgent({
838
+ house,
839
+ directory,
840
+ kind,
841
+ configuration: values["--config"] ?? join(directory, "kit.json"),
842
+ });
843
+ const signedIn = reported.find((agent) => agent.kind === kind)?.signed_in === true;
844
+ write(
845
+ signedIn
846
+ ? `Signed ${kind} in and reported it to House.`
847
+ : `The ${kind} sign-in command exited ${status} and House holds that CLI as not signed in.`,
848
+ );
849
+ if (!signedIn) process.exitCode = 1;
850
+ }
851
+
852
+ async function runWorkspaceHost(args, directory) {
853
+ const { values } = declared(args, ["--house", "--config", "--passes"], 0, { exact: true });
854
+ const house = await selectedHouse(values, directory);
855
+ if (!house) throw new Error(WORKSPACE_USAGE);
856
+ const passes = values["--passes"] === undefined ? undefined : Number(values["--passes"]);
857
+ if (passes !== undefined && (!Number.isSafeInteger(passes) || passes < 1)) {
858
+ throw new Error(WORKSPACE_USAGE);
859
+ }
860
+ const served = await serveWorkspaces({
861
+ house,
862
+ directory,
863
+ configuration: values["--config"] ?? join(directory, "kit.json"),
864
+ passes,
865
+ });
866
+ for (const name of served.reconciled) {
867
+ write(`Stopped ${name}; House authorizes no running workspace for it.`);
868
+ }
869
+ for (const settlement of served.settled) {
870
+ write(
871
+ `Settled ${settlement.kind} for ${settlement.workspace ?? "an uncreated workspace"} as ${settlement.outcome}${settlement.refusal === undefined ? "" : `: ${settlement.refusal}`}.`,
872
+ );
873
+ }
874
+ for (const operation of served.withdrawn) {
875
+ write(`House withdrew the ${operation.kind} it had handed this host; its outcome was dropped.`);
876
+ }
877
+ }
878
+
879
+ async function runTelegramConnect(args, directory) {
880
+ const { values } = declared(args, ["--house"], 0, { exact: true });
881
+ const house = await selectedHouse(values, directory);
882
+ if (!house) throw new Error(TELEGRAM_USAGE);
883
+ const cancellation = new AbortController();
884
+ const cancel = () => cancellation.abort();
885
+ process.once("SIGINT", cancel);
886
+ process.once("SIGTERM", cancel);
887
+ let connected;
888
+ try {
889
+ connected = await connectPersonalBot({ house, directory, signal: cancellation.signal });
890
+ } finally {
891
+ process.removeListener("SIGINT", cancel);
892
+ process.removeListener("SIGTERM", cancel);
893
+ }
894
+ write(
895
+ `Connected @${connected.bot_username} (bot ${connected.telegram_bot_user_id}) for owner ${connected.owner_telegram_user_id}; this Environment is now the gateway.`,
896
+ );
897
+ }
898
+
899
+ async function runTelegramDisconnect(args, directory) {
900
+ const { values } = declared(args, ["--house"], 0, { exact: true });
901
+ const house = await selectedHouse(values, directory);
902
+ if (!house) throw new Error(TELEGRAM_USAGE);
903
+ const cancellation = new AbortController();
904
+ const cancel = () => cancellation.abort();
905
+ process.once("SIGINT", cancel);
906
+ process.once("SIGTERM", cancel);
907
+ let disconnected;
908
+ try {
909
+ disconnected = await disconnectPersonalBot({ house, directory, signal: cancellation.signal });
910
+ } finally {
911
+ process.removeListener("SIGINT", cancel);
912
+ process.removeListener("SIGTERM", cancel);
913
+ }
914
+ write(
915
+ disconnected.revoked
916
+ ? "Deleted the webhook, removed the local bot token and offset, and told House this Environment is no longer the gateway."
917
+ : "Completed an interrupted disconnect; the local bot token was already gone and House was told.",
918
+ );
919
+ }
920
+
921
+ async function runTelegramServe(args, directory) {
922
+ const { values } = declared(args, ["--house", "--passes"], 0, { exact: true });
923
+ const house = await selectedHouse(values, directory);
924
+ if (!house) throw new Error(TELEGRAM_USAGE);
925
+ const passes = values["--passes"] === undefined ? undefined : Number(values["--passes"]);
926
+ if (passes !== undefined && (!Number.isSafeInteger(passes) || passes < 1)) {
927
+ throw new Error(TELEGRAM_USAGE);
928
+ }
929
+ const cancellation = new AbortController();
930
+ const cancel = () => cancellation.abort();
931
+ process.once("SIGINT", cancel);
932
+ process.once("SIGTERM", cancel);
933
+ let served;
934
+ try {
935
+ served = await serveTelegramGateway({ house, directory, passes, signal: cancellation.signal });
936
+ } finally {
937
+ process.removeListener("SIGINT", cancel);
938
+ process.removeListener("SIGTERM", cancel);
939
+ }
940
+ write(`Recovered ${served.abandoned} abandoned deliveries before serving.`);
941
+ for (const update of served.submitted) {
942
+ write(`Submitted ${update.kind} update ${update.update}: ${update.outcome}.`);
943
+ }
944
+ for (const file of served.relayed) {
945
+ write(`Relayed ${file.bytes} inbound bytes for attachment ${file.attachment}.`);
946
+ }
947
+ for (const settled of served.deliveries) {
948
+ write(`Delivery ${settled.delivery} settled ${settled.outcome}.`);
949
+ }
950
+ for (const settled of served.uploads) {
951
+ write(
952
+ `File send ${settled.upload} settled ${settled.outcome}${settled.reason === null ? "" : ` (${settled.reason})`}.`,
953
+ );
954
+ }
955
+ for (const failure of served.failures) {
956
+ write(
957
+ `The ${failure.activity} pass${failure.item === null ? "" : ` for ${failure.item}`} could not finish (${failure.code}).`,
958
+ );
959
+ }
960
+ if (served.failures.length > 0) process.exitCode = 1;
961
+ }
962
+
963
+ async function main() {
964
+ const [command, ...args] = process.argv.slice(2);
965
+ const directory = process.env.HOUSE_KIT_HOME ?? join(homedir(), ".house-kit");
966
+ if (command === "login") return runLogin(args, directory);
967
+ if (command === "credential" && args[0] === "check") return runCredentialCheck(args.slice(1), directory);
968
+ if (command === "resident") return runResidentCommand(args, directory);
969
+ if (command === "setup") return runSetup(args, directory);
970
+ if (command === "apply") return runApply(args, directory);
971
+ if (command === "worker") {
972
+ const [action, ...rest] = args;
973
+ if (action === "run") return runWorkerRun(rest, directory);
974
+ if (action === "attach") return runWorkerAttach(rest, directory);
975
+ throw new Error(WORKER_USAGE);
976
+ }
977
+ if (command === "source") {
978
+ const [action, ...rest] = args;
979
+ if (action === "capture") return runSourceCapture(rest, directory);
980
+ if (action === "status") return runSourceStatus(rest, directory);
981
+ if (action === "sign-in") return runSourceSignIn(rest, directory);
982
+ if (action === "reset") return runSourceReset(rest, directory);
983
+ if (action === "retry-files") return runSourceRetryFiles(rest, directory);
984
+ throw new Error(SOURCE_USAGE);
985
+ }
986
+ if (command === "repository") {
987
+ const [action, ...rest] = args;
988
+ if (action === "capture") return runRepositoryCapture(rest, directory);
989
+ throw new Error(REPOSITORY_USAGE);
990
+ }
991
+ if (command === "host") {
992
+ const [action, ...rest] = args;
993
+ if (action === "report") return runHostReport(rest, directory);
994
+ throw new Error(HOST_USAGE);
995
+ }
996
+ if (command === "agents") {
997
+ const [action, ...rest] = args;
998
+ if (action === "update") return runAgentsUpdate(rest, directory);
999
+ if (action === "sign-in") return runAgentsSignIn(rest, directory);
1000
+ throw new Error(AGENTS_USAGE);
1001
+ }
1002
+ if (command === "workspace") {
1003
+ const [action, ...rest] = args;
1004
+ if (action === "host") return runWorkspaceHost(rest, directory);
1005
+ throw new Error(WORKSPACE_USAGE);
1006
+ }
1007
+ if (command === "telegram") {
1008
+ const [action, ...rest] = args;
1009
+ if (action === "connect") return runTelegramConnect(rest, directory);
1010
+ if (action === "disconnect") return runTelegramDisconnect(rest, directory);
1011
+ if (action === "serve") return runTelegramServe(rest, directory);
1012
+ throw new Error(TELEGRAM_USAGE);
1013
+ }
1014
+ if (command === "sync") return runSync(args, directory);
1015
+ if (command === "migrate") return runMigrate(args, directory);
1016
+ if (command === "attachment") return runAttachment(args, directory);
1017
+ throw new Error(USAGE);
1018
+ }
1019
+
1020
+ main().catch((error) => {
1021
+ process.stderr.write(`${formatCommandError(error)}\n`);
1022
+ process.exitCode = 1;
1023
+ });