@sema-agent/core 7.52.0 → 7.54.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.
- package/CHANGELOG.md +470 -0
- package/LICENSE +3 -1
- package/README.md +3 -1
- package/dist/agents/cross-session-envelope.d.ts +8 -0
- package/dist/agents/cross-session-envelope.js +6 -0
- package/dist/agents/cross-session-judge.d.ts +21 -1
- package/dist/agents/cross-session-judge.js +8 -0
- package/dist/agents/cross-session-ref.d.ts +5 -0
- package/dist/agents/cross-session-ref.js +1 -0
- package/dist/agents/org-delivery-gate.d.ts +122 -0
- package/dist/agents/org-delivery-gate.js +69 -0
- package/dist/agents/peer-held-queue.d.ts +9 -4
- package/dist/agents/peer-held-queue.js +6 -5
- package/dist/agents/peer-session-drain.d.ts +35 -6
- package/dist/agents/peer-session-drain.js +271 -182
- package/dist/agents/read-inbox-tool.d.ts +7 -0
- package/dist/agents/read-inbox-tool.js +5 -1
- package/dist/agents/send-message-tool.js +3 -4
- package/dist/agents/subagent.d.ts +6 -5
- package/dist/agents/subagent.js +117 -83
- package/dist/agents/tool-filter.d.ts +25 -4
- package/dist/agents/tool-filter.js +13 -3
- package/dist/brain/anthropic.d.ts +6 -1
- package/dist/brain/anthropic.js +4 -4
- package/dist/brain/model-presets.js +2 -1
- package/dist/brain/route-adjudicator.d.ts +6 -0
- package/dist/brain/routing.d.ts +24 -1
- package/dist/brain/routing.js +54 -19
- package/dist/brain/timeout.js +5 -4
- package/dist/core/a2a.d.ts +5 -2
- package/dist/core/a2a.js +33 -14
- package/dist/core/allow-entry.d.ts +3 -1
- package/dist/core/allow-entry.js +3 -2
- package/dist/core/approval-object.d.ts +87 -0
- package/dist/core/approval-object.js +85 -0
- package/dist/core/arg-summary.d.ts +3 -2
- package/dist/core/arg-summary.js +17 -11
- package/dist/core/ask-origin.d.ts +43 -27
- package/dist/core/ask-origin.js +20 -11
- package/dist/core/ask-question.d.ts +0 -1
- package/dist/core/ask-question.js +4 -67
- package/dist/core/ask-unresolvable-notice.d.ts +3 -2
- package/dist/core/auto-compaction.d.ts +106 -50
- package/dist/core/auto-compaction.js +69 -53
- package/dist/core/auto-mode-prompt.d.ts +4 -2
- package/dist/core/auto-mode-prompt.js +4 -3
- package/dist/core/canonical-json.d.ts +69 -0
- package/dist/core/canonical-json.js +130 -8
- package/dist/core/checkpoint-requirements.d.ts +13 -7
- package/dist/core/checkpoint-requirements.js +4 -1
- package/dist/core/checkpoint-store.d.ts +60 -36
- package/dist/core/checkpoint-store.js +7 -6
- package/dist/core/child-clamp-seats.d.ts +45 -6
- package/dist/core/child-clamp-seats.js +56 -4
- package/dist/core/compliance.js +3 -1
- package/dist/core/consolidate-scope.d.ts +1 -1
- package/dist/core/cross-boundary-taint.d.ts +213 -0
- package/dist/core/cross-boundary-taint.js +196 -0
- package/dist/core/decision-receipt.d.ts +44 -1
- package/dist/core/decision-receipt.js +16 -0
- package/dist/core/effective-path-target.d.ts +28 -6
- package/dist/core/effective-path-target.js +28 -2
- package/dist/core/engine-notice.d.ts +47 -4
- package/dist/core/engine-notice.js +17 -1
- package/dist/core/engine-storage-backend.d.ts +73 -0
- package/dist/core/engine-storage-backend.js +1 -0
- package/dist/core/file-history-store.d.ts +6 -2
- package/dist/core/file-history-store.js +1 -0
- package/dist/core/gate-fold.js +13 -4
- package/dist/core/gate-lanes.js +41 -34
- package/dist/core/gate-outcome.d.ts +18 -9
- package/dist/core/governance-codes.d.ts +9 -2
- package/dist/core/governance-codes.js +9 -0
- package/dist/core/hands-band-screen.d.ts +7 -0
- package/dist/core/hands-band-screen.js +32 -0
- package/dist/core/hooks.d.ts +21 -19
- package/dist/core/hooks.js +6 -5
- package/dist/core/locked-config.d.ts +110 -31
- package/dist/core/locked-config.js +155 -42
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mcp-probe.js +1 -1
- package/dist/core/mcp-server-spec.d.ts +152 -17
- package/dist/core/mcp-server-spec.js +223 -40
- package/dist/core/mcp.d.ts +21 -14
- package/dist/core/mcp.js +59 -54
- package/dist/core/memory-engine/engine.d.ts +14 -19
- package/dist/core/memory-engine/engine.js +9 -6
- package/dist/core/memory-engine/export-bundle.d.ts +1 -1
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/file-backend.d.ts +2 -6
- package/dist/core/memory-engine/file-backend.js +1 -12
- package/dist/core/memory.d.ts +3 -3
- package/dist/core/mode-policy.d.ts +16 -26
- package/dist/core/mode-policy.js +5 -18
- package/dist/core/mods/kernel/dispatch.js +13 -15
- package/dist/core/mods/kernel/events.d.ts +9 -6
- package/dist/core/mods/kernel/events.js +2 -2
- package/dist/core/mods/kernel/receive.d.ts +41 -11
- package/dist/core/mods/kernel/receive.js +124 -54
- package/dist/core/mods/kernel/site-rules.d.ts +80 -15
- package/dist/core/mods/kernel/site-rules.js +232 -40
- package/dist/core/mods/stations.d.ts +0 -6
- package/dist/core/mods/stations.js +0 -7
- package/dist/core/mounted-mcp.d.ts +57 -0
- package/dist/core/mounted-mcp.js +71 -2
- package/dist/core/own-property-receipt.d.ts +13 -0
- package/dist/core/own-property-receipt.js +3 -1
- package/dist/core/parent-spec-seat.d.ts +12 -15
- package/dist/core/parent-spec-seat.js +14 -6
- package/dist/core/pause-registry.d.ts +35 -1
- package/dist/core/pause-registry.js +10 -0
- package/dist/core/permission-rule-consent.d.ts +18 -5
- package/dist/core/permission-rule-consent.js +13 -8
- package/dist/core/permission-rule-model.d.ts +87 -15
- package/dist/core/permission-rule-model.js +87 -8
- package/dist/core/permission-rule-org.d.ts +200 -33
- package/dist/core/permission-rule-org.js +217 -91
- package/dist/core/permission-rule-session.js +2 -2
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +1 -0
- package/dist/core/permission-rule-sync.js +3 -2
- package/dist/core/permission-rules.d.ts +7 -1
- package/dist/core/permission-rules.js +37 -8
- package/dist/core/read-only-shell.d.ts +1 -1
- package/dist/core/read-only-shell.js +30 -9
- package/dist/core/remote-env.d.ts +41 -1
- package/dist/core/remote-env.js +18 -0
- package/dist/core/resume-startup-cause.d.ts +18 -0
- package/dist/core/resume-startup-cause.js +12 -0
- package/dist/core/retention.js +7 -2
- package/dist/core/retired-keys.d.ts +8 -2
- package/dist/core/retired-keys.js +12 -1
- package/dist/core/roles.js +60 -10
- package/dist/core/rule-lane-reads.js +1 -1
- package/dist/core/runner/approval-feedback.d.ts +46 -0
- package/dist/core/runner/approval-feedback.js +41 -0
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/compaction-call-options.js +2 -2
- package/dist/core/runner/contracts.d.ts +34 -41
- package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
- package/dist/core/runner/cross-boundary-lane.js +190 -0
- package/dist/core/runner/decide-continuation.d.ts +4 -4
- package/dist/core/runner/decide-continuation.js +3 -1
- package/dist/core/runner/gate-exit.d.ts +32 -12
- package/dist/core/runner/gate-exit.js +77 -34
- package/dist/core/runner/inherited-ask-grants.js +2 -2
- package/dist/core/runner/mcp-redial.d.ts +52 -10
- package/dist/core/runner/mcp-redial.js +38 -9
- package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
- package/dist/core/runner/permission-rule-lanes.js +22 -10
- package/dist/core/runner/person-path-rows.d.ts +5 -3
- package/dist/core/runner/person-path-rows.js +2 -2
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -4
- package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
- package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
- package/dist/core/runner/prepare-config-doors.d.ts +58 -15
- package/dist/core/runner/prepare-config-doors.js +167 -146
- package/dist/core/runner/prepare-context-lane.js +6 -3
- package/dist/core/runner/prepare-defer-classify.js +4 -7
- package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
- package/dist/core/runner/prepare-delegation-surface.js +8 -7
- package/dist/core/runner/prepare-execution-env.d.ts +1 -2
- package/dist/core/runner/prepare-execution-env.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
- package/dist/core/runner/prepare-gate-stations.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -3
- package/dist/core/runner/prepare-hands-readface.js +3 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
- package/dist/core/runner/prepare-inherited-gate.js +3 -2
- package/dist/core/runner/prepare-listings.d.ts +1 -1
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
- package/dist/core/runner/prepare-memory-engine-session.js +19 -6
- package/dist/core/runner/prepare-memory.d.ts +3 -2
- package/dist/core/runner/prepare-memory.js +6 -2
- package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
- package/dist/core/runner/prepare-offload-wrappers.js +2 -1
- package/dist/core/runner/prepare-park-ask.d.ts +1 -1
- package/dist/core/runner/prepare-park-ask.js +9 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
- package/dist/core/runner/prepare-policy-chain.js +14 -19
- package/dist/core/runner/prepare-project-context.d.ts +1 -1
- package/dist/core/runner/prepare-project-context.js +6 -4
- package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
- package/dist/core/runner/prepare-prompt-assembly.js +20 -16
- package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
- package/dist/core/runner/prepare-prompt-inputs.js +6 -3
- package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
- package/dist/core/runner/prepare-protocol-tools.js +19 -12
- package/dist/core/runner/prepare-question-face.d.ts +4 -2
- package/dist/core/runner/prepare-question-face.js +4 -3
- package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
- package/dist/core/runner/prepare-safety-scan.js +34 -27
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
- package/dist/core/runner/prepare-suspend-saga.js +6 -2
- package/dist/core/runner/prepare-task.js +22 -19
- package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
- package/dist/core/runner/prepare-turn-wiring.js +14 -49
- package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -5
- package/dist/core/runner/prepare-wiring-manifest.js +9 -6
- package/dist/core/runner/prepare-workspace-restore.js +2 -2
- package/dist/core/runner/resume-admission.d.ts +4 -1
- package/dist/core/runner/resume-admission.js +16 -14
- package/dist/core/runner/resume-apply.js +4 -4
- package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
- package/dist/core/runner/resume-internals-and-config.js +3 -8
- package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
- package/dist/core/runner/resume-policy-outcome.js +18 -15
- package/dist/core/runner/resume-preflight.js +2 -2
- package/dist/core/runner/resume-review-outcome.js +3 -3
- package/dist/core/runner/retired-runner-deps.d.ts +5 -6
- package/dist/core/runner/retired-runner-deps.js +1 -4
- package/dist/core/runner/run-leg.js +97 -43
- package/dist/core/runner/run-recovery-lanes.js +5 -3
- package/dist/core/runner/run-settle-and-teardown.js +2 -0
- package/dist/core/runner/run-terminal-adoption.js +21 -9
- package/dist/core/runner/run-turn-boundary.js +9 -3
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/runner/session-file-state-replay.d.ts +5 -2
- package/dist/core/runner/session-file-state-replay.js +3 -2
- package/dist/core/runner/skills-seat.d.ts +15 -0
- package/dist/core/runner/skills-seat.js +57 -0
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
- package/dist/core/runner/sub-agent-deny-lists.js +7 -5
- package/dist/core/runner/synthetic-tools.d.ts +5 -2
- package/dist/core/runner/synthetic-tools.js +2 -12
- package/dist/core/runner/tool-face-overlay.d.ts +24 -6
- package/dist/core/runner/tool-face-overlay.js +24 -15
- package/dist/core/runner-deps.d.ts +37 -22
- package/dist/core/session-policy-store.d.ts +19 -0
- package/dist/core/session-policy-store.js +22 -2
- package/dist/core/session-store.d.ts +35 -16
- package/dist/core/session-store.js +20 -12
- package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
- package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
- package/dist/core/shell-wrapper-table.d.ts +5 -3
- package/dist/core/shell-wrapper-table.js +1 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
- package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
- package/dist/core/store-contracts/session-store-contract.js +639 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
- package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
- package/dist/core/task-event.d.ts +44 -6
- package/dist/core/task-notification.js +2 -2
- package/dist/core/task-registry-agent.js +1 -0
- package/dist/core/task-registry-shared.d.ts +5 -0
- package/dist/core/task-registry.d.ts +7 -0
- package/dist/core/task-registry.js +17 -0
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +7 -7
- package/dist/core/tighten-task-spec.d.ts +6 -4
- package/dist/core/tighten-task-spec.js +2 -2
- package/dist/core/tool-catalog-entries.d.ts +1 -1
- package/dist/core/tool-catalog-entries.js +52 -52
- package/dist/core/tool-face-reader.d.ts +49 -0
- package/dist/core/tool-face-reader.js +94 -0
- package/dist/core/tool-face.d.ts +17 -8
- package/dist/core/tool-face.js +1 -0
- package/dist/core/tool-policy.d.ts +67 -23
- package/dist/core/tool-policy.js +72 -60
- package/dist/core/tool-roster.d.ts +46 -28
- package/dist/core/tool-roster.js +91 -14
- package/dist/core/tool-spec-members.js +2 -2
- package/dist/core/tool-spec.d.ts +20 -4
- package/dist/core/tools.js +2 -0
- package/dist/core/trace.d.ts +24 -2
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +23 -0
- package/dist/core/untrusted-text.js +10 -10
- package/dist/core/user-attachment.d.ts +56 -2
- package/dist/core/user-attachment.js +139 -56
- package/dist/core/wiring-manifest.d.ts +4 -2
- package/dist/engine/compaction/compaction.d.ts +2 -2
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/harness/types.d.ts +10 -4
- package/dist/engine/llm/types.d.ts +7 -2
- package/dist/engine/llm/types.js +2 -1
- package/dist/engine/loop/agent-loop.d.ts +12 -1
- package/dist/engine/loop/agent-loop.js +17 -5
- package/dist/engine/loop/types.d.ts +7 -1
- package/dist/engine/session/repo-utils.d.ts +5 -0
- package/dist/engine/session/repo-utils.js +9 -0
- package/dist/engine/session/storage-base.js +2 -2
- package/dist/index.d.ts +37 -17
- package/dist/index.js +31 -14
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/internal/llm.d.ts +1 -1
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/goal.d.ts +24 -1
- package/dist/orchestration/goal.js +3 -2
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +1 -0
- package/dist/orchestration/workflow-governance.d.ts +1 -1
- package/dist/orchestration/workflow.d.ts +3 -0
- package/dist/orchestration/workflow.js +2 -1
- package/dist/prompt-assembly/artifact-store.d.ts +44 -9
- package/dist/prompt-assembly/artifact-store.js +38 -12
- package/dist/prompt-assembly/artifact.d.ts +12 -11
- package/dist/prompt-assembly/artifact.js +52 -21
- package/dist/prompt-assembly/assemble.d.ts +2 -0
- package/dist/prompt-assembly/assemble.js +2 -1
- package/dist/prompt-assembly/catalog/index.js +2 -1
- package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
- package/dist/prompt-assembly/catalog/memory.js +6 -0
- package/dist/prompt-assembly/catalog/schema.d.ts +19 -2
- package/dist/prompt-assembly/catalog/schema.js +3 -0
- package/dist/prompt-assembly/composer.d.ts +2 -0
- package/dist/prompt-assembly/composer.js +1 -1
- package/dist/prompt-assembly/overlay.d.ts +21 -11
- package/dist/prompt-assembly/overlay.js +57 -19
- package/dist/prompt-assembly/select.d.ts +21 -6
- package/dist/prompt-assembly/select.js +41 -4
- package/dist/prompts/default.d.ts +1 -12
- package/dist/prompts/default.js +0 -3
- package/dist/shell/lex.d.ts +87 -8
- package/dist/shell/lex.js +283 -49
- package/dist/stores/file/index.d.ts +8 -6
- package/dist/stores/file/permission-rule-store.d.ts +1 -1
- package/dist/stores/file/permission-rule-store.js +2 -1
- package/dist/tools/artifact/artifact-tool.js +14 -3
- package/dist/tools/fs/bash-program-position.d.ts +3 -15
- package/dist/tools/fs/bash-program-position.js +3 -10
- package/dist/tools/fs/bash-readonly-classifier.js +2 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-bash.js +72 -101
- package/dist/tools/fs/fs-read.d.ts +12 -0
- package/dist/tools/fs/fs-read.js +10 -9
- package/dist/tools/fs/fs-shared.d.ts +27 -31
- package/dist/tools/fs/fs-shared.js +12 -15
- package/dist/tools/fs/fs-write.js +1 -1
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/read-deny-home.d.ts +1 -1
- package/dist/tools/fs/read-deny-home.js +5 -4
- package/dist/tools/fs/read-deny.d.ts +16 -3
- package/dist/tools/fs/read-deny.js +12 -9
- package/dist/tools/fs/safety.d.ts +7 -6
- package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
- package/dist/tools/fs/shell-read-bindings.js +15 -16
- package/dist/tools/fs/shell-read-device.js +1 -1
- package/dist/tools/fs/shell-read-plan.js +36 -47
- package/dist/tools/fs/shell-read-word.d.ts +1 -6
- package/dist/tools/fs/shell-read-word.js +2 -20
- package/dist/tools/fs/shell-reentry-text.js +1 -1
- package/dist/tools/web.d.ts +2 -2
- package/package.json +4 -1
- package/test/export-surface.snapshot.json +5651 -5457
- package/wire-changes/core@7.53.0.json +1968 -0
- package/wire-changes/core@7.54.0.json +2870 -0
- package/dist/core/shell-scan.d.ts +0 -60
- package/dist/core/shell-scan.js +0 -183
|
@@ -1,23 +1,47 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The TIGHTENING reader's view of a command line — "which programs could this command line run, and can the text
|
|
3
|
+
* even be read?" — for the deny/ask direction of the permission-rule lane (`permission-rule-model.ts`
|
|
4
|
+
* `programRunReachOf`) and the read-only allow member (`read-only-shell.ts`). It reads the ONE shell lexer's result
|
|
5
|
+
* (`shell/lex.ts`) and adds only this reader's own domain: which pieces of a line are run candidates, the leading
|
|
6
|
+
* keyword of a control structure, and the additive peel. A segment that carries a command/process substitution
|
|
7
|
+
* anywhere (an argument, a redirection operand, an unquoted heredoc body) is one whose program runs are not fully
|
|
8
|
+
* known — the reader answers `unreadable` for every rule such a segment does not otherwise reach, never silence; the
|
|
9
|
+
* leading assignments and the wrapper programs of {@link SHELL_WRAPPER_TABLE} are peeled ADDITIVELY — a segment keeps
|
|
10
|
+
* its spelled argv as one run candidate and gains one per peeled layer, so `sudo env X=1 nohup rm -r x` is a run of
|
|
11
|
+
* `sudo …`, of `env …`, of `nohup …` and of `rm -r x`, and a deny on any of them reaches.
|
|
12
|
+
*
|
|
13
|
+
* What it answers is deliberately THREE-valued per segment: the argv words it could read, each marked literal or
|
|
14
|
+
* expansion-bearing, or an `unreadable` reason where the program runs cannot be known from the text (a command the
|
|
15
|
+
* lexer cannot delimit, a syntax error, a control structure whose commands need a syntax tree, a wrapper handed its
|
|
16
|
+
* command as one string or an option of unknown arity). The reader above turns "unreadable" into a fail-closed ask
|
|
17
|
+
* rather than into silence — reading it as "no match" is how a deny went blind on `rm -r x; echo "$HOME"`. Where the
|
|
18
|
+
* lexer cannot delimit the text, the segments it read before it stopped stay candidates (bash runs every complete line
|
|
19
|
+
* before the one it cannot parse) and the segment it stopped in is the unreadable one.
|
|
20
|
+
*
|
|
21
|
+
* The ALLOW direction's rule lane never reads this module: a standing yes covers the exact spelling a person read
|
|
22
|
+
* (`ruleAdmitsCommand`), and its whole-string refusal of every shell construct stays where it is.
|
|
23
|
+
*/
|
|
24
|
+
import { type LexedWord } from "../shell/lex.js";
|
|
25
|
+
/** One argv word as the reader projects it. */
|
|
2
26
|
export interface ShellWord {
|
|
3
27
|
/** The word's value with quotes and escapes removed — meaningful only when `expands` is `false`. */
|
|
4
28
|
readonly text: string;
|
|
5
29
|
/** The unquoted source spelling (assignment and wrapper-flag tests read this, never `text`). */
|
|
6
30
|
readonly raw: string;
|
|
7
31
|
/** `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 —
|
|
9
|
-
* yield zero, one or several words. */
|
|
32
|
+
* `"many"`: an unquoted expansion, glob or brace pattern, an ANSI-C quote, or a quoted `"$@"` / array expansion —
|
|
33
|
+
* may yield zero, one or several words. */
|
|
10
34
|
readonly expands: false | "one" | "many";
|
|
11
35
|
}
|
|
12
|
-
/** One redirection of a segment, as read: the operator and its operand word (a heredoc's operand is its
|
|
13
|
-
*
|
|
14
|
-
* kept). The tightening reader never reads these — a rule speaks about program runs; the
|
|
15
|
-
* reader does (an output redirection is a write). */
|
|
36
|
+
/** One redirection of a segment, as read: the operator and its operand word (a heredoc's operand is its delimiter;
|
|
37
|
+
* a descriptor duplication's is the descriptor; the IO number or `{name}` before the operator is folded into the
|
|
38
|
+
* operator's meaning and not kept). The tightening reader never reads these — a rule speaks about program runs; the
|
|
39
|
+
* read-only reader does (an output redirection is a write). */
|
|
16
40
|
export interface ShellRedirection {
|
|
17
41
|
readonly op: string;
|
|
18
42
|
readonly target: ShellWord;
|
|
19
43
|
}
|
|
20
|
-
/** One program run the
|
|
44
|
+
/** One program run the reader found, after keyword stripping. */
|
|
21
45
|
export interface ShellSegment {
|
|
22
46
|
/** The run as SPELLED (a leading keyword removed): the first candidate a rule is compared against. */
|
|
23
47
|
readonly argv: readonly ShellWord[];
|
|
@@ -32,7 +56,7 @@ export interface ShellSegment {
|
|
|
32
56
|
/** Present when the segment's program runs are NOT fully known although its candidates could be read:
|
|
33
57
|
* a peel that stopped before the wrapped program could be named (its options carry an expansion, a
|
|
34
58
|
* command string, an option of unknown arity), or a command/process substitution anywhere in the
|
|
35
|
-
* segment (it runs a program the
|
|
59
|
+
* segment (it runs a program the reader does not read). The candidates stand — a rule they reach is
|
|
36
60
|
* reached — and every other rule reads `unreadable`. */
|
|
37
61
|
readonly peelUnreadable?: string;
|
|
38
62
|
}
|
|
@@ -54,10 +78,10 @@ export interface ShellCommandShape {
|
|
|
54
78
|
readonly strayRedirection: boolean;
|
|
55
79
|
}
|
|
56
80
|
export { SHELL_WRAPPER_TABLE, type ShellWrapperName } from "./shell-wrapper-table.js";
|
|
57
|
-
/** Longer than this and the command is not read at all (one unreadable segment)
|
|
58
|
-
*
|
|
59
|
-
* cap is what bounds that on model-supplied text. Upstream's own reader stops at the same size. */
|
|
81
|
+
/** Longer than this and the command is not read at all (one unreadable segment). Upstream's own reader stops at the
|
|
82
|
+
* same size. */
|
|
60
83
|
export declare const MAX_SHELL_READ_CHARS = 10000;
|
|
84
|
+
export declare function lexedWordOf(w: ShellWord): LexedWord | undefined;
|
|
61
85
|
/** Read `source` as the tightening reader must: every program run the text could perform, or why it cannot be read. */
|
|
62
86
|
export declare function readShellCommand(source: string): ShellCommandShape;
|
|
63
87
|
/** Every program run of `shape` can be NAMED from the text: no segment is unreadable, no peel stopped,
|
|
@@ -1,62 +1,74 @@
|
|
|
1
|
+
import { arithmeticTextIsPlain, compoundValueHidesRun, expansionHidesRun, heredocBodyHidesRun, lexShell, namesRedirectDescriptor, PARSE_FAILURE_SENTENCE, } from "../shell/lex.js";
|
|
1
2
|
import { shellWordsOf } from "../shell/shell-words.js";
|
|
3
|
+
import { isLauncherAssignmentOperand } from "../tools/fs/bash-program-position.js";
|
|
4
|
+
import { extglobOpenerIn } from "../tools/fs/bash-readonly-classifier.js";
|
|
2
5
|
import { evaluatesOperand, SHELL_WRAPPER_TABLE } from "./shell-wrapper-table.js";
|
|
3
|
-
import { arithmeticHidesRun, carriesSubstitution, freshWord, matchOp, readDoubleQuoted, readExpansion, skipBalanced, skipHeredocBodies } from "./shell-scan.js";
|
|
4
6
|
export { SHELL_WRAPPER_TABLE } from "./shell-wrapper-table.js";
|
|
5
7
|
const WRAPPERS = new Map(Object.entries(SHELL_WRAPPER_TABLE));
|
|
6
8
|
const PREFIX_KEYWORDS = shellWordsOf("stripped_prefix");
|
|
7
9
|
const CLOSING_KEYWORDS = shellWordsOf("stripped_standalone");
|
|
8
10
|
const OPAQUE_KEYWORDS = shellWordsOf("opaque");
|
|
9
|
-
const
|
|
11
|
+
const CONTROL_PREFIXES = shellWordsOf("control_prefix");
|
|
10
12
|
const DURATION_RE = /^\d+(?:\.\d+)?[smhd]?$/;
|
|
11
|
-
const REDIRECT_OPS = ["<<<", "<<-", "<<", "<>", "<&", "<", ">>", ">|", ">&", "&>>", "&>", ">"];
|
|
12
|
-
const CONNECTOR_OPS = ["&&", "||", "|&", "|", ";;&", ";&", ";;", ";", "&"];
|
|
13
|
-
const ALL_OPS = [...CONNECTOR_OPS, ...REDIRECT_OPS];
|
|
14
13
|
export const MAX_SHELL_READ_CHARS = 10_000;
|
|
14
|
+
const LEXED_WORDS = new WeakMap();
|
|
15
|
+
export function lexedWordOf(w) {
|
|
16
|
+
return LEXED_WORDS.get(w);
|
|
17
|
+
}
|
|
18
|
+
function wordOf(w) {
|
|
19
|
+
let expands = false;
|
|
20
|
+
const widen = (to) => { if (expands !== "many")
|
|
21
|
+
expands = to; };
|
|
22
|
+
let text = "";
|
|
23
|
+
for (const p of w.pieces) {
|
|
24
|
+
const e = p.expansion;
|
|
25
|
+
if (p.quotedBy === "$'" || (e === undefined && p.literal === undefined)) {
|
|
26
|
+
widen("many");
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (e === undefined) {
|
|
30
|
+
text += p.literal ?? "";
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (e.kind === "tilde") {
|
|
34
|
+
if (p === w.pieces[0])
|
|
35
|
+
widen(e.yields);
|
|
36
|
+
text += e.body ?? "";
|
|
37
|
+
}
|
|
38
|
+
else if (e.kind === "brace") {
|
|
39
|
+
widen("many");
|
|
40
|
+
text += `{${e.body ?? ""}}`;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
widen(e.yields);
|
|
44
|
+
text += e.pattern?.folded ?? "";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const out = { text, raw: w.raw, expands };
|
|
48
|
+
LEXED_WORDS.set(out, w);
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
const literalIn = (w, set) => w !== undefined && w.expands === false && set.has(w.text);
|
|
52
|
+
const bareAssignment = (w) => lexedWordOf(w)?.assignment?.bare === true;
|
|
53
|
+
const hidesRun = (w) => w.expansions.some((p) => p.expansion !== undefined && expansionHidesRun(p.expansion)) || (w.assignment?.subscript !== undefined && !arithmeticTextIsPlain(w.assignment.subscript.raw)) || compoundValueHidesRun(w);
|
|
54
|
+
const CASE_ARM_UNREAD = "a `case` arm terminator outside a `case` this lane reads";
|
|
55
|
+
const EXTGLOB_UNREAD = "an extended pattern (`?(…)` `*(…)` `+(…)` `@(…)` `!(…)`) is one this reader does not expand";
|
|
15
56
|
export function readShellCommand(source) {
|
|
16
57
|
if (source.length > MAX_SHELL_READ_CHARS)
|
|
17
58
|
return { segments: [{ argv: [], redirections: [], peeled: [], unreadable: `the command is longer than ${MAX_SHELL_READ_CHARS} characters — not read` }], connectors: [], grouped: false, backgrounded: false, strayRedirection: false };
|
|
59
|
+
const lexed = lexShell(source);
|
|
60
|
+
const failed = "parseFailure" in lexed ? lexed : undefined;
|
|
61
|
+
const read = "parseFailure" in lexed ? lexed.readBefore : lexed.segments;
|
|
18
62
|
const segments = [];
|
|
19
63
|
const connectors = [];
|
|
20
64
|
let grouped = false;
|
|
21
65
|
let backgrounded = false;
|
|
22
66
|
let strayRedirection = false;
|
|
23
67
|
let seg = { words: [], redirections: [] };
|
|
24
|
-
let word = freshWord();
|
|
25
|
-
let pendingRedirect;
|
|
26
|
-
let heredocs = [];
|
|
27
|
-
let badDelimiter = false;
|
|
28
68
|
let afterBoundary = true;
|
|
29
69
|
let lastConnector;
|
|
30
|
-
let
|
|
31
|
-
const n = source.length;
|
|
32
|
-
let i = 0;
|
|
33
|
-
const endWord = () => {
|
|
34
|
-
if (!word.started)
|
|
35
|
-
return;
|
|
36
|
-
if (word.braceExpands)
|
|
37
|
-
word.expands = "many";
|
|
38
|
-
if (word.substitution)
|
|
39
|
-
seg.substitution = true;
|
|
40
|
-
if (pendingRedirect !== undefined) {
|
|
41
|
-
if (pendingRedirect === "<<" || pendingRedirect === "<<-") {
|
|
42
|
-
if (word.expands !== false)
|
|
43
|
-
badDelimiter = true;
|
|
44
|
-
heredocs.push({ delimiter: word.text, quoted: word.raw !== word.text, dash: pendingRedirect === "<<-" });
|
|
45
|
-
}
|
|
46
|
-
seg.redirections.push({ op: pendingRedirect, target: publicWord(word) });
|
|
47
|
-
pendingRedirect = undefined;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
seg.words.push(word);
|
|
51
|
-
}
|
|
52
|
-
word = freshWord();
|
|
53
|
-
};
|
|
70
|
+
let arithmetic;
|
|
54
71
|
const finishSegment = (connector) => {
|
|
55
|
-
endWord();
|
|
56
|
-
if (pendingRedirect !== undefined) {
|
|
57
|
-
seg.unreadable ??= `the redirection \`${pendingRedirect}\` has no operand — a syntax error`;
|
|
58
|
-
pendingRedirect = undefined;
|
|
59
|
-
}
|
|
60
72
|
let built = buildSegment(seg);
|
|
61
73
|
if (built === "keyworded")
|
|
62
74
|
grouped = true;
|
|
@@ -86,214 +98,121 @@ export function readShellCommand(source) {
|
|
|
86
98
|
afterBoundary = false;
|
|
87
99
|
};
|
|
88
100
|
const boundary = () => { finishSegment(undefined); grouped = true; afterBoundary = true; lastConnector = undefined; };
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
break;
|
|
101
|
+
const word = (w) => { if (hidesRun(w))
|
|
102
|
+
seg.substitution = true; return wordOf(w); };
|
|
103
|
+
const readTokens = (s) => {
|
|
104
|
+
const tokens = s.tokens;
|
|
105
|
+
const shown = tokens.flatMap((t) => (t.kind === "word" ? t.expansions : t.kind === "redirect" ? t.target?.expansions ?? [] : [])).filter((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic").length;
|
|
106
|
+
if (s.substitutions.length > shown)
|
|
107
|
+
seg.substitution = true;
|
|
108
|
+
let definitionClose = false;
|
|
109
|
+
tokens.forEach((t, i) => {
|
|
110
|
+
if (arithmetic !== undefined) {
|
|
111
|
+
if (t.kind === "group") {
|
|
112
|
+
arithmetic.depth += t.op === "(" ? 1 : -1;
|
|
113
|
+
if (arithmetic.depth === 0)
|
|
114
|
+
arithmetic = undefined;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const w = t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined;
|
|
118
|
+
if (w === undefined || hidesRun(w) || !arithmeticTextIsPlain(w.raw) || t.kind === "heredoc")
|
|
119
|
+
seg.substitution = true;
|
|
120
|
+
if (t.kind === "redirect" && t.dup !== undefined && !arithmeticTextIsPlain(t.dup))
|
|
121
|
+
seg.substitution = true;
|
|
122
|
+
return;
|
|
112
123
|
}
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (ch === "#" && !word.started) {
|
|
120
|
-
while (i < n && source[i] !== "\n")
|
|
121
|
-
i++;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (ch === "\\") {
|
|
125
|
-
if (i + 1 >= n) {
|
|
126
|
-
tail = "a trailing backslash — the line never ends";
|
|
127
|
-
break;
|
|
124
|
+
if (t.kind === "word") {
|
|
125
|
+
if (!namesRedirectDescriptor(t, tokens[i + 1]))
|
|
126
|
+
seg.words.push(word(t));
|
|
127
|
+
return;
|
|
128
128
|
}
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
if (t.kind === "redirect") {
|
|
130
|
+
const target = t.target !== undefined ? word(t.target) : { text: t.dup ?? "", raw: t.dup ?? "", expands: false };
|
|
131
|
+
seg.redirections.push({ op: t.op, target });
|
|
132
|
+
return;
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
if (ch === "'") {
|
|
140
|
-
const close = source.indexOf("'", i + 1);
|
|
141
|
-
if (close < 0) {
|
|
142
|
-
tail = "an unterminated single quote";
|
|
143
|
-
break;
|
|
134
|
+
if (t.kind === "heredoc") {
|
|
135
|
+
seg.redirections.push({ op: "<<", target: { text: t.delimiter, raw: t.delimiter, expands: false } });
|
|
136
|
+
if (!t.quoted && heredocBodyHidesRun(t.body))
|
|
137
|
+
seg.substitution = true;
|
|
138
|
+
return;
|
|
144
139
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
let dq = i + 1;
|
|
152
|
-
if (ch === "$")
|
|
153
|
-
while (source[dq] === "\\" && source[dq + 1] === "\n")
|
|
154
|
-
dq += 2;
|
|
155
|
-
if (ch === '"' || (ch === "$" && source[dq] === '"')) {
|
|
156
|
-
const open = ch === "$" ? dq + 1 : i + 1;
|
|
157
|
-
const end = readDoubleQuoted(source, open, word);
|
|
158
|
-
if (end === undefined) {
|
|
159
|
-
tail = "an unterminated double quote";
|
|
160
|
-
break;
|
|
140
|
+
const next = tokens[i + 1];
|
|
141
|
+
if (definitionClose) {
|
|
142
|
+
definitionClose = false;
|
|
143
|
+
return;
|
|
161
144
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
if (ch === "$" || ch === "`" || ((ch === "<" || ch === ">") && source[i + 1] === "(")) {
|
|
167
|
-
const end = readExpansion(source, i, word);
|
|
168
|
-
if (end === undefined) {
|
|
169
|
-
tail = "an unterminated substitution";
|
|
170
|
-
break;
|
|
145
|
+
if (t.op === "(" && next?.kind === "group" && next.op === "(" && seg.redirections.length === 0 && seg.words.every((w) => literalIn(w, CONTROL_PREFIXES) || literalIn(w, CLOSING_KEYWORDS) || (w.expands === false && w.text === "-p"))) {
|
|
146
|
+
arithmetic = { depth: 1 };
|
|
147
|
+
return;
|
|
171
148
|
}
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
word.raw += ch;
|
|
176
|
-
i = end;
|
|
177
|
-
continue;
|
|
149
|
+
if (t.op === "(" && next?.kind === "group" && next.op === ")" && seg.words.length === 1 && seg.words[0].expands === false && seg.redirections.length === 0) {
|
|
150
|
+
seg.words.pop();
|
|
151
|
+
definitionClose = true;
|
|
178
152
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (op === ";;" || op === ";&" || op === ";;&") {
|
|
191
|
-
endWord();
|
|
192
|
-
seg.unreadable ??= "a `case` arm terminator outside a `case` this lane reads";
|
|
193
|
-
i += op.length;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (op === "&")
|
|
197
|
-
backgrounded = true;
|
|
198
|
-
finishSegment(op);
|
|
199
|
-
i += op.length;
|
|
153
|
+
boundary();
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
for (let k = 0; k < read.length; k++) {
|
|
157
|
+
const s = read[k];
|
|
158
|
+
if (k > 0) {
|
|
159
|
+
const armEnd = caseArmEnd(read, k);
|
|
160
|
+
if (armEnd !== undefined) {
|
|
161
|
+
seg.unreadable ??= CASE_ARM_UNREAD;
|
|
162
|
+
k = armEnd;
|
|
163
|
+
readTokens(read[k]);
|
|
200
164
|
continue;
|
|
201
165
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (pendingRedirect !== undefined)
|
|
207
|
-
seg.unreadable ??= `the redirection \`${pendingRedirect}\` has no operand — a syntax error`;
|
|
208
|
-
pendingRedirect = op;
|
|
209
|
-
i += op.length;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (ch === "(" && word.started && "@?*+!".includes(word.raw.slice(-1)) && word.raw !== "!") {
|
|
213
|
-
const end = skipBalanced(source, i, "(", ")");
|
|
214
|
-
if (end === undefined) {
|
|
215
|
-
tail = "an unterminated extended pattern (`@(…)`)";
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
if (carriesSubstitution(source.slice(i + 1, end - 1)))
|
|
219
|
-
word.substitution = true;
|
|
220
|
-
word.expands = "many";
|
|
221
|
-
word.text += source.slice(i, end);
|
|
222
|
-
word.raw += source.slice(i, end);
|
|
223
|
-
i = end;
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
if (ch === "(" && source[i + 1] === "(" && (!word.started || word.raw === "!") && pendingRedirect === undefined && seg.words.every((w) => w.expands === false && (shellWordsOf("control_prefix").has(w.text) || CLOSING_KEYWORDS.has(w.text) || w.text === "-p"))) {
|
|
227
|
-
endWord();
|
|
228
|
-
const end = skipBalanced(source, i, "(", ")");
|
|
229
|
-
if (end === undefined) {
|
|
230
|
-
tail = "an unterminated arithmetic command";
|
|
231
|
-
break;
|
|
166
|
+
const connector = s.pipesStderr === true ? "|&" : s.connector;
|
|
167
|
+
if (arithmetic !== undefined) {
|
|
168
|
+
if (connector === ";")
|
|
169
|
+
seg.substitution = true;
|
|
232
170
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
if (ch === "(" || ch === ")") {
|
|
239
|
-
if (ch === "(" && word.started && !word.expands && seg.words.length === 0 && pendingRedirect === undefined) {
|
|
240
|
-
let j = i + 1;
|
|
241
|
-
while (j < n && (source[j] === " " || source[j] === "\t"))
|
|
242
|
-
j++;
|
|
243
|
-
if (source[j] === ")") {
|
|
244
|
-
word = freshWord();
|
|
245
|
-
boundary();
|
|
246
|
-
i = j + 1;
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
171
|
+
else {
|
|
172
|
+
if (connector === "&")
|
|
173
|
+
backgrounded = true;
|
|
174
|
+
finishSegment(connector);
|
|
249
175
|
}
|
|
250
|
-
boundary();
|
|
251
|
-
i++;
|
|
252
|
-
continue;
|
|
253
176
|
}
|
|
254
|
-
|
|
255
|
-
word.expands = "many";
|
|
256
|
-
if (ch === "[")
|
|
257
|
-
word.bracketOpen = true;
|
|
258
|
-
if (ch === "{")
|
|
259
|
-
word.braceOpen = true;
|
|
260
|
-
if (word.braceOpen && (ch === "," || (ch === "." && source[i + 1] === ".")))
|
|
261
|
-
word.braceExpands = true;
|
|
262
|
-
if (ch === "}" && !word.braceExpands)
|
|
263
|
-
word.braceOpen = false;
|
|
264
|
-
word.started = true;
|
|
265
|
-
word.text += ch;
|
|
266
|
-
word.raw += ch;
|
|
267
|
-
i++;
|
|
177
|
+
readTokens(s);
|
|
268
178
|
}
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
179
|
+
if (failed !== undefined) {
|
|
180
|
+
seg.unreadable ??= PARSE_FAILURE_SENTENCE[failed.parseFailure.reason];
|
|
181
|
+
}
|
|
182
|
+
else if (arithmetic !== undefined) {
|
|
183
|
+
seg.unreadable ??= "an unterminated arithmetic command";
|
|
273
184
|
}
|
|
274
185
|
else {
|
|
275
|
-
|
|
276
|
-
seg.unreadable ??= "a here-document with no body";
|
|
277
|
-
const dangling = seg.words.length === 0 && !word.started && lastConnector !== undefined && lastConnector !== ";" && lastConnector !== "&" && lastConnector !== "\n";
|
|
186
|
+
const dangling = seg.words.length === 0 && seg.redirections.length === 0 && lastConnector !== undefined && lastConnector !== ";" && lastConnector !== "&" && lastConnector !== "\n";
|
|
278
187
|
if (dangling)
|
|
279
188
|
seg.unreadable ??= `the connector \`${lastConnector}\` has no command after it — a syntax error`;
|
|
280
|
-
finishSegment(undefined);
|
|
281
189
|
}
|
|
190
|
+
finishSegment(undefined);
|
|
191
|
+
if (failed === undefined && "segments" in lexed && extglobOpenerIn(lexed, source))
|
|
192
|
+
return { segments: segments.map((s) => (s.unreadable !== undefined ? s : { ...s, peelUnreadable: s.peelUnreadable ?? EXTGLOB_UNREAD })), connectors, grouped, backgrounded, strayRedirection };
|
|
282
193
|
return { segments, connectors, grouped, backgrounded, strayRedirection };
|
|
283
194
|
}
|
|
284
|
-
|
|
195
|
+
function caseArmEnd(read, k) {
|
|
196
|
+
const arm = (j) => read[j].connector === ";" && read[j].text === "" && read[j].tokens.length === 0;
|
|
197
|
+
const next = read[k + 1];
|
|
198
|
+
if (!arm(k) || next === undefined || (next.connector !== ";" && next.connector !== "&"))
|
|
199
|
+
return undefined;
|
|
200
|
+
const third = read[k + 2];
|
|
201
|
+
return next.connector === ";" && arm(k + 1) && third?.connector === "&" ? k + 2 : k + 1;
|
|
202
|
+
}
|
|
285
203
|
const SUBSTITUTION_UNREAD = "a command substitution runs a program this reader does not read";
|
|
286
204
|
const MAX_WRAPPER_LAYERS = 8;
|
|
287
205
|
export function isFullyReadable(shape) {
|
|
288
206
|
return shape.segments.every((s) => s.unreadable === undefined && s.peelUnreadable === undefined && [s.argv, ...s.peeled].every((argv) => argv[0]?.expands === false));
|
|
289
207
|
}
|
|
208
|
+
const operandsOf = (run) => run.map((w) => ({ text: w.text, raw: w.raw, assignment: lexedWordOf(w)?.assignment }));
|
|
290
209
|
function buildSegment(seg) {
|
|
291
210
|
if (seg.unreadable !== undefined)
|
|
292
|
-
return { argv: seg.words
|
|
211
|
+
return { argv: seg.words, redirections: seg.redirections, peeled: [], unreadable: seg.unreadable };
|
|
293
212
|
const carried = () => (seg.substitution === true ? { argv: [], redirections: seg.redirections, peeled: [], peelUnreadable: SUBSTITUTION_UNREAD } : keyworded ? "keyworded" : "empty");
|
|
294
213
|
let keyworded = false;
|
|
295
214
|
let k0 = 0;
|
|
296
|
-
while (k0 < seg.words.length && seg.words[k0]
|
|
215
|
+
while (k0 < seg.words.length && (literalIn(seg.words[k0], PREFIX_KEYWORDS) || literalIn(seg.words[k0], CLOSING_KEYWORDS))) {
|
|
297
216
|
k0++;
|
|
298
217
|
keyworded = true;
|
|
299
218
|
}
|
|
@@ -301,24 +220,24 @@ function buildSegment(seg) {
|
|
|
301
220
|
const head = words[0];
|
|
302
221
|
if (head === undefined)
|
|
303
222
|
return carried();
|
|
304
|
-
if (evaluatesOperand(words))
|
|
223
|
+
if (evaluatesOperand(operandsOf(words)))
|
|
305
224
|
seg.substitution = true;
|
|
306
225
|
if (head.expands === false && (head.text === "[[" || head.text === "["))
|
|
307
226
|
return carried();
|
|
308
|
-
if (head
|
|
309
|
-
return { argv: words
|
|
310
|
-
const argv = words
|
|
227
|
+
if (literalIn(head, OPAQUE_KEYWORDS))
|
|
228
|
+
return { argv: words, redirections: seg.redirections, peeled: [], unreadable: `a \`${head.text}\` control structure — its commands need a syntax tree` };
|
|
229
|
+
const argv = words;
|
|
311
230
|
const peeled = [];
|
|
312
231
|
let peelUnreadable;
|
|
313
232
|
let k = 0;
|
|
314
|
-
while (k < argv.length &&
|
|
233
|
+
while (k < argv.length && bareAssignment(argv[k]))
|
|
315
234
|
k++;
|
|
316
235
|
let run = argv.slice(k);
|
|
317
236
|
if (k > 0 && run.length > 0)
|
|
318
237
|
peeled.push(run);
|
|
319
238
|
for (let layer = 0; run.length > 0; layer++) {
|
|
320
239
|
let j = 0;
|
|
321
|
-
while (j < run.length && ((run[j].text === "!" && run[j].expands === false) ||
|
|
240
|
+
while (j < run.length && ((run[j].text === "!" && run[j].expands === false) || bareAssignment(run[j])))
|
|
322
241
|
j++;
|
|
323
242
|
if (j > 0) {
|
|
324
243
|
run = run.slice(j);
|
|
@@ -344,7 +263,7 @@ function buildSegment(seg) {
|
|
|
344
263
|
run = next;
|
|
345
264
|
peeled.push(run);
|
|
346
265
|
}
|
|
347
|
-
if (seg.substitution === true || [argv, ...peeled].some((c) => c[0]?.expands === false && evaluatesOperand(c)))
|
|
266
|
+
if (seg.substitution === true || [argv, ...peeled].some((c) => c[0]?.expands === false && evaluatesOperand(operandsOf(c))))
|
|
348
267
|
peelUnreadable ??= SUBSTITUTION_UNREAD;
|
|
349
268
|
return { argv, redirections: seg.redirections, peeled, ...(peelUnreadable !== undefined ? { peelUnreadable } : {}) };
|
|
350
269
|
}
|
|
@@ -364,6 +283,7 @@ function unwrap(words) {
|
|
|
364
283
|
const flags = new Set(row.flags);
|
|
365
284
|
const attached = new Set(row.attached ?? []);
|
|
366
285
|
const commandString = new Set(row.commandString);
|
|
286
|
+
const assignmentOperand = (w) => row.assignments === true && w.expands === false && isLauncherAssignmentOperand(w.text);
|
|
367
287
|
let positionalTaken = row.positional === undefined;
|
|
368
288
|
let durationTaken = row.duration !== true;
|
|
369
289
|
let replaceToken;
|
|
@@ -405,7 +325,7 @@ function unwrap(words) {
|
|
|
405
325
|
return r;
|
|
406
326
|
i++;
|
|
407
327
|
}
|
|
408
|
-
while (
|
|
328
|
+
while (i < words.length && assignmentOperand(words[i]))
|
|
409
329
|
i++;
|
|
410
330
|
break;
|
|
411
331
|
}
|
|
@@ -464,7 +384,7 @@ function unwrap(words) {
|
|
|
464
384
|
i++;
|
|
465
385
|
continue;
|
|
466
386
|
}
|
|
467
|
-
if (
|
|
387
|
+
if (assignmentOperand(w)) {
|
|
468
388
|
i++;
|
|
469
389
|
continue;
|
|
470
390
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { AssignmentFact } from "../shell/lex.js";
|
|
1
2
|
/**
|
|
2
|
-
* The wrapper programs the tightening reader ({@link import("./shell-
|
|
3
|
+
* The wrapper programs the tightening reader ({@link import("./shell-run-candidates.js").readShellCommand}) peels to
|
|
3
4
|
* reach the program they run — upstream's three tables (its light strip on every arm, its full strip on
|
|
4
5
|
* the deny/ask arms, and its `xargs` clause inlined in the matcher) as ONE closed table. Peeling is
|
|
5
6
|
* ADDITIVE on the reading side: the segment keeps its spelled argv as one run candidate and adds one
|
|
@@ -395,13 +396,14 @@ export interface ShellWrapperRow {
|
|
|
395
396
|
* subscript (`arr[a]`, quoted, bare, attached `-varr[a]`, or `arr[a]=1`) is evaluated as arithmetic —
|
|
396
397
|
* recursively, like `$((a))` — when the builtin reads or writes it (`printf -v 'arr[a]' …`, `unset arr[a]`,
|
|
397
398
|
* `declare arr[a]=1`, `read arr[a]`, `[[ -v arr[a] ]]`, `test -v arr[a]`), and so is the value of an
|
|
398
|
-
* INTEGER-attribute assignment (`declare -i x=a`). The
|
|
399
|
+
* INTEGER-attribute assignment (`declare -i x=a`). The tightening reader marks such a run's hidden run at every peel
|
|
399
400
|
* candidate (`command printf -v …`). An operand that is WHOLLY an expansion is not judged here (`unset "$X"`
|
|
400
401
|
* — one more indirection; the price of reading it would be a prompt on every `export FOO="$BAR"`), nor is
|
|
401
402
|
* an arithmetic comparison's operand (`[[ $n -gt 0 ]]` evaluates `$n` — the same price). */
|
|
402
403
|
export declare const VARIABLE_OPERAND_BUILTINS: ReadonlySet<string>;
|
|
403
|
-
/** Does this run of a variable-operand builtin evaluate an operand arithmetically?
|
|
404
|
+
/** Does this run of a variable-operand builtin evaluate an operand arithmetically? `assignment`: the lexer's fact on the word. */
|
|
404
405
|
export declare function evaluatesOperand(run: readonly {
|
|
405
406
|
readonly text: string;
|
|
406
407
|
readonly raw: string;
|
|
408
|
+
readonly assignment?: AssignmentFact | undefined;
|
|
407
409
|
}[]): boolean;
|
|
@@ -67,7 +67,7 @@ export function evaluatesOperand(run) {
|
|
|
67
67
|
if (!VARIABLE_OPERAND_BUILTINS.has(run[0]?.text ?? ""))
|
|
68
68
|
return false;
|
|
69
69
|
const integer = run.some((w) => /^-[A-Za-z]*i/.test(w.text));
|
|
70
|
-
return run.some((w) => namesArrayElement(w.text) || namesArrayElement(w.raw) || (integer &&
|
|
70
|
+
return run.some((w) => namesArrayElement(w.text) || namesArrayElement(w.raw) || (integer && w.assignment !== undefined && /[A-Za-z_$]/.test(w.assignment.value.folded)));
|
|
71
71
|
}
|
|
72
72
|
function namesArrayElement(word) {
|
|
73
73
|
for (let open = word.indexOf("["); open > 0; open = word.indexOf("[", open + 1)) {
|
|
@@ -308,7 +308,7 @@ export async function checkpointStoreContract(make, runAssertion) {
|
|
|
308
308
|
const stored = (await store.get(bitless.token)).pendingAction;
|
|
309
309
|
assert.equal("hasBidiControls" in stored, false, "projection backfill must not write back to the row");
|
|
310
310
|
});
|
|
311
|
-
run("
|
|
311
|
+
run("the approval object's members: a row's identity + face group and its targets (capabilityId/contractId/shapeDigest/implementationRevision + family/pathTarget/names + targets) survive the round-trip verbatim; an absent group stays absent", async () => {
|
|
312
312
|
const store = make();
|
|
313
313
|
const carried = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-carried" });
|
|
314
314
|
const group = {
|
|
@@ -316,10 +316,10 @@ export async function checkpointStoreContract(make, runAssertion) {
|
|
|
316
316
|
contractId: "acme.write@2",
|
|
317
317
|
shapeDigest: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
318
318
|
implementationRevision: "r7",
|
|
319
|
-
rosterDigest: "sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
|
|
320
319
|
family: "file-write",
|
|
321
320
|
pathTarget: { param: "destination", aliases: ["path"], access: "create", skillScopeEligible: true },
|
|
322
321
|
names: ["AcmeWrite", "acme_write"],
|
|
322
|
+
targets: { protective: "/srv/work/out.txt", declared: "/srv/work/out.txt" },
|
|
323
323
|
};
|
|
324
324
|
Object.assign(carried.pendingAction, group);
|
|
325
325
|
const bare = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-bare" });
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { strict as assert } from "node:assert";
|
|
2
2
|
import { MAILBOX_INVALID_PEER_META_CODE, MAILBOX_TOMBSTONED_RECIPIENT_CODE } from "../mailbox-store.js";
|
|
3
3
|
import { beginContract } from "./contract-harness.js";
|
|
4
|
+
import { PRINCIPAL_INBOX_HANDLE } from "../../agents/cross-session-ref.js";
|
|
5
|
+
import { mintCrossPrincipalRecord } from "../../agents/org-delivery-gate.js";
|
|
4
6
|
export const MAILBOX_CONTRACT_SCOPE = "default";
|
|
5
7
|
const msg = (content, sentAt = 1000) => ({ content, sentAt });
|
|
6
8
|
async function withStores(mk, fn) {
|
|
@@ -259,6 +261,17 @@ export async function mailboxBundledOnlyContract(mk, runAssertion) {
|
|
|
259
261
|
await s.drop("tenant-b", "shared-handle");
|
|
260
262
|
assert.equal(await s.peekCount("tenant-b", "shared-handle"), 0);
|
|
261
263
|
});
|
|
264
|
+
run("第四条消费链(principal 收件箱):投递门铸的信落收件人 scope 的 principal.inbox,两只信任锚逐字节回读,发信方 scope 零残留", async (make) => {
|
|
265
|
+
const s = make();
|
|
266
|
+
const letter = mintCrossPrincipalRecord({ from: { scope: "principal-a", name: "alice" }, body: { content: "letter" }, receiptId: "gr-1", sentAt: 10_000, consumer: { kind: "terminals" } });
|
|
267
|
+
assert.equal(await s.append("principal-b", PRINCIPAL_INBOX_HANDLE, letter), 1);
|
|
268
|
+
assert.equal(await s.peekCount("principal-a", PRINCIPAL_INBOX_HANDLE), 0, "发信方 scope 的同名箱不得出现这封信");
|
|
269
|
+
const lease = await s.claimLease("principal-b", PRINCIPAL_INBOX_HANDLE, "drain:r1", 60_000, 10_000);
|
|
270
|
+
assert.deepEqual(lease.messages.map((m) => m.peerMeta), [{ kind: "peer_message", fromScope: "principal-a", gateReceiptId: "gr-1" }]);
|
|
271
|
+
assert.equal(lease.messages[0].from, "alice");
|
|
272
|
+
assert.deepEqual(await s.ack("principal-b", PRINCIPAL_INBOX_HANDLE, "drain:r1", lease.maxSeq), { acked: true });
|
|
273
|
+
assert.equal(await s.peekCount("principal-b", PRINCIPAL_INBOX_HANDLE), 0);
|
|
274
|
+
});
|
|
262
275
|
await settle();
|
|
263
276
|
}
|
|
264
277
|
export async function mailboxCrossProcessContract(hooks) {
|