@sema-agent/core 7.45.0 → 7.47.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 (139) hide show
  1. package/CHANGELOG.md +298 -1
  2. package/dist/agents/extra-tool-names.d.ts +65 -0
  3. package/dist/agents/extra-tool-names.js +124 -0
  4. package/dist/agents/subagent.d.ts +17 -5
  5. package/dist/agents/subagent.js +33 -25
  6. package/dist/agents/tool-filter.d.ts +17 -1
  7. package/dist/agents/tool-filter.js +7 -0
  8. package/dist/core/agent-config-dir.d.ts +20 -0
  9. package/dist/core/agent-config-dir.js +49 -0
  10. package/dist/core/allow-entry.d.ts +6 -4
  11. package/dist/core/allow-entry.js +6 -5
  12. package/dist/core/ask-origin.d.ts +35 -12
  13. package/dist/core/ask-origin.js +24 -7
  14. package/dist/core/checkpoint-store.d.ts +39 -2
  15. package/dist/core/effective-path-target.d.ts +136 -4
  16. package/dist/core/effective-path-target.js +110 -2
  17. package/dist/core/engine-notice.d.ts +12 -4
  18. package/dist/core/gate-fold.js +5 -2
  19. package/dist/core/gate-lanes.js +11 -4
  20. package/dist/core/gate-outcome.d.ts +4 -2
  21. package/dist/core/gate-outcome.js +2 -2
  22. package/dist/core/governance-codes.d.ts +3 -1
  23. package/dist/core/governance-codes.js +3 -0
  24. package/dist/core/hooks.d.ts +3 -0
  25. package/dist/core/path-rule-binding.d.ts +65 -0
  26. package/dist/core/path-rule-binding.js +99 -0
  27. package/dist/core/permission-rule-model.d.ts +59 -24
  28. package/dist/core/permission-rule-model.js +48 -44
  29. package/dist/core/permission-rule-provider.d.ts +7 -0
  30. package/dist/core/permission-rule-provider.js +10 -3
  31. package/dist/core/permission-rule-store.d.ts +8 -0
  32. package/dist/core/permission-rule-store.js +6 -2
  33. package/dist/core/permission-rule-sync.js +2 -2
  34. package/dist/core/permission-rules.d.ts +26 -45
  35. package/dist/core/permission-rules.js +77 -57
  36. package/dist/core/runner/child-tree.d.ts +97 -0
  37. package/dist/core/runner/child-tree.js +104 -0
  38. package/dist/core/runner/construction-doors.d.ts +5 -0
  39. package/dist/core/runner/construction-doors.js +6 -0
  40. package/dist/core/runner/contracts.d.ts +46 -0
  41. package/dist/core/runner/gate-exit.d.ts +4 -1
  42. package/dist/core/runner/gate-exit.js +5 -1
  43. package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
  44. package/dist/core/runner/permission-rule-lanes.js +18 -12
  45. package/dist/core/runner/persisted-path-rows.d.ts +31 -7
  46. package/dist/core/runner/persisted-path-rows.js +31 -12
  47. package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
  48. package/dist/core/runner/prepare-ask-lane.js +4 -2
  49. package/dist/core/runner/prepare-execution-env.d.ts +5 -2
  50. package/dist/core/runner/prepare-execution-env.js +10 -3
  51. package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
  52. package/dist/core/runner/prepare-gate-stations.js +2 -1
  53. package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
  54. package/dist/core/runner/prepare-hands-readface.js +11 -11
  55. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
  56. package/dist/core/runner/prepare-inherited-gate.js +8 -2
  57. package/dist/core/runner/prepare-park-ask.d.ts +3 -0
  58. package/dist/core/runner/prepare-park-ask.js +5 -3
  59. package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
  60. package/dist/core/runner/prepare-policy-chain.js +13 -5
  61. package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
  62. package/dist/core/runner/prepare-suspend-saga.js +7 -1
  63. package/dist/core/runner/prepare-task.js +37 -18
  64. package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
  65. package/dist/core/runner/prepare-turn-wiring.js +2 -1
  66. package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
  67. package/dist/core/runner/prepare-workspace-restore.js +1 -0
  68. package/dist/core/runner/resume-apply.js +17 -3
  69. package/dist/core/runner/resume-checkpoint-screen.js +13 -0
  70. package/dist/core/runner/run-leg.js +5 -1
  71. package/dist/core/runner/runtask.js +2 -2
  72. package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
  73. package/dist/core/runner/write-protection-station.d.ts +3 -2
  74. package/dist/core/runner/write-protection-station.js +1 -0
  75. package/dist/core/runner-deps.d.ts +14 -0
  76. package/dist/core/sensitive-path-policy.d.ts +8 -0
  77. package/dist/core/sensitive-path-policy.js +18 -2
  78. package/dist/core/shell-wrapper-projection.d.ts +11 -0
  79. package/dist/core/shell-wrapper-projection.js +56 -0
  80. package/dist/core/shell-wrapper-table.d.ts +144 -34
  81. package/dist/core/shell-wrapper-table.js +40 -20
  82. package/dist/core/synthetic-read-verdict.d.ts +3 -0
  83. package/dist/core/synthetic-read-verdict.js +3 -1
  84. package/dist/core/tool-catalog-entries.d.ts +21 -0
  85. package/dist/core/tool-catalog-entries.js +12 -2
  86. package/dist/core/tool-face.d.ts +10 -1
  87. package/dist/core/tool-name-readings.d.ts +8 -0
  88. package/dist/core/tool-name-readings.js +2 -0
  89. package/dist/core/tool-policy.d.ts +40 -17
  90. package/dist/core/tool-policy.js +11 -9
  91. package/dist/core/tool-registry.d.ts +9 -0
  92. package/dist/core/tool-registry.js +10 -0
  93. package/dist/core/tool-spec.d.ts +3 -2
  94. package/dist/core/write-protect.d.ts +14 -3
  95. package/dist/core/write-protect.js +41 -10
  96. package/dist/index.d.ts +6 -4
  97. package/dist/index.js +5 -3
  98. package/dist/orchestration/run-spec.js +8 -7
  99. package/dist/orchestration/workflow.js +1 -1
  100. package/dist/shell/lex.d.ts +22 -0
  101. package/dist/shell/lex.js +75 -13
  102. package/dist/tools/fs/bash-program-position.d.ts +14 -90
  103. package/dist/tools/fs/bash-program-position.js +41 -74
  104. package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
  105. package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
  106. package/dist/tools/fs/index.d.ts +3 -2
  107. package/dist/tools/fs/program-read-model.d.ts +711 -0
  108. package/dist/tools/fs/program-read-model.js +1259 -0
  109. package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
  110. package/dist/tools/fs/program-read-model.rows.js +624 -0
  111. package/dist/tools/fs/read-deny-home.d.ts +19 -1
  112. package/dist/tools/fs/read-deny-home.js +62 -24
  113. package/dist/tools/fs/read-deny.d.ts +54 -14
  114. package/dist/tools/fs/read-deny.js +104 -50
  115. package/dist/tools/fs/removal-targets.d.ts +7 -6
  116. package/dist/tools/fs/removal-targets.js +34 -24
  117. package/dist/tools/fs/safety.d.ts +3 -0
  118. package/dist/tools/fs/safety.js +10 -3
  119. package/dist/tools/fs/shell-read-argv.d.ts +86 -0
  120. package/dist/tools/fs/shell-read-argv.js +388 -0
  121. package/dist/tools/fs/shell-read-bindings.d.ts +300 -0
  122. package/dist/tools/fs/shell-read-bindings.js +755 -0
  123. package/dist/tools/fs/shell-read-check.js +23 -15
  124. package/dist/tools/fs/shell-read-establish.js +82 -8
  125. package/dist/tools/fs/shell-read-judge.d.ts +11 -1
  126. package/dist/tools/fs/shell-read-judge.js +9 -1
  127. package/dist/tools/fs/shell-read-plan.d.ts +33 -8
  128. package/dist/tools/fs/shell-read-plan.js +309 -407
  129. package/dist/tools/fs/shell-read-word.d.ts +60 -26
  130. package/dist/tools/fs/shell-read-word.js +47 -37
  131. package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
  132. package/dist/tools/fs/shell-read-write-targets.js +85 -137
  133. package/dist/tools/fs/shell-reentry-text.d.ts +136 -6
  134. package/dist/tools/fs/shell-reentry-text.js +203 -53
  135. package/dist/tools/monitor.d.ts +3 -3
  136. package/package.json +1 -1
  137. package/test/export-surface.snapshot.json +25 -5
  138. package/wire-changes/core@7.46.0.json +1522 -0
  139. package/wire-changes/core@7.47.0.json +412 -0
@@ -1,9 +1,11 @@
1
+ import { releasableAskClassesFor } from "./ask-origin.js";
1
2
  import { canonicalizeTarget, isWinFormPath, writeTargetPath, isUncPath, isAbsoluteForFamily, joinForFamily, pathFamilyOf } from "../tools/fs/safety.js";
2
3
  import { protectivePathTargetOf } from "./tool-registry.js";
3
4
  import { pathTargetBaseOf } from "./effective-path-target.js";
4
5
  import { canonicalDataRootKeys, dataRootSpellings, relativeUnderRoot } from "./sensitive-path-policy.js";
5
6
  import { symlinkChainHops } from "./path-identity.js";
6
7
  import { readDenyPatternRun } from "../tools/fs/read-deny.js";
8
+ import { agentConfigDirSegments, agentConfigDirTail } from "./agent-config-dir.js";
7
9
  import { thrownText } from "./thrown-value.js";
8
10
  const freezeTable = (rows) => Object.freeze(rows.map((r) => Object.freeze(r)));
9
11
  export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
@@ -196,6 +198,7 @@ export function compileWriteProtection(entries, opts) {
196
198
  const basenames = new Map();
197
199
  const segments = new Map();
198
200
  const runs = [];
201
+ const configTails = [];
199
202
  for (const row of rows) {
200
203
  const folded = foldWriteProtectName(row.name);
201
204
  if (row.kind === "basename") {
@@ -209,8 +212,23 @@ export function compileWriteProtection(entries, opts) {
209
212
  else {
210
213
  runs.push({ name: row.name, parts: folded.split("/").filter((s) => s.length > 0) });
211
214
  }
215
+ const tail = row.kind === "basename" ? undefined : agentConfigDirTail(row.name);
216
+ if (tail !== undefined && opts?.agentConfigDir !== undefined)
217
+ configTails.push({ hit: { name: row.name, kind: row.kind }, tail: foldWriteProtectName(tail).split("/").filter((s) => s.length > 0) });
212
218
  }
213
- const judgeSegments = (raw) => {
219
+ const configRootSpelled = opts?.agentConfigDir === undefined ? [] : [agentConfigDirSegments(opts.agentConfigDir)];
220
+ const judgeAtConfigRoots = (segs, roots) => {
221
+ for (const root of roots) {
222
+ const at = root.map(foldWriteProtectSegment);
223
+ if (segs.length < at.length || !at.every((a, i) => segs[i] === a))
224
+ continue;
225
+ for (const { hit, tail } of configTails)
226
+ if (segs.length >= at.length + tail.length && tail.every((t, i) => segs[at.length + i] === t))
227
+ return hit;
228
+ }
229
+ return null;
230
+ };
231
+ const judgeSegments = (raw, configRoots) => {
214
232
  if (raw.length === 0)
215
233
  return null;
216
234
  const segs = raw.map(foldWriteProtectSegment);
@@ -229,23 +247,26 @@ export function compileWriteProtection(entries, opts) {
229
247
  return { name: run.name, kind: "segment-run" };
230
248
  }
231
249
  }
232
- return null;
250
+ return configTails.length === 0 ? null : judgeAtConfigRoots(segs, configRoots);
233
251
  };
234
- const judgeViews = (spelling) => {
235
- const posix = judgeSegments(pathViewSegments(spelling, "literal", POSIX_SEPARATOR_FAMILY, uncAnchorSegments(spelling)));
252
+ const judgeViews = (spelling, configRoots = configRootSpelled) => {
253
+ const posix = judgeSegments(pathViewSegments(spelling, "literal", POSIX_SEPARATOR_FAMILY, uncAnchorSegments(spelling)), configRoots);
236
254
  if (posix !== null)
237
255
  return posix;
238
- const bothSeparators = judgeSegments(pathViewSegments(spelling, "literal"));
256
+ const bothSeparators = judgeSegments(pathViewSegments(spelling, "literal"), configRoots);
239
257
  if (bothSeparators !== null)
240
258
  return bothSeparators;
241
259
  for (const normalization of WIN32_NORMALIZATIONS) {
242
- const hit = judgeSegments(pathViewSegments(spelling, normalization));
260
+ const hit = judgeSegments(pathViewSegments(spelling, normalization), configRoots);
243
261
  if (hit !== null)
244
262
  return hit;
245
263
  }
246
264
  return null;
247
265
  };
248
- const judgeSpelled = (spelled) => judgeViews(relativeUnderDataRoot(spelled, rootViews) ?? spelled);
266
+ const judgeSpelled = (spelled) => {
267
+ const remainder = relativeUnderDataRoot(spelled, rootViews);
268
+ return remainder === null ? judgeViews(spelled) : judgeViews(remainder, []);
269
+ };
249
270
  const judgeSpelling = (path, cwd) => {
250
271
  const base = typeof cwd === "string" && cwd.length > 0 ? cwd : undefined;
251
272
  const family = pathFamilyOf({ cwd: base });
@@ -291,7 +312,8 @@ export function compileWriteProtection(entries, opts) {
291
312
  }
292
313
  try {
293
314
  if (canonKey !== undefined) {
294
- const byTarget = judgeViews(canonKey);
315
+ const canonicalRoots = configTails.length === 0 || opts?.agentConfigDir === undefined ? [] : (await canonicalDataRootKeys(env, [opts.agentConfigDir], ctx.signal)).map(agentConfigDirSegments);
316
+ const byTarget = judgeViews(canonKey, [...configRootSpelled, ...canonicalRoots]);
295
317
  if (byTarget !== null) {
296
318
  for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
297
319
  const relative = relativeUnderRoot(canonKey, rootKey);
@@ -299,7 +321,7 @@ export function compileWriteProtection(entries, opts) {
299
321
  continue;
300
322
  if (relative === "")
301
323
  return { kind: "clear" };
302
- const remainder = judgeViews(relative);
324
+ const remainder = judgeViews(relative, []);
303
325
  return remainder === null ? { kind: "clear" } : { kind: "hit", hit: remainder, via: "target" };
304
326
  }
305
327
  return { kind: "hit", hit: byTarget, via: "target" };
@@ -325,7 +347,7 @@ export function compileWriteProtection(entries, opts) {
325
347
  },
326
348
  };
327
349
  }
328
- export function writeProtectionTightenDecision(toolName, verdict) {
350
+ export function writeProtectionTightenDecision(toolName, verdict, face) {
329
351
  if (verdict.kind === "hit") {
330
352
  return {
331
353
  action: "ask",
@@ -338,6 +360,15 @@ export function writeProtectionTightenDecision(toolName, verdict) {
338
360
  };
339
361
  }
340
362
  if (verdict.kind === "unresolvable") {
363
+ if (releasableAskClassesFor(face).includes("write_protection_unresolved")) {
364
+ return {
365
+ action: "ask",
366
+ message: verdict.unresolvedSymlink
367
+ ? `tool "${toolName}" writes to a symlink whose real target could not be resolved, so the write-protection judge cannot tell whether it lands on a protected path (${verdict.cause}) — explicit approval required`
368
+ : `tool "${toolName}" writes to a target that could not be resolved on this tree, so the write-protection judge cannot tell whether it lands on a protected path (${verdict.cause}) — explicit approval required`,
369
+ decisionReason: "safety",
370
+ };
371
+ }
341
372
  return {
342
373
  action: "deny",
343
374
  message: verdict.unresolvedSymlink
package/dist/index.d.ts CHANGED
@@ -151,7 +151,8 @@ export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES
151
151
  export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, type ToolCallLike, pathConfinableWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
152
152
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, type RosterSource, type ToolIdentity, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, rosterToolName, ruleNamesOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, type RosterBoundMembers, type RosterBoundCode, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
153
153
  export { type ToolCallFace } from "./core/tool-policy.js";
154
- export { effectivePathTargetOf, pathTargetBaseOf, type ToolPathBases, type PathTargetReading } from "./core/effective-path-target.js";
154
+ export { effectivePathTargetOf, pathTargetBaseOf, type ToolPathBases, type PathTargetReading, type CallTargetReading, type CallTargetView } from "./core/effective-path-target.js";
155
+ export { type BoundPathRule, type PathRuleSpec } from "./core/path-rule-binding.js";
155
156
  export { pathFamilyOf, isAbsoluteForFamily, pathIdentityOf, win32NamespaceScreen, WIN32_NAMESPACE_REFUSALS, type PathFamily, type PathIdentity, type PathIdentityReading, type Win32NamespaceRefusal } from "./tools/fs/safety.js";
156
157
  export { judgeShellRead, type ShellReadJudgeInput, type ShellReadJudgment, type ShellReadVerdict, type ShellReadStationSeat, type ShellReadAdmission, } from "./tools/fs/shell-read-check.js";
157
158
  export { SHELL_READ_FINDINGS, SHELL_READ_SEATS, SHELL_READ_DECISION, SHELL_READ_FINDING_RANK, type ShellReadFinding, type ShellReadSeat, type ShellReadDisposition, type ShellReadBoundary, type ReadEstablishment, type EstablishedRead, type ReadFinding, } from "./tools/fs/shell-read-judge.js";
@@ -193,21 +194,22 @@ export { createAllowDenyPolicy, createApprovalPolicy,
193
194
  * members that seat has no lane for (`"unavailable"`, a routing answer; `updatedInput`, an operator edit a
194
195
  * policy verdict cannot get re-adjudicated). Exported so a host can NAME it instead of deriving it through
195
196
  * `Parameters<typeof createApprovalPolicy>[0]["approve"]`. */
196
- type ApprovalSeatOutcome, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, abortedSeatDeny, receiveHookResult, isEngineRefusal, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, type ToolPolicyNameSets, type NamedToolPolicy, type ToolPolicyProjection, type ToolPolicyProjectionComponent, type ConstraintChainEntry, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, type ToolPolicy, type ToolCallRequest, type PermissionResult, type DecisionReason, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, type OnAsk, type AskOutcome, type ResolvedAsk, type AskRequest, type AskDelegationProvenance, type AskRuleEvidence, type AskEvidenceAbsence, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
197
+ type ApprovalSeatOutcome, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, abortedSeatDeny, receiveHookResult, isEngineRefusal, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathRules, type ToolPolicyNameSets, type NamedToolPolicy, type ToolPolicyProjection, type ToolPolicyProjectionComponent, type ConstraintChainEntry, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, type ToolPolicy, type ToolCallRequest, type PermissionResult, type DecisionReason, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, type OnAsk, type AskOutcome, type ResolvedAsk, type AskRequest, type AskDelegationProvenance, type AskRuleEvidence, type AskEvidenceAbsence, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
197
198
  export { type ReadRootGrantScreen } from "./core/read-root-grant-screen.js";
198
199
  export { type AskOrigin, ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, type PersistedRuleMandate, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
199
- export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, type ModeReleaseReason, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, type AskClassification, type AskClassFacts, type ToolMarkSource, arbitrateByMode, type Arbitration, fileCommandOptionsSentence } from "./core/ask-origin.js";
200
+ export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow, ASK_FACES, type AskFace, askFaceOf, releasableAskClassesFor, MODE_RELEASE_REASONS, type ModeReleaseReason, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, type AskClassification, type AskClassFacts, type ToolMarkSource, arbitrateByMode, type Arbitration, fileCommandOptionsSentence } from "./core/ask-origin.js";
200
201
  export { modePolicyOf, type ModePolicy, PERMISSION_MODE_FOLD_REASONS, type PermissionModeFoldReason, type PermissionModeReceipt, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
201
202
  export type { PermissionSeatInputs, PermissionSeatReading } from "./core/mode-policy.js";
202
203
  export type { SkillGateSeats, SkillGrantHit, SkillGrantLane, SkillGrantOrigin, SkillGrantRecord } from "./core/skill-grant.js";
203
204
  export { SKILL_GRANT_ORIGINS } from "./core/skill-grant.js";
204
205
  export type { BoundSkillGrants } from "./core/skill-grant-binding.js";
205
- export { ALLOW_ENTRY_INERT_REASONS, compileAllowEntry, compileAllowList, type AllowEntryInertReason, type CompiledAllowEntry, type AllowEntryGateSeats, type AllowEntryLane, type AllowEntryHit } from "./core/allow-entry.js";
206
+ export { ALLOW_ENTRY_INERT_REASONS, compileAllowEntry, compileAllowList, renderListEntry, type AllowEntryInertReason, type CompiledAllowEntry, type AllowEntryGateSeats, type AllowEntryLane, type AllowEntryHit } from "./core/allow-entry.js";
206
207
  export { ALLOW_RULE_MEMBERS, type AllowRuleMember, type AllowRuleShadow } from "./core/allow-rule-shadow.js";
207
208
  export { mintNamespacePrefix } from "./core/protocol-naming.js";
208
209
  export { readDenyClassifierRules, renderClassifierDenyRules, type DerivedDenyRule } from "./core/auto-mode-prompt.js";
209
210
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource, type AutoModeDenySource } from "./core/runtime-caps.js";
210
211
  export { SETTLEMENT_KINDS, type SettlementKind, isSettlementKind, type Settlement, SETTLEMENT_PARTIES, type SettlementParty, isSettlementParty, HOST_DECIDERS, type HostDecider, isHostDecider, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, type DeniedBy, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, type ClassifierDenyCause, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, type GateDisposition, type GateOutcome, type ModeRelease, type ModeRefusal, screenGateOutcome } from "./core/gate-outcome.js";
212
+ export { ENGINE_BUILTIN_TOOL_NAMES, isEngineBuiltinToolName } from "./core/tool-catalog-entries.js";
211
213
  export { policyCannotAnswer, type HeldQuestion } from "./core/real-approval.js";
212
214
  export { type AskCarry } from "./core/hooks.js";
213
215
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps, type SwappableDepSeat, type SwappableDeps } from "./core/swappable-deps.js";
package/dist/index.js CHANGED
@@ -124,6 +124,7 @@ export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRule
124
124
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, rosterToolName, ruleNamesOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
125
125
  export {} from "./core/tool-policy.js";
126
126
  export { effectivePathTargetOf, pathTargetBaseOf } from "./core/effective-path-target.js";
127
+ export {} from "./core/path-rule-binding.js";
127
128
  export { pathFamilyOf, isAbsoluteForFamily, pathIdentityOf, win32NamespaceScreen, WIN32_NAMESPACE_REFUSALS } from "./tools/fs/safety.js";
128
129
  export { judgeShellRead, } from "./tools/fs/shell-read-check.js";
129
130
  export { SHELL_READ_FINDINGS, SHELL_READ_SEATS, SHELL_READ_DECISION, SHELL_READ_FINDING_RANK, } from "./tools/fs/shell-read-judge.js";
@@ -158,18 +159,19 @@ export { hasScheduler, isValidCronExpr, SchedulerError } from "./core/scheduler.
158
159
  export { createSchedulerTools, SCHEDULE_WAKEUP_TOOL_NAME, AUTONOMOUS_LOOP_SENTINEL, AUTONOMOUS_LOOP_DYNAMIC_SENTINEL, } from "./tools/scheduler-tools.js";
159
160
  export { resolveAutonomousLoopPrompt, AUTONOMOUS_LOOP_PREAMBLE, AUTONOMOUS_LOOP_PREAMBLE_PERSISTENT, } from "./tools/loop-tick.js";
160
161
  export { tightenTaskSpec, TaskSpecTightenError } from "./core/tighten-task-spec.js";
161
- export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, abortedSeatDeny, receiveHookResult, isEngineRefusal, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
162
+ export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, abortedSeatDeny, receiveHookResult, isEngineRefusal, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathRules, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
162
163
  export {} from "./core/read-root-grant-screen.js";
163
164
  export { ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
164
- export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, arbitrateByMode, fileCommandOptionsSentence } from "./core/ask-origin.js";
165
+ export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION, ASK_FACES, askFaceOf, releasableAskClassesFor, MODE_RELEASE_REASONS, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, arbitrateByMode, fileCommandOptionsSentence } from "./core/ask-origin.js";
165
166
  export { modePolicyOf, PERMISSION_MODE_FOLD_REASONS, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
166
167
  export { SKILL_GRANT_ORIGINS } from "./core/skill-grant.js";
167
- export { ALLOW_ENTRY_INERT_REASONS, compileAllowEntry, compileAllowList } from "./core/allow-entry.js";
168
+ export { ALLOW_ENTRY_INERT_REASONS, compileAllowEntry, compileAllowList, renderListEntry } from "./core/allow-entry.js";
168
169
  export { ALLOW_RULE_MEMBERS } from "./core/allow-rule-shadow.js";
169
170
  export { mintNamespacePrefix } from "./core/protocol-naming.js";
170
171
  export { readDenyClassifierRules, renderClassifierDenyRules } from "./core/auto-mode-prompt.js";
171
172
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./core/runtime-caps.js";
172
173
  export { SETTLEMENT_KINDS, isSettlementKind, SETTLEMENT_PARTIES, isSettlementParty, HOST_DECIDERS, isHostDecider, SETTLEMENT_IS_REFUSAL, DENIED_BY_VALUES, isDeniedBy, DENIED_BY_MAY_VETO, CLASSIFIER_DENY_CAUSES, isClassifierDenyCause, DENIED_BY_MAY_CARRY_CAUSE, screenGateOutcome } from "./core/gate-outcome.js";
174
+ export { ENGINE_BUILTIN_TOOL_NAMES, isEngineBuiltinToolName } from "./core/tool-catalog-entries.js";
173
175
  export { policyCannotAnswer } from "./core/real-approval.js";
174
176
  export {} from "./core/hooks.js";
175
177
  export { SWAPPABLE_DEP_SEATS, SWAP_SEAT_NOTICE, isSwappableDepSeat, screenSwappableDeps } from "./core/swappable-deps.js";
@@ -5,7 +5,8 @@ import { combinePolicies } from "../core/tool-policy.js";
5
5
  import { delimitUntrusted } from "../core/untrusted-text.js";
6
6
  import { isDurablePause } from "../agents/suspend-guard.js";
7
7
  import { terminalProjection } from "../core/runner/terminal-projection.js";
8
- import { isAbsoluteForFamily, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
8
+ import { writeTargetPath } from "../tools/fs/safety.js";
9
+ import { callTargetViewsOf } from "../core/effective-path-target.js";
9
10
  import { captureBaseline, resolveFrozenPaths, restoreFrozenPaths, runOracle, snapshotFrozenPaths, specError, } from "../core/spec-contract.js";
10
11
  import { isProtectedWrite } from "../core/tool-registry.js";
11
12
  const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
@@ -21,13 +22,13 @@ export function frozenDenyPolicy(rootDir, frozenResolved) {
21
22
  check(req) {
22
23
  if (!isProtectedWrite(req))
23
24
  return { action: "allow" };
24
- const raw = writeTargetPath(req);
25
- if (typeof raw !== "string" || raw.length === 0)
25
+ const views = callTargetViewsOf(req, "tighten", { root: rootDir });
26
+ if (views === undefined || views.spellings.length === 0)
26
27
  return { action: "allow" };
27
- const absForm = isAbsoluteForFamily(pathFamilyOf({ root: rootDir, cwd: req.cwd }), raw);
28
- const abs = pathKey(absForm ? raw : resolve(req.cwd || rootDir, raw));
29
- if (frozen.has(abs)) {
30
- return { action: "deny", message: `${raw} is part of the frozen specification surface (read-only).` };
28
+ const raw = writeTargetPath(req);
29
+ const hit = views.spellings.some((v) => frozen.has(pathKey(v)));
30
+ if (hit) {
31
+ return { action: "deny", message: `${typeof raw === "string" && raw.length > 0 ? raw : views.spellings[0]} is part of the frozen specification surface (read-only).` };
31
32
  }
32
33
  return { action: "allow" };
33
34
  },
@@ -1579,7 +1579,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1579
1579
  opts.onForwardEvent(e.type === "task_progress" ? { ...e, workflowRunId: runId, workflowAgentLabel: label } : e);
1580
1580
  }
1581
1581
  : undefined;
1582
- const baseInternals = { ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}), ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd } : {}), ...spawnAttribution, ...(await captureFloorSeatsNow()), ...(enrichedForward !== undefined ? { onForwardEvent: enrichedForward } : {}), delegationTaskType: "workflow", agentName: label, onWorkspaceResolved: createWorkspaceObserver(rec) };
1582
+ const baseInternals = { ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}), ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd, callerRoot: opts.parentCwd } : {}), ...spawnAttribution, ...(await captureFloorSeatsNow()), ...(enrichedForward !== undefined ? { onForwardEvent: enrichedForward } : {}), delegationTaskType: "workflow", agentName: label, onWorkspaceResolved: createWorkspaceObserver(rec) };
1583
1583
  let attempts = 0;
1584
1584
  let throttleRetried = false;
1585
1585
  let lastAttemptReason;
@@ -103,6 +103,17 @@ export interface WordExpansion {
103
103
  readonly body?: string;
104
104
  /** The parameter name, where the expansion names one. */
105
105
  readonly name?: string;
106
+ /** A glob's pattern — absent where an expansion stands inside it (its text is not the pattern): `folded`, what the
107
+ * program receives where it matches nothing — quotes removed, the spelling {@link LexedWord.folded} carries at its place
108
+ * (`[^.]`); `written`, as written, quoting kept (`[^\.]`, `["!"a]`) — the text a shell re-reading it matches by;
109
+ * `judged`, the spelling a reader judges the names it matches by — `folded`, save a class the judge reads as any one
110
+ * character: one holding a quoted or escaped character (its quote-removed text reads another class: `["!"a]` is not
111
+ * the negation `[!a]`) or a `[:…:]` / `[=…=]` / `[.….]` unit — spelled `?`, a superset of its members. */
112
+ readonly pattern?: {
113
+ readonly folded: string;
114
+ readonly written: string;
115
+ readonly judged: string;
116
+ };
106
117
  }
107
118
  export interface QuotedSpan {
108
119
  /** Offsets into {@link LexedWord.raw}, `[start, end)`; the quote characters belong to their span. */
@@ -143,6 +154,10 @@ export interface LexedWord {
143
154
  readonly kind: "word";
144
155
  readonly raw: string;
145
156
  readonly folded: string;
157
+ /** The spelling a reader judges the word's names by, where it is not `folded`: each bracket class holding a quoted or
158
+ * escaped character or a `[:…:]` / `[=…=]` / `[.….]` unit spelled `?` (`["!"a]*`, `[[:alpha:]]*` ⇒ `?*`,
159
+ * {@link WordExpansion.pattern}). */
160
+ readonly judged?: string;
146
161
  readonly pieces: readonly WordPiece[];
147
162
  readonly quotedSpans: readonly QuotedSpan[];
148
163
  /** The pieces that carry an expansion, in order. */
@@ -248,6 +263,13 @@ export declare function substitutionsInHeredocBody(body: string): {
248
263
  readonly body: string;
249
264
  readonly kind: "command" | "arithmetic";
250
265
  }[] | undefined;
266
+ /** The text a word's pieces hand the program when every piece is literal; `undefined` when one is an expansion or
267
+ * unresolved. */
268
+ export declare function literalTextOf(pieces: readonly WordPiece[]): string | undefined;
269
+ /** The text an UNQUOTED here-document body delivers when the shell expands nothing in it: a backslash before `$`, a
270
+ * backquote or a backslash is removed (a line continuation was joined when the body was read), every other character
271
+ * stands. `undefined` when an unescaped `$` or backquote stands in it: the program reads what the expansion makes. */
272
+ export declare function literalHeredocBody(body: string): string | undefined;
251
273
  /** Lex a command: its segments, or the one reason bash (or this lexer) cannot delimit them. */
252
274
  export declare function lexShell(source: string): ShellLex;
253
275
  /**
package/dist/shell/lex.js CHANGED
@@ -319,6 +319,23 @@ export function substitutionsInHeredocBody(body) {
319
319
  }
320
320
  return out;
321
321
  }
322
+ export function literalTextOf(pieces) {
323
+ return pieces.every((q) => q.literal !== undefined) ? pieces.map((q) => q.literal).join("") : undefined;
324
+ }
325
+ export function literalHeredocBody(body) {
326
+ let out = "";
327
+ for (let i = 0; i < body.length; i++) {
328
+ const ch = body[i];
329
+ if (ch === "$" || ch === "`")
330
+ return undefined;
331
+ if (ch === "\\" && i + 1 < body.length && "$`\\".includes(body[i + 1])) {
332
+ out += body[++i];
333
+ continue;
334
+ }
335
+ out += ch;
336
+ }
337
+ return out;
338
+ }
322
339
  const BLANK = /[ \t]/;
323
340
  const fail = (reason, at) => ({ parseFailure: { reason, at } });
324
341
  const publicPiece = (p) => ({
@@ -340,6 +357,7 @@ class WordBuilder {
340
357
  marks = [];
341
358
  subscript;
342
359
  compound = false;
360
+ widenedClasses = [];
343
361
  begin(src) { if (!this.started)
344
362
  this.at = src; }
345
363
  push(w, f, by, src) {
@@ -385,6 +403,17 @@ class WordBuilder {
385
403
  mark(reason, rawAt) { this.marks.push({ at: rawAt, reason }); }
386
404
  inSubscript() { return this.subscript !== undefined && this.subscript.rawEnd === undefined; }
387
405
  foldedOf(r) { return r >= this.foldedAt.length ? this.folded.length : this.foldedAt[r]; }
406
+ classSpelled(from, to) {
407
+ let out = "";
408
+ let at = from;
409
+ for (const [a, b] of this.widenedClasses) {
410
+ if (a < at || b > to)
411
+ continue;
412
+ out += `${this.folded.slice(at, a)}?`;
413
+ at = b;
414
+ }
415
+ return out + this.folded.slice(at, to);
416
+ }
388
417
  slice(from, to) {
389
418
  const out = [];
390
419
  for (const p of this.pieces) {
@@ -502,9 +531,23 @@ class WordBuilder {
502
531
  else if (stack.length > 0 && (d === "," || (d === "." && this.raw[j + 1] === ".")))
503
532
  stack[stack.length - 1].listed = true;
504
533
  }
505
- const nextBracketClose = new Array(n + 1).fill(-1);
506
- for (let j = n - 1; j >= 0; j--)
507
- nextBracketClose[j] = readable(j) && this.raw[j] === "]" ? j : nextBracketClose[j + 1];
534
+ const text = this.folded;
535
+ const nf = text.length;
536
+ const readableAt = (f) => f < nf && readable(this.rawOfFolded[f]);
537
+ const unitClose = (d) => {
538
+ const at = new Array(nf + 2).fill(-1);
539
+ for (let f = nf - 2; f >= 0; f--)
540
+ at[f] = readableAt(f) && readableAt(f + 1) && text[f] === d && text[f + 1] === "]" ? f : at[f + 1];
541
+ return at;
542
+ };
543
+ const unitCloses = new Map([":", "=", "."].map((d) => [d, unitClose(d)]));
544
+ const nextBracketClose = new Array(nf + 2).fill(-1);
545
+ const nextSlash = new Array(nf + 1).fill(nf);
546
+ for (let f = nf - 1; f >= 0; f--) {
547
+ const unitEnd = readableAt(f) && text[f] === "[" && readableAt(f + 1) ? unitCloses.get(text[f + 1] ?? "")?.[f + 2] ?? -1 : -1;
548
+ nextBracketClose[f] = readableAt(f) && text[f] === "]" ? f : unitEnd >= 0 ? nextBracketClose[unitEnd + 2] : nextBracketClose[f + 1];
549
+ nextSlash[f] = text[f] === "/" ? f : nextSlash[f + 1];
550
+ }
508
551
  const ranges = [];
509
552
  for (let k = 0; k < n; k++) {
510
553
  if (!readable(k))
@@ -519,19 +562,35 @@ class WordBuilder {
519
562
  continue;
520
563
  }
521
564
  if (c === "*" || c === "?") {
522
- ranges.push({ from: k, to: k + 1, exp: { kind: "glob", yields: "many" } });
565
+ ranges.push({ from: k, to: k + 1, exp: { kind: "glob", yields: "many", pattern: { folded: c, written: c, judged: c } } });
523
566
  continue;
524
567
  }
525
568
  if (c === "[") {
526
- let j = k + 1;
527
- if (this.raw[j] === "!" || this.raw[j] === "^")
569
+ if (this.rawOfFolded[this.foldedOf(k)] !== k)
570
+ continue;
571
+ let j = this.foldedOf(k) + 1;
572
+ if ((text[j] === "!" || text[j] === "^") && readableAt(j))
528
573
  j++;
529
- if (this.raw[j] === "]")
574
+ if (text[j] === "]" && readableAt(j))
530
575
  j++;
531
- const close = j < n ? nextBracketClose[j] : -1;
532
- if (close < 0)
576
+ const closeAt = j < nf ? nextBracketClose[j] : -1;
577
+ if (closeAt < 0 || nextSlash[j] < closeAt)
533
578
  continue;
534
- ranges.push({ from: k, to: close + 1, exp: { kind: "glob", yields: "many", body: this.raw.slice(k + 1, close) } });
579
+ const close = this.rawOfFolded[closeAt];
580
+ let expands = false;
581
+ for (let m = k + 1; m < close && !expands; m++)
582
+ expands = inExp[m] || braceClose.get(m)?.listed === true;
583
+ let widened = false;
584
+ for (let m = k + 1; m < close && !widened; m++)
585
+ widened = this.rawQuoted[m] !== undefined;
586
+ for (let f = j; f < closeAt && !widened; f++)
587
+ widened = readableAt(f) && text[f] === "[" && readableAt(f + 1) && (unitCloses.get(text[f + 1] ?? "")?.[f + 2] ?? -1) >= 0;
588
+ const from = this.foldedOf(k);
589
+ const to = this.foldedOf(close + 1);
590
+ if (!expands && widened)
591
+ this.widenedClasses.push([from, to]);
592
+ const pattern = expands ? {} : { pattern: { folded: this.folded.slice(from, to), written: this.raw.slice(k, close + 1), judged: widened ? "?" : this.folded.slice(from, to) } };
593
+ ranges.push({ from: k, to: close + 1, exp: { kind: "glob", yields: "many", body: this.raw.slice(k + 1, close), ...pattern } });
535
594
  k = close;
536
595
  }
537
596
  }
@@ -630,18 +689,20 @@ class WordBuilder {
630
689
  }
631
690
  finish(atCommandPosition, tildeSpelling = true) {
632
691
  const rawText = this.raw;
633
- let foldedText = this.folded;
692
+ let literalTilde = false;
634
693
  let assignment;
635
694
  const tildeEnd = this.tildePrefixEnd(0, false);
636
695
  if (tildeEnd !== undefined)
637
696
  this.carveTilde(0, tildeEnd);
638
697
  else {
639
- if (tildeSpelling)
640
- foldedText = literalTildeSpelling(foldedText);
698
+ literalTilde = tildeSpelling;
641
699
  assignment = this.readAssignment();
642
700
  }
643
701
  if (!(assignment !== undefined && assignment.bare && atCommandPosition))
644
702
  this.carveBraceAndGlob();
703
+ const spelled = (text) => (literalTilde ? literalTildeSpelling(text) : text);
704
+ const foldedText = spelled(this.folded);
705
+ const judged = this.widenedClasses.length > 0 ? spelled(this.classSpelled(0, this.folded.length)) : undefined;
645
706
  if (assignment !== undefined && this.subscript?.text)
646
707
  this.mark("subscript_text", this.subscript.rawStart);
647
708
  const marks = this.marks.filter((m) => m.reason !== "subscript_text" || assignment?.subscript !== undefined).sort((a, b) => a.at - b.at);
@@ -662,6 +723,7 @@ class WordBuilder {
662
723
  kind: "word",
663
724
  raw: rawText,
664
725
  folded: foldedText,
726
+ ...(judged !== undefined ? { judged } : {}),
665
727
  pieces,
666
728
  quotedSpans: spans,
667
729
  expansions: pieces.filter((p) => p.expansion !== undefined),
@@ -31,8 +31,6 @@ export declare function writtenAssignmentPrefix(token: string): {
31
31
  readonly settlesWholeName: boolean;
32
32
  } | undefined;
33
33
  export { SHELL_CONTROL_PREFIXES };
34
- /** Shell block words that name no command of their own (a loop head, a block close, a case head). */
35
- export declare const SHELL_BLOCK_WORDS: ReadonlySet<string>;
36
34
  /** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program
37
35
  * looked up on PATH, whose failure changes nothing), so a quoted or escaped spelling never counts as one. */
38
36
  export declare function isBareControlWord(words: readonly LexedWord[], i: number, set: ReadonlySet<string>): boolean;
@@ -43,88 +41,6 @@ export declare function isBareControlWord(words: readonly LexedWord[], i: number
43
41
  * over control words and earlier assignments re-traces). One fact, two readers that must agree.
44
42
  */
45
43
  export declare function isLeadingAssignmentWord(words: readonly LexedWord[], i: number): boolean;
46
- /**
47
- * Programs that turn their STANDARD INPUT into the OPERANDS of another program, and EXEC that program directly.
48
- *
49
- * The same class as the shell re-entry programs, one level down: there the unseen text is COMMANDS; here the
50
- * command's name IS visible and its argv is not — `echo <path> | xargs cat` opens a file no token of this walk
51
- * ever named, because the operand arrives on a stream. The membership criterion is that behaviour and only that
52
- * behaviour; a program that consumes stdin as DATA (`sort`, `wc`, a reader the deployment never listed) is NOT a
53
- * member, because its stdin never becomes a filename. Spelled as a pattern — these are program names, never tool
54
- * names.
55
- *
56
- * "EXECS DIRECTLY" is what separates this class from the shell re-entry class, and it is why GNU `parallel` is
57
- * NOT a member: parallel BUILDS A COMMAND LINE and hands it to a shell, and with no command word at all it runs
58
- * each input line as a command — so parallel belongs to the shell re-entry class and is refused whole with no
59
- * exemption at all. A spelling this file has no name for (an aliased or wrapped `xargs`) is judged as any other
60
- * unlisted program is — the name-table floor the re-entry class records for itself.
61
- */
62
- export declare const ARGV_REENTRY_PROGRAM_RE: RegExp;
63
- /** The program an {@link ARGV_REENTRY_PROGRAM_RE} launcher runs when the chain names none: `xargs` defaults to
64
- * `echo`, which is why a bare one reads nothing. */
65
- export declare const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
66
- /**
67
- * What a launcher declares about its OWN options: each exact spelling with its ARITY — `0` for a switch, `1` for an
68
- * option whose value is the next token. Every spelling not in the map falls to the refusal (the conservative side,
69
- * and the verdict those spellings always had); a `1` with no token left to take is not readable either.
70
- *
71
- * THREE SPELLINGS OF ONE DECLARATION, because getopt has three: an arity-1 entry is honoured separated (`-k 5`),
72
- * fused to its long name (`--signal=TERM`) and fused to its short letter (`-k5`), and an arity-0 short entry inside
73
- * a cluster of arity-0 letters (`-pv`). Reading only the exact spelling made one program two.
74
- *
75
- * WHAT A ROW MAY NOT GET WRONG: declaring an arity-1 option as arity 0 steps onto its VALUE and reads the value as
76
- * the program — the one direction that turns a refusal into a wrong answer rather than a lost one. A row carries
77
- * only options whose arity every supported implementation agrees on; a flag no row declares still refuses.
78
- */
79
- export interface LauncherRow {
80
- /** Consulted ONLY for the launcher the command line itself names — a bare keyword in front of the command
81
- * (`time -p …`) or the written command word (`"time" -p …`, `X=1 time -p …`, `/usr/bin/time -p …`). A launcher
82
- * nested behind another (`env time -p …`) is that launcher's operand, and its options refuse: resolving past
83
- * them would turn a whole refusal into an ordinary walked segment — a widening, where every other read of this
84
- * table only ever finds MORE program to judge. */
85
- readonly optionArity: ReadonlyMap<string, 0 | 1>;
86
- /** A bare NEGATIVE integer in this launcher's option section is its OWN argument (`nice -5 <cmd>`), not a flag
87
- * the table cannot read. Per row: elsewhere a dash-leading token is an option, and reading one as a number
88
- * would step over a flag whose meaning this table does not know. */
89
- readonly negativeIntegerIsOwnArgument?: true;
90
- /** Positional operands the launcher takes BEFORE the program, consumed whatever they spell (`timeout DURATION
91
- * COMMAND`); a spelling-based reading under-accepted, and every spelling it missed became the program word. */
92
- readonly requiredOperands?: number;
93
- /** The launcher reads NAME=VALUE operands with ITS OWN name grammar (`env 1=x …`), wider than the shell's. */
94
- readonly assignmentOperands?: true;
95
- /** The launcher hands its operands to a SHELL as text (`watch "cat /x"` runs `sh -c`): no program word to read. */
96
- readonly shellText?: true;
97
- /** With no program word the launcher runs a SHELL: piped text would reach it, so an empty program position refuses. */
98
- readonly whenNoProgram?: "shell";
99
- /** Options whose value is a WHOLE COMMAND LINE the launcher splits and runs (`env -S "cmd arg"`,
100
- * `--split-string=…`). Not an arity this reader steps over — the program is inside the value, so the
101
- * program-position walk still refuses here — but the candidate extraction reads the value as nested shell
102
- * text instead of as one opaque word. */
103
- readonly commandString?: readonly string[];
104
- }
105
- /**
106
- * The ONE launcher truth: programs that RUN ANOTHER PROGRAM named in ordinary argv tokens, so a declared reader can
107
- * sit behind one (`env cat <path>`, `sudo cat <path>`, `timeout 5 cat <path>`), each with the option grammar this
108
- * file reads for it.
109
- *
110
- * Membership is the key set. A row with no declared options means every option-shaped word after that launcher
111
- * refuses ("which program runs is not readable without the launcher's option table") — the answer every launcher
112
- * but `time` has always had, now stated per row rather than by omission. Declaring a row's options is a behaviour
113
- * decision with its own criteria; adding a launcher is a row.
114
- *
115
- * `time` carries both spellings a deployment can meet: the shell builtin takes `-p` alone, and the standalone
116
- * program adds a format, an output file, and three switches.
117
- *
118
- * A deliberate SUBSET of the rule lane's own `BARE_INTERPRETER_NAMES`, mirrored rather than imported because that
119
- * module imports the classifier — the dependency runs that way, and a cycle is a worse problem than a mirror whose
120
- * subset relation is pinned by test. GNU `parallel` is absent for the reason the shells are absent: it does not run
121
- * a program named in token-visible argv — it BUILDS a command line and hands it to a shell — so it is a shell
122
- * re-entry member instead. Language interpreters and shells are excluded as a scope statement: their program
123
- * argument is a script path or a quoted code string, not a token-visible program with token-visible operands.
124
- */
125
- export declare const LAUNCHER_TABLE: ReadonlyMap<string, LauncherRow>;
126
- /** The launcher names — derived from the table, never kept beside it. */
127
- export declare const COMMAND_LAUNCHERS: ReadonlySet<string>;
128
44
  export declare const PROGRAM_POSITION_REFUSAL_KINDS: readonly ["option_at_program_position", "control_syntax_at_program_position"];
129
45
  export type ProgramPositionRefusalKind = (typeof PROGRAM_POSITION_REFUSAL_KINDS)[number];
130
46
  /** Why the program could not be read — a closed set, each member minted into one sentence. `owner` is the word
@@ -137,9 +53,15 @@ export type ProgramPositionRefusal = {
137
53
  readonly kind: "control_syntax_at_program_position";
138
54
  readonly owner: string;
139
55
  readonly word: string;
140
- /** Not a DECLINE ({@link programPositionRunsShell}): the row WAS read and a SHELL stands at the program
141
- * position. Absent ⇒ genuine control syntax, which IS one. */
142
- readonly runsShell?: true;
56
+ /** Not a DECLINE ({@link programPositionRunsShell}): the row WAS read and a SHELL — or a program the launcher
57
+ * names by an option of its own (`su -s PROGRAM`) — stands at the program position, run by the launcher at argv
58
+ * index `launcher` and handed the words from index `from` on (none where `from` is the argv's end: `sudo -s`,
59
+ * `chroot DIR`), as that launcher's row composes them (`launchedCommands`, shell-reentry-text.ts). Absent ⇒
60
+ * genuine control syntax, which IS one. */
61
+ readonly runsShell?: {
62
+ readonly launcher: number;
63
+ readonly from: number;
64
+ };
143
65
  };
144
66
  export interface ProgramPosition {
145
67
  /** Indices of the bare control words stripped in front (`!`, `if`, `time`, …), in order; their declared options
@@ -159,6 +81,8 @@ export interface ProgramPosition {
159
81
  readonly argvFromStdin: boolean;
160
82
  /** The chain ends ON the re-entry program (nothing follows it), so it runs its default program. */
161
83
  readonly runsDefaultProgram: boolean;
84
+ /** The program the stream-fed chain member runs when the chain names none (`xargs` runs `echo`). */
85
+ readonly defaultProgram?: string;
162
86
  /** The program bash RUNS: the first word past the chain. `name` is the folded word as written, `basename` what
163
87
  * bash resolves (`/usr/bin/cat` is `cat`). `undefined` when the chain names none or the read was refused. */
164
88
  readonly program: {
@@ -184,7 +108,7 @@ export declare function readProgramPosition(words: readonly LexedWord[]): Progra
184
108
  /** Does this program position hand a program operands the walk cannot see? True when the chain re-enters through
185
109
  * stdin AND the program it runs is not one the caller accepts as a stream target. ONE reading, shared by every
186
110
  * consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */
187
- export declare function argvReentryUnjudged(pos: ProgramPosition, acceptedTargets: ReadonlySet<string>): boolean;
111
+ export declare function argvReentryUnjudged(pos: ProgramPosition, acceptsTarget: (basename: string) => boolean): boolean;
188
112
  /**
189
113
  * The two ways this reader DECLINES rather than judges — a CLOSED set (`docs/CLOSED-SETS.md`), because what a
190
114
  * seat does with a declined segment is a ROW of the shell read decision table (`shell-read-judge.ts`, the
@@ -197,9 +121,9 @@ export type ShellScanDecline = (typeof SHELL_SCAN_DECLINES)[number];
197
121
  /** Does a SHELL stand where this segment's program would be — as the program itself (`sh -c`), or as a launcher
198
122
  * ROW handing its operands to one (`watch "…"`)? ONE predicate for the re-entry class, so the two spellings
199
123
  * cannot be answered differently; which names are shells is the caller's declaration. Not a decline. */
200
- export declare function programPositionRunsShell(pos: ProgramPosition, shellProgram: RegExp): boolean;
124
+ export declare function programPositionRunsShell(pos: ProgramPosition, runsShell: (basename: string) => boolean): boolean;
201
125
  /** Which decline, if any, this program position raises — ONE reading of the two facts above, so no consumer
202
126
  * re-derives either. A refusal that says a SHELL runs is NOT one: it names what runs. */
203
- export declare function shellScanDeclineOf(pos: ProgramPosition, acceptedStreamTargets: ReadonlySet<string>): ShellScanDecline | undefined;
127
+ export declare function shellScanDeclineOf(pos: ProgramPosition, acceptsStreamTarget: (basename: string) => boolean): ShellScanDecline | undefined;
204
128
  /** ONE sentence per refusal kind, minted here and nowhere else. */
205
129
  export declare function programPositionRefusalSentence(refusal: ProgramPositionRefusal): string;