@agentshouse/kit 0.2.1-c3cf4b145dcda → 0.2.1-c46722ad48058
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 +20 -19
- package/dist/acp.js +7 -97
- package/dist/agents.js +2 -1
- package/dist/api.js +3 -3
- package/dist/bootstrap.json +4 -4
- package/dist/bridge.js +11 -37
- package/dist/clis.js +52 -13
- package/dist/codex-main.js +58 -0
- package/dist/conversations.js +47 -50
- package/dist/copies.js +127 -102
- package/dist/end.js +1 -1
- package/dist/enrol-main.js +7 -1
- package/dist/events.js +10 -76
- package/dist/home.js +6 -2
- package/dist/house-main.js +40 -13
- package/dist/instructions.js +6 -4
- package/dist/login.js +75 -106
- package/dist/logout.js +14 -14
- package/dist/relay.js +12 -2
- package/dist/resident.js +21 -19
- package/dist/scope.js +203 -0
- package/dist/sign-in.js +1 -1
- package/dist/work.js +348 -0
- package/package.json +2 -2
package/dist/events.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const COMMAND = { shown: 'command' };
|
|
2
2
|
const HIDDEN = { shown: 'hidden' };
|
|
3
3
|
const FINISHED = new Set(['completed', 'failed']);
|
|
4
|
-
const RUNNING_JOB = new Set(['running', 'paused']);
|
|
4
|
+
export const RUNNING_JOB = new Set(['running', 'paused']);
|
|
5
5
|
const ROUTINE_TARGETS = { send_routine_request: 'access', cancel_routine_request: 'request' };
|
|
6
6
|
function words(line) {
|
|
7
7
|
const found = [];
|
|
@@ -113,9 +113,6 @@ class Reader {
|
|
|
113
113
|
refused(option) {
|
|
114
114
|
this.requestedOptions.delete(option);
|
|
115
115
|
}
|
|
116
|
-
turnStarted() {
|
|
117
|
-
return [];
|
|
118
|
-
}
|
|
119
116
|
closeTurn() {
|
|
120
117
|
const events = [];
|
|
121
118
|
for (const [id, call] of this.calls) {
|
|
@@ -187,10 +184,7 @@ class Reader {
|
|
|
187
184
|
const label = this.jobs.get(id) ?? '';
|
|
188
185
|
if (state !== 'running')
|
|
189
186
|
this.jobs.delete(id);
|
|
190
|
-
return [
|
|
191
|
-
{ kind: 'busy', key: `job:${id}`, running: state === 'running' },
|
|
192
|
-
{ kind: 'marker', marker: 'job', text: `${label}: ${state}` },
|
|
193
|
-
];
|
|
187
|
+
return [{ kind: 'marker', marker: 'job', text: `${label}: ${state}` }];
|
|
194
188
|
}
|
|
195
189
|
optionsChanged(options) {
|
|
196
190
|
const events = [];
|
|
@@ -275,7 +269,6 @@ class Reader {
|
|
|
275
269
|
class ClaudeReader extends Reader {
|
|
276
270
|
watchedOptions = { model: 'model' };
|
|
277
271
|
tasks = new Map();
|
|
278
|
-
scheduled = new Map();
|
|
279
272
|
own(message) {
|
|
280
273
|
if (message.params?.update?._meta?.claudeCode?.parentToolUseId !== undefined)
|
|
281
274
|
return [];
|
|
@@ -295,10 +288,6 @@ class ClaudeReader extends Reader {
|
|
|
295
288
|
return [];
|
|
296
289
|
const sdk = message.params.message;
|
|
297
290
|
switch (sdk.subtype) {
|
|
298
|
-
case 'background_tasks_changed':
|
|
299
|
-
return [{ kind: 'busy', key: 'tasks', running: sdk.tasks.length > 0 }];
|
|
300
|
-
case 'session_state_changed':
|
|
301
|
-
return [{ kind: 'busy', key: 'state', running: sdk.state === 'running' }];
|
|
302
291
|
case 'task_started':
|
|
303
292
|
if (sdk.tool_use_id !== undefined)
|
|
304
293
|
this.tasks.set(sdk.task_id, sdk.tool_use_id);
|
|
@@ -309,16 +298,6 @@ class ClaudeReader extends Reader {
|
|
|
309
298
|
return [];
|
|
310
299
|
}
|
|
311
300
|
}
|
|
312
|
-
turnStarted() {
|
|
313
|
-
const events = [];
|
|
314
|
-
for (const [id, entry] of this.scheduled) {
|
|
315
|
-
if (entry.recurring)
|
|
316
|
-
continue;
|
|
317
|
-
this.scheduled.delete(id);
|
|
318
|
-
events.push({ kind: 'busy', key: `scheduled:${id}`, running: false });
|
|
319
|
-
}
|
|
320
|
-
return events;
|
|
321
|
-
}
|
|
322
301
|
decide(call, final) {
|
|
323
302
|
const tool = call.meta.claudeCode?.toolName;
|
|
324
303
|
if (tool === 'Agent' || tool === 'Task' || call.meta.jetbrains?.air?.subagent === true) {
|
|
@@ -350,22 +329,7 @@ class ClaudeReader extends Reader {
|
|
|
350
329
|
if (call.status !== 'completed')
|
|
351
330
|
return [];
|
|
352
331
|
if (tool === 'CronCreate' || (tool === 'ScheduleWakeup' && call.rawInput?.stop !== true)) {
|
|
353
|
-
|
|
354
|
-
recurring: tool === 'CronCreate' && call.rawInput?.recurring !== false,
|
|
355
|
-
text: contentText(call.content) ?? '',
|
|
356
|
-
});
|
|
357
|
-
return [
|
|
358
|
-
{ kind: 'busy', key: `scheduled:${call.id}`, running: true },
|
|
359
|
-
{ kind: 'marker', marker: 'job', text: `${call.rawInput?.prompt ?? call.title ?? ''}: scheduled` },
|
|
360
|
-
];
|
|
361
|
-
}
|
|
362
|
-
if (tool === 'CronDelete') {
|
|
363
|
-
for (const [id, entry] of this.scheduled) {
|
|
364
|
-
if (!entry.text.includes(String(call.rawInput?.id)))
|
|
365
|
-
continue;
|
|
366
|
-
this.scheduled.delete(id);
|
|
367
|
-
return [{ kind: 'busy', key: `scheduled:${id}`, running: false }];
|
|
368
|
-
}
|
|
332
|
+
return [{ kind: 'marker', marker: 'job', text: `${call.rawInput?.prompt ?? call.title ?? ''}: scheduled` }];
|
|
369
333
|
}
|
|
370
334
|
return [];
|
|
371
335
|
}
|
|
@@ -402,15 +366,9 @@ class CodexReader extends Reader {
|
|
|
402
366
|
case 'subagent_spawned':
|
|
403
367
|
this.children.set(update.subagentSessionId, { id: null, text: '' });
|
|
404
368
|
this.subagents.add(update.subagentSessionId);
|
|
405
|
-
return [
|
|
406
|
-
{ kind: 'busy', key: `subagent:${update.subagentSessionId}`, running: true },
|
|
407
|
-
{ kind: 'subagent', id: update.subagentSessionId, description: update.name },
|
|
408
|
-
];
|
|
369
|
+
return [{ kind: 'subagent', id: update.subagentSessionId, description: update.name }];
|
|
409
370
|
case 'subagent_state_update':
|
|
410
|
-
return
|
|
411
|
-
{ kind: 'busy', key: `subagent:${update.subagentSessionId}`, running: false },
|
|
412
|
-
...this.finish(update.subagentSessionId, this.children.get(update.subagentSessionId)?.text || null),
|
|
413
|
-
];
|
|
371
|
+
return this.finish(update.subagentSessionId, this.children.get(update.subagentSessionId)?.text || null);
|
|
414
372
|
default:
|
|
415
373
|
return super.update(update);
|
|
416
374
|
}
|
|
@@ -427,10 +385,6 @@ class CodexReader extends Reader {
|
|
|
427
385
|
child.id = id;
|
|
428
386
|
child.text += update.content.text;
|
|
429
387
|
}
|
|
430
|
-
const status = update._meta?.codex?.threadStatus?.type;
|
|
431
|
-
if (update.sessionUpdate === 'session_info_update' && status !== undefined) {
|
|
432
|
-
return [{ kind: 'busy', key: `thread:${session}`, running: status === 'active' }];
|
|
433
|
-
}
|
|
434
388
|
return [];
|
|
435
389
|
}
|
|
436
390
|
decide(call) {
|
|
@@ -460,7 +414,6 @@ class CodexReader extends Reader {
|
|
|
460
414
|
class GrokReader extends Reader {
|
|
461
415
|
watchedOptions = {};
|
|
462
416
|
children = new Map();
|
|
463
|
-
queues = new Map();
|
|
464
417
|
models = [];
|
|
465
418
|
model = null;
|
|
466
419
|
answered(result) {
|
|
@@ -473,15 +426,10 @@ class GrokReader extends Reader {
|
|
|
473
426
|
if (this.session !== null && session !== undefined && session !== this.session && !this.children.has(session))
|
|
474
427
|
return [];
|
|
475
428
|
if (message.method === '_x.ai/queue/changed') {
|
|
476
|
-
|
|
477
|
-
this.queues.set(session, { running, entries: message.params.entries.length });
|
|
478
|
-
return [this.queued(session), ...(running !== null && session === this.session ? [{ kind: 'started' }] : [])];
|
|
429
|
+
return (message.params.runningPromptId ?? null) !== null && session === this.session ? [{ kind: 'started' }] : [];
|
|
479
430
|
}
|
|
480
431
|
if (message.params?.update?.sessionUpdate === 'turn_completed') {
|
|
481
|
-
|
|
482
|
-
if (queue !== undefined && queue.running === message.params.update.prompt_id)
|
|
483
|
-
queue.running = null;
|
|
484
|
-
return [this.queued(session), ...(session === this.session ? [{ kind: 'ended' }] : [])];
|
|
432
|
+
return session === this.session ? [{ kind: 'ended' }] : [];
|
|
485
433
|
}
|
|
486
434
|
if (message.method === '_x.ai/models/update') {
|
|
487
435
|
this.offered(message.params);
|
|
@@ -489,10 +437,6 @@ class GrokReader extends Reader {
|
|
|
489
437
|
}
|
|
490
438
|
return super.read(message);
|
|
491
439
|
}
|
|
492
|
-
queued(session) {
|
|
493
|
-
const queue = this.queues.get(session);
|
|
494
|
-
return { kind: 'busy', key: `queue:${session}`, running: queue !== undefined && (queue.running !== null || queue.entries > 0) };
|
|
495
|
-
}
|
|
496
440
|
offered(models) {
|
|
497
441
|
this.models = models.availableModels ?? this.models;
|
|
498
442
|
this.model = models.currentModelId ?? this.model;
|
|
@@ -508,16 +452,9 @@ class GrokReader extends Reader {
|
|
|
508
452
|
case '_x.ai/task_completed':
|
|
509
453
|
return this.job(update.task_snapshot.task_id, null, update.task_snapshot.exit_code === 0 ? 'completed' : 'failed');
|
|
510
454
|
case '_x.ai/scheduled_task_created':
|
|
511
|
-
return [
|
|
512
|
-
{ kind: 'busy', key: `scheduled:${update.task_id}`, running: true },
|
|
513
|
-
{ kind: 'marker', marker: 'job', text: `${update.prompt}: next run ${update.next_fire_at}` },
|
|
514
|
-
];
|
|
515
|
-
case '_x.ai/scheduled_task_deleted':
|
|
516
|
-
return [{ kind: 'busy', key: `scheduled:${update.task_id}`, running: false }];
|
|
455
|
+
return [{ kind: 'marker', marker: 'job', text: `${update.prompt}: next run ${update.next_fire_at}` }];
|
|
517
456
|
case '_x.ai/session_notification':
|
|
518
457
|
return this.notification(update);
|
|
519
|
-
case '_x.ai/mcp_initialized':
|
|
520
|
-
return [{ kind: 'servers' }];
|
|
521
458
|
default:
|
|
522
459
|
return [];
|
|
523
460
|
}
|
|
@@ -538,14 +475,11 @@ class GrokReader extends Reader {
|
|
|
538
475
|
candidate.decided.description === update.description) ??
|
|
539
476
|
[...this.calls.values()].find((candidate) => candidate.decided?.shown === 'subagent' && ![...this.children.values()].includes(candidate.id));
|
|
540
477
|
this.children.set(update.child_session_id, call?.id ?? null);
|
|
541
|
-
return [
|
|
478
|
+
return [];
|
|
542
479
|
}
|
|
543
480
|
case 'subagent_finished': {
|
|
544
481
|
const call = this.children.get(update.child_session_id);
|
|
545
|
-
return [
|
|
546
|
-
{ kind: 'busy', key: `subagent:${update.child_session_id}`, running: false },
|
|
547
|
-
...(typeof call === 'string' ? this.finish(call, update.output ?? null) : []),
|
|
548
|
-
];
|
|
482
|
+
return typeof call === 'string' ? this.finish(call, update.output ?? null) : [];
|
|
549
483
|
}
|
|
550
484
|
case 'auto_compact_completed':
|
|
551
485
|
return [{ kind: 'marker', marker: 'compaction', text: '' }];
|
package/dist/home.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { selectRelay } from './relay.js';
|
|
5
6
|
const ENROLMENT = 'credential.json';
|
|
6
7
|
const DISCONNECTED = 'disconnected.json';
|
|
7
8
|
const CONFIGURATION = 'kit.json';
|
|
@@ -32,8 +33,11 @@ export async function writeHome(name, value) {
|
|
|
32
33
|
await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
33
34
|
await rename(temporary, join(home, name));
|
|
34
35
|
}
|
|
35
|
-
export function readEnrolment() {
|
|
36
|
-
|
|
36
|
+
export async function readEnrolment() {
|
|
37
|
+
const enrolment = await readHome(ENROLMENT);
|
|
38
|
+
if (enrolment !== null)
|
|
39
|
+
selectRelay(process.env.HOUSE_KIT_RELAY || enrolment.relay);
|
|
40
|
+
return enrolment;
|
|
37
41
|
}
|
|
38
42
|
export async function writeEnrolment(enrolment) {
|
|
39
43
|
await writeHome(ENROLMENT, enrolment);
|
package/dist/house-main.js
CHANGED
|
@@ -3,15 +3,20 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { request, setGlobalProxyFromEnv } from 'node:http';
|
|
5
5
|
import { basename, join } from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { HOUSE_REF } from './git.js';
|
|
7
|
+
import { kitHome, readEnrolment, readOwnAgent } from './home.js';
|
|
7
8
|
import { callHouse, rpc, UNREACHABLE } from './mcp.js';
|
|
8
9
|
import { refusalOf } from './refusals.js';
|
|
10
|
+
import { relayed } from './relay.js';
|
|
11
|
+
import { parse } from 'yaml';
|
|
9
12
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
10
13
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
11
14
|
const PUSH = "push [commit]: sends the Local copy's committed changes to House";
|
|
15
|
+
const FETCH_USAGE = 'usage: house fetch';
|
|
16
|
+
const FETCH = `fetch: records House's changes on each Local copy's ${HOUSE_REF}`;
|
|
12
17
|
const ARGUMENTS = `the arguments are one JSON object in single quotes, like house search '{"query":"invoice"}'`;
|
|
13
18
|
const CLOSED = "this conversation's House connection is closed; nothing to do from here";
|
|
14
|
-
const SANDBOXED = "your CLI's sandbox kept house from House; run house
|
|
19
|
+
const SANDBOXED = "your CLI's sandbox kept house from House; run house directly, not through another program";
|
|
15
20
|
const REVOKED = 'this conversation no longer has House access; nothing to do from here';
|
|
16
21
|
const UNCONNECTED = "this computer's own agent has no House connection; run kit login";
|
|
17
22
|
const DISCONNECTED = "House no longer accepts this computer's own agent; run kit login";
|
|
@@ -51,6 +56,7 @@ async function direct(message) {
|
|
|
51
56
|
const ownAgent = await readOwnAgent();
|
|
52
57
|
if (ownAgent === null)
|
|
53
58
|
throw new Error(UNCONNECTED);
|
|
59
|
+
await readEnrolment();
|
|
54
60
|
setGlobalProxyFromEnv();
|
|
55
61
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
56
62
|
throw new Error(UNREACHABLE);
|
|
@@ -79,11 +85,24 @@ async function localFile(path) {
|
|
|
79
85
|
: `${path} is not a readable file`);
|
|
80
86
|
}
|
|
81
87
|
}
|
|
88
|
+
async function sent(url, bytes, operation) {
|
|
89
|
+
const headers = { 'content-type': 'application/octet-stream', 'x-house-byte-operation': operation };
|
|
90
|
+
if (socketPath !== undefined)
|
|
91
|
+
return bridged(url, bytes, headers);
|
|
92
|
+
setGlobalProxyFromEnv();
|
|
93
|
+
const answer = await fetch(relayed(url), { method: 'POST', headers, body: bytes }).catch(() => {
|
|
94
|
+
throw new Error(UNREACHABLE);
|
|
95
|
+
});
|
|
96
|
+
const text = await answer.text();
|
|
97
|
+
if (answer.status === 200)
|
|
98
|
+
return text;
|
|
99
|
+
throw new Error(refused(url, answer.status, text));
|
|
100
|
+
}
|
|
82
101
|
async function upload(path, roomRef, missing) {
|
|
83
102
|
if (typeof path !== 'string' || path === '')
|
|
84
103
|
throw new Error(missing);
|
|
85
104
|
const bytes = await localFile(path);
|
|
86
|
-
const declared =
|
|
105
|
+
const declared = parse(await called('upload', {
|
|
87
106
|
...(roomRef === undefined ? {} : { room_ref: roomRef }),
|
|
88
107
|
name: basename(path),
|
|
89
108
|
bytes: bytes.length,
|
|
@@ -91,10 +110,7 @@ async function upload(path, roomRef, missing) {
|
|
|
91
110
|
}));
|
|
92
111
|
if (declared.upload === undefined)
|
|
93
112
|
return declared.attachment;
|
|
94
|
-
const saved = JSON.parse(await
|
|
95
|
-
'content-type': 'application/octet-stream',
|
|
96
|
-
'x-house-byte-operation': declared.upload.operation,
|
|
97
|
-
}));
|
|
113
|
+
const saved = JSON.parse(await sent(declared.upload.url, bytes, declared.upload.operation));
|
|
98
114
|
if (saved.upload_failure !== undefined)
|
|
99
115
|
throw new Error(`${path} was not saved: ${saved.upload_failure}; call again later`);
|
|
100
116
|
return saved.attachment;
|
|
@@ -113,6 +129,14 @@ async function push(args) {
|
|
|
113
129
|
throw new Error(pushed.text);
|
|
114
130
|
return pushed.text;
|
|
115
131
|
}
|
|
132
|
+
async function fetched(args) {
|
|
133
|
+
if (args.includes('--help'))
|
|
134
|
+
return FETCH_USAGE;
|
|
135
|
+
const answer = JSON.parse(await bridged('/git/fetch', {}, undefined, socketPath ?? join(kitHome(), 'owner.sock')));
|
|
136
|
+
if (answer.refused)
|
|
137
|
+
throw new Error(answer.text);
|
|
138
|
+
return answer.text;
|
|
139
|
+
}
|
|
116
140
|
function argumentsOf(argument) {
|
|
117
141
|
if (argument === undefined)
|
|
118
142
|
return {};
|
|
@@ -129,7 +153,7 @@ function argumentsOf(argument) {
|
|
|
129
153
|
}
|
|
130
154
|
async function house([verb, argument]) {
|
|
131
155
|
if (verb === undefined || verb === '--help' || verb === 'help') {
|
|
132
|
-
return [USAGE, 'help', PUSH, ...(await tools()).map((tool) => (tool.description === undefined ? tool.name : `${tool.name}: ${tool.description}`))].join('\n');
|
|
156
|
+
return [USAGE, 'help', PUSH, FETCH, ...(await tools()).map((tool) => (tool.description === undefined ? tool.name : `${tool.name}: ${tool.description}`))].join('\n');
|
|
133
157
|
}
|
|
134
158
|
if (argument === '--help') {
|
|
135
159
|
const tool = (await tools()).find((listed) => listed.name === verb);
|
|
@@ -138,24 +162,27 @@ async function house([verb, argument]) {
|
|
|
138
162
|
return [...(tool.description === undefined ? [] : [tool.description]), JSON.stringify(tool.inputSchema)].join('\n');
|
|
139
163
|
}
|
|
140
164
|
const args = argumentsOf(argument);
|
|
141
|
-
if (
|
|
142
|
-
return JSON.stringify({ attachment: await upload(args.path, args.room_ref, '
|
|
165
|
+
if (verb === 'upload') {
|
|
166
|
+
return JSON.stringify({ attachment: await upload(args.path, args.room_ref, 'upload needs "path", a local file') });
|
|
143
167
|
}
|
|
144
|
-
if (
|
|
168
|
+
if (verb === 'append_record' && Array.isArray(args.attachments)) {
|
|
145
169
|
const attachments = [];
|
|
146
170
|
for (const path of args.attachments) {
|
|
147
171
|
attachments.push(await upload(path, args.room_ref, 'each attachments entry is the path of a local file'));
|
|
148
172
|
}
|
|
149
173
|
args.attachments = attachments;
|
|
150
174
|
}
|
|
151
|
-
|
|
175
|
+
return called(verb, args);
|
|
176
|
+
}
|
|
177
|
+
async function called(name, args) {
|
|
178
|
+
const result = await mcp('tools/call', { name, arguments: args });
|
|
152
179
|
const text = result.content.map((content) => content.text).join('\n');
|
|
153
180
|
if (result.isError === true)
|
|
154
181
|
throw new Error(text.replace(/\n+$/, ''));
|
|
155
182
|
return text;
|
|
156
183
|
}
|
|
157
184
|
const argv = process.argv.slice(2);
|
|
158
|
-
(argv[0] === 'push' ? push(argv.slice(1)) : house(argv)).then((text) => {
|
|
185
|
+
(argv[0] === 'push' ? push(argv.slice(1)) : argv[0] === 'fetch' ? fetched(argv.slice(1)) : house(argv)).then((text) => {
|
|
159
186
|
process.stdout.write(`${text}\n`);
|
|
160
187
|
if (argv[0] === 'shell')
|
|
161
188
|
process.exitCode = Number(/^exit: (\d+)(?:\n|$)/.exec(text)?.[1] ?? 0);
|
package/dist/instructions.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { HOUSE_REF } from './git.js';
|
|
1
2
|
import { HOW_WE_WORK } from './skills.js';
|
|
3
|
+
const SOURCE_LINE = "From House, not the User's message:";
|
|
2
4
|
const HOUSE_LINE = 'Work with House through the `house` CLI: run `house --help`.';
|
|
3
|
-
const FILE_LINE = 'To give the User a file or a page, upload it with `house
|
|
4
|
-
const READ_LINE = '
|
|
5
|
-
const COPIES_LINE =
|
|
5
|
+
const FILE_LINE = 'To give the User a file or a page, upload it with `house upload` and link it; never start a server.';
|
|
6
|
+
const READ_LINE = "Your User's instructions for a Room, if any: `/rooms/private/rooms/<handle>/AGENTS.md`.";
|
|
7
|
+
const COPIES_LINE = `House Local copies; before work run \`house fetch\` and rebase onto \`${HOUSE_REF}\`; after it commit, then \`house push\`:`;
|
|
6
8
|
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
7
9
|
const FAILED = /^exit: [1-9]\d*$/;
|
|
8
10
|
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
@@ -29,6 +31,6 @@ async function howWeWork(bridge) {
|
|
|
29
31
|
return document.replace(/\n$/, '');
|
|
30
32
|
}
|
|
31
33
|
export async function instructions(bridge, base, copies) {
|
|
32
|
-
const lines = [HOUSE_LINE, FILE_LINE, READ_LINE, ...(copies.length === 0 ? [] : [COPIES_LINE, ...copies])];
|
|
34
|
+
const lines = [SOURCE_LINE, HOUSE_LINE, FILE_LINE, READ_LINE, ...(copies.length === 0 ? [] : [COPIES_LINE, ...copies])];
|
|
33
35
|
return [lines.join('\n'), base, await howWeWork(bridge)].filter((part) => part !== '').join('\n\n');
|
|
34
36
|
}
|
package/dist/login.js
CHANGED
|
@@ -1,54 +1,20 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
1
2
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { createServer } from 'node:http';
|
|
3
3
|
import { hostname } from 'node:os';
|
|
4
|
-
import { createInterface } from 'node:readline/promises';
|
|
5
4
|
import { parseArgs } from 'node:util';
|
|
6
|
-
import { post } from './api.js';
|
|
5
|
+
import { post, retrying } from './api.js';
|
|
7
6
|
import { placeAppGit } from './app-git.js';
|
|
8
7
|
import { forgetOwnAgent, readDisconnected, readEnrolment, readOwnAgent, writeEnrolment, writeOwnAgent } from './home.js';
|
|
9
8
|
import { callHouse, rpc } from './mcp.js';
|
|
9
|
+
import { selectRelay } from './relay.js';
|
|
10
10
|
const DEFAULT_HOUSE = 'https://agents.house';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const server = createServer((request, response) => {
|
|
19
|
-
const received = new URL(request.url ?? '/', 'http://127.0.0.1').searchParams.get('code');
|
|
20
|
-
if (received === null || held !== null) {
|
|
21
|
-
response.writeHead(404).end();
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
held = response;
|
|
25
|
-
deliver(received);
|
|
26
|
-
});
|
|
27
|
-
const close = () => {
|
|
28
|
-
server.close();
|
|
29
|
-
server.closeAllConnections();
|
|
30
|
-
};
|
|
31
|
-
const port = Number(process.env.HOUSE_KIT_LOGIN_PORT ?? 0);
|
|
32
|
-
server.once('error', reject);
|
|
33
|
-
server.listen(port, port === 0 ? '127.0.0.1' : '0.0.0.0', () => {
|
|
34
|
-
const address = server.address();
|
|
35
|
-
if (address === null || typeof address === 'string') {
|
|
36
|
-
reject(new Error('the login callback has no port'));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
resolve({
|
|
40
|
-
uri: `http://127.0.0.1:${address.port}/callback`,
|
|
41
|
-
code,
|
|
42
|
-
answer: (text) => new Promise((written) => {
|
|
43
|
-
if (held === null || held.destroyed || !held.socket || held.socket.destroyed)
|
|
44
|
-
return written();
|
|
45
|
-
held.once('close', () => written());
|
|
46
|
-
held.writeHead(200, { 'content-type': 'text/plain; charset=utf-8', connection: 'close' }).end(`${text}\n`);
|
|
47
|
-
}),
|
|
48
|
-
close,
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
});
|
|
11
|
+
const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
12
|
+
// Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
|
|
13
|
+
const POLL_INTERVAL_MS = 2000;
|
|
14
|
+
function open(link) {
|
|
15
|
+
spawn(OPENER, [link], { detached: true, stdio: 'ignore' })
|
|
16
|
+
.on('error', () => undefined)
|
|
17
|
+
.unref();
|
|
52
18
|
}
|
|
53
19
|
async function accepted(house, credential) {
|
|
54
20
|
const checked = await callHouse(house, credential, rpc('tools/list', {}));
|
|
@@ -58,17 +24,19 @@ async function accepted(house, credential) {
|
|
|
58
24
|
return false;
|
|
59
25
|
throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
|
|
60
26
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
27
|
+
const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private] [--code <code>]
|
|
28
|
+
--house <origin> the House to connect to, ${DEFAULT_HOUSE} unless one is stored
|
|
29
|
+
--environment <id> reconnect this Environment
|
|
30
|
+
--rooms <ref>[,<ref>...] give this Environment only these Rooms
|
|
31
|
+
--no-private give this Environment every Room but your Private Room
|
|
32
|
+
--code <code> connect this machine with a login code
|
|
33
|
+
`;
|
|
34
|
+
function chosenRooms(rooms, noPrivate) {
|
|
35
|
+
if (rooms !== undefined && noPrivate)
|
|
36
|
+
throw new Error('--rooms and --no-private are two choices of Rooms; pass one');
|
|
37
|
+
if (rooms !== undefined)
|
|
38
|
+
return { rooms: { reach: 'selected', refs: rooms.split(',') } };
|
|
39
|
+
return noPrivate ? { rooms: { reach: 'all_but_private' } } : {};
|
|
72
40
|
}
|
|
73
41
|
export async function login(argv) {
|
|
74
42
|
const { values } = parseArgs({
|
|
@@ -76,65 +44,66 @@ export async function login(argv) {
|
|
|
76
44
|
options: {
|
|
77
45
|
house: { type: 'string' },
|
|
78
46
|
environment: { type: 'string' },
|
|
79
|
-
|
|
47
|
+
rooms: { type: 'string' },
|
|
48
|
+
'no-private': { type: 'boolean', default: false },
|
|
49
|
+
code: { type: 'string' },
|
|
50
|
+
help: { type: 'boolean', default: false },
|
|
80
51
|
},
|
|
81
52
|
});
|
|
53
|
+
if (values.help) {
|
|
54
|
+
process.stdout.write(USAGE);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (values.code !== undefined && values.rooms !== undefined)
|
|
58
|
+
throw new Error('--code cannot be used with --rooms');
|
|
59
|
+
if (values.code !== undefined && values['no-private'])
|
|
60
|
+
throw new Error('--code cannot be used with --no-private');
|
|
61
|
+
if (values.code !== undefined && values.environment !== undefined)
|
|
62
|
+
throw new Error('--code cannot be used with --environment');
|
|
63
|
+
const rooms = chosenRooms(values.rooms, values['no-private']);
|
|
82
64
|
const enrolled = (await readEnrolment()) ?? (await readDisconnected());
|
|
65
|
+
if (values.code !== undefined && enrolled !== null) {
|
|
66
|
+
throw new Error(`--code connects a new machine; this Kit home records Environment ${enrolled.environment}, so run kit login to reconnect it`);
|
|
67
|
+
}
|
|
68
|
+
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
69
|
+
selectRelay(relay);
|
|
83
70
|
const house = values.house ?? enrolled?.house ?? DEFAULT_HOUSE;
|
|
84
71
|
const replaces = process.env.HOUSE_KIT_REPLACES;
|
|
85
72
|
const environment = replaces === undefined ? (values.environment ?? (enrolled?.house === house ? enrolled.environment : undefined)) : undefined;
|
|
86
73
|
const ownAgent = await readOwnAgent();
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
? { act: 'new_environment', label: hostname(), ...(replaces === undefined ? {} : { replaces }) }
|
|
92
|
-
: { act: 'existing_environment', environment_id: environment }),
|
|
93
|
-
code_challenge: createHash('sha256').update(verifier).digest('base64url'),
|
|
94
|
-
...(held ? {} : { headless: true }),
|
|
95
|
-
};
|
|
96
|
-
const exchange = async (code) => {
|
|
97
|
-
const exchanged = await post(house, '/kit/token', { code, code_verifier: verifier });
|
|
98
|
-
await writeEnrolment({ house, environment: exchanged.environment, credential: exchanged.credential });
|
|
99
|
-
process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
|
|
100
|
-
if (exchanged.headless_credential !== undefined) {
|
|
101
|
-
await writeOwnAgent({ house, user: exchanged.user, credential: exchanged.headless_credential });
|
|
102
|
-
}
|
|
103
|
-
else if (held && ownAgent.user !== exchanged.user) {
|
|
104
|
-
await forgetOwnAgent();
|
|
105
|
-
process.stdout.write("This computer's own agent was another User's; run kit login again to connect it to you.\n");
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
const placeGit = async () => placeAppGit(ownAgent, await readOwnAgent());
|
|
109
|
-
if (values.manual) {
|
|
110
|
-
const { start_url } = await post(house, '/kit', {
|
|
111
|
-
...started,
|
|
112
|
-
presentation: 'manual',
|
|
113
|
-
});
|
|
114
|
-
process.stdout.write(`Open this link and confirm: ${start_url}\n`);
|
|
115
|
-
await exchange(await typed('Code: '));
|
|
116
|
-
await placeGit();
|
|
117
|
-
return;
|
|
74
|
+
let held = false;
|
|
75
|
+
let exchanged;
|
|
76
|
+
if (values.code !== undefined) {
|
|
77
|
+
exchanged = await post(house, '/kit/token', { code: values.code, label: hostname() });
|
|
118
78
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
79
|
+
else {
|
|
80
|
+
held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
|
|
81
|
+
const verifier = randomBytes(32).toString('base64url');
|
|
82
|
+
const started = await retrying(() => post(house, '/kit', {
|
|
83
|
+
...(environment === undefined
|
|
84
|
+
? { act: 'new_environment', label: hostname(), ...(replaces === undefined ? {} : { replaces }) }
|
|
85
|
+
: { act: 'existing_environment', environment_id: environment }),
|
|
86
|
+
...rooms,
|
|
87
|
+
code_challenge: createHash('sha256').update(verifier).digest('base64url'),
|
|
88
|
+
...(held ? {} : { headless: true }),
|
|
89
|
+
}));
|
|
90
|
+
process.stdout.write(`Open this link and confirm: ${started.start_url}\n`);
|
|
91
|
+
open(started.start_url);
|
|
92
|
+
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
93
|
+
}
|
|
94
|
+
await writeEnrolment({
|
|
95
|
+
house,
|
|
96
|
+
...(relay === undefined ? {} : { relay }),
|
|
97
|
+
environment: exchanged.environment,
|
|
98
|
+
credential: exchanged.credential,
|
|
99
|
+
});
|
|
100
|
+
process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
|
|
101
|
+
if (exchanged.headless_credential !== undefined) {
|
|
102
|
+
await writeOwnAgent({ house, user: exchanged.user, credential: exchanged.headless_credential });
|
|
135
103
|
}
|
|
136
|
-
|
|
137
|
-
|
|
104
|
+
else if (held && ownAgent !== null && ownAgent.user !== exchanged.user) {
|
|
105
|
+
await forgetOwnAgent();
|
|
106
|
+
process.stdout.write("This computer's own agent was another User's; run kit login again to connect it to you.\n");
|
|
138
107
|
}
|
|
139
|
-
await
|
|
108
|
+
await placeAppGit(ownAgent, await readOwnAgent());
|
|
140
109
|
}
|
package/dist/logout.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { HouseRefusal, post } from './api.js';
|
|
2
2
|
import { forgetAppGit } from './app-git.js';
|
|
3
|
-
import { forgetEnrolment, readEnrolment } from './home.js';
|
|
3
|
+
import { forgetEnrolment, forgetOwnAgent, readEnrolment } from './home.js';
|
|
4
4
|
export async function logout() {
|
|
5
5
|
const enrolled = await readEnrolment();
|
|
6
|
-
if (enrolled
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
if (enrolled !== null) {
|
|
7
|
+
try {
|
|
8
|
+
await post(enrolled.house, '/kit/logout', {}, enrolled.credential);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (!(error instanceof HouseRefusal && error.status === 401))
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
await forgetEnrolment(enrolled);
|
|
10
15
|
}
|
|
11
|
-
try {
|
|
12
|
-
await post(enrolled.house, '/kit/logout', {}, enrolled.credential);
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
if (!(error instanceof HouseRefusal && error.status === 401))
|
|
16
|
-
throw error;
|
|
17
|
-
}
|
|
18
|
-
await forgetEnrolment(enrolled);
|
|
19
16
|
await forgetAppGit();
|
|
20
|
-
|
|
17
|
+
await forgetOwnAgent();
|
|
18
|
+
process.stdout.write(enrolled === null
|
|
19
|
+
? 'This computer is not connected to House.\n'
|
|
20
|
+
: `Environment ${enrolled.environment} is disconnected; kit login reconnects it.\n`);
|
|
21
21
|
}
|
package/dist/relay.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
let selected;
|
|
2
|
+
export function selectRelay(relay) {
|
|
3
|
+
selected = relay === undefined || relay === '' ? null : relay;
|
|
4
|
+
}
|
|
5
|
+
function relayOrigin() {
|
|
6
|
+
if (selected !== undefined)
|
|
7
|
+
return selected ?? undefined;
|
|
8
|
+
const ambient = process.env.HOUSE_KIT_RELAY;
|
|
9
|
+
return ambient === undefined || ambient === '' ? undefined : ambient;
|
|
10
|
+
}
|
|
1
11
|
export function relayed(target) {
|
|
2
12
|
const url = new URL(target);
|
|
3
|
-
const relay =
|
|
4
|
-
if (relay === undefined
|
|
13
|
+
const relay = relayOrigin();
|
|
14
|
+
if (relay === undefined)
|
|
5
15
|
return url;
|
|
6
16
|
const through = new URL(`${url.host}${url.pathname}${url.search}`, `${relay}/`);
|
|
7
17
|
if (url.protocol === 'wss:' || url.protocol === 'ws:')
|