@agentshouse/kit 0.2.1-c441d29eb63f4 → 0.2.1-c4613862fad84

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/dist/files.js CHANGED
@@ -9,6 +9,7 @@ import { retryDelay } from './api.js';
9
9
  import { Unsettled } from './mcp.js';
10
10
  import { relayed } from './relay.js';
11
11
  const STALLED_ROUNDS = 8;
12
+ const PART_CHECKSUM = 'x-amz-checksum-sha256';
12
13
  const LINK = /\]\((\/rooms\/[^/)\s]+\/[^)\s]+)\)/gu;
13
14
  export function linkedPaths(text) {
14
15
  return [...new Set(Array.from(text.matchAll(LINK), (link) => link[1]))];
@@ -32,17 +33,39 @@ function paused(milliseconds) {
32
33
  export function storeRefusal(act, status) {
33
34
  return new Error(`the ${act} answered ${status}; ${act} the file again`);
34
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
+ }
35
52
  export async function uploaded(declared, uploading) {
36
- let upload = declared;
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);
37
57
  let idle = 0;
38
58
  let stalled = 0;
39
- for (;;) {
59
+ while ('upload' in answer) {
60
+ const { id, parts } = answer.upload;
40
61
  let sent = 0;
41
62
  let failure = null;
42
- for (const { part, url } of upload.parts) {
43
- const start = (part - 1) * upload.part_bytes;
63
+ for (const { part, url } of parts ?? []) {
64
+ const start = (part - 1) * partBytes;
44
65
  try {
45
- await uploading.send(url, start, Math.min(start + upload.part_bytes, uploading.bytes));
66
+ await uploading.send(url, start, Math.min(start + partBytes, uploading.bytes), {
67
+ [PART_CHECKSUM]: digests[part - 1].toString('base64'),
68
+ });
46
69
  sent++;
47
70
  }
48
71
  catch (error) {
@@ -57,24 +80,17 @@ export async function uploaded(declared, uploading) {
57
80
  else {
58
81
  if (failure !== null && ++stalled >= STALLED_ROUNDS)
59
82
  throw failure;
60
- // A round that sent no part, its store unreachable or not yet started, is followed by the next finishing call on the jittered curve.
83
+ // A round that sent no part, its store unreachable, is followed by the next finishing call on the jittered curve.
61
84
  await paused(retryDelay(idle++));
62
85
  }
63
- let answer = await finishing(uploading, upload.id);
64
- while ('checking' in answer) {
65
- // 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.
66
- await paused(5_000);
67
- answer = await finishing(uploading, upload.id);
68
- }
69
- if (!('upload' in answer))
70
- return answer;
71
- upload = answer.upload;
86
+ answer = await finishing(uploading, id, partSha256);
72
87
  }
88
+ return answer;
73
89
  }
74
- async function finishing(uploading, upload) {
90
+ async function finishing(uploading, upload, partSha256) {
75
91
  for (let unsettled = 1;; unsettled++) {
76
92
  try {
77
- return await uploading.finish(upload);
93
+ return await uploading.finish(upload, partSha256);
78
94
  }
79
95
  catch (error) {
80
96
  if (!(error instanceof Unsettled) || unsettled === STALLED_ROUNDS)
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: { ...withoutProxy(), ...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/home.js CHANGED
@@ -8,7 +8,6 @@ const ENROLMENT = 'credential.json';
8
8
  const DISCONNECTED = 'disconnected.json';
9
9
  const CONFIGURATION = 'kit.json';
10
10
  const OWN_AGENT = 'own-agent.json';
11
- const RESTART = 'restart';
12
11
  let defaulted;
13
12
  function writable(directory) {
14
13
  try {
@@ -132,12 +131,6 @@ export async function forgetRejected(rejected) {
132
131
  export function enrolled() {
133
132
  return existsSync(join(kitHome(), ENROLMENT));
134
133
  }
135
- export function restartAsked() {
136
- return existsSync(join(kitHome(), RESTART));
137
- }
138
- export function forgetRestart() {
139
- return rm(join(kitHome(), RESTART), { force: true });
140
- }
141
134
  export function readOwnAgent() {
142
135
  return readHome(OWN_AGENT);
143
136
  }
package/dist/house-cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { createReadStream } from 'node:fs';
3
- import { request, setGlobalProxyFromEnv } from 'node:http';
3
+ import { request } from 'node:http';
4
4
  import { request as httpsRequest } from 'node:https';
5
5
  import { extname, join, resolve } from 'node:path';
6
6
  import { Readable } from 'node:stream';
@@ -10,7 +10,7 @@ import { HOUSE_REF } from './git.js';
10
10
  import { ownerSocket, readOwnAgent } from './home.js';
11
11
  import { callHouse, rpc, UNREACHABLE, Unsettled } from './mcp.js';
12
12
  import { refusalOf, retryable } from './refusals.js';
13
- import { relayed, selectRelay } from './relay.js';
13
+ import { proxyUnlessRelayed, relayed, selectRelay } from './relay.js';
14
14
  import { parse } from 'yaml';
15
15
  const USAGE = "usage: house <tool> ['<arguments as JSON>']";
16
16
  const PUSH_USAGE = 'usage: house push [commit] [--owner]';
@@ -89,7 +89,6 @@ async function direct(message) {
89
89
  if (ownAgent === null)
90
90
  throw new Error(UNCONNECTED);
91
91
  selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
92
- setGlobalProxyFromEnv();
93
92
  const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
94
93
  throw new Unsettled(UNREACHABLE);
95
94
  });
@@ -120,7 +119,7 @@ function opened(url, method, headers, body) {
120
119
  const local = socketPath !== undefined;
121
120
  const target = local ? null : relayed(url);
122
121
  if (!local)
123
- setGlobalProxyFromEnv();
122
+ proxyUnlessRelayed();
124
123
  const sent = target === null
125
124
  ? request({ socketPath, path: url, method, headers }, answered)
126
125
  : (target.protocol === 'https:' ? httpsRequest : request)(target, { method, headers }, answered);
@@ -131,8 +130,19 @@ function opened(url, method, headers, body) {
131
130
  pipeline(body, sent).catch(() => undefined);
132
131
  });
133
132
  }
134
- async function sentPart(url, file, start, end) {
135
- const answer = await opened(url, 'PUT', { 'content-length': String(end - start) }, end === start ? undefined : createReadStream(file, { start, end: end - 1 }));
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) {
137
+ try {
138
+ yield* ranged(file, start, end) ?? [];
139
+ }
140
+ catch (error) {
141
+ throw unreadable(file, error);
142
+ }
143
+ }
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));
136
146
  await answer.toArray();
137
147
  if (answer.statusCode !== 200)
138
148
  throw storeRefusal('upload', answer.statusCode);
@@ -171,10 +181,10 @@ async function described(file) {
171
181
  const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
172
182
  return { bytes, sha256: digest.digest('hex'), ...(mediaType === undefined ? {} : { media_type: mediaType }) };
173
183
  }
174
- async function finished(upload, room) {
175
- 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 });
176
186
  const answer = parse(text);
177
- return answer !== null && ('upload' in answer || 'checking' in answer) ? answer : { text };
187
+ return answer !== null && 'upload' in answer ? answer : { text };
178
188
  }
179
189
  async function upload({ file, ...destination }) {
180
190
  if (typeof file !== 'string' || file === '')
@@ -185,9 +195,12 @@ async function upload({ file, ...destination }) {
185
195
  if (declared?.upload === undefined)
186
196
  return answer;
187
197
  const accepted = await uploaded(declared.upload, {
198
+ name: file,
188
199
  bytes: description.bytes,
189
- send: (url, start, end) => sentPart(url, file, start, end),
190
- finish: (id) => finished(id, destination.room_ref),
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),
191
204
  });
192
205
  return accepted.text;
193
206
  }
@@ -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,6 +1,6 @@
1
1
  import { agentInstructions } from '@agentshouse/skills';
2
2
  import { HOUSE_REF } from './git.js';
3
- const HOW_WE_WORK = '/rooms/private/how-we-work.md';
3
+ const OWNER_RULES = '/rooms/private/owner-rules.md';
4
4
  const UNREADABLE = /^(?:\S+: )?(?:path_not_found|room_not_found|operation_denied)\b/;
5
5
  const FAILED = /^exit: [1-9]\d*$/;
6
6
  const CUT = /^stderr: shell: output_cut (\d+) of \d+ bytes; continue with: (.+)$/;
@@ -8,9 +8,9 @@ function cutOf(lines) {
8
8
  const cut = CUT.exec(lines[0] ?? '');
9
9
  return cut !== null && Buffer.byteLength(lines.slice(1).join('\n')) === Number(cut[1]) ? cut : null;
10
10
  }
11
- async function readHowWeWork(bridge) {
11
+ async function readOwnerRules(bridge) {
12
12
  let document = '';
13
- for (let command = `cat ${HOW_WE_WORK}`; command !== undefined;) {
13
+ for (let command = `cat ${OWNER_RULES}`; command !== undefined;) {
14
14
  const read = await bridge.tool('shell', { command });
15
15
  const text = read.content.map((content) => content.text).join('\n');
16
16
  const lines = text.split('\n');
@@ -28,5 +28,5 @@ async function readHowWeWork(bridge) {
28
28
  }
29
29
  export async function instructions(bridge, base, copies) {
30
30
  const lines = agentInstructions(HOUSE_REF, copies);
31
- return [lines.join('\n'), base, (await readHowWeWork(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
31
+ return [lines.join('\n'), base, (await readOwnerRules(bridge)).replace(/\n$/, '')].filter((part) => part !== '').join('\n\n');
32
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
- 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
@@ -10,6 +10,7 @@ import { refusalOf } from './refusals.js';
10
10
  import { selectRelay } from './relay.js';
11
11
  import { startService } from './service.js';
12
12
  import { placeSkillSet } from './skills.js';
13
+ import { endUnserved } from './wire.js';
13
14
  export const DEFAULT_HOUSE = 'https://agents.house';
14
15
  const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
15
16
  // Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
@@ -25,6 +26,7 @@ export async function accepted(house, credential) {
25
26
  return true;
26
27
  if (checked.status === 401)
27
28
  return false;
29
+ endUnserved(checked.text);
28
30
  throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
29
31
  }
30
32
  const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private]
@@ -119,7 +121,7 @@ export async function login(argv) {
119
121
  open(started.start_url);
120
122
  exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
121
123
  }
122
- await placeSkillSet();
124
+ await placeSkillSet(house);
123
125
  await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
124
126
  const relayRecord = relay === undefined ? {} : { relay };
125
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/placement.js CHANGED
@@ -1,23 +1,6 @@
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
4
  function recorded(name) {
22
5
  try {
23
6
  return readFileSync(join(kitHome(), name), 'utf8').trim();
@@ -37,3 +20,12 @@ export function machine() {
37
20
  return placed;
38
21
  return process.platform === 'darwin' ? 'macos' : 'linux';
39
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();
package/dist/resident.js CHANGED
@@ -10,16 +10,17 @@ import { house } from './api.js';
10
10
  import { received } from './bridge.js';
11
11
  import { CONVERSATION_METHODS, Conversations } from './conversations.js';
12
12
  import { LocalCopies } from './copies.js';
13
- import { restart } from './end.js';
14
- import { agentBase, enrolled, forgetRestart, kitHome, ownerLock, ownerSocket, restartAsked } from './home.js';
13
+ import { restart, unknownMethod } from './end.js';
14
+ import { agentBase, enrolled, kitHome, ownerLock, ownerSocket, readEnrolment } from './home.js';
15
15
  import { Calls } from './calls.js';
16
16
  import { killDescendants } from './scope.js';
17
+ import { removeInputs, Scripts } from './scripts.js';
17
18
  import { quoted } from './shell.js';
18
19
  import { SignIns } from './sign-in.js';
19
20
  import { placeSkillSet } from './skills.js';
20
21
  import { holdConnection } from './connection.js';
21
22
  import { KitUpdate } from './update.js';
22
- import { WebWizards } from './web-wizard.js';
23
+ import { takeUnservedRelease } from './wire.js';
23
24
  import { codexLauncher } from './work.js';
24
25
  function report(error) {
25
26
  process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
@@ -110,14 +111,13 @@ function connected() {
110
111
  }
111
112
  async function started() {
112
113
  await connected();
113
- await forgetRestart();
114
114
  watch(kitHome(), () => {
115
115
  if (enrolled())
116
116
  return;
117
117
  killDescendants();
118
118
  process.exit(1);
119
119
  });
120
- await placeSkillSet();
120
+ await placeSkillSet((await readEnrolment()).house);
121
121
  await mkdir(agentBase(), { recursive: true });
122
122
  await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
123
123
  await installShim(codexLauncher(), 'codex-main');
@@ -140,8 +140,8 @@ export async function resident() {
140
140
  if (!idle()) {
141
141
  reported = false;
142
142
  }
143
- else if (restartAsked() && conversations.quiet()) {
144
- restart('restarting for an updated CLI');
143
+ else if (update.placedRelease() !== null && conversations.quiet()) {
144
+ restart(`Kit ${update.placedRelease()} is placed; its supervisor starts it`);
145
145
  }
146
146
  else if (!reported) {
147
147
  reported = true;
@@ -159,34 +159,38 @@ export async function resident() {
159
159
  });
160
160
  const signIns = new SignIns(house, agents, calls);
161
161
  const update = new KitUpdate(calls);
162
- const wizards = new WebWizards(calls, (notification) => connection?.send(notification) ?? false);
162
+ if (process.env.HOUSE_KIT_SANDBOX !== '1')
163
+ takeUnservedRelease((release) => logged(update.takeRefused(release)));
164
+ removeInputs();
165
+ const scripts = new Scripts(calls, (notification) => connection?.send(notification) ?? false);
163
166
  for (const signal of ['SIGTERM', 'SIGINT']) {
164
167
  process.once(signal, () => {
165
168
  killDescendants();
169
+ removeInputs();
166
170
  process.kill(process.pid, signal);
167
171
  });
168
172
  }
169
173
  await house.deliver('/kit/restarted', {}).catch(report);
170
- await update.resumed().catch(report);
171
174
  logged(agents.refresh());
175
+ agents.keepCurrent();
172
176
  connection = holdConnection({
173
177
  opened: () => {
174
178
  conversations.opened();
175
179
  changed();
176
180
  logged(copies.connected());
177
181
  },
178
- closed: () => wizards.disconnected(),
182
+ closed: () => scripts.disconnected(),
179
183
  call: (call) => {
180
184
  if (call.kind === 'sign_in')
181
185
  signIns.start(call);
182
186
  else if (call.kind === 'update')
183
- update.start(call);
184
- else if (call.kind === 'run_wizard')
185
- wizards.start(call);
187
+ update.take(call);
188
+ else if (call.kind === 'run_wizard' || call.kind === 'run_function')
189
+ scripts.start(call);
186
190
  else if (CONVERSATION_METHODS.has(call.kind))
187
191
  conversations.call(call);
188
192
  else {
189
- const refused = { refused: `This Kit does not know ${call.kind}; update the Kit` };
193
+ const refused = { refused: unknownMethod(call.kind) };
190
194
  void calls.carry(call.input_id, async () => ({ result: refused }));
191
195
  }
192
196
  },
@@ -197,7 +201,7 @@ export async function resident() {
197
201
  logged(copies.changed(notification.rooms));
198
202
  if (notification.type === 'work_available' && notification.subject === 'agents')
199
203
  logged(agents.refresh());
200
- wizards.notified(notification);
204
+ scripts.notified(notification);
201
205
  },
202
206
  });
203
207
  }
package/dist/scope.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from 'node:child_process';
2
- import { mkdirSync, readdirSync, readFileSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
2
+ import { mkdirSync, readdirSync, readFileSync, realpathSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  const LISTING_BYTES = 1 << 28;
5
5
  const CGROUPS = '/sys/fs/cgroup';
@@ -122,15 +122,23 @@ function members(group) {
122
122
  }
123
123
  return pids;
124
124
  }
125
+ function besideCli(cli) {
126
+ try {
127
+ return `${realpathSync(cli)}-`;
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ }
125
133
  export class Scope {
126
134
  marker;
127
- cli;
135
+ helpers;
128
136
  group;
129
137
  session = null;
130
138
  recorded = null;
131
139
  constructor(marker, name, cli) {
132
140
  this.marker = marker;
133
- this.cli = cli;
141
+ this.helpers = besideCli(cli);
134
142
  const own = ownGroup();
135
143
  this.group = own === null ? null : join(own, name);
136
144
  try {
@@ -168,7 +176,7 @@ export class Scope {
168
176
  return true;
169
177
  if (this.recorded === null)
170
178
  return found.size > 0;
171
- return [...found].some(([pid, { session, command }]) => session !== this.session || !(this.recorded.has(pid) || command.startsWith(`${this.cli}-`)));
179
+ return [...found].some(([pid, { session, command }]) => session !== this.session || !(this.recorded.has(pid) || (this.helpers !== null && command.startsWith(this.helpers))));
172
180
  }
173
181
  empty() {
174
182
  return this.found()?.size === 0;