@agentshouse/kit 0.2.1-c3895fcff473b → 0.2.1-c3cf4b145dcda

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/clis.js CHANGED
@@ -4,37 +4,12 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
4
  import { homedir, userInfo } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { kitHome } from './home.js';
7
- const RUNNING_JOB = new Set(['running', 'paused']);
8
7
  async function allowHouseInCodex() {
9
8
  const rules = join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'rules');
10
9
  await mkdir(rules, { recursive: true });
11
10
  await writeFile(join(rules, 'house.rules'), 'prefix_rule(pattern = ["house"], decision = "allow")\n');
12
11
  }
13
12
  async function nothing() { }
14
- function threadStatus({ method, params }) {
15
- if (method !== 'session/update' || params?.update?.sessionUpdate !== 'session_info_update')
16
- return undefined;
17
- return params.update._meta?.codex?.threadStatus?.type;
18
- }
19
- function asyncTask({ method, params }) {
20
- const update = params?.update;
21
- if (method !== 'session/update' || update?.asyncTaskId === undefined)
22
- return null;
23
- if (update.sessionUpdate === 'async_task_spawned')
24
- return { id: update.asyncTaskId, running: true };
25
- if (update.sessionUpdate === 'async_task_state_update' && !RUNNING_JOB.has(update.state ?? '')) {
26
- return { id: update.asyncTaskId, running: false };
27
- }
28
- return null;
29
- }
30
- function airPhase({ _meta }) {
31
- const phase = _meta?.jetbrains?.air?.phase;
32
- if (phase === 'commentary')
33
- return 'note';
34
- if (phase === 'final_answer')
35
- return 'answer';
36
- return null;
37
- }
38
13
  export const CLIS = {
39
14
  'codex-acp': {
40
15
  bin: 'codex',
@@ -47,11 +22,7 @@ export const CLIS = {
47
22
  allowHouse: allowHouseInCodex,
48
23
  signedIn: { command: ['login', 'status'] },
49
24
  login: { args: ['login', '--device-auth'], code: 'show' },
50
- queues: false,
51
- turnStarted: (notice) => threadStatus(notice) === 'active',
52
- turnEnded: (notice) => threadStatus(notice) === 'idle',
53
- job: asyncTask,
54
- phase: airPhase,
25
+ air: ['asyncTasks', 'nativeSubagentSessions'],
55
26
  },
56
27
  'claude-agent-acp': {
57
28
  bin: 'claude',
@@ -65,17 +36,16 @@ export const CLIS = {
65
36
  },
66
37
  args: [],
67
38
  fullAccess: 'bypassPermissions',
68
- sessionMeta: { claudeCode: { options: { allowedTools: ['Bash(house:*)'] } } },
39
+ sessionMeta: {
40
+ claudeCode: {
41
+ options: { allowedTools: ['Bash(house:*)'] },
42
+ emitRawSDKMessages: ['background_tasks_changed', 'session_state_changed', 'task_started', 'task_notification'].map((subtype) => ({ type: 'system', subtype })),
43
+ },
44
+ },
69
45
  allowHouse: nothing,
70
46
  signedIn: { command: ['auth', 'status'] },
71
47
  login: { args: ['auth', 'login', '--claudeai'], code: 'collect', rejected: 'Invalid code' },
72
- queues: false,
73
- turnStarted: () => false,
74
- turnEnded: ({ method, params }) => method === 'session/update' &&
75
- params?.update?.sessionUpdate === 'usage_update' &&
76
- params.update._meta?.['_claude/origin'] !== undefined,
77
- job: asyncTask,
78
- phase: () => null,
48
+ air: ['asyncTasks', 'sessionFailure'],
79
49
  },
80
50
  'grok-build': {
81
51
  bin: 'grok',
@@ -88,24 +58,16 @@ export const CLIS = {
88
58
  allowHouse: nothing,
89
59
  signedIn: { initializeMeta: 'defaultAuthMethodId' },
90
60
  login: { args: ['login', '--device-auth'], code: 'show' },
91
- queues: true,
92
- turnStarted: ({ method, params }) => method === '_x.ai/queue/changed' && params?.runningPromptId !== undefined,
93
- turnEnded: ({ method, params }) => method === '_x.ai/session_notification' && params?.update?.sessionUpdate === 'turn_completed',
94
- job: ({ method, params }) => {
95
- if (method === '_x.ai/task_backgrounded')
96
- return { id: params.update.task_id, running: true };
97
- if (method === '_x.ai/task_completed')
98
- return { id: params.update.task_snapshot.task_id, running: false };
99
- return null;
100
- },
101
- phase: () => null,
61
+ air: ['asyncTasks'],
102
62
  },
103
63
  };
104
64
  const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
105
65
  export const KIT_VERSION = KIT_PACKAGE.version;
106
66
  const PROXY = /^https?_proxy$/i;
107
67
  const VERSION = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?/;
68
+ // An install still running after fifteen minutes is ended and reported as failed, so a hung download cannot hold its CLI's report.
108
69
  const INSTALL_MS = 15 * 60_000;
70
+ // A login shell or a version read is ended after half a minute, so a profile that waits for input cannot stall the report.
109
71
  const READ_MS = 30_000;
110
72
  const CAUSE_CHARACTERS = 4000;
111
73
  function prefix(kind) {
@@ -133,8 +95,22 @@ export function run(command, args, timeoutMs, detached = false, signal) {
133
95
  timeout: timeoutMs,
134
96
  env: withoutProxy(),
135
97
  detached,
136
- signal,
98
+ signal: detached ? undefined : signal,
137
99
  });
100
+ const stop = () => {
101
+ try {
102
+ process.kill(-child.pid, 'SIGKILL');
103
+ }
104
+ catch { }
105
+ };
106
+ if (detached && signal?.aborted)
107
+ stop();
108
+ if (detached)
109
+ signal?.addEventListener('abort', stop, { once: true });
110
+ const ended = (ran) => {
111
+ signal?.removeEventListener('abort', stop);
112
+ resolve(ran);
113
+ };
138
114
  let stdout = '';
139
115
  let output = '';
140
116
  child.stdout.setEncoding('utf8').on('data', (chunk) => {
@@ -144,25 +120,25 @@ export function run(command, args, timeoutMs, detached = false, signal) {
144
120
  child.stderr.setEncoding('utf8').on('data', (chunk) => {
145
121
  output += chunk;
146
122
  });
147
- child.on('error', (error) => resolve({ status: null, stdout, output: error.message }));
148
- child.on('close', (status) => resolve({ status, stdout, output }));
123
+ child.on('error', (error) => ended({ status: null, stdout, output: error.message }));
124
+ child.on('close', (status) => ended({ status, stdout, output }));
149
125
  });
150
126
  }
151
127
  export function failureOf(ran, program) {
152
128
  return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
153
129
  }
154
- export async function installAdapter(kind) {
130
+ export async function installAdapter(kind, signal) {
155
131
  const adapter = CLIS[kind].adapter;
156
132
  if (adapter === null)
157
133
  return null;
158
134
  const release = KIT_PACKAGE.peerDependencies[adapter.package];
159
135
  if ((await installedAdapter(kind)) === release)
160
136
  return null;
161
- const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS);
137
+ const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
162
138
  return ran.status === 0 ? null : failureOf(ran, 'npm');
163
139
  }
164
- export async function installCli(kind) {
165
- const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true);
140
+ export async function installCli(kind, signal) {
141
+ const ran = await run('bash', ['-o', 'pipefail', '-c', CLIS[kind].install], INSTALL_MS, true, signal);
166
142
  return ran.status === 0 ? null : failureOf(ran, 'the install');
167
143
  }
168
144
  function loginShell() {
@@ -171,11 +147,11 @@ function loginShell() {
171
147
  const FOUND = 'house-kit-cli ';
172
148
  const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
173
149
  const USER_BIN = 'case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) PATH="$PATH:$HOME/.local/bin" ;; esac; export PATH';
174
- function inLoginShell(script, argument = '') {
175
- return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], READ_MS, true);
150
+ function inLoginShell(script, argument = '', signal) {
151
+ return run(loginShell(), ['-l', '-i', '-c', `exec /bin/sh -c '${USER_BIN}; ${script}' sh ${argument}`], READ_MS, true, signal);
176
152
  }
177
- export async function locate(kind) {
178
- const ran = await inLoginShell(LOOKUP, CLIS[kind].bin);
153
+ export async function locate(kind, signal) {
154
+ const ran = await inLoginShell(LOOKUP, CLIS[kind].bin, signal);
179
155
  return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
180
156
  }
181
157
  export async function loginPath() {
@@ -183,8 +159,8 @@ export async function loginPath() {
183
159
  const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
184
160
  return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
185
161
  }
186
- export async function versionOf(path) {
187
- return VERSION.exec((await run(path, ['--version'], READ_MS)).stdout)?.[0] ?? null;
162
+ export async function versionOf(path, signal) {
163
+ return VERSION.exec((await run(path, ['--version'], READ_MS, false, signal)).stdout)?.[0] ?? null;
188
164
  }
189
165
  function numbers(version) {
190
166
  return version.split(/[.-]/, 3).map(Number);