@agentshouse/kit 0.2.1-c845e172df1af → 0.2.1-c849d8d4ab62f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/acp.js +57 -6
- package/dist/agents.js +6 -9
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +46 -0
- package/dist/calls.js +11 -3
- package/dist/clis-main.js +2 -2
- package/dist/clis.js +39 -15
- package/dist/connection.js +20 -29
- package/dist/conversations.js +235 -81
- package/dist/end.js +8 -2
- package/dist/events.js +29 -8
- package/dist/files.js +53 -40
- package/dist/git.js +2 -2
- package/dist/house-cli.js +31 -59
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +29 -9
- package/dist/kit-cli.js +2 -2
- package/dist/kit-main.js +3 -3
- package/dist/login.js +4 -4
- package/dist/mcp.js +2 -0
- package/dist/relay.js +21 -0
- package/dist/resident.js +17 -6
- package/dist/scripts.js +255 -0
- package/dist/sign-in.js +3 -2
- package/dist/silence.js +28 -0
- package/dist/skills.js +41 -96
- package/dist/transcription.js +83 -43
- package/dist/update.js +51 -8
- package/package.json +19 -20
- package/dist/bootstrap.json +0 -24
- package/how-we-work.md +0 -33
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -97
- package/skills/ask-matt/agents/openai.yaml +0 -5
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -114
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -138
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -74
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -7
- package/skills/grill-me/agents/openai.yaml +0 -5
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -7
- package/skills/grill-with-docs/agents/openai.yaml +0 -5
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -16
- package/skills/handoff/agents/openai.yaml +0 -5
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -17
- package/skills/implement/agents/openai.yaml +0 -5
- package/skills/implement-spec/LICENSE +0 -21
- package/skills/implement-spec/SKILL.md +0 -40
- package/skills/implement-spec/agents/openai.yaml +0 -5
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -71
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
- package/skills/pr/CREDITS.md +0 -3
- package/skills/pr/LICENSE +0 -21
- package/skills/pr/SKILL.md +0 -170
- package/skills/pr/agents/openai.yaml +0 -3
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -26
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -12
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/retro/LICENSE +0 -21
- package/skills/retro/SKILL.md +0 -44
- package/skills/retro/agents/openai.yaml +0 -5
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -38
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -140
- package/skills/teach/agents/openai.yaml +0 -5
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -54
- package/skills/to-questionnaire/agents/openai.yaml +0 -5
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -75
- package/skills/to-spec/agents/openai.yaml +0 -5
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -105
- package/skills/to-tickets/agents/openai.yaml +0 -5
- package/skills/triage/AGENT-BRIEF.md +0 -207
- package/skills/triage/LICENSE +0 -21
- package/skills/triage/OUT-OF-SCOPE.md +0 -105
- package/skills/triage/SKILL.md +0 -112
- package/skills/triage/agents/openai.yaml +0 -5
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -7
- package/skills/wait-what/agents/openai.yaml +0 -5
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -128
- package/skills/wayfinder/agents/openai.yaml +0 -5
- package/skills/wizard/LICENSE +0 -21
- package/skills/wizard/SKILL.md +0 -44
- package/skills/wizard/agents/openai.yaml +0 -3
- package/skills/wizard/template.sh +0 -223
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -81
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/events.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
const AUTONOMOUS_ORIGINS = new Set(['task-notification', 'peer', 'coordinator', 'observer', 'observer-activity']);
|
|
1
2
|
const COMMAND = { shown: 'command' };
|
|
2
3
|
const HIDDEN = { shown: 'hidden' };
|
|
3
4
|
const FINISHED = new Set(['completed', 'failed']);
|
|
4
5
|
export const RUNNING_JOB = new Set(['running', 'paused']);
|
|
5
|
-
const MESSAGE_TARGETS =
|
|
6
|
+
const MESSAGE_TARGETS = new Map([
|
|
7
|
+
['send_routine_request', ['routine', 'agent']],
|
|
8
|
+
['cancel_routine_request', ['request']],
|
|
9
|
+
['send_mail', ['conversation', 'request']],
|
|
10
|
+
]);
|
|
6
11
|
function words(line) {
|
|
7
12
|
const found = [];
|
|
8
13
|
let word = null;
|
|
@@ -48,11 +53,13 @@ export function houseMessage(line) {
|
|
|
48
53
|
catch {
|
|
49
54
|
return null;
|
|
50
55
|
}
|
|
51
|
-
|
|
52
|
-
if (argument === undefined)
|
|
56
|
+
if (call === null)
|
|
53
57
|
return null;
|
|
54
|
-
const
|
|
55
|
-
|
|
58
|
+
const targets = MESSAGE_TARGETS.get(call.command);
|
|
59
|
+
if (targets === undefined)
|
|
60
|
+
return null;
|
|
61
|
+
const target = targets.map((argument) => call.arguments?.[argument]).find((value) => typeof value === 'string');
|
|
62
|
+
return { shown: 'message', command: call.command, target: target ?? null };
|
|
56
63
|
}
|
|
57
64
|
function decidedByLine(command, final) {
|
|
58
65
|
const line = typeof command === 'string' ? command : Array.isArray(command) ? command.at(-1) : undefined;
|
|
@@ -269,6 +276,7 @@ class Reader {
|
|
|
269
276
|
class ClaudeReader extends Reader {
|
|
270
277
|
watchedOptions = { model: 'model' };
|
|
271
278
|
tasks = new Map();
|
|
279
|
+
stop = null;
|
|
272
280
|
own(message) {
|
|
273
281
|
if (message.params?.update?._meta?.claudeCode?.parentToolUseId !== undefined)
|
|
274
282
|
return [];
|
|
@@ -276,7 +284,9 @@ class ClaudeReader extends Reader {
|
|
|
276
284
|
}
|
|
277
285
|
update(update) {
|
|
278
286
|
if (update.sessionUpdate === 'usage_update' && update._meta?.['_claude/origin'] !== undefined) {
|
|
279
|
-
|
|
287
|
+
const stop = this.stop;
|
|
288
|
+
this.stop = null;
|
|
289
|
+
return [...super.update(update), { kind: 'ended', stop }];
|
|
280
290
|
}
|
|
281
291
|
if (update.sessionUpdate === 'session_info_update' && update._meta?.jetbrains?.air?.sessionFailure !== undefined) {
|
|
282
292
|
return [{ kind: 'marker', marker: 'retry', text: update._meta.jetbrains.air.sessionFailure.title }];
|
|
@@ -287,6 +297,13 @@ class ClaudeReader extends Reader {
|
|
|
287
297
|
if (message.method !== '_claude/sdkMessage')
|
|
288
298
|
return [];
|
|
289
299
|
const sdk = message.params.message;
|
|
300
|
+
if (sdk.type === 'result') {
|
|
301
|
+
if (AUTONOMOUS_ORIGINS.has(sdk.origin?.kind))
|
|
302
|
+
this.stop = null;
|
|
303
|
+
else
|
|
304
|
+
this.stop = sdk.stop_reason === 'refusal' ? 'refusal' : sdk.is_error === true ? null : 'end_turn';
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
290
307
|
switch (sdk.subtype) {
|
|
291
308
|
case 'task_started':
|
|
292
309
|
if (sdk.tool_use_id !== undefined)
|
|
@@ -294,6 +311,10 @@ class ClaudeReader extends Reader {
|
|
|
294
311
|
return [];
|
|
295
312
|
case 'task_notification':
|
|
296
313
|
return sdk.tool_use_id === undefined ? [] : this.finish(sdk.tool_use_id, sdk.summary ?? null);
|
|
314
|
+
case 'task_progress':
|
|
315
|
+
if (!this.subagents.has(sdk.tool_use_id) || this.done.has(sdk.tool_use_id) || typeof sdk.description !== 'string')
|
|
316
|
+
return [];
|
|
317
|
+
return [{ kind: 'subagent-activity', id: sdk.tool_use_id, activity: sdk.description }];
|
|
297
318
|
default:
|
|
298
319
|
return [];
|
|
299
320
|
}
|
|
@@ -356,7 +377,7 @@ class CodexReader extends Reader {
|
|
|
356
377
|
if (codex?.threadStatus?.type === 'active')
|
|
357
378
|
return [{ kind: 'started' }];
|
|
358
379
|
if (codex?.threadStatus?.type === 'idle')
|
|
359
|
-
return [{ kind: 'ended' }];
|
|
380
|
+
return [{ kind: 'ended', stop: null }];
|
|
360
381
|
if (codex?.error !== undefined)
|
|
361
382
|
return [{ kind: 'marker', marker: 'retry', text: codex.error.message ?? '' }];
|
|
362
383
|
return [];
|
|
@@ -429,7 +450,7 @@ class GrokReader extends Reader {
|
|
|
429
450
|
return (message.params.runningPromptId ?? null) !== null && session === this.session ? [{ kind: 'started' }] : [];
|
|
430
451
|
}
|
|
431
452
|
if (message.params?.update?.sessionUpdate === 'turn_completed') {
|
|
432
|
-
return session === this.session ? [{ kind: 'ended' }] : [];
|
|
453
|
+
return session === this.session ? [{ kind: 'ended', stop: null }] : [];
|
|
433
454
|
}
|
|
434
455
|
if (message.method === '_x.ai/models/update') {
|
|
435
456
|
this.offered(message.params);
|
package/dist/files.js
CHANGED
|
@@ -4,27 +4,68 @@ import { mkdir, rename, rm } from 'node:fs/promises';
|
|
|
4
4
|
import { basename, dirname, join } from 'node:path';
|
|
5
5
|
import { Readable, Transform } from 'node:stream';
|
|
6
6
|
import { pipeline } from 'node:stream/promises';
|
|
7
|
-
import {
|
|
7
|
+
import { parseDocument } from 'yaml';
|
|
8
|
+
import { retryDelay } from './api.js';
|
|
8
9
|
import { Unsettled } from './mcp.js';
|
|
9
10
|
import { relayed } from './relay.js';
|
|
10
11
|
const STALLED_ROUNDS = 8;
|
|
12
|
+
const PART_CHECKSUM = 'x-amz-checksum-sha256';
|
|
13
|
+
const LINK = /\]\((\/rooms\/[^/)\s]+\/[^)\s]+)\)/gu;
|
|
14
|
+
export function linkedPaths(text) {
|
|
15
|
+
return [...new Set(Array.from(text.matchAll(LINK), (link) => link[1]))];
|
|
16
|
+
}
|
|
17
|
+
export function inspectedFile(answer) {
|
|
18
|
+
const document = parseDocument(answer);
|
|
19
|
+
if (document.errors.length > 0)
|
|
20
|
+
return null;
|
|
21
|
+
const inspected = document.toJS();
|
|
22
|
+
if (typeof inspected?.download !== 'string')
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
download: inspected.download,
|
|
26
|
+
sha256: String(inspected.sha256),
|
|
27
|
+
media_type: typeof inspected.media_type === 'string' ? inspected.media_type : null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
11
30
|
function paused(milliseconds) {
|
|
12
31
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
13
32
|
}
|
|
14
33
|
export function storeRefusal(act, status) {
|
|
15
34
|
return new Error(`the ${act} answered ${status}; ${act} the file again`);
|
|
16
35
|
}
|
|
36
|
+
async function partDigests(uploading, partBytes) {
|
|
37
|
+
const whole = createHash('sha256');
|
|
38
|
+
const digests = [];
|
|
39
|
+
for (let start = 0; digests.length === 0 || start < uploading.bytes; start += partBytes) {
|
|
40
|
+
const end = Math.min(start + partBytes, uploading.bytes);
|
|
41
|
+
const digest = createHash('sha256');
|
|
42
|
+
for await (const chunk of uploading.read(start, end)) {
|
|
43
|
+
digest.update(chunk);
|
|
44
|
+
whole.update(chunk);
|
|
45
|
+
}
|
|
46
|
+
digests.push(digest.digest());
|
|
47
|
+
}
|
|
48
|
+
if (whole.digest('hex') !== uploading.sha256)
|
|
49
|
+
throw new Error(`${uploading.name} changed while it was uploaded; upload it again`);
|
|
50
|
+
return digests;
|
|
51
|
+
}
|
|
17
52
|
export async function uploaded(declared, uploading) {
|
|
18
|
-
|
|
53
|
+
const partBytes = declared.part_bytes;
|
|
54
|
+
const digests = await partDigests(uploading, partBytes);
|
|
55
|
+
const partSha256 = digests.map((digest) => digest.toString('hex'));
|
|
56
|
+
let answer = await finishing(uploading, declared.id, partSha256);
|
|
19
57
|
let idle = 0;
|
|
20
58
|
let stalled = 0;
|
|
21
|
-
|
|
59
|
+
while ('upload' in answer) {
|
|
60
|
+
const { id, parts } = answer.upload;
|
|
22
61
|
let sent = 0;
|
|
23
62
|
let failure = null;
|
|
24
|
-
for (const { part, url } of
|
|
25
|
-
const start = (part - 1) *
|
|
63
|
+
for (const { part, url } of parts ?? []) {
|
|
64
|
+
const start = (part - 1) * partBytes;
|
|
26
65
|
try {
|
|
27
|
-
await uploading.send(url, start, Math.min(start +
|
|
66
|
+
await uploading.send(url, start, Math.min(start + partBytes, uploading.bytes), {
|
|
67
|
+
[PART_CHECKSUM]: digests[part - 1].toString('base64'),
|
|
68
|
+
});
|
|
28
69
|
sent++;
|
|
29
70
|
}
|
|
30
71
|
catch (error) {
|
|
@@ -39,24 +80,17 @@ export async function uploaded(declared, uploading) {
|
|
|
39
80
|
else {
|
|
40
81
|
if (failure !== null && ++stalled >= STALLED_ROUNDS)
|
|
41
82
|
throw failure;
|
|
42
|
-
// A round that sent no part, its store unreachable
|
|
83
|
+
// A round that sent no part, its store unreachable, is followed by the next finishing call on the jittered curve.
|
|
43
84
|
await paused(retryDelay(idle++));
|
|
44
85
|
}
|
|
45
|
-
|
|
46
|
-
while ('checking' in answer) {
|
|
47
|
-
// A finishing call that finds House still checking the stored bytes is sent again after five seconds, the cadence at which House takes up a check it handed over.
|
|
48
|
-
await paused(5_000);
|
|
49
|
-
answer = await finishing(uploading, upload.id);
|
|
50
|
-
}
|
|
51
|
-
if (!('upload' in answer))
|
|
52
|
-
return answer;
|
|
53
|
-
upload = answer.upload;
|
|
86
|
+
answer = await finishing(uploading, id, partSha256);
|
|
54
87
|
}
|
|
88
|
+
return answer;
|
|
55
89
|
}
|
|
56
|
-
async function finishing(uploading, upload) {
|
|
90
|
+
async function finishing(uploading, upload, partSha256) {
|
|
57
91
|
for (let unsettled = 1;; unsettled++) {
|
|
58
92
|
try {
|
|
59
|
-
return await uploading.finish(upload);
|
|
93
|
+
return await uploading.finish(upload, partSha256);
|
|
60
94
|
}
|
|
61
95
|
catch (error) {
|
|
62
96
|
if (!(error instanceof Unsettled) || unsettled === STALLED_ROUNDS)
|
|
@@ -104,28 +138,7 @@ export async function downloaded(address, fetching, destination, name) {
|
|
|
104
138
|
return;
|
|
105
139
|
}
|
|
106
140
|
}
|
|
107
|
-
async function fetched(url, signal) {
|
|
141
|
+
export async function fetched(url, signal) {
|
|
108
142
|
const answer = await fetch(relayed(url), { signal });
|
|
109
143
|
return { status: answer.status, body: answer.body ?? Readable.from([]) };
|
|
110
144
|
}
|
|
111
|
-
export async function placeFiles(house, directory, files, signal) {
|
|
112
|
-
const paths = [];
|
|
113
|
-
for (const file of files) {
|
|
114
|
-
const original = () => retrying(() => house.post('/kit/conversation/originals/get', { version: file.version, download: true }, signal), signal);
|
|
115
|
-
let first = await original();
|
|
116
|
-
const folder = join(directory, '.house', 'files', first.message, file.version);
|
|
117
|
-
await mkdir(join(directory, '.house')).catch((error) => {
|
|
118
|
-
if (error.code !== 'EEXIST')
|
|
119
|
-
throw error;
|
|
120
|
-
});
|
|
121
|
-
await mkdir(folder, { recursive: true });
|
|
122
|
-
const path = join(folder, file.name);
|
|
123
|
-
await downloaded(async () => {
|
|
124
|
-
const answered = first ?? (await original());
|
|
125
|
-
first = null;
|
|
126
|
-
return { download: answered.download.url, sha256: file.sha256 };
|
|
127
|
-
}, (url) => fetched(url, signal), path, file.name);
|
|
128
|
-
paths.push(path);
|
|
129
|
-
}
|
|
130
|
-
return paths;
|
|
131
|
-
}
|
package/dist/git.js
CHANGED
|
@@ -2,8 +2,8 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { isUtf8 } from 'node:buffer';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { withoutProxy } from './clis.js';
|
|
6
5
|
import { workspaceRoot } from './home.js';
|
|
6
|
+
import { childEnvironment } from './relay.js';
|
|
7
7
|
export function localCopies() {
|
|
8
8
|
return join(workspaceRoot(), 'local-copies');
|
|
9
9
|
}
|
|
@@ -11,7 +11,7 @@ export const HOUSE_REF = 'refs/house/received';
|
|
|
11
11
|
const COMMITTER = 'Kit <house-kit@localhost>';
|
|
12
12
|
export function run(cwd, args, input, env = {}) {
|
|
13
13
|
return new Promise((resolve, reject) => {
|
|
14
|
-
const child = spawn('git', args, { cwd, env: { ...
|
|
14
|
+
const child = spawn('git', args, { cwd, env: { ...childEnvironment(), ...env } });
|
|
15
15
|
const stdout = [];
|
|
16
16
|
let stderr = '';
|
|
17
17
|
child.stdout.on('data', (chunk) => stdout.push(chunk));
|
package/dist/house-cli.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
-
import {
|
|
4
|
-
import { request, setGlobalProxyFromEnv } from 'node:http';
|
|
3
|
+
import { request } from 'node:http';
|
|
5
4
|
import { request as httpsRequest } from 'node:https';
|
|
6
|
-
import {
|
|
5
|
+
import { extname, join, resolve } from 'node:path';
|
|
7
6
|
import { Readable } from 'node:stream';
|
|
8
7
|
import { pipeline } from 'node:stream/promises';
|
|
9
|
-
import { downloaded, storeRefusal, uploaded } from './files.js';
|
|
8
|
+
import { downloaded, inspectedFile, storeRefusal, uploaded } from './files.js';
|
|
10
9
|
import { HOUSE_REF } from './git.js';
|
|
11
10
|
import { ownerSocket, readOwnAgent } from './home.js';
|
|
12
11
|
import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
|
|
13
12
|
import { refusalOf, retryable } from './refusals.js';
|
|
14
|
-
import { relayed, selectRelay } from './relay.js';
|
|
13
|
+
import { proxyUnlessRelayed, relayed, selectRelay } from './relay.js';
|
|
15
14
|
import { parse } from 'yaml';
|
|
16
15
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
17
16
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
@@ -90,7 +89,6 @@ async function direct(message) {
|
|
|
90
89
|
if (ownAgent === null)
|
|
91
90
|
throw new Error(UNCONNECTED);
|
|
92
91
|
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
93
|
-
setGlobalProxyFromEnv();
|
|
94
92
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
95
93
|
throw new Unsettled(UNREACHABLE);
|
|
96
94
|
});
|
|
@@ -116,43 +114,35 @@ function unreadable(path, error) {
|
|
|
116
114
|
? `${path} is a folder; upload one file at a time`
|
|
117
115
|
: `${path} is not a readable file`);
|
|
118
116
|
}
|
|
119
|
-
async function localFile(path) {
|
|
120
|
-
try {
|
|
121
|
-
return await readFile(path);
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
throw unreadable(path, error);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
117
|
function opened(url, method, headers, body) {
|
|
128
118
|
return new Promise((answered, failed) => {
|
|
129
119
|
const local = socketPath !== undefined;
|
|
130
120
|
const target = local ? null : relayed(url);
|
|
131
121
|
if (!local)
|
|
132
|
-
|
|
122
|
+
proxyUnlessRelayed();
|
|
133
123
|
const sent = target === null
|
|
134
124
|
? request({ socketPath, path: url, method, headers }, answered)
|
|
135
125
|
: (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
|
|
136
126
|
sent.on('error', (error) => failed(new Error(!local ? UNREACHABLE : error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
|
|
137
|
-
if (body
|
|
138
|
-
|
|
127
|
+
if (body === undefined)
|
|
128
|
+
sent.end();
|
|
139
129
|
else
|
|
140
|
-
sent.
|
|
130
|
+
pipeline(body, sent).catch(() => undefined);
|
|
141
131
|
});
|
|
142
132
|
}
|
|
143
|
-
|
|
144
|
-
return
|
|
133
|
+
function ranged(file, start, end) {
|
|
134
|
+
return end === start ? undefined : createReadStream(file, { start, end: end - 1 });
|
|
145
135
|
}
|
|
146
|
-
async function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
136
|
+
async function* read(file, start, end) {
|
|
137
|
+
try {
|
|
138
|
+
yield* ranged(file, start, end) ?? [];
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw unreadable(file, error);
|
|
142
|
+
}
|
|
153
143
|
}
|
|
154
|
-
async function sentPart(url, file, start, end) {
|
|
155
|
-
const answer = await opened(url, 'PUT', { 'content-length': String(end - start) },
|
|
144
|
+
async function sentPart(url, file, start, end, headers) {
|
|
145
|
+
const answer = await opened(url, 'PUT', { ...headers, 'content-length': String(end - start) }, ranged(file, start, end));
|
|
156
146
|
await answer.toArray();
|
|
157
147
|
if (answer.statusCode !== 200)
|
|
158
148
|
throw storeRefusal('upload', answer.statusCode);
|
|
@@ -166,10 +156,10 @@ async function download(args) {
|
|
|
166
156
|
throw new Error('download needs "file", the local file to write');
|
|
167
157
|
const written = resolve(file);
|
|
168
158
|
await downloaded(async () => {
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
159
|
+
const file = inspectedFile(await called('inspect', selected));
|
|
160
|
+
if (file === null)
|
|
171
161
|
throw new Error(`${String(selected.path)} holds no binary file; shell reads its text`);
|
|
172
|
-
return
|
|
162
|
+
return file;
|
|
173
163
|
}, async (url) => {
|
|
174
164
|
const answer = await opened(url, 'GET', {});
|
|
175
165
|
return { status: answer.statusCode, body: answer };
|
|
@@ -191,10 +181,10 @@ async function described(file) {
|
|
|
191
181
|
const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
|
|
192
182
|
return { bytes, sha256: digest.digest('hex'), ...(mediaType === undefined ? {} : { media_type: mediaType }) };
|
|
193
183
|
}
|
|
194
|
-
async function finished(upload, room) {
|
|
195
|
-
const text = await called('upload', { upload, room_ref: room });
|
|
184
|
+
async function finished(upload, room, partSha256) {
|
|
185
|
+
const text = await called('upload', { upload, room_ref: room, part_sha256: partSha256 });
|
|
196
186
|
const answer = parse(text);
|
|
197
|
-
return answer !== null &&
|
|
187
|
+
return answer !== null && 'upload' in answer ? answer : { text };
|
|
198
188
|
}
|
|
199
189
|
async function upload({ file, ...destination }) {
|
|
200
190
|
if (typeof file !== 'string' || file === '')
|
|
@@ -205,33 +195,15 @@ async function upload({ file, ...destination }) {
|
|
|
205
195
|
if (declared?.upload === undefined)
|
|
206
196
|
return answer;
|
|
207
197
|
const accepted = await uploaded(declared.upload, {
|
|
198
|
+
name: file,
|
|
208
199
|
bytes: description.bytes,
|
|
209
|
-
|
|
210
|
-
|
|
200
|
+
sha256: description.sha256,
|
|
201
|
+
read: (start, end) => read(file, start, end),
|
|
202
|
+
send: (url, start, end, headers) => sentPart(url, file, start, end, headers),
|
|
203
|
+
finish: (id, partSha256) => finished(id, destination.room_ref, partSha256),
|
|
211
204
|
});
|
|
212
205
|
return accepted.text;
|
|
213
206
|
}
|
|
214
|
-
async function appendRecord(args) {
|
|
215
|
-
const held = new Map();
|
|
216
|
-
const attachments = Array.isArray(args.attachments)
|
|
217
|
-
? await Promise.all(args.attachments.map(async (attachment) => {
|
|
218
|
-
if (typeof attachment !== 'string')
|
|
219
|
-
return attachment;
|
|
220
|
-
const description = await described(attachment);
|
|
221
|
-
held.set(description.sha256, attachment);
|
|
222
|
-
return { name: basename(attachment), ...description };
|
|
223
|
-
}))
|
|
224
|
-
: args.attachments;
|
|
225
|
-
const record = { ...args, attachments };
|
|
226
|
-
for (;;) {
|
|
227
|
-
const answer = await called('append_record', record);
|
|
228
|
-
const { upload } = parse(answer);
|
|
229
|
-
if (upload === undefined)
|
|
230
|
-
return answer;
|
|
231
|
-
for (const grant of upload)
|
|
232
|
-
await sent(grant.url, await localFile(held.get(grant.sha256)), grant.operation);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
207
|
function unexpected(argument, usage) {
|
|
236
208
|
return new Error(`unexpected argument ${argument}; ${usage}`);
|
|
237
209
|
}
|
|
@@ -299,7 +271,7 @@ async function house(args) {
|
|
|
299
271
|
return [...(tool.description === undefined ? [] : [tool.description]), JSON.stringify(tool.inputSchema)].join('\n');
|
|
300
272
|
}
|
|
301
273
|
const parsed = argumentsOf(argument);
|
|
302
|
-
return verb === 'upload' ? upload(parsed) :
|
|
274
|
+
return verb === 'upload' ? upload(parsed) : called(verb, parsed);
|
|
303
275
|
}
|
|
304
276
|
async function called(name, args) {
|
|
305
277
|
const result = await mcp('tools/call', { name, arguments: args });
|
package/dist/house-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
-
if (major < 24 || (major === 24 && minor <
|
|
4
|
-
process.stderr.write(`house: the Kit needs Node 24.
|
|
2
|
+
const [major = 0, minor = 0, patch = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
+
if (major < 24 || (major === 24 && (minor < 22 || (minor === 22 && patch < 1)))) {
|
|
4
|
+
process.stderr.write(`house: the Kit needs Node 24.22.1 or newer; this is ${process.versions.node}\n`);
|
|
5
5
|
process.exitCode = 1;
|
|
6
6
|
}
|
|
7
7
|
else {
|
package/dist/instructions.js
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
|
+
import { agentInstructions } from '@agentshouse/skills';
|
|
1
2
|
import { HOUSE_REF } from './git.js';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
3
|
+
const OWNER_RULES = '/rooms/private/owner-rules.md';
|
|
4
|
+
const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
|
|
5
|
+
const FAILED = /^exit: [1-9]\d*$/;
|
|
6
|
+
const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
|
|
7
|
+
function cutOf(lines) {
|
|
8
|
+
const cut = CUT.exec(lines[0] ?? '');
|
|
9
|
+
return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
|
|
10
|
+
}
|
|
11
|
+
async function readOwnerRules(bridge) {
|
|
12
|
+
let document = '';
|
|
13
|
+
for (let command = `cat ${OWNER_RULES}`; command !== undefined;) {
|
|
14
|
+
const read = await bridge.tool('shell', { command });
|
|
15
|
+
const text = read.content.map((content) => content.text).join('\n');
|
|
16
|
+
const lines = text.split('\n');
|
|
17
|
+
if (read.isError === true || (FAILED.test(lines[0] ?? '') && lines[1]?.startsWith('stderr: '))) {
|
|
18
|
+
const refusal = read.isError === true ? text : lines.slice(1).join('\n').replace(/^stderr: /gm, '');
|
|
19
|
+
if (UNREADABLE.test(refusal))
|
|
20
|
+
return '';
|
|
21
|
+
throw new Error(refusal);
|
|
22
|
+
}
|
|
23
|
+
const cut = cutOf(lines);
|
|
24
|
+
document += lines.slice(cut === null ? 0 : 1).join('\n');
|
|
25
|
+
command = cut?.[2];
|
|
26
|
+
}
|
|
27
|
+
return document;
|
|
28
|
+
}
|
|
9
29
|
export async function instructions(bridge, base, copies) {
|
|
10
|
-
const lines =
|
|
11
|
-
return [lines.join('\n'), base, (await
|
|
30
|
+
const lines = agentInstructions(HOUSE_REF, copies);
|
|
31
|
+
return [lines.join('\n'), base, (await readOwnerRules(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
|
|
12
32
|
}
|
package/dist/kit-cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
1
|
import { parseArgs } from 'node:util';
|
|
3
2
|
import { answerGitCredential } from './app-git.js';
|
|
4
3
|
import { chatgpt } from './chatgpt.js';
|
|
5
4
|
import { login } from './login.js';
|
|
6
5
|
import { logout } from './logout.js';
|
|
6
|
+
import { proxyUnlessRelayed } from './relay.js';
|
|
7
7
|
import { resident } from './resident.js';
|
|
8
8
|
const USAGE = 'usage: kit <login|logout|resident|chatgpt>\n';
|
|
9
9
|
const COMMANDS = new Map([
|
|
@@ -36,7 +36,7 @@ const COMMANDS = new Map([
|
|
|
36
36
|
],
|
|
37
37
|
]);
|
|
38
38
|
const [command, ...argv] = process.argv.slice(2);
|
|
39
|
-
|
|
39
|
+
proxyUnlessRelayed();
|
|
40
40
|
async function run() {
|
|
41
41
|
if (command === 'login')
|
|
42
42
|
return login(argv);
|
package/dist/kit-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
-
if (major < 24 || (major === 24 && minor <
|
|
4
|
-
process.stderr.write(`kit: the Kit needs Node 24.
|
|
2
|
+
const [major = 0, minor = 0, patch = 0] = process.versions.node.split('.').map(Number);
|
|
3
|
+
if (major < 24 || (major === 24 && (minor < 22 || (minor === 22 && patch < 1)))) {
|
|
4
|
+
process.stderr.write(`kit: the Kit needs Node 24.22.1 or newer; this is ${process.versions.node}\n`);
|
|
5
5
|
process.exitCode = 1;
|
|
6
6
|
}
|
|
7
7
|
else {
|
package/dist/login.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createHash, randomBytes } from 'node:crypto';
|
|
3
|
-
import { hostname } from 'node:os';
|
|
4
3
|
import { parseArgs } from 'node:util';
|
|
5
4
|
import { HouseRefusal, post, retrying } from './api.js';
|
|
6
5
|
import { placeAppGit } from './app-git.js';
|
|
@@ -11,6 +10,7 @@ import { refusalOf } from './refusals.js';
|
|
|
11
10
|
import { selectRelay } from './relay.js';
|
|
12
11
|
import { startService } from './service.js';
|
|
13
12
|
import { placeSkillSet } from './skills.js';
|
|
13
|
+
import { endUnserved } from './wire.js';
|
|
14
14
|
export const DEFAULT_HOUSE = 'https://agents.house';
|
|
15
15
|
const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
16
16
|
// Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
|
|
@@ -26,6 +26,7 @@ export async function accepted(house, credential) {
|
|
|
26
26
|
return true;
|
|
27
27
|
if (checked.status === 401)
|
|
28
28
|
return false;
|
|
29
|
+
endUnserved(checked.text);
|
|
29
30
|
throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
|
|
30
31
|
}
|
|
31
32
|
const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private]
|
|
@@ -91,13 +92,12 @@ export async function login(argv) {
|
|
|
91
92
|
let named = environment;
|
|
92
93
|
let exchanged;
|
|
93
94
|
if (values.code !== undefined) {
|
|
94
|
-
exchanged = await post(house, '/kit/token', { code: values.code
|
|
95
|
+
exchanged = await post(house, '/kit/token', { code: values.code });
|
|
95
96
|
}
|
|
96
97
|
else {
|
|
97
98
|
held = ownAgent !== null && ownAgent.house === house && (await accepted(house, ownAgent.credential));
|
|
98
99
|
const verifier = randomBytes(32).toString('base64url');
|
|
99
100
|
const start = () => retrying(() => post(house, '/kit', {
|
|
100
|
-
label: hostname(),
|
|
101
101
|
...(named === undefined
|
|
102
102
|
? { act: 'new_environment', ...(replaces === undefined ? {} : { replaces }) }
|
|
103
103
|
: { act: 'existing_environment', environment_id: named }),
|
|
@@ -121,7 +121,7 @@ export async function login(argv) {
|
|
|
121
121
|
open(started.start_url);
|
|
122
122
|
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
123
123
|
}
|
|
124
|
-
await placeSkillSet();
|
|
124
|
+
await placeSkillSet(house);
|
|
125
125
|
await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
126
126
|
const relayRecord = relay === undefined ? {} : { relay };
|
|
127
127
|
await writeEnrolment({
|
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { KIT_RELEASE } from './clis.js';
|
|
3
3
|
import { relayed } from './relay.js';
|
|
4
|
+
import { KIT_WIRE, KIT_WIRE_HEADER } from './wire.js';
|
|
4
5
|
const MCP_PROTOCOL_VERSION = '2026-07-28';
|
|
5
6
|
export const UNREACHABLE = 'House is unreachable; call again in a minute';
|
|
6
7
|
export class Unsettled extends Error {
|
|
@@ -33,6 +34,7 @@ export async function callHouse(origin, credential, message, operation = null, s
|
|
|
33
34
|
'mcp-protocol-version': MCP_PROTOCOL_VERSION,
|
|
34
35
|
'mcp-method': message.method,
|
|
35
36
|
...(tool === null ? {} : { 'mcp-name': tool }),
|
|
37
|
+
[KIT_WIRE_HEADER]: String(KIT_WIRE),
|
|
36
38
|
},
|
|
37
39
|
body: mcpBody(message, operation),
|
|
38
40
|
signal,
|
package/dist/relay.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { setGlobalProxyFromEnv } from 'node:http';
|
|
2
|
+
const PROXY = /^https?_proxy$/i;
|
|
3
|
+
const LOOPBACK = ['127.0.0.1', 'localhost', '::1'];
|
|
1
4
|
let selected;
|
|
5
|
+
let restoreProxy;
|
|
2
6
|
export function selectRelay(relay) {
|
|
3
7
|
selected = relay === undefined || relay === '' ? null : relay;
|
|
8
|
+
proxyUnlessRelayed();
|
|
4
9
|
}
|
|
5
10
|
export function relayOrigin() {
|
|
6
11
|
if (selected !== undefined)
|
|
@@ -8,6 +13,22 @@ export function relayOrigin() {
|
|
|
8
13
|
const ambient = process.env.HOUSE_KIT_RELAY;
|
|
9
14
|
return ambient === undefined || ambient === '' ? undefined : ambient;
|
|
10
15
|
}
|
|
16
|
+
function unlessRelayed() {
|
|
17
|
+
if (relayOrigin() === undefined)
|
|
18
|
+
return { ...process.env };
|
|
19
|
+
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !PROXY.test(name)));
|
|
20
|
+
}
|
|
21
|
+
export function proxyUnlessRelayed() {
|
|
22
|
+
restoreProxy?.();
|
|
23
|
+
restoreProxy = setGlobalProxyFromEnv(unlessRelayed());
|
|
24
|
+
}
|
|
25
|
+
export function childEnvironment() {
|
|
26
|
+
const environment = unlessRelayed();
|
|
27
|
+
if (!Object.keys(environment).some((name) => PROXY.test(name)))
|
|
28
|
+
return environment;
|
|
29
|
+
const bypassed = [environment.NO_PROXY ?? environment.no_proxy, ...LOOPBACK].filter((entry) => entry !== undefined && entry !== '').join(',');
|
|
30
|
+
return { ...environment, NO_PROXY: bypassed, no_proxy: bypassed };
|
|
31
|
+
}
|
|
11
32
|
export function relayed(target) {
|
|
12
33
|
const url = new URL(target);
|
|
13
34
|
const relay = relayOrigin();
|