@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
package/dist/question.js
CHANGED
|
@@ -1,56 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* §4④ tool-panorama — AskUserQuestion LIVE-stream HITL, the SERVICE side of core's `RunnerDeps.onQuestion`
|
|
3
|
-
* seam (core `ask-question.ts`, design/64 §5.2). The SIBLING of {@link import("./elicitation.js").ElicitationCoordinator}:
|
|
4
|
-
* elicitation is an inbound-MCP-server ask; this is the AGENT's own `AskUserQuestion` tool (a structured multiple-choice
|
|
5
|
-
* question to the end user). core EMITS the call and hands it to `onQuestion`; the deployment routes it to a live human
|
|
6
|
-
* over the run's stream and returns their {@link QuestionAnswer}. The durable-disconnected-human case is ALREADY wired
|
|
7
|
-
* separately (`createDurableQuestionPolicy` + `QUESTION_AWAITS_RESUME` on the durable leg — the question SUSPENDS and a
|
|
8
|
-
* resume carries the answer); this is the LIVE-only, same-replica, `steer()`-class half.
|
|
9
|
-
*
|
|
10
|
-
* The service is pure WIRE glue (the abstraction is core's): forward the questions to whoever tails the run's stream (a
|
|
11
|
-
* `question` SSE frame), park a promise, resolve it from `POST /v1/questions/:id/respond`. 🔒 SECURITY: the ANSWER fence
|
|
12
|
-
* is CORE's — core's AskUserQuestion tool echoes a `selected` value ONLY if it is one of the offered option labels
|
|
13
|
-
* (`selected ⊆ options`) and wraps the free-text `note` in an untrusted-DATA fence (design/80 D-F), so a wire answer can
|
|
14
|
-
* never be read as instructions. The service therefore does NOT re-fence the answer; it validates only the outer SHAPE
|
|
15
|
-
* (the HTTP boundary) and REDACTS secrets from the model-authored QUESTION text before it is persisted/streamed (the
|
|
16
|
-
* same redact-at-write contract as elicitation's `message`/tool outputs — the question is shown to a human, never
|
|
17
|
-
* re-fed to a model, so no `delimitUntrusted` is needed on it). Deployment responsibilities core delegates: THROTTLE
|
|
18
|
-
* (bound concurrency/total/ttl per run — an over-eager agent could flood the human).
|
|
19
|
-
*
|
|
20
|
-
* 🔴 VERDICT vs LANDING are separate jobs (#166 裁1). This coordinator is the deployment's HONEST reporter of one fact:
|
|
21
|
-
* "no human answered this". Every arm with no real human answer — no run context, the run aborted, the throttle window
|
|
22
|
-
* is spent, the question cannot be presented, the TTL expired (incl. the run-exit sweep) — returns core's
|
|
23
|
-
* `{kind:"unavailable"}`. It NEVER synthesizes an empty answer set: an empty `{answers:[]}` reads to core as "a human
|
|
24
|
-
* was here and picked nothing", which on a durable deployment consumes the pending approval and hands the model a
|
|
25
|
-
* fabricated human verdict instead of parking the question where an operator can still answer it. Choosing the landing
|
|
26
|
-
* is CORE's job: a durable leg parks a checkpoint (operator answers later via decide), a non-durable leg continues with
|
|
27
|
-
* the `declined_unavailable` synthetic-continuation card (the model is told nobody is reachable and proceeds on its own
|
|
28
|
-
* judgment — a run with no human still never hangs). Only a REAL human answer returns a {@link QuestionAnswer}.
|
|
29
|
-
*
|
|
30
|
-
* ROUTING mirrors elicitation: `onQuestion` lives on the SINGLETON runner's `RunnerDeps` (the request carries no taskId),
|
|
31
|
-
* so the run leg establishes a per-run AsyncLocalStorage context (taskId throttle key, owner respond-gate, `emit`
|
|
32
|
-
* closure). `node:async_hooks` propagates it through every await; no context ⇒ unavailable (never a hang).
|
|
33
|
-
*/
|
|
34
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
35
2
|
import { uuidv7 } from "@sema-agent/core";
|
|
36
3
|
import { redactDeep } from "./trace/redact.js";
|
|
37
4
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
38
|
-
/** Size bound on the model-authored questions payload before it is persisted to the durable log + streamed (parity
|
|
39
|
-
* with elicitation's MAX_ELICIT_* — an over-large questions blob amplifies into the socket/log). Over the cap ⇒ the
|
|
40
|
-
* questions are dropped and the ask reports unavailable (a question the human can't see can't be answered). */
|
|
41
5
|
const MAX_QUESTIONS_BYTES = 16384;
|
|
42
6
|
export const DEFAULT_QUESTION_THROTTLE = {
|
|
43
7
|
maxConcurrentPerRun: 2,
|
|
44
8
|
maxTotalPerRun: 20,
|
|
45
9
|
ttlMs: 5 * 60_000,
|
|
46
10
|
};
|
|
47
|
-
/** The honest "nobody answered this" outcome. Frozen + shared: it is pure data core reads through
|
|
48
|
-
* `classifyQuestionOutcome`, and it must never be confused with an empty ANSWER (which claims a human chose nothing
|
|
49
|
-
* and, on a durable leg, spends the approval that park would have preserved). */
|
|
50
11
|
const UNAVAILABLE = Object.freeze({ kind: "unavailable" });
|
|
51
|
-
/** Validate the respond body = core's {@link QuestionAnswer}. `selected`/`note` are OPAQUE passthrough (core fences
|
|
52
|
-
* them: `selected ⊆ options`, `note` untrusted-fenced) — we validate only the outer SHAPE (answers[] of {header,
|
|
53
|
-
* selected:string[], note?}), never the option semantics. */
|
|
54
12
|
export function parseQuestionResponse(body) {
|
|
55
13
|
if (body === null || typeof body !== "object" || Array.isArray(body))
|
|
56
14
|
return { ok: false, error: "body must be an object" };
|
|
@@ -73,9 +31,6 @@ export function parseQuestionResponse(body) {
|
|
|
73
31
|
}
|
|
74
32
|
return { ok: true, value: { answers: out } };
|
|
75
33
|
}
|
|
76
|
-
/** redactDeep + size-bound the UNTRUSTED model-authored questions before they are persisted/streamed (a question's
|
|
77
|
-
* free-text — question/header/option label+description — can echo a secret from the model's context). Over the byte
|
|
78
|
-
* cap OR unserializable ⇒ undefined (drop; the ask reports unavailable). */
|
|
79
34
|
function boundQuestions(questions) {
|
|
80
35
|
const redacted = redactDeep(questions);
|
|
81
36
|
try {
|
|
@@ -87,12 +42,6 @@ function boundQuestions(questions) {
|
|
|
87
42
|
}
|
|
88
43
|
return redacted;
|
|
89
44
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Coordinates AskUserQuestion HITL for the singleton runner. Process-local + same-replica (the pending map is in memory,
|
|
92
|
-
* like ElicitationCoordinator / steerableRuns): a respond that lands on another replica finds nothing → 404. Present
|
|
93
|
-
* (passed into `RunnerDeps.onQuestion` + the respond route) ONLY when `ASK_QUESTION_ENABLED` — absent ⇒ core mounts the
|
|
94
|
-
* AskUserQuestion tool with no seam at all (`seam_absent` continuation; the model just can't get a live answer).
|
|
95
|
-
*/
|
|
96
45
|
export class QuestionCoordinator {
|
|
97
46
|
als = new AsyncLocalStorage();
|
|
98
47
|
pending = new Map();
|
|
@@ -101,8 +50,6 @@ export class QuestionCoordinator {
|
|
|
101
50
|
constructor(throttle = DEFAULT_QUESTION_THROTTLE) {
|
|
102
51
|
this.throttle = throttle;
|
|
103
52
|
}
|
|
104
|
-
/** Run `fn` with the per-run question context ambient. On exit, release any still-pending question for this run (a
|
|
105
|
-
* live-only question cannot outlive its leg → unavailable) and drop the run's counters (no leak). */
|
|
106
53
|
runWithContext(ctx, fn) {
|
|
107
54
|
return this.als.run(ctx, async () => {
|
|
108
55
|
try {
|
|
@@ -117,30 +64,20 @@ export class QuestionCoordinator {
|
|
|
117
64
|
}
|
|
118
65
|
});
|
|
119
66
|
}
|
|
120
|
-
/** `RunnerDeps.onQuestion`. core calls this when the agent's AskUserQuestion tool fires; the resolved outcome is what
|
|
121
|
-
* core acts on — a {@link QuestionAnswer} ONLY when a human really answered, otherwise `{kind:"unavailable"}` (core
|
|
122
|
-
* then parks on a durable leg / continues with the `declined_unavailable` card on a non-durable one; either way the
|
|
123
|
-
* run never hangs). Arrow property so it can be passed as `onQuestion: coordinator.question` with `this` bound. */
|
|
124
67
|
question = async (req, signal) => {
|
|
125
68
|
const ctx = this.als.getStore();
|
|
126
|
-
// No run context (a leg not wrapped by runWithContext — synchronous /v1/tasks, verify/cascade, or a leader sub-task
|
|
127
|
-
// with no live stream) ⇒ nobody to ask. Sub-tasks run in the parent's ALS frame → a child's question inherits the
|
|
128
|
-
// top-level run's ctx (one human, one stream, one throttle budget), same as elicitation.
|
|
129
69
|
if (!ctx)
|
|
130
70
|
return UNAVAILABLE;
|
|
131
|
-
// Aborted (call signal or the run's own): whatever comes back has no consumer, so claiming a human answered is a lie.
|
|
132
71
|
if (signal?.aborted || ctx.abortSignal?.aborted)
|
|
133
72
|
return UNAVAILABLE;
|
|
134
|
-
// THROTTLE (service's job — core imposes none): bound concurrency + total. Breach ⇒ unavailable, never a denial and
|
|
135
|
-
// never a fabricated answer — a spent window is a "not now", which is exactly what park/continue is for.
|
|
136
73
|
const rc = this.countersFor(ctx.taskId);
|
|
137
74
|
if (rc.inflight >= this.throttle.maxConcurrentPerRun || rc.total >= this.throttle.maxTotalPerRun)
|
|
138
75
|
return UNAVAILABLE;
|
|
139
76
|
const questions = boundQuestions(req.questions);
|
|
140
77
|
if (!questions)
|
|
141
|
-
return UNAVAILABLE;
|
|
78
|
+
return UNAVAILABLE;
|
|
142
79
|
const id = uuidv7();
|
|
143
|
-
rc.inflight += 1;
|
|
80
|
+
rc.inflight += 1;
|
|
144
81
|
let done = false;
|
|
145
82
|
let timer;
|
|
146
83
|
let resolveAnswer;
|
|
@@ -155,33 +92,17 @@ export class QuestionCoordinator {
|
|
|
155
92
|
ctx.abortSignal?.removeEventListener("abort", onAbort);
|
|
156
93
|
this.pending.delete(id);
|
|
157
94
|
rc.inflight = Math.max(0, rc.inflight - 1);
|
|
158
|
-
// stash the outcome for the completion frame emitted below (after the await returns)
|
|
159
95
|
lastOutcome = outcome;
|
|
160
96
|
resolveAnswer(a);
|
|
161
97
|
};
|
|
162
98
|
let lastOutcome = "unanswered";
|
|
163
99
|
const onAbort = () => settle(UNAVAILABLE, "unanswered");
|
|
164
|
-
// TTL: the human walked away. Release as unavailable — core decides whether that parks (durable, operator can still
|
|
165
|
-
// answer) or continues (non-durable); holding core past the window is not one of the choices.
|
|
166
100
|
timer = setTimeout(() => settle(UNAVAILABLE, "unanswered"), this.throttle.ttlMs);
|
|
167
101
|
timer.unref?.();
|
|
168
102
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
169
103
|
ctx.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
170
|
-
// Register BEFORE emitting so a (fast) respond can never miss the entry.
|
|
171
104
|
this.pending.set(id, { settle, owner: ctx.owner, taskId: ctx.taskId });
|
|
172
|
-
// Emit the OPEN frame AWAITED so its durable append is ORDERED ahead of the answer/completion/terminal appends (a
|
|
173
|
-
// fire-and-forget append can commit out of seq order and be skipped by the monotonic events-tail cursor). A FAILED
|
|
174
|
-
// emit settles unavailable (nobody can answer a frame that never arrived) and must NOT burn the per-run total.
|
|
175
|
-
// 🔴 RACED against the settlement (codex 复审 R2-high): a STALLED delivery (a durable append that never settles, a
|
|
176
|
-
// half-open socket) is a third shape next to ok/threw, and a bare `await ctx.emit` on it outlives TTL *and* abort —
|
|
177
|
-
// settle would resolve `answerP` while this frame is still parked on the emit, so `question()` never returns, core's
|
|
178
|
-
// callTool never unblocks and the leg cannot even reach its own cleanup. "The window always releases" has to hold
|
|
179
|
-
// for the stalled shape too, so the await ends at whichever comes first. Ordering is unchanged on the normal path
|
|
180
|
-
// (the settlement is still pending there, so the emit is still fully awaited).
|
|
181
|
-
// The loser of the race keeps its own handlers — an emit that rejects AFTER the race must not surface as an
|
|
182
|
-
// unhandled rejection.
|
|
183
105
|
const emitted = (async () => {
|
|
184
|
-
// [4851]③ 窗三键与上面 setTimeout 用同一个 ttlMs 铸(单源;壳倒计时永不与真释放口径分歧)。
|
|
185
106
|
const now = Date.now();
|
|
186
107
|
return ctx.emit({ type: "question", questionId: id, questions, serverNowMs: now, expiresInMs: this.throttle.ttlMs, expiresAtMs: now + this.throttle.ttlMs });
|
|
187
108
|
})()
|
|
@@ -191,23 +112,10 @@ export class QuestionCoordinator {
|
|
|
191
112
|
rc.total += 1;
|
|
192
113
|
}
|
|
193
114
|
else if (delivery === "failed") {
|
|
194
|
-
// #157(codex 复审 MEDIUM,2026-08-07):投递失败 ⇒ 人在环这道门被**一次故障**跳过,模型自己往下判。
|
|
195
|
-
// 「绝不把 run 挂死」是刻意的产品姿态,所以方向不改;但它是 P-DEBT 不是合法兜底——债要在遥测里显形。
|
|
196
|
-
// #166 后本臂结算成 unavailable(而非空答):留痕语义不变(一次投递故障仍可能让人在环被跳过——非
|
|
197
|
-
// durable 形 core 合成续跑),durable 形则改由 core park,债的方向与本记账口径一致。
|
|
198
115
|
recordFailOpen("server.question.open-frame-undelivered", `question=${id}`);
|
|
199
116
|
settle(UNAVAILABLE, "unanswered");
|
|
200
117
|
}
|
|
201
|
-
// delivery==="stalled":窗已由 TTL/abort/清扫结算,这一问就此让位;账在下面那个迟到臂里补(投递还没
|
|
202
|
-
// 判出成败,现在既不能计费也不能记债)。
|
|
203
118
|
const answer = await answerP;
|
|
204
|
-
// Completion breadcrumb (dialog dismiss) — FIRE-AND-FORGET so a slow durable append can never delay returning the
|
|
205
|
-
// answer to core. Best-effort (the shell already has the answer via respond), but CHAINED BEHIND the open frame and
|
|
206
|
-
// BRANCHED on how that frame ended (codex 复审 R3):
|
|
207
|
-
// · 送达(含**迟到**送达)⇒ 人真看见了这张卡:补烧总量额度(否则「反复悬挂再落地」是一条绕过刷屏帽的
|
|
208
|
-
// 路子),再发消卡帧把它消解掉;
|
|
209
|
-
// · 投递失败(含**迟到**失败)⇒ 没有卡可消解,发消卡帧只会在流里留一条无主帧;此时补记那条 P-DEBT
|
|
210
|
-
// 留痕(同步臂已经记过的不会重复:那条腿的 delivery 不是 stalled)。
|
|
211
119
|
void emitted
|
|
212
120
|
.then((result) => {
|
|
213
121
|
if (result === "failed") {
|
|
@@ -219,17 +127,10 @@ export class QuestionCoordinator {
|
|
|
219
127
|
rc.total += 1;
|
|
220
128
|
return ctx.emit({ type: "question_complete", questionId: id, outcome: lastOutcome, serverNowMs: Date.now() });
|
|
221
129
|
})
|
|
222
|
-
// #157:F 类兜底(丢的是收尾渲染,不是答案)——合法,但必须留痕,否则"面包屑成片消失"只能靠
|
|
223
|
-
// 用户报"对话框不消解"发现。
|
|
224
130
|
.catch(() => recordFailOpen("server.question.complete-breadcrumb-dropped", `question=${id}`));
|
|
225
131
|
return answer;
|
|
226
132
|
};
|
|
227
|
-
/** `POST /v1/questions/:id/respond` — resolve a parked question with the shell's answer. Owner-gated with a 404 (no
|
|
228
|
-
* existence oracle): a non-owner AND an unknown id (answered / expired / wrong replica) both get 404. Returns the
|
|
229
|
-
* HTTP {status, body}; the HTTP layer owns auth (gatedPrincipal + REQUIRE_PRINCIPAL) before calling. */
|
|
230
133
|
respond(id, principal, body) {
|
|
231
|
-
// Validate the body BEFORE the lookup (steer/elicit parity): a bad body is the caller's error regardless of run
|
|
232
|
-
// state, so 400 is existence-independent (no 400-vs-404 existence oracle).
|
|
233
134
|
const parsed = parseQuestionResponse(body);
|
|
234
135
|
if (!parsed.ok)
|
|
235
136
|
return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape" } };
|
|
@@ -240,19 +141,9 @@ export class QuestionCoordinator {
|
|
|
240
141
|
entry.settle(parsed.value, "answered");
|
|
241
142
|
return { status: 200, body: { questionId: id, delivery: "applied" } };
|
|
242
143
|
}
|
|
243
|
-
/** Test/observability hook: number of currently-parked questions. */
|
|
244
144
|
pendingCount() {
|
|
245
145
|
return this.pending.size;
|
|
246
146
|
}
|
|
247
|
-
/** #152 ([2703] 案二):durable 部署上的 AskUserQuestion 判决探针——本调用点是否处在某条**投递得到人的**
|
|
248
|
-
* 活流腿上(runWithContext 包裹的 bg/SSE 腿=true;sync /v1/tasks、verify/cascade、durable resume 驱动=false)。
|
|
249
|
-
* resolve-spec 的 durable question policy 用它在**判决时**分腿:有活流 ⇒ allow(问活人),无 ⇒
|
|
250
|
-
* ask(durable park)。ALS 让这个判断天然 per-leg,policy 组装期不必预知腿别。
|
|
251
|
-
* 🔴 判据是「投递面此刻可达」而不只是「ALS 在场」(复审 A1):detach 腿断连后 run 仍在本作用域里跑,
|
|
252
|
-
* 只判 ALS 会把一条谁也收不到的问题判成 allow——挂满 ttl 后以 unavailable 结算,非 durable 形 core
|
|
253
|
-
* 就此合成续跑,活人再没机会答;而 park 才是它该走的腿。
|
|
254
|
-
* 谓词缺席 ⇒ 恒可达(后台腿的 durable events tail 语义)。谓词本身抛错按不可达处理:判决面
|
|
255
|
-
* fail-closed 到 park(park 可由运维补答,续跑掉的问题不可回收)。 */
|
|
256
147
|
hasLiveContext() {
|
|
257
148
|
const ctx = this.als.getStore();
|
|
258
149
|
if (ctx === undefined)
|
package/dist/resource-suspend.js
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/80 seam #2 — the resource/preempt durable-suspend opt-in decision (design/74 "失败≠没做完" third state).
|
|
3
|
-
*
|
|
4
|
-
* Pure so it is unit-testable in isolation (the spec is otherwise assembled inside main.ts's bootstrap). When a
|
|
5
|
-
* task opts in, a budget/turns/walltime limit OR a scheduler preempt durably SUSPENDS the task (resumable via
|
|
6
|
-
* `POST /v1/assistant/tasks/:id/resume`) instead of FAILING; absent ⇒ a limit still FAILS.
|
|
7
|
-
*
|
|
8
|
-
* 🔴 EXCLUDE verify/cascade tasks. Core runs their implementation via `runWithVerification`/`runCascade`, which
|
|
9
|
-
* map an inner durable suspend to a FAILED result (`errorCode: "unexpected.suspended"`,内层 status
|
|
10
|
-
* "needs_review" 时铸 `"unexpected.needs_review"` —— 真源是 core `agents/suspend-guard.js` 的
|
|
11
|
-
* `mapNestedSuspend`;`suspended.*` 三码是 bg 子代/委派报告面的词,不落在这条 TaskResult 腿上). The service would then
|
|
12
|
-
* `setTerminal(failed)` while core had already minted a pending `resource_limit` checkpoint + suspended the
|
|
13
|
-
* remote VM — ORPHANING both (the run row is terminal, so nothing resumes them). So a resource-limit hit on a
|
|
14
|
-
* verify/cascade task must FAIL CLEANLY (no suspend), exactly as before the flag. This exclusion is the SAME
|
|
15
|
-
* boundary the `preemptSignal` wiring enforces in runs.ts (stream-path only), so eligibility and the signal stay
|
|
16
|
-
* aligned. (Adversarial review wtyc7ist3 HIGH finding: without this, enabling RESOURCE_SUSPEND leaks a suspended
|
|
17
|
-
* VM + checkpoint on every verify/cascade task that hits a resource limit.)
|
|
18
|
-
*/
|
|
19
1
|
export function resourceSuspendOptIn(opts) {
|
|
20
2
|
if (!opts.enabled || opts.isVerify || opts.isCascade)
|
|
21
3
|
return undefined;
|
|
@@ -1,51 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The A value router — cash out the S1 supervisor value verdict (sema-internal server/docs/S1-VALUE-VERDICT.md §6.3) into a per-task
|
|
3
|
-
* orchestration decision: run a task as `single` / `supervisor` / `team`.
|
|
4
|
-
*
|
|
5
|
-
* The verdict, in one line: **competent tasks have SUP/TEAM dominated (pure overhead) ⇒ default single; the real
|
|
6
|
-
* supervisor value is (a) DANGER backstop (SUP prevention gate-deny > TEAM fan-out which AMPLIFIES danger ×K) and
|
|
7
|
-
* (b) STRUCTURAL view-gap (goal-keeping / ask-aggregation, universally useful).** So this router is CONSERVATIVE
|
|
8
|
-
* by construction:
|
|
9
|
-
* - default **single** (escalation is never free — the verdict measured it as pure overhead on competent work);
|
|
10
|
-
* - escalate to **supervisor** ONLY on a positive (a)-danger or (b)-structural signal;
|
|
11
|
-
* - NEVER auto-select **team** (fan-out is dominated everywhere tested AND amplifies danger; team stays
|
|
12
|
-
* caller-explicit — a deliberate `council`/`debate`/`team` request, or the `/v1/leader` endpoint).
|
|
13
|
-
*
|
|
14
|
-
* 🔴 ALL signals are DETERMINISTIC (config / mounted tools / exec-env isolation / autonomy / delegation). There is
|
|
15
|
-
* NO LLM "difficulty" judge in the routing decision — the verdict's measurement-integrity rule (design/89 §2.2.1):
|
|
16
|
-
* an LLM judge is a GATE inside an arm, NEVER the routing metric (it would make the router un-auditable + gameable).
|
|
17
|
-
*
|
|
18
|
-
* Pure + unit-testable. The decision is VALIDATED empirically against the S1 harness (a ROUTER arm that runs the
|
|
19
|
-
* router's chosen arm per trap; the objective hidden-test oracle confirms danger→SUP prevents + competent→single
|
|
20
|
-
* is not-worse) BEFORE it is wired into the live task path — never ship an unvalidated router.
|
|
21
|
-
*/
|
|
22
1
|
import {} from "@sema-agent/core";
|
|
23
|
-
/**
|
|
24
|
-
* The routing decision (pure). Order matters: explicit caller choice first, then (a) danger, then (b) structural,
|
|
25
|
-
* else the single default. `team` is NEVER produced here — it only ever appears via {@link RoutingSignals.explicitMode}.
|
|
26
|
-
*/
|
|
27
2
|
export function routeOrchestration(s) {
|
|
28
|
-
// 1. Caller-explicit wins — a deliberate council/debate/team request (or the /v1/leader endpoint) is intent.
|
|
29
3
|
if (s.explicitMode)
|
|
30
4
|
return { mode: s.explicitMode, reason: `explicit:${s.explicitMode}` };
|
|
31
|
-
// 2. (a) DANGER → supervisor prevention. The verdict's strongest value: gate-deny PREVENTS (SUP) beats
|
|
32
|
-
// fan-out which AMPLIFIES the danger ×K (TEAM) and even beats single's after-the-fact exposure.
|
|
33
5
|
if (s.dangerousCapability)
|
|
34
6
|
return { mode: "supervisor", reason: "danger:prevention-gate" };
|
|
35
|
-
// 3. (b) STRUCTURAL → supervisor. Goal-keeping (worker local view vs supervisor global blueprint) +
|
|
36
|
-
// cross-subtask ask-aggregation — universally useful when the task delegates / runs long.
|
|
37
7
|
if (s.needsGoalKeeping)
|
|
38
8
|
return { mode: "supervisor", reason: "structural:goal-keeping" };
|
|
39
|
-
// 4. Default SINGLE — competent tasks: SUP/TEAM measured as pure overhead (dominated). Escalation is not free.
|
|
40
9
|
return { mode: "single", reason: "default:single" };
|
|
41
10
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Derive the routing signals from a task's resolved context — DETERMINISTIC. The danger rule is CONSERVATIVE and
|
|
44
|
-
* fail-safe: a `read-only` task cannot harm (no escalation); EGRESS / irreversible-external capability is danger
|
|
45
|
-
* regardless of sandboxing (it escapes the sandbox); plain write/exec is danger ONLY on a non-isolated env (an
|
|
46
|
-
* isolated sandbox contains it + is torn down). The exact threshold is the one tunable knob — validated
|
|
47
|
-
* empirically against the S1 harness, not guessed.
|
|
48
|
-
*/
|
|
49
11
|
export function deriveRoutingSignals(ctx) {
|
|
50
12
|
const readOnly = ctx.autonomy === "read-only";
|
|
51
13
|
const dangerousCapability = !readOnly && (ctx.canEgress || (ctx.canWriteOrExec && !ctx.isolatedExecEnv));
|
|
@@ -55,41 +17,18 @@ export function deriveRoutingSignals(ctx) {
|
|
|
55
17
|
needsGoalKeeping: ctx.delegatedOrLongHorizon,
|
|
56
18
|
};
|
|
57
19
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Service-facing convenience: map a /v1/tasks task's DETERMINISTIC facts → a routing decision (the service signal
|
|
60
|
-
* derivation + {@link deriveRoutingSignals} + {@link routeOrchestration} in one tested call; main.ts's resolveSpec
|
|
61
|
-
* calls this). Signal mapping:
|
|
62
|
-
* - `isolatedExecEnv` ⟺ a sandboxed remote-exec backend (`e2b`/`k8s` Kata) — `ssh`/`host`/`adb` are NOT isolated.
|
|
63
|
-
* - hand tools (write_file/bash) are mounted only when an executionEnv exists AND hands aren't read-only/plan.
|
|
64
|
-
* - `canEgress` is left false in v1 (per-tool egress detection deferred; danger derives from non-isolation).
|
|
65
|
-
* - `delegatedOrLongHorizon` is false on the single-task path (the (b) value lives on the /v1/leader path).
|
|
66
|
-
*/
|
|
67
20
|
export function routeServiceTask(input) {
|
|
68
21
|
const readOnlyHands = input.autonomy === "read-only" || input.autonomy === "plan";
|
|
69
22
|
return routeOrchestration(deriveRoutingSignals({
|
|
70
23
|
...(input.explicitTeam ? { explicitMode: "team" } : {}),
|
|
71
24
|
...(input.autonomy ? { autonomy: input.autonomy } : {}),
|
|
72
25
|
canWriteOrExec: input.hasExecutionEnv && !readOnlyHands,
|
|
73
|
-
// 🔴 v1 EXPLICITLY-ACCEPTED LIMITATION (review MEDIUM): `canEgress` is false here, so an isolated sandbox
|
|
74
|
-
// with outbound network (e2b defaults internet ON) is NOT auto-routed to SUP for egress. Deriving danger
|
|
75
|
-
// from network-PRESENCE would over-escalate (a routine `npm install` task needs network) — defeating the
|
|
76
|
-
// verdict's default-single. The real egress danger is IRREVERSIBLE EXTERNAL actions (git push to a real
|
|
77
|
-
// repo, a deploy), which are TOOL-specific, not network-presence, and not cleanly detectable here. When a
|
|
78
|
-
// per-tool egress/irreversibility signal is wired (k8s P3 egress + tool marks), set this from it.
|
|
79
26
|
canEgress: false,
|
|
80
27
|
isolatedExecEnv: input.isolatedExecEnv,
|
|
81
28
|
delegatedOrLongHorizon: false,
|
|
82
29
|
}));
|
|
83
30
|
}
|
|
84
|
-
/** VM-isolation runtime classes — a REAL sandbox boundary (separate kernel). Plain `runc` / `""` / unknown share
|
|
85
|
-
* the host kernel ⇒ NOT isolated. */
|
|
86
31
|
const VM_ISOLATION_CLASSES = new Set(["kata", "kata-qemu", "kata-clh", "kata-fc", "gvisor", "runsc"]);
|
|
87
|
-
/**
|
|
88
|
-
* Resolve whether a task's execution env STRONGLY isolates side effects (a VM/kernel boundary the sandbox tears
|
|
89
|
-
* down). FAIL-CLOSED (review HIGH): a `k8s` backend is isolated ONLY with a VM-isolation `runtimeClass`
|
|
90
|
-
* (kata-x or gvisor) — a runc downgrade (`runtimeClass: ""` / `runc` / unknown) is NOT isolated (shared kernel,
|
|
91
|
-
* container escape). `e2b` is a Firecracker microVM (isolated). `host`/`ssh`/`adb` and anything unknown ⇒ NOT isolated.
|
|
92
|
-
*/
|
|
93
32
|
export function isIsolatedExecEnv(provider, runtimeClass) {
|
|
94
33
|
if (provider === "e2b")
|
|
95
34
|
return true;
|
|
@@ -99,23 +38,7 @@ export function isIsolatedExecEnv(provider, runtimeClass) {
|
|
|
99
38
|
}
|
|
100
39
|
return false;
|
|
101
40
|
}
|
|
102
|
-
/** The write-capable hand tools the SUP posture gates DIRECTLY (Bash is gated via `shellGate`). Q6 (core 1.161):
|
|
103
|
-
* CC-aligned tool names (was edit_file/write_file). NotebookEdit (core 1.163) is the same fs-mutation class — it
|
|
104
|
-
* edits a `.ipynb` on the real fs, so it MUST be gated too, else a SUP-routed (capability-danger) task could mutate
|
|
105
|
-
* a notebook ungated (the exact "write tool bypasses the SUP gate" hole the review-HIGH closed for Edit/Write).
|
|
106
|
-
* Curated, NOT derived from HAND_TOOL_EFFECTS: that taxonomy is replay-safety (Write="idempotent"; Bash="write" but
|
|
107
|
-
* shell-gated), not "is an fs mutation". CANONICAL names only (core 1.202 retired MultiEdit into an Edit alias —
|
|
108
|
-
* (5.0.0:折叠面退役——重放的旧名调用由 core roster 层响亮 miss,不再折到 "Edit";raw 直比)
|
|
109
|
-
* gated without a stale alias entry here). */
|
|
110
41
|
const WRITE_HAND_TOOLS = new Set(["Edit", "Write", "NotebookEdit"]);
|
|
111
|
-
/**
|
|
112
|
-
* The TaskSpec overrides for the SUP prevention posture (mode=supervisor). Applied via `tightenTaskSpec`
|
|
113
|
-
* (tighten-only — composes with the deployment baseline, never loosens):
|
|
114
|
-
* - `shellGate: "always"` — gate every `Bash` as an irreversible_ask (the verdict's gate-deny on the shell).
|
|
115
|
-
* - `toolPolicy` — ALSO ask on the direct write hand tools `Edit`/`Write` (review HIGH: shellGate alone
|
|
116
|
-
* leaves a non-isolated SUP task able to mutate the real fs via write tools ungated). The worker's existing
|
|
117
|
-
* approval machinery (durable suspend / poll) enforces the `ask` — same precondition as `shellGate:"always"`.
|
|
118
|
-
*/
|
|
119
42
|
export function supPostureOverrides() {
|
|
120
43
|
const ask = (toolName) => ({ action: "ask", message: `${toolName} gated by supervisor routing (capability-danger task)`, decisionReason: "rule" });
|
|
121
44
|
const writeGate = {
|
package/dist/rules-consent.d.ts
CHANGED
|
@@ -107,7 +107,26 @@ export type RuleImportRedeemed = {
|
|
|
107
107
|
detail: string;
|
|
108
108
|
retryable?: true;
|
|
109
109
|
};
|
|
110
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* #340([4763])—— 一次卡道兑付**要兑的是什么**:两个显式臂,永不折叠。
|
|
112
|
+
*
|
|
113
|
+
* 🔴 判别式联合而不是「一个文本 + 一个布尔」:两个臂的文本**含义不同**——候选臂的串是「卡上第几条」的
|
|
114
|
+
* 定位键(对不上 ⇒ `rule_not_offered`,语义一个字不变),编辑臂的串是**人自己写的规则**(由 core 的
|
|
115
|
+
* 卡编辑面校验 + 覆盖门判)。同一个字段两种含义会让下一个读者(和下一次改动)分不清哪条路上该做哪道门。
|
|
116
|
+
*/
|
|
117
|
+
export type CardRuleRedemption =
|
|
118
|
+
/** 人**点了卡上的某条候选**:文本用来在引擎铸的候选表里定位下标。 */
|
|
119
|
+
{
|
|
120
|
+
kind: "offered";
|
|
121
|
+
ruleText: string;
|
|
122
|
+
}
|
|
123
|
+
/** 人在卡上**手改了规则文本**(core 的 card-edit 面:同一条已鉴权确认通道,`editedCandidate`)。 */
|
|
124
|
+
| {
|
|
125
|
+
kind: "edited";
|
|
126
|
+
text: string;
|
|
127
|
+
};
|
|
128
|
+
/** 卡道兑付的结果。`canonical` = **真正落盘**的那条规则文本(编辑臂上它可能与人敲的原字节不同 ——
|
|
129
|
+
* core 会把 `Bash(adb *)` 规范成 `Bash(adb:*)`;界面要回显的是这一份,不是输入框里的那一份)。 */
|
|
111
130
|
export type CardRulePersisted = {
|
|
112
131
|
ok: true;
|
|
113
132
|
rule: string;
|
|
@@ -115,7 +134,25 @@ export type CardRulePersisted = {
|
|
|
115
134
|
alreadyRedeemed: boolean;
|
|
116
135
|
} | {
|
|
117
136
|
ok: false;
|
|
118
|
-
reason: "no-candidates" | "unknown-candidate" | "confirm-refused" | "redeem-refused"
|
|
137
|
+
reason: "no-candidates" | "unknown-candidate" | "confirm-refused" | "redeem-refused"
|
|
138
|
+
/** #340:编辑面**在这只卡上用不了**——卡没有绑定锚(`boundInputHash` 缺席 ⇒ 无从证明「我编辑的
|
|
139
|
+
* 是显示了这条命令的那张卡」),或这台部署没有打开编辑面。与下面那个词刻意分家:这个说的是
|
|
140
|
+
* 「别再渲那个输入框」,那个说的是「你写的这条不行,改一下再来」——壳要做的事完全相反。 */
|
|
141
|
+
| "edit-unsupported"
|
|
142
|
+
/**
|
|
143
|
+
* #340:人写的文本**被引擎的门拒了**——共享校验器不认这个拼写,或它不覆盖本次被裁决的命令
|
|
144
|
+
* (「编辑可以更宽,但不许换成另一条授权」)。
|
|
145
|
+
*
|
|
146
|
+
* 🔴 **这一格不是「重试就好」**(codex 对抗复审 [medium],验真后按实况改词):兑付发生在裁决
|
|
147
|
+
* **落定之后**(那条顺序是硬的 —— 一次没有生效的裁决不该留下一条永久规则),所以拿到这个词时
|
|
148
|
+
* 这张卡已经消费掉了,同一个 `approvalId` 再回决是 404。人的「允许这次」照常生效,只是这一次
|
|
149
|
+
* 没能顺手存下规则;改好的文本要落,得等**下一次同命令的 ask**(与 `rule_input_edited` 逐字
|
|
150
|
+
* 同一条处置)。壳的措辞据此写,别渲成「点这里重试」。
|
|
151
|
+
* 想做到「同一张卡上改了再来」需要一次**裁决之前**的预检,而那要求 core 把编辑面的校验(含它自己
|
|
152
|
+
* 的拼写规范化)导出成一只纯函数 —— 在本层照抄一个更严的校验器会把 `Bash(adb *)` 这条**成因形**
|
|
153
|
+
* 当场误拒。已登记为上游请托,不在本层用第二个解析器凑。
|
|
154
|
+
*/
|
|
155
|
+
| "edit-rejected";
|
|
119
156
|
detail: string;
|
|
120
157
|
};
|
|
121
158
|
/** 一层用户交上来的 settings(HTTP 载荷已 zod 校验过的形)。 */
|
|
@@ -197,11 +234,13 @@ export interface RuleConsentLane {
|
|
|
197
234
|
persistCardRule(input: {
|
|
198
235
|
principal: string;
|
|
199
236
|
toolName: string;
|
|
200
|
-
/** 被裁决的命令**原字节**(与 ask
|
|
237
|
+
/** 被裁决的命令**原字节**(与 ask 上的那份同源;候选臂的规则文本由引擎从它铸,编辑臂的覆盖门也拿它判)。 */
|
|
201
238
|
command: string;
|
|
202
|
-
/**
|
|
203
|
-
|
|
239
|
+
/** 这次要兑的是候选表里的哪一条,还是人手改的自由文本(两个显式臂,见 {@link CardRuleRedemption})。 */
|
|
240
|
+
redemption: CardRuleRedemption;
|
|
204
241
|
toolCallId?: string;
|
|
242
|
+
/** 卡上的实参摘要。编辑臂的**必要条件**:core 要求编辑回显它(「我编辑的是显示了这条命令的那张卡」),
|
|
243
|
+
* 缺席 ⇒ 编辑臂 fail-closed 拒(候选臂不受影响,那条路上它只是记账用)。 */
|
|
205
244
|
boundInputHash?: string;
|
|
206
245
|
scope?: RuleScope;
|
|
207
246
|
}): Promise<CardRulePersisted>;
|