@phnx-labs/agents-cli 1.22.114 → 1.22.115
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 +110 -0
- package/README.md +22 -102
- package/dist/browser.js +0 -0
- package/dist/cli/command-registry.js +0 -7
- package/dist/commands/accounts.d.ts +3 -77
- package/dist/commands/accounts.js +13 -366
- package/dist/commands/audit.js +1 -1
- package/dist/commands/auth.js +0 -2
- package/dist/commands/browser.d.ts +53 -0
- package/dist/commands/browser.js +199 -9
- package/dist/commands/doctor.d.ts +2 -3
- package/dist/commands/doctor.js +0 -11
- package/dist/commands/events.d.ts +1 -3
- package/dist/commands/events.js +4 -7
- package/dist/commands/exec.d.ts +0 -1
- package/dist/commands/exec.js +2 -7
- package/dist/commands/feed-watch.js +52 -8
- package/dist/commands/focus.js +1 -1
- package/dist/commands/go.d.ts +0 -17
- package/dist/commands/go.js +2 -19
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mcp.js +8 -83
- package/dist/commands/memory.js +4 -47
- package/dist/commands/message.js +4 -4
- package/dist/commands/plugins.js +1 -93
- package/dist/commands/repo.js +0 -44
- package/dist/commands/resume.js +4 -1
- package/dist/commands/secrets-passthrough.js +2 -2
- package/dist/commands/send.d.ts +4 -4
- package/dist/commands/send.js +6 -51
- package/dist/commands/sessions-backup-setup.js +1 -1
- package/dist/commands/sessions-resume.js +0 -1
- package/dist/commands/sessions-share.d.ts +5 -7
- package/dist/commands/sessions-share.js +98 -49
- package/dist/commands/sessions.js +1 -8
- package/dist/commands/setup-browser.js +18 -2
- package/dist/commands/setup-computer.js +20 -6
- package/dist/commands/setup-secrets.js +24 -4
- package/dist/commands/setup-terminal.d.ts +3 -0
- package/dist/commands/setup-terminal.js +22 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +20 -10
- package/dist/commands/skills.js +0 -8
- package/dist/commands/ssh.d.ts +6 -0
- package/dist/commands/ssh.js +92 -233
- package/dist/commands/sync.js +14 -5
- package/dist/commands/teams.js +1 -1
- package/dist/commands/traces.js +1 -1
- package/dist/lib/accounts/add.d.ts +0 -5
- package/dist/lib/accounts/add.js +1 -7
- package/dist/lib/artifacts-client.d.ts +20 -0
- package/dist/lib/artifacts-client.js +46 -0
- package/dist/lib/auth-mint.js +2 -2
- package/dist/lib/browser/runtime-state.d.ts +55 -0
- package/dist/lib/browser/runtime-state.js +99 -18
- package/dist/lib/browser/service.js +21 -1
- package/dist/lib/cli-resources.js +3 -1
- package/dist/lib/cloud/dispatch.js +1 -1
- package/dist/lib/cloudflare/creds.d.ts +10 -0
- package/dist/lib/cloudflare/creds.js +46 -0
- package/dist/lib/cloudflare/provision.d.ts +35 -0
- package/dist/lib/cloudflare/provision.js +144 -0
- package/dist/lib/computer/sessions-list.d.ts +55 -0
- package/dist/lib/computer/sessions-list.js +168 -1
- package/dist/lib/daemon/daemon.js +8 -1
- package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
- package/dist/lib/daemon/feed-stream-service.js +40 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/devices/connect.d.ts +49 -5
- package/dist/lib/devices/connect.js +169 -21
- package/dist/lib/feed/envelope.d.ts +79 -0
- package/dist/lib/feed/envelope.js +23 -0
- package/dist/lib/feed/events.d.ts +6 -0
- package/dist/lib/feed/events.js +8 -0
- package/dist/lib/feed/hub-server.d.ts +86 -0
- package/dist/lib/feed/hub-server.js +334 -0
- package/dist/lib/feed/hub.d.ts +95 -0
- package/dist/lib/feed/hub.js +255 -0
- package/dist/lib/feed/tool-activity.d.ts +108 -0
- package/dist/lib/feed/tool-activity.js +313 -0
- package/dist/lib/feed/tools.d.ts +198 -0
- package/dist/lib/feed/tools.js +265 -0
- package/dist/lib/feed/watch.d.ts +50 -50
- package/dist/lib/feed/watch.js +147 -16
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.js +1 -1
- package/dist/lib/git.d.ts +0 -16
- package/dist/lib/git.js +0 -58
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hosts/remote-cmd.d.ts +51 -1
- package/dist/lib/hosts/remote-cmd.js +125 -8
- package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
- package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
- package/dist/lib/mcp.js +17 -11
- package/dist/lib/probe.d.ts +4 -1
- package/dist/lib/probe.js +5 -2
- package/dist/lib/pwsh.d.ts +33 -0
- package/dist/lib/pwsh.js +56 -0
- package/dist/lib/redact.d.ts +8 -0
- package/dist/lib/redact.js +11 -0
- package/dist/lib/refresh.d.ts +6 -2
- package/dist/lib/refresh.js +92 -72
- package/dist/lib/secrets-cli.d.ts +11 -0
- package/dist/lib/secrets-cli.js +30 -0
- package/dist/lib/secrets-client.js +3 -2
- package/dist/lib/session/detached.d.ts +7 -0
- package/dist/lib/session/detached.js +29 -0
- package/dist/lib/session/remote/peer-stream.d.ts +24 -2
- package/dist/lib/session/remote/peer-stream.js +33 -6
- package/dist/lib/session/sync/backend.d.ts +3 -3
- package/dist/lib/session/sync/backend.js +3 -3
- package/dist/lib/session/sync/provision.d.ts +1 -1
- package/dist/lib/session/sync/provision.js +2 -2
- package/dist/lib/sessions-client.js +0 -3
- package/dist/lib/setup-tool-install.d.ts +3 -0
- package/dist/lib/setup-tool-install.js +26 -0
- package/dist/lib/setup-tool-status.d.ts +22 -0
- package/dist/lib/setup-tool-status.js +215 -0
- package/dist/lib/share-runtime.d.ts +11 -0
- package/dist/lib/share-runtime.js +63 -0
- package/dist/lib/smart-launch.d.ts +1 -5
- package/dist/lib/smart-launch.js +3 -11
- package/dist/lib/ssh-exec.d.ts +44 -0
- package/dist/lib/ssh-exec.js +119 -0
- package/dist/lib/startup/command-registry.d.ts +6 -4
- package/dist/lib/startup/command-registry.js +10 -7
- package/dist/lib/state.js +2 -2
- package/dist/lib/storage/selection.d.ts +2 -2
- package/dist/lib/storage/selection.js +2 -2
- package/dist/lib/sync-umbrella.d.ts +5 -0
- package/dist/lib/sync-umbrella.js +18 -10
- package/dist/lib/traces/backend.d.ts +1 -2
- package/dist/lib/traces/backend.js +1 -2
- package/dist/lib/traces/provision.d.ts +1 -1
- package/dist/lib/traces/provision.js +2 -2
- package/dist/lib/types.d.ts +8 -6
- package/package.json +2 -3
- package/dist/commands/artifacts-setup.d.ts +0 -53
- package/dist/commands/artifacts-setup.js +0 -161
- package/dist/commands/artifacts.d.ts +0 -18
- package/dist/commands/artifacts.js +0 -58
- package/dist/commands/attach.d.ts +0 -12
- package/dist/commands/attach.js +0 -86
- package/dist/commands/auth-mint.d.ts +0 -12
- package/dist/commands/auth-mint.js +0 -108
- package/dist/commands/reconnect.d.ts +0 -46
- package/dist/commands/reconnect.js +0 -115
- package/dist/commands/share.d.ts +0 -293
- package/dist/commands/share.js +0 -1424
- package/dist/lib/share/analytics.d.ts +0 -13
- package/dist/lib/share/analytics.js +0 -45
- package/dist/lib/share/backend.d.ts +0 -120
- package/dist/lib/share/backend.js +0 -176
- package/dist/lib/share/capture.d.ts +0 -31
- package/dist/lib/share/capture.js +0 -174
- package/dist/lib/share/config.d.ts +0 -72
- package/dist/lib/share/config.js +0 -211
- package/dist/lib/share/delete.d.ts +0 -123
- package/dist/lib/share/delete.js +0 -173
- package/dist/lib/share/html.d.ts +0 -20
- package/dist/lib/share/html.js +0 -88
- package/dist/lib/share/http-error.d.ts +0 -53
- package/dist/lib/share/http-error.js +0 -65
- package/dist/lib/share/og.d.ts +0 -26
- package/dist/lib/share/og.js +0 -84
- package/dist/lib/share/provision.d.ts +0 -127
- package/dist/lib/share/provision.js +0 -285
- package/dist/lib/share/publish.d.ts +0 -379
- package/dist/lib/share/publish.js +0 -818
- package/dist/lib/share/worker-template.d.ts +0 -27
- package/dist/lib/share/worker-template.js +0 -2424
- package/dist/lib/storage/index.d.ts +0 -14
- package/dist/lib/storage/index.js +0 -14
- package/dist/lib/storage/visibility.d.ts +0 -82
- package/dist/lib/storage/visibility.js +0 -99
package/dist/lib/mcp.js
CHANGED
|
@@ -380,25 +380,31 @@ function installMcpViaClaude(binaryPath, server, versionHome) {
|
|
|
380
380
|
* Uses: codex mcp add <name> -- <cmd> [args...]
|
|
381
381
|
*/
|
|
382
382
|
function installMcpViaCodex(binaryPath, server, versionHome) {
|
|
383
|
-
|
|
383
|
+
let args;
|
|
384
|
+
if (server.config.transport === 'http') {
|
|
385
|
+
if (!server.config.url)
|
|
386
|
+
throw new Error(`HTTP MCP '${server.name}' has no url`);
|
|
387
|
+
// codex mcp add <name> --url <url>
|
|
388
|
+
args = ['mcp', 'add', server.name, '--url', server.config.url];
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
384
391
|
// codex mcp add -- <name> <cmd> [args...]
|
|
385
|
-
|
|
392
|
+
args = [
|
|
386
393
|
'mcp', 'add',
|
|
387
394
|
'--',
|
|
388
395
|
server.name,
|
|
389
396
|
server.config.command,
|
|
390
397
|
...(server.config.args || [])
|
|
391
398
|
];
|
|
392
|
-
// RUSH-1752: user-controlled MCP command/args must not reach cmd.exe unquoted.
|
|
393
|
-
const spec = execFileShellSpec(binaryPath, args);
|
|
394
|
-
execFileSync(spec.command, spec.args, {
|
|
395
|
-
stdio: 'pipe',
|
|
396
|
-
timeout: 30000,
|
|
397
|
-
env: { ...process.env, HOME: versionHome },
|
|
398
|
-
shell: spec.shell,
|
|
399
|
-
});
|
|
400
399
|
}
|
|
401
|
-
//
|
|
400
|
+
// RUSH-1752: user-controlled MCP command/args must not reach cmd.exe unquoted.
|
|
401
|
+
const spec = execFileShellSpec(binaryPath, args);
|
|
402
|
+
execFileSync(spec.command, spec.args, {
|
|
403
|
+
stdio: 'pipe',
|
|
404
|
+
timeout: 30000,
|
|
405
|
+
env: { ...process.env, HOME: versionHome },
|
|
406
|
+
shell: spec.shell,
|
|
407
|
+
});
|
|
402
408
|
}
|
|
403
409
|
export async function registerMcpCommandToTargets(targets, name, commandSpec, scope = 'user', transport = 'stdio') {
|
|
404
410
|
const results = [];
|
package/dist/lib/probe.d.ts
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* timeout — matching the `execFileAsync` contract the version probe had — and
|
|
4
4
|
* reaps the probe's whole process group on every settle path.
|
|
5
5
|
*/
|
|
6
|
-
export declare function probeCapture(cmd: string, args: string[], timeoutMs: number
|
|
6
|
+
export declare function probeCapture(cmd: string, args: string[], timeoutMs: number, options?: {
|
|
7
|
+
acceptedExitCodes?: number[];
|
|
8
|
+
maxOutputBytes?: number;
|
|
9
|
+
}): Promise<{
|
|
7
10
|
stdout: string;
|
|
8
11
|
}>;
|
package/dist/lib/probe.js
CHANGED
|
@@ -59,7 +59,7 @@ function reapGroup(pid) {
|
|
|
59
59
|
* timeout — matching the `execFileAsync` contract the version probe had — and
|
|
60
60
|
* reaps the probe's whole process group on every settle path.
|
|
61
61
|
*/
|
|
62
|
-
export function probeCapture(cmd, args, timeoutMs) {
|
|
62
|
+
export function probeCapture(cmd, args, timeoutMs, options = {}) {
|
|
63
63
|
return new Promise((resolve, reject) => {
|
|
64
64
|
const child = spawn(cmd, args, {
|
|
65
65
|
detached: GROUP_REAP,
|
|
@@ -92,6 +92,9 @@ export function probeCapture(cmd, args, timeoutMs) {
|
|
|
92
92
|
child.stdout?.setEncoding('utf8');
|
|
93
93
|
child.stdout?.on('data', (d) => {
|
|
94
94
|
out += d;
|
|
95
|
+
if (Buffer.byteLength(out) > (options.maxOutputBytes ?? 1024 * 1024)) {
|
|
96
|
+
settle(new Error(`probe output exceeded limit: ${cmd}`));
|
|
97
|
+
}
|
|
95
98
|
});
|
|
96
99
|
child.on('error', (e) => settle(e));
|
|
97
100
|
// 'exit', not 'close': a forked grandchild inherits the stdout pipe, and
|
|
@@ -99,7 +102,7 @@ export function probeCapture(cmd, args, timeoutMs) {
|
|
|
99
102
|
// process this helper exists to reap. Settle when the probed binary
|
|
100
103
|
// itself exits; one tick's grace lets its final stdout chunks land.
|
|
101
104
|
child.on('exit', (code) => {
|
|
102
|
-
setImmediate(() => settle(code
|
|
105
|
+
setImmediate(() => settle(code !== null && (options.acceptedExitCodes ?? [0]).includes(code) ? null : new Error(`probe exited ${code}: ${cmd} ${args.join(' ')}`)));
|
|
103
106
|
});
|
|
104
107
|
});
|
|
105
108
|
}
|
package/dist/lib/pwsh.d.ts
CHANGED
|
@@ -9,3 +9,36 @@
|
|
|
9
9
|
* spawns powershell.exe with an argv array).
|
|
10
10
|
*/
|
|
11
11
|
export declare function encodePwshBase64(script: string): string;
|
|
12
|
+
/** Single-quote a value for PowerShell's own parser (embedded `'` doubled). */
|
|
13
|
+
export declare function pwshLiteral(value: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* PowerShell statements that run a NATIVE program with exact argv.
|
|
16
|
+
*
|
|
17
|
+
* Windows has no argv array: a process receives ONE string and splits it itself.
|
|
18
|
+
* PowerShell 5.1 rebuilds that string when it invokes a native program, and its
|
|
19
|
+
* serializer is lossy — measured on a real peer, an EMPTY argument is dropped and
|
|
20
|
+
* an embedded `"` is discarded. So the arguments are pre-escaped by the caller to
|
|
21
|
+
* the `CommandLineToArgvW` rules (`quoteWin32ExecArg`) and handed to .NET as one
|
|
22
|
+
* `Arguments` string, which `CreateProcess` receives essentially verbatim. No shell
|
|
23
|
+
* is involved, so no `%VAR%` expansion and no newline sensitivity.
|
|
24
|
+
*
|
|
25
|
+
* `UseShellExecute = $false` with no redirection leaves the child on the inherited
|
|
26
|
+
* handles, which is what lets a binary stdout stream through unchanged.
|
|
27
|
+
*
|
|
28
|
+
* `WorkingDirectory` is set explicitly from `(Get-Location).ProviderPath`, and that
|
|
29
|
+
* is not belt-and-braces: PowerShell's `Set-Location` moves the SHELL's location
|
|
30
|
+
* without updating the .NET process's OS working directory, so a child launched
|
|
31
|
+
* through `ProcessStartInfo` would otherwise start in whatever directory the
|
|
32
|
+
* process began in — silently ignoring a caller's `cwd` (`--remote-cwd`). The
|
|
33
|
+
* `ProviderPath` form is what yields a real filesystem path rather than a
|
|
34
|
+
* PowerShell provider path.
|
|
35
|
+
*
|
|
36
|
+
* @param fileNameExpr a PowerShell EXPRESSION for the executable (a quoted literal,
|
|
37
|
+
* or something like `$__c.Source`) — not a raw path, so a caller that resolved the
|
|
38
|
+
* target on the peer can pass its variable.
|
|
39
|
+
* @param argumentsExpr a PowerShell EXPRESSION producing the argument string —
|
|
40
|
+
* `pwshLiteral(line)` for a static one, or a concatenation when part of it is
|
|
41
|
+
* only known on the peer (a resolved interpreter entry, say).
|
|
42
|
+
* @param exitVar variable name for the child, so a caller can chain on it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function pwshNativeExecStatements(fileNameExpr: string, argumentsExpr: string, exitVar?: string): string[];
|
package/dist/lib/pwsh.js
CHANGED
|
@@ -11,3 +11,59 @@
|
|
|
11
11
|
export function encodePwshBase64(script) {
|
|
12
12
|
return Buffer.from(script, 'utf16le').toString('base64');
|
|
13
13
|
}
|
|
14
|
+
/** Single-quote a value for PowerShell's own parser (embedded `'` doubled). */
|
|
15
|
+
export function pwshLiteral(value) {
|
|
16
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* PowerShell statements that run a NATIVE program with exact argv.
|
|
20
|
+
*
|
|
21
|
+
* Windows has no argv array: a process receives ONE string and splits it itself.
|
|
22
|
+
* PowerShell 5.1 rebuilds that string when it invokes a native program, and its
|
|
23
|
+
* serializer is lossy — measured on a real peer, an EMPTY argument is dropped and
|
|
24
|
+
* an embedded `"` is discarded. So the arguments are pre-escaped by the caller to
|
|
25
|
+
* the `CommandLineToArgvW` rules (`quoteWin32ExecArg`) and handed to .NET as one
|
|
26
|
+
* `Arguments` string, which `CreateProcess` receives essentially verbatim. No shell
|
|
27
|
+
* is involved, so no `%VAR%` expansion and no newline sensitivity.
|
|
28
|
+
*
|
|
29
|
+
* `UseShellExecute = $false` with no redirection leaves the child on the inherited
|
|
30
|
+
* handles, which is what lets a binary stdout stream through unchanged.
|
|
31
|
+
*
|
|
32
|
+
* `WorkingDirectory` is set explicitly from `(Get-Location).ProviderPath`, and that
|
|
33
|
+
* is not belt-and-braces: PowerShell's `Set-Location` moves the SHELL's location
|
|
34
|
+
* without updating the .NET process's OS working directory, so a child launched
|
|
35
|
+
* through `ProcessStartInfo` would otherwise start in whatever directory the
|
|
36
|
+
* process began in — silently ignoring a caller's `cwd` (`--remote-cwd`). The
|
|
37
|
+
* `ProviderPath` form is what yields a real filesystem path rather than a
|
|
38
|
+
* PowerShell provider path.
|
|
39
|
+
*
|
|
40
|
+
* @param fileNameExpr a PowerShell EXPRESSION for the executable (a quoted literal,
|
|
41
|
+
* or something like `$__c.Source`) — not a raw path, so a caller that resolved the
|
|
42
|
+
* target on the peer can pass its variable.
|
|
43
|
+
* @param argumentsExpr a PowerShell EXPRESSION producing the argument string —
|
|
44
|
+
* `pwshLiteral(line)` for a static one, or a concatenation when part of it is
|
|
45
|
+
* only known on the peer (a resolved interpreter entry, say).
|
|
46
|
+
* @param exitVar variable name for the child, so a caller can chain on it.
|
|
47
|
+
*/
|
|
48
|
+
export function pwshNativeExecStatements(fileNameExpr, argumentsExpr, exitVar = '$zp') {
|
|
49
|
+
// Deliberately terse. The whole script is base64'd into ONE ssh command line,
|
|
50
|
+
// and OpenSSH-for-Windows caps that far below cmd.exe's 8191 — measured on a
|
|
51
|
+
// real peer, it fails between ~2.8k and ~4.1k characters. UTF-16LE + base64
|
|
52
|
+
// inflates by ~2.7x, so every character here costs almost three on the wire.
|
|
53
|
+
// `System.` is implicit for these types, and `-EA` is the canonical alias.
|
|
54
|
+
return [
|
|
55
|
+
`$zi=New-Object Diagnostics.ProcessStartInfo`,
|
|
56
|
+
`$zi.FileName=${fileNameExpr}`,
|
|
57
|
+
`$zi.Arguments=${argumentsExpr}`,
|
|
58
|
+
`$zi.UseShellExecute=$false`,
|
|
59
|
+
`$zi.WorkingDirectory=(Get-Location).ProviderPath`,
|
|
60
|
+
// The CALLER sets `$ErrorActionPreference='Stop'` for the whole script, so it
|
|
61
|
+
// is not repeated here — every character costs ~2.7 on the wire after base64.
|
|
62
|
+
// The null guard below is what makes a swallowed Start failure impossible to
|
|
63
|
+
// mistake for success: without it the handle is $null, the code is $null, and
|
|
64
|
+
// `exit $null` reports 0.
|
|
65
|
+
`${exitVar}=[Diagnostics.Process]::Start($zi)`,
|
|
66
|
+
`if($null -eq ${exitVar}){throw "start failed: $($zi.FileName)"}`,
|
|
67
|
+
`${exitVar}.WaitForExit()`,
|
|
68
|
+
];
|
|
69
|
+
}
|
package/dist/lib/redact.d.ts
CHANGED
|
@@ -23,3 +23,11 @@ export declare function redactSecrets(text: string, knownValues?: readonly strin
|
|
|
23
23
|
* credential verbatim even when its format matches no pattern.
|
|
24
24
|
*/
|
|
25
25
|
export declare function knownSecretValuesFromEnv(env?: NodeJS.ProcessEnv): string[];
|
|
26
|
+
/**
|
|
27
|
+
* Mask email addresses in already-rendered text. A published page's sensitive-
|
|
28
|
+
* content scan refuses a body containing an email (`artifacts share` does the
|
|
29
|
+
* same), and almost every real transcript carries a few — git author addresses,
|
|
30
|
+
* `gh api user`, a pasted log. Masking them here means the page genuinely does
|
|
31
|
+
* not carry them, rather than training callers to pass `--force`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function redactEmails(text: string): string;
|
package/dist/lib/redact.js
CHANGED
|
@@ -185,3 +185,14 @@ export function knownSecretValuesFromEnv(env = process.env) {
|
|
|
185
185
|
}
|
|
186
186
|
return out;
|
|
187
187
|
}
|
|
188
|
+
const EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
189
|
+
/**
|
|
190
|
+
* Mask email addresses in already-rendered text. A published page's sensitive-
|
|
191
|
+
* content scan refuses a body containing an email (`artifacts share` does the
|
|
192
|
+
* same), and almost every real transcript carries a few — git author addresses,
|
|
193
|
+
* `gh api user`, a pasted log. Masking them here means the page genuinely does
|
|
194
|
+
* not carry them, rather than training callers to pass `--force`.
|
|
195
|
+
*/
|
|
196
|
+
export function redactEmails(text) {
|
|
197
|
+
return text.replace(EMAIL_RE, '[EMAIL]');
|
|
198
|
+
}
|
package/dist/lib/refresh.d.ts
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* The reconcile stage behind `agents sync` (the umbrella `--local` path calls
|
|
7
7
|
* this; see sync-umbrella.ts) and any other caller that needs to re-derive local
|
|
8
8
|
* state from declared configuration. Does NOT do any git operations — that lives
|
|
9
|
-
* in `agents repo pull`.
|
|
10
|
-
* `agents repo refresh` alias.)
|
|
9
|
+
* in `agents repo pull`.
|
|
11
10
|
*/
|
|
12
11
|
import type { AgentId } from './types.js';
|
|
12
|
+
import { type ResourceSelection } from './installations/versions.js';
|
|
13
13
|
interface RefreshOptions {
|
|
14
14
|
/** Limit operations to a single agent (claude/codex/etc). Default: all installed. */
|
|
15
15
|
agentFilter?: AgentId;
|
|
@@ -22,6 +22,10 @@ interface RefreshOptions {
|
|
|
22
22
|
* (`agents sync --json` / fleet fan-out) so stdout stays a single JSON object.
|
|
23
23
|
*/
|
|
24
24
|
quiet?: boolean;
|
|
25
|
+
/** Limit reconciliation to the requested resource kinds/names. */
|
|
26
|
+
selection?: ResourceSelection;
|
|
27
|
+
/** Explicit consent for selected plugins that add executable surfaces. */
|
|
28
|
+
allowExecSurfaces?: boolean;
|
|
25
29
|
}
|
|
26
30
|
/**
|
|
27
31
|
* Re-materialize local state from declared configuration: install CLI versions,
|
package/dist/lib/refresh.js
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* The reconcile stage behind `agents sync` (the umbrella `--local` path calls
|
|
7
7
|
* this; see sync-umbrella.ts) and any other caller that needs to re-derive local
|
|
8
8
|
* state from declared configuration. Does NOT do any git operations — that lives
|
|
9
|
-
* in `agents repo pull`.
|
|
10
|
-
* `agents repo refresh` alias.)
|
|
9
|
+
* in `agents repo pull`.
|
|
11
10
|
*/
|
|
12
11
|
import * as fs from 'fs';
|
|
13
12
|
import * as path from 'path';
|
|
@@ -50,7 +49,7 @@ function migratePromptcutsToRoot(agentsDir, quiet = false) {
|
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
export async function refresh(options = {}) {
|
|
53
|
-
const { agentFilter, skipPrompts = false, skipClis = false, quiet = false } = options;
|
|
52
|
+
const { agentFilter, skipPrompts = false, skipClis = false, quiet = false, selection: requestedSelection, allowExecSurfaces = false, } = options;
|
|
54
53
|
const agentsDir = getUserAgentsDir();
|
|
55
54
|
// Gate every human progress line so --json / fleet fan-out can parse stdout.
|
|
56
55
|
const log = (...args) => { if (!quiet)
|
|
@@ -59,7 +58,8 @@ export async function refresh(options = {}) {
|
|
|
59
58
|
// synced list cannot also mean "declined and here is why" (RUSH-2700).
|
|
60
59
|
const declined = [];
|
|
61
60
|
const reconciled = [];
|
|
62
|
-
|
|
61
|
+
if (!requestedSelection)
|
|
62
|
+
migratePromptcutsToRoot(agentsDir, quiet);
|
|
63
63
|
const manifest = readManifest(agentsDir);
|
|
64
64
|
if (!manifest) {
|
|
65
65
|
log(chalk.gray(`No ${MANIFEST_FILENAME} found`));
|
|
@@ -94,17 +94,23 @@ export async function refresh(options = {}) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
// 2. Register MCP servers
|
|
97
|
-
if (manifest?.mcp && Object.keys(manifest.mcp).length > 0) {
|
|
97
|
+
if ((!requestedSelection || requestedSelection.mcp) && manifest?.mcp && Object.keys(manifest.mcp).length > 0) {
|
|
98
98
|
log(chalk.bold('\nMCP Servers:\n'));
|
|
99
99
|
for (const [name, config] of Object.entries(manifest.mcp)) {
|
|
100
|
-
if (
|
|
100
|
+
if (Array.isArray(requestedSelection?.mcp) && !requestedSelection.mcp.includes(name))
|
|
101
101
|
continue;
|
|
102
|
+
const transport = config.transport || 'stdio';
|
|
103
|
+
const commandOrUrl = transport === 'http' ? config.url : config.command;
|
|
104
|
+
if (!commandOrUrl) {
|
|
105
|
+
log(` ${chalk.cyan(name)}: ${chalk.yellow(`missing ${transport === 'http' ? 'url' : 'command'}`)}`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
102
108
|
const scopedAgents = (config.agents ? [...config.agents] : [...capableAgents('mcp')]).filter((id) => !agentFilter || id === agentFilter);
|
|
103
109
|
const scopedVersions = config.agentVersions
|
|
104
110
|
? Object.fromEntries(Object.entries(config.agentVersions).filter(([agentId]) => !agentFilter || agentId === agentFilter))
|
|
105
111
|
: undefined;
|
|
106
112
|
const targets = resolveConfiguredAgentTargets(scopedAgents, scopedVersions, capableAgents('mcp'));
|
|
107
|
-
const results = await registerMcpToTargets(targets, name,
|
|
113
|
+
const results = await registerMcpToTargets(targets, name, commandOrUrl, config.scope || 'user', transport);
|
|
108
114
|
for (const result of results) {
|
|
109
115
|
if (result.success) {
|
|
110
116
|
const label = result.version
|
|
@@ -116,22 +122,25 @@ export async function refresh(options = {}) {
|
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
// 3. Sync resources into version homes.
|
|
119
|
-
// Unattended (`skipPrompts` / `agents sync --yes --local`)
|
|
120
|
-
// version
|
|
121
|
-
//
|
|
122
|
-
// Interactive: default only
|
|
125
|
+
// Unattended (`skipPrompts` / `agents sync --yes --local`) and explicit
|
|
126
|
+
// resource selectors: every installed version. Otherwise non-default homes
|
|
127
|
+
// keep stale resources after a system update or named plugin sync.
|
|
128
|
+
// Interactive full reconcile: default only.
|
|
123
129
|
const cliStates = await getAllCliStates();
|
|
124
130
|
const agentsToSync = agentFilter ? [agentFilter] : MANAGED_AGENT_IDS;
|
|
125
131
|
const available = getAvailableResources();
|
|
126
132
|
for (const agentId of agentsToSync) {
|
|
127
|
-
|
|
133
|
+
const installedVersions = listInstalledVersions(agentId);
|
|
134
|
+
if (!cliStates[agentId]?.installed && installedVersions.length === 0)
|
|
128
135
|
continue;
|
|
129
136
|
const defaultVer = getGlobalDefault(agentId);
|
|
130
|
-
if (!defaultVer)
|
|
137
|
+
if (!defaultVer && !requestedSelection)
|
|
131
138
|
continue;
|
|
132
|
-
const versionsToSync = skipPrompts
|
|
133
|
-
?
|
|
139
|
+
const versionsToSync = requestedSelection || skipPrompts
|
|
140
|
+
? installedVersions
|
|
134
141
|
: [defaultVer];
|
|
142
|
+
if (versionsToSync.length === 0)
|
|
143
|
+
continue;
|
|
135
144
|
// Interactive-only: getActuallySyncedResources walks every skill tree with
|
|
136
145
|
// content compares (~1s/agent on a full install). The unattended path
|
|
137
146
|
// (`skipPrompts` / `agents sync --yes`) never reads these — it always
|
|
@@ -139,7 +148,7 @@ export async function refresh(options = {}) {
|
|
|
139
148
|
let actuallySynced;
|
|
140
149
|
let newResources;
|
|
141
150
|
let hasAnySynced = false;
|
|
142
|
-
if (!skipPrompts) {
|
|
151
|
+
if (!skipPrompts && !requestedSelection) {
|
|
143
152
|
actuallySynced = getActuallySyncedResources(agentId, defaultVer);
|
|
144
153
|
newResources = getNewResources(available, actuallySynced, getProjectOnlyResources());
|
|
145
154
|
hasAnySynced = actuallySynced.commands.length > 0 ||
|
|
@@ -153,7 +162,10 @@ export async function refresh(options = {}) {
|
|
|
153
162
|
try {
|
|
154
163
|
let selection;
|
|
155
164
|
let forceFullSync = false;
|
|
156
|
-
if (
|
|
165
|
+
if (requestedSelection) {
|
|
166
|
+
selection = requestedSelection;
|
|
167
|
+
}
|
|
168
|
+
else if (skipPrompts) {
|
|
157
169
|
forceFullSync = true;
|
|
158
170
|
}
|
|
159
171
|
else if (!hasAnySynced) {
|
|
@@ -176,7 +188,11 @@ export async function refresh(options = {}) {
|
|
|
176
188
|
for (const ver of versionsToSync) {
|
|
177
189
|
// Pass the already-built `available` so each version does not re-scan
|
|
178
190
|
// resource trees (RUSH-2320 #5).
|
|
179
|
-
const syncResult = syncResourcesToVersion(agentId, ver, selection, {
|
|
191
|
+
const syncResult = syncResourcesToVersion(agentId, ver, selection, {
|
|
192
|
+
available,
|
|
193
|
+
allowExecSurfaces,
|
|
194
|
+
...(forceFullSync || requestedSelection ? { force: true } : {}),
|
|
195
|
+
});
|
|
180
196
|
reconciled.push({ agent: agentId, version: ver });
|
|
181
197
|
if (syncResult.commands)
|
|
182
198
|
kinds.add('commands');
|
|
@@ -214,7 +230,7 @@ export async function refresh(options = {}) {
|
|
|
214
230
|
}
|
|
215
231
|
}
|
|
216
232
|
// 4. Register hooks as lifecycle events (same version set as resource sync)
|
|
217
|
-
const hookManifest = parseHookManifest();
|
|
233
|
+
const hookManifest = requestedSelection ? {} : parseHookManifest();
|
|
218
234
|
if (Object.keys(hookManifest).length > 0) {
|
|
219
235
|
let hookRegistered = 0;
|
|
220
236
|
const hookAgents = new Set(capableAgents('hooks'));
|
|
@@ -242,25 +258,27 @@ export async function refresh(options = {}) {
|
|
|
242
258
|
// 5. Auto-add shims to PATH
|
|
243
259
|
// Refresh the gh overload shim so `gh pr checks` escapes the GraphQL rate limit
|
|
244
260
|
// for every user, transparently (PHNX-3501). Idempotent; POSIX-only in v1.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (!isShimsInPath()) {
|
|
252
|
-
const pathResult = addShimsToPath();
|
|
253
|
-
if (pathResult.success && !pathResult.alreadyPresent) {
|
|
254
|
-
log(chalk.green(`\nAdded shims to ${pathResult.location}`));
|
|
255
|
-
log(chalk.gray(pathResult.reloadHint));
|
|
261
|
+
if (!requestedSelection) {
|
|
262
|
+
try {
|
|
263
|
+
ensureGhOverloadShim();
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Never let a shim-write hiccup break sync — real gh stays fine without it.
|
|
256
267
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
268
|
+
if (!isShimsInPath()) {
|
|
269
|
+
const pathResult = addShimsToPath();
|
|
270
|
+
if (pathResult.success && !pathResult.alreadyPresent) {
|
|
271
|
+
log(chalk.green(`\nAdded shims to ${pathResult.location}`));
|
|
272
|
+
log(chalk.gray(pathResult.reloadHint));
|
|
273
|
+
}
|
|
274
|
+
else if (!pathResult.success) {
|
|
275
|
+
log(chalk.yellow('\nCould not auto-add shims to PATH:'));
|
|
276
|
+
log(chalk.gray(getPathSetupInstructions()));
|
|
277
|
+
}
|
|
260
278
|
}
|
|
261
279
|
}
|
|
262
280
|
// 6. Prompt for missing default versions
|
|
263
|
-
if (!skipPrompts) {
|
|
281
|
+
if (!skipPrompts && !requestedSelection) {
|
|
264
282
|
const agentsNeedingDefault = [];
|
|
265
283
|
for (const agentId of agentsToSync) {
|
|
266
284
|
const versions = listInstalledVersions(agentId);
|
|
@@ -307,53 +325,55 @@ export async function refresh(options = {}) {
|
|
|
307
325
|
}
|
|
308
326
|
}
|
|
309
327
|
// 7. Install declared host-CLIs
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const missing = statuses.filter((s) => !s.installed);
|
|
316
|
-
if (missing.length > 0) {
|
|
317
|
-
log(chalk.bold('\nDeclared CLIs missing from this host:'));
|
|
318
|
-
for (const s of missing) {
|
|
319
|
-
const method = selectInstallMethod(s.manifest);
|
|
320
|
-
const action = method ? describeMethod(method) : chalk.red('no compatible install method');
|
|
321
|
-
log(` ${chalk.cyan(s.manifest.name.padEnd(20))} ${chalk.gray(action)}`);
|
|
328
|
+
if (!requestedSelection) {
|
|
329
|
+
try {
|
|
330
|
+
const { statuses, errors } = listCliStatus(process.cwd());
|
|
331
|
+
for (const err of errors) {
|
|
332
|
+
log(chalk.yellow(` CLI manifest parse error: ${err.file}: ${err.reason}`));
|
|
322
333
|
}
|
|
323
|
-
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
334
|
+
const missing = statuses.filter((s) => !s.installed);
|
|
335
|
+
if (missing.length > 0) {
|
|
336
|
+
log(chalk.bold('\nDeclared CLIs missing from this host:'));
|
|
337
|
+
for (const s of missing) {
|
|
338
|
+
const method = selectInstallMethod(s.manifest);
|
|
339
|
+
const action = method ? describeMethod(method) : chalk.red('no compatible install method');
|
|
340
|
+
log(` ${chalk.cyan(s.manifest.name.padEnd(20))} ${chalk.gray(action)}`);
|
|
341
|
+
}
|
|
342
|
+
log('');
|
|
343
|
+
if (!skipPrompts) {
|
|
344
|
+
const proceed = await confirm({ message: `Install ${missing.length} missing CLI(s) now?`, default: true });
|
|
345
|
+
if (proceed) {
|
|
346
|
+
for (const s of missing) {
|
|
347
|
+
log(chalk.bold(`\n→ ${s.manifest.name}`));
|
|
348
|
+
const result = installCli(s.manifest);
|
|
349
|
+
if (result.error) {
|
|
350
|
+
log(chalk.red(` ${result.error}`));
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (result.installed) {
|
|
354
|
+
log(chalk.green(` installed`));
|
|
355
|
+
if (s.manifest.postInstall) {
|
|
356
|
+
log(chalk.gray(s.manifest.postInstall.trim().split('\n').map((l) => ' ' + l).join('\n')));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
log(chalk.yellow(` install ran but \`${describeCheck(s.manifest.check)}\` still fails`));
|
|
338
361
|
}
|
|
339
362
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
log(chalk.gray(`Skipped. Run 'agents cli install' later.`));
|
|
343
366
|
}
|
|
344
367
|
}
|
|
345
368
|
else {
|
|
346
|
-
log(chalk.gray(`
|
|
369
|
+
log(chalk.gray(`Run 'agents cli install' to install them.`));
|
|
347
370
|
}
|
|
348
371
|
}
|
|
349
|
-
else {
|
|
350
|
-
log(chalk.gray(`Run 'agents cli install' to install them.`));
|
|
351
|
-
}
|
|
352
372
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
373
|
+
catch (err) {
|
|
374
|
+
if (!isPromptCancelled(err)) {
|
|
375
|
+
log(chalk.yellow(`CLI install skipped: ${err.message}`));
|
|
376
|
+
}
|
|
357
377
|
}
|
|
358
378
|
}
|
|
359
379
|
// A resource agents-cli refused to write is reported, never swallowed —
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const SECRETS_CLI_NAME = "secrets";
|
|
2
|
+
export declare const SECRETS_CLI_PACKAGE = "@phnx-labs/secrets-cli";
|
|
3
|
+
/** Published standalone `agents setup secrets` installs; bump with the protocol. */
|
|
4
|
+
export declare const SECRETS_CLI_VERSION = "0.1.4";
|
|
5
|
+
export declare const SECRETS_CLI_SPEC = "@phnx-labs/secrets-cli@0.1.4";
|
|
6
|
+
export declare const SECRETS_CLI_INSTALL_HINT = "npm i -g @phnx-labs/secrets-cli@0.1.4";
|
|
7
|
+
/**
|
|
8
|
+
* True when `$SECRETS_BIN` is set or a real `secrets` executable is on PATH
|
|
9
|
+
* outside agents-cli's shims dir. Does not spawn the binary.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isSecretsPresent(): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The standalone `secrets` CLI that agents-cli talks to (PHNX-3989).
|
|
3
|
+
*
|
|
4
|
+
* The engine lives in `@phnx-labs/secrets-cli`. This module is the single pin
|
|
5
|
+
* and presence check shared by the process client (`secrets-client.ts`) and
|
|
6
|
+
* `agents setup secrets` — it carries no storage. Presence is `findInPath`,
|
|
7
|
+
* which skips `~/.agents/.cache/shims`: a leftover alias there `exec`s
|
|
8
|
+
* `agents secrets` and would recurse (agi-cli#3532).
|
|
9
|
+
*
|
|
10
|
+
* Keep this file free of `cli-resources` imports: `secrets-client.ts` loads it,
|
|
11
|
+
* and a cycle through the host-CLI parser would pull yaml/spawn into the
|
|
12
|
+
* process-client module graph.
|
|
13
|
+
*/
|
|
14
|
+
import { findInPath } from './agent-spec/agents.js';
|
|
15
|
+
export const SECRETS_CLI_NAME = 'secrets';
|
|
16
|
+
export const SECRETS_CLI_PACKAGE = '@phnx-labs/secrets-cli';
|
|
17
|
+
/** Published standalone `agents setup secrets` installs; bump with the protocol. */
|
|
18
|
+
export const SECRETS_CLI_VERSION = '0.1.4';
|
|
19
|
+
export const SECRETS_CLI_SPEC = `${SECRETS_CLI_PACKAGE}@${SECRETS_CLI_VERSION}`;
|
|
20
|
+
export const SECRETS_CLI_INSTALL_HINT = `npm i -g ${SECRETS_CLI_SPEC}`;
|
|
21
|
+
/**
|
|
22
|
+
* True when `$SECRETS_BIN` is set or a real `secrets` executable is on PATH
|
|
23
|
+
* outside agents-cli's shims dir. Does not spawn the binary.
|
|
24
|
+
*/
|
|
25
|
+
export function isSecretsPresent() {
|
|
26
|
+
const explicit = process.env.SECRETS_BIN?.trim();
|
|
27
|
+
if (explicit)
|
|
28
|
+
return true;
|
|
29
|
+
return findInPath(SECRETS_CLI_NAME) !== null;
|
|
30
|
+
}
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
*/
|
|
50
50
|
import { spawn, spawnSync } from 'node:child_process';
|
|
51
51
|
import { findInPath } from './agent-spec/agents.js';
|
|
52
|
+
import { SECRETS_CLI_INSTALL_HINT } from './secrets-cli.js';
|
|
52
53
|
import { getUserAgentsDir } from './state.js';
|
|
53
54
|
/**
|
|
54
55
|
* Wire contract, mirrored from `secrets-cli/src/protocol.ts`. Both sides MUST
|
|
@@ -216,8 +217,8 @@ export function resolveSecretsBin() {
|
|
|
216
217
|
const resolved = explicit && explicit.length > 0 ? explicit : findInPath('secrets');
|
|
217
218
|
if (!resolved) {
|
|
218
219
|
throw new SecretsClientError('SECRETS_BIN_MISSING', 'The standalone `secrets` CLI was not found. Install it with:\n' +
|
|
219
|
-
|
|
220
|
-
'or
|
|
220
|
+
` ${SECRETS_CLI_INSTALL_HINT}\n` +
|
|
221
|
+
'or run `agents setup secrets`.');
|
|
221
222
|
}
|
|
222
223
|
cachedBin = resolved;
|
|
223
224
|
return resolved;
|
|
@@ -20,6 +20,13 @@ export declare function clearDetachRecord(sessionId: string): void;
|
|
|
20
20
|
export declare function listDetachRecords(): DetachRecord[];
|
|
21
21
|
/** True while the recorded headless continuation is still the live process we spawned. */
|
|
22
22
|
export declare function isHeadlessAlive(rec: DetachRecord): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Stop the detached continuation before a local foreground resume starts.
|
|
25
|
+
* The start-time check in {@link isHeadlessAlive} prevents signalling a reused
|
|
26
|
+
* PID; a continuation that ignores SIGTERM fails closed instead of running
|
|
27
|
+
* alongside a second process against the same transcript.
|
|
28
|
+
*/
|
|
29
|
+
export declare function takeOverDetachedSession(sessionId: string): Promise<boolean>;
|
|
23
30
|
/**
|
|
24
31
|
* Presence for a session id from the detach store alone:
|
|
25
32
|
* - no record -> undefined (caller decides: `attached` for a live
|
|
@@ -77,6 +77,35 @@ export function isHeadlessAlive(rec) {
|
|
|
77
77
|
}
|
|
78
78
|
return true;
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Stop the detached continuation before a local foreground resume starts.
|
|
82
|
+
* The start-time check in {@link isHeadlessAlive} prevents signalling a reused
|
|
83
|
+
* PID; a continuation that ignores SIGTERM fails closed instead of running
|
|
84
|
+
* alongside a second process against the same transcript.
|
|
85
|
+
*/
|
|
86
|
+
export async function takeOverDetachedSession(sessionId) {
|
|
87
|
+
const rec = readDetachRecord(sessionId);
|
|
88
|
+
if (!rec)
|
|
89
|
+
return false;
|
|
90
|
+
if (isHeadlessAlive(rec)) {
|
|
91
|
+
try {
|
|
92
|
+
process.kill(rec.headlessPid, 'SIGTERM');
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
if (isHeadlessAlive(rec))
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
const deadline = Date.now() + 2_000;
|
|
99
|
+
while (isHeadlessAlive(rec) && Date.now() < deadline) {
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
101
|
+
}
|
|
102
|
+
if (isHeadlessAlive(rec)) {
|
|
103
|
+
throw new Error(`Detached continuation ${rec.headlessPid} did not stop; refusing to resume a duplicate process.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
clearDetachRecord(sessionId);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
80
109
|
/**
|
|
81
110
|
* Presence for a session id from the detach store alone:
|
|
82
111
|
* - no record -> undefined (caller decides: `attached` for a live
|