@agentshouse/kit 0.2.1-cb345f4157685 → 0.2.1-cb66cb2fd0696

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 (140) hide show
  1. package/README.md +28 -28
  2. package/dist/acp.js +58 -7
  3. package/dist/agents.js +85 -40
  4. package/dist/api.js +5 -5
  5. package/dist/authority-main.js +2 -2
  6. package/dist/bridge.js +1 -1
  7. package/dist/building.js +50 -0
  8. package/dist/calls.js +71 -0
  9. package/dist/clis.js +34 -40
  10. package/dist/{stream.js → connection.js} +31 -49
  11. package/dist/conversations.js +423 -139
  12. package/dist/copies.js +36 -43
  13. package/dist/end.js +14 -4
  14. package/dist/events.js +29 -8
  15. package/dist/files.js +139 -37
  16. package/dist/git.js +3 -3
  17. package/dist/home.js +0 -7
  18. package/dist/house-cli.js +95 -87
  19. package/dist/house-main.js +3 -3
  20. package/dist/instructions.js +29 -9
  21. package/dist/kit-cli.js +4 -4
  22. package/dist/kit-main.js +3 -3
  23. package/dist/login.js +4 -4
  24. package/dist/mcp.js +6 -2
  25. package/dist/placement.js +21 -20
  26. package/dist/relay.js +21 -0
  27. package/dist/resident.js +45 -38
  28. package/dist/scope.js +15 -4
  29. package/dist/scripts.js +255 -0
  30. package/dist/service.js +1 -1
  31. package/dist/sign-in.js +22 -21
  32. package/dist/silence.js +28 -0
  33. package/dist/skills.js +42 -97
  34. package/dist/transcription.js +83 -43
  35. package/dist/translate.js +2 -8
  36. package/dist/update.js +91 -125
  37. package/dist/wire.js +19 -3
  38. package/package.json +20 -21
  39. package/dist/bootstrap.json +0 -24
  40. package/dist/clis-main.js +0 -23
  41. package/dist/inputs.js +0 -37
  42. package/how-we-work.md +0 -33
  43. package/skills/ask-matt/LICENSE +0 -21
  44. package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
  45. package/skills/ask-matt/SKILL.md +0 -97
  46. package/skills/ask-matt/agents/openai.yaml +0 -5
  47. package/skills/code-review/LICENSE +0 -21
  48. package/skills/code-review/SKILL.md +0 -89
  49. package/skills/code-review/agents/openai.yaml +0 -3
  50. package/skills/codebase-design/DEEPENING.md +0 -37
  51. package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  52. package/skills/codebase-design/LICENSE +0 -21
  53. package/skills/codebase-design/SKILL.md +0 -114
  54. package/skills/codebase-design/agents/openai.yaml +0 -3
  55. package/skills/diagnosing-bugs/LICENSE +0 -21
  56. package/skills/diagnosing-bugs/SKILL.md +0 -138
  57. package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  58. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
  59. package/skills/domain-modeling/ADR-FORMAT.md +0 -47
  60. package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
  61. package/skills/domain-modeling/LICENSE +0 -21
  62. package/skills/domain-modeling/SKILL.md +0 -74
  63. package/skills/domain-modeling/agents/openai.yaml +0 -3
  64. package/skills/grill-me/LICENSE +0 -21
  65. package/skills/grill-me/SKILL.md +0 -7
  66. package/skills/grill-me/agents/openai.yaml +0 -5
  67. package/skills/grill-with-docs/LICENSE +0 -21
  68. package/skills/grill-with-docs/SKILL.md +0 -7
  69. package/skills/grill-with-docs/agents/openai.yaml +0 -5
  70. package/skills/grilling/LICENSE +0 -21
  71. package/skills/grilling/SKILL.md +0 -30
  72. package/skills/grilling/agents/openai.yaml +0 -3
  73. package/skills/handoff/LICENSE +0 -21
  74. package/skills/handoff/SKILL.md +0 -16
  75. package/skills/handoff/agents/openai.yaml +0 -5
  76. package/skills/implement/LICENSE +0 -21
  77. package/skills/implement/SKILL.md +0 -17
  78. package/skills/implement/agents/openai.yaml +0 -5
  79. package/skills/implement-spec/LICENSE +0 -21
  80. package/skills/implement-spec/SKILL.md +0 -40
  81. package/skills/implement-spec/agents/openai.yaml +0 -5
  82. package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  83. package/skills/improve-codebase-architecture/LICENSE +0 -21
  84. package/skills/improve-codebase-architecture/SKILL.md +0 -71
  85. package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  86. package/skills/pr/CREDITS.md +0 -3
  87. package/skills/pr/LICENSE +0 -21
  88. package/skills/pr/SKILL.md +0 -170
  89. package/skills/pr/agents/openai.yaml +0 -3
  90. package/skills/prototype/LICENSE +0 -21
  91. package/skills/prototype/LOGIC.md +0 -67
  92. package/skills/prototype/SKILL.md +0 -26
  93. package/skills/prototype/UI.md +0 -112
  94. package/skills/prototype/agents/openai.yaml +0 -3
  95. package/skills/research/LICENSE +0 -21
  96. package/skills/research/SKILL.md +0 -12
  97. package/skills/research/agents/openai.yaml +0 -3
  98. package/skills/retro/LICENSE +0 -21
  99. package/skills/retro/SKILL.md +0 -44
  100. package/skills/retro/agents/openai.yaml +0 -5
  101. package/skills/tdd/LICENSE +0 -21
  102. package/skills/tdd/SKILL.md +0 -38
  103. package/skills/tdd/agents/openai.yaml +0 -3
  104. package/skills/tdd/mocking.md +0 -59
  105. package/skills/tdd/tests.md +0 -77
  106. package/skills/teach/GLOSSARY-FORMAT.md +0 -35
  107. package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  108. package/skills/teach/LICENSE +0 -21
  109. package/skills/teach/MISSION-FORMAT.md +0 -31
  110. package/skills/teach/RESOURCES-FORMAT.md +0 -32
  111. package/skills/teach/SKILL.md +0 -140
  112. package/skills/teach/agents/openai.yaml +0 -5
  113. package/skills/to-questionnaire/LICENSE +0 -21
  114. package/skills/to-questionnaire/SKILL.md +0 -54
  115. package/skills/to-questionnaire/agents/openai.yaml +0 -5
  116. package/skills/to-spec/LICENSE +0 -21
  117. package/skills/to-spec/SKILL.md +0 -75
  118. package/skills/to-spec/agents/openai.yaml +0 -5
  119. package/skills/to-tickets/LICENSE +0 -21
  120. package/skills/to-tickets/SKILL.md +0 -105
  121. package/skills/to-tickets/agents/openai.yaml +0 -5
  122. package/skills/triage/AGENT-BRIEF.md +0 -207
  123. package/skills/triage/LICENSE +0 -21
  124. package/skills/triage/OUT-OF-SCOPE.md +0 -105
  125. package/skills/triage/SKILL.md +0 -112
  126. package/skills/triage/agents/openai.yaml +0 -5
  127. package/skills/wait-what/LICENSE +0 -21
  128. package/skills/wait-what/SKILL.md +0 -7
  129. package/skills/wait-what/agents/openai.yaml +0 -5
  130. package/skills/wayfinder/LICENSE +0 -21
  131. package/skills/wayfinder/SKILL.md +0 -128
  132. package/skills/wayfinder/agents/openai.yaml +0 -5
  133. package/skills/wizard/LICENSE +0 -21
  134. package/skills/wizard/SKILL.md +0 -44
  135. package/skills/wizard/agents/openai.yaml +0 -3
  136. package/skills/wizard/template.sh +0 -223
  137. package/skills/writing-for-agents/LICENSE +0 -21
  138. package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
  139. package/skills/writing-for-agents/SKILL.md +0 -81
  140. package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/house-cli.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
- import { request, setGlobalProxyFromEnv } from 'node:http';
4
- import { basename, dirname, extname, join, resolve } from 'node:path';
5
- import { verified } from './files.js';
2
+ import { createReadStream } from 'node:fs';
3
+ import { request } from 'node:http';
4
+ import { request as httpsRequest } from 'node:https';
5
+ import { extname, join, resolve } from 'node:path';
6
+ import { Readable } from 'node:stream';
7
+ import { pipeline } from 'node:stream/promises';
8
+ import { downloaded, inspectedFile, storeRefusal, uploaded } from './files.js';
6
9
  import { HOUSE_REF } from './git.js';
7
10
  import { ownerSocket, readOwnAgent } from './home.js';
8
- import { callHouse, rpc, UNREACHABLE } from './mcp.js';
9
- import { refusalOf } from './refusals.js';
10
- import { relayed, selectRelay } from './relay.js';
11
- import { parse, stringify } from 'yaml';
11
+ import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
12
+ import { refusalOf, retryable } from './refusals.js';
13
+ import { proxyUnlessRelayed, relayed, selectRelay } from './relay.js';
14
+ import { parse } from 'yaml';
12
15
  const USAGE = "usage: house <tool> ['<arguments as JSON>']";
13
16
  const PUSH_USAGE = 'usage: house push [commit] [--owner]';
14
17
  const PUSH = "push [commit]: sends the Local copy's committed changes to House";
@@ -51,15 +54,12 @@ const MEDIA_TYPES = new Map([
51
54
  ['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
52
55
  ['zip', 'application/zip'],
53
56
  ]);
54
- function refused(path, method, status, text, local) {
57
+ function refused(path, status, text, local) {
55
58
  const refusal = refusalOf(text);
56
59
  if (refusal !== null)
57
60
  return refusal.text;
58
61
  if (status === 401)
59
62
  return REVOKED;
60
- const act = method === 'GET' ? 'download' : 'upload';
61
- if (URL.canParse(path))
62
- return `the ${act} answered ${status}; ${act} the file again`;
63
63
  if (status === 502 && local)
64
64
  return text;
65
65
  return path === '/' ? unconfirmed(status) : `${path} answered ${status}`;
@@ -67,38 +67,37 @@ function refused(path, method, status, text, local) {
67
67
  function unconfirmed(status) {
68
68
  return `House answered ${status} ${UNCONFIRMED}`;
69
69
  }
70
- function exchanged(path, method, body, headers = { 'content-type': 'application/json' }, to = socketPath) {
70
+ function bridged(path, body, to = socketPath) {
71
71
  return new Promise((resolve, reject) => {
72
- const sent = request({ socketPath: to, path, method, headers }, (answer) => {
72
+ const sent = request({ socketPath: to, path, method: 'POST', headers: { 'content-type': 'application/json' } }, (answer) => {
73
73
  const chunks = [];
74
74
  answer.on('data', (chunk) => chunks.push(chunk));
75
75
  answer.on('end', () => {
76
- const bytes = Buffer.concat(chunks);
76
+ const text = Buffer.concat(chunks).toString('utf8');
77
77
  if (answer.statusCode === 200)
78
- resolve(bytes);
78
+ resolve(text);
79
79
  else
80
- reject(new Error(refused(path, method, answer.statusCode, bytes.toString('utf8'), answer.headers['content-type'] === undefined)));
80
+ reject(new Error(refused(path, answer.statusCode, text, answer.headers['content-type'] === undefined)));
81
81
  });
82
82
  });
83
83
  sent.on('error', (error) => reject(new Error(error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
84
- sent.end(body === undefined ? undefined : body instanceof Buffer ? body : JSON.stringify(body));
84
+ sent.end(JSON.stringify(body));
85
85
  });
86
86
  }
87
- async function bridged(path, body, headers, to = socketPath) {
88
- return (await exchanged(path, 'POST', body, headers, to)).toString('utf8');
89
- }
90
87
  async function direct(message) {
91
88
  const ownAgent = await readOwnAgent();
92
89
  if (ownAgent === null)
93
90
  throw new Error(UNCONNECTED);
94
91
  selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
95
- setGlobalProxyFromEnv();
96
92
  const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
97
- throw new Error(UNREACHABLE);
93
+ throw new Unsettled(UNREACHABLE);
98
94
  });
99
95
  if (answer.status === 200)
100
96
  return answer.text;
101
- throw new Error(answer.status === 401 ? DISCONNECTED : (refusalOf(answer.text)?.text ?? unconfirmed(answer.status)));
97
+ if (answer.status === 401)
98
+ throw new Error(DISCONNECTED);
99
+ const refusal = refusalOf(answer.text)?.text ?? unconfirmed(answer.status);
100
+ throw retryable(answer.text) === false ? new Error(refusal) : new Unsettled(refusal);
102
101
  }
103
102
  async function mcp(method, params) {
104
103
  const message = rpc(method, params);
@@ -110,31 +109,43 @@ async function mcp(method, params) {
110
109
  async function tools() {
111
110
  return (await mcp('tools/list', {})).tools;
112
111
  }
113
- async function localFile(path) {
112
+ function unreadable(path, error) {
113
+ return new Error(error.code === 'EISDIR'
114
+ ? `${path} is a folder; upload one file at a time`
115
+ : `${path} is not a readable file`);
116
+ }
117
+ function opened(url, method, headers, body) {
118
+ return new Promise((answered, failed) => {
119
+ const local = socketPath !== undefined;
120
+ const target = local ? null : relayed(url);
121
+ if (!local)
122
+ proxyUnlessRelayed();
123
+ const sent = target === null
124
+ ? request({ socketPath, path: url, method, headers }, answered)
125
+ : (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
126
+ sent.on('error', (error) => failed(new Error(!local ? UNREACHABLE : error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
127
+ if (body === undefined)
128
+ sent.end();
129
+ else
130
+ pipeline(body, sent).catch(() => undefined);
131
+ });
132
+ }
133
+ function ranged(file, start, end) {
134
+ return end === start ? undefined : createReadStream(file, { start, end: end - 1 });
135
+ }
136
+ async function* read(file, start, end) {
114
137
  try {
115
- return await readFile(path);
138
+ yield* ranged(file, start, end) ?? [];
116
139
  }
117
140
  catch (error) {
118
- throw new Error(error.code === 'EISDIR'
119
- ? `${path} is a folder; upload one file at a time`
120
- : `${path} is not a readable file`);
141
+ throw unreadable(file, error);
121
142
  }
122
143
  }
123
- async function transferred(url, method, bytes, headers) {
124
- if (socketPath !== undefined)
125
- return exchanged(url, method, bytes, headers);
126
- setGlobalProxyFromEnv();
127
- const answer = await fetch(relayed(url), { method, ...(headers === undefined ? {} : { headers }), ...(bytes === undefined ? {} : { body: bytes }) }).catch(() => {
128
- throw new Error(UNREACHABLE);
129
- });
130
- const received = Buffer.from(await answer.arrayBuffer());
131
- if (answer.status === 200)
132
- return received;
133
- throw new Error(refused(url, method, answer.status, received.toString('utf8'), false));
134
- }
135
- async function sent(url, bytes, operation) {
136
- const headers = { 'content-type': 'application/octet-stream', 'x-house-byte-operation': operation };
137
- return (await transferred(url, 'POST', bytes, headers)).toString('utf8');
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));
146
+ await answer.toArray();
147
+ if (answer.statusCode !== 200)
148
+ throw storeRefusal('upload', answer.statusCode);
138
149
  }
139
150
  async function download(args) {
140
151
  const { flags, positionals: [argument] } = accepted(args, ['--help'], 1, DOWNLOAD_USAGE);
@@ -143,58 +154,55 @@ async function download(args) {
143
154
  const { file, ...selected } = argumentsOf(argument);
144
155
  if (typeof file !== 'string' || file === '')
145
156
  throw new Error('download needs "file", the local file to write');
146
- const inspected = parse(await called('inspect', selected));
147
- if (typeof inspected?.download !== 'string')
148
- throw new Error(`${String(selected.path)} holds no binary file; shell reads its text`);
149
- const bytes = verified(await transferred(inspected.download, 'GET'), String(inspected.sha256), String(selected.path));
150
157
  const written = resolve(file);
151
- await mkdir(dirname(written), { recursive: true });
152
- await writeFile(written, bytes);
158
+ await downloaded(async () => {
159
+ const file = inspectedFile(await called('inspect', selected));
160
+ if (file === null)
161
+ throw new Error(`${String(selected.path)} holds no binary file; shell reads its text`);
162
+ return file;
163
+ }, async (url) => {
164
+ const answer = await opened(url, 'GET', {});
165
+ return { status: answer.statusCode, body: answer };
166
+ }, written, String(selected.path));
153
167
  return written;
154
168
  }
155
169
  async function described(file) {
156
- const bytes = await localFile(file);
170
+ const digest = createHash('sha256');
171
+ let bytes = 0;
172
+ try {
173
+ for await (const chunk of createReadStream(file)) {
174
+ digest.update(chunk);
175
+ bytes += chunk.length;
176
+ }
177
+ }
178
+ catch (error) {
179
+ throw unreadable(file, error);
180
+ }
157
181
  const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
158
- return {
159
- bytes,
160
- description: {
161
- bytes: bytes.length,
162
- sha256: createHash('sha256').update(bytes).digest('hex'),
163
- ...(mediaType === undefined ? {} : { media_type: mediaType }),
164
- },
165
- };
182
+ return { bytes, sha256: digest.digest('hex'), ...(mediaType === undefined ? {} : { media_type: mediaType }) };
183
+ }
184
+ async function finished(upload, room, partSha256) {
185
+ const text = await called('upload', { upload, room_ref: room, part_sha256: partSha256 });
186
+ const answer = parse(text);
187
+ return answer !== null && 'upload' in answer ? answer : { text };
166
188
  }
167
189
  async function upload({ file, ...destination }) {
168
190
  if (typeof file !== 'string' || file === '')
169
191
  throw new Error('upload needs "file", a local file');
170
- const { bytes, description } = await described(file);
192
+ const description = await described(file);
171
193
  const answer = await called('upload', { ...destination, ...description });
172
194
  const declared = parse(answer);
173
- if (declared.upload === undefined)
195
+ if (declared?.upload === undefined)
174
196
  return answer;
175
- const written = JSON.parse(await sent(declared.upload.url, bytes, declared.upload.operation));
176
- return stringify(written, { lineWidth: 0 }).trimEnd();
177
- }
178
- async function appendRecord(args) {
179
- const held = new Map();
180
- const attachments = Array.isArray(args.attachments)
181
- ? await Promise.all(args.attachments.map(async (attachment) => {
182
- if (typeof attachment !== 'string')
183
- return attachment;
184
- const { bytes, description } = await described(attachment);
185
- held.set(description.sha256, bytes);
186
- return { name: basename(attachment), ...description };
187
- }))
188
- : args.attachments;
189
- const record = { ...args, attachments };
190
- for (;;) {
191
- const answer = await called('append_record', record);
192
- const { upload } = parse(answer);
193
- if (upload === undefined)
194
- return answer;
195
- for (const grant of upload)
196
- await sent(grant.url, held.get(grant.sha256), grant.operation);
197
- }
197
+ const accepted = await uploaded(declared.upload, {
198
+ name: file,
199
+ bytes: description.bytes,
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),
204
+ });
205
+ return accepted.text;
198
206
  }
199
207
  function unexpected(argument, usage) {
200
208
  return new Error(`unexpected argument ${argument}; ${usage}`);
@@ -220,7 +228,7 @@ async function push(args) {
220
228
  throw new Error('--owner is not for Agent conversations; run house push without it');
221
229
  if (!owner && socketPath === undefined)
222
230
  throw new Error('outside an Agent conversation, house push needs --owner');
223
- const pushed = JSON.parse(await bridged('/git/push', { cwd: process.cwd(), commit }, undefined, owner ? ownerSocket() : socketPath));
231
+ const pushed = JSON.parse(await bridged('/git/push', { cwd: process.cwd(), commit }, owner ? ownerSocket() : socketPath));
224
232
  if (pushed.refused)
225
233
  throw new Error(pushed.text);
226
234
  return pushed.text;
@@ -228,7 +236,7 @@ async function push(args) {
228
236
  async function fetched(args) {
229
237
  if (accepted(args, ['--help'], 0, FETCH_USAGE).flags.has('--help'))
230
238
  return FETCH_USAGE;
231
- const answer = JSON.parse(await bridged('/git/fetch', {}, undefined, socketPath ?? ownerSocket()));
239
+ const answer = JSON.parse(await bridged('/git/fetch', {}, socketPath ?? ownerSocket()));
232
240
  if (answer.refused)
233
241
  throw new Error(answer.text);
234
242
  return answer.text;
@@ -263,7 +271,7 @@ async function house(args) {
263
271
  return [...(tool.description === undefined ? [] : [tool.description]), JSON.stringify(tool.inputSchema)].join('\n');
264
272
  }
265
273
  const parsed = argumentsOf(argument);
266
- return verb === 'upload' ? upload(parsed) : verb === 'append_record' ? appendRecord(parsed) : called(verb, parsed);
274
+ return verb === 'upload' ? upload(parsed) : called(verb, parsed);
267
275
  }
268
276
  async function called(name, args) {
269
277
  const result = await mcp('tools/call', { name, arguments: args });
@@ -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 < 14)) {
4
- process.stderr.write(`house: the Kit needs Node 24.14.0 or newer; this is ${process.versions.node}\n`);
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 {
@@ -1,12 +1,32 @@
1
+ import { agentInstructions } from '@agentshouse/skills';
1
2
  import { HOUSE_REF } from './git.js';
2
- import { readHowWeWork } from './skills.js';
3
- const SOURCE_LINE = "From House:";
4
- const HOUSE_LINE = "Work with House through the `house` CLI: run `house --help`, and `house find_command` for House's guides and formats.";
5
- const FILE_LINE = 'To give the User a file, upload it with `house upload` and link it; for an App, a Site or a website, follow `house find_command \'{"query":"app"}\'`. Never start a server.';
6
- const READ_LINE = "Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`.";
7
- const WAIT_LINE = 'To get what another Agent will write into a Room, `create_trigger` on where it will land with a `conversation` action, then end your turn: this conversation continues when it lands.';
8
- const COPIES_LINE = `House Local copies; before work run \`house fetch\` and rebase onto \`${HOUSE_REF}\`; after it commit, then \`house push\`:`;
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 = [SOURCE_LINE, HOUSE_LINE, FILE_LINE, READ_LINE, WAIT_LINE, ...(copies.length === 0 ? [] : [COPIES_LINE, ...copies])];
11
- return [lines.join('\n'), base, (await readHowWeWork(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
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([
@@ -11,7 +11,7 @@ const COMMANDS = new Map([
11
11
  'logout',
12
12
  {
13
13
  usage: 'usage: kit logout',
14
- about: "disconnects this computer, stops House Kit and ends your own agent's connection",
14
+ about: "disconnects this computer, stops the Kit process and ends your own agent's connection",
15
15
  operands: 0,
16
16
  run: logout,
17
17
  },
@@ -20,7 +20,7 @@ const COMMANDS = new Map([
20
20
  'resident',
21
21
  {
22
22
  usage: 'usage: kit resident',
23
- about: "keeps this Environment connected; House Kit's service runs it",
23
+ about: "keeps this Environment connected; Kit's service runs it",
24
24
  operands: 0,
25
25
  run: resident,
26
26
  },
@@ -36,7 +36,7 @@ const COMMANDS = new Map([
36
36
  ],
37
37
  ]);
38
38
  const [command, ...argv] = process.argv.slice(2);
39
- setGlobalProxyFromEnv();
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 < 14)) {
4
- process.stderr.write(`kit: the Kit needs Node 24.14.0 or newer; this is ${process.versions.node}\n`);
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, label: hostname() });
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,8 +1,11 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { KIT_VERSION } from './clis.js';
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';
7
+ export class Unsettled extends Error {
8
+ }
6
9
  export function rpc(method, params) {
7
10
  return { jsonrpc: '2.0', id: randomUUID(), method, params };
8
11
  }
@@ -13,7 +16,7 @@ export function mcpBody(message, operation) {
13
16
  ...message.params,
14
17
  _meta: {
15
18
  'io.modelcontextprotocol/protocolVersion': MCP_PROTOCOL_VERSION,
16
- 'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_VERSION },
19
+ 'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_RELEASE },
17
20
  'io.modelcontextprotocol/clientCapabilities': {},
18
21
  ...(operation === null ? {} : { 'agents.house/agent-operation': operation }),
19
22
  },
@@ -31,6 +34,7 @@ export async function callHouse(origin, credential, message, operation = null, s
31
34
  'mcp-protocol-version': MCP_PROTOCOL_VERSION,
32
35
  'mcp-method': message.method,
33
36
  ...(tool === null ? {} : { 'mcp-name': tool }),
37
+ [KIT_WIRE_HEADER]: String(KIT_WIRE),
34
38
  },
35
39
  body: mcpBody(message, operation),
36
40
  signal,
package/dist/placement.js CHANGED
@@ -1,26 +1,9 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { run } from './clis.js';
2
+ import { join, resolve } from 'node:path';
4
3
  import { kitHome } from './home.js';
5
- export function service() {
6
- if (process.platform !== 'linux' || !process.env.INVOCATION_ID)
7
- return null;
8
- return process.env.MANAGERPID ? 'user' : 'system';
9
- }
10
- export function throughSudo() {
11
- return service() === 'system' && process.getuid() !== 0;
12
- }
13
- export async function updatesItself() {
14
- if (service() === null)
15
- return false;
16
- if (!throughSudo())
17
- return true;
18
- // sudo answers at once, so ten seconds only bounds a stuck sudo.
19
- return (await run('sudo', ['-n', 'true'], 10_000)).status === 0;
20
- }
21
- export function installCommand() {
4
+ function recorded(name) {
22
5
  try {
23
- return readFileSync(join(kitHome(), 'install-command'), 'utf8').trim();
6
+ return readFileSync(join(kitHome(), name), 'utf8').trim();
24
7
  }
25
8
  catch (error) {
26
9
  if (error.code === 'ENOENT')
@@ -28,3 +11,21 @@ export function installCommand() {
28
11
  throw error;
29
12
  }
30
13
  }
14
+ export function installCommand() {
15
+ return recorded('install-command');
16
+ }
17
+ export function machine() {
18
+ const placed = recorded('placement');
19
+ if (placed === 'container' || placed === 'server' || placed === 'windows')
20
+ return placed;
21
+ return process.platform === 'darwin' ? 'macos' : 'linux';
22
+ }
23
+ export function placedPrefix() {
24
+ const prefix = process.env.HOUSE_KIT_PREFIX;
25
+ if (prefix === undefined || prefix === '')
26
+ throw new Error('this Kit process was not started by a launcher its installer placed');
27
+ return resolve(prefix);
28
+ }
29
+ export function releaseDirectory(release) {
30
+ return join(placedPrefix(), 'releases', release);
31
+ }
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();