@byokit/herdr 0.4.1 → 0.7.0
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/CHANGELOG.md +20 -0
- package/README.md +96 -7
- package/dist/accounts.js +107 -28
- package/dist/agents.d.ts +2 -0
- package/dist/agents.js +105 -29
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- package/dist/kinds.d.ts +20 -0
- package/dist/kinds.js +6 -0
- package/dist/kinds.json +503 -0
- package/dist/kit.js +1 -0
- package/dist/launch-env.d.ts +6 -0
- package/dist/launch-env.js +71 -0
- package/dist/muse.d.ts +47 -0
- package/dist/muse.js +257 -0
- package/dist/testing/fake-herdr/server.js +23 -0
- package/dist/types.d.ts +32 -4
- package/dist/words.json +4 -0
- package/package.json +1 -1
package/dist/muse.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const MUSE_INSTALL_URL = "https://dev.meta.ai/install.sh";
|
|
2
|
+
/** A launcher alone cannot run without its selected native release. Never executes Muse. */
|
|
3
|
+
export declare function museNative(launcher: string): {
|
|
4
|
+
native: string;
|
|
5
|
+
version: string;
|
|
6
|
+
} | undefined;
|
|
7
|
+
export type MuseReadiness = {
|
|
8
|
+
state: 'unknown' | 'missing' | 'launcher-only' | 'installed';
|
|
9
|
+
signedIn: 'unknown';
|
|
10
|
+
bin?: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
};
|
|
13
|
+
/** Only the supplied effective pane PATH is probed, never the controller's personal directories. */
|
|
14
|
+
export declare function museReadiness(env?: Record<string, string>): MuseReadiness;
|
|
15
|
+
export type InstallMuseOptions = {
|
|
16
|
+
/** Caller-owned absolute private home. Never the person's default home or a system location. */
|
|
17
|
+
home: string;
|
|
18
|
+
/** Explicit clean tool PATH; also returned for launch with the installed bin directory prepended. */
|
|
19
|
+
path: string[];
|
|
20
|
+
/** Must be inside home; defaults to home/.local/bin. */
|
|
21
|
+
installDir?: string;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
/** Total download/process deadline, capped at five minutes. */
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
};
|
|
26
|
+
export type MuseInstallReceipt = {
|
|
27
|
+
bin: string;
|
|
28
|
+
native: string;
|
|
29
|
+
version: string;
|
|
30
|
+
launcherSha256: string;
|
|
31
|
+
nativeSha256: string;
|
|
32
|
+
installerSha256?: string;
|
|
33
|
+
source: typeof MUSE_INSTALL_URL;
|
|
34
|
+
path: string[];
|
|
35
|
+
env: Record<string, string>;
|
|
36
|
+
};
|
|
37
|
+
export type MuseInstallResult = {
|
|
38
|
+
ok: true;
|
|
39
|
+
receipt: MuseInstallReceipt;
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
code: 'unsupported_platform' | 'unsafe_path' | 'tools_missing' | 'cancelled' | 'timeout' | 'download_failed' | 'protected_download' | 'install_failed' | 'incomplete_install';
|
|
43
|
+
message: string;
|
|
44
|
+
installerSha256?: string;
|
|
45
|
+
};
|
|
46
|
+
/** Calling this authorizes the official download/install only. A protected artifact needs separate qualification. */
|
|
47
|
+
export declare function installMuse(o: InstallMuseOptions): Promise<MuseInstallResult>;
|
package/dist/muse.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Explicit, opt-in official Muse installation. No auth, model calls or inherited environment.
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { accessSync, closeSync, constants, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
7
|
+
export const MUSE_INSTALL_URL = 'https://dev.meta.ai/install.sh';
|
|
8
|
+
const VERSION = /^[0-9]+\.[0-9]+\.[0-9]+-R[0-9]+(?:\.[0-9]+)?$/;
|
|
9
|
+
const hash = (file) => {
|
|
10
|
+
const fd = openSync(file, 'r');
|
|
11
|
+
const digest = createHash('sha256');
|
|
12
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
13
|
+
try {
|
|
14
|
+
for (;;) {
|
|
15
|
+
const n = readSync(fd, buf);
|
|
16
|
+
if (!n)
|
|
17
|
+
return digest.digest('hex');
|
|
18
|
+
digest.update(buf.subarray(0, n));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
closeSync(fd);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function launcherHead(file) {
|
|
26
|
+
const fd = openSync(file, 'r');
|
|
27
|
+
const buf = Buffer.alloc(2048);
|
|
28
|
+
try {
|
|
29
|
+
return buf.subarray(0, readSync(fd, buf)).toString('utf8');
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
closeSync(fd);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const quote = (s) => `'${s.replaceAll("'", "'\\''")}'`;
|
|
36
|
+
/** A launcher alone cannot run without its selected native release. Never executes Muse. */
|
|
37
|
+
export function museNative(launcher) {
|
|
38
|
+
try {
|
|
39
|
+
const state = join(dirname(launcher), '.muse-version');
|
|
40
|
+
if (!lstatSync(state).isFile() || lstatSync(state).size > 128)
|
|
41
|
+
return;
|
|
42
|
+
const version = readFileSync(state, 'utf8').trim();
|
|
43
|
+
if (!VERSION.test(version))
|
|
44
|
+
return;
|
|
45
|
+
const native = join(dirname(launcher), `muse-bin-${version}`);
|
|
46
|
+
const launcherStat = lstatSync(launcher);
|
|
47
|
+
const nativeStat = lstatSync(native);
|
|
48
|
+
if (!launcherStat.isFile() || launcherStat.size > 1024 * 1024
|
|
49
|
+
|| !nativeStat.isFile() || nativeStat.size > 512 * 1024 * 1024)
|
|
50
|
+
return;
|
|
51
|
+
accessSync(launcher, constants.X_OK);
|
|
52
|
+
accessSync(native, constants.X_OK);
|
|
53
|
+
return { native, version };
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Only the supplied effective pane PATH is probed, never the controller's personal directories. */
|
|
60
|
+
export function museReadiness(env) {
|
|
61
|
+
if (env?.PATH === undefined)
|
|
62
|
+
return { state: 'unknown', signedIn: 'unknown' };
|
|
63
|
+
for (const dir of env.PATH.split(delimiter)) {
|
|
64
|
+
if (!isAbsolute(dir))
|
|
65
|
+
continue;
|
|
66
|
+
const bin = join(dir, 'muse');
|
|
67
|
+
try {
|
|
68
|
+
if (!lstatSync(bin).isFile())
|
|
69
|
+
continue;
|
|
70
|
+
accessSync(bin, constants.X_OK);
|
|
71
|
+
const selected = museNative(bin);
|
|
72
|
+
const head = launcherHead(bin);
|
|
73
|
+
const launcherOnly = head.startsWith('#!') && (head.includes('mise') || (!selected && head.includes('MUSE_CHANNEL')));
|
|
74
|
+
return { state: launcherOnly ? 'launcher-only' : 'installed', signedIn: 'unknown', bin,
|
|
75
|
+
...(selected ? { version: selected.version } : {}) };
|
|
76
|
+
}
|
|
77
|
+
catch { /* next PATH entry */ }
|
|
78
|
+
}
|
|
79
|
+
return { state: 'missing', signedIn: 'unknown' };
|
|
80
|
+
}
|
|
81
|
+
// Walk existing ancestors before making anything: no symlink directory can redirect writes.
|
|
82
|
+
function safePath(path) {
|
|
83
|
+
if (!isAbsolute(path) || resolve(path) !== path || /[\0\r\n:]/.test(path))
|
|
84
|
+
throw new Error('unsafe_path');
|
|
85
|
+
for (let p = path;; p = dirname(p)) {
|
|
86
|
+
try {
|
|
87
|
+
if (lstatSync(p).isSymbolicLink())
|
|
88
|
+
throw new Error('unsafe_path');
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
if (e.code !== 'ENOENT')
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
94
|
+
if (dirname(p) === p)
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function tool(name, path) {
|
|
99
|
+
for (const dir of path) {
|
|
100
|
+
const file = join(dir, name);
|
|
101
|
+
try {
|
|
102
|
+
accessSync(file, constants.X_OK);
|
|
103
|
+
return file;
|
|
104
|
+
}
|
|
105
|
+
catch { /* next */ }
|
|
106
|
+
}
|
|
107
|
+
throw new Error('tools_missing');
|
|
108
|
+
}
|
|
109
|
+
// Detached group cancellation kills the owned installer and every curl/bash child, not shared tools.
|
|
110
|
+
async function run(bin, args, env, cwd, signal) {
|
|
111
|
+
signal.throwIfAborted();
|
|
112
|
+
return new Promise((done, reject) => {
|
|
113
|
+
const child = spawn(bin, args, { env, cwd, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
114
|
+
const kill = () => { if (child.pid)
|
|
115
|
+
try {
|
|
116
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
117
|
+
}
|
|
118
|
+
catch { /* exited */ } };
|
|
119
|
+
const abort = () => kill();
|
|
120
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
121
|
+
let output = '';
|
|
122
|
+
const collect = (chunk) => {
|
|
123
|
+
output += chunk.toString();
|
|
124
|
+
if (output.length > 64 * 1024) {
|
|
125
|
+
output = output.slice(0, 64 * 1024);
|
|
126
|
+
kill();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
child.stdout.on('data', collect);
|
|
130
|
+
child.stderr.on('data', collect);
|
|
131
|
+
child.once('error', (e) => { signal.removeEventListener('abort', abort); reject(e); });
|
|
132
|
+
child.once('close', (code) => {
|
|
133
|
+
signal.removeEventListener('abort', abort);
|
|
134
|
+
// Kill any surviving descendants before removing their private staging files.
|
|
135
|
+
kill();
|
|
136
|
+
if (signal.aborted)
|
|
137
|
+
reject(signal.reason);
|
|
138
|
+
else
|
|
139
|
+
done({ code, output });
|
|
140
|
+
});
|
|
141
|
+
if (signal.aborted)
|
|
142
|
+
kill();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** Calling this authorizes the official download/install only. A protected artifact needs separate qualification. */
|
|
146
|
+
export async function installMuse(o) {
|
|
147
|
+
const bad = (code, message) => ({ ok: false, code, message, ...(installerSha256 ? { installerSha256 } : {}) });
|
|
148
|
+
let installerSha256;
|
|
149
|
+
if (!['linux-x64', 'linux-arm64', 'darwin-x64', 'darwin-arm64'].includes(`${process.platform}-${process.arch}`)) {
|
|
150
|
+
return bad('unsupported_platform', 'The official private installer is supported here only on Linux and macOS x64/arm64.');
|
|
151
|
+
}
|
|
152
|
+
const dir = o.installDir ?? join(o.home, '.local/bin');
|
|
153
|
+
let stage;
|
|
154
|
+
const signal = AbortSignal.any([...(o.signal ? [o.signal] : []), AbortSignal.timeout(Math.min(300_000, Math.max(1, o.timeoutMs ?? 180_000)))]);
|
|
155
|
+
try {
|
|
156
|
+
safePath(o.home);
|
|
157
|
+
safePath(dir);
|
|
158
|
+
if (o.home === '/' || o.home === '/tmp' || o.home === homedir() || homedir().startsWith(o.home + sep)
|
|
159
|
+
|| !dir.startsWith(o.home + sep) || /^\/(?:usr|bin|sbin|etc|opt|var|root|dev|proc|sys|run|lib|lib64|System|Library|Applications)(?:\/|$)/.test(o.home)
|
|
160
|
+
|| !o.path.length || o.path.some((p) => !isAbsolute(p) || /[\0\r\n:]/.test(p)))
|
|
161
|
+
throw new Error('unsafe_path');
|
|
162
|
+
try {
|
|
163
|
+
const stat = lstatSync(o.home);
|
|
164
|
+
if (!stat.isDirectory() || (stat.mode & 0o022) !== 0 || (process.getuid && stat.uid !== process.getuid()))
|
|
165
|
+
throw new Error('unsafe_path');
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
if (e.code !== 'ENOENT')
|
|
169
|
+
throw e;
|
|
170
|
+
}
|
|
171
|
+
const bash = tool('bash', o.path);
|
|
172
|
+
const curl = tool('curl', o.path);
|
|
173
|
+
const path = [dir, ...o.path];
|
|
174
|
+
const env = { HOME: o.home, XDG_CONFIG_HOME: join(o.home, '.config'), XDG_CACHE_HOME: join(o.home, '.cache'),
|
|
175
|
+
XDG_STATE_HOME: join(o.home, '.local/state'), TMPDIR: join(o.home, '.tmp'), PATH: path.join(delimiter),
|
|
176
|
+
LANG: 'C.UTF-8', MUSE_LOGIN: '0', MUSE_NO_AUTO_UPDATE: '1', MUSE_AUTH_PATH: join(o.home, '.config/muse/auth.json') };
|
|
177
|
+
for (const p of [env.XDG_CONFIG_HOME, env.XDG_CACHE_HOME, env.XDG_STATE_HOME, env.TMPDIR, dirname(env.MUSE_AUTH_PATH)])
|
|
178
|
+
safePath(p);
|
|
179
|
+
const receipt = (selected) => ({ ok: true, receipt: {
|
|
180
|
+
bin: join(dir, 'muse'), ...selected, launcherSha256: hash(join(dir, 'muse')), nativeSha256: hash(selected.native),
|
|
181
|
+
...(installerSha256 ? { installerSha256 } : {}), source: MUSE_INSTALL_URL, path, env,
|
|
182
|
+
} });
|
|
183
|
+
signal.throwIfAborted();
|
|
184
|
+
const existing = museNative(join(dir, 'muse'));
|
|
185
|
+
if (existing) {
|
|
186
|
+
for (const p of [env.XDG_CONFIG_HOME, env.XDG_CACHE_HOME, env.XDG_STATE_HOME, env.TMPDIR])
|
|
187
|
+
mkdirSync(p, { recursive: true, mode: 0o700 });
|
|
188
|
+
return receipt(existing); // No updater and no risk to a working installation.
|
|
189
|
+
}
|
|
190
|
+
// Never replace an existing incomplete directory either: caller may inspect/recover it separately.
|
|
191
|
+
try {
|
|
192
|
+
lstatSync(dir);
|
|
193
|
+
return bad('incomplete_install', 'The target exists without a complete Muse installation; choose a fresh private install directory.');
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
if (e.code !== 'ENOENT')
|
|
197
|
+
throw e;
|
|
198
|
+
}
|
|
199
|
+
mkdirSync(o.home, { recursive: true, mode: 0o700 });
|
|
200
|
+
stage = mkdtempSync(join(o.home, '.muse-install-'));
|
|
201
|
+
const privateHome = join(stage, 'home');
|
|
202
|
+
const temp = join(stage, 'tmp');
|
|
203
|
+
const tools = join(stage, 'tools');
|
|
204
|
+
for (const p of [privateHome, temp, tools])
|
|
205
|
+
mkdirSync(p, { mode: 0o700 });
|
|
206
|
+
// Official scripts remain byte-for-byte unchanged. This curl policy disables curlrc and bounds
|
|
207
|
+
// their downloads as well as the initial installer (HTTPS, redirects, bytes, connect/total time).
|
|
208
|
+
writeFileSync(join(tools, 'curl'), `#!/bin/sh\nexec ${quote(curl)} -q "$@" --proto '=https' --proto-redir '=https' --max-redirs 3 --connect-timeout 10 --max-time 60 --max-filesize 536870912\n`, { mode: 0o700 });
|
|
209
|
+
const stagedDir = join(stage, 'bin');
|
|
210
|
+
const installEnv = { HOME: privateHome, XDG_CONFIG_HOME: join(privateHome, '.config'), XDG_CACHE_HOME: join(privateHome, '.cache'),
|
|
211
|
+
XDG_STATE_HOME: join(privateHome, '.local/state'), TMPDIR: temp, PATH: [tools, stagedDir, ...o.path].join(delimiter),
|
|
212
|
+
LANG: 'C.UTF-8', MUSE_INSTALL_DIR: stagedDir, MUSE_NO_MODIFY_PATH: '1', MUSE_LOGIN: '0', MUSE_NO_AUTO_UPDATE: '1',
|
|
213
|
+
MUSE_AUTH_PATH: join(privateHome, '.config/muse/auth.json') };
|
|
214
|
+
const installer = join(stage, 'install.sh');
|
|
215
|
+
const fetched = await run(join(tools, 'curl'), ['--fail', '--silent', '--show-error', '--location', '--output', installer, MUSE_INSTALL_URL], installEnv, stage, signal);
|
|
216
|
+
if (fetched.code !== 0)
|
|
217
|
+
return bad('download_failed', 'The official installer download failed.');
|
|
218
|
+
if (!lstatSync(installer).isFile() || lstatSync(installer).size > 1024 * 1024)
|
|
219
|
+
return bad('download_failed', 'The official installer exceeds the allowed script size.');
|
|
220
|
+
installerSha256 = hash(installer);
|
|
221
|
+
const installed = await run(bash, ['--noprofile', '--norc', installer], installEnv, stage, signal);
|
|
222
|
+
if (installed.code !== 0) {
|
|
223
|
+
const denial = /HTTP (401|403|404)/.exec(installed.output);
|
|
224
|
+
return denial ? bad('protected_download', `The official Muse download is protected or unavailable (HTTP ${denial[1]}); no login was attempted.`)
|
|
225
|
+
: bad('install_failed', 'The official Muse installer failed; the previous installation was not changed.');
|
|
226
|
+
}
|
|
227
|
+
const selected = museNative(join(stagedDir, 'muse'));
|
|
228
|
+
if (!selected)
|
|
229
|
+
return bad('incomplete_install', 'The official installer did not produce an executable selected native release.');
|
|
230
|
+
signal.throwIfAborted();
|
|
231
|
+
safePath(o.home);
|
|
232
|
+
safePath(dir);
|
|
233
|
+
mkdirSync(dirname(dir), { recursive: true, mode: 0o700 });
|
|
234
|
+
for (const p of [env.XDG_CONFIG_HOME, env.XDG_CACHE_HOME, env.XDG_STATE_HOME, env.TMPDIR]) {
|
|
235
|
+
safePath(p);
|
|
236
|
+
mkdirSync(p, { recursive: true, mode: 0o700 });
|
|
237
|
+
}
|
|
238
|
+
signal.throwIfAborted();
|
|
239
|
+
renameSync(stagedDir, dir);
|
|
240
|
+
return receipt({ version: selected.version, native: join(dir, `muse-bin-${selected.version}`) });
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
if (o.signal?.aborted)
|
|
244
|
+
return bad('cancelled', 'Muse installation was cancelled; the previous installation was not changed.');
|
|
245
|
+
if (signal.aborted)
|
|
246
|
+
return bad('timeout', 'Muse installation exceeded its deadline; the previous installation was not changed.');
|
|
247
|
+
if (e.message === 'unsafe_path')
|
|
248
|
+
return bad('unsafe_path', 'Use absolute private app-managed paths without symlinks, inside the selected home.');
|
|
249
|
+
if (e.message === 'tools_missing')
|
|
250
|
+
return bad('tools_missing', 'Bash and curl are required on the explicit tool PATH.');
|
|
251
|
+
return bad('install_failed', 'Muse installation failed; the previous installation was not changed.');
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
if (stage)
|
|
255
|
+
rmSync(stage, { recursive: true, force: true });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// request per connection — the server closes after answering; only `events.subscribe` is held open.
|
|
3
3
|
// Ported from muxr `perf/fake-herdr/server.mjs` with the UI plugins, title churn, graphics and perf
|
|
4
4
|
// byte rates removed.
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
5
6
|
import { mkdirSync, unlinkSync } from 'node:fs';
|
|
6
7
|
import { createServer } from 'node:net';
|
|
7
8
|
import { join } from 'node:path';
|
|
@@ -235,6 +236,28 @@ export async function startFakeHerdr(options) {
|
|
|
235
236
|
emitEvent('tab.renamed', { tab_id: tab.tab_id, workspace_id: tab.workspace_id, label: tab.label });
|
|
236
237
|
return { type: 'tab_info', tab: tabView(tab) };
|
|
237
238
|
},
|
|
239
|
+
'pane.process_info': (p) => {
|
|
240
|
+
const pane = paneOf(p.pane_id);
|
|
241
|
+
const busy = live.agents.some((a) => a.pane_id === pane.pane_id);
|
|
242
|
+
return { process_info: { shell_pid: 1, foreground_processes: [{ pid: busy ? 2 : 1, name: busy ? 'agent' : 'sh' }] } };
|
|
243
|
+
},
|
|
244
|
+
'pane.send_text': (p) => {
|
|
245
|
+
const pane = paneOf(p.pane_id);
|
|
246
|
+
const text = String(p.text ?? '');
|
|
247
|
+
// A real offline shell exercises the private launch file, never a real agent CLI.
|
|
248
|
+
if (!/^set \+x \+v; \. '/.test(text))
|
|
249
|
+
throw fail('unsupported', 'unsupported fake shell command');
|
|
250
|
+
const r = spawnSync('/bin/sh', ['-c', text + "printf '\\0'; /usr/bin/env -0"], {
|
|
251
|
+
env: pane.env ?? { PATH: '/usr/bin:/bin', HOME: dir }, encoding: 'utf8', timeout: 5000,
|
|
252
|
+
});
|
|
253
|
+
const [output, ...environment] = r.stdout.split('\0');
|
|
254
|
+
pane.text.push(output);
|
|
255
|
+
pane.env = Object.fromEntries(environment.filter(Boolean).map((entry) => {
|
|
256
|
+
const i = entry.indexOf('=');
|
|
257
|
+
return [entry.slice(0, i), entry.slice(i + 1)];
|
|
258
|
+
}));
|
|
259
|
+
return {};
|
|
260
|
+
},
|
|
238
261
|
'pane.get': (p) => ({ pane: paneView(paneOf(p.pane_id)) }),
|
|
239
262
|
'pane.close': (p) => {
|
|
240
263
|
const pane = paneOf(p.pane_id);
|
package/dist/types.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export type AgentReadiness = {
|
|
|
65
65
|
export type AgentStatusRunner = (command: string, args: string[], o?: {
|
|
66
66
|
stdin?: string;
|
|
67
67
|
timeoutMs?: number;
|
|
68
|
+
env?: Record<string, string>;
|
|
68
69
|
}) => Promise<{
|
|
69
70
|
stdout: string;
|
|
70
71
|
} | undefined>;
|
|
@@ -74,8 +75,12 @@ export type AgentStatusOptions = {
|
|
|
74
75
|
readFile?: (file: string) => string | undefined;
|
|
75
76
|
run?: AgentStatusRunner;
|
|
76
77
|
timeoutMs?: number;
|
|
78
|
+
/** Explicit app-managed account folders. Absent selection never probes a default login. */
|
|
79
|
+
folders?: Record<string, string>;
|
|
80
|
+
/** Clean, host-passed env only; HOME and XDG directories are confined to the selected folder. */
|
|
81
|
+
env?: Record<string, string>;
|
|
77
82
|
};
|
|
78
|
-
export type AgentLaunchFailureReason = 'placement-failed' | 'pane-busy' | 'install-failed' | 'start-rejected';
|
|
83
|
+
export type AgentLaunchFailureReason = 'placement-failed' | 'pane-busy' | 'install-failed' | 'start-rejected' | 'not-installed';
|
|
79
84
|
export type AgentStartEvent = {
|
|
80
85
|
phase: 'installing';
|
|
81
86
|
kind: string;
|
|
@@ -116,7 +121,10 @@ export type StartAgent = {
|
|
|
116
121
|
base?: string;
|
|
117
122
|
};
|
|
118
123
|
args?: string[];
|
|
119
|
-
env?: Record<string, string
|
|
124
|
+
env?: Record<string, string> | {
|
|
125
|
+
env: Record<string, string>;
|
|
126
|
+
unset: string[];
|
|
127
|
+
};
|
|
120
128
|
timeoutMs?: number;
|
|
121
129
|
onEvent?: (e: AgentStartEvent) => void;
|
|
122
130
|
installProbe?: AgentInstallProbe;
|
|
@@ -124,20 +132,39 @@ export type StartAgent = {
|
|
|
124
132
|
export type OpenSignInTab = Omit<StartAgent, 'place' | 'worktree'> & {
|
|
125
133
|
workspaceId: string;
|
|
126
134
|
label?: string;
|
|
135
|
+
folder?: string;
|
|
136
|
+
};
|
|
137
|
+
export type BusyHandoff = {
|
|
138
|
+
busy?: 'refuse';
|
|
139
|
+
} | {
|
|
140
|
+
busy: 'wait';
|
|
141
|
+
confirmed: {
|
|
142
|
+
session: string;
|
|
143
|
+
terminalId: string;
|
|
144
|
+
};
|
|
145
|
+
waitMs: number;
|
|
146
|
+
} | {
|
|
147
|
+
busy: 'interrupt';
|
|
148
|
+
confirmed: {
|
|
149
|
+
session: string;
|
|
150
|
+
terminalId: string;
|
|
151
|
+
seq: number;
|
|
152
|
+
};
|
|
127
153
|
};
|
|
128
154
|
export type MoveToAccount = {
|
|
129
|
-
provider:
|
|
155
|
+
provider: string;
|
|
130
156
|
folder: string;
|
|
131
157
|
env?: Record<string, string>;
|
|
132
158
|
direction?: 'right' | 'down';
|
|
133
159
|
timeoutMs?: number;
|
|
160
|
+
whenBusy?: BusyHandoff;
|
|
134
161
|
};
|
|
135
162
|
export type MoveToAccountResult = {
|
|
136
163
|
ok: true;
|
|
137
164
|
session: string;
|
|
138
165
|
} | {
|
|
139
166
|
ok: false;
|
|
140
|
-
code: 'too_early' | 'busy' | 'unsupported' | 'env_mismatch' | 'close_failed' | 'start_failed';
|
|
167
|
+
code: 'too_early' | 'busy' | 'unsupported' | 'env_mismatch' | 'close_failed' | 'start_failed' | 'blocked' | 'changed' | 'interrupt_unsupported';
|
|
141
168
|
message: string;
|
|
142
169
|
live?: string;
|
|
143
170
|
};
|
|
@@ -150,6 +177,7 @@ export type Move = {
|
|
|
150
177
|
onStaged?(newPaneId: string): void;
|
|
151
178
|
onReplaced?(newPaneId: string): void;
|
|
152
179
|
timeoutMs?: number;
|
|
180
|
+
whenBusy?: BusyHandoff;
|
|
153
181
|
};
|
|
154
182
|
export type MoveResult = {
|
|
155
183
|
ok: true;
|
package/dist/words.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"agent.notReady": "This helper isn't ready yet. Try again in a moment.",
|
|
16
16
|
"agent.installing": "Installing {agent}…",
|
|
17
17
|
"agent.installFailed": "Installing {agent} didn't finish. Try again in a moment.",
|
|
18
|
+
"agent.notInstalled": "Install this helper in the app first, then try again.",
|
|
18
19
|
"agent.launchFailed": "That helper couldn't start. Try again in a moment.",
|
|
19
20
|
"approval.stale": "That question already changed. Look again before answering.",
|
|
20
21
|
"close.wouldWiden": "Closing this would close more than you picked. Close the bigger one instead.",
|
|
@@ -25,5 +26,8 @@
|
|
|
25
26
|
"move.env_mismatch": "The new pane did not receive that sign-in. Try again.",
|
|
26
27
|
"move.close_failed": "The move could not be confirmed. Check the remaining panes before trying again.",
|
|
27
28
|
"move.start_failed": "The new account could not take over. Try again.",
|
|
29
|
+
"move.blocked": "This conversation is waiting for your answer. Answer it, then move it.",
|
|
30
|
+
"move.changed": "This conversation changed while moving, so it was not moved. Check your panes, then try again.",
|
|
31
|
+
"move.interrupt_unsupported": "Stopping a step is not available for this agent. Move it when the step finishes.",
|
|
28
32
|
"turn.failed": "This turn could not be confirmed. Check the helper before trying again."
|
|
29
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byokit/herdr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Drive the Herdr on this computer — workspaces, tabs, panes, agents and blocked-approval answers — from an app, or hand it to a phone over @byokit/link.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|