@nathapp/nax-agent 0.91.6 → 0.91.7-canary.10
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 +40 -0
- package/dist/coding-tools/coding-tool-extras.d.ts +52 -0
- package/dist/coding-tools/coding-tool-extras.js +56 -0
- package/dist/coding-tools/coding-tool-support.d.ts +9 -0
- package/dist/coding-tools/coding-tool-support.js +12 -1
- package/dist/config/catalog-overrides.d.ts +9 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/internal/argv-exec.d.ts +6 -0
- package/dist/internal/argv-exec.js +158 -63
- package/dist/mcp/connect.js +26 -3
- package/dist/mcp/types.d.ts +7 -0
- package/dist/native/adapter-deps.d.ts +2 -0
- package/dist/native/adapter-deps.js +2 -0
- package/dist/native/models.js +2 -0
- package/dist/native/session/loop-events/types.d.ts +5 -0
- package/dist/native/session-adapter.js +19 -5
- package/dist/permissions/approval-memory.d.ts +18 -0
- package/dist/permissions/approval-memory.js +88 -0
- package/dist/permissions/index.d.ts +1 -0
- package/dist/permissions/index.js +1 -0
- package/dist/permissions/types.d.ts +6 -0
- package/dist/runtime/node-runtime.js +5 -0
- package/dist/sandbox/claude-settings.d.ts +2 -1
- package/dist/sandbox/claude-settings.js +7 -3
- package/dist/sandbox/defaults.js +1 -0
- package/dist/sandbox/launcher.js +7 -0
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/session/agent-session-command.d.ts +19 -0
- package/dist/session/agent-session-command.js +38 -0
- package/dist/session/agent-session-types.d.ts +26 -0
- package/dist/session/agent-session.js +86 -1
- package/dist/session/native-backend-options.d.ts +37 -2
- package/dist/session/native-backend-options.js +39 -3
- package/dist/session/native-backend.js +55 -5
- package/dist/session/native-posture-rules.d.ts +18 -0
- package/dist/session/native-posture-rules.js +33 -0
- package/dist/session/session-ask-link.d.ts +6 -0
- package/dist/session/session-ask-link.js +5 -0
- package/dist/session/session-ask-port.js +4 -0
- package/dist/session/session-backend.d.ts +23 -1
- package/dist/session/session-interaction.d.ts +2 -0
- package/dist/session/session-interaction.js +8 -1
- package/dist/session/session-tool-support.d.ts +49 -3
- package/dist/session/session-tool-support.js +66 -11
- package/dist/session-store/index.d.ts +3 -0
- package/dist/session-store/index.js +3 -0
- package/dist/session-store/session-lock-guard.d.ts +6 -0
- package/dist/session-store/session-lock-guard.js +85 -0
- package/dist/session-store/session-lock.d.ts +39 -0
- package/dist/session-store/session-lock.js +178 -0
- package/dist/session-store/session-meta.d.ts +27 -0
- package/dist/session-store/session-meta.js +20 -0
- package/dist/session-store/storage.d.ts +39 -0
- package/dist/session-store/storage.js +164 -0
- package/dist/tools/ask-request.d.ts +18 -0
- package/dist/tools/ask-request.js +30 -0
- package/dist/tools/bash-background.d.ts +74 -0
- package/dist/tools/bash-background.js +151 -0
- package/dist/tools/bash.d.ts +11 -0
- package/dist/tools/bash.js +77 -17
- package/dist/tools/git.js +10 -3
- package/dist/tools/policy-match.d.ts +2 -2
- package/dist/tools/policy-match.js +9 -6
- package/dist/tools/policy-paths-branch.js +4 -1
- package/dist/tools/provider-advertise.d.ts +7 -0
- package/dist/tools/provider-advertise.js +13 -1
- package/dist/tools/provider-types.d.ts +6 -0
- package/dist/tools/run-command-exec.js +12 -0
- package/dist/tools/run-command-surface.d.ts +28 -0
- package/dist/tools/run-command-surface.js +109 -0
- package/dist/tools/run-command.d.ts +17 -16
- package/dist/tools/run-command.js +48 -68
- package/dist/tools/runtime-calltool.d.ts +101 -0
- package/dist/tools/runtime-calltool.js +112 -3
- package/dist/tools/runtime.d.ts +12 -0
- package/dist/tools/runtime.js +36 -18
- package/dist/tools/types.d.ts +8 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,46 @@ All notable changes to `@nathapp/nax-agent` are recorded here. The format follow
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). While the version is `0.x`, a minor
|
|
5
5
|
release may change the public API.
|
|
6
6
|
|
|
7
|
+
## [0.91.7-canary.10] - 2026-10-30
|
|
8
|
+
|
|
9
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
10
|
+
|
|
11
|
+
## [0.91.7-canary.9] - 2026-10-30
|
|
12
|
+
|
|
13
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
14
|
+
|
|
15
|
+
## [0.91.7-canary.8] - 2026-10-29
|
|
16
|
+
|
|
17
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
18
|
+
|
|
19
|
+
## [0.91.7-canary.7] - 2026-10-29
|
|
20
|
+
|
|
21
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
22
|
+
|
|
23
|
+
## [0.91.7-canary.6] - 2026-10-28
|
|
24
|
+
|
|
25
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
26
|
+
|
|
27
|
+
## [0.91.7-canary.5] - 2026-10-27
|
|
28
|
+
|
|
29
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
30
|
+
|
|
31
|
+
## [0.91.7-canary.4] - 2026-10-27
|
|
32
|
+
|
|
33
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
34
|
+
|
|
35
|
+
## [0.91.7-canary.3] - 2026-10-27
|
|
36
|
+
|
|
37
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
38
|
+
|
|
39
|
+
## [0.91.7-canary.2] - 2026-10-26
|
|
40
|
+
|
|
41
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
42
|
+
|
|
43
|
+
## [0.91.7-canary.1] - 2026-10-24
|
|
44
|
+
|
|
45
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
46
|
+
|
|
7
47
|
## [0.91.6] - 2026-10-24
|
|
8
48
|
|
|
9
49
|
- No changes in this package. Released in lockstep with the other nax packages.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { BashApprovalMode } from "#src/config/bash-approval";
|
|
10
10
|
import type { QualityCommandSpec } from "#src/internal/command-spec/index";
|
|
11
11
|
import type { CommandLauncher } from "#src/sandbox/index";
|
|
12
|
+
import type { BackgroundCommandPort } from "#src/tools/bash-background";
|
|
12
13
|
import { type CodingTool, type DeclaredCommandRunner, type ToolGrant } from "#src/tools/index";
|
|
13
14
|
export interface DeclaredCommandToolsArgs {
|
|
14
15
|
readonly declaredCommands: ReadonlyMap<string, QualityCommandSpec>;
|
|
@@ -31,5 +32,56 @@ export interface DeclaredCommandToolsArgs {
|
|
|
31
32
|
readonly runDeclaredCommand?: DeclaredCommandRunner;
|
|
32
33
|
/** P4: how Bash and Exec-branch commands run; absent = direct spawn (tests). */
|
|
33
34
|
readonly launcher?: CommandLauncher;
|
|
35
|
+
/**
|
|
36
|
+
* US-003/US-008: whether this session offers the per-call `unsandboxed`
|
|
37
|
+
* argument — on Bash (`createBashTool`) and on RunCommand's argv branch
|
|
38
|
+
* (`createRunCommandTool`'s `exec` option, US-008). Advertisement only:
|
|
39
|
+
* RunCommand's DECLARED-command branch has no argv to unwrap and refuses the
|
|
40
|
+
* flag by name.
|
|
41
|
+
*/
|
|
42
|
+
readonly unsandboxedCommands?: boolean;
|
|
43
|
+
readonly backgroundCommands?: BackgroundCommandPort;
|
|
34
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* US-003: `unsandboxedCommands` as a spreadable field — present only when the
|
|
47
|
+
* session opted in, so "off" and "absent" stay one shape all the way down to
|
|
48
|
+
* `createBashTool` and to RunCommand's `exec` option (US-008).
|
|
49
|
+
*
|
|
50
|
+
* A helper rather than an inline `cond ? { field } : {}` spread because
|
|
51
|
+
* `buildDeclaredCommandTools` and `buildCodingToolSupport` both sit at their
|
|
52
|
+
* cognitive-complexity baseline and a ternary inside either one would raise it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function unsandboxedToolOptions(args: {
|
|
55
|
+
readonly unsandboxedCommands?: boolean;
|
|
56
|
+
}): {
|
|
57
|
+
readonly unsandboxedCommands?: true;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* US-005: the same option as the RUNTIME's field — whether a `Bash`/`Exec`
|
|
61
|
+
* call may run outside the sandbox at all, plus the Bash approval mode that
|
|
62
|
+
* decides how an enabled request is routed.
|
|
63
|
+
*
|
|
64
|
+
* Absent when the session did not opt in, so "off" and "absent" stay one shape
|
|
65
|
+
* all the way to `createCodingToolRuntime` (absent means disabled). A helper
|
|
66
|
+
* for the same reason as `unsandboxedToolOptions`: `buildCodingToolSupport`
|
|
67
|
+
* sits at its cognitive-complexity baseline and a ternary inside it would
|
|
68
|
+
* raise it.
|
|
69
|
+
*/
|
|
70
|
+
export declare function unsandboxedRuntimeOption(args: {
|
|
71
|
+
readonly unsandboxedCommands?: boolean;
|
|
72
|
+
readonly askOnUnsandboxed?: boolean;
|
|
73
|
+
readonly bashApproval: BashApprovalMode;
|
|
74
|
+
}): {
|
|
75
|
+
readonly unsandboxed?: {
|
|
76
|
+
readonly enabled: boolean;
|
|
77
|
+
readonly bashApproval: BashApprovalMode;
|
|
78
|
+
readonly alwaysAsk?: boolean;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
35
81
|
export declare function buildDeclaredCommandTools(args: DeclaredCommandToolsArgs): CodingTool[];
|
|
82
|
+
/** Preserve absence at every session/support forwarding hop. */
|
|
83
|
+
export declare function backgroundToolOptions(args?: {
|
|
84
|
+
readonly backgroundCommands?: BackgroundCommandPort;
|
|
85
|
+
}): {
|
|
86
|
+
readonly backgroundCommands?: BackgroundCommandPort;
|
|
87
|
+
};
|
|
@@ -1,4 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session-local tools an operation declares: RunCommand (its declared
|
|
3
|
+
* commands and, when `Exec` is declared, its argv branch) and Bash.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from coding-tool-support.ts to keep that file under its 600-line
|
|
6
|
+
* source limit ahead of the P4 sandbox wiring. A pure move: every comment that
|
|
7
|
+
* explains a field below travelled with it.
|
|
8
|
+
*/
|
|
1
9
|
import { createBashTool, createRunCommandTool, } from "#src/tools/index";
|
|
10
|
+
/**
|
|
11
|
+
* US-003: `unsandboxedCommands` as a spreadable field — present only when the
|
|
12
|
+
* session opted in, so "off" and "absent" stay one shape all the way down to
|
|
13
|
+
* `createBashTool` and to RunCommand's `exec` option (US-008).
|
|
14
|
+
*
|
|
15
|
+
* A helper rather than an inline `cond ? { field } : {}` spread because
|
|
16
|
+
* `buildDeclaredCommandTools` and `buildCodingToolSupport` both sit at their
|
|
17
|
+
* cognitive-complexity baseline and a ternary inside either one would raise it.
|
|
18
|
+
*/
|
|
19
|
+
export function unsandboxedToolOptions(args) {
|
|
20
|
+
return args.unsandboxedCommands === true ? { unsandboxedCommands: true } : {};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* US-005: the same option as the RUNTIME's field — whether a `Bash`/`Exec`
|
|
24
|
+
* call may run outside the sandbox at all, plus the Bash approval mode that
|
|
25
|
+
* decides how an enabled request is routed.
|
|
26
|
+
*
|
|
27
|
+
* Absent when the session did not opt in, so "off" and "absent" stay one shape
|
|
28
|
+
* all the way to `createCodingToolRuntime` (absent means disabled). A helper
|
|
29
|
+
* for the same reason as `unsandboxedToolOptions`: `buildCodingToolSupport`
|
|
30
|
+
* sits at its cognitive-complexity baseline and a ternary inside it would
|
|
31
|
+
* raise it.
|
|
32
|
+
*/
|
|
33
|
+
export function unsandboxedRuntimeOption(args) {
|
|
34
|
+
if (args.unsandboxedCommands !== true)
|
|
35
|
+
return {};
|
|
36
|
+
return {
|
|
37
|
+
unsandboxed: {
|
|
38
|
+
enabled: true,
|
|
39
|
+
bashApproval: args.bashApproval,
|
|
40
|
+
...(args.askOnUnsandboxed === true ? { alwaysAsk: true } : {}),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
2
44
|
export function buildDeclaredCommandTools(args) {
|
|
3
45
|
return [
|
|
4
46
|
...(args.declaredCommands.size > 0 || args.allowExec
|
|
@@ -31,6 +73,10 @@ export function buildDeclaredCommandTools(args) {
|
|
|
31
73
|
patterns: args.execGrant?.patterns ?? [],
|
|
32
74
|
...(args.packageName !== undefined ? { packageName: args.packageName } : {}),
|
|
33
75
|
...(args.launcher !== undefined ? { launcher: args.launcher } : {}),
|
|
76
|
+
// US-008: the same option Bash takes, forwarded to the argv
|
|
77
|
+
// branch's advertisement through the same helper — one
|
|
78
|
+
// shape for "off" and "absent" on both tools.
|
|
79
|
+
...unsandboxedToolOptions(args),
|
|
34
80
|
},
|
|
35
81
|
}
|
|
36
82
|
: {}),
|
|
@@ -49,8 +95,18 @@ export function buildDeclaredCommandTools(args) {
|
|
|
49
95
|
bashApproval: args.bashApproval,
|
|
50
96
|
...(args.humanApproval === true ? { humanApproval: true } : {}),
|
|
51
97
|
...(args.launcher !== undefined ? { launcher: args.launcher } : {}),
|
|
98
|
+
...unsandboxedToolOptions(args),
|
|
99
|
+
...bashBackgroundOption(args),
|
|
52
100
|
}),
|
|
53
101
|
]
|
|
54
102
|
: []),
|
|
55
103
|
];
|
|
56
104
|
}
|
|
105
|
+
/** Preserve absence at every session/support forwarding hop. */
|
|
106
|
+
export function backgroundToolOptions(args) {
|
|
107
|
+
return args?.backgroundCommands === undefined ? {} : { backgroundCommands: args.backgroundCommands };
|
|
108
|
+
}
|
|
109
|
+
/** Only Bash receives this capability; RunCommand's argv branch stays unchanged. */
|
|
110
|
+
function bashBackgroundOption(args) {
|
|
111
|
+
return args.backgroundCommands === undefined ? {} : { background: args.backgroundCommands };
|
|
112
|
+
}
|
|
@@ -14,6 +14,7 @@ import type { BashApprovalMode } from "#src/config/bash-approval";
|
|
|
14
14
|
import type { QualityCommandSpec } from "#src/internal/command-spec/index";
|
|
15
15
|
import type { AskResolver } from "#src/permissions/index";
|
|
16
16
|
import type { CommandLauncher } from "#src/sandbox/index";
|
|
17
|
+
import type { BackgroundCommandPort } from "#src/tools/bash-background";
|
|
17
18
|
import { type CodingTool, type CodingToolName, type CodingToolRuntime, type DeclaredCommandRunner, type ProtectedPathsPolicy, type ToolAuditSink, type ToolGrant, type ToolPatternNarrowing, type ToolSurfaceDraft, type ToolSurfaceRecord } from "#src/tools/index";
|
|
18
19
|
import type { OwnedPathsPolicy } from "#src/tools/owned-paths";
|
|
19
20
|
export interface CodingToolSupport {
|
|
@@ -142,6 +143,14 @@ export declare function buildCodingToolSupport(args: {
|
|
|
142
143
|
commandShadow?: CommandShadow;
|
|
143
144
|
/** P4: resolved by resolveCodingToolSupport (async); data only here. */
|
|
144
145
|
launcher?: CommandLauncher;
|
|
146
|
+
/**
|
|
147
|
+
* US-003: whether this session offers Bash's per-call `unsandboxed` argument
|
|
148
|
+
* (the session profile's `allowUnsandboxedCommands`). Advertisement only —
|
|
149
|
+
* the runtime's own reading of the field is a later story.
|
|
150
|
+
*/
|
|
151
|
+
unsandboxedCommands?: boolean;
|
|
152
|
+
backgroundCommands?: BackgroundCommandPort;
|
|
153
|
+
askOnUnsandboxed?: boolean;
|
|
145
154
|
/** US-001: forwarded into ToolRunContext so Bash/Exec can SIGKILL on cancel. */
|
|
146
155
|
abortSignal?: AbortSignal;
|
|
147
156
|
/**
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { getSafeLogger, NaxError } from "#src/infra/index";
|
|
12
12
|
import { advertisedSchemaBytes, BASH_TOOL_NAME, compileToolPolicy, createCodingToolRuntime, createNoOpToolAuditSink, createToolAuditSink, EXEC_TOOL_NAME, } from "#src/tools/index";
|
|
13
13
|
import { resolveBashSupport } from "./coding-tool-bash.js";
|
|
14
|
-
import { buildDeclaredCommandTools } from "./coding-tool-extras.js";
|
|
14
|
+
import { backgroundToolOptions, buildDeclaredCommandTools, unsandboxedRuntimeOption, unsandboxedToolOptions, } from "./coding-tool-extras.js";
|
|
15
15
|
import { isTempConfined, rawScreenOptionsFor } from "./coding-tool-sandbox.js";
|
|
16
16
|
export function buildCodingToolSupport(args) {
|
|
17
17
|
if (args.declared.length === 0)
|
|
@@ -119,9 +119,20 @@ export function buildCodingToolSupport(args) {
|
|
|
119
119
|
...(args.stripEnvVars !== undefined ? { stripEnvVars: args.stripEnvVars } : {}),
|
|
120
120
|
...(args.shell !== undefined ? { shell: args.shell } : {}),
|
|
121
121
|
...(args.launcher !== undefined ? { launcher: args.launcher } : {}),
|
|
122
|
+
...unsandboxedToolOptions(args),
|
|
123
|
+
...backgroundToolOptions(args),
|
|
122
124
|
}),
|
|
123
125
|
],
|
|
124
126
|
...(args.providerIdByTool !== undefined ? { providerIdByTool: args.providerIdByTool } : {}),
|
|
127
|
+
// US-005: the escape option reaches the RUNTIME too, not just the Bash
|
|
128
|
+
// schema — with the locally resolved mode, so a caller that left
|
|
129
|
+
// `bashApproval` unset cannot enable an escape under a mode it did not
|
|
130
|
+
// choose.
|
|
131
|
+
...unsandboxedRuntimeOption({
|
|
132
|
+
unsandboxedCommands: args.unsandboxedCommands,
|
|
133
|
+
askOnUnsandboxed: args.askOnUnsandboxed,
|
|
134
|
+
bashApproval,
|
|
135
|
+
}),
|
|
125
136
|
});
|
|
126
137
|
const tools = runtime.advertised(advertised);
|
|
127
138
|
// The fixed per-hop cost of advertising provider tools: their schemas enter
|
|
@@ -91,6 +91,11 @@ export interface CatalogModelOverride {
|
|
|
91
91
|
* `protocol: "openai-completions"` (nax-ai `assertOverrideModelRouting`).
|
|
92
92
|
*/
|
|
93
93
|
openRouterRouting?: OpenRouterRouting;
|
|
94
|
+
/**
|
|
95
|
+
* Thinking wire format for an `openai-completions` model (nax#2696).
|
|
96
|
+
* Mirrored from nax-ai's ThinkingFormat; omit to inherit the provider's format.
|
|
97
|
+
*/
|
|
98
|
+
thinkingFormat?: "openai" | "qwen" | "qwen-chat-template" | "deepseek" | "zai" | "openrouter";
|
|
94
99
|
}
|
|
95
100
|
/** Provider-scoped catalog overrides — maps 1:1 onto nax-ai's `ProviderOverride[]`. */
|
|
96
101
|
export interface ProviderCatalogOverride {
|
|
@@ -100,6 +105,10 @@ export interface ProviderCatalogOverride {
|
|
|
100
105
|
* not to `models` individually, despite sitting alongside a model list.
|
|
101
106
|
*/
|
|
102
107
|
baseUrl?: string;
|
|
108
|
+
/** Environment variable supplying a custom provider's bearer credential. */
|
|
109
|
+
authEnv?: string;
|
|
110
|
+
/** Config/precheck opt-in only; never forwarded to nax-ai or enforced here. */
|
|
111
|
+
allowInsecureHttp?: boolean;
|
|
103
112
|
/** Provider-wide extra request headers (nax#2019). May carry credentials. */
|
|
104
113
|
headers?: Record<string, string>;
|
|
105
114
|
models: CatalogModelOverride[];
|
package/dist/index.d.ts
CHANGED
|
@@ -24,13 +24,14 @@ export { AuthCancelledError, type AuthEvent, type AuthInteraction, type AuthLink
|
|
|
24
24
|
export type { NativeSessionAdapterOptions } from "#src/native/session-adapter";
|
|
25
25
|
export type { AfterResponsePatch, AfterResponsePayload, AfterToolPatch, AfterToolPayload, BeforeCompactionPatch, BeforeCompactionPayload, BeforeRequestPatch, BeforeRequestPayload, BeforeToolOutcome, BeforeToolPayload, BeforeTurnEndPatch, BeforeTurnEndPayload, BeforeTurnPatch, BeforeTurnPayload, CompleteCallOptions, ExternalHandlerOf, LoopEvent, LoopEventMap, LoopHandlerContext, LoopHandlerEntry, LoopHandlerSet, PatchOf, PayloadOf, TransformContextPatch, TransformContextPayload, } from "#src/native/session/loop-events/types";
|
|
26
26
|
export { displayToolInput, TOOL_CALL_INPUT_BYTES, TOOL_RESULT_PREVIEW_BYTES, toolResultPreview, } from "#src/native/session/turn-event-emitter";
|
|
27
|
-
export { type ApprovalAuditRow, type ApprovalEntry, type ApprovalsFile, type ApprovalsFileRead, type ApprovalsFileState, type ApprovalsTaint, ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_PROFILE_REASON, ASK_TIMEOUT_REASON, ASK_UNAVAILABLE_REASON, ASK_UNSHOWABLE_REASON, type AskControl, type AskDecidedBy, type AskDecision, type AskLink, type AskLinkOutcome, type AskRequest, type AskResolver, type AskVerdict, appendApproval, appendApprovalAudit, approvalId, approvalsPath, type BashLexResult, type BashRedirect, type BashSegment, type BashSegmentSeparator, type BashToken, type ClearTaintOutcome, chainAskLinks, clearApprovalsTaint, createApprovalsLink, findApproval, findSecretSpans, headlessAskResolver, isForgeCapable, lexBashCommand, type MaskResult, maskForPrompt, type PrepareApprovalsStoreOptions, parseRuleList, parseToolExpression, prepareApprovalsStore, type RemovalDecision, type RemovalResult, type ResolvedPermissions, readApprovals, readApprovalsFile, readApprovalsFileDetailed, redactForRow, redactRowStrings, removeApprovals, type SecretSpan, taintApprovals, writeApprovalsFile, } from "#src/permissions/index";
|
|
27
|
+
export { type ApprovalAuditRow, type ApprovalEntry, type ApprovalMemoryDecision, type ApprovalMemoryRequest, approvalMemoryApplies, approvalMemoryKey, type ApprovalsFile, type ApprovalsFileRead, type ApprovalsFileState, type ApprovalsTaint, ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_PROFILE_REASON, ASK_TIMEOUT_REASON, ASK_UNAVAILABLE_REASON, ASK_UNSHOWABLE_REASON, type AskControl, type AskDecidedBy, type AskDecision, type AskLink, type AskLinkOutcome, type AskRequest, type AskResolver, type AskVerdict, appendApproval, appendApprovalAudit, approvalId, approvalsPath, type BashLexResult, type BashRedirect, type BashSegment, type BashSegmentSeparator, type BashToken, type ClearTaintOutcome, chainAskLinks, clearApprovalsTaint, createApprovalsLink, findApproval, findSecretSpans, headlessAskResolver, isForgeCapable, lexBashCommand, type MaskResult, maskForPrompt, type PrepareApprovalsStoreOptions, parseRuleList, parseToolExpression, prepareApprovalsStore, type RemovalDecision, type RemovalResult, type ResolvedPermissions, readApprovals, readApprovalsFile, readApprovalsFileDetailed, redactForRow, redactRowStrings, removeApprovals, type SecretSpan, taintApprovals, writeApprovalsFile, } from "#src/permissions/index";
|
|
28
28
|
export { type AgentGlobOptions, type AgentRuntime, type AgentSpawnOptions, type AgentSpawnResult, type AgentSpawnStdin, getAgentRuntime, nodeRuntime, setAgentRuntime, } from "#src/runtime/index";
|
|
29
29
|
export { buildSandboxPolicy, type CommandLauncher, type CommandLauncherOptions, createCommandLauncher, createSrtBackend, DISABLED_SANDBOX_STATE, SANDBOXED_ENV, defaultTempRoots, denialHintLine, type GitLayout, type LaunchRequest, type LaunchResult, type LaunchSpec, LIKELY_SANDBOX_DENIAL, listCredentialFiles, listGitGuardFiles, listNaxEntries, type ProbeResult, probeSandbox, probeSandboxOnce, quoteArgvForShell, rawBashRefusalReason, resetSandboxBackend, resolveGitLayout, runTempRoots, runTmpRoot, type SandboxBackend, type SandboxBackendName, type SandboxNetworkPolicy, type SandboxPolicy, type SandboxPolicyInput, type SandboxRecord, type SandboxState, type SandboxWrapRequest, type SessionSandbox, sessionSandboxFrom, sandboxBackendFor, sandboxSentence, sessionTmpDir, sessionTmpDirUnder, strayCommonDirTripwire, unsandboxedSentence, warnSandboxUnavailableOnce, } from "#src/sandbox/index";
|
|
30
|
+
export { createSessionStorage, defaultSessionsDir, processAlive, LIST_PAGE_SIZE, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, SESSION_ID_PATTERN, SESSION_META_MODES, type SessionMeta, type SessionMetaMode, type SessionListEntry, type SessionListPage, type SessionLockHolder, type SessionLockHolderState, type SessionStorage, type SessionStorageDeps, } from "#src/session-store/index";
|
|
30
31
|
export type { AdapterFailure } from "#src/session/adapter-failure";
|
|
31
32
|
export { createAgentSession, resumeAgentSession } from "#src/session/agent-session";
|
|
32
33
|
export { AgentSessionError, type AgentSessionErrorCode } from "#src/session/agent-session-errors";
|
|
33
|
-
export type { AgentSession, AgentSessionHostPorts, AgentSessionProfile, AnswerReply, AnswerStatus, ApprovalDecidedBy, CostSource, CreateAgentSessionOptions, EmbedderTool, EmbedderToolContext, EmbedderToolResult, SessionEvent, SessionEventBase, SessionEventBody, TurnEndStatus, } from "#src/session/agent-session-types";
|
|
34
|
+
export type { AgentSession, AgentSessionHostPorts, AgentSessionProfile, AnswerReply, AnswerStatus, ApprovalDecidedBy, CostSource, CommandRunResult, CreateAgentSessionOptions, EmbedderTool, EmbedderToolContext, EmbedderToolResult, SessionConfigureInput, SessionEvent, SessionEventBase, SessionEventBody, TurnEndStatus, } from "#src/session/agent-session-types";
|
|
34
35
|
export type { AgentAwaitingHumanEvent, AgentCallEndedEvent, AgentCallStartedEvent, AgentMessageUpdateEvent, AgentProcessUpdateEvent, AgentStreamEvent, AgentStreamEventBase, AgentThinkingUpdateEvent, AgentToolCallUpdateEvent, AgentUsageUpdateEvent, } from "#src/session/agent-stream-events";
|
|
35
36
|
export { type AdapterInteraction, type AdapterInteractionResponse, type InteractionHandler, NO_OP_INTERACTION_HANDLER, } from "#src/session/interaction-handler";
|
|
36
37
|
export { nativeBackend } from "#src/session/native-backend";
|
|
@@ -45,3 +46,4 @@ export type { TurnEvent, TurnEventSink } from "#src/session/turn-event";
|
|
|
45
46
|
export { adaptProviderTool, advertisedSchemaBytes, applyModelTruncationPolicy, BASH_TIMEOUT_MS, BASH_TOOL_NAME, buildCommitArgvs, buildGitArgv, buildGrepArgv, type CodingTool, type CodingToolName, type CodingToolOutcome, type CodingToolRuntime, classifyExec, compileToolPolicy, createBashTool, createCodingToolRuntime, createNoOpToolAuditSink, createRunCommandTool, createToolAuditSink, cutBufferToByteCap, DEFAULT_BASH_SHELL, DEFAULT_LOG_FORMAT, DEFAULT_LOG_MAX_COUNT, DEFAULT_TOOL_MAX_BYTES, DEFAULT_TOOL_MAX_FILE_BYTES, type DeclaredCommandRequest, type DeclaredCommandResult, type DeclaredCommandRunner, deleteTool, EXEC_TOOL_NAME, type ExecTarget, editTool, expandMcpRuleGrants, expandProviderGrants, flushOpenToolAuditSinks, GIT_ESCAPE_FLAGS, GIT_READ_VERBS, getCodingTool, gitCommitTool, gitExcludePathspecsOf, gitIgnorePatternsOf, gitTool, globTool, grepTool, isKnownManager, listCodingTools, MAX_PROVIDER_DESCRIPTION_BYTES, MAX_PROVIDER_SCHEMA_BYTES, MCP_INSTRUCTIONS_MAX_CHARS_PER_SERVER, MCP_INSTRUCTIONS_MAX_CHARS_TOTAL, MCP_RULE_TOOL, MODEL_MAX_BYTES, MODEL_MAX_LINE_CHARS, MODEL_MAX_LINES, type ModelTruncationOptions, mcpRuleAdmits, type NormalizeInput, type NormalizeResult, namespacedToolName, narrowGrants, normalizeExec, normalizeManagerBinary, type PolicyVerdict, PROVIDER_ID_RE, type ProtectedPathsPolicy, type ProviderGrantEntry, type ProviderKind, type ProviderTool, partitionMcpRules, partitionNaxOwnedPaths, providerAttachesTo, READ_CEILING, RESERVED_TOOL_NAMES, type ReadFileSliceOptions, type ReadFileSliceResult, type RegisteredSink, type ResolvedProviderTools, readFileSlice, readTool, registerBuiltinCodingTools, registerBuiltinTool, registerCodingTool, registerToolAuditSink, renderProviderInstructions, requestCapabilityTool, resolveProviderTools, resolveWithin, SCRATCHPAD_DIR, SPILL_DIR, type SpillRequest, sanitizeProviderTools, scratchpadListTool, scratchpadReadTool, scratchpadWriteTool, spillRelativePath, substituteCommand, substituteCommandSpec, type ToolAuditHeader, type ToolAuditSink, type ToolCallContext, type ToolCallRecord, type ToolGrant, type ToolPatternNarrowing, type ToolPolicy, type ToolPolicyOptions, type ToolProvider, type ToolResult, type ToolRunContext, type ToolScope, type ToolSurfaceDraft, type ToolSurfaceRecord, type TruncateForModelOptions, type TruncationDirection, type TruncationResult, truncateForModel, truncationDirectionFor, unregisterToolAuditSink, validateProviderId, writeSpill, writeTool, } from "#src/tools/index";
|
|
46
47
|
export { compileArgvPattern, matchesArgvPattern, type CompiledPattern } from "#src/tools/policy-match";
|
|
47
48
|
export { EMPTY_OWNED_PATHS_POLICY, type OwnedBashCandidate, type OwnedPathsPolicy, } from "#src/tools/owned-paths";
|
|
49
|
+
export type { BackgroundCommandPort, BackgroundCommandHandle, BackgroundCommandSink, BackgroundCommandEnd, ForegroundCommand, } from "#src/tools/bash-background";
|
package/dist/index.js
CHANGED
|
@@ -19,9 +19,10 @@ export { NaxError } from "#src/infra/nax-error";
|
|
|
19
19
|
export { createTerminalAuthInteraction, PLAIN_STYLE, PromptCancelledError, } from "#src/terminal-auth/index";
|
|
20
20
|
export { AuthCancelledError, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, createFileTranscriptStore, createMemoryTranscriptStore, credentialFilePath, DEFAULT_PI_AUTH_PATH, importPiCredentials, listProviderModels, listStoredProviders, loginProviderIds, MAX_RETAINED_TRANSCRIPTS, NATIVE_AGENT, NativeSessionAdapter, NativeSessionUnsupportedError, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, providersWithoutCredentials, pruneRetainedTranscripts, removeStoredProvider, resolveNativeId, runLogin, servedAuth, } from "#src/native/index";
|
|
21
21
|
export { displayToolInput, TOOL_CALL_INPUT_BYTES, TOOL_RESULT_PREVIEW_BYTES, toolResultPreview, } from "#src/native/session/turn-event-emitter";
|
|
22
|
-
export { ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_PROFILE_REASON, ASK_TIMEOUT_REASON, ASK_UNAVAILABLE_REASON, ASK_UNSHOWABLE_REASON, appendApproval, appendApprovalAudit, approvalId, approvalsPath, chainAskLinks, clearApprovalsTaint, createApprovalsLink, findApproval, findSecretSpans, headlessAskResolver, isForgeCapable, lexBashCommand, maskForPrompt, parseRuleList, parseToolExpression, prepareApprovalsStore, readApprovals, readApprovalsFile, readApprovalsFileDetailed, redactForRow, redactRowStrings, removeApprovals, taintApprovals, writeApprovalsFile, } from "#src/permissions/index";
|
|
22
|
+
export { approvalMemoryApplies, approvalMemoryKey, ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_PROFILE_REASON, ASK_TIMEOUT_REASON, ASK_UNAVAILABLE_REASON, ASK_UNSHOWABLE_REASON, appendApproval, appendApprovalAudit, approvalId, approvalsPath, chainAskLinks, clearApprovalsTaint, createApprovalsLink, findApproval, findSecretSpans, headlessAskResolver, isForgeCapable, lexBashCommand, maskForPrompt, parseRuleList, parseToolExpression, prepareApprovalsStore, readApprovals, readApprovalsFile, readApprovalsFileDetailed, redactForRow, redactRowStrings, removeApprovals, taintApprovals, writeApprovalsFile, } from "#src/permissions/index";
|
|
23
23
|
export { getAgentRuntime, nodeRuntime, setAgentRuntime, } from "#src/runtime/index";
|
|
24
24
|
export { buildSandboxPolicy, createCommandLauncher, createSrtBackend, DISABLED_SANDBOX_STATE, SANDBOXED_ENV, defaultTempRoots, denialHintLine, LIKELY_SANDBOX_DENIAL, listCredentialFiles, listGitGuardFiles, listNaxEntries, probeSandbox, probeSandboxOnce, quoteArgvForShell, rawBashRefusalReason, resetSandboxBackend, resolveGitLayout, runTempRoots, runTmpRoot, sessionSandboxFrom, sandboxBackendFor, sandboxSentence, sessionTmpDir, sessionTmpDirUnder, strayCommonDirTripwire, unsandboxedSentence, warnSandboxUnavailableOnce, } from "#src/sandbox/index";
|
|
25
|
+
export { createSessionStorage, defaultSessionsDir, processAlive, LIST_PAGE_SIZE, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, SESSION_ID_PATTERN, SESSION_META_MODES, } from "#src/session-store/index";
|
|
25
26
|
export { createAgentSession, resumeAgentSession } from "#src/session/agent-session";
|
|
26
27
|
export { AgentSessionError } from "#src/session/agent-session-errors";
|
|
27
28
|
export { NO_OP_INTERACTION_HANDLER, } from "#src/session/interaction-handler";
|
|
@@ -18,6 +18,10 @@ export interface RunArgvOptions {
|
|
|
18
18
|
* and the result resolves with `aborted: true`. US-001.
|
|
19
19
|
*/
|
|
20
20
|
readonly signal?: AbortSignal;
|
|
21
|
+
/** Abort to remove only the deadline, without cancelling the command. */
|
|
22
|
+
readonly releaseDeadline?: AbortSignal;
|
|
23
|
+
/** Observes every read chunk before capture; observer exceptions are ignored. */
|
|
24
|
+
readonly onOutput?: (stream: "stdout" | "stderr", chunk: Uint8Array) => void;
|
|
21
25
|
}
|
|
22
26
|
export interface ArgvExecResult {
|
|
23
27
|
readonly exitCode: number;
|
|
@@ -45,6 +49,8 @@ export declare const _argvExecDeps: {
|
|
|
45
49
|
* stays at the DRAIN_GRACE_MS constant.
|
|
46
50
|
*/
|
|
47
51
|
drainGraceMs: number;
|
|
52
|
+
/** Per-stream capture cap (STREAM_CAPTURE_BYTES); a test shrinks it to exercise the cut. */
|
|
53
|
+
streamCaptureBytes: number;
|
|
48
54
|
/**
|
|
49
55
|
* Injectable timer pair — lets the BUG-13 "hung install" test drive the
|
|
50
56
|
* timeout off a virtual clock instead of waiting the full 1s schema minimum.
|
|
@@ -23,6 +23,15 @@ import { killProcessGroup } from "./process-kill.js";
|
|
|
23
23
|
* Injectable for tests; not exposed as user configuration.
|
|
24
24
|
*/
|
|
25
25
|
export const DRAIN_GRACE_MS = 500;
|
|
26
|
+
/**
|
|
27
|
+
* Most bytes of ONE stream runArgv keeps in memory: its first half and its last half, with the
|
|
28
|
+
* middle replaced by a marker naming how much was dropped. Callers cap what they show far below
|
|
29
|
+
* this (Bash at READ_CEILING, 2 MB), so it bounds the work rather than the answer: before it, a
|
|
30
|
+
* `yes` or a looping test runner was buffered whole for up to the timeout and could exhaust the
|
|
31
|
+
* orchestrator's memory. The stream is still read to EOF, so the child never blocks or takes a
|
|
32
|
+
* SIGPIPE on a full pipe; the tail survives because that is where a failure's error usually is.
|
|
33
|
+
*/
|
|
34
|
+
const STREAM_CAPTURE_BYTES = 8_000_000;
|
|
26
35
|
/** Injectable seam, mirroring _worktreeDependencyDeps. */
|
|
27
36
|
export const _argvExecDeps = {
|
|
28
37
|
spawn: runtimeSpawn,
|
|
@@ -33,6 +42,8 @@ export const _argvExecDeps = {
|
|
|
33
42
|
* stays at the DRAIN_GRACE_MS constant.
|
|
34
43
|
*/
|
|
35
44
|
drainGraceMs: DRAIN_GRACE_MS,
|
|
45
|
+
/** Per-stream capture cap (STREAM_CAPTURE_BYTES); a test shrinks it to exercise the cut. */
|
|
46
|
+
streamCaptureBytes: STREAM_CAPTURE_BYTES,
|
|
36
47
|
/**
|
|
37
48
|
* Injectable timer pair — lets the BUG-13 "hung install" test drive the
|
|
38
49
|
* timeout off a virtual clock instead of waiting the full 1s schema minimum.
|
|
@@ -73,13 +84,12 @@ function buildEnv(options) {
|
|
|
73
84
|
* after a process-group kill: a child that ignores the kill cannot pin
|
|
74
85
|
* runArgv forever on its still-open pipe.
|
|
75
86
|
*/
|
|
76
|
-
async function drainToEof(stream, signal) {
|
|
87
|
+
async function drainToEof(stream, signal, onChunk) {
|
|
77
88
|
const reader = stream.getReader();
|
|
78
|
-
const
|
|
79
|
-
let text = "";
|
|
89
|
+
const capture = new BoundedCapture(_argvExecDeps.streamCaptureBytes);
|
|
80
90
|
const onAbort = () => {
|
|
81
91
|
// Closing the stream forces the in-flight `read()` below to resolve with
|
|
82
|
-
// `{done: true}`; any bytes already captured stay in `
|
|
92
|
+
// `{done: true}`; any bytes already captured stay in `capture`.
|
|
83
93
|
reader.cancel().catch(() => {
|
|
84
94
|
// Cancelling after the pipe already closed is a no-op race; ignore.
|
|
85
95
|
});
|
|
@@ -90,16 +100,23 @@ async function drainToEof(stream, signal) {
|
|
|
90
100
|
const { done, value } = await reader.read();
|
|
91
101
|
if (done === true)
|
|
92
102
|
break;
|
|
93
|
-
if (value !== undefined)
|
|
94
|
-
|
|
103
|
+
if (value !== undefined) {
|
|
104
|
+
try {
|
|
105
|
+
// Copy even Buffer-backed chunks: observers must not mutate captured bytes.
|
|
106
|
+
onChunk?.(Uint8Array.from(value));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Observers are best-effort: never let one truncate capture or stop draining.
|
|
110
|
+
}
|
|
111
|
+
capture.push(value);
|
|
112
|
+
}
|
|
95
113
|
}
|
|
96
114
|
}
|
|
97
115
|
catch {
|
|
98
116
|
// Stream cancelled by SIGKILL on the process group; the partial text
|
|
99
|
-
// is still in `
|
|
117
|
+
// is still in `capture`, so resolve with what it holds and let the caller
|
|
100
118
|
// decide.
|
|
101
119
|
}
|
|
102
|
-
text += decoder.decode();
|
|
103
120
|
if (signal !== undefined)
|
|
104
121
|
signal.removeEventListener("abort", onAbort);
|
|
105
122
|
try {
|
|
@@ -108,7 +125,81 @@ async function drainToEof(stream, signal) {
|
|
|
108
125
|
catch {
|
|
109
126
|
// Already released; nothing to do.
|
|
110
127
|
}
|
|
111
|
-
return { text, closed: true };
|
|
128
|
+
return { text: capture.text(), closed: true };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Keeps the first and the last `maxBytes / 2` bytes of a stream and counts what falls between
|
|
132
|
+
* (STREAM_CAPTURE_BYTES). The head is decoded as it arrives; the tail is kept as bytes, trimmed from
|
|
133
|
+
* the front, and decoded once at the end from a codepoint boundary.
|
|
134
|
+
*/
|
|
135
|
+
class BoundedCapture {
|
|
136
|
+
half;
|
|
137
|
+
decoder = new TextDecoder();
|
|
138
|
+
head = "";
|
|
139
|
+
headBytes = 0;
|
|
140
|
+
tail = [];
|
|
141
|
+
tailBytes = 0;
|
|
142
|
+
omitted = 0;
|
|
143
|
+
constructor(maxBytes) {
|
|
144
|
+
this.half = Math.max(1, Math.floor(maxBytes / 2));
|
|
145
|
+
}
|
|
146
|
+
push(chunk) {
|
|
147
|
+
let rest = chunk;
|
|
148
|
+
if (this.headBytes < this.half) {
|
|
149
|
+
const take = Math.min(this.half - this.headBytes, rest.length);
|
|
150
|
+
this.head += this.decoder.decode(rest.subarray(0, take), { stream: true });
|
|
151
|
+
this.headBytes += take;
|
|
152
|
+
rest = rest.subarray(take);
|
|
153
|
+
}
|
|
154
|
+
if (rest.length === 0)
|
|
155
|
+
return;
|
|
156
|
+
this.tail.push(rest);
|
|
157
|
+
this.tailBytes += rest.length;
|
|
158
|
+
while (this.tailBytes > this.half) {
|
|
159
|
+
const first = this.tail[0];
|
|
160
|
+
const excess = this.tailBytes - this.half;
|
|
161
|
+
if (first.length <= excess) {
|
|
162
|
+
this.tail.shift();
|
|
163
|
+
this.tailBytes -= first.length;
|
|
164
|
+
this.omitted += first.length;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
this.tail[0] = first.subarray(excess);
|
|
168
|
+
this.tailBytes -= excess;
|
|
169
|
+
this.omitted += excess;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
text() {
|
|
174
|
+
const tail = Buffer.concat(this.tail);
|
|
175
|
+
if (this.omitted === 0)
|
|
176
|
+
return this.head + this.decoder.decode(tail);
|
|
177
|
+
// The cut can land inside a codepoint: skip its continuation bytes so the tail opens cleanly.
|
|
178
|
+
let start = 0;
|
|
179
|
+
while (start < tail.length && (tail[start] & 0xc0) === 0x80)
|
|
180
|
+
start += 1;
|
|
181
|
+
const head = this.head + this.decoder.decode();
|
|
182
|
+
const marker = `\n[... ${this.omitted + start} bytes of output omitted ...]\n`;
|
|
183
|
+
return head + marker + new TextDecoder().decode(tail.subarray(start));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** The deadline is releasable independently of command cancellation and drain grace. */
|
|
187
|
+
function armDeadline(options, onTimeout) {
|
|
188
|
+
let timerId;
|
|
189
|
+
const release = () => {
|
|
190
|
+
if (timerId !== undefined)
|
|
191
|
+
_argvExecDeps.clearTimeout(timerId);
|
|
192
|
+
timerId = undefined;
|
|
193
|
+
};
|
|
194
|
+
if (options.releaseDeadline?.aborted !== true) {
|
|
195
|
+
// A cancellable timer: releaseDeadline and settlement both clear it mid-flight.
|
|
196
|
+
timerId = _argvExecDeps.setTimeout(onTimeout, options.timeoutMs);
|
|
197
|
+
options.releaseDeadline?.addEventListener("abort", release, { once: true });
|
|
198
|
+
}
|
|
199
|
+
return () => {
|
|
200
|
+
release();
|
|
201
|
+
options.releaseDeadline?.removeEventListener("abort", release);
|
|
202
|
+
};
|
|
112
203
|
}
|
|
113
204
|
export async function runArgv(options) {
|
|
114
205
|
const signal = options.signal;
|
|
@@ -145,14 +236,14 @@ export async function runArgv(options) {
|
|
|
145
236
|
// BUG-13: unlike every git call (routed through gitWithTimeout), a spawn
|
|
146
237
|
// with no deadline can block its caller forever on a hung install
|
|
147
238
|
// (registry/NFS stall).
|
|
148
|
-
const
|
|
239
|
+
const clearDeadline = armDeadline(options, () => {
|
|
149
240
|
timedOut = true;
|
|
150
241
|
// MEM-4: proc.kill() reaches only the direct child, orphaning postinstall
|
|
151
242
|
// grandchildren. killProcessGroup(pid, "SIGKILL") kills the whole group
|
|
152
243
|
// (negative pid), falling back to the single process on ESRCH.
|
|
153
244
|
killGroup();
|
|
154
245
|
stopReaders();
|
|
155
|
-
}
|
|
246
|
+
});
|
|
156
247
|
// US-001: an abort races the timeout. One listener, removed on every settle
|
|
157
248
|
// path so a never-aborted signal never leaks a registration (AC10).
|
|
158
249
|
const onAbort = () => {
|
|
@@ -168,57 +259,61 @@ export async function runArgv(options) {
|
|
|
168
259
|
// to settle (via reader.cancel()) after we kill the process group. US-001
|
|
169
260
|
// requires that settlement stay bounded even when a background process
|
|
170
261
|
// inherits the pipe and ignores the SIGKILL.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
262
|
+
try {
|
|
263
|
+
const stdoutPromise = drainToEof(proc.stdout, stdoutController.signal, options.onOutput?.bind(undefined, "stdout"));
|
|
264
|
+
const stderrPromise = drainToEof(proc.stderr, stderrController.signal, options.onOutput?.bind(undefined, "stderr"));
|
|
265
|
+
const exitCode = await proc.exited;
|
|
266
|
+
// Post-exit drain grace. After exit, the streams MUST close — they were
|
|
267
|
+
// owned by the shell, which is now dead. Background processes that
|
|
268
|
+
// inherited the pipe (the `&` case) keep it open: that's the orphan.
|
|
269
|
+
const graceMs = _argvExecDeps.drainGraceMs;
|
|
270
|
+
// Cleared on every settle path: a successful command whose readers close
|
|
271
|
+
// before the grace elapses would otherwise leave a pending timer whose
|
|
272
|
+
// callback fires 500ms later and resolves an orphaned promise. Under load
|
|
273
|
+
// the leaked registrations accumulate in the timer wheel for nothing.
|
|
274
|
+
let graceTimerId;
|
|
275
|
+
const gracePromise = new Promise((resolve) => {
|
|
276
|
+
graceTimerId = _argvExecDeps.setTimeout(() => resolve("expired"), graceMs);
|
|
277
|
+
});
|
|
278
|
+
const stdoutSettled = await Promise.race([
|
|
279
|
+
stdoutPromise,
|
|
280
|
+
gracePromise.then(() => "expired"),
|
|
281
|
+
]);
|
|
282
|
+
const stderrSettled = await Promise.race([
|
|
283
|
+
stderrPromise,
|
|
284
|
+
gracePromise.then(() => "expired"),
|
|
285
|
+
]);
|
|
286
|
+
if (graceTimerId !== undefined)
|
|
287
|
+
_argvExecDeps.clearTimeout(graceTimerId);
|
|
288
|
+
const stdoutClosed = stdoutSettled !== "expired";
|
|
289
|
+
const stderrClosed = stderrSettled !== "expired";
|
|
290
|
+
if (!stdoutClosed || !stderrClosed) {
|
|
291
|
+
orphansKilled = true;
|
|
292
|
+
killGroup();
|
|
293
|
+
// The OS should close the pipes once the group dies; race that against
|
|
294
|
+
// a small post-kill budget. If a holdout somehow ignores SIGKILL or is
|
|
295
|
+
// not yet reaped, abort the matching reader controller — that drives
|
|
296
|
+
// `reader.cancel()` in drainToEof, which forces the pending `read()` to
|
|
297
|
+
// resolve with `done: true` and bounds settlement.
|
|
298
|
+
if (!stdoutClosed)
|
|
299
|
+
stdoutController.abort();
|
|
300
|
+
if (!stderrClosed)
|
|
301
|
+
stderrController.abort();
|
|
302
|
+
}
|
|
303
|
+
// Always await the readers to harvest whatever they captured.
|
|
304
|
+
const stdoutFinal = stdoutClosed ? stdoutSettled : await stdoutPromise;
|
|
305
|
+
const stderrFinal = stderrClosed ? stderrSettled : await stderrPromise;
|
|
306
|
+
return {
|
|
307
|
+
exitCode,
|
|
308
|
+
stdout: stdoutFinal.text,
|
|
309
|
+
stderr: stderrFinal.text,
|
|
310
|
+
timedOut,
|
|
311
|
+
aborted,
|
|
312
|
+
orphansKilled,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
clearDeadline();
|
|
317
|
+
signal?.removeEventListener("abort", onAbort);
|
|
210
318
|
}
|
|
211
|
-
// Always await the readers to harvest whatever they captured.
|
|
212
|
-
const stdoutFinal = stdoutClosed ? stdoutSettled : await stdoutPromise;
|
|
213
|
-
const stderrFinal = stderrClosed ? stderrSettled : await stderrPromise;
|
|
214
|
-
_argvExecDeps.clearTimeout(timerId);
|
|
215
|
-
signal?.removeEventListener("abort", onAbort);
|
|
216
|
-
return {
|
|
217
|
-
exitCode,
|
|
218
|
-
stdout: stdoutFinal.text,
|
|
219
|
-
stderr: stderrFinal.text,
|
|
220
|
-
timedOut,
|
|
221
|
-
aborted,
|
|
222
|
-
orphansKilled,
|
|
223
|
-
};
|
|
224
319
|
}
|