@nathapp/nax-agent 0.87.0 → 0.87.1
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 +4 -0
- package/dist/coding-tools/coding-tool-support.d.ts +15 -1
- package/dist/coding-tools/coding-tool-support.js +19 -8
- package/dist/index.d.ts +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.js +1 -0
- package/dist/native/session/session.d.ts +3 -0
- package/dist/native/session/session.js +7 -0
- package/dist/native/session/transcript-archive.d.ts +28 -0
- package/dist/native/session/transcript-archive.js +43 -0
- package/dist/session/session-types.d.ts +19 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/provider-advertise.d.ts +18 -0
- package/dist/tools/provider-advertise.js +16 -5
- package/dist/tools/tool-audit.d.ts +42 -0
- package/dist/tools/tool-audit.js +5 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@ 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.87.1] - 2026-10-12
|
|
8
|
+
|
|
9
|
+
- No changes in this package. Released in lockstep with the other nax packages.
|
|
10
|
+
|
|
7
11
|
## [0.87.0] - 2026-10-11
|
|
8
12
|
|
|
9
13
|
- No changes in this package. Released in lockstep with the other nax packages.
|
|
@@ -14,12 +14,24 @@ 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 CodingTool, type CodingToolName, type CodingToolRuntime, type DeclaredCommandRunner, type ProtectedPathsPolicy, type ToolAuditSink, type ToolGrant, type ToolPatternNarrowing } from "#src/tools/index";
|
|
17
|
+
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
18
|
import type { OwnedPathsPolicy } from "#src/tools/owned-paths";
|
|
19
19
|
export interface CodingToolSupport {
|
|
20
20
|
readonly runtime: CodingToolRuntime;
|
|
21
21
|
readonly tools: readonly CodingTool[];
|
|
22
22
|
readonly auditSink: ToolAuditSink;
|
|
23
|
+
/** The materialized declaration this support resolved from (nax#2444 D2) — set on every resolve. */
|
|
24
|
+
readonly declaredTools?: readonly CodingToolName[];
|
|
25
|
+
/** The session's MCP tool surface (nax#2410 D4), stamped into the audit file at flush. */
|
|
26
|
+
readonly toolSurface?: ToolSurfaceRecord;
|
|
27
|
+
/**
|
|
28
|
+
* The policy-filtered provider-tool delivery for an ACP session (nax#2444 D4),
|
|
29
|
+
* attached by the nax resolver; the ACP adapter adapts `tools` to EmbedderTools.
|
|
30
|
+
*/
|
|
31
|
+
readonly acpDelivery?: {
|
|
32
|
+
readonly tools: readonly CodingTool[];
|
|
33
|
+
readonly surface: ToolSurfaceDraft;
|
|
34
|
+
};
|
|
23
35
|
}
|
|
24
36
|
export declare function buildCodingToolSupport(args: {
|
|
25
37
|
root?: string;
|
|
@@ -76,6 +88,8 @@ export declare function buildCodingToolSupport(args: {
|
|
|
76
88
|
/** `quality.shell` — the shell the Bash tool spawns. Defaults to /bin/sh. */
|
|
77
89
|
shell?: string;
|
|
78
90
|
auditDir?: string;
|
|
91
|
+
/** Stamped into the audit file at flush (nax#2410 D4); absent ⇒ no surface block. */
|
|
92
|
+
toolSurface?: ToolSurfaceRecord;
|
|
79
93
|
sessionName?: string;
|
|
80
94
|
header?: import("../tools/tool-audit.ts").ToolAuditHeader;
|
|
81
95
|
callId?: string;
|
|
@@ -71,13 +71,7 @@ export function buildCodingToolSupport(args) {
|
|
|
71
71
|
// P4/S5: under `raw`, an UNAVAILABLE launcher refuses every Bash call.
|
|
72
72
|
// US-002: an AVAILABLE one marks the screen sandbox-wrapped, so a PRD it only reads is allowed.
|
|
73
73
|
const rawScreenOptions = rawScreenOptionsFor(args.launcher);
|
|
74
|
-
const sink = args
|
|
75
|
-
? createToolAuditSink({
|
|
76
|
-
dir: args.auditDir,
|
|
77
|
-
sessionName: args.sessionName ?? "unattached",
|
|
78
|
-
...(args.header ? { header: args.header } : {}),
|
|
79
|
-
})
|
|
80
|
-
: createNoOpToolAuditSink();
|
|
74
|
+
const sink = supportSink(args);
|
|
81
75
|
const runtime = createCodingToolRuntime({
|
|
82
76
|
policy: compileToolPolicy(effectiveGrants, args.root, {
|
|
83
77
|
bashApproval,
|
|
@@ -144,7 +138,24 @@ export function buildCodingToolSupport(args) {
|
|
|
144
138
|
}
|
|
145
139
|
if (tools.length === 0)
|
|
146
140
|
return undefined;
|
|
147
|
-
return {
|
|
141
|
+
return {
|
|
142
|
+
runtime,
|
|
143
|
+
tools,
|
|
144
|
+
auditSink: sink,
|
|
145
|
+
declaredTools: args.declared,
|
|
146
|
+
...(args.toolSurface !== undefined ? { toolSurface: args.toolSurface } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** The audit sink a support resolution records through; no dir ⇒ a non-recording sink. */
|
|
150
|
+
function supportSink(args) {
|
|
151
|
+
if (args.auditDir === undefined)
|
|
152
|
+
return createNoOpToolAuditSink();
|
|
153
|
+
return createToolAuditSink({
|
|
154
|
+
dir: args.auditDir,
|
|
155
|
+
sessionName: args.sessionName ?? "unattached",
|
|
156
|
+
...(args.header !== undefined ? { header: args.header } : {}),
|
|
157
|
+
...(args.toolSurface !== undefined ? { toolSurface: args.toolSurface } : {}),
|
|
158
|
+
});
|
|
148
159
|
}
|
|
149
160
|
/**
|
|
150
161
|
* Ledger session name.
|
package/dist/index.d.ts
CHANGED
|
@@ -42,5 +42,5 @@ export { attachTurnSpend, type FailedTurnSpend, readTurnSpend } from "#src/sessi
|
|
|
42
42
|
export type { JSONSchema, ToolDescriptor, } from "#src/session/tool-descriptor";
|
|
43
43
|
export { createTurnDeadline, type TurnDeadline, } from "#src/session/turn-deadline";
|
|
44
44
|
export type { TurnEvent, TurnEventSink } from "#src/session/turn-event";
|
|
45
|
-
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 ToolAuditHeader, 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";
|
|
45
|
+
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 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
46
|
export { EMPTY_OWNED_PATHS_POLICY, type OwnedBashCandidate, type OwnedPathsPolicy, } from "#src/tools/owned-paths";
|
package/dist/internal.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ export { _repositoryInstructionDeps } from "#src/native/session/repository-instr
|
|
|
69
69
|
export * from "#src/native/session/session";
|
|
70
70
|
export * as sessionModule from "#src/native/session/session";
|
|
71
71
|
export * from "#src/native/session/tool-mapping";
|
|
72
|
+
export { _transcriptArchiveDeps } from "#src/native/session/transcript-archive";
|
|
72
73
|
export * from "#src/native/session/transcript-identity";
|
|
73
74
|
export * from "#src/native/session/transcript-store";
|
|
74
75
|
export * as transcriptStoreModule from "#src/native/session/transcript-store";
|
package/dist/internal.js
CHANGED
|
@@ -69,6 +69,7 @@ export { _repositoryInstructionDeps } from "#src/native/session/repository-instr
|
|
|
69
69
|
export * from "#src/native/session/session";
|
|
70
70
|
export * as sessionModule from "#src/native/session/session";
|
|
71
71
|
export * from "#src/native/session/tool-mapping";
|
|
72
|
+
export { _transcriptArchiveDeps } from "#src/native/session/transcript-archive";
|
|
72
73
|
export * from "#src/native/session/transcript-identity";
|
|
73
74
|
export * from "#src/native/session/transcript-store";
|
|
74
75
|
export * as transcriptStoreModule from "#src/native/session/transcript-store";
|
|
@@ -11,6 +11,7 @@ import type { AgentStreamEvent } from "#src/session/agent-stream-events";
|
|
|
11
11
|
import type { OpenSessionOpts, SessionHandle } from "#src/session/session-types";
|
|
12
12
|
import type { ResolvedCompaction } from "./compaction.ts";
|
|
13
13
|
import type { RepositoryInstructions } from "./repository-instructions.ts";
|
|
14
|
+
import { type TranscriptArchive } from "./transcript-archive.ts";
|
|
14
15
|
import type { TranscriptStore } from "./transcript-types.ts";
|
|
15
16
|
import type { TurnRetryConfig } from "./turn-retry.ts";
|
|
16
17
|
/** The runtime hooks handed to openSession. */
|
|
@@ -22,6 +23,8 @@ export interface NativeSessionStreamHooks {
|
|
|
22
23
|
export interface SessionTranscript {
|
|
23
24
|
readonly store: TranscriptStore;
|
|
24
25
|
readonly retainOnClose: boolean;
|
|
26
|
+
/** When set, close writes a redacted copy here before deleting or retaining the live document (nax#2453). */
|
|
27
|
+
readonly transcriptArchive?: TranscriptArchive;
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
27
30
|
* The per-owner state behind the native session lifecycle.
|
|
@@ -13,6 +13,7 @@ import { nativeSessionId } from "../session-affinity.js";
|
|
|
13
13
|
import { instructionFileNameFor } from "./instruction-file-name.js";
|
|
14
14
|
import { openRepositoryInstructions } from "./instruction-session.js";
|
|
15
15
|
import { instructionTranscriptStore } from "./instruction-transcript.js";
|
|
16
|
+
import { archiveTranscript } from "./transcript-archive.js";
|
|
16
17
|
import { createFileTranscriptStore } from "./transcript-store.js";
|
|
17
18
|
export function createNativeSessionState() {
|
|
18
19
|
return {
|
|
@@ -106,6 +107,7 @@ export async function openNativeSession(state, name, opts) {
|
|
|
106
107
|
state.transcripts.set(name, {
|
|
107
108
|
store,
|
|
108
109
|
retainOnClose: opts.retainOnClose === true,
|
|
110
|
+
transcriptArchive: opts.transcriptArchive,
|
|
109
111
|
});
|
|
110
112
|
state.timeouts.set(name, opts.timeoutSeconds);
|
|
111
113
|
state.scratchpadRoots.set(name, opts.workdir);
|
|
@@ -123,6 +125,7 @@ export async function openNativeSession(state, name, opts) {
|
|
|
123
125
|
state.transcripts.set(name, {
|
|
124
126
|
store: instructionTranscriptStore(store, instructions),
|
|
125
127
|
retainOnClose: opts.retainOnClose === true,
|
|
128
|
+
transcriptArchive: opts.transcriptArchive,
|
|
126
129
|
});
|
|
127
130
|
// `resume` is SessionManager's "this name already has a descriptor in this
|
|
128
131
|
// process" signal, and it had no consumer on this transport (nax#1877) — a
|
|
@@ -199,6 +202,10 @@ export async function closeNativeSession(state, handle, failed) {
|
|
|
199
202
|
const treatAsFailed = failed ?? state.failed.has(handle.id);
|
|
200
203
|
try {
|
|
201
204
|
if (transcript !== undefined && !transcript.retainOnClose) {
|
|
205
|
+
// Archive first (nax#2453): best-effort, never throws, so the branch below is unchanged.
|
|
206
|
+
if (transcript.transcriptArchive !== undefined) {
|
|
207
|
+
await archiveTranscript(transcript.store, handle.id, transcript.transcriptArchive);
|
|
208
|
+
}
|
|
202
209
|
// Retain for a human, out of reach of the next session of this name.
|
|
203
210
|
if (treatAsFailed)
|
|
204
211
|
await transcript.store.retainFailed(handle.id);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit archive for native session transcripts (nax#2453).
|
|
3
|
+
*
|
|
4
|
+
* `closeNativeSession` deletes a clean session's transcript and renames a failed
|
|
5
|
+
* one, so a passing session, which is where most tokens go, left no turn-level
|
|
6
|
+
* record. When the caller opts in, this writes a redacted copy into an archive
|
|
7
|
+
* directory first. The archive file name contains `.transcript.` and ends in
|
|
8
|
+
* `.json`, so `pruneRetainedTranscripts` already counts it; it never sits in the
|
|
9
|
+
* live `sessions/` directory, so it can never be loaded as a resumption source
|
|
10
|
+
* (nax#1877).
|
|
11
|
+
*/
|
|
12
|
+
import type { TranscriptStore } from "./transcript-types.ts";
|
|
13
|
+
/** Injectable clock for the archive stamp, so tests order archives without sleeping. */
|
|
14
|
+
export declare const _transcriptArchiveDeps: {
|
|
15
|
+
now: () => Date;
|
|
16
|
+
};
|
|
17
|
+
/** Where, and how many, archived transcripts are kept (the `OpenSessionOpts.transcriptArchive` shape). */
|
|
18
|
+
export interface TranscriptArchive {
|
|
19
|
+
readonly dir: string;
|
|
20
|
+
readonly maxRetained: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Write a redacted copy of the session's transcript into `archive.dir`, then
|
|
24
|
+
* prune the directory to `archive.maxRetained`. Nothing to archive (no saved
|
|
25
|
+
* document) is a quiet return. Every error is logged and swallowed: the archive
|
|
26
|
+
* is supplementary and must never change how a session closes.
|
|
27
|
+
*/
|
|
28
|
+
export declare function archiveTranscript(store: TranscriptStore, sessionId: string, archive: TranscriptArchive): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit archive for native session transcripts (nax#2453).
|
|
3
|
+
*
|
|
4
|
+
* `closeNativeSession` deletes a clean session's transcript and renames a failed
|
|
5
|
+
* one, so a passing session, which is where most tokens go, left no turn-level
|
|
6
|
+
* record. When the caller opts in, this writes a redacted copy into an archive
|
|
7
|
+
* directory first. The archive file name contains `.transcript.` and ends in
|
|
8
|
+
* `.json`, so `pruneRetainedTranscripts` already counts it; it never sits in the
|
|
9
|
+
* live `sessions/` directory, so it can never be loaded as a resumption source
|
|
10
|
+
* (nax#1877).
|
|
11
|
+
*/
|
|
12
|
+
import { mkdir } from "node:fs/promises";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { getLogger } from "#src/infra/index";
|
|
15
|
+
import { writeFileAtomic } from "#src/internal/atomic-write";
|
|
16
|
+
import { redactSecrets } from "#src/internal/redact";
|
|
17
|
+
import { pruneRetainedTranscripts } from "./transcript-store.js";
|
|
18
|
+
/** Injectable clock for the archive stamp, so tests order archives without sleeping. */
|
|
19
|
+
export const _transcriptArchiveDeps = { now: () => new Date() };
|
|
20
|
+
/**
|
|
21
|
+
* Write a redacted copy of the session's transcript into `archive.dir`, then
|
|
22
|
+
* prune the directory to `archive.maxRetained`. Nothing to archive (no saved
|
|
23
|
+
* document) is a quiet return. Every error is logged and swallowed: the archive
|
|
24
|
+
* is supplementary and must never change how a session closes.
|
|
25
|
+
*/
|
|
26
|
+
export async function archiveTranscript(store, sessionId, archive) {
|
|
27
|
+
try {
|
|
28
|
+
const doc = await store.load(sessionId);
|
|
29
|
+
if (doc === null)
|
|
30
|
+
return;
|
|
31
|
+
const stamp = _transcriptArchiveDeps.now().toISOString().replace(/[:.]/g, "-");
|
|
32
|
+
await mkdir(archive.dir, { recursive: true });
|
|
33
|
+
await writeFileAtomic(join(archive.dir, `${sessionId}.transcript.${stamp}.json`), JSON.stringify(redactSecrets(doc), null, 2));
|
|
34
|
+
await pruneRetainedTranscripts(archive.dir, archive.maxRetained);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
getLogger().warn("native-session", "Transcript archive failed; close continues unchanged", {
|
|
38
|
+
sessionId,
|
|
39
|
+
dir: archive.dir,
|
|
40
|
+
error: err instanceof Error ? err.message : String(err),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -146,6 +146,17 @@ export interface OpenSessionOpts extends TrackedSpawnDeadlineOptions {
|
|
|
146
146
|
readonly dir: string;
|
|
147
147
|
readonly header: import("#src/tools/tool-audit").ToolAuditHeader;
|
|
148
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* ACP delivery (nax#2444, D3): provider tools already policy-filtered at the
|
|
151
|
+
* hop (deny/ask-ruled tools withheld), adapted by the ACP backend only when
|
|
152
|
+
* its registry entry declares a pre-approval channel. Native ignores it —
|
|
153
|
+
* provider tools ride `codingTools` on the turn.
|
|
154
|
+
*/
|
|
155
|
+
providerTools?: readonly import("#src/tools/registry").CodingTool[];
|
|
156
|
+
/** ACP: the op's materialized declaration, which narrows the session profile (nax#2444, D2). */
|
|
157
|
+
declaredTools?: readonly string[];
|
|
158
|
+
/** ACP: the transport-less surface draft; the adapter stamps transport + profile (nax#2410, D4). */
|
|
159
|
+
toolSurface?: import("#src/tools/tool-audit").ToolSurfaceDraft;
|
|
149
160
|
/**
|
|
150
161
|
* Native: identity that owns this session's transcript — the op invocation's
|
|
151
162
|
* `scopeId ?? callId`. Stable across the retries and hops of one invocation,
|
|
@@ -169,6 +180,14 @@ export interface OpenSessionOpts extends TrackedSpawnDeadlineOptions {
|
|
|
169
180
|
* that close failed.
|
|
170
181
|
*/
|
|
171
182
|
retainOnClose?: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Native: when set, closeNativeSession writes a redacted copy of the transcript
|
|
185
|
+
* here before deleting or retaining it. ACP ignores it.
|
|
186
|
+
*/
|
|
187
|
+
transcriptArchive?: {
|
|
188
|
+
readonly dir: string;
|
|
189
|
+
readonly maxRetained: number;
|
|
190
|
+
};
|
|
172
191
|
/**
|
|
173
192
|
* Native: the session's system prompt, sent as the request's top-level
|
|
174
193
|
* `system` on every round trip (not on the compaction summary). The S3
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type { CodingToolOutcome, CodingToolRuntime, ToolCallContext } from "./ru
|
|
|
28
28
|
export { _codingToolDeps, _resetBuiltinsForTest, createCodingToolRuntime, DEFAULT_TOOL_MAX_BYTES, DEFAULT_TOOL_MAX_FILE_BYTES, registerBuiltinCodingTools, } from "./runtime.ts";
|
|
29
29
|
export { SCRATCHPAD_DIR, scratchpadListTool, scratchpadReadTool, scratchpadWriteTool } from "./scratchpad.ts";
|
|
30
30
|
export { _spillDeps, applyModelTruncationPolicy, type ModelTruncationOptions, SPILL_DIR, type SpillRequest, spillRelativePath, writeSpill, } from "./spill.ts";
|
|
31
|
-
export type { RegisteredSink, ToolAuditHeader, ToolAuditSink, ToolCallRecord } from "./tool-audit.ts";
|
|
31
|
+
export type { RegisteredSink, ToolAuditHeader, ToolAuditSink, ToolCallRecord, ToolSurfaceDraft, ToolSurfaceRecord, } from "./tool-audit.ts";
|
|
32
32
|
export { createNoOpToolAuditSink, createToolAuditSink, flushOpenToolAuditSinks, registerToolAuditSink, unregisterToolAuditSink, } from "./tool-audit.ts";
|
|
33
33
|
export { cutBufferToByteCap, MODEL_MAX_BYTES, MODEL_MAX_LINE_CHARS, MODEL_MAX_LINES, READ_CEILING, type TruncateForModelOptions, type TruncationDirection, type TruncationResult, truncateForModel, truncationDirectionFor, } from "./truncate.ts";
|
|
34
34
|
export type { CodingToolName, PolicyVerdict, ToolGrant, ToolPolicy, ToolScope } from "./types.ts";
|
|
@@ -9,6 +9,24 @@ export interface ResolvedProviderTools {
|
|
|
9
9
|
providerId: string;
|
|
10
10
|
reason: string;
|
|
11
11
|
}[];
|
|
12
|
+
/**
|
|
13
|
+
* Providers whose configured `stages` exclude this hop's stage, with the
|
|
14
|
+
* stages they were configured for (#2410 gap 2: a misconfigured `stages`
|
|
15
|
+
* must be visible, not silent). The scope-none arm contributes the
|
|
16
|
+
* whole-layer `provider-scope:none` row (permission profile `safe`, or a
|
|
17
|
+
* `rules` scope that resolved no providers).
|
|
18
|
+
*/
|
|
19
|
+
readonly skipped: readonly {
|
|
20
|
+
providerId: string;
|
|
21
|
+
reason: "stage-mismatch" | "provider-scope:none";
|
|
22
|
+
stages: readonly string[];
|
|
23
|
+
}[];
|
|
24
|
+
/** Sanitised tools a scoped stage's `admits` rejected (#2410 gap 1). */
|
|
25
|
+
readonly withheld: readonly {
|
|
26
|
+
providerId: string;
|
|
27
|
+
localName: string;
|
|
28
|
+
reason: "not-admitted";
|
|
29
|
+
}[];
|
|
12
30
|
/**
|
|
13
31
|
* Advertised tool name -> owning provider id, carried explicitly so the
|
|
14
32
|
* ledger never has to parse the `<id>__<local>` name apart (a naming
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
* Per-hop resolution of provider tools.
|
|
3
3
|
*
|
|
4
4
|
* A provider whose `tools()` rejects is DROPPED, not fatal: a broken provider
|
|
5
|
-
* must never wedge a run. The failure is
|
|
6
|
-
*
|
|
5
|
+
* must never wedge a run. The failure reason is carried in `failures` and
|
|
6
|
+
* logged by the caller, which owns the logger. Stage mismatches and
|
|
7
|
+
* `admits`-rejected tools are carried in `skipped`/`withheld` (#2410) and the
|
|
8
|
+
* stage skip is logged once here at debug — the null-safe logger keeps the
|
|
9
|
+
* module testable without one.
|
|
7
10
|
*/
|
|
11
|
+
import { getSafeLogger } from "#src/infra/index";
|
|
8
12
|
import { adaptProviderTool } from "./provider-adapt.js";
|
|
9
13
|
import { expandProviderGrants } from "./provider-grants.js";
|
|
10
14
|
import { sanitizeProviderTools } from "./provider-sanitize.js";
|
|
@@ -13,16 +17,23 @@ export async function resolveProviderTools(providers, stage, workdir, options) {
|
|
|
13
17
|
const tools = [];
|
|
14
18
|
const entries = [];
|
|
15
19
|
const failures = [];
|
|
20
|
+
const skipped = [];
|
|
21
|
+
const withheld = [];
|
|
16
22
|
const providerIdByTool = new Map();
|
|
17
23
|
for (const provider of providers) {
|
|
18
|
-
if (!providerAttachesTo(provider, stage))
|
|
24
|
+
if (!providerAttachesTo(provider, stage)) {
|
|
25
|
+
skipped.push({ providerId: provider.id, reason: "stage-mismatch", stages: provider.stages });
|
|
26
|
+
getSafeLogger()?.debug("mcp", `[provider] ${provider.id} not attached`, { stage, stages: provider.stages });
|
|
19
27
|
continue;
|
|
28
|
+
}
|
|
20
29
|
try {
|
|
21
30
|
const sanitized = sanitizeProviderTools(provider.kind, await provider.tools(workdir));
|
|
22
31
|
const localNames = [];
|
|
23
32
|
for (const tool of sanitized) {
|
|
24
|
-
if (options?.admits !== undefined && !options.admits(provider.id, tool.localName))
|
|
33
|
+
if (options?.admits !== undefined && !options.admits(provider.id, tool.localName)) {
|
|
34
|
+
withheld.push({ providerId: provider.id, localName: tool.localName, reason: "not-admitted" });
|
|
25
35
|
continue;
|
|
36
|
+
}
|
|
26
37
|
const adapted = adaptProviderTool(provider.id, tool);
|
|
27
38
|
tools.push(adapted);
|
|
28
39
|
providerIdByTool.set(adapted.name, provider.id);
|
|
@@ -34,7 +45,7 @@ export async function resolveProviderTools(providers, stage, workdir, options) {
|
|
|
34
45
|
failures.push({ providerId: provider.id, reason: String(error) });
|
|
35
46
|
}
|
|
36
47
|
}
|
|
37
|
-
return { tools, grants: expandProviderGrants(entries), failures, providerIdByTool, entries };
|
|
48
|
+
return { tools, grants: expandProviderGrants(entries), failures, skipped, withheld, providerIdByTool, entries };
|
|
38
49
|
}
|
|
39
50
|
/**
|
|
40
51
|
* Bytes a tool list costs the prompt, paid on EVERY hop whether or not any
|
|
@@ -164,8 +164,50 @@ export interface ToolAuditHeader {
|
|
|
164
164
|
readonly storyId?: string;
|
|
165
165
|
readonly sessionRole?: string;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Which MCP provider tools a session was OFFERED — the delivery-side twin of
|
|
169
|
+
* the per-call `calls` rows (#2410 gap 1). Present in the file body as
|
|
170
|
+
* `toolSurface` whenever a session's hop resolved provider tools, so
|
|
171
|
+
* "the model ignored the tool" can be told apart from "the tool was never
|
|
172
|
+
* advertised".
|
|
173
|
+
*/
|
|
174
|
+
export interface ToolSurfaceDraft {
|
|
175
|
+
/** The pipeline stage the session was resolved for. */
|
|
176
|
+
readonly stage: string;
|
|
177
|
+
/** The op's materialized declaration (what the profile/delivery decision saw). */
|
|
178
|
+
readonly declaredTools: readonly string[];
|
|
179
|
+
/** Provider tools actually offered: (providerId, local names) with content. */
|
|
180
|
+
readonly advertised: readonly {
|
|
181
|
+
providerId: string;
|
|
182
|
+
names: readonly string[];
|
|
183
|
+
}[];
|
|
184
|
+
/** Provider tools resolved but not offered, with why. */
|
|
185
|
+
readonly withheld: readonly {
|
|
186
|
+
providerId: string;
|
|
187
|
+
name?: string;
|
|
188
|
+
reason: string;
|
|
189
|
+
}[];
|
|
190
|
+
/** Providers skipped whole, with why. */
|
|
191
|
+
readonly skippedProviders: readonly {
|
|
192
|
+
id: string;
|
|
193
|
+
reason: string;
|
|
194
|
+
stages?: readonly string[];
|
|
195
|
+
}[];
|
|
196
|
+
/** Providers whose `tools()` rejected (same rows as `failures`). */
|
|
197
|
+
readonly failures: readonly {
|
|
198
|
+
providerId: string;
|
|
199
|
+
reason: string;
|
|
200
|
+
}[];
|
|
201
|
+
}
|
|
202
|
+
/** A draft stamped with the transport (and, on ACP, the narrowed profile). */
|
|
203
|
+
export interface ToolSurfaceRecord extends ToolSurfaceDraft {
|
|
204
|
+
readonly transport: "native" | "acp";
|
|
205
|
+
readonly profile?: "full" | "read";
|
|
206
|
+
}
|
|
167
207
|
export declare function createToolAuditSink(opts: {
|
|
168
208
|
dir: string;
|
|
169
209
|
sessionName: string;
|
|
170
210
|
header?: ToolAuditHeader;
|
|
211
|
+
/** The session's MCP tool surface (#2410): present ⇒ the file is written even with zero calls. */
|
|
212
|
+
toolSurface?: ToolSurfaceRecord;
|
|
171
213
|
}): RegisteredSink;
|
package/dist/tools/tool-audit.js
CHANGED
|
@@ -129,7 +129,10 @@ export function createToolAuditSink(opts) {
|
|
|
129
129
|
// may still run afterwards must not write a second, non-partial file.
|
|
130
130
|
let partialFlushed = false;
|
|
131
131
|
const write = async (partial) => {
|
|
132
|
-
|
|
132
|
+
// Zero calls normally means "nothing to audit" — but a surface with no
|
|
133
|
+
// calls is exactly the never-called session #2410 exists to expose, so it
|
|
134
|
+
// must still produce its file.
|
|
135
|
+
if (calls.length === 0 && opts.toolSurface === undefined)
|
|
133
136
|
return;
|
|
134
137
|
await mkdir(opts.dir, { recursive: true });
|
|
135
138
|
const body = JSON.stringify({
|
|
@@ -139,6 +142,7 @@ export function createToolAuditSink(opts) {
|
|
|
139
142
|
// Additive and optional: absent on a normal flush, `true` only when the
|
|
140
143
|
// shutdown path persisted a buffer its hop never closed.
|
|
141
144
|
...(partial ? { partial: true } : {}),
|
|
145
|
+
...(opts.toolSurface !== undefined ? { toolSurface: opts.toolSurface } : {}),
|
|
142
146
|
calls: redactRowStrings(calls),
|
|
143
147
|
}, null, 2);
|
|
144
148
|
const prefix = runId !== undefined ? `${runId}-` : "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nax-agent",
|
|
3
|
-
"version": "0.87.
|
|
3
|
+
"version": "0.87.1",
|
|
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.87.
|
|
50
|
+
"@nathapp/nax-ai": "0.87.1",
|
|
51
51
|
"zod": "^4.3.6"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|