@nathapp/nax-agent 0.1.0 → 0.2.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 (113) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +47 -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 +10 -1
  8. package/dist/index.js +4 -1
  9. package/dist/internal/redact.d.ts +23 -0
  10. package/dist/internal/redact.js +60 -0
  11. package/dist/internal.d.ts +7 -0
  12. package/dist/internal.js +8 -0
  13. package/dist/native/adapter-deps.d.ts +3 -3
  14. package/dist/native/adapter-deps.js +3 -3
  15. package/dist/native/client.d.ts +7 -1
  16. package/dist/native/client.js +1 -1
  17. package/dist/native/credentials/change-guard.d.ts +8 -1
  18. package/dist/native/credentials/change-guard.js +1 -1
  19. package/dist/native/credentials/fingerprint.d.ts +4 -1
  20. package/dist/native/credentials/fingerprint.js +6 -3
  21. package/dist/native/credentials/session-source.d.ts +12 -0
  22. package/dist/native/credentials/session-source.js +33 -0
  23. package/dist/native/index.d.ts +3 -1
  24. package/dist/native/index.js +2 -1
  25. package/dist/native/session/loop-events/types.d.ts +1 -1
  26. package/dist/native/session/loop-handlers.d.ts +3 -0
  27. package/dist/native/session/loop-handlers.js +1 -1
  28. package/dist/native/session/memory-transcript-store.d.ts +12 -0
  29. package/dist/native/session/memory-transcript-store.js +36 -0
  30. package/dist/native/session/session.d.ts +138 -96
  31. package/dist/native/session/session.js +107 -127
  32. package/dist/native/session/transcript-identity.d.ts +39 -0
  33. package/dist/native/session/transcript-identity.js +81 -0
  34. package/dist/native/session/transcript-store.d.ts +22 -21
  35. package/dist/native/session/transcript-store.js +59 -68
  36. package/dist/native/session/transcript-types.d.ts +38 -0
  37. package/dist/native/session/transcript-types.js +7 -0
  38. package/dist/native/session/truncation-handler.d.ts +4 -3
  39. package/dist/native/session/truncation-handler.js +7 -10
  40. package/dist/native/session/turn-complete-step.d.ts +4 -1
  41. package/dist/native/session/turn-complete-step.js +11 -4
  42. package/dist/native/session/turn-event-emitter.d.ts +48 -0
  43. package/dist/native/session/turn-event-emitter.js +134 -0
  44. package/dist/native/session/turn-loop-round-trip.d.ts +5 -2
  45. package/dist/native/session/turn-loop-round-trip.js +8 -4
  46. package/dist/native/session/turn-loop.js +27 -13
  47. package/dist/native/session/turn-result.d.ts +1 -1
  48. package/dist/native/session/turn-result.js +1 -1
  49. package/dist/native/session/turn-tool-batch.d.ts +3 -0
  50. package/dist/native/session/turn-tool-batch.js +14 -6
  51. package/dist/native/session/turn-types.d.ts +17 -1
  52. package/dist/native/session-adapter.d.ts +22 -4
  53. package/dist/native/session-adapter.js +79 -35
  54. package/dist/native/stream-complete.d.ts +17 -0
  55. package/dist/native/stream-complete.js +40 -0
  56. package/dist/permissions/approvals-link.js +5 -4
  57. package/dist/sandbox/policy-builder.d.ts +6 -1
  58. package/dist/sandbox/policy-builder.js +18 -14
  59. package/dist/session/agent-session-deps.d.ts +12 -0
  60. package/dist/session/agent-session-deps.js +28 -0
  61. package/dist/session/agent-session-errors.d.ts +11 -0
  62. package/dist/session/agent-session-errors.js +12 -0
  63. package/dist/session/agent-session-options.d.ts +16 -0
  64. package/dist/session/agent-session-options.js +137 -0
  65. package/dist/session/agent-session-resume.d.ts +12 -0
  66. package/dist/session/agent-session-resume.js +47 -0
  67. package/dist/session/agent-session-turn.d.ts +45 -0
  68. package/dist/session/agent-session-turn.js +179 -0
  69. package/dist/session/agent-session-types.d.ts +182 -0
  70. package/dist/session/agent-session-types.js +1 -0
  71. package/dist/session/agent-session.d.ts +8 -0
  72. package/dist/session/agent-session.js +223 -0
  73. package/dist/session/pending-asks.d.ts +24 -0
  74. package/dist/session/pending-asks.js +77 -0
  75. package/dist/session/session-ask-link.d.ts +26 -0
  76. package/dist/session/session-ask-link.js +52 -0
  77. package/dist/session/session-event-channel.d.ts +17 -0
  78. package/dist/session/session-event-channel.js +132 -0
  79. package/dist/session/session-interaction.d.ts +20 -0
  80. package/dist/session/session-interaction.js +164 -0
  81. package/dist/session/session-tool-support.d.ts +48 -0
  82. package/dist/session/session-tool-support.js +101 -0
  83. package/dist/session/session-types.d.ts +31 -2
  84. package/dist/session/turn-event.d.ts +61 -0
  85. package/dist/session/turn-event.js +1 -0
  86. package/dist/tools/bash-cwd.d.ts +3 -3
  87. package/dist/tools/credential-read-deny.d.ts +22 -0
  88. package/dist/tools/credential-read-deny.js +56 -0
  89. package/dist/tools/glob.js +9 -2
  90. package/dist/tools/grep.js +55 -26
  91. package/dist/tools/owned-paths.d.ts +43 -0
  92. package/dist/tools/owned-paths.js +17 -0
  93. package/dist/tools/package-managers.d.ts +3 -0
  94. package/dist/tools/package-managers.js +7 -3
  95. package/dist/tools/policy-bash-raw.d.ts +3 -0
  96. package/dist/tools/policy-bash-raw.js +49 -62
  97. package/dist/tools/policy-command-branch.d.ts +3 -0
  98. package/dist/tools/policy-command-branch.js +2 -1
  99. package/dist/tools/policy-paths-branch.d.ts +14 -8
  100. package/dist/tools/policy-paths-branch.js +20 -19
  101. package/dist/tools/policy.d.ts +10 -7
  102. package/dist/tools/policy.js +9 -7
  103. package/dist/tools/protected-paths.d.ts +6 -6
  104. package/dist/tools/read.js +38 -20
  105. package/dist/tools/registry.d.ts +3 -0
  106. package/dist/tools/run-command-exec.js +1 -0
  107. package/dist/tools/runtime.d.ts +3 -0
  108. package/dist/tools/runtime.js +1 -0
  109. package/dist/tools/scratchpad.js +2 -1
  110. package/dist/tools/types.d.ts +2 -2
  111. package/package.json +2 -2
  112. package/dist/tools/nax-owned-writes.d.ts +0 -135
  113. package/dist/tools/nax-owned-writes.js +0 -259
@@ -16,7 +16,7 @@
16
16
  import { relative, resolve, sep } from "node:path";
17
17
  import { realOrRaw } from "#src/internal/realpath";
18
18
  import { validateArgv } from "./exec-guard.js";
19
- import { naxOwnedWriteRefusal, naxWriteOptIns } from "./nax-owned-writes.js";
19
+ import { EMPTY_OWNED_PATHS_POLICY } from "./owned-paths.js";
20
20
  import { commandBranch } from "./policy-command-branch.js";
21
21
  import { compileArgvPattern, compileRuleMap, globToRegExp, matchedArgvSource, matchedGlobSource, matchedRulePatterns, matchesAny, matchesArgvGrant, } from "./policy-match.js";
22
22
  import { pathsBranch, resolveWithin } from "./policy-paths-branch.js";
@@ -35,13 +35,14 @@ function isFieldlessScope(scope) {
35
35
  }
36
36
  export function compileToolPolicy(grants, root, options) {
37
37
  const resolvedRoot = realOrRaw(root);
38
+ const owned = options?.ownedPaths ?? EMPTY_OWNED_PATHS_POLICY;
38
39
  // nax#2115: the SAME transform `relativeTo` applies to every checked path, so
39
40
  // the guard compares like with like. A path outside the root yields a
40
41
  // ".."-prefixed rel that can never equal a checked path's, exempting nothing.
41
42
  const ownedWriteExemption = options?.ownedWriteExemption === undefined
42
43
  ? undefined
43
44
  : relative(resolvedRoot, realOrRaw(options.ownedWriteExemption)).split(sep).join("/");
44
- const naxOptIns = naxWriteOptIns(root, options?.naxAllowWrite ?? []);
45
+ const naxOptIns = owned.writeOptIns(root, options?.naxAllowWrite ?? []);
45
46
  const denyBy = compileRuleMap(options?.denyRules);
46
47
  const askBy = compileRuleMap(options?.askRules);
47
48
  const bashApproval = options?.bashApproval ?? "gated";
@@ -87,9 +88,9 @@ export function compileToolPolicy(grants, root, options) {
87
88
  * denied and an ask on an ungranted call never becomes an approval prompt.
88
89
  */
89
90
  function applyPathRules(tool, rel, state) {
90
- const naxOwned = naxOwnedWriteRefusal(tool, rel, ownedWriteExemption, naxOptIns);
91
- if (naxOwned !== undefined)
92
- return deny(`${tool} may not modify ${naxOwned}`);
91
+ const ownedRefusal = owned.writeRefusal(tool, rel, { exemptRel: ownedWriteExemption, optIns: naxOptIns });
92
+ if (ownedRefusal !== undefined)
93
+ return deny(`${tool} may not modify ${ownedRefusal}`);
93
94
  const denyEntry = denyBy.get(tool);
94
95
  const askEntry = askBy.get(tool);
95
96
  if (denyEntry !== undefined && (denyEntry.unconditional || matchesAny(denyEntry.matchers, rel))) {
@@ -187,7 +188,7 @@ export function compileToolPolicy(grants, root, options) {
187
188
  * the nested helpers below stay authoritative, and the sibling reads them at
188
189
  * call time — `pathsBranch` itself lives in `./policy-paths-branch`.
189
190
  */
190
- const pathsBranchContext = { resolvedRoot, deny, askVerdict, applyPathRules };
191
+ const pathsBranchContext = { resolvedRoot, deny, askVerdict, applyPathRules, ownedPaths: owned };
191
192
  return {
192
193
  root: resolvedRoot,
193
194
  grantedTools() {
@@ -217,10 +218,11 @@ export function compileToolPolicy(grants, root, options) {
217
218
  bashApproval,
218
219
  rawBashRefusal: options?.rawBashRefusal,
219
220
  sandboxWrapped: options?.sandboxWrapped,
221
+ ownedPaths: owned,
220
222
  resolvedRoot,
221
223
  denyBy,
222
224
  askBy,
223
- resolvePath: (candidate, cwd) => resolveWithin(resolvedRoot, resolve(cwd, candidate)),
225
+ resolvePath: (candidate, cwd) => resolveWithin(resolvedRoot, resolve(cwd, candidate), owned),
224
226
  deny,
225
227
  askVerdict,
226
228
  }) ??
@@ -9,12 +9,12 @@ export interface ProtectedPathsPolicy {
9
9
  readonly gitExcludePathspecs: readonly string[];
10
10
  /** Gitignore patterns GitCommit refuses to stage. */
11
11
  readonly gitIgnorePatterns: readonly string[];
12
- /** Project-relative directory whose top-level entries the sandbox policy lists. */
13
- readonly projectStateDir: string;
14
- /** Directory whose `credentials*` files the sandbox denies. */
15
- readonly credentialDir: string;
16
- /** File the sandbox denies writes to: the trust store deciding whether repository code runs. */
17
- readonly trustStoreFile: string;
12
+ /** Project-relative directory whose top-level entries the sandbox policy lists. Absent: the sandbox skips it (S3 spec 6.2). */
13
+ readonly projectStateDir?: string;
14
+ /** Directory whose `credentials*` files the sandbox denies. Absent: the sandbox skips it (S3 spec 6.2). */
15
+ readonly credentialDir?: string;
16
+ /** File the sandbox denies writes to: the trust store deciding whether repository code runs. Absent: the sandbox skips it (S3 spec 6.2). */
17
+ readonly trustStoreFile?: string;
18
18
  }
19
19
  /** The Git tool's default excludes; none when the session carries no policy. */
20
20
  export declare function gitExcludePathspecsOf(ctx: {
@@ -22,6 +22,7 @@
22
22
  * both.
23
23
  */
24
24
  import { readPrefix } from "#src/internal/bounded-io";
25
+ import { credentialReadRefusal } from "./credential-read-deny.js";
25
26
  import { applyCapCut, limitStopFooter, shouldAppendLimitStopFooter } from "./read-continuation.js";
26
27
  import { MODEL_MAX_BYTES, MODEL_MAX_LINES, READ_CEILING, splitModelLines } from "./truncate.js";
27
28
  /** Range arguments models invent instead of offset/limit -- rejected by name, never silently dropped. */
@@ -41,6 +42,34 @@ function countLines(prefix) {
41
42
  const trimmed = prefix.endsWith("\n") ? prefix.slice(0, -1) : prefix;
42
43
  return trimmed.split("\n").length;
43
44
  }
45
+ /**
46
+ * The range half of Read's input: the unsupported-alias rejection (#1923) plus
47
+ * offset/limit parsing. Extracted from `run` so the credential read-deny adds
48
+ * no complexity there.
49
+ */
50
+ function parseRange(input) {
51
+ const usedAlias = UNSUPPORTED_RANGE_ALIASES.find((alias) => input[alias] !== undefined);
52
+ if (usedAlias !== undefined) {
53
+ return {
54
+ error: `"${usedAlias}" is not a supported Read argument -- use offset (1-based line number) and/or limit (line count) instead`,
55
+ };
56
+ }
57
+ let offset = 1;
58
+ if (input.offset !== undefined) {
59
+ const parsed = parsePositiveInt(input.offset, "offset");
60
+ if (typeof parsed === "string")
61
+ return { error: parsed };
62
+ offset = parsed;
63
+ }
64
+ let limit;
65
+ if (input.limit !== undefined) {
66
+ const parsed = parsePositiveInt(input.limit, "limit");
67
+ if (typeof parsed === "string")
68
+ return { error: parsed };
69
+ limit = parsed;
70
+ }
71
+ return { offset, limit };
72
+ }
44
73
  export const readTool = {
45
74
  name: "Read",
46
75
  description: "Read a UTF-8 text file from the repository. Paths are relative to the repository root. " +
@@ -64,15 +93,18 @@ export const readTool = {
64
93
  const [target] = ctx.resolvedPaths;
65
94
  if (target === undefined)
66
95
  return { content: "no path supplied", isError: true };
67
- const usedAlias = UNSUPPORTED_RANGE_ALIASES.find((alias) => input[alias] !== undefined);
68
- if (usedAlias !== undefined) {
69
- return {
70
- content: `"${usedAlias}" is not a supported Read argument -- use offset (1-based line number) and/or limit (line count) instead`,
71
- isError: true,
72
- };
96
+ // S3 spec 6.3: a workdir may resolve onto the host's credential directory
97
+ // or trust store; the read tools refuse those regardless of grant.
98
+ const credential = credentialReadRefusal(ctx.protectedPaths, target);
99
+ if (credential !== undefined) {
100
+ return { content: `path "${String(input.path)}" ${credential}`, isError: true };
73
101
  }
74
102
  const hasOffset = input.offset !== undefined;
75
103
  const hasLimit = input.limit !== undefined;
104
+ const range = parseRange(input);
105
+ if ("error" in range)
106
+ return { content: range.error, isError: true };
107
+ const { offset, limit } = range;
76
108
  try {
77
109
  if (!hasOffset && !hasLimit) {
78
110
  // Leading [N lines] header so an agent has a way to count the lines it
@@ -106,20 +138,6 @@ export const readTool = {
106
138
  });
107
139
  return { content: result.content };
108
140
  }
109
- let offset = 1;
110
- if (hasOffset) {
111
- const parsed = parsePositiveInt(input.offset, "offset");
112
- if (typeof parsed === "string")
113
- return { content: parsed, isError: true };
114
- offset = parsed;
115
- }
116
- let limit;
117
- if (hasLimit) {
118
- const parsed = parsePositiveInt(input.limit, "limit");
119
- if (typeof parsed === "string")
120
- return { content: parsed, isError: true };
121
- limit = parsed;
122
- }
123
141
  // Read up to maxFileBytes, not maxBytes: the requested range may start
124
142
  // past the display ceiling, and we still need to reach it to slice.
125
143
  const body = await readPrefix(target, ctx.maxFileBytes);
@@ -12,6 +12,7 @@
12
12
  import type { CommandInterceptor } from "#src/command-interceptor/index";
13
13
  import type { JSONSchema } from "#src/session/tool-descriptor";
14
14
  import type { SandboxRecord } from "../sandbox/index.ts";
15
+ import type { OwnedPathsPolicy } from "./owned-paths.ts";
15
16
  import type { ProtectedPathsPolicy } from "./protected-paths.ts";
16
17
  import type { CodingToolName, ToolScope } from "./types.ts";
17
18
  export interface ToolResult {
@@ -96,6 +97,8 @@ export interface ToolRunContext {
96
97
  readonly interceptor?: CommandInterceptor;
97
98
  /** Host-owned paths (S1 spec port 6). Absent: the Git tools exclude and skip nothing extra. */
98
99
  readonly protectedPaths?: ProtectedPathsPolicy;
100
+ /** Host-owned path rules (S3-2 port). Absent: no owned-path refusals. */
101
+ readonly ownedPaths?: OwnedPathsPolicy;
99
102
  }
100
103
  export interface CodingTool {
101
104
  readonly name: string;
@@ -112,6 +112,7 @@ export async function runExecBranch(input, ctx, opts) {
112
112
  packageWorkdir: opts.exec.packageWorkdir,
113
113
  packageRelPath,
114
114
  allowScripts: opts.exec.allowScripts,
115
+ ownedPaths: ctx.ownedPaths,
115
116
  ...(opts.exec.packageName !== undefined ? { packageName: opts.exec.packageName } : {}),
116
117
  });
117
118
  if ("error" in normalized)
@@ -12,6 +12,7 @@ import type { CommandInterceptor } from "#src/command-interceptor/index";
12
12
  import type { CommandShadow } from "#src/command-safety/index";
13
13
  import { getSafeLogger } from "#src/infra/index";
14
14
  import { type AskResolver } from "#src/permissions/index";
15
+ import type { OwnedPathsPolicy } from "./owned-paths.ts";
15
16
  import type { ProtectedPathsPolicy } from "./protected-paths.ts";
16
17
  import { type CodingTool } from "./registry.ts";
17
18
  import { type ToolAuditSink } from "./tool-audit.ts";
@@ -146,4 +147,6 @@ export declare function createCodingToolRuntime(opts: {
146
147
  interceptor?: CommandInterceptor;
147
148
  /** Port 6: placed on every ToolRunContext this runtime builds. */
148
149
  protectedPaths?: ProtectedPathsPolicy;
150
+ /** S3-2 port: placed on every ToolRunContext this runtime builds. */
151
+ ownedPaths?: OwnedPathsPolicy;
149
152
  }): CodingToolRuntime;
@@ -80,6 +80,7 @@ function contextPorts(opts) {
80
80
  return {
81
81
  ...(opts.interceptor !== undefined ? { interceptor: opts.interceptor } : {}),
82
82
  ...(opts.protectedPaths !== undefined ? { protectedPaths: opts.protectedPaths } : {}),
83
+ ...(opts.ownedPaths !== undefined ? { ownedPaths: opts.ownedPaths } : {}),
83
84
  };
84
85
  }
85
86
  export function createCodingToolRuntime(opts) {
@@ -17,6 +17,7 @@ import { mkdir, writeFile } from "node:fs/promises";
17
17
  import { dirname, join, sep } from "node:path";
18
18
  import { fileSizeOrZero } from "#src/internal/file-size";
19
19
  import { getAgentRuntime } from "#src/runtime/index";
20
+ import { EMPTY_OWNED_PATHS_POLICY } from "./owned-paths.js";
20
21
  import { resolveWithin } from "./policy.js";
21
22
  import { readFileSlice } from "./read-file.js";
22
23
  import { READ_CEILING } from "./truncate.js";
@@ -185,7 +186,7 @@ export const scratchpadListTool = {
185
186
  for (const hit of sorted) {
186
187
  if (files.length >= MAX_MATCHES)
187
188
  break;
188
- if (resolveWithin(ctx.root, hit) === null)
189
+ if (resolveWithin(ctx.root, hit, ctx.ownedPaths ?? EMPTY_OWNED_PATHS_POLICY) === null)
189
190
  continue;
190
191
  files.push(hit.split(sep).join("/"));
191
192
  }
@@ -63,8 +63,8 @@ export interface ToolScope {
63
63
  * Root-relative subdirectory the tool's path fields are confined to.
64
64
  * Containment runs against `<root>/<confineTo>` instead of `<root>`, so a
65
65
  * path-bearing tool can never reach the rest of the repository even under
66
- * an unconditional grant. Grant globs, deny rules and `naxOwnedWriteRefusal`
67
- * keep seeing the canonical repo-root-relative spelling: containment is the
66
+ * an unconditional grant. Grant globs, deny rules and the injected policy's
67
+ * `writeRefusal` keep seeing the canonical repo-root-relative spelling: containment is the
68
68
  * single seam, only its ROOT shifts. Tool-declared, never config-declared --
69
69
  * a mis-set `pathFields` grant must not be able to widen the tool's reach.
70
70
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax-agent",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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",
@@ -48,7 +48,7 @@
48
48
  "publishConfig": {
49
49
  "access": "public",
50
50
  "registry": "https://registry.npmjs.org/",
51
- "provenance": false,
51
+ "provenance": true,
52
52
  "tag": "latest"
53
53
  }
54
54
  }
@@ -1,135 +0,0 @@
1
- /**
2
- * Which paths nax refuses to let an agent touch, and to which tools.
3
- *
4
- * A separate file rather than an addition to src/tools/policy.ts, for the same
5
- * reason src/tools/deny-paths.ts is one: that file carries the containment
6
- * seam and sits at the project's file-size ratchet, while this is a narrower
7
- * concern -- it has nothing to do with resolving or containing a path, only
8
- * with refusing one containment would otherwise allow.
9
- *
10
- * Segment-exact, never a prefix or substring match: `.naxignore`,
11
- * `docs/nax/config.json` and `.nax/mono/api/notes.md` are ordinary paths a
12
- * tool must still reach.
13
- */
14
- /**
15
- * The one top-level `.nax/` entry agents may write freely: the scratchpad.
16
- * Must equal the last segment of SCRATCHPAD_DIR (pinned by a test; importing
17
- * it here would close a scratchpad -> policy -> nax-owned-writes cycle).
18
- */
19
- export declare const NAX_SCRATCHPAD_ENTRY = "scratchpad";
20
- /**
21
- * Entries nax loads as human-authored input. The sandbox denies these even
22
- * when absent, so an agent cannot create one: a new `.nax/rules/x.md` would be
23
- * injected into every later session's prompt, and `hooks.json` / `plugins/`
24
- * run unsandboxed on the next `nax run`. `templates/` and `prompts/` are where
25
- * `prompts.overrides` points. nax creates none of them mid-run (CLI only).
26
- */
27
- export declare const NAX_ALWAYS_DENIED_ENTRIES: readonly string[];
28
- /**
29
- * Top-level `.nax/` entries a human opened for agent writes by listing them in
30
- * `execution.sandbox.filesystem.allowWrite` (nax#2260). Only an exact entry
31
- * counts -- `.nax/rules`, never `.nax/rules/a.md` -- and never the scratchpad
32
- * (always open) or a NAX_NEVER_OPT_IN entry. Relative entries resolve against
33
- * `root`, as the sandbox's own write roots do.
34
- */
35
- export declare function naxWriteOptIns(root: string, allowWrite: readonly string[]): ReadonlySet<string>;
36
- /**
37
- * Is `resolved` one of nax's own CONFIG files, relative to `root`?
38
- *
39
- * `.nax/config.json`, and `.nax/mono/<package>/config.json` in a monorepo.
40
- *
41
- * Why these at all: `quality.commands` and `acceptance.command` are run by key
42
- * through a shell and never pass the permission gate -- they are trusted
43
- * because a HUMAN wrote them. That trust rests entirely on a model being
44
- * unable to write them. An agent holding `Write` under the default
45
- * `unrestricted` profile could otherwise add a quality command and receive an
46
- * ungated shell on the next run, routing around every `Bash(...)` rule, the
47
- * lexer's construct refusals and containment itself.
48
- *
49
- * Refused to EVERY tool, reads included -- this is the pre-existing behaviour
50
- * and it is deliberate.
51
- */
52
- export declare function isNaxConfigFile(root: string, resolved: string): boolean;
53
- /**
54
- * The path-bearing tools that MUTATE. Read, Grep, Glob and Git are read-only
55
- * and are deliberately absent: an agent legitimately reads its own PRD, and
56
- * refusing that would break ordinary work to close one hole.
57
- *
58
- * Bash and Exec are absent because they carry no path fields -- a shell
59
- * redirect into `.nax/` is gated by the human-authored `Bash(...)` rules and
60
- * the lexer, which is a different seam from this one.
61
- */
62
- export declare const NAX_OWNED_WRITE_TOOLS: ReadonlySet<string>;
63
- /**
64
- * nax's run-control files at the root: the command channel an agent could
65
- * otherwise use to PAUSE, ABORT or SKIP stories without writing any code.
66
- *
67
- * `.queue.txt.processing` is the atomic-rename target the queue handler reads
68
- * from (src/execution/queue-handler.ts), so guarding only `.queue.txt` would
69
- * leave the same hole one rename downstream.
70
- */
71
- export declare const QUEUE_CONTROL_FILES: ReadonlySet<string>;
72
- /**
73
- * The kinds of nax-owned path the raw Bash screen refuses by name.
74
- * `"config"` is produced by the lexical `isNaxConfigFile` pass, never by
75
- * `naxOwnedKind` -- see that function.
76
- */
77
- export type NaxOwnedKind = "prd" | "queue" | "config";
78
- /**
79
- * Is this path one nax owns the writes to, independent of WHICH tool is asking?
80
- *
81
- * `naxOwnedWriteRefusal` answers the same question for the four file-writing
82
- * tools and returns prose. This predicate is the tool-agnostic half, extracted
83
- * so the `raw` bash screen (ADR-030) can consult exactly the same path set
84
- * without being in `NAX_OWNED_WRITE_TOOLS`. One definition, two callers.
85
- *
86
- * @param rel - Path relative to the permitted root, `/`-joined.
87
- */
88
- export declare function isNaxOwnedWritePath(rel: string): boolean;
89
- /**
90
- * Which kind of nax-owned write path `rel` is, or undefined when it is not one.
91
- *
92
- * `/`-joined and root-relative, like `isNaxOwnedWritePath`. Never returns
93
- * `"config"` -- nax config files are recognised by the lexical
94
- * `isNaxConfigFile` pass, which callers (`protectedHit` in policy-bash-raw.ts)
95
- * run FIRST. Keeping the two apart is what lets the raw screen name the kind it
96
- * matched without duplicating the config rule here.
97
- */
98
- export declare function naxOwnedKind(rel: string): "prd" | "queue" | undefined;
99
- /**
100
- * Refusal text for a Bash command that names (verb "names") or redirects into
101
- * (verb "redirects into") a nax-owned file.
102
- *
103
- * Kind-specific on purpose. The previous screen returned one modification-only
104
- * sentence for every kind, which read as "you cannot WRITE this" -- so an agent
105
- * that only wanted to READ a PRD retried a command it will never be allowed to
106
- * run. Each branch now says what the file IS and what the agent can do instead.
107
- *
108
- * The text never spells `.nax/features/` literally: `src/tools/` is covered by
109
- * the `check:feature-dir-ssot` gate, so the PRD branch names the token the agent
110
- * used (`hit`) rather than the tree layout.
111
- *
112
- * US-002: `opts.sandboxWrapped` (an available launcher) swaps only the PRD
113
- * sentence -- reading through Bash is then allowed, writing still is not.
114
- * `config` and `queue` are unchanged in both modes.
115
- */
116
- export declare function naxOwnedBashRefusal(tool: string, kind: NaxOwnedKind, hit: string, verb: "names" | "redirects into", opts?: {
117
- sandboxWrapped?: boolean;
118
- }): string;
119
- /**
120
- * Why `tool` may not touch `rel`, or `undefined` when it may.
121
- *
122
- * `rel` MUST be the canonical, posix-separated, root-relative spelling the
123
- * policy itself derived -- never the caller's own. Matching the caller's
124
- * spelling means this guard and the policy that approved the call are reading
125
- * different strings, and every alternate spelling ("./x", "a/../x") walks past
126
- * the guard. Same rule, and the same reason, as `matchesDenyPaths`.
127
- *
128
- * The PRD defines the acceptance criteria the story is judged against. An
129
- * agent that can rewrite it can pass any review without writing any code,
130
- * which defeats the review layer without touching a config file.
131
- *
132
- * The queue file is the other half of the same concern: it is the run-control
133
- * channel, and a write there can pause, abort or skip stories outright.
134
- */
135
- export declare function naxOwnedWriteRefusal(tool: string, rel: string, exemptRel?: string, optIns?: ReadonlySet<string>): string | undefined;