@nathapp/nax-agent 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +56 -2
  3. package/dist/coding-tools/coding-tool-sandbox.d.ts +3 -0
  4. package/dist/coding-tools/coding-tool-sandbox.js +14 -3
  5. package/dist/coding-tools/coding-tool-support.d.ts +3 -0
  6. package/dist/coding-tools/coding-tool-support.js +2 -0
  7. package/dist/index.d.ts +21 -3
  8. package/dist/index.js +13 -3
  9. package/dist/infra/stderr-tail.d.ts +8 -0
  10. package/dist/infra/stderr-tail.js +48 -0
  11. package/dist/internal/redact.d.ts +23 -0
  12. package/dist/internal/redact.js +60 -0
  13. package/dist/internal.d.ts +7 -0
  14. package/dist/internal.js +8 -0
  15. package/dist/native/adapter-deps.d.ts +3 -3
  16. package/dist/native/adapter-deps.js +3 -3
  17. package/dist/native/client.d.ts +7 -1
  18. package/dist/native/client.js +1 -1
  19. package/dist/native/credentials/change-guard.d.ts +8 -1
  20. package/dist/native/credentials/change-guard.js +1 -1
  21. package/dist/native/credentials/fingerprint.d.ts +4 -1
  22. package/dist/native/credentials/fingerprint.js +6 -3
  23. package/dist/native/credentials/session-source.d.ts +12 -0
  24. package/dist/native/credentials/session-source.js +33 -0
  25. package/dist/native/index.d.ts +3 -1
  26. package/dist/native/index.js +2 -1
  27. package/dist/native/session/loop-events/types.d.ts +1 -1
  28. package/dist/native/session/loop-handlers.d.ts +3 -0
  29. package/dist/native/session/loop-handlers.js +1 -1
  30. package/dist/native/session/memory-transcript-store.d.ts +12 -0
  31. package/dist/native/session/memory-transcript-store.js +36 -0
  32. package/dist/native/session/session.d.ts +138 -96
  33. package/dist/native/session/session.js +107 -127
  34. package/dist/native/session/transcript-identity.d.ts +39 -0
  35. package/dist/native/session/transcript-identity.js +81 -0
  36. package/dist/native/session/transcript-store.d.ts +22 -21
  37. package/dist/native/session/transcript-store.js +59 -68
  38. package/dist/native/session/transcript-types.d.ts +54 -0
  39. package/dist/native/session/transcript-types.js +7 -0
  40. package/dist/native/session/truncation-handler.d.ts +4 -3
  41. package/dist/native/session/truncation-handler.js +7 -10
  42. package/dist/native/session/turn-complete-step.d.ts +4 -1
  43. package/dist/native/session/turn-complete-step.js +11 -4
  44. package/dist/native/session/turn-event-emitter.d.ts +48 -0
  45. package/dist/native/session/turn-event-emitter.js +134 -0
  46. package/dist/native/session/turn-loop-round-trip.d.ts +5 -2
  47. package/dist/native/session/turn-loop-round-trip.js +8 -4
  48. package/dist/native/session/turn-loop.js +27 -13
  49. package/dist/native/session/turn-result.d.ts +1 -1
  50. package/dist/native/session/turn-result.js +1 -1
  51. package/dist/native/session/turn-tool-batch.d.ts +3 -0
  52. package/dist/native/session/turn-tool-batch.js +14 -6
  53. package/dist/native/session/turn-types.d.ts +17 -1
  54. package/dist/native/session-adapter.d.ts +22 -4
  55. package/dist/native/session-adapter.js +79 -35
  56. package/dist/native/stream-complete.d.ts +17 -0
  57. package/dist/native/stream-complete.js +40 -0
  58. package/dist/permissions/approvals-link.js +5 -4
  59. package/dist/permissions/ask-chain.d.ts +1 -1
  60. package/dist/permissions/ask.d.ts +6 -0
  61. package/dist/permissions/ask.js +6 -0
  62. package/dist/permissions/index.d.ts +1 -1
  63. package/dist/permissions/index.js +1 -1
  64. package/dist/sandbox/policy-builder.d.ts +6 -1
  65. package/dist/sandbox/policy-builder.js +18 -14
  66. package/dist/session/agent-session-deps.d.ts +12 -0
  67. package/dist/session/agent-session-deps.js +28 -0
  68. package/dist/session/agent-session-errors.d.ts +11 -0
  69. package/dist/session/agent-session-errors.js +12 -0
  70. package/dist/session/agent-session-options.d.ts +12 -0
  71. package/dist/session/agent-session-options.js +96 -0
  72. package/dist/session/agent-session-resume.d.ts +14 -0
  73. package/dist/session/agent-session-resume.js +49 -0
  74. package/dist/session/agent-session-turn.d.ts +41 -0
  75. package/dist/session/agent-session-turn.js +177 -0
  76. package/dist/session/agent-session-types.d.ts +182 -0
  77. package/dist/session/agent-session-types.js +1 -0
  78. package/dist/session/agent-session.d.ts +8 -0
  79. package/dist/session/agent-session.js +184 -0
  80. package/dist/session/native-backend-options.d.ts +24 -0
  81. package/dist/session/native-backend-options.js +81 -0
  82. package/dist/session/native-backend.d.ts +4 -0
  83. package/dist/session/native-backend.js +106 -0
  84. package/dist/session/pending-asks.d.ts +24 -0
  85. package/dist/session/pending-asks.js +77 -0
  86. package/dist/session/session-ask-link.d.ts +33 -0
  87. package/dist/session/session-ask-link.js +53 -0
  88. package/dist/session/session-ask-port.d.ts +12 -0
  89. package/dist/session/session-ask-port.js +64 -0
  90. package/dist/session/session-backend.d.ts +79 -0
  91. package/dist/session/session-backend.js +1 -0
  92. package/dist/session/session-event-channel.d.ts +17 -0
  93. package/dist/session/session-event-channel.js +132 -0
  94. package/dist/session/session-interaction.d.ts +20 -0
  95. package/dist/session/session-interaction.js +161 -0
  96. package/dist/session/session-tool-support.d.ts +48 -0
  97. package/dist/session/session-tool-support.js +104 -0
  98. package/dist/session/session-types.d.ts +33 -2
  99. package/dist/session/turn-event.d.ts +66 -0
  100. package/dist/session/turn-event.js +1 -0
  101. package/dist/tools/ask-request.js +3 -1
  102. package/dist/tools/bash-cwd.d.ts +3 -3
  103. package/dist/tools/credential-read-deny.d.ts +22 -0
  104. package/dist/tools/credential-read-deny.js +56 -0
  105. package/dist/tools/glob.js +9 -2
  106. package/dist/tools/grep.js +55 -26
  107. package/dist/tools/owned-paths.d.ts +43 -0
  108. package/dist/tools/owned-paths.js +17 -0
  109. package/dist/tools/package-managers.d.ts +3 -0
  110. package/dist/tools/package-managers.js +7 -3
  111. package/dist/tools/policy-bash-raw.d.ts +3 -0
  112. package/dist/tools/policy-bash-raw.js +49 -62
  113. package/dist/tools/policy-command-branch.d.ts +3 -0
  114. package/dist/tools/policy-command-branch.js +2 -1
  115. package/dist/tools/policy-paths-branch.d.ts +14 -8
  116. package/dist/tools/policy-paths-branch.js +20 -19
  117. package/dist/tools/policy.d.ts +10 -7
  118. package/dist/tools/policy.js +9 -7
  119. package/dist/tools/protected-paths.d.ts +6 -6
  120. package/dist/tools/read.js +38 -20
  121. package/dist/tools/registry.d.ts +3 -0
  122. package/dist/tools/run-command-exec.js +1 -0
  123. package/dist/tools/runtime.d.ts +3 -0
  124. package/dist/tools/runtime.js +1 -0
  125. package/dist/tools/scratchpad.js +2 -1
  126. package/dist/tools/types.d.ts +2 -2
  127. package/package.json +2 -2
  128. package/dist/tools/nax-owned-writes.d.ts +0 -135
  129. package/dist/tools/nax-owned-writes.js +0 -259
package/CHANGELOG.md CHANGED
@@ -4,6 +4,49 @@ 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.3.0] - 2026-10-06
8
+
9
+ The backend seam for S4. Breaking: `createAgentSession` takes a `SessionBackend`. nax behaviour unchanged.
10
+
11
+ ### Changed
12
+
13
+ - **Breaking:** `CreateAgentSessionOptions.backend` is a `SessionBackend` (was `"native"`). `model`, `credentials`, `catalogOverrides`, `loopHandlers`, `hostPorts`, `bashApproval` and `allowUnsandboxed` move into `nativeBackend({ ... })`. A native-only key at the top level is `AGENT_SESSION_INVALID_OPTIONS`.
14
+ - `resumeAgentSession` refuses a document written by another backend kind (`AGENT_SESSION_BACKEND_MISMATCH`); documents without a `backend` field are native.
15
+
16
+ ### Added
17
+
18
+ - `SessionBackend`, `BackendOpenContext`, `OpenedBackend`, `BackendInfo`, `TurnContribution`, `SessionAskPort`, `ApprovalRequest`, and `nativeBackend` with `NativeBackendOptions`. `AgentSession.backend` reports the backend's kind and capabilities.
19
+ - The `ask` profile: every Write, Edit, Delete, GitCommit and Bash is approved through `answer()`; it requires `bashApproval: "gated"` and the sandbox floor of `full`.
20
+ - `ApprovalDecidedBy` gains `"profile"`; `usage` and `turn_end` gain `costSource` (`computed` | `reported` | `unpriced`; `CostSource`); `TurnResult` gains `costSource`.
21
+ - Error codes `AGENT_SESSION_BACKEND_UNAVAILABLE`, `AGENT_SESSION_AUTH_REQUIRED`, `AGENT_SESSION_CAPABILITY_UNSUPPORTED`, `AGENT_SESSION_BACKEND_MISMATCH`.
22
+ - `TranscriptDoc.backend` and `TranscriptDoc.acp` (`TranscriptAcpRecord`), optional and additive (schemaVersion stays 1).
23
+ - `TranscriptAcpRecord.costUsd`: the ACP agent's cumulative cost reading at the session's last priced turn, so a resumed ACP session prices its next turn from it.
24
+ - Backend kit on `.`: `redactSecrets`, `capStrings`, `killProcessGroup`, `isProcessAlive`, `TOOL_CALL_INPUT_BYTES`, `TOOL_RESULT_PREVIEW_BYTES`, `createStderrTail` (`StderrTail`), and `NaxError`, the base class of `AgentSessionError`, so a backend can throw codes outside `AgentSessionErrorCode` (S4 spec §5.7).
25
+ - `getLogger` on `.`: the logger the host installed with `setAgentLogger`, or a silent no-op, so a backend can log through the host's logger.
26
+ - `SessionAskPort.askQuestion(text, { signal })`: an extra abort source combined with the turn signal, as `ApprovalRequest.signal` is for approvals, so a backend can settle a question when its own request scope ends (S4 spec §6.3 step 5).
27
+
28
+ ## [0.2.0] - 2026-10-05
29
+
30
+ The conversational session API for embedders. nax behaviour unchanged.
31
+
32
+ ### Added
33
+
34
+ - `createAgentSession` and `resumeAgentSession`: a multi-turn session with streamed `SessionEvent`s, embedder tools (`EmbedderTool`, approval `"never" | "always"`), approvals and questions answered with `session.answer()`, `cancel()`, `close()`, and the `none` / `read` / `full` tool profiles. Types: `AgentSession`, `CreateAgentSessionOptions`, `AgentSessionHostPorts`, `AgentSessionProfile`, `EmbedderToolContext`, `EmbedderToolResult`, `SessionEvent`, `SessionEventBase`, `SessionEventBody`, `TurnEndStatus`, `ApprovalDecidedBy`, `AnswerReply`, `AnswerStatus`. Errors: `AgentSessionError` with `AgentSessionErrorCode` (`AGENT_SESSION_*`).
35
+ - `resumeAgentSession` reopens a stored session after a restart; a turn the dead process left running is reported as `lastTurn.status: "interrupted"`.
36
+ - Facade sessions retry a transient provider fault after text has streamed (3 attempts) and emit `stream_reset` for the voided deltas.
37
+ - The `TranscriptStore` port: `TranscriptDoc`, `TurnMarker`, `createFileTranscriptStore`, `createMemoryTranscriptStore` (`MemoryTranscriptStore`). `OpenSessionOpts` gains `transcriptStore`, `retainOnClose` and `systemPrompt`.
38
+ - Native model calls stream. `SendTurnOpts.onTurnEvent` receives `TurnEvent`s (`TurnEventSink`): text and thinking deltas, `stream_reset`, `tool_call`, `tool_result`, per-call `usage` and `compaction`. Payloads are redacted and byte-capped.
39
+ - Per-session credential sources (`CredentialSource`: `memory`, `exec`) and adapter-owned clients (`NativeSessionAdapterOptions`); `AuthStamp.source` may be `memory`.
40
+ - The loop-handler and loop-event types are public on `.`: `LoopHandlerSet`, `LoopHandlerEntry`, `LoopHandlerContext`, `LoopEvent`, `LoopEventMap`, `PayloadOf`, `PatchOf`, `ExternalHandlerOf`, `CompleteCallOptions` and the `Before*` / `After*` / `TransformContext*` payload, patch and outcome types. So are the command-interceptor types `CommandInterceptor`, `InterceptRequest`, `InterceptResult`, `ShellInterceptRequest` and `ShellInterceptResult`.
41
+ - The `OwnedPathsPolicy` host port with `EMPTY_OWNED_PATHS_POLICY` and `OwnedBashCandidate`: which paths the host owns the writes to, and how its refusals read. nax supplies its own policy; an embedder that injects nothing gets the empty policy.
42
+
43
+ ### Changed
44
+
45
+ - `resolveWithin` gains a required third parameter (`ownedPaths`). `SandboxPolicyInput` gains `ownedPaths` (required) and `projectStateDir` (optional), and `buildSandboxPolicy` follows.
46
+ - Three `ProtectedPathsPolicy` fields (`projectStateDir`, `credentialDir`, `trustStoreFile`) become optional; the sandbox skips the absent ones.
47
+ - `Read`, `Glob` and `Grep` refuse a symlink-resolved host credential directory or trust-store file when the workdir contains it, regardless of grant.
48
+ - `./internal` (outside semver): the module-scope native session maps are replaced by a per-adapter `NativeSessionState`.
49
+
7
50
  ## [0.1.0] - 2026-10-03
8
51
 
9
52
  First published version. Extracted from nax, where it was the native agent.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  nax's native coding agent as a package: the session contract, the native session adapter and its turn loop over `@nathapp/nax-ai`, the tool set, permission resolution, the OS sandbox, command-safety and cost accounting.
4
4
 
5
- > **Pre-1.0, not yet published.** `0.1.0` is the first planned version. Until `1.0` the API of `.` may change in any minor release. Pin an exact version.
5
+ > **Pre-1.0.** Until `1.0` the API of `.` may change in any minor release. Pin an exact version.
6
6
 
7
7
  ## Install
8
8
 
@@ -37,9 +37,63 @@ Three pieces of host knowledge are passed in as data or functions, because the p
37
37
  - **`ProtectedPathsPolicy`** names the paths you own and want kept away from the agent: git pathspecs the Git tool excludes from its default view, gitignore patterns `GitCommit` refuses to stage, the project state directory, the credential directory and the trust-store file the sandbox protects. If you do not supply it, the Git tool excludes nothing from its default view, and `GitCommit` fails closed: it refuses every path and stages nothing until the policy supplies a non-empty `gitIgnorePatterns` (an empty list refuses the same way). Supply it whenever the agent works in a directory that holds files you own. Building a sandboxed session requires it.
38
38
  - **`commandInterceptor`** may rewrite a command before it runs (for example to prefix a wrapper binary). Rewrites are validated: an argv rewrite may only prefix the original argv with the provider's own binary, and a shell rewrite goes through the same narrowing. If you do not supply one, commands run unchanged. An interceptor that throws, or returns a rewrite that fails validation, is treated as a decline, and the original command runs.
39
39
 
40
+ ## Conversational sessions
41
+
42
+ `createAgentSession` gives an embedder (for example a long-running Node service) a multi-turn chat with a person in the loop. Events stream, tools come from the embedder, history lives in a store the embedder supplies, and a turn can be cancelled or answered with an approval.
43
+
44
+ ```ts
45
+ import { createAgentSession, createFileTranscriptStore, type EmbedderTool, nativeBackend } from "@nathapp/nax-agent";
46
+
47
+ const lookupOrder: EmbedderTool = {
48
+ name: "lookup_order",
49
+ description: "Look up an order by id.",
50
+ inputSchema: { type: "object", properties: { id: { type: "number" } }, required: ["id"] },
51
+ approval: "always", // ask the person before every run
52
+ async run(input, { signal }) {
53
+ return { content: JSON.stringify(await orders.get(input, { signal })) };
54
+ },
55
+ };
56
+
57
+ const session = await createAgentSession({
58
+ backend: nativeBackend({ model: "anthropic/claude-sonnet-5-5" }),
59
+ profile: "ask",
60
+ workdir: "/abs/project",
61
+ tools: [lookupOrder],
62
+ transcriptStore: createFileTranscriptStore("/abs/state/sessions"),
63
+ });
64
+
65
+ for await (const event of session.send("Where is order 42?")) {
66
+ if (event.type === "text_delta") process.stdout.write(event.text);
67
+ if (event.type === "approval_requested") showApproval(event); // later: session.answer(event.requestId, { decision: "allow" })
68
+ if (event.type === "turn_end") console.log(event.status, event.costUsd);
69
+ }
70
+ await session.close();
71
+ ```
72
+
73
+ Profiles set what a session may do:
74
+
75
+ | Profile | Side effects |
76
+ |:--------|:-------------|
77
+ | `none` | no side effects; |
78
+ | `read` | read-only; |
79
+ | `ask` | every Write, Edit, Delete, GitCommit and Bash is put to `answer()`; |
80
+ | `full` | no prompts; `bashApproval` and the sandbox floor apply. |
81
+
82
+ `createAgentSession` takes a `SessionBackend`. `nativeBackend(opts)` takes `model`, `credentials`, `catalogOverrides`, `loopHandlers`, `hostPorts`, `bashApproval` and `allowUnsandboxed`. The ACP backend ships in `@nathapp/nax-agent-acp`.
83
+
84
+ `usage.costSource` is `computed` (native), `reported` or `unpriced`; never sum `unpriced` rows as cost.
85
+
86
+ - **One turn at a time.** `send()` claims the session's turn slot at once. A second `send()` while a turn runs throws `AGENT_SESSION_BUSY`. The returned iterable is single-use, and the turn starts on its first `next()`. Breaking out of the loop cancels the turn.
87
+ - **Events.** `turn_start`, `text_delta`, `thinking_delta`, `stream_reset`, `tool_call`, `tool_result`, `approval_requested`, `approval_resolved`, `question`, `usage`, `compaction` and `turn_end`. Sessions do not compact, so `compaction` is not emitted and a conversation that outgrows the model's context window ends `errored`. Each carries `sessionId`, `turnId`, `at` and your `metadata`. `turn_end` is always last, and a failed turn arrives as `turn_end`, never as a throw.
88
+ - **Deltas are provisional.** `stream_reset` means the deltas of that round so far are void: the provider call was retried after a transient fault (up to 3 attempts; a rate-limit retry waits the provider's `retryAfter` silently, and `cancel()` ends the wait). `turn_end.output` and the stored transcript are authoritative. While your consumer lags, adjacent deltas are merged; control events are never merged or dropped.
89
+ - **Approvals and questions.** Answer them with `session.answer(requestId, { decision: "allow" | "deny" })` or `{ text }`. An unanswered one is denied after `approvalTimeoutMs` (default 600000, range 30000..3600000). `answer` returns `"accepted"`, `"expired"`, `"cancelled"` or `"unknown"`.
90
+ - **Credentials.** A session's `credentials` (`memory` or `exec`) and `catalogOverrides` give it its own client. Without them it uses the process-wide `configureCredentials` slot.
91
+ - **History and restarts.** The store holds one document per session, saved at the end of every turn. `close()` keeps it. After a restart, `resumeAgentSession(sessionId, options)` reopens it with the same options you created it with; pass `instructions` and `tools` again, since they are not stored. If the process died mid-turn, `session.lastTurn` is `{ turnId, status: "interrupted" }`, and that turn's message is not in history. A resume with a different model throws `AGENT_SESSION_MODEL_MISMATCH`; a different reasoning effort (`[high]`) is the same model. Resuming with a different backend kind throws `AGENT_SESSION_BACKEND_MISMATCH`; a stored document with no `backend` field is native. Do not open one session id twice at once: the store has no lock.
92
+ - **Errors.** Every error is an `AgentSessionError` with a code `AGENT_SESSION_*`: `INVALID_OPTIONS`, `EXISTS`, `BUSY`, `CLOSED`, `INVALID_ANSWER`, `NOT_FOUND`, `SCHEMA_UNSUPPORTED`, `MODEL_MISMATCH`, `SANDBOX_UNAVAILABLE`, `TOOL_NAME_RESERVED`, `BACKEND_UNAVAILABLE`, `AUTH_REQUIRED`, `CAPABILITY_UNSUPPORTED` or `BACKEND_MISMATCH`. A stored document that cannot be read is a `NaxError` with `TRANSCRIPT_CORRUPT`.
93
+
40
94
  ## Status and roadmap
41
95
 
42
- The embedder-facing session API is still being designed, so `0.x` may reshape `.` with a minor bump. See [`CHANGELOG.md`](CHANGELOG.md).
96
+ `0.x` may reshape `.` with a minor bump. `@nathapp/nax-agent-acp` provides an ACP backend for the same API (0.3.0). See [`CHANGELOG.md`](CHANGELOG.md).
43
97
 
44
98
  Maintainers: see the [release procedure](https://github.com/nathapp-io/nax/blob/main/packages/nax-agent/RELEASING.md)
45
99
  for the manual 0.1.0 publish and OTP step, trusted-publisher setup and subsequent tagged releases.
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
2
2
  import type { SandboxConfig } from "#src/config/schemas-sandbox";
3
3
  import { type CommandLauncher, defaultTempRoots, listCredentialFiles, listGitGuardFiles, listNaxEntries, probeSandboxOnce, resolveGitLayout, runTempRoots, sandboxBackendFor, strayCommonDirTripwire } from "#src/sandbox/index";
4
4
  import type { ProtectedPathsPolicy } from "#src/tools/index";
5
+ import type { OwnedPathsPolicy } from "#src/tools/owned-paths";
5
6
  export declare const _sessionSandboxDeps: {
6
7
  backendFor: typeof sandboxBackendFor;
7
8
  probe: typeof probeSandboxOnce;
@@ -32,6 +33,8 @@ export declare function resolveSessionSandbox(args: {
32
33
  readonly runTmpRoot?: string;
33
34
  /** Host-owned paths the policy denies (S1 spec port 6); nax passes `naxProtectedPaths()`. */
34
35
  readonly protectedPaths: ProtectedPathsPolicy;
36
+ /** S3-2: host-owned path rules; nax passes naxOwnedPathsPolicy, an embedder EMPTY_OWNED_PATHS_POLICY. */
37
+ readonly ownedPaths: OwnedPathsPolicy;
35
38
  }): Promise<CommandLauncher>;
36
39
  /** The compile-time policy refusal for `raw` (Task 8), or undefined. */
37
40
  export declare function rawRefusalFor(launcher: CommandLauncher | undefined): string | undefined;
@@ -81,7 +81,9 @@ export async function resolveSessionSandbox(args) {
81
81
  });
82
82
  }
83
83
  const git = await _sessionSandboxDeps.gitLayout(args.root);
84
- const credentialFiles = await _sessionSandboxDeps.credentialFiles(args.protectedPaths.credentialDir);
84
+ // S3 spec 6.2: an absent credentialDir denies nothing -- the lookup itself is skipped.
85
+ const credentialDir = args.protectedPaths.credentialDir;
86
+ const credentialFiles = credentialDir === undefined ? [] : await _sessionSandboxDeps.credentialFiles(credentialDir);
85
87
  const approvalsFile = args.outputDir !== undefined ? approvalsPath(args.outputDir) : undefined;
86
88
  // Before the policy: the confined roots include the session temp dir itself.
87
89
  const { tempRoots, confined } = await sessionTempRoots({
@@ -95,12 +97,21 @@ export async function resolveSessionSandbox(args) {
95
97
  git,
96
98
  // Per build, like the .nax entries: a worktree added mid-run gets its denies too.
97
99
  gitGuardFiles: await _sessionSandboxDeps.gitGuardFiles(git),
98
- naxEntries: await _sessionSandboxDeps.naxEntries(root, args.protectedPaths.projectStateDir),
100
+ // S3 spec 6.2: no project-state dir, no lookup and no project-state denies.
101
+ naxEntries: args.protectedPaths.projectStateDir === undefined
102
+ ? []
103
+ : await _sessionSandboxDeps.naxEntries(root, args.protectedPaths.projectStateDir),
99
104
  credentialFiles,
105
+ ...(args.protectedPaths.projectStateDir !== undefined
106
+ ? { projectStateDir: args.protectedPaths.projectStateDir }
107
+ : {}),
108
+ ownedPaths: args.ownedPaths,
100
109
  ...(approvalsFile !== undefined ? { approvalsFile } : {}),
101
110
  // US-006: a command inside the sandbox must not be able to rewrite the
102
111
  // trust store that decides whether repository code runs.
103
- trustStoreFile: args.protectedPaths.trustStoreFile,
112
+ ...(args.protectedPaths.trustStoreFile !== undefined
113
+ ? { trustStoreFile: args.protectedPaths.trustStoreFile }
114
+ : {}),
104
115
  home: _sessionSandboxDeps.homedir(),
105
116
  tempRoots,
106
117
  // #2301: the RESOLVED confinement, not `config.filesystem.allowSharedTmp`.
@@ -15,6 +15,7 @@ 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
17
  import { type CodingTool, type CodingToolName, type CodingToolRuntime, type DeclaredCommandRunner, type ProtectedPathsPolicy, type ToolAuditSink, type ToolGrant, type ToolPatternNarrowing } from "#src/tools/index";
18
+ import type { OwnedPathsPolicy } from "#src/tools/owned-paths";
18
19
  export interface CodingToolSupport {
19
20
  readonly runtime: CodingToolRuntime;
20
21
  readonly tools: readonly CodingTool[];
@@ -105,6 +106,8 @@ export declare function buildCodingToolSupport(args: {
105
106
  fileOutputPath?: string;
106
107
  /** nax#2260: `execution.sandbox.filesystem.allowWrite`, forwarded to `compileToolPolicy`. */
107
108
  naxAllowWrite?: readonly string[];
109
+ /** S3-2: host-owned path rules; forwarded to compileToolPolicy and onto every ToolRunContext. nax passes naxOwnedPathsPolicy. */
110
+ ownedPaths?: OwnedPathsPolicy;
108
111
  bashApproval?: BashApprovalMode;
109
112
  /** Injectable ask resolver (Task 3); defaults to the headless deny resolver. */
110
113
  askResolver?: AskResolver;
@@ -86,10 +86,12 @@ export function buildCodingToolSupport(args) {
86
86
  ...(args.askRules !== undefined ? { askRules: args.askRules } : {}),
87
87
  ...(args.fileOutputPath !== undefined ? { ownedWriteExemption: args.fileOutputPath } : {}),
88
88
  ...(args.naxAllowWrite !== undefined ? { naxAllowWrite: args.naxAllowWrite } : {}),
89
+ ownedPaths: args.ownedPaths,
89
90
  }),
90
91
  declaredCommands: new Set(declaredCommands.keys()),
91
92
  interceptor: args.interceptor,
92
93
  protectedPaths: args.protectedPaths,
94
+ ownedPaths: args.ownedPaths,
93
95
  ...(args.abortSignal !== undefined ? { signal: args.abortSignal } : {}),
94
96
  ...(args.askResolver !== undefined ? { askResolver: args.askResolver } : {}),
95
97
  ...(args.commandShadow !== undefined ? { commandShadow: args.commandShadow } : {}),
package/dist/index.d.ts CHANGED
@@ -7,20 +7,38 @@
7
7
  *
8
8
  * Generated once by S2-7 from the previous `export *` barrels; maintained by hand.
9
9
  */
10
+ export type { CommandInterceptor, InterceptRequest, InterceptResult, ShellInterceptRequest, ShellInterceptResult, } from "#src/command-interceptor/index";
10
11
  export { appendCommandSafetyRow, type BuildCommandShadowOptions, buildCommandShadow, buildRequest, type Classify, COMMAND_SAFETY_DIR, type CommandGuard, type CommandSafetyRow, type CommandShadow, type CommandShadowOptions, createCommandGuard, createCommandShadow, createSystemOneClient, detectTmpWrite, type ExecRun, type FinalOutcome, type GuardDecision, type GuardInput, HARM_OPTIONS, HARM_QUESTION_ID, type HarmOption, isTempOnly, type LedgerOutcome, type MechanicalVerdict, type ModelAnswers, type ModelResult, type Observation, openShadowTap, parseAnswer, QUESTION_IDS, QUESTION_SET_VERSION, type QuestionId, RULE_SET_VERSION, type RuleContext, type RuleResult, type ShadowCall, type ShadowTap, SYSTEMONE_MODEL_LABEL, type SystemOneClientOptions, type SystemOneQuestion, type SystemOneRequest, scoreGuard, scoreRules, shadowCacheKey, toMechanical, } from "#src/command-safety/index";
11
12
  export { addTokenUsage, estimateCostUsd, inputClassTokens, type Pricing, type PricingRates, type PricingTier, priceCall, type TokenUsage, } from "#src/cost/core/index";
12
13
  export { type ModelSpec, parseModelSpec, } from "#src/cost/model-spec";
13
14
  export type { AgentLogger, CredentialAuthConfig, CredentialsConfig } from "#src/infra/index";
14
- export { configureCredentials, setAgentLogger } from "#src/infra/index";
15
- export { AuthCancelledError, type AuthEvent, type AuthInteraction, type AuthLink, type AuthMethod, type AuthOption, type AuthPrompt, type AuthResult, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, credentialFilePath, DEFAULT_PI_AUTH_PATH, type ImportOutcome, importPiCredentials, listStoredProviders, MAX_RETAINED_TRANSCRIPTS, NATIVE_AGENT, type NativeCatalogOverrides, type NativeCompleteContext, type NativeCompleteOptions, type NativeCompleteResult, NativeSessionAdapter, NativeSessionUnsupportedError, type NativeTierConfig, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, providersWithoutCredentials, pruneRetainedTranscripts, type ResolveResult, type ResolveStatus, removeStoredProvider, resolveNativeId, runLogin, type StoredEntry, servedAuth, } from "#src/native/index";
16
- export { type ApprovalAuditRow, type ApprovalEntry, type ApprovalsFile, type ApprovalsFileRead, type ApprovalsFileState, type ApprovalsTaint, ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_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";
15
+ export { configureCredentials, getLogger, setAgentLogger } from "#src/infra/index";
16
+ export { createStderrTail, type StderrTail } from "#src/infra/stderr-tail";
17
+ export { isProcessAlive } from "#src/internal/process-alive";
18
+ export { killProcessGroup } from "#src/internal/process-kill";
19
+ export { capStrings, redactSecrets } from "#src/internal/redact";
20
+ export { NaxError } from "#src/infra/nax-error";
21
+ export type { CredentialSource } from "#src/native/credentials/session-source";
22
+ export { AuthCancelledError, type AuthEvent, type AuthInteraction, type AuthLink, type AuthMethod, type AuthOption, type AuthPrompt, type AuthResult, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, createFileTranscriptStore, createMemoryTranscriptStore, credentialFilePath, DEFAULT_PI_AUTH_PATH, type ImportOutcome, importPiCredentials, listStoredProviders, MAX_RETAINED_TRANSCRIPTS, type MemoryTranscriptStore, NATIVE_AGENT, type NativeCatalogOverrides, type NativeCompleteContext, type NativeCompleteOptions, type NativeCompleteResult, NativeSessionAdapter, NativeSessionUnsupportedError, type NativeTierConfig, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, providersWithoutCredentials, pruneRetainedTranscripts, type ResolveResult, type ResolveStatus, removeStoredProvider, resolveNativeId, runLogin, type StoredEntry, servedAuth, type TranscriptAcpRecord, type TranscriptDoc, type TranscriptStore, type TurnMarker, } from "#src/native/index";
23
+ export type { NativeSessionAdapterOptions } from "#src/native/session-adapter";
24
+ 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";
25
+ export { TOOL_CALL_INPUT_BYTES, TOOL_RESULT_PREVIEW_BYTES } from "#src/native/session/turn-event-emitter";
26
+ 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";
17
27
  export { type AgentGlobOptions, type AgentRuntime, type AgentSpawnOptions, type AgentSpawnResult, type AgentSpawnStdin, getAgentRuntime, nodeRuntime, setAgentRuntime, } from "#src/runtime/index";
18
28
  export { buildSandboxPolicy, type CommandLauncher, type CommandLauncherOptions, createCommandLauncher, createSrtBackend, DISABLED_SANDBOX_STATE, 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, sandboxBackendFor, sandboxSentence, sessionTmpDir, sessionTmpDirUnder, strayCommonDirTripwire, unsandboxedSentence, warnSandboxUnavailableOnce, } from "#src/sandbox/index";
19
29
  export type { AdapterFailure } from "#src/session/adapter-failure";
30
+ export { createAgentSession, resumeAgentSession } from "#src/session/agent-session";
31
+ export { AgentSessionError, type AgentSessionErrorCode } from "#src/session/agent-session-errors";
32
+ export type { AgentSession, AgentSessionHostPorts, AgentSessionProfile, AnswerReply, AnswerStatus, ApprovalDecidedBy, CostSource, CreateAgentSessionOptions, EmbedderTool, EmbedderToolContext, EmbedderToolResult, SessionEvent, SessionEventBase, SessionEventBody, TurnEndStatus, } from "#src/session/agent-session-types";
20
33
  export type { AgentAwaitingHumanEvent, AgentCallEndedEvent, AgentCallStartedEvent, AgentMessageUpdateEvent, AgentProcessUpdateEvent, AgentStreamEvent, AgentStreamEventBase, AgentThinkingUpdateEvent, AgentToolCallUpdateEvent, AgentUsageUpdateEvent, } from "#src/session/agent-stream-events";
21
34
  export { type AdapterInteraction, type AdapterInteractionResponse, type InteractionHandler, NO_OP_INTERACTION_HANDLER, } from "#src/session/interaction-handler";
35
+ export { nativeBackend } from "#src/session/native-backend";
36
+ export type { NativeBackendOptions } from "#src/session/native-backend-options";
22
37
  export type { ProtocolIds } from "#src/session/protocol-types";
38
+ export type { ApprovalRequest, BackendInfo, BackendOpenContext, OpenedBackend, SessionAskPort, SessionBackend, TurnContribution, } from "#src/session/session-backend";
23
39
  export { type AgentSessionAdapter, type AuthStamp, type InteractionExchange, type InvalidToolCallDetail, type OpenSessionOpts, type SendTurnOpts, type SessionHandle, type SessionModel, SessionTurnError, type TrackedSpawnDeadlineOptions, type TurnResult, } from "#src/session/session-types";
24
40
  export type { JSONSchema, ToolDescriptor, } from "#src/session/tool-descriptor";
25
41
  export { createTurnDeadline, type TurnDeadline, } from "#src/session/turn-deadline";
42
+ export type { TurnEvent, TurnEventSink } from "#src/session/turn-event";
26
43
  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_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, requestCapabilityTool, resolveProviderTools, resolveWithin, SCRATCHPAD_DIR, SPILL_DIR, type SpillRequest, sanitizeProviderTools, scratchpadListTool, scratchpadReadTool, scratchpadWriteTool, spillRelativePath, substituteCommand, substituteCommandSpec, type ToolAuditSink, type ToolCallContext, type ToolCallRecord, type ToolGrant, type ToolPatternNarrowing, type ToolPolicy, type ToolPolicyOptions, type ToolProvider, type ToolResult, type ToolRunContext, type ToolScope, type TruncateForModelOptions, type TruncationDirection, type TruncationResult, truncateForModel, truncationDirectionFor, unregisterToolAuditSink, validateProviderId, writeSpill, writeTool, } from "#src/tools/index";
44
+ export { EMPTY_OWNED_PATHS_POLICY, type OwnedBashCandidate, type OwnedPathsPolicy, } from "#src/tools/owned-paths";
package/dist/index.js CHANGED
@@ -10,12 +10,22 @@
10
10
  export { appendCommandSafetyRow, buildCommandShadow, buildRequest, COMMAND_SAFETY_DIR, createCommandGuard, createCommandShadow, createSystemOneClient, detectTmpWrite, HARM_OPTIONS, HARM_QUESTION_ID, isTempOnly, openShadowTap, parseAnswer, QUESTION_IDS, QUESTION_SET_VERSION, RULE_SET_VERSION, SYSTEMONE_MODEL_LABEL, scoreGuard, scoreRules, shadowCacheKey, toMechanical, } from "#src/command-safety/index";
11
11
  export { addTokenUsage, estimateCostUsd, inputClassTokens, priceCall, } from "#src/cost/core/index";
12
12
  export { parseModelSpec, } from "#src/cost/model-spec";
13
- export { configureCredentials, setAgentLogger } from "#src/infra/index";
14
- export { AuthCancelledError, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, credentialFilePath, DEFAULT_PI_AUTH_PATH, importPiCredentials, listStoredProviders, MAX_RETAINED_TRANSCRIPTS, NATIVE_AGENT, NativeSessionAdapter, NativeSessionUnsupportedError, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, providersWithoutCredentials, pruneRetainedTranscripts, removeStoredProvider, resolveNativeId, runLogin, servedAuth, } from "#src/native/index";
15
- export { ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_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";
13
+ export { configureCredentials, getLogger, setAgentLogger } from "#src/infra/index";
14
+ export { createStderrTail } from "#src/infra/stderr-tail";
15
+ export { isProcessAlive } from "#src/internal/process-alive";
16
+ export { killProcessGroup } from "#src/internal/process-kill";
17
+ export { capStrings, redactSecrets } from "#src/internal/redact";
18
+ export { NaxError } from "#src/infra/nax-error";
19
+ export { AuthCancelledError, ambientShadows, anyAmbientCredential, authImportOutcomeLabel, createFileTranscriptStore, createMemoryTranscriptStore, credentialFilePath, DEFAULT_PI_AUTH_PATH, importPiCredentials, listStoredProviders, MAX_RETAINED_TRANSCRIPTS, NATIVE_AGENT, NativeSessionAdapter, NativeSessionUnsupportedError, nativeComplete, nativeTierProviders, naxCredentialStore, newSessionKey, providersWithoutCredentials, pruneRetainedTranscripts, removeStoredProvider, resolveNativeId, runLogin, servedAuth, } from "#src/native/index";
20
+ export { TOOL_CALL_INPUT_BYTES, TOOL_RESULT_PREVIEW_BYTES } from "#src/native/session/turn-event-emitter";
21
+ 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";
16
22
  export { getAgentRuntime, nodeRuntime, setAgentRuntime, } from "#src/runtime/index";
17
23
  export { buildSandboxPolicy, createCommandLauncher, createSrtBackend, DISABLED_SANDBOX_STATE, defaultTempRoots, denialHintLine, LIKELY_SANDBOX_DENIAL, listCredentialFiles, listGitGuardFiles, listNaxEntries, probeSandbox, probeSandboxOnce, quoteArgvForShell, rawBashRefusalReason, resetSandboxBackend, resolveGitLayout, runTempRoots, runTmpRoot, sandboxBackendFor, sandboxSentence, sessionTmpDir, sessionTmpDirUnder, strayCommonDirTripwire, unsandboxedSentence, warnSandboxUnavailableOnce, } from "#src/sandbox/index";
24
+ export { createAgentSession, resumeAgentSession } from "#src/session/agent-session";
25
+ export { AgentSessionError } from "#src/session/agent-session-errors";
18
26
  export { NO_OP_INTERACTION_HANDLER, } from "#src/session/interaction-handler";
27
+ export { nativeBackend } from "#src/session/native-backend";
19
28
  export { SessionTurnError, } from "#src/session/session-types";
20
29
  export { createTurnDeadline, } from "#src/session/turn-deadline";
21
30
  export { adaptProviderTool, advertisedSchemaBytes, applyModelTruncationPolicy, BASH_TIMEOUT_MS, BASH_TOOL_NAME, buildCommitArgvs, buildGitArgv, buildGrepArgv, 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, deleteTool, EXEC_TOOL_NAME, 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_RULE_TOOL, MODEL_MAX_BYTES, MODEL_MAX_LINE_CHARS, MODEL_MAX_LINES, mcpRuleAdmits, namespacedToolName, narrowGrants, normalizeExec, normalizeManagerBinary, PROVIDER_ID_RE, partitionMcpRules, partitionNaxOwnedPaths, providerAttachesTo, READ_CEILING, RESERVED_TOOL_NAMES, readFileSlice, readTool, registerBuiltinCodingTools, registerBuiltinTool, registerCodingTool, registerToolAuditSink, requestCapabilityTool, resolveProviderTools, resolveWithin, SCRATCHPAD_DIR, SPILL_DIR, sanitizeProviderTools, scratchpadListTool, scratchpadReadTool, scratchpadWriteTool, spillRelativePath, substituteCommand, substituteCommandSpec, truncateForModel, truncationDirectionFor, unregisterToolAuditSink, validateProviderId, writeSpill, writeTool, } from "#src/tools/index";
31
+ export { EMPTY_OWNED_PATHS_POLICY, } from "#src/tools/owned-paths";
@@ -0,0 +1,8 @@
1
+ export interface StderrTail {
2
+ push(chunk: string | Uint8Array): void;
3
+ excerpt(opts?: {
4
+ readonly maxBytes?: number;
5
+ readonly secrets?: readonly string[];
6
+ }): string;
7
+ }
8
+ export declare function createStderrTail(capacityBytes?: number): StderrTail;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A bounded tail of a child process's stderr (S4 spec 5.6, 7). Backends keep
3
+ * the last bytes for error excerpts; excerpt() strips control characters,
4
+ * redacts caller-known secrets and known token shapes, and returns at most
5
+ * maxBytes from the end, so an error never carries a raw agent log.
6
+ */
7
+ import { redactSecrets } from "#src/internal/redact";
8
+ const DEFAULT_CAPACITY = 65_536;
9
+ const DEFAULT_EXCERPT = 4096;
10
+ const MIN_SECRET_LENGTH = 4;
11
+ // C0 controls and DEL, except tab (\u0009) and newline (\u000A).
12
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping control characters is the point of this regex
13
+ const CONTROL = /[\u0000-\u0008\u000B-\u001F\u007F]/g;
14
+ function lastBytes(text, maxBytes) {
15
+ const bytes = Buffer.from(text, "utf8");
16
+ if (bytes.byteLength <= maxBytes)
17
+ return text;
18
+ // A cut inside a multi-byte character decodes to U+FFFD; drop it.
19
+ return bytes
20
+ .subarray(bytes.byteLength - maxBytes)
21
+ .toString("utf8")
22
+ .replace(/^�+/, "");
23
+ }
24
+ function scrub(text, secrets) {
25
+ let out = text.replace(CONTROL, "");
26
+ for (const secret of secrets) {
27
+ if (secret.length >= MIN_SECRET_LENGTH)
28
+ out = out.split(secret).join("[REDACTED]");
29
+ }
30
+ return redactSecrets(out);
31
+ }
32
+ export function createStderrTail(capacityBytes = DEFAULT_CAPACITY) {
33
+ const decoder = new TextDecoder();
34
+ let buffer = "";
35
+ return {
36
+ push(chunk) {
37
+ buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
38
+ // Known limitation: trimming at the raw capacity boundary can cut a
39
+ // caller secret in two, leaving the older half as a suffix in the buffer;
40
+ // excerpt() redacts only whole occurrences it sees. Capacity semantics are
41
+ // deliberate and stay as they are.
42
+ buffer = lastBytes(buffer, capacityBytes);
43
+ },
44
+ excerpt(opts = {}) {
45
+ return lastBytes(scrub(buffer, opts.secrets ?? []), opts.maxBytes ?? DEFAULT_EXCERPT);
46
+ },
47
+ };
48
+ }
@@ -38,3 +38,26 @@ export declare function redactEntry<T extends {
38
38
  message: string;
39
39
  data?: Record<string, unknown>;
40
40
  }>(entry: T): T;
41
+ /**
42
+ * A copy of `value` with every string cut to `scanBytes`, so a downstream
43
+ * regex scan (typically `redactSecrets`) never walks a multi-MB payload.
44
+ * Cycles become `"[Circular]"`; only the current recursion path is "seen",
45
+ * matching `redactValue` so a shared, non-cyclic reference is copied, not
46
+ * labelled.
47
+ *
48
+ * Exported for callers that need to bound their own redaction: the JSONL
49
+ * logger writes via `redactSecrets` without a cap because individual log
50
+ * entries are bounded; `native/session/turn-event-emitter.ts:cappedInput`
51
+ * and `session/session-interaction.ts:defaultSummary` cap before their
52
+ * downstream scan so a 2 MB `Write` content or embedder tool input does not
53
+ * run every `SECRET_VALUE_PATTERNS` regex over megabytes.
54
+ *
55
+ * The byte cap is inlined rather than imported from `tools/truncate.ts`:
56
+ * `internal/` is below `tools/`, so the direction is one-way. No
57
+ * `MAX_REDACT_DEPTH` guard here — `redactValue` carries one as a backstop
58
+ * against pathologically deep-but-acyclic input; capStrings is only called
59
+ * on values the surrounding code already bounds (tool inputs under the
60
+ * provider's request cap, embedder describe strings under the model call
61
+ * size), and any cycle is caught by the WeakSet on the current path.
62
+ */
63
+ export declare function capStrings(value: unknown, scanBytes: number, seen?: WeakSet<object>): unknown;
@@ -129,6 +129,66 @@ export function redactEntry(entry) {
129
129
  /** Depth guard is a backstop against pathological (non-circular but very deep) input; the WeakSet is what actually stops cycles. */
130
130
  const MAX_REDACT_DEPTH = 100;
131
131
  const CIRCULAR_REF_MARKER = "[Circular]";
132
+ /**
133
+ * A copy of `value` with every string cut to `scanBytes`, so a downstream
134
+ * regex scan (typically `redactSecrets`) never walks a multi-MB payload.
135
+ * Cycles become `"[Circular]"`; only the current recursion path is "seen",
136
+ * matching `redactValue` so a shared, non-cyclic reference is copied, not
137
+ * labelled.
138
+ *
139
+ * Exported for callers that need to bound their own redaction: the JSONL
140
+ * logger writes via `redactSecrets` without a cap because individual log
141
+ * entries are bounded; `native/session/turn-event-emitter.ts:cappedInput`
142
+ * and `session/session-interaction.ts:defaultSummary` cap before their
143
+ * downstream scan so a 2 MB `Write` content or embedder tool input does not
144
+ * run every `SECRET_VALUE_PATTERNS` regex over megabytes.
145
+ *
146
+ * The byte cap is inlined rather than imported from `tools/truncate.ts`:
147
+ * `internal/` is below `tools/`, so the direction is one-way. No
148
+ * `MAX_REDACT_DEPTH` guard here — `redactValue` carries one as a backstop
149
+ * against pathologically deep-but-acyclic input; capStrings is only called
150
+ * on values the surrounding code already bounds (tool inputs under the
151
+ * provider's request cap, embedder describe strings under the model call
152
+ * size), and any cycle is caught by the WeakSet on the current path.
153
+ */
154
+ export function capStrings(value, scanBytes, seen = new WeakSet()) {
155
+ if (typeof value === "string")
156
+ return cutStringToBytes(value, scanBytes);
157
+ if (typeof value !== "object" || value === null)
158
+ return value;
159
+ if (seen.has(value))
160
+ return CIRCULAR_REF_MARKER;
161
+ seen.add(value);
162
+ try {
163
+ if (Array.isArray(value))
164
+ return value.map((item) => capStrings(item, scanBytes, seen));
165
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, capStrings(item, scanBytes, seen)]));
166
+ }
167
+ finally {
168
+ seen.delete(value);
169
+ }
170
+ }
171
+ /**
172
+ * Backs up over UTF-8 continuation bytes so a cut inside a codepoint lands
173
+ * on a whole one. Mirrors `tools/truncate.ts:cutToByteCap` without the
174
+ * `internal/` → `tools/` dependency. Continuation bytes span `0x80`–`0xBF`
175
+ * (`10xxxxxx`); the predicate matches that range, not the single value
176
+ * `0x80`, so a multi-byte char whose boundary lands on a different
177
+ * continuation byte (`0xA9`, `0xB8`, …) is also rejected.
178
+ */
179
+ function cutStringToBytes(body, maxBytes) {
180
+ if (Buffer.byteLength(body, "utf8") <= maxBytes)
181
+ return body;
182
+ const buf = Buffer.from(body, "utf8");
183
+ let cut = Math.min(buf.length, maxBytes);
184
+ while (cut > 0) {
185
+ const byte = buf[cut] ?? 0;
186
+ if (byte < 0x80 || byte > 0xbf)
187
+ break;
188
+ cut -= 1;
189
+ }
190
+ return buf.subarray(0, cut).toString("utf8");
191
+ }
132
192
  /**
133
193
  * MED-02: unguarded recursion here threw a RangeError (stack overflow) out
134
194
  * of every logger call whenever a data payload contained a circular
@@ -60,25 +60,32 @@ export * from "#src/native/session/compaction";
60
60
  export * from "#src/native/session/loop-events/external-handler";
61
61
  export * from "#src/native/session/loop-events/index";
62
62
  export * from "#src/native/session/loop-events/types";
63
+ export * from "#src/native/session/memory-transcript-store";
63
64
  export * from "#src/native/session/session";
64
65
  export * as sessionModule from "#src/native/session/session";
65
66
  export * from "#src/native/session/tool-mapping";
67
+ export * from "#src/native/session/transcript-identity";
66
68
  export * from "#src/native/session/transcript-store";
67
69
  export * as transcriptStoreModule from "#src/native/session/transcript-store";
70
+ export * from "#src/native/session/transcript-types";
68
71
  export * from "#src/native/session/turn-accumulator";
69
72
  export * from "#src/native/session/turn-complete-step";
70
73
  export * from "#src/native/session/turn-events";
71
74
  export * from "#src/native/session/turn-loop";
72
75
  export * from "#src/native/session/turn-retry";
73
76
  export * from "#src/native/session/turn-types";
77
+ export { nativeSessionStateOf } from "#src/native/session-adapter";
74
78
  export * from "#src/native/session-affinity";
75
79
  export * from "#src/permissions/index";
76
80
  export * from "#src/sandbox/index";
77
81
  export * as policyInputsModule from "#src/sandbox/policy-inputs";
82
+ export * from "#src/session/agent-session-deps";
83
+ export * from "#src/tools/credential-read-deny";
78
84
  export * from "#src/tools/git";
79
85
  export * from "#src/tools/git-commit";
80
86
  export * from "#src/tools/grep";
81
87
  export * from "#src/tools/index";
88
+ export * from "#src/tools/owned-paths";
82
89
  export * from "#src/tools/package-managers";
83
90
  export * from "#src/tools/policy";
84
91
  export * from "#src/tools/provider-grants";
package/dist/internal.js CHANGED
@@ -60,25 +60,33 @@ export * from "#src/native/session/compaction";
60
60
  export * from "#src/native/session/loop-events/external-handler";
61
61
  export * from "#src/native/session/loop-events/index";
62
62
  export * from "#src/native/session/loop-events/types";
63
+ export * from "#src/native/session/memory-transcript-store";
63
64
  export * from "#src/native/session/session";
64
65
  export * as sessionModule from "#src/native/session/session";
65
66
  export * from "#src/native/session/tool-mapping";
67
+ export * from "#src/native/session/transcript-identity";
66
68
  export * from "#src/native/session/transcript-store";
67
69
  export * as transcriptStoreModule from "#src/native/session/transcript-store";
70
+ export * from "#src/native/session/transcript-types";
68
71
  export * from "#src/native/session/turn-accumulator";
69
72
  export * from "#src/native/session/turn-complete-step";
70
73
  export * from "#src/native/session/turn-events";
71
74
  export * from "#src/native/session/turn-loop";
72
75
  export * from "#src/native/session/turn-retry";
73
76
  export * from "#src/native/session/turn-types";
77
+ export { nativeSessionStateOf } from "#src/native/session-adapter";
74
78
  export * from "#src/native/session-affinity";
75
79
  export * from "#src/permissions/index";
76
80
  export * from "#src/sandbox/index";
77
81
  export * as policyInputsModule from "#src/sandbox/policy-inputs";
82
+ export * from "#src/session/agent-session-deps";
83
+ export * from "#src/tools/credential-read-deny";
78
84
  export * from "#src/tools/git";
79
85
  export * from "#src/tools/git-commit";
80
86
  export * from "#src/tools/grep";
81
87
  export * from "#src/tools/index";
88
+ // S3-2: the port (nax supplies the policy behind it from its own module).
89
+ export * from "#src/tools/owned-paths";
82
90
  export * from "#src/tools/package-managers";
83
91
  export * from "#src/tools/policy";
84
92
  export * from "#src/tools/provider-grants";
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { AuthStamp } from "#src/session/session-types";
7
7
  import { anyAmbientCredential, listStoredProviders } from "./auth.ts";
8
- import { authSourceIsExec, servedAuth } from "./credentials/index.ts";
8
+ import { authSourceIsExec, type GuardedCredentialStore, servedAuth } from "./credentials/index.ts";
9
9
  export declare function isProtocolStreamError(err: unknown): err is {
10
10
  protocolError: {
11
11
  kind: string;
@@ -39,8 +39,8 @@ export declare const _adapterDeps: {
39
39
  * `provider`, `{}` otherwise — so the key stays absent, never `undefined`. A
40
40
  * helper because `sendTurn` cannot carry another branch on the complexity
41
41
  * ratchet; it reads the `_adapterDeps` seam so a unit test can pin the stamp
42
- * without assembling a real store.
42
+ * without assembling a real store. With `store`, it reads only that store.
43
43
  */
44
- export declare function authFields(provider: string): {
44
+ export declare function authFields(provider: string, store?: GuardedCredentialStore): {
45
45
  auth?: AuthStamp;
46
46
  };
@@ -35,9 +35,9 @@ export const _adapterDeps = {
35
35
  * `provider`, `{}` otherwise — so the key stays absent, never `undefined`. A
36
36
  * helper because `sendTurn` cannot carry another branch on the complexity
37
37
  * ratchet; it reads the `_adapterDeps` seam so a unit test can pin the stamp
38
- * without assembling a real store.
38
+ * without assembling a real store. With `store`, it reads only that store.
39
39
  */
40
- export function authFields(provider) {
41
- const auth = _adapterDeps.servedAuth(provider);
40
+ export function authFields(provider, store) {
41
+ const auth = store === undefined ? _adapterDeps.servedAuth(provider) : store.servedAuth(provider);
42
42
  return auth === undefined ? {} : { auth };
43
43
  }
@@ -8,7 +8,7 @@
8
8
  * This file and its siblings are the only place in src/ permitted to import
9
9
  * nax-ai (packages/repo-tooling/scripts/check-nax-ai-imports.ts).
10
10
  */
11
- import { type Client, defaultProtocols } from "@nathapp/nax-ai";
11
+ import { type Client, type CredentialStore, defaultProtocols } from "@nathapp/nax-ai";
12
12
  import type { ProviderCatalogOverride } from "#src/config/catalog-overrides";
13
13
  /** The catalog overrides a native client is built with (`agent.native.catalogOverrides`). */
14
14
  export type NativeCatalogOverrides = readonly ProviderCatalogOverride[];
@@ -26,6 +26,12 @@ export interface BuildNativeClientOptions {
26
26
  * in tests that intentionally fail at the protocol boundary.
27
27
  */
28
28
  readonly transportRetries?: number;
29
+ /**
30
+ * The store the protocols resolve credentials from. Absent = the process
31
+ * store (`naxCredentialStore()`). A session that owns its credentials passes
32
+ * its own store, so it never reads the process slot.
33
+ */
34
+ readonly credentials?: CredentialStore;
29
35
  }
30
36
  /**
31
37
  * The real builder. Exported on its own — not just as `_clientDeps.build` —
@@ -57,7 +57,7 @@ export async function buildNativeClient(catalogOverrides = [], options = {}) {
57
57
  // is what makes `nax auth login` reach a run. This is the only inlet:
58
58
  // ClientOptions once carried a `credentials` field that createClient
59
59
  // never read, and nax-ai 0.1.4 removed it for exactly that reason.
60
- credentials: naxCredentialStore(),
60
+ credentials: options.credentials ?? naxCredentialStore(),
61
61
  // Construction-time, like `credentials`: the identity is a constant of
62
62
  // the process, so nax-ai takes it here rather than on every request.
63
63
  clientApp: NAX_CLIENT_APP,