@nathapp/nax-agent 0.91.7-canary.2 → 0.91.7-canary.4
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 +8 -0
- package/dist/coding-tools/coding-tool-extras.d.ts +2 -0
- package/dist/coding-tools/coding-tool-extras.js +9 -1
- package/dist/coding-tools/coding-tool-support.d.ts +1 -0
- package/dist/coding-tools/coding-tool-support.js +5 -1
- package/dist/runtime/node-runtime.js +5 -0
- package/dist/session/agent-session-types.d.ts +2 -0
- package/dist/session/native-backend-options.d.ts +19 -0
- package/dist/session/native-backend-options.js +10 -0
- package/dist/session/native-backend.js +12 -2
- package/dist/session/native-posture-rules.d.ts +18 -0
- package/dist/session/native-posture-rules.js +33 -0
- 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 +8 -0
- package/dist/session/session-tool-support.js +13 -3
- package/dist/session-store/session-lock.js +4 -1
- package/dist/tools/runtime-calltool.d.ts +1 -0
- package/dist/tools/runtime-calltool.js +1 -1
- package/dist/tools/runtime.d.ts +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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.4] - 2026-10-27
|
|
8
|
+
|
|
9
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
10
|
+
|
|
11
|
+
## [0.91.7-canary.3] - 2026-10-27
|
|
12
|
+
|
|
13
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
14
|
+
|
|
7
15
|
## [0.91.7-canary.2] - 2026-10-26
|
|
8
16
|
|
|
9
17
|
- No changes in this package. Released in lockstep with the other nax packages.
|
|
@@ -67,11 +67,13 @@ export declare function unsandboxedToolOptions(args: {
|
|
|
67
67
|
*/
|
|
68
68
|
export declare function unsandboxedRuntimeOption(args: {
|
|
69
69
|
readonly unsandboxedCommands?: boolean;
|
|
70
|
+
readonly askOnUnsandboxed?: boolean;
|
|
70
71
|
readonly bashApproval: BashApprovalMode;
|
|
71
72
|
}): {
|
|
72
73
|
readonly unsandboxed?: {
|
|
73
74
|
readonly enabled: boolean;
|
|
74
75
|
readonly bashApproval: BashApprovalMode;
|
|
76
|
+
readonly alwaysAsk?: boolean;
|
|
75
77
|
};
|
|
76
78
|
};
|
|
77
79
|
export declare function buildDeclaredCommandTools(args: DeclaredCommandToolsArgs): CodingTool[];
|
|
@@ -23,7 +23,15 @@ export function unsandboxedToolOptions(args) {
|
|
|
23
23
|
* raise it.
|
|
24
24
|
*/
|
|
25
25
|
export function unsandboxedRuntimeOption(args) {
|
|
26
|
-
|
|
26
|
+
if (args.unsandboxedCommands !== true)
|
|
27
|
+
return {};
|
|
28
|
+
return {
|
|
29
|
+
unsandboxed: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
bashApproval: args.bashApproval,
|
|
32
|
+
...(args.askOnUnsandboxed === true ? { alwaysAsk: true } : {}),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
27
35
|
}
|
|
28
36
|
export function buildDeclaredCommandTools(args) {
|
|
29
37
|
return [
|
|
@@ -148,6 +148,7 @@ export declare function buildCodingToolSupport(args: {
|
|
|
148
148
|
* the runtime's own reading of the field is a later story.
|
|
149
149
|
*/
|
|
150
150
|
unsandboxedCommands?: boolean;
|
|
151
|
+
askOnUnsandboxed?: boolean;
|
|
151
152
|
/** US-001: forwarded into ToolRunContext so Bash/Exec can SIGKILL on cancel. */
|
|
152
153
|
abortSignal?: AbortSignal;
|
|
153
154
|
/**
|
|
@@ -127,7 +127,11 @@ export function buildCodingToolSupport(args) {
|
|
|
127
127
|
// schema — with the locally resolved mode, so a caller that left
|
|
128
128
|
// `bashApproval` unset cannot enable an escape under a mode it did not
|
|
129
129
|
// choose.
|
|
130
|
-
...unsandboxedRuntimeOption({
|
|
130
|
+
...unsandboxedRuntimeOption({
|
|
131
|
+
unsandboxedCommands: args.unsandboxedCommands,
|
|
132
|
+
askOnUnsandboxed: args.askOnUnsandboxed,
|
|
133
|
+
bashApproval,
|
|
134
|
+
}),
|
|
131
135
|
});
|
|
132
136
|
const tools = runtime.advertised(advertised);
|
|
133
137
|
// The fixed per-hop cost of advertising provider tools: their schemas enter
|
|
@@ -67,6 +67,11 @@ function exitStatus(code, signal) {
|
|
|
67
67
|
return code;
|
|
68
68
|
return 128 + (signal !== null ? (osConstants.signals[signal] ?? 0) : 0);
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* `Readable.toWeb` is declared against `node:stream/web`'s `ReadableStream`, while
|
|
72
|
+
* `AgentSpawnResult` carries the global one. They are the same class at runtime and differ
|
|
73
|
+
* only in how their `getReader` overloads are written, so the bridge has to go via `unknown`.
|
|
74
|
+
*/
|
|
70
75
|
function toWeb(stream) {
|
|
71
76
|
return stream === null ? emptyStream() : Readable.toWeb(stream);
|
|
72
77
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @nathapp/nax-agent-acp backend (S4) reuses these types unchanged.
|
|
5
5
|
*/
|
|
6
6
|
import type { CommandInterceptor } from "#src/command-interceptor/index";
|
|
7
|
+
import type { CommandShadow } from "#src/command-safety/types";
|
|
7
8
|
import type { TokenUsage } from "#src/cost/standard-types";
|
|
8
9
|
import type { TranscriptStore } from "#src/native/session/transcript-types";
|
|
9
10
|
import type { ProtectedPathsPolicy } from "#src/tools/protected-paths";
|
|
@@ -43,6 +44,7 @@ export interface AgentSessionHostPorts {
|
|
|
43
44
|
/** Paths the sandbox and the read tools deny. Default: the configured credentials directory, if any. */
|
|
44
45
|
readonly protectedPaths?: ProtectedPathsPolicy;
|
|
45
46
|
readonly commandInterceptor?: CommandInterceptor;
|
|
47
|
+
readonly commandShadow?: CommandShadow;
|
|
46
48
|
}
|
|
47
49
|
export interface CreateAgentSessionOptions {
|
|
48
50
|
/** The driver: `nativeBackend({ model })`, or an ACP backend. Native-only options live on the backend. */
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nativeBackend's options (S4 spec 5.2): the native half of the facade's
|
|
3
|
+
* createAgentSession options, validated here rather than by the facade. zod
|
|
4
|
+
* checks the shape; the caller's own object is kept as `raw` (zod returns
|
|
5
|
+
* copies, which would break identity for handlers the caller owns).
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
1
8
|
import type { BashApprovalMode } from "#src/config/bash-approval";
|
|
9
|
+
import { SandboxConfigSchema } from "#src/config/schemas-sandbox";
|
|
2
10
|
import type { NativeCatalogOverrides } from "#src/native/client";
|
|
3
11
|
import type { CredentialSource } from "#src/native/credentials/session-source";
|
|
4
12
|
import type { LoopHandlerSet } from "#src/native/session/loop-events/types";
|
|
@@ -17,6 +25,17 @@ export interface NativeBackendOptions {
|
|
|
17
25
|
readonly loopHandlerRole?: string;
|
|
18
26
|
readonly hostPorts?: AgentSessionHostPorts;
|
|
19
27
|
readonly bashApproval?: BashApprovalMode;
|
|
28
|
+
/** Ask before a per-call sandbox escape, including under raw; requires allowUnsandboxedCommands. */
|
|
29
|
+
readonly askOnUnsandboxed?: boolean;
|
|
30
|
+
/** Gate Bash when the sandbox is disabled or unavailable at session open. */
|
|
31
|
+
readonly askWhenSandboxDown?: boolean;
|
|
32
|
+
/** Keep provider-designated mutating tool asks outside the ask profile too. */
|
|
33
|
+
readonly askDiscoveredTools?: boolean;
|
|
34
|
+
/** Rule overlays appended to the profile's own permission rules. */
|
|
35
|
+
readonly askRules?: readonly string[];
|
|
36
|
+
readonly denyRules?: readonly string[];
|
|
37
|
+
/** Partial sandbox input, validated here and default-filled at posture resolution. */
|
|
38
|
+
readonly sandbox?: z.input<typeof SandboxConfigSchema>;
|
|
20
39
|
readonly allowUnsandboxed?: boolean;
|
|
21
40
|
/** Narrow the declared tools to these names (built-in names, `<provider>__<tool>`, or `<provider>__*`); scratchpad tools are always kept. */
|
|
22
41
|
readonly allowedTools?: readonly string[];
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* copies, which would break identity for handlers the caller owns).
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { SandboxConfigSchema } from "#src/config/schemas-sandbox";
|
|
8
9
|
import { parseNativeModel } from "#src/native/models";
|
|
9
10
|
import { compactionSettingsSchema } from "#src/native/session/compaction-settings";
|
|
10
11
|
import { isInstructionFileName } from "#src/native/session/instruction-file-name";
|
|
@@ -41,10 +42,19 @@ const OptionsSchema = z.strictObject({
|
|
|
41
42
|
protectedPaths: z
|
|
42
43
|
.custom((value) => isRecord(value) && isStringArray(value.gitExcludePathspecs) && isStringArray(value.gitIgnorePatterns), "must be a protected-paths policy with gitExcludePathspecs and gitIgnorePatterns string arrays")
|
|
43
44
|
.optional(),
|
|
45
|
+
commandShadow: z
|
|
46
|
+
.custom(hasMethods(["observe", "settle", "drain"]), "must implement observe, settle and drain")
|
|
47
|
+
.optional(),
|
|
44
48
|
commandInterceptor: z.custom(hasMethods(["intercept"]), "must implement intercept").optional(),
|
|
45
49
|
})
|
|
46
50
|
.optional(),
|
|
47
51
|
bashApproval: z.enum(["raw", "gated", "escalate"]).optional(),
|
|
52
|
+
askOnUnsandboxed: z.boolean().optional(),
|
|
53
|
+
askWhenSandboxDown: z.boolean().optional(),
|
|
54
|
+
askDiscoveredTools: z.boolean().optional(),
|
|
55
|
+
askRules: z.array(z.string().min(1)).optional(),
|
|
56
|
+
denyRules: z.array(z.string().min(1)).optional(),
|
|
57
|
+
sandbox: SandboxConfigSchema.optional(),
|
|
48
58
|
allowUnsandboxed: z.boolean().optional(),
|
|
49
59
|
allowedTools: z.array(z.string().min(1)).optional(),
|
|
50
60
|
allowUnsandboxedCommands: z.boolean().optional(),
|
|
@@ -14,6 +14,7 @@ import { NativeSessionAdapter } from "#src/native/session-adapter";
|
|
|
14
14
|
import { resolveProviderTools } from "#src/tools/provider-advertise";
|
|
15
15
|
import { AgentSessionError } from "./agent-session-errors.js";
|
|
16
16
|
import { nativeProfileRules, parseNativeBackendOptions, } from "./native-backend-options.js";
|
|
17
|
+
import { effectiveBashApproval, nativePostureRules } from "./native-posture-rules.js";
|
|
17
18
|
import { createSessionAskLink, createSessionAskResolver } from "./session-ask-link.js";
|
|
18
19
|
import { createSessionInteractionHandler, embedderToolDescriptor } from "./session-interaction.js";
|
|
19
20
|
import { buildSessionToolSupport, defaultProtectedPaths, providerAdmits, resolveSessionLauncher, SESSION_STAGE, } from "./session-tool-support.js";
|
|
@@ -41,7 +42,8 @@ async function openNative(resolved, ctx) {
|
|
|
41
42
|
const raw = resolved.raw;
|
|
42
43
|
if (ctx.resume !== undefined && raw.carryHistoryAcrossModels !== true)
|
|
43
44
|
checkResumeModel(ctx.resume.doc, ctx.sessionId, raw.model);
|
|
44
|
-
const { bashApproval, allowUnsandboxed, allowUnsandboxedCommands } = nativeProfileRules(ctx.profile, raw);
|
|
45
|
+
const { bashApproval: profileMode, allowUnsandboxed, allowUnsandboxedCommands, } = nativeProfileRules(ctx.profile, raw);
|
|
46
|
+
const posture = nativePostureRules(ctx.profile, raw);
|
|
45
47
|
const protectedPaths = {
|
|
46
48
|
...defaultProtectedPaths(raw.credentials !== undefined),
|
|
47
49
|
...raw.hostPorts?.protectedPaths,
|
|
@@ -50,9 +52,11 @@ async function openNative(resolved, ctx) {
|
|
|
50
52
|
profile: ctx.profile,
|
|
51
53
|
root: ctx.workdir,
|
|
52
54
|
protectedPaths,
|
|
53
|
-
bashApproval,
|
|
55
|
+
bashApproval: profileMode,
|
|
54
56
|
allowUnsandboxed,
|
|
57
|
+
config: posture.sandbox,
|
|
55
58
|
});
|
|
59
|
+
const bashApproval = effectiveBashApproval(profileMode, posture.askWhenSandboxDown, sandbox.kind);
|
|
56
60
|
const providerTools = ctx.profile === "none" || raw.toolProviders === undefined
|
|
57
61
|
? undefined
|
|
58
62
|
: await resolveProviderTools(raw.toolProviders, SESSION_STAGE, ctx.workdir, raw.allowedTools !== undefined ? { admits: providerAdmits(raw.allowedTools) } : undefined);
|
|
@@ -76,6 +80,11 @@ async function openNative(resolved, ctx) {
|
|
|
76
80
|
launcher,
|
|
77
81
|
askResolver: createSessionAskResolver(createSessionAskLink({ port: ctx.asks, currentCallId: () => callId })),
|
|
78
82
|
interceptor: raw.hostPorts?.commandInterceptor,
|
|
83
|
+
askRules: posture.askRules,
|
|
84
|
+
denyRules: posture.denyRules,
|
|
85
|
+
askOnUnsandboxed: posture.askOnUnsandboxed,
|
|
86
|
+
askDiscoveredTools: posture.askDiscoveredTools,
|
|
87
|
+
commandShadow: raw.hostPorts?.commandShadow,
|
|
79
88
|
// US-003: the profile's validated opt-in, so an interactive "full" session
|
|
80
89
|
// can offer Bash's per-call escape without turning the sandbox off.
|
|
81
90
|
...(allowUnsandboxedCommands ? { unsandboxedCommands: true } : {}),
|
|
@@ -83,6 +92,7 @@ async function openNative(resolved, ctx) {
|
|
|
83
92
|
const interactionHandler = createSessionInteractionHandler({
|
|
84
93
|
sessionId: ctx.sessionId,
|
|
85
94
|
runtime: support.runtime,
|
|
95
|
+
unavailableTools: new Set((support.declaredTools ?? []).filter((name) => !support.tools.some((tool) => tool.name === name))),
|
|
86
96
|
embedderTools: new Map(ctx.tools.map((tool) => [tool.name, tool])),
|
|
87
97
|
asks: ctx.asks,
|
|
88
98
|
turnSignal: ctx.turnSignal,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Optional native-session overlays, separate from the existing profile defaults. */
|
|
2
|
+
import type { BashApprovalMode } from "#src/config/bash-approval";
|
|
3
|
+
import { type SandboxConfig } from "#src/config/schemas-sandbox";
|
|
4
|
+
import type { SandboxState } from "#src/sandbox/index";
|
|
5
|
+
import type { ToolGrant } from "#src/tools/types";
|
|
6
|
+
import type { AgentSessionProfile } from "./agent-session-types.ts";
|
|
7
|
+
import type { NativeBackendOptions } from "./native-backend-options.ts";
|
|
8
|
+
export interface ResolvedPostureRules {
|
|
9
|
+
readonly askRules: readonly ToolGrant[];
|
|
10
|
+
readonly denyRules: readonly ToolGrant[];
|
|
11
|
+
readonly sandbox: SandboxConfig;
|
|
12
|
+
readonly askOnUnsandboxed: boolean;
|
|
13
|
+
readonly askWhenSandboxDown: boolean;
|
|
14
|
+
readonly askDiscoveredTools: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function nativePostureRules(profile: AgentSessionProfile, raw: NativeBackendOptions): ResolvedPostureRules;
|
|
17
|
+
/** Snapshot the sandbox posture at open; a running session never silently changes modes. */
|
|
18
|
+
export declare function effectiveBashApproval(profileMode: BashApprovalMode, askWhenSandboxDown: boolean, sandboxKind: SandboxState["kind"]): BashApprovalMode;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DEFAULT_SANDBOX_CONFIG, SandboxConfigSchema } from "#src/config/schemas-sandbox";
|
|
2
|
+
import { parseRuleList } from "#src/permissions/grammar";
|
|
3
|
+
import { AgentSessionError } from "./agent-session-errors.js";
|
|
4
|
+
export function nativePostureRules(profile, raw) {
|
|
5
|
+
const toolOnlyOptions = [
|
|
6
|
+
"askRules",
|
|
7
|
+
"denyRules",
|
|
8
|
+
"askOnUnsandboxed",
|
|
9
|
+
"askWhenSandboxDown",
|
|
10
|
+
"askDiscoveredTools",
|
|
11
|
+
];
|
|
12
|
+
for (const name of toolOnlyOptions) {
|
|
13
|
+
if ((profile === "none" || profile === "read") && raw[name] !== undefined) {
|
|
14
|
+
const path = `backend.${name}`;
|
|
15
|
+
throw new AgentSessionError(`Invalid agent session options: ${path} applies to profiles "ask", "full" and "review" only`, "AGENT_SESSION_INVALID_OPTIONS", { path });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (raw.askOnUnsandboxed === true && raw.allowUnsandboxedCommands !== true) {
|
|
19
|
+
throw new AgentSessionError("Invalid agent session options: backend.askOnUnsandboxed requires backend.allowUnsandboxedCommands: true", "AGENT_SESSION_INVALID_OPTIONS", { path: "backend.askOnUnsandboxed" });
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
askOnUnsandboxed: raw.askOnUnsandboxed === true,
|
|
23
|
+
askWhenSandboxDown: raw.askWhenSandboxDown === true,
|
|
24
|
+
askDiscoveredTools: raw.askDiscoveredTools === true,
|
|
25
|
+
askRules: parseRuleList(raw.askRules ?? []),
|
|
26
|
+
denyRules: parseRuleList(raw.denyRules ?? []),
|
|
27
|
+
sandbox: raw.sandbox === undefined ? DEFAULT_SANDBOX_CONFIG : SandboxConfigSchema.parse(raw.sandbox),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Snapshot the sandbox posture at open; a running session never silently changes modes. */
|
|
31
|
+
export function effectiveBashApproval(profileMode, askWhenSandboxDown, sandboxKind) {
|
|
32
|
+
return askWhenSandboxDown && sandboxKind !== "available" ? "gated" : profileMode;
|
|
33
|
+
}
|
|
@@ -9,6 +9,8 @@ export interface SessionInteractionDeps {
|
|
|
9
9
|
readonly sessionId: string;
|
|
10
10
|
readonly runtime: CodingToolRuntime;
|
|
11
11
|
readonly embedderTools: ReadonlyMap<string, EmbedderTool>;
|
|
12
|
+
/** Declared coding tools withheld by the runtime's advertisement policy. Never execute these calls. */
|
|
13
|
+
readonly unavailableTools?: ReadonlySet<string>;
|
|
12
14
|
readonly asks: SessionAskPort;
|
|
13
15
|
/** The running turn's signal (a never-aborting one between turns). */
|
|
14
16
|
readonly turnSignal: () => AbortSignal;
|
|
@@ -168,8 +168,15 @@ export function createSessionInteractionHandler(deps) {
|
|
|
168
168
|
async onInteraction(request) {
|
|
169
169
|
if (request.kind === "question")
|
|
170
170
|
return answerQuestion(deps, request.text);
|
|
171
|
-
if (request.kind === "context-tool")
|
|
171
|
+
if (request.kind === "context-tool") {
|
|
172
|
+
// A model may still call a denied tool it remembers from earlier turns.
|
|
173
|
+
// Keep it hidden, but distinguish a policy refusal from an unknown name.
|
|
174
|
+
if (deps.unavailableTools?.has(request.name)) {
|
|
175
|
+
const reason = `Tool "${request.name}" is not available under the session policy`;
|
|
176
|
+
return { answer: `Denied: ${reason}`, denied: { reason, breach: false } };
|
|
177
|
+
}
|
|
172
178
|
throw toolError(`Unknown tool "${request.name}"`, request.name);
|
|
179
|
+
}
|
|
173
180
|
const tool = deps.embedderTools.get(request.name);
|
|
174
181
|
return tool !== undefined ? runEmbedderTool(deps, tool, request) : runCodingTool(deps, request);
|
|
175
182
|
},
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type CodingToolSupport } from "#src/coding-tools/coding-tool-support";
|
|
2
2
|
import type { CommandInterceptor } from "#src/command-interceptor/index";
|
|
3
|
+
import type { CommandShadow } from "#src/command-safety/types";
|
|
3
4
|
import type { BashApprovalMode } from "#src/config/bash-approval";
|
|
5
|
+
import { type SandboxConfig } from "#src/config/schemas-sandbox";
|
|
4
6
|
import type { AskResolver } from "#src/permissions/index";
|
|
5
7
|
import type { CommandLauncher, SandboxState } from "#src/sandbox/index";
|
|
6
8
|
import type { ProtectedPathsPolicy } from "#src/tools/protected-paths";
|
|
@@ -35,6 +37,7 @@ export interface SessionLauncherArgs {
|
|
|
35
37
|
*/
|
|
36
38
|
readonly bashApproval: BashApprovalMode;
|
|
37
39
|
readonly allowUnsandboxed: boolean;
|
|
40
|
+
readonly config?: SandboxConfig;
|
|
38
41
|
}
|
|
39
42
|
/** What the floor decided: the launcher to run under (if any), and the state to report. */
|
|
40
43
|
export interface ResolvedSessionLauncher {
|
|
@@ -58,6 +61,11 @@ export interface SessionToolSupportArgs {
|
|
|
58
61
|
readonly launcher: CommandLauncher | undefined;
|
|
59
62
|
readonly askResolver: AskResolver;
|
|
60
63
|
readonly interceptor: CommandInterceptor | undefined;
|
|
64
|
+
readonly askRules?: readonly ToolGrant[];
|
|
65
|
+
readonly denyRules?: readonly ToolGrant[];
|
|
66
|
+
readonly askOnUnsandboxed?: boolean;
|
|
67
|
+
readonly askDiscoveredTools?: boolean;
|
|
68
|
+
readonly commandShadow?: CommandShadow;
|
|
61
69
|
/**
|
|
62
70
|
* US-003: offer Bash's per-call `unsandboxed` argument for this session
|
|
63
71
|
* (from `nativeProfileRules(...).allowUnsandboxedCommands`). Optional and
|
|
@@ -73,7 +73,7 @@ export async function resolveSessionLauncher(args) {
|
|
|
73
73
|
return { launcher: undefined, sandbox: { kind: "disabled" } };
|
|
74
74
|
}
|
|
75
75
|
const launcher = await resolveSessionSandbox({
|
|
76
|
-
config: DEFAULT_SANDBOX_CONFIG,
|
|
76
|
+
config: args.config ?? DEFAULT_SANDBOX_CONFIG,
|
|
77
77
|
root: args.root,
|
|
78
78
|
needsLauncher: true,
|
|
79
79
|
protectedPaths: args.protectedPaths,
|
|
@@ -101,6 +101,14 @@ function narrowBuiltIns(profileTools, allowedTools) {
|
|
|
101
101
|
const allowed = new Set(allowedTools);
|
|
102
102
|
return profileTools.filter((tool) => UNIVERSAL_CODING_TOOLS.includes(tool) || allowed.has(tool));
|
|
103
103
|
}
|
|
104
|
+
/** Optional overlays share one forwarding point for future posture knobs. */
|
|
105
|
+
function postureToolOptions(args) {
|
|
106
|
+
return {
|
|
107
|
+
...(args.denyRules !== undefined ? { denyRules: args.denyRules } : {}),
|
|
108
|
+
...(args.askOnUnsandboxed !== undefined ? { askOnUnsandboxed: args.askOnUnsandboxed } : {}),
|
|
109
|
+
...(args.commandShadow !== undefined ? { commandShadow: args.commandShadow } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
104
112
|
export function buildSessionToolSupport(args) {
|
|
105
113
|
const profileTools = narrowBuiltIns(declaredToolsFor(args.profile, args.protectedPaths), args.allowedTools);
|
|
106
114
|
// CodingToolName enumerates built-ins, but the runtime also accepts provider names.
|
|
@@ -108,13 +116,14 @@ export function buildSessionToolSupport(args) {
|
|
|
108
116
|
...new Set([...profileTools, ...(args.providerTools?.tools.map((tool) => tool.name) ?? [])]),
|
|
109
117
|
];
|
|
110
118
|
const grants = [...grantsFor(profileTools), ...(args.providerTools?.grants ?? [])];
|
|
111
|
-
//
|
|
119
|
+
// Provider asks remain opt-in outside "ask"; configured rules follow them.
|
|
112
120
|
const askRules = [
|
|
113
121
|
...askRulesFor(profileTools, args.bashApproval, args.profile),
|
|
114
|
-
...(args.profile === "ask" ? (args.providerTools?.askTools ?? []) : []).map((tool) => ({
|
|
122
|
+
...(args.profile === "ask" || args.askDiscoveredTools ? (args.providerTools?.askTools ?? []) : []).map((tool) => ({
|
|
115
123
|
tool: tool,
|
|
116
124
|
patterns: ["*"],
|
|
117
125
|
})),
|
|
126
|
+
...(args.askRules ?? []),
|
|
118
127
|
];
|
|
119
128
|
const providerInstructions = renderProviderInstructions(args.providerTools?.instructions ?? []);
|
|
120
129
|
const support = buildCodingToolSupport({
|
|
@@ -139,6 +148,7 @@ export function buildSessionToolSupport(args) {
|
|
|
139
148
|
...(args.interceptor !== undefined ? { interceptor: args.interceptor } : {}),
|
|
140
149
|
...(args.launcher !== undefined ? { launcher: args.launcher } : {}),
|
|
141
150
|
...unsandboxedToolOptions(args),
|
|
151
|
+
...postureToolOptions(args),
|
|
142
152
|
});
|
|
143
153
|
// Unreachable while the scratchpad trio is always declared and granted; kept for the type.
|
|
144
154
|
if (support === undefined) {
|
|
@@ -161,7 +161,10 @@ export function acquireSessionLock(path, deps, isAlive) {
|
|
|
161
161
|
rmSync(temp, { force: true });
|
|
162
162
|
}
|
|
163
163
|
}, LOCK_HEARTBEAT_MS);
|
|
164
|
-
|
|
164
|
+
// Bun returns a number and Node a Timeout; only the latter can be unref'd, and only it
|
|
165
|
+
// needs to be (a number handle never holds the loop open on its own).
|
|
166
|
+
if (typeof timer !== "number")
|
|
167
|
+
timer.unref();
|
|
165
168
|
return async () => {
|
|
166
169
|
try {
|
|
167
170
|
// The heartbeat stays live while release waits out contention.
|
|
@@ -81,7 +81,7 @@ export function resolveUnsandboxedCall(p) {
|
|
|
81
81
|
return {
|
|
82
82
|
kind: "unsandboxed",
|
|
83
83
|
suffix: unsandboxedReasonSuffix(p.input),
|
|
84
|
-
asksOnAllow: p.option.bashApproval !== "raw",
|
|
84
|
+
asksOnAllow: p.option.alwaysAsk === true || p.option.bashApproval !== "raw",
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
/** The reason a converted allow carries (spec US-006), before the suffix. */
|
package/dist/tools/runtime.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nax-agent",
|
|
3
|
-
"version": "0.91.7-canary.
|
|
3
|
+
"version": "0.91.7-canary.4",
|
|
4
4
|
"description": "nax's native coding agent: session contract, native loop, tools, permissions, sandbox, command-safety.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "William Khoo",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@anthropic-ai/sandbox-runtime": "0.0.77",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
50
|
-
"@nathapp/nax-ai": "0.91.7-canary.
|
|
50
|
+
"@nathapp/nax-ai": "0.91.7-canary.4",
|
|
51
51
|
"zod": "^4.3.6"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|