@agentshouse/kit 0.2.1-cb6679d048e60 → 0.2.1-cca9e1f43de44

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
@@ -1,34 +1,15 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { readFileSync } from 'node:fs';
3
- import { readFile } from 'node:fs/promises';
4
- import { userInfo } from 'node:os';
3
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
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
- function threadStatus({ method, params }) {
9
- if (method !== 'session/update' || params?.update?.sessionUpdate !== 'session_info_update')
10
- return undefined;
11
- return params.update._meta?.codex?.threadStatus?.type;
12
- }
13
- function asyncTask({ method, params }) {
14
- const update = params?.update;
15
- if (method !== 'session/update' || update?.asyncTaskId === undefined)
16
- return null;
17
- if (update.sessionUpdate === 'async_task_spawned')
18
- return { id: update.asyncTaskId, running: true };
19
- if (update.sessionUpdate === 'async_task_state_update' && !RUNNING_JOB.has(update.state ?? '')) {
20
- return { id: update.asyncTaskId, running: false };
21
- }
22
- return null;
23
- }
24
- function airPhase({ _meta }) {
25
- const phase = _meta?.jetbrains?.air?.phase;
26
- if (phase === 'commentary')
27
- return 'note';
28
- if (phase === 'final_answer')
29
- return 'answer';
30
- return null;
7
+ async function allowHouseInCodex() {
8
+ const rules = join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'rules');
9
+ await mkdir(rules, { recursive: true });
10
+ await writeFile(join(rules, 'house.rules'), 'prefix_rule(pattern = ["house"], decision = "allow")\n');
31
11
  }
12
+ async function nothing() { }
32
13
  export const CLIS = {
33
14
  'codex-acp': {
34
15
  bin: 'codex',
@@ -36,13 +17,12 @@ export const CLIS = {
36
17
  install: 'curl -fsSL https://chatgpt.com/codex/install.sh | sh',
37
18
  adapter: { package: '@agentclientprotocol/codex-acp', bin: 'codex-acp', executable: 'CODEX_PATH', env: {} },
38
19
  args: [],
20
+ fullAccess: 'agent-full-access',
21
+ sessionMeta: {},
22
+ allowHouse: allowHouseInCodex,
39
23
  signedIn: { command: ['login', 'status'] },
40
24
  login: { args: ['login', '--device-auth'], code: 'show' },
41
- queues: false,
42
- turnStarted: (notice) => threadStatus(notice) === 'active',
43
- turnEnded: (notice) => threadStatus(notice) === 'idle',
44
- job: asyncTask,
45
- phase: airPhase,
25
+ air: ['asyncTasks', 'nativeSubagentSessions'],
46
26
  },
47
27
  'claude-agent-acp': {
48
28
  bin: 'claude',
@@ -52,45 +32,42 @@ export const CLIS = {
52
32
  package: '@agentclientprotocol/claude-agent-acp',
53
33
  bin: 'claude-agent-acp',
54
34
  executable: 'CLAUDE_CODE_EXECUTABLE',
55
- env: { CLAUDE_CODE_ENTRYPOINT: 'claude-agent-acp' },
35
+ env: { CLAUDE_CODE_ENTRYPOINT: 'claude-agent-acp', IS_SANDBOX: '1' },
56
36
  },
57
37
  args: [],
38
+ fullAccess: 'bypassPermissions',
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
+ },
45
+ allowHouse: nothing,
58
46
  signedIn: { command: ['auth', 'status'] },
59
47
  login: { args: ['auth', 'login', '--claudeai'], code: 'collect', rejected: 'Invalid code' },
60
- queues: false,
61
- turnStarted: () => false,
62
- turnEnded: ({ method, params }) => method === 'session/update' &&
63
- params?.update?.sessionUpdate === 'usage_update' &&
64
- params.update._meta?.['_claude/origin'] !== undefined,
65
- job: asyncTask,
66
- phase: () => null,
48
+ air: ['asyncTasks', 'sessionFailure'],
67
49
  },
68
50
  'grok-build': {
69
51
  bin: 'grok',
70
52
  minimum: '1.0.46',
71
53
  install: 'curl -fsSL https://x.ai/cli/install.sh | bash',
72
54
  adapter: null,
73
- args: ['agent', '--no-leader', 'stdio'],
55
+ args: ['agent', '--always-approve', '--no-leader', 'stdio'],
56
+ fullAccess: null,
57
+ sessionMeta: {},
58
+ allowHouse: nothing,
74
59
  signedIn: { initializeMeta: 'defaultAuthMethodId' },
75
60
  login: { args: ['login', '--device-auth'], code: 'show' },
76
- queues: true,
77
- turnStarted: ({ method, params }) => method === '_x.ai/queue/changed' && params?.runningPromptId !== undefined,
78
- turnEnded: ({ method, params }) => method === '_x.ai/session_notification' && params?.update?.sessionUpdate === 'turn_completed',
79
- job: ({ method, params }) => {
80
- if (method === '_x.ai/task_backgrounded')
81
- return { id: params.update.task_id, running: true };
82
- if (method === '_x.ai/task_completed')
83
- return { id: params.update.task_snapshot.task_id, running: false };
84
- return null;
85
- },
86
- phase: () => null,
61
+ air: ['asyncTasks'],
87
62
  },
88
63
  };
89
64
  const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
90
65
  export const KIT_VERSION = KIT_PACKAGE.version;
91
66
  const PROXY = /^https?_proxy$/i;
92
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.
93
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.
94
71
  const READ_MS = 30_000;
95
72
  const CAUSE_CHARACTERS = 4000;
96
73
  function prefix(kind) {
@@ -118,8 +95,22 @@ export function run(command, args, timeoutMs, detached = false, signal) {
118
95
  timeout: timeoutMs,
119
96
  env: withoutProxy(),
120
97
  detached,
121
- signal,
98
+ signal: detached ? undefined : signal,
122
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
+ };
123
114
  let stdout = '';
124
115
  let output = '';
125
116
  child.stdout.setEncoding('utf8').on('data', (chunk) => {
@@ -129,25 +120,25 @@ export function run(command, args, timeoutMs, detached = false, signal) {
129
120
  child.stderr.setEncoding('utf8').on('data', (chunk) => {
130
121
  output += chunk;
131
122
  });
132
- child.on('error', (error) => resolve({ status: null, stdout, output: error.message }));
133
- 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 }));
134
125
  });
135
126
  }
136
127
  export function failureOf(ran, program) {
137
128
  return ran.output.trim().slice(-CAUSE_CHARACTERS) || `${program} exited with ${ran.status ?? 'a signal'}`;
138
129
  }
139
- export async function installAdapter(kind) {
130
+ export async function installAdapter(kind, signal) {
140
131
  const adapter = CLIS[kind].adapter;
141
132
  if (adapter === null)
142
133
  return null;
143
134
  const release = KIT_PACKAGE.peerDependencies[adapter.package];
144
135
  if ((await installedAdapter(kind)) === release)
145
136
  return null;
146
- 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);
147
138
  return ran.status === 0 ? null : failureOf(ran, 'npm');
148
139
  }
149
- export async function installCli(kind) {
150
- 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);
151
142
  return ran.status === 0 ? null : failureOf(ran, 'the install');
152
143
  }
153
144
  function loginShell() {
@@ -156,11 +147,11 @@ function loginShell() {
156
147
  const FOUND = 'house-kit-cli ';
157
148
  const LOOKUP = `found=$(command -v "$1") || exit 0; case $found in /*) ;; *) found=$(pwd -P)/$found ;; esac; printf "${FOUND}%s\\n" "$found"`;
158
149
  const USER_BIN = 'case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) PATH="$PATH:$HOME/.local/bin" ;; esac; export PATH';
159
- function inLoginShell(script, argument = '') {
160
- 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);
161
152
  }
162
- export async function locate(kind) {
163
- 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);
164
155
  return ran.stdout.split('\n').findLast((line) => line.startsWith(FOUND))?.slice(FOUND.length) ?? null;
165
156
  }
166
157
  export async function loginPath() {
@@ -168,8 +159,8 @@ export async function loginPath() {
168
159
  const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
169
160
  return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
170
161
  }
171
- export async function versionOf(path) {
172
- 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;
173
164
  }
174
165
  function numbers(version) {
175
166
  return version.split(/[.-]/, 3).map(Number);