@sema-agent/core 7.8.0 → 7.9.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 (103) hide show
  1. package/CHANGELOG.md +75 -1
  2. package/dist/agents/subagent.d.ts +1 -1
  3. package/dist/core/ask-origin.d.ts +16 -1
  4. package/dist/core/ask-origin.js +5 -1
  5. package/dist/core/checkpoint-store.d.ts +36 -35
  6. package/dist/core/effective-path-target.d.ts +43 -0
  7. package/dist/core/effective-path-target.js +56 -0
  8. package/dist/core/engine-notice.d.ts +8 -0
  9. package/dist/core/fs-write-gate-policy.js +2 -1
  10. package/dist/core/gate-lanes.d.ts +1 -0
  11. package/dist/core/gate-lanes.js +60 -28
  12. package/dist/core/gate-outcome.d.ts +8 -4
  13. package/dist/core/gate-outcome.js +2 -1
  14. package/dist/core/governance-codes.d.ts +1 -1
  15. package/dist/core/governance-codes.js +2 -0
  16. package/dist/core/hooks.d.ts +66 -32
  17. package/dist/core/mcp-injection-drop.d.ts +74 -0
  18. package/dist/core/mcp-injection-drop.js +27 -0
  19. package/dist/core/permission-rule-consent.d.ts +29 -36
  20. package/dist/core/permission-rule-consent.js +110 -60
  21. package/dist/core/permission-rule-model.d.ts +311 -96
  22. package/dist/core/permission-rule-model.js +309 -50
  23. package/dist/core/permission-rule-org.d.ts +19 -12
  24. package/dist/core/permission-rule-org.js +13 -16
  25. package/dist/core/permission-rule-provider.d.ts +10 -6
  26. package/dist/core/permission-rule-provider.js +12 -8
  27. package/dist/core/permission-rule-session.d.ts +7 -6
  28. package/dist/core/permission-rule-session.js +29 -10
  29. package/dist/core/permission-rule-store.d.ts +40 -18
  30. package/dist/core/permission-rule-store.js +68 -42
  31. package/dist/core/permission-rule-sync.d.ts +9 -3
  32. package/dist/core/permission-rule-sync.js +29 -25
  33. package/dist/core/permission-rule-syntax.d.ts +30 -0
  34. package/dist/core/permission-rule-syntax.js +44 -0
  35. package/dist/core/permission-rules.d.ts +56 -33
  36. package/dist/core/permission-rules.js +88 -55
  37. package/dist/core/persisted-rule-arms.d.ts +61 -0
  38. package/dist/core/persisted-rule-arms.js +50 -0
  39. package/dist/core/runner/active-skill-scope.js +2 -1
  40. package/dist/core/runner/contracts.d.ts +21 -2
  41. package/dist/core/runner/permission-rule-lanes.d.ts +42 -27
  42. package/dist/core/runner/permission-rule-lanes.js +25 -21
  43. package/dist/core/runner/prepare-gate-stations.js +1 -1
  44. package/dist/core/runner/prepare-safety-scan.js +8 -2
  45. package/dist/core/runner/prepare-task.js +1 -1
  46. package/dist/core/runner/resume-admission.d.ts +53 -0
  47. package/dist/core/runner/resume-admission.js +83 -0
  48. package/dist/core/runner/resume-apply.d.ts +50 -0
  49. package/dist/core/runner/resume-apply.js +184 -0
  50. package/dist/core/runner/resume-checkpoint-screen.d.ts +18 -0
  51. package/dist/core/runner/resume-checkpoint-screen.js +108 -0
  52. package/dist/core/runner/resume-claim.d.ts +32 -0
  53. package/dist/core/runner/resume-claim.js +27 -0
  54. package/dist/core/runner/resume-internals-and-config.d.ts +33 -0
  55. package/dist/core/runner/resume-internals-and-config.js +50 -0
  56. package/dist/core/runner/resume-policy-outcome.d.ts +31 -0
  57. package/dist/core/runner/resume-policy-outcome.js +127 -0
  58. package/dist/core/runner/resume-preflight.d.ts +40 -0
  59. package/dist/core/runner/resume-preflight.js +122 -0
  60. package/dist/core/runner/resume-review-outcome.d.ts +30 -0
  61. package/dist/core/runner/resume-review-outcome.js +88 -0
  62. package/dist/core/runner/run-harness-handlers.js +1 -1
  63. package/dist/core/runner/runtask.d.ts +20 -0
  64. package/dist/core/runner/runtask.js +85 -734
  65. package/dist/core/runner/session-rule-policy.js +2 -1
  66. package/dist/core/runner/tool-end-body.d.ts +12 -5
  67. package/dist/core/runner/tool-end-body.js +5 -5
  68. package/dist/core/runner/tool-face-overlay.js +22 -3
  69. package/dist/core/runner/tool-output-projection.d.ts +9 -6
  70. package/dist/core/runner/tool-output-projection.js +1 -18
  71. package/dist/core/runner/turn-attachments.d.ts +2 -2
  72. package/dist/core/sensitive-path-policy.js +5 -3
  73. package/dist/core/shell-lexer.d.ts +47 -0
  74. package/dist/core/shell-lexer.js +478 -0
  75. package/dist/core/shell-scan.d.ts +60 -0
  76. package/dist/core/shell-scan.js +183 -0
  77. package/dist/core/shell-wrapper-table.d.ts +297 -0
  78. package/dist/core/shell-wrapper-table.js +58 -0
  79. package/dist/core/store-contracts/permission-rule-sync-contract.js +29 -10
  80. package/dist/core/tool-catalog-entries.js +6 -6
  81. package/dist/core/tool-face.d.ts +80 -4
  82. package/dist/core/tool-face.js +10 -0
  83. package/dist/core/tool-policy.d.ts +1 -6
  84. package/dist/core/tool-registry.d.ts +8 -11
  85. package/dist/core/tool-registry.js +5 -2
  86. package/dist/core/tool-roster.d.ts +20 -2
  87. package/dist/core/tool-roster.js +36 -8
  88. package/dist/index.d.ts +7 -4
  89. package/dist/index.js +6 -3
  90. package/dist/stores/file/checkpoint-store.js +1 -1
  91. package/dist/stores/file/permission-rule-store.d.ts +28 -23
  92. package/dist/stores/file/permission-rule-store.js +64 -16
  93. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  94. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  95. package/dist/tools/fs/fs-search-tools.d.ts +3 -2
  96. package/dist/tools/fs/fs-search-tools.js +17 -9
  97. package/dist/tools/fs/index.js +1 -1
  98. package/dist/tools/fs/notebook.d.ts +1 -1
  99. package/dist/tools/fs/safety.d.ts +1 -1
  100. package/dist/tools/fs/search.d.ts +0 -8
  101. package/dist/tools/fs/search.js +0 -23
  102. package/package.json +1 -1
  103. package/test/export-surface.snapshot.json +99 -11
@@ -2,6 +2,7 @@ import { canonicalizeTarget, writeTargetPath } from "../../tools/fs/safety.js";
2
2
  import { isWinFormPath } from "../../tools/fs/safety.js";
3
3
  import { createCoarseCommandNamePolicy, namespacedCoveringEntries, namespacedCoveringHit } from "../tool-policy.js";
4
4
  import { declaredPathTargetOf, isProtectedWrite, pathConfinableWriteToolNames, skillScopeWriteToolNames } from "../tool-registry.js";
5
+ import { pathTargetBaseOf } from "../effective-path-target.js";
5
6
  export const PATH_WRITE_TOOLS = skillScopeWriteToolNames();
6
7
  export const PATH_CONFINABLE_WRITE_TOOLS = pathConfinableWriteToolNames();
7
8
  export function isWithin(root, p) {
@@ -65,7 +66,7 @@ export function createSessionRulePolicy(rules, opts) {
65
66
  if (typeof path !== "string" || path.length === 0) {
66
67
  return deny(`write tool "${req.toolName}" denied: session rule confines writes to allowDirs but the call has no resolvable path`);
67
68
  }
68
- const canon = await canonicalizeTarget(env, path, signal, req.cwd ?? rootPath);
69
+ const canon = await canonicalizeTarget(env, path, signal, pathTargetBaseOf(declared, { root: rootPath, cwd: req.cwd }));
69
70
  if (!canon.ok) {
70
71
  return deny(`write to "${path}" denied: its real target could not be resolved against the session-rule allowDirs`);
71
72
  }
@@ -7,24 +7,31 @@
7
7
  *
8
8
  * Projections over values the caller already holds, with ONE clock read: `toolResultMsg` stamps the message
9
9
  * it mints with `Date.now()` (as it did inside the driver — the transcript's timestamp is minted where the
10
- * message is). Nothing here reads host state through a callback, awaits, or keeps state between calls.
10
+ * message is). The one callback here is the leg's card projector, handed in by every caller (it keeps the
11
+ * once-per-leg announce state; this module keeps none). Nothing here awaits.
11
12
  * Layer-1 machinery, not a driven lane; the prefix rule it follows is written once, on initial-run-state.ts.
12
13
  */
13
14
  import { type AgentMessage } from "../../internal/harness.js";
14
15
  import type { GateOutcome } from "../gate-outcome.js";
15
16
  import { type McpDelivered } from "../mcp-failure.js";
16
17
  import type { RecoveredOrphan } from "../session-reconcile.js";
18
+ import { type StructuredProjector } from "./tool-output-projection.js";
17
19
  import type { WriteFamily } from "./turn-attachments.js";
18
20
  /** design/134 §3.1b — derive the thin `response` digest from a harness tool result: model-facing text
19
21
  * content only (never `details` — the H4 thin-projection discipline), capped with a truncation note. */
20
22
  export declare function batchResponseDigest(result: unknown): string | undefined;
21
23
  /** The `tool_end` body fields projected from a harness tool result — output/truncated/totalChars via
22
- * {@link toolOutputFrom} and the CC card via {@link structuredFrom}. Single construction point for BOTH
24
+ * {@link toolOutputFrom} and the CC card via the LEG's projector (`Prepared.structuredProjector`: the engine card set
25
+ * ∪ the caller rows' declared `renderHints.resultCards`, an undeclared type dropped and announced once per leg —
26
+ * design/388 L4; handed in because the once-per-leg state lives on the leg). Single construction point for BOTH
23
27
  * the live loop's frames and the resumed batch's frames (`resolvePendingCall` + the deferred-sibling
24
28
  * close): the resumed frames used to carry only `isError`, so a client rendering tool output from frames
25
29
  * showed an empty body for every durable-approved call. Same projection = same source as the transcript.
26
30
  * Also the one place the gate outcome reaches a frame — see the parameter. */
27
31
  export declare function toolEndBodyFrom(result: unknown, isError: boolean,
32
+ /** The leg's card projector — see the doc above. A parameter, never a module default: a body minted with the engine
33
+ * set alone would silently drop every declared caller card and never announce. */
34
+ structured: StructuredProjector,
28
35
  /** The gate's record of the pass that admitted or refused this call, supplied by the CALLER of this
29
36
  * projection — the live loop reads it off the gate's per-call sideband, the resumed leg off the decide's
30
37
  * minted record. Deliberately a parameter and never derived from `result`: a tool's own `details`
@@ -58,10 +65,10 @@ gatedCallIdOfRun?: string): {
58
65
  * disagreed. The projection goes through the same {@link toolEndBodyFrom} every live tool result uses, so
59
66
  * `output` = the persisted model-facing text and `errorCode` = the persisted `details.errorKind`
60
67
  * (`interrupted_never_started` / `interrupted_outcome_unknown`) — a consumer discriminates on the code
61
- * instead of prose-matching. No `structured`: the reconcile mints no CC card (no `details.type`), which
62
- * `structuredFrom`'s allowlist already enforces.
68
+ * instead of prose-matching. No `structured`: the reconcile mints no CC card (no `details.type`), which the
69
+ * leg's projector already enforces.
63
70
  */
64
- export declare function reconciledToolEndBody(orphan: Pick<RecoveredOrphan, "text" | "errorKind">): ReturnType<typeof toolEndBodyFrom>;
71
+ export declare function reconciledToolEndBody(orphan: Pick<RecoveredOrphan, "text" | "errorKind">, structured: StructuredProjector): ReturnType<typeof toolEndBodyFrom>;
65
72
  /** task #51 F2/F3 — the CANONICAL write-tool → reminder-window mapping (CC qFm :480253-480254
66
73
  * `a.name === bD || a.name === hF` = TaskCreate/TaskUpdate; BFm :480217-480219 = TodoWrite). Live
67
74
  * deployment-alias resolution happens at the call sites (run-local `writeFamilyOf` over the mounted
@@ -1,7 +1,7 @@
1
1
  import {} from "../../internal/harness.js";
2
2
  import { MCP_DELIVERY_VERDICTS } from "../mcp-failure.js";
3
3
  import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
4
- import { structuredFrom, toolOutputFrom } from "./tool-output-projection.js";
4
+ import { toolOutputFrom } from "./tool-output-projection.js";
5
5
  import { TASK_CREATE_TOOL_NAME, TASK_UPDATE_TOOL_NAME, TODO_WRITE_TOOL_NAME } from "../tool-catalog-entries.js";
6
6
  const BATCH_RESPONSE_MAX_CHARS = 500;
7
7
  export function batchResponseDigest(result) {
@@ -32,9 +32,9 @@ export function batchResponseDigest(result) {
32
32
  ? `${text.slice(0, BATCH_RESPONSE_MAX_CHARS)}…[+${text.length - BATCH_RESPONSE_MAX_CHARS} chars truncated]`
33
33
  : text;
34
34
  }
35
- export function toolEndBodyFrom(result, isError, gate, gatedCallIdOfRun) {
35
+ export function toolEndBodyFrom(result, isError, structured, gate, gatedCallIdOfRun) {
36
36
  const o = toolOutputFrom(result);
37
- const st = structuredFrom(result);
37
+ const st = structured(result);
38
38
  const det = isError ? result?.details : undefined;
39
39
  const codeRaw = det?.code;
40
40
  const kindRaw = det?.errorKind;
@@ -51,8 +51,8 @@ export function toolEndBodyFrom(result, isError, gate, gatedCallIdOfRun) {
51
51
  ...(gate !== undefined ? { gate } : {}),
52
52
  };
53
53
  }
54
- export function reconciledToolEndBody(orphan) {
55
- return toolEndBodyFrom({ content: orphan.text, details: { code: orphan.errorKind } }, true);
54
+ export function reconciledToolEndBody(orphan, structured) {
55
+ return toolEndBodyFrom({ content: orphan.text, details: { code: orphan.errorKind } }, true, structured);
56
56
  }
57
57
  export function writeFamilyOfCanonical(name) {
58
58
  if (name === TASK_CREATE_TOOL_NAME || name === TASK_UPDATE_TOOL_NAME)
@@ -1,6 +1,6 @@
1
1
  import { MCP_NAMESPACE } from "../protocol-table.js";
2
2
  import { mintNamespacePrefix } from "../protocol-naming.js";
3
- import { TOOL_APPROVAL_CARDS, TOOL_FAMILIES, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_CHARS, RENDER_HINT_MAX_LIST, TOOL_KEY_MAX_CHARS } from "../tool-face.js";
3
+ import { TOOL_APPROVAL_CARDS, TOOL_FAMILIES, TOOL_PATH_ABSENCES, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_CHARS, RENDER_HINT_MAX_LIST, TOOL_KEY_MAX_CHARS, } from "../tool-face.js";
4
4
  import { inputKeysOf } from "../tool-roster.js";
5
5
  import { deliverEngineNotice } from "../engine-notice.js";
6
6
  export const APPROVAL_CARD_REQUIRED_KEYS = Object.freeze({
@@ -26,9 +26,28 @@ export function toolFaceProblem(face, schema) {
26
26
  if (face.pathTarget !== undefined) {
27
27
  if (typeof face.pathTarget.param !== "string" || !top.has(face.pathTarget.param))
28
28
  return `pathTarget.param ${JSON.stringify(face.pathTarget.param)} is not a top-level property of the tool's schema`;
29
- if (!["read", "create", "edit"].includes(face.pathTarget.access))
29
+ if (!TOOL_PATH_ACCESSES.includes(face.pathTarget.access))
30
30
  return `pathTarget.access ${JSON.stringify(face.pathTarget.access)} is outside the closed set`;
31
- const long = overlong("pathTarget.param", [face.pathTarget.param]) ?? overlong("pathTarget.aliases", face.pathTarget.aliases ?? []);
31
+ const base = face.pathTarget.base;
32
+ if (base !== undefined && !TOOL_PATH_BASES.includes(base))
33
+ return `pathTarget.base ${JSON.stringify(base)} is outside the closed set`;
34
+ if (base === "root" && face.pathTarget.access !== "read")
35
+ return `pathTarget.base "root" needs pathTarget.access "read" — two write guards (the session-transcript directory guard and the frozen-spec deny) resolve a write target against the call's working directory only, so a write face declaring a root base would be judged on two different files (#638)`;
36
+ const absent = face.pathTarget.absent;
37
+ if (absent !== undefined && !TOOL_PATH_ABSENCES.includes(absent))
38
+ return `pathTarget.absent ${JSON.stringify(absent)} is outside the closed set`;
39
+ if (absent === "base" && face.pathTarget.access !== "read")
40
+ return `pathTarget.absent "base" needs pathTarget.access "read" — it says the call is about the whole base directory when the slot is empty, which is a search SCOPE; a write with no path names no file, and the containment fences judge a write on its own target`;
41
+ const patternParam = face.pathTarget.patternParam;
42
+ if (patternParam !== undefined) {
43
+ if (typeof patternParam !== "string" || !top.has(patternParam))
44
+ return `pathTarget.patternParam ${JSON.stringify(patternParam)} is not a top-level property of the tool's schema`;
45
+ if (absent !== "base")
46
+ return `pathTarget.patternParam ${JSON.stringify(patternParam)} is declared but pathTarget.absent is ${JSON.stringify(absent ?? "none")} — the pattern base is only ever read for an absent slot, so this declaration would be inert`;
47
+ }
48
+ const long = overlong("pathTarget.param", [face.pathTarget.param]) ??
49
+ overlong("pathTarget.aliases", face.pathTarget.aliases ?? []) ??
50
+ overlong("pathTarget.patternParam", patternParam === undefined ? [] : [patternParam]);
32
51
  if (long !== undefined)
33
52
  return long;
34
53
  vocabulary.add(face.pathTarget.param);
@@ -9,14 +9,17 @@ export declare const toolOutputFrom: (result: unknown) => {
9
9
  truncated: boolean;
10
10
  totalChars?: number;
11
11
  } | undefined;
12
+ /** The `tool_end.structured` projection over a harness tool result: the CC card when `details.type` names a card
13
+ * the leg admits, else undefined. Minted per leg by {@link createStructuredProjector}; every body projection takes it
14
+ * as a parameter — there is no module-level card set to fall back to. */
15
+ export type StructuredProjector = (result: unknown) => unknown;
12
16
  /**
13
17
  * L4's run-time half (design/388 §2.4): a per-leg projector — the engine card set plus the leg's declared
14
- * caller cards — that DROPS an undeclared `details.type` exactly as the module-level gate does and announces
15
- * it ONCE per leg (`config.tool_card_undeclared`, audience operator). The module-level {@link structuredFrom}
16
- * is this projector over the engine set alone, with no announce seat.
18
+ * caller cards — that DROPS an undeclared `details.type` and announces it ONCE per leg
19
+ * (`config.tool_card_undeclared`, audience operator). With no options it is the engine set alone, unannounced —
20
+ * the shape a caller outside a leg (a test, a fixture) uses.
17
21
  */
18
- export declare function createStructuredProjector(opts: {
22
+ export declare function createStructuredProjector(opts?: {
19
23
  declaredCards?: Iterable<string>;
20
24
  onUndeclared?: (type: string) => void;
21
- }): (result: unknown) => unknown;
22
- export declare const structuredFrom: (result: unknown) => unknown;
25
+ }): StructuredProjector;
@@ -58,7 +58,7 @@ export const toolOutputFrom = (result) => {
58
58
  return { output: raw, truncated: true, totalChars };
59
59
  };
60
60
  const CC_DETAIL_TYPES = engineCardTypes();
61
- export function createStructuredProjector(opts) {
61
+ export function createStructuredProjector(opts = {}) {
62
62
  const allowed = new Set([...CC_DETAIL_TYPES, ...(opts.declaredCards ?? [])]);
63
63
  const announced = new Set();
64
64
  return (result) => {
@@ -89,20 +89,3 @@ function boundedCard(details) {
89
89
  }
90
90
  return details;
91
91
  }
92
- export const structuredFrom = (result) => {
93
- const details = result !== null && typeof result === "object" ? result.details : undefined;
94
- if (details === null || typeof details !== "object")
95
- return undefined;
96
- const t = details.type;
97
- if (typeof t !== "string" || !CC_DETAIL_TYPES.has(t))
98
- return undefined;
99
- try {
100
- const serialized = JSON.stringify(details) ?? "";
101
- if (serialized.length > MAX_TOOL_END_OUTPUT_CHARS * 4)
102
- return undefined;
103
- }
104
- catch {
105
- return undefined;
106
- }
107
- return details;
108
- };
@@ -110,7 +110,7 @@ export interface TurnAttachment {
110
110
  source: AttachmentSource;
111
111
  body: string;
112
112
  }
113
- /** H4 — the thin projection of a todo list kept run-local (NEVER the wire-capped structuredFrom card).
113
+ /** H4 — the thin projection of a todo list kept run-local (NEVER the wire-capped `tool_end.structured` card).
114
114
  * Pure CONTENT memory: the write-window anchor lives on {@link AttachmentState.todoLastWriteTurn}
115
115
  * (task #51 F3 — the window resets on tool-call INITIATION, which may precede/outlive any ingest). */
116
116
  export interface ListProjection {
@@ -273,7 +273,7 @@ export declare const INSTRUCTIONS_CHANGE_BYTE_CAP = 512;
273
273
  */
274
274
  export declare function createAttachmentState(): AttachmentState;
275
275
  /**
276
- * H4 — thin-projection reducer over a tool's RAW `result.details` (NOT the wire `structuredFrom`
276
+ * H4 — thin-projection reducer over a tool's RAW `result.details` (NOT the wire card projector
277
277
  * projection and its 4×16K caps: a huge todo list must shrink into a bounded snapshot here, never
278
278
  * disarm the reminder by being dropped wholesale). Only the first-party discriminants `todo` /
279
279
  * `task` / `task-list` are recognized — every other card type (subagent `agent`/`fork`/`task-output`,
@@ -2,7 +2,8 @@ import { homedir } from "node:os";
2
2
  import { isAbsolute, join, resolve } from "node:path";
3
3
  import { canonicalizeTarget, expandHomeTilde, isAbsolutePathForm, isWinFormPath, writeTargetPath } from "../tools/fs/safety.js";
4
4
  import { compileSegmentPattern, matchSegmentPatterns } from "../tools/fs/read-deny.js";
5
- import { isProtectedWrite } from "./tool-registry.js";
5
+ import { isProtectedWrite, protectivePathTargetOf } from "./tool-registry.js";
6
+ import { pathTargetBaseOf } from "./effective-path-target.js";
6
7
  export const RECOMMENDED_SENSITIVE_PATTERNS = [
7
8
  ".env",
8
9
  ".env.*",
@@ -97,10 +98,11 @@ export function createSensitivePathPolicy(opts) {
97
98
  return { action: "allow" };
98
99
  if (guardedByName !== undefined ? !guardedByName.has(req.toolName) : !isProtectedWrite(req))
99
100
  return { action: "allow" };
100
- const path = writeTargetPath(req);
101
+ const protective = protectivePathTargetOf(req);
102
+ const path = writeTargetPath(req, protective);
101
103
  if (typeof path !== "string" || path.length === 0)
102
104
  return { action: "allow" };
103
- const canon = await canonicalizeTarget(opts.env, path, signal, req.cwd ?? opts.rootPath);
105
+ const canon = await canonicalizeTarget(opts.env, path, signal, pathTargetBaseOf(protective, { root: opts.rootPath, cwd: req.cwd }));
104
106
  if (!canon.ok) {
105
107
  if (canon.unresolvedSymlink) {
106
108
  return {
@@ -0,0 +1,47 @@
1
+ /** One argv word as the lexer read it. */
2
+ export interface ShellWord {
3
+ /** The word's value with quotes and escapes removed — meaningful only when `expands` is `false`. */
4
+ readonly text: string;
5
+ /** The unquoted source spelling (assignment and wrapper-flag tests read this, never `text`). */
6
+ readonly raw: string;
7
+ /** `false`: a literal word. `"one"`: carries an expansion but yields exactly ONE word (it was double-quoted).
8
+ * `"many"`: an unquoted expansion, glob or brace pattern, or a quoted `"$@"` / array expansion — may
9
+ * yield zero, one or several words. */
10
+ readonly expands: false | "one" | "many";
11
+ }
12
+ /** One program run the lexer found, after keyword stripping. */
13
+ export interface ShellSegment {
14
+ /** The run as SPELLED (a leading keyword removed): the first candidate a rule is compared against. */
15
+ readonly argv: readonly ShellWord[];
16
+ /** The deeper candidates, one per peeled layer (leading assignments, then each wrapper of the table):
17
+ * `sudo -u root rm -r x` ⇒ `[[rm, -r, x]]`. Empty when nothing peeled. */
18
+ readonly peeled: readonly (readonly ShellWord[])[];
19
+ /** Present when this segment's program runs cannot be known from the text at all; `argv` then holds
20
+ * whatever was read before the failure and must not be matched as a run. */
21
+ readonly unreadable?: string;
22
+ /** Present when the segment's program runs are NOT fully known although its candidates could be read:
23
+ * a peel that stopped before the wrapped program could be named (its options carry an expansion, a
24
+ * command string, an option of unknown arity), or a command/process substitution anywhere in the
25
+ * segment (it runs a program the lexer does not read). The candidates stand — a rule they reach is
26
+ * reached — and every other rule reads `unreadable`. */
27
+ readonly peelUnreadable?: string;
28
+ }
29
+ /** A command line as the tightening reader sees it. */
30
+ export interface ShellCommandShape {
31
+ readonly segments: readonly ShellSegment[];
32
+ /** The connector BETWEEN each consecutive pair of segments (`;` `&&` `||` `|` `|&` `&` `\n`). */
33
+ readonly connectors: readonly string[];
34
+ /** A subshell, group, control-structure keyword or dropped empty piece bounded the segments: the
35
+ * connector list is not one flat chain a compound rule could spell. */
36
+ readonly grouped: boolean;
37
+ }
38
+ export { SHELL_WRAPPER_TABLE, type ShellWrapperName } from "./shell-wrapper-table.js";
39
+ /** Longer than this and the command is not read at all (one unreadable segment): every pass is linear,
40
+ * but a substitution nested inside quoting re-enters the quote reader once per nesting level, and the
41
+ * cap is what bounds that on model-supplied text. Upstream's own reader stops at the same size. */
42
+ export declare const MAX_SHELL_READ_CHARS = 10000;
43
+ /** Read `source` as the tightening reader must: every program run the text could perform, or why it cannot be read. */
44
+ export declare function readShellCommand(source: string): ShellCommandShape;
45
+ /** Every program run of `shape` can be NAMED from the text: no segment is unreadable, no peel stopped,
46
+ * and every run candidate's program word is literal. The offer/mint side's "judgeable" question. */
47
+ export declare function isFullyReadable(shape: ShellCommandShape): boolean;