@nathapp/nax-agent 0.2.0 → 0.3.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +22 -13
  3. package/dist/index.d.ts +15 -5
  4. package/dist/index.js +10 -2
  5. package/dist/infra/stderr-tail.d.ts +8 -0
  6. package/dist/infra/stderr-tail.js +48 -0
  7. package/dist/native/index.d.ts +1 -1
  8. package/dist/native/session/transcript-types.d.ts +16 -0
  9. package/dist/native/session/turn-types.d.ts +4 -5
  10. package/dist/native/session/turn-types.js +8 -8
  11. package/dist/permissions/ask-chain.d.ts +1 -1
  12. package/dist/permissions/ask.d.ts +6 -0
  13. package/dist/permissions/ask.js +6 -0
  14. package/dist/permissions/index.d.ts +1 -1
  15. package/dist/permissions/index.js +1 -1
  16. package/dist/session/agent-session-errors.d.ts +1 -1
  17. package/dist/session/agent-session-options.d.ts +0 -4
  18. package/dist/session/agent-session-options.js +7 -48
  19. package/dist/session/agent-session-resume.d.ts +3 -1
  20. package/dist/session/agent-session-resume.js +10 -8
  21. package/dist/session/agent-session-turn.d.ts +3 -7
  22. package/dist/session/agent-session-turn.js +14 -9
  23. package/dist/session/agent-session-types.d.ts +22 -22
  24. package/dist/session/agent-session.js +51 -90
  25. package/dist/session/native-backend-options.d.ts +24 -0
  26. package/dist/session/native-backend-options.js +81 -0
  27. package/dist/session/native-backend.d.ts +4 -0
  28. package/dist/session/native-backend.js +106 -0
  29. package/dist/session/session-ask-link.d.ts +12 -5
  30. package/dist/session/session-ask-link.js +3 -2
  31. package/dist/session/session-ask-port.d.ts +12 -0
  32. package/dist/session/session-ask-port.js +64 -0
  33. package/dist/session/session-backend.d.ts +79 -0
  34. package/dist/session/session-backend.js +1 -0
  35. package/dist/session/session-interaction.d.ts +2 -2
  36. package/dist/session/session-interaction.js +3 -6
  37. package/dist/session/session-tool-support.d.ts +5 -5
  38. package/dist/session/session-tool-support.js +12 -9
  39. package/dist/session/session-types.d.ts +12 -0
  40. package/dist/session/turn-event.d.ts +12 -0
  41. package/dist/session/turn-spend.d.ts +18 -0
  42. package/dist/session/turn-spend.js +9 -0
  43. package/dist/tools/ask-request.js +3 -1
  44. package/dist/tools/index.d.ts +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ import type { SessionEventBody } from "./agent-session-types.ts";
2
+ import type { PendingAskTable } from "./pending-asks.ts";
3
+ import type { SessionAskPort } from "./session-backend.ts";
4
+ export interface SessionAskPortDeps {
5
+ readonly table: PendingAskTable;
6
+ readonly emit: (body: SessionEventBody) => void;
7
+ readonly turn: () => {
8
+ readonly turnId: string;
9
+ readonly signal: AbortSignal;
10
+ } | undefined;
11
+ }
12
+ export declare function createSessionAskPort(deps: SessionAskPortDeps): SessionAskPort;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The session's ask port (S4 spec 5.1): approvals and questions any backend
3
+ * raises, on top of the pending-ask table. A request needs a running turn: its
4
+ * events go on that turn's stream and its turn signal settles it. Profile
5
+ * auto-decisions and informational questions are emitted, never queued.
6
+ */
7
+ import { NaxError } from "#src/infra/nax-error";
8
+ import { _agentSessionDeps } from "./agent-session-deps.js";
9
+ import { askPerson } from "./session-ask-link.js";
10
+ const ALREADY_ABORTED = AbortSignal.abort();
11
+ function noTurn() {
12
+ return new NaxError("No turn is running; an approval needs a running turn", "AGENT_SESSION_TURN_FAILED", {
13
+ stage: "agent-session",
14
+ detail: "no-turn",
15
+ });
16
+ }
17
+ export function createSessionAskPort(deps) {
18
+ const askDeps = { table: deps.table, emit: deps.emit };
19
+ const now = () => new Date(_agentSessionDeps.now()).toISOString();
20
+ return {
21
+ async requestApproval(req) {
22
+ const turn = deps.turn();
23
+ if (turn === undefined)
24
+ throw noTurn();
25
+ const signal = req.signal === undefined ? turn.signal : AbortSignal.any([turn.signal, req.signal]);
26
+ const { signal: _ignored, ...ask } = req;
27
+ return askPerson(askDeps, ask, signal);
28
+ },
29
+ // Informational only. The requestId is emitted but never put in the pending
30
+ // table, so session.answer() on it is not answerable, and expiresAt is the
31
+ // emit time (the auto-decision is already resolved), not a real deadline.
32
+ recordAutoDecision(req, decision) {
33
+ if (deps.turn() === undefined)
34
+ return;
35
+ const requestId = _agentSessionDeps.randomUUID();
36
+ deps.emit({
37
+ type: "approval_requested",
38
+ requestId,
39
+ ...(req.callId !== undefined ? { callId: req.callId } : {}),
40
+ tool: req.tool,
41
+ summary: req.summary,
42
+ reason: req.reason,
43
+ expiresAt: now(),
44
+ });
45
+ deps.emit({ type: "approval_resolved", requestId, decision, decidedBy: "profile" });
46
+ },
47
+ async askQuestion(text, opts) {
48
+ const turn = deps.turn();
49
+ if (turn === undefined)
50
+ return null;
51
+ const signal = opts?.signal === undefined ? turn.signal : AbortSignal.any([turn.signal, opts.signal]);
52
+ const { requestId, expiresAt, settled } = deps.table.issue("question", signal);
53
+ deps.emit({ type: "question", requestId, text, expiresAt });
54
+ const settlement = await settled;
55
+ return settlement.by === "human" && "text" in settlement.reply ? settlement.reply.text : null;
56
+ },
57
+ noteQuestion(text) {
58
+ if (deps.turn() === undefined)
59
+ return;
60
+ const { requestId } = deps.table.issue("question", ALREADY_ABORTED);
61
+ deps.emit({ type: "question", requestId, text, expiresAt: now() });
62
+ },
63
+ };
64
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The backend seam (S4 spec §5.1). A `SessionBackend` opens an
3
+ * `OpenedBackend`: the S1 `AgentSessionAdapter` plus the per-turn options it
4
+ * contributes, the ask port it reaches the facade through, and the info the
5
+ * facade records. The native backend and the out-of-tree ACP backend both
6
+ * satisfy this contract; nothing here is backend-specific.
7
+ */
8
+ import type { TranscriptDoc, TranscriptStore } from "#src/native/session/transcript-types";
9
+ import type { AgentSessionProfile, ApprovalDecidedBy, EmbedderTool } from "./agent-session-types.ts";
10
+ import type { AgentSessionAdapter, SendTurnOpts, SessionHandle } from "./session-types.ts";
11
+ export interface BackendInfo {
12
+ /** "native" or "acp:<agent>"; recorded in the transcript document. */
13
+ readonly kind: string;
14
+ /** Read-only, JSON-safe capability summary. `{}` for native. */
15
+ readonly capabilities: Readonly<Record<string, unknown>>;
16
+ }
17
+ export interface ApprovalRequest {
18
+ readonly callId?: string;
19
+ readonly tool: string;
20
+ readonly summary: string;
21
+ readonly command?: string;
22
+ readonly reason: string;
23
+ /** Extra abort source, combined with the turn signal (for example a tool-level abort). */
24
+ readonly signal?: AbortSignal;
25
+ }
26
+ export interface SessionAskPort {
27
+ /** Throws NaxError AGENT_SESSION_TURN_FAILED (context.detail "no-turn") when no turn is running. */
28
+ requestApproval(req: ApprovalRequest): Promise<{
29
+ readonly decision: "allow" | "deny";
30
+ readonly decidedBy: ApprovalDecidedBy;
31
+ }>;
32
+ /** Emits approval_requested then approval_resolved (decidedBy "profile"). No-op when no turn is running. */
33
+ recordAutoDecision(req: Omit<ApprovalRequest, "command" | "signal">, decision: "allow" | "deny"): void;
34
+ /**
35
+ * The person's text, or null on deadline, cancel or no running turn. `opts.signal`
36
+ * is an extra abort source combined with the turn signal, as for approvals (for
37
+ * example a backend's per-request scope that ends before the turn does).
38
+ */
39
+ askQuestion(text: string, opts?: {
40
+ readonly signal?: AbortSignal;
41
+ }): Promise<string | null>;
42
+ /** An informational question event; answer() on its id returns "cancelled". No-op when no turn is running. */
43
+ noteQuestion(text: string): void;
44
+ }
45
+ /** What a backend contributes to every sendTurn. */
46
+ export type TurnContribution = Pick<SendTurnOpts, "interactionHandler"> & Partial<Pick<SendTurnOpts, "codingTools" | "loopHandlers" | "loopHandlerContext">>;
47
+ export interface BackendOpenContext {
48
+ readonly sessionId: string;
49
+ /** The facade's resolved root: the workdir, or a private scratch root for profile "none". */
50
+ readonly workdir: string;
51
+ readonly profile: AgentSessionProfile;
52
+ readonly instructions: string | undefined;
53
+ readonly tools: readonly EmbedderTool[];
54
+ readonly transcriptStore: TranscriptStore;
55
+ /** The stored document on resume (already presence-, schema- and kind-checked by the facade). */
56
+ readonly resume: {
57
+ readonly doc: TranscriptDoc;
58
+ } | undefined;
59
+ readonly asks: SessionAskPort;
60
+ /** Re-read at use time: the running turn's signal, or a never-aborting one between turns. */
61
+ readonly turnSignal: () => AbortSignal;
62
+ readonly currentTurnId: () => string | undefined;
63
+ readonly turnTimeoutSeconds: number;
64
+ readonly metadata: Readonly<Record<string, string>>;
65
+ /** Aborted when the session starts closing. */
66
+ readonly openSignal: AbortSignal;
67
+ }
68
+ export interface OpenedBackend {
69
+ readonly adapter: AgentSessionAdapter;
70
+ readonly handle: SessionHandle;
71
+ readonly info: BackendInfo;
72
+ turnOpts(): TurnContribution;
73
+ /** Releases backend resources after adapter.closeSession. Idempotent. */
74
+ close(): Promise<void>;
75
+ }
76
+ export interface SessionBackend {
77
+ readonly kind: string;
78
+ open(ctx: BackendOpenContext): Promise<OpenedBackend>;
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -2,14 +2,14 @@ import type { CodingTool } from "#src/tools/registry";
2
2
  import type { CodingToolRuntime } from "#src/tools/runtime";
3
3
  import type { EmbedderTool } from "./agent-session-types.ts";
4
4
  import type { InteractionHandler } from "./interaction-handler.ts";
5
- import { type SessionAskDeps } from "./session-ask-link.ts";
5
+ import type { SessionAskPort } from "./session-backend.ts";
6
6
  /** Byte cap on an embedder approval's default summary. */
7
7
  export declare const EMBEDDER_SUMMARY_BYTES = 1024;
8
8
  export interface SessionInteractionDeps {
9
9
  readonly sessionId: string;
10
10
  readonly runtime: CodingToolRuntime;
11
11
  readonly embedderTools: ReadonlyMap<string, EmbedderTool>;
12
- readonly asks: SessionAskDeps;
12
+ readonly asks: SessionAskPort;
13
13
  /** The running turn's signal (a never-aborting one between turns). */
14
14
  readonly turnSignal: () => AbortSignal;
15
15
  readonly setCurrentCallId: (callId: string | undefined) => void;
@@ -13,7 +13,6 @@ import { NaxError } from "#src/infra/nax-error";
13
13
  import { capStrings, redactSecrets } from "#src/internal/redact";
14
14
  import { askDenyReason } from "#src/tools/ask-request";
15
15
  import { cutToByteCap } from "#src/tools/truncate";
16
- import { askPerson } from "./session-ask-link.js";
17
16
  /** Byte cap on an embedder approval's default summary. */
18
17
  export const EMBEDDER_SUMMARY_BYTES = 1024;
19
18
  /**
@@ -108,7 +107,7 @@ async function runEmbedderTool(deps, tool, request) {
108
107
  reason: `"${tool.name}" asks before every run`,
109
108
  callId: toolCallId,
110
109
  };
111
- const outcome = await askPerson(deps.asks, ask, signal);
110
+ const outcome = await deps.asks.requestApproval({ ...ask, signal });
112
111
  if (outcome.decision !== "allow") {
113
112
  const reason = askDenyReason(outcome.decidedBy);
114
113
  return { answer: `Denied: ${reason}`, denied: { reason, breach: false } };
@@ -145,10 +144,8 @@ async function runCodingTool(deps, request) {
145
144
  }
146
145
  }
147
146
  async function answerQuestion(deps, text) {
148
- const { requestId, expiresAt, settled } = deps.asks.table.issue("question", deps.turnSignal());
149
- deps.asks.emit({ type: "question", requestId, text, expiresAt });
150
- const settlement = await settled;
151
- return settlement.by === "human" && "text" in settlement.reply ? { answer: settlement.reply.text } : null;
147
+ const answer = await deps.asks.askQuestion(text);
148
+ return answer === null ? null : { answer };
152
149
  }
153
150
  export function createSessionInteractionHandler(deps) {
154
151
  return {
@@ -11,11 +11,11 @@ export declare const SESSION_STAGE = "session";
11
11
  export declare function declaredToolsFor(profile: AgentSessionProfile, protectedPaths: ProtectedPathsPolicy): readonly CodingToolName[];
12
12
  export declare function grantsFor(declared: readonly CodingToolName[]): readonly ToolGrant[];
13
13
  /**
14
- * Under "gated", every Bash command is put to the person: an unconditional ask
15
- * rule on top of the unconditional grant (ask rules are evaluated after
16
- * grants). "raw" and "escalate" add none.
14
+ * Ask rules evaluate after grants. Under "gated" every Bash command is put to
15
+ * the person. Under the "ask" profile every mutating path tool is too, and the
16
+ * profile forces "gated", so Bash always asks there.
17
17
  */
18
- export declare function askRulesFor(declared: readonly CodingToolName[], bashApproval: BashApprovalMode): readonly ToolGrant[];
18
+ export declare function askRulesFor(declared: readonly CodingToolName[], bashApproval: BashApprovalMode, profile: AgentSessionProfile): readonly ToolGrant[];
19
19
  /**
20
20
  * The embedder default (spec 6.2). A session with its own credentials source
21
21
  * (memory or exec) has no credentials directory on disk; one that falls back
@@ -29,7 +29,7 @@ export interface SessionLauncherArgs {
29
29
  readonly bashApproval: BashApprovalMode;
30
30
  readonly allowUnsandboxed: boolean;
31
31
  }
32
- /** The sandbox floor (spec 6.3): "full" needs a usable sandbox unless gated + allowUnsandboxed. */
32
+ /** The sandbox floor (spec 6.3): "ask" and "full" need a usable sandbox unless gated + allowUnsandboxed. */
33
33
  export declare function resolveSessionLauncher(args: SessionLauncherArgs): Promise<CommandLauncher | undefined>;
34
34
  export interface SessionToolSupportArgs {
35
35
  readonly profile: AgentSessionProfile;
@@ -15,6 +15,7 @@ import { AgentSessionError } from "./agent-session-errors.js";
15
15
  export const SESSION_STAGE = "session";
16
16
  const READ_TOOLS = ["Read", "Glob", "Grep", "Git"];
17
17
  const WRITE_TOOLS = ["Write", "Edit", "Delete", "Bash"];
18
+ const ASK_PROFILE_TOOLS = ["Write", "Edit", "Delete", "GitCommit"];
18
19
  export function declaredToolsFor(profile, protectedPaths) {
19
20
  if (profile === "none")
20
21
  return [...UNIVERSAL_CODING_TOOLS];
@@ -28,12 +29,14 @@ export function grantsFor(declared) {
28
29
  return declared.map((tool) => ({ tool, patterns: ["*"] }));
29
30
  }
30
31
  /**
31
- * Under "gated", every Bash command is put to the person: an unconditional ask
32
- * rule on top of the unconditional grant (ask rules are evaluated after
33
- * grants). "raw" and "escalate" add none.
32
+ * Ask rules evaluate after grants. Under "gated" every Bash command is put to
33
+ * the person. Under the "ask" profile every mutating path tool is too, and the
34
+ * profile forces "gated", so Bash always asks there.
34
35
  */
35
- export function askRulesFor(declared, bashApproval) {
36
- return bashApproval === "gated" && declared.includes("Bash") ? [{ tool: "Bash", patterns: ["*"] }] : [];
36
+ export function askRulesFor(declared, bashApproval, profile) {
37
+ const bash = bashApproval === "gated" && declared.includes("Bash") ? ["Bash"] : [];
38
+ const mutating = profile === "ask" ? ASK_PROFILE_TOOLS.filter((tool) => declared.includes(tool)) : [];
39
+ return [...mutating, ...bash].map((tool) => ({ tool, patterns: ["*"] }));
37
40
  }
38
41
  /** The configured credentials directory; undefined when configureCredentials was never called. */
39
42
  function configuredCredentialDir() {
@@ -54,9 +57,9 @@ export function defaultProtectedPaths(ownCredentials) {
54
57
  const credentialDir = ownCredentials ? undefined : configuredCredentialDir();
55
58
  return credentialDir === undefined ? base : { ...base, credentialDir };
56
59
  }
57
- /** The sandbox floor (spec 6.3): "full" needs a usable sandbox unless gated + allowUnsandboxed. */
60
+ /** The sandbox floor (spec 6.3): "ask" and "full" need a usable sandbox unless gated + allowUnsandboxed. */
58
61
  export async function resolveSessionLauncher(args) {
59
- if (args.profile !== "full")
62
+ if (args.profile !== "full" && args.profile !== "ask")
60
63
  return undefined;
61
64
  const launcher = await resolveSessionSandbox({
62
65
  config: DEFAULT_SANDBOX_CONFIG,
@@ -70,12 +73,12 @@ export async function resolveSessionLauncher(args) {
70
73
  if (args.bashApproval === "gated" && args.allowUnsandboxed)
71
74
  return undefined;
72
75
  const reason = launcher.state.kind === "unavailable" ? launcher.state.reason : "the sandbox is disabled";
73
- throw new AgentSessionError(`Profile "full" needs a usable sandbox (${reason}). Pass bashApproval "gated" with allowUnsandboxed: true to run without one.`, "AGENT_SESSION_SANDBOX_UNAVAILABLE", { reason });
76
+ throw new AgentSessionError(`Profile "${args.profile}" needs a usable sandbox (${reason}). Pass bashApproval "gated" with allowUnsandboxed: true to run without one.`, "AGENT_SESSION_SANDBOX_UNAVAILABLE", { reason });
74
77
  }
75
78
  export function buildSessionToolSupport(args) {
76
79
  const declared = declaredToolsFor(args.profile, args.protectedPaths);
77
80
  const grants = grantsFor(declared);
78
- const askRules = askRulesFor(declared, args.bashApproval);
81
+ const askRules = askRulesFor(declared, args.bashApproval, args.profile);
79
82
  const support = buildCodingToolSupport({
80
83
  root: args.root,
81
84
  commandCwd: args.root,
@@ -136,6 +136,16 @@ export interface OpenSessionOpts extends TrackedSpawnDeadlineOptions {
136
136
  * scratch dir reaches the adapter otherwise. ACP ignores it.
137
137
  */
138
138
  transcriptDir?: string;
139
+ /**
140
+ * S4b spec 7.4: where an S1 adapter that writes its own tool-audit ledger (nax's
141
+ * ACP adapter) writes it, and the run-scoped header stamped on it. nax fills it
142
+ * from the same resolution the native coding tools use. nax-agent itself does not
143
+ * read it; native takes its sink from coding-tool support.
144
+ */
145
+ toolAudit?: {
146
+ readonly dir: string;
147
+ readonly header: import("#src/tools/tool-audit").ToolAuditHeader;
148
+ };
139
149
  /**
140
150
  * Native: identity that owns this session's transcript — the op invocation's
141
151
  * `scopeId ?? callId`. Stable across the retries and hops of one invocation,
@@ -260,6 +270,8 @@ export interface TurnResult {
260
270
  estimatedCostUsd: number;
261
271
  /** Exact cost reported by wire protocol (when available). */
262
272
  exactCostUsd?: number;
273
+ /** Absent means computed. */
274
+ costSource?: import("./agent-session-types.ts").CostSource;
263
275
  /** Number of session.prompt() calls made. */
264
276
  internalRoundTrips: number;
265
277
  /**
@@ -43,6 +43,13 @@ export type TurnEvent = {
43
43
  readonly callId: string;
44
44
  readonly isError: boolean;
45
45
  readonly preview: string;
46
+ /** UTF-8 byte length of the full result before the preview cap (S4b spec 7.4). Absent when unknown. */
47
+ readonly resultBytes?: number;
48
+ }
49
+ /** A running tool's liveness beat (an ACP heartbeat); carries no content. Not a session event. */
50
+ | {
51
+ readonly type: "tool_progress";
52
+ readonly callId: string;
46
53
  } | {
47
54
  readonly type: "usage";
48
55
  readonly round: number;
@@ -53,6 +60,11 @@ export type TurnEvent = {
53
60
  /** Absent when the call reported no cache data -- never coerced to 0. */
54
61
  readonly cacheWrite?: number;
55
62
  readonly costUsd: number;
63
+ /**
64
+ * Absent means computed from the catalog (the native backend). `unpriced`
65
+ * rows carry `costUsd: 0` and must not be summed as a real cost.
66
+ */
67
+ readonly costSource?: import("./agent-session-types.ts").CostSource;
56
68
  } | {
57
69
  readonly type: "compaction";
58
70
  readonly reason: "proactive" | "overflow";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The spend a failed turn had already incurred, attached to the error it throws
3
+ * (#2367). Keyed on the error's identity, so the error itself is rethrown
4
+ * unchanged and keeps its own code. The facade's turn_end reads it on every
5
+ * error path; the native loop writes it through recordNativeTurnFailureUsage
6
+ * (nax#1840), an ACP backend through attachTurnSpend.
7
+ */
8
+ import type { TokenUsage } from "#src/cost/standard-types";
9
+ import type { CostSource } from "./agent-session-types.ts";
10
+ export interface FailedTurnSpend {
11
+ readonly tokenUsage: TokenUsage;
12
+ readonly costUsd: number;
13
+ readonly costSource?: CostSource;
14
+ }
15
+ /** Attaches the spend a failed turn had already incurred to the error it throws. */
16
+ export declare function attachTurnSpend(err: object, spend: FailedTurnSpend): void;
17
+ /** The spend attached to `err`, if any. */
18
+ export declare function readTurnSpend(err: unknown): FailedTurnSpend | undefined;
@@ -0,0 +1,9 @@
1
+ const spendByError = new WeakMap();
2
+ /** Attaches the spend a failed turn had already incurred to the error it throws. */
3
+ export function attachTurnSpend(err, spend) {
4
+ spendByError.set(err, Object.freeze({ ...spend }));
5
+ }
6
+ /** The spend attached to `err`, if any. */
7
+ export function readTurnSpend(err) {
8
+ return typeof err === "object" && err !== null ? spendByError.get(err) : undefined;
9
+ }
@@ -6,7 +6,7 @@
6
6
  * `maskForPrompt` before they leave the process; a secret that cannot be
7
7
  * shown safely withholds the arguments entirely (`unshowable`, review #9).
8
8
  */
9
- import { ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_TIMEOUT_REASON, ASK_UNSHOWABLE_REASON, maskForPrompt, } from "#src/permissions/index";
9
+ import { ASK_CANCELLED_REASON, ASK_DENIED_REASON, ASK_NO_CHANNEL_REASON, ASK_PROFILE_REASON, ASK_TIMEOUT_REASON, ASK_UNSHOWABLE_REASON, maskForPrompt, } from "#src/permissions/index";
10
10
  /** Ceiling for the one-line call description an AskResolver receives. */
11
11
  export const MAX_ASK_SUMMARY_CHARS = 200;
12
12
  /**
@@ -53,5 +53,7 @@ export function askDenyReason(decidedBy) {
53
53
  return ASK_CANCELLED_REASON;
54
54
  if (decidedBy === "unshowable")
55
55
  return ASK_UNSHOWABLE_REASON;
56
+ if (decidedBy === "profile")
57
+ return ASK_PROFILE_REASON;
56
58
  return ASK_NO_CHANNEL_REASON;
57
59
  }
@@ -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, ToolAuditSink, ToolCallRecord } from "./tool-audit.ts";
31
+ export type { RegisteredSink, ToolAuditHeader, ToolAuditSink, ToolCallRecord } 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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax-agent",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",