@sema-agent/server 7.125.1 → 7.127.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/MIGRATION.md +229 -2
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +346 -145
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +57 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +32 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +3 -2
- package/deploy/sema-up/chart/templates/server.yaml +2 -1
- package/deploy/sema-up/chart/values.yaml +22 -0
- package/deploy/sema-up/cluster-up.sh +10 -10
- package/deploy/sema-up/kube-up.sh +20 -13
- package/deploy/sema-up/lib/dataroot.sh +1 -1
- package/deploy/sema-up/lib/envf.sh +5 -5
- package/deploy/sema-up/lib/flagshape.sh +5 -5
- package/deploy/sema-up/lib/lanes.py +1 -1
- package/deploy/sema-up/lib/node-addrs.sh +42 -0
- package/deploy/sema-up/lib/parse-machines.py +2 -2
- package/deploy/sema-up/lib/shell-access.sh +1 -1
- package/deploy/sema-up/preflight.sh +4 -4
- package/deploy/sema-up/sema-up.sh +33 -33
- package/deploy/sema-up/smoke.sh +3 -3
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +26 -3
- package/dist/approval-card.d.ts +20 -4
- package/dist/approval-content-kind.d.ts +38 -22
- package/dist/approval-content-kind.js +20 -4
- package/dist/audit.js +3 -0
- package/dist/bench/s1/arms.d.ts +9 -37
- package/dist/bench/s1/arms.js +4 -61
- package/dist/bench/s1/live-deps.d.ts +4 -10
- package/dist/bench/s1/live-deps.js +1 -222
- package/dist/bench/s1/oracle.d.ts +1 -1
- package/dist/bench/s1/reviewer.d.ts +1 -1
- package/dist/bench/s1/row.d.ts +5 -7
- package/dist/bench/s1/row.js +0 -2
- package/dist/bench/s1/run-firm.d.ts +2 -2
- package/dist/bench/s1/run-firm.js +1 -4
- package/dist/bench/s1/runner-ctx.d.ts +17 -29
- package/dist/bench/s1/runner-ctx.js +3 -7
- package/dist/bench/s1/tasks.d.ts +9 -46
- package/dist/bench/s1/tasks.js +4 -63
- package/dist/boot/execution-env.d.ts +38 -26
- package/dist/boot/execution-env.js +87 -41
- package/dist/boot/leader.d.ts +2 -1
- package/dist/boot/memory-boundary.d.ts +1 -1
- package/dist/boot/memory-consolidation.d.ts +37 -3
- package/dist/boot/memory-consolidation.js +18 -2
- package/dist/boot/parked-revive-gate.d.ts +0 -3
- package/dist/boot/parked-revive-gate.js +2 -2
- package/dist/boot/resolve-spec.d.ts +0 -2
- package/dist/boot/resolve-spec.js +12 -17
- package/dist/boot/retention-lane.d.ts +12 -1
- package/dist/boot/retention-lane.js +2 -0
- package/dist/boot/runner-deps.d.ts +7 -6
- package/dist/boot/runner-deps.js +5 -16
- package/dist/boot/session-faces.d.ts +7 -2
- package/dist/boot/session-faces.js +37 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +2 -2
- package/dist/boot/stage-05-execution-env.js +7 -4
- package/dist/boot/stage-06-runners.d.ts +2 -3
- package/dist/boot/stage-06-runners.js +13 -9
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +3 -4
- package/dist/boot/stage-08-reapers.d.ts +2 -3
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +4 -3
- package/dist/boot/web-admin.d.ts +5 -0
- package/dist/boot/web-admin.js +28 -14
- package/dist/brain.d.ts +2 -2
- package/dist/brain.js +1 -1
- package/dist/capabilities/builtin-tools.d.ts +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/hands-lane.d.ts +9 -6
- package/dist/capabilities/hands-lane.js +2 -2
- package/dist/capabilities/memory-notice.d.ts +27 -22
- package/dist/capabilities/memory-notice.js +15 -17
- package/dist/capabilities/scenarios.d.ts +9 -14
- package/dist/capabilities/scenarios.js +5 -50
- package/dist/capability-facts.d.ts +22 -11
- package/dist/capability-facts.js +13 -6
- package/dist/config-catalog.js +14 -5
- package/dist/config-provider.d.ts +5 -1
- package/dist/config-provider.js +23 -4
- package/dist/config-types.d.ts +19 -2
- package/dist/config.js +41 -6
- package/dist/deployment-governance.d.ts +19 -21
- package/dist/deployment-governance.js +7 -8
- package/dist/env-name-allowlist-knobs.d.ts +5 -5
- package/dist/env-name-allowlist-knobs.js +1 -0
- package/dist/execution-lane-caps.d.ts +12 -0
- package/dist/execution-lane-caps.js +18 -4
- package/dist/fleet/subagent-tail-bus.js +15 -5
- package/dist/hooks/hook-runner.d.ts +84 -30
- package/dist/hooks/hook-runner.js +74 -41
- package/dist/http/admission.js +17 -10
- package/dist/http/durable-run-leg.d.ts +5 -4
- package/dist/http/durable-run-leg.js +2 -4
- package/dist/http/idempotency.d.ts +28 -6
- package/dist/http/idempotency.js +10 -2
- package/dist/http/principal-gate.d.ts +14 -0
- package/dist/http/principal-gate.js +7 -0
- package/dist/http/request-guards.d.ts +2 -1
- package/dist/http/request-guards.js +14 -14
- package/dist/http/resume-legs.d.ts +2 -2
- package/dist/http/resume-legs.js +20 -10
- package/dist/http/route-ctx.d.ts +15 -7
- package/dist/http/routes/a2a-serve.js +11 -7
- package/dist/http/routes/approvals-assistant.js +18 -3
- package/dist/http/routes/background.d.ts +1 -1
- package/dist/http/routes/background.js +1 -1
- package/dist/http/routes/capabilities.js +10 -2
- package/dist/http/routes/mcp-reconnect.js +1 -1
- package/dist/http/routes/runs.d.ts +4 -3
- package/dist/http/routes/runs.js +35 -14
- package/dist/http/routes/session-sync.js +14 -11
- package/dist/http/routes/sessions.js +9 -9
- package/dist/http/routes/side-query.js +6 -19
- package/dist/http/routes/tasks.js +29 -19
- package/dist/http/routes/workflows.js +8 -0
- package/dist/http/server.d.ts +4 -0
- package/dist/http/server.js +3 -5
- package/dist/http/submit-replay.d.ts +15 -8
- package/dist/http/submit-replay.js +6 -0
- package/dist/http/wire-types.d.ts +7 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal-run-env.d.ts +30 -0
- package/dist/internal-run-env.js +11 -0
- package/dist/leader/wire.js +3 -2
- package/dist/lsp/manager.js +1 -1
- package/dist/memory-scope.d.ts +19 -7
- package/dist/memory-scope.js +25 -8
- package/dist/observability/fail-open.d.ts +31 -3
- package/dist/observability/fail-open.js +31 -3
- package/dist/observability/metrics.d.ts +0 -1
- package/dist/observability/metrics.js +1 -4
- package/dist/observability/run-terminal.js +72 -4
- package/dist/permission-rule-vocab.d.ts +3 -2
- package/dist/permission-rule-vocab.js +1 -1
- package/dist/plan-cache-probe.d.ts +3 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
- package/dist/plugins/caching-session-store.d.ts +42 -22
- package/dist/plugins/caching-session-store.js +19 -9
- package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
- package/dist/plugins/checkpoint-store-sql.js +57 -11
- package/dist/plugins/file-run-store.d.ts +25 -21
- package/dist/plugins/file-run-store.js +70 -34
- package/dist/plugins/k8s-exec-protocol.js +3 -0
- package/dist/plugins/local-checkpoint-store.d.ts +8 -5
- package/dist/plugins/local-checkpoint-store.js +5 -15
- package/dist/plugins/local-session-store.d.ts +66 -7
- package/dist/plugins/local-session-store.js +168 -101
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +1 -1
- package/dist/plugins/memory-run-store.d.ts +19 -3
- package/dist/plugins/memory-run-store.js +38 -3
- package/dist/plugins/one-time-migrations.d.ts +165 -39
- package/dist/plugins/one-time-migrations.js +51 -144
- package/dist/plugins/pg-pool.js +11 -1
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +9 -5
- package/dist/plugins/remote-env-adb.d.ts +3 -0
- package/dist/plugins/remote-env-adb.js +1 -0
- package/dist/plugins/remote-env-device.d.ts +3 -0
- package/dist/plugins/remote-env-device.js +1 -0
- package/dist/plugins/remote-env-e2b.d.ts +4 -0
- package/dist/plugins/remote-env-e2b.js +2 -1
- package/dist/plugins/remote-env-host.d.ts +23 -1
- package/dist/plugins/remote-env-host.js +20 -2
- package/dist/plugins/remote-env-k8s.d.ts +264 -41
- package/dist/plugins/remote-env-k8s.js +551 -82
- package/dist/plugins/remote-env-local-docker.d.ts +4 -0
- package/dist/plugins/remote-env-local-docker.js +2 -1
- package/dist/plugins/remote-env-ssh.d.ts +3 -0
- package/dist/plugins/remote-env-ssh.js +1 -0
- package/dist/plugins/retention-store-sql.d.ts +4 -1
- package/dist/plugins/retention-store-sql.js +11 -9
- package/dist/plugins/run-store-sql.d.ts +44 -6
- package/dist/plugins/run-store-sql.js +92 -27
- package/dist/plugins/session-capture-record-store-sql.d.ts +2 -3
- package/dist/plugins/session-store.d.ts +4 -3
- package/dist/plugins/session-store.js +1 -1
- package/dist/plugins/sql-driver.d.ts +2 -1
- package/dist/plugins/store-backend.d.ts +12 -5
- package/dist/plugins/store-backend.js +13 -4
- package/dist/plugins/store-contracts.d.ts +20 -3
- package/dist/plugins/store-open-wait.js +3 -0
- package/dist/plugins/tidb-pool.js +15 -3
- package/dist/plugins/tidb-session-storage.js +3 -2
- package/dist/plugins/tidb-session-store.d.ts +2 -0
- package/dist/plugins/tidb-session-store.js +6 -2
- package/dist/request-key-closure.d.ts +32 -7
- package/dist/request-key-closure.js +25 -9
- package/dist/router/route-orchestration.d.ts +3 -11
- package/dist/router/route-orchestration.js +0 -4
- package/dist/rule-entry-list.js +0 -4
- package/dist/rules-consent.js +3 -0
- package/dist/run-acceptance.d.ts +51 -0
- package/dist/run-acceptance.js +27 -0
- package/dist/run-local.d.ts +5 -4
- package/dist/run-local.js +23 -17
- package/dist/runs.d.ts +7 -11
- package/dist/runs.js +6 -16
- package/dist/sandbox-egress-policy.d.ts +61 -0
- package/dist/sandbox-egress-policy.js +235 -0
- package/dist/security.d.ts +7 -0
- package/dist/security.js +5 -0
- package/dist/server-secret-env.d.ts +10 -4
- package/dist/server-secret-env.js +4 -1
- package/dist/session-error-wire.d.ts +2 -2
- package/dist/session-incarnation.d.ts +46 -2
- package/dist/session-incarnation.js +14 -3
- package/dist/session-sync-kernel.d.ts +10 -3
- package/dist/session-titler.d.ts +5 -3
- package/dist/spec-fields.d.ts +24 -8
- package/dist/spec-fields.js +21 -2
- package/dist/task-workflow.d.ts +6 -4
- package/dist/task-workflow.js +3 -2
- package/dist/tool-approval.d.ts +78 -6
- package/dist/tool-approval.js +7 -3
- package/dist/trace/core-keyset-guard.d.ts +6 -5
- package/dist/trace/engine-notice-wire.d.ts +26 -1
- package/dist/trace/engine-notice-wire.js +7 -2
- package/dist/trace/ledger-events.d.ts +9 -3
- package/dist/trace/ledger-sink.d.ts +7 -0
- package/dist/trace/ledger-sink.js +14 -4
- package/dist/trace/project.d.ts +57 -11
- package/dist/trace/project.js +54 -8
- package/dist/trace/self-minted-wire.d.ts +94 -2
- package/dist/trace/self-minted-wire.js +28 -0
- package/dist/trace/tool-output-media.d.ts +26 -0
- package/dist/trace/tool-output-media.js +57 -0
- package/dist/trace/wire-projection-faces.d.ts +7 -1
- package/dist/trace/wire-projection-faces.js +2 -1
- package/dist/web-admin-client/apply-effective.d.ts +13 -7
- package/dist/web-admin-client/apply-effective.js +11 -26
- package/dist/web-admin-client/apply-ledger.d.ts +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +39 -4
- package/dist/web-admin-client/candidate-gate.js +9 -2
- package/dist/web-admin-client/facade.d.ts +2 -2
- package/dist/web-admin-client/http-client.d.ts +3 -1
- package/dist/web-admin-client/http-client.js +4 -3
- package/dist/web-admin-client/read-warnings.d.ts +33 -5
- package/dist/web-admin-client/read-warnings.js +41 -6
- package/dist/web-admin-client/types.d.ts +11 -5
- package/dist/workspace-cache.d.ts +45 -0
- package/dist/workspace-cache.js +30 -0
- package/dist/workspace-space-commit.d.ts +116 -0
- package/dist/workspace-space-commit.js +117 -0
- package/dist/workspace-space.d.ts +75 -9
- package/dist/workspace-space.js +52 -1
- package/package.json +4 -4
- package/dist/boot/session-incarnation-seat.d.ts +0 -41
- package/dist/boot/session-incarnation-seat.js +0 -69
- package/dist/capabilities/code-review-council.d.ts +0 -49
- package/dist/capabilities/code-review-council.js +0 -111
- package/dist/capabilities/collab-wire.d.ts +0 -36
- package/dist/capabilities/collab-wire.js +0 -2
- package/dist/capabilities/collab-workflows.d.ts +0 -47
- package/dist/capabilities/collab-workflows.js +0 -90
- package/dist/capabilities/prompts/code-review.d.ts +0 -25
- package/dist/capabilities/prompts/code-review.js +0 -39
- package/dist/capabilities/prompts/team.d.ts +0 -10
- package/dist/capabilities/prompts/team.js +0 -6
- package/dist/capabilities/team.d.ts +0 -113
- package/dist/capabilities/team.js +0 -70
package/dist/http/resume-legs.js
CHANGED
|
@@ -6,10 +6,9 @@ const sessionFaceUnavailable = async (sessionId) => {
|
|
|
6
6
|
throw new Error(`parked decide cannot verify which occupant session ${sessionId} belongs to: this deployment's session store has no sessionFaceOf read (every built-in store has one) — refusing rather than resuming unverified`);
|
|
7
7
|
};
|
|
8
8
|
import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
|
|
9
|
-
import {
|
|
9
|
+
import { parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
|
|
10
10
|
import { failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane } from "../terminal.js";
|
|
11
11
|
import { buildCancelledLedgerRow, createRunAbort, mintCancelledResult } from "../run-cancel-context.js";
|
|
12
|
-
import {} from "../host-decision.js";
|
|
13
12
|
import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
|
|
14
13
|
import { recordResumeResourceWindow } from "../resource-window.js";
|
|
15
14
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
@@ -23,10 +22,10 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
|
|
|
23
22
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
24
23
|
import { withModelReachScope } from "../model-reach-scope.js";
|
|
25
24
|
import { createInstructionSelectionLeg } from "../instruction-selection.js";
|
|
26
|
-
import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
25
|
+
import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
27
26
|
import { redactSecrets } from "../trace/redact.js";
|
|
28
27
|
import { buildRunObjectivePreview } from "../user-authored-text.js";
|
|
29
|
-
import { createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
|
|
28
|
+
import { createForwardAppendFailedNote, createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
|
|
30
29
|
import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
|
|
31
30
|
import { reasoningDeltaBytes, reasoningSegmentEventId } from "../trace/model-segment.js";
|
|
32
31
|
import { errorText } from "../observability/err-text.js";
|
|
@@ -49,6 +48,7 @@ import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
|
|
|
49
48
|
import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
|
|
50
49
|
import { storedBodyReplayRefusal } from "./stored-body-replay.js";
|
|
51
50
|
import { httpErrorCode } from "./send.js";
|
|
51
|
+
import { createRunAcceptance } from "../run-acceptance.js";
|
|
52
52
|
function isCheckpointReopenedFailure(r) {
|
|
53
53
|
const code = failureCodeOf(r.terminal);
|
|
54
54
|
return code === "resume.env_failed" || code === "resume.tool_unavailable" || code === "resume.tool_contract_mismatch";
|
|
@@ -68,6 +68,7 @@ const WORKER_SWAP_REDEEMABLE = {
|
|
|
68
68
|
ledger_event_unknown: true,
|
|
69
69
|
real_approval_damaged: false,
|
|
70
70
|
real_approval_forged: false,
|
|
71
|
+
requirements_damaged: false,
|
|
71
72
|
read_face_damaged: false,
|
|
72
73
|
read_face_forged: false,
|
|
73
74
|
write_face_damaged: false,
|
|
@@ -162,7 +163,7 @@ function notResumableAnswer() {
|
|
|
162
163
|
return { status: 409, body: { error: "run is not in a resumable (suspended) state (already resumed, decided, or expired)", errorCode: "conflict.not_resumable" } };
|
|
163
164
|
}
|
|
164
165
|
export function createResumeLegs(ctx) {
|
|
165
|
-
const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
|
|
166
|
+
const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
|
|
166
167
|
const resumeFleetScope = (req, auth) => req ? (gatedPrincipal(req, deps.config) ?? "default") : (auth.principal ?? "default");
|
|
167
168
|
async function resumeCheckpoint(sessionId, decision, reason, hostDecision, req, answer, binding, onResumeCommitted, acceptEarly, decider) {
|
|
168
169
|
const cs = deps.checkpointStore;
|
|
@@ -769,10 +770,16 @@ export function createResumeLegs(ctx) {
|
|
|
769
770
|
if (!rs || !taskId)
|
|
770
771
|
return;
|
|
771
772
|
const t = e.type;
|
|
772
|
-
const warnAppend = (kind) => (err) =>
|
|
773
|
+
const warnAppend = (kind) => (err) => {
|
|
774
|
+
createForwardAppendFailedNote(kind)(err);
|
|
775
|
+
deps.logger?.warn?.("forward_event_append_failed", { type: kind, taskId, err: err instanceof Error ? err.message : String(err) });
|
|
776
|
+
};
|
|
773
777
|
if (t === "task_progress") {
|
|
774
778
|
void append("task_progress", taskProgressEventData(e)).catch(warnAppend(t));
|
|
775
779
|
}
|
|
780
|
+
else if (t === "task_started") {
|
|
781
|
+
void append("task_started", taskStartedEventData(e)).catch(warnAppend(t));
|
|
782
|
+
}
|
|
776
783
|
else if (t === "tool_start") {
|
|
777
784
|
void append("tool_start", toolStartEventData(e)).catch(warnAppend(t));
|
|
778
785
|
}
|
|
@@ -901,6 +908,9 @@ export function createResumeLegs(ctx) {
|
|
|
901
908
|
case "message_committed":
|
|
902
909
|
await anchor.onMessageCommitted(ev.role, ev.entryId);
|
|
903
910
|
break;
|
|
911
|
+
case "task_started":
|
|
912
|
+
await append("task_started", taskStartedEventData(ev));
|
|
913
|
+
break;
|
|
904
914
|
case "task_progress":
|
|
905
915
|
await append("task_progress", taskProgressEventData(ev));
|
|
906
916
|
break;
|
|
@@ -1085,7 +1095,6 @@ export function createResumeLegs(ctx) {
|
|
|
1085
1095
|
clearInterval(resumeHeartbeat);
|
|
1086
1096
|
resumeLegLive = false;
|
|
1087
1097
|
}
|
|
1088
|
-
evictIfConflict(deps.runner, sessionId, result);
|
|
1089
1098
|
const checkpointReopened = reopenConfirmedPark;
|
|
1090
1099
|
const sealed = sealResultForExit(result);
|
|
1091
1100
|
const resumePlane = outwardPlane(sealed.terminal);
|
|
@@ -1214,7 +1223,7 @@ export function createResumeLegs(ctx) {
|
|
|
1214
1223
|
const { spec, engineNotices } = resolved;
|
|
1215
1224
|
const hostSpec = { ...spec, objective, sessionId };
|
|
1216
1225
|
const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
|
|
1217
|
-
prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
|
|
1226
|
+
prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, acceptance: createRunAcceptance({}, { objective }), ...(engineNotices.length > 0 ? { engineNotices } : {}) },
|
|
1218
1227
|
fleetScope: resumeFleetScope(req, auth),
|
|
1219
1228
|
elicitOwner: principal ?? null,
|
|
1220
1229
|
source: "workflow_parked_decide",
|
|
@@ -1377,7 +1386,7 @@ export function createResumeLegs(ctx) {
|
|
|
1377
1386
|
return refuse(refuseStoredBodyReplay(sessionId, replay));
|
|
1378
1387
|
return { ok: true, prepared: { sessionId, token, cp, ctx: ctxRead.ctx } };
|
|
1379
1388
|
}
|
|
1380
|
-
async function drivePlanReview(prepared, decision, editedPlan, reason, req, acceptEarly, permissionModeAfter) {
|
|
1389
|
+
async function drivePlanReview(prepared, decision, editedPlan, reason, hostDecision, req, acceptEarly, permissionModeAfter) {
|
|
1381
1390
|
const { sessionId, token, cp, ctx } = prepared;
|
|
1382
1391
|
if ((await deps.checkpointStore.findPendingTokenBySession(sessionId, undefined, { gateKinds: ["plan_review"] })) !== token)
|
|
1383
1392
|
return notResumableAnswer();
|
|
@@ -1399,6 +1408,7 @@ export function createResumeLegs(ctx) {
|
|
|
1399
1408
|
decision,
|
|
1400
1409
|
...(editedPlan !== undefined ? { editedPlan } : {}),
|
|
1401
1410
|
...(reason ? { reason } : {}),
|
|
1411
|
+
hostDecision,
|
|
1402
1412
|
};
|
|
1403
1413
|
const out = await driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx: { sessionId, ctx: rebuildCtx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
|
|
1404
1414
|
return out;
|
|
@@ -1406,7 +1416,7 @@ export function createResumeLegs(ctx) {
|
|
|
1406
1416
|
const routeCtxBase = {
|
|
1407
1417
|
deps,
|
|
1408
1418
|
registry: { idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters },
|
|
1409
|
-
helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk },
|
|
1419
|
+
helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk },
|
|
1410
1420
|
local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal(), capabilities: createCapabilitiesLocal() },
|
|
1411
1421
|
legs: { prepareSpec, finalizeTaskResult, resumeCheckpoint, driveResumeIntoRunLog, resumePreempted, resumeWake, preparePlanReview, drivePlanReview },
|
|
1412
1422
|
};
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -72,11 +72,11 @@ export interface RouteHelpers {
|
|
|
72
72
|
usageWindowDenied(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
73
73
|
safeDecode(seg: string): string | null;
|
|
74
74
|
isFleetWide(req: IncomingMessage): boolean;
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*
|
|
79
|
-
|
|
75
|
+
/** run 面的属主门(谓词 `runOwnerReadable`;非属主 ⇒ 404 `not_found.run` + 长文,与 run 读口的 unknown 臂同码同串,[ref])。 */
|
|
76
|
+
runOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null): boolean;
|
|
77
|
+
/** 会话面的属主门(谓词 `sessionOwnerReadable`,S-1452:没有身份边界的部署不钉人,与会话列表同读;非属主 ⇒ 404 `not_found.session` +
|
|
78
|
+
* {@link SESSION_NOT_FOUND_MESSAGE},与会话读口的 unknown 臂同码同串 —— G52 [ref] / S-989)。每一个按会话寻址的属主判都走它。 */
|
|
79
|
+
sessionOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null): boolean;
|
|
80
80
|
sessionOwnerScope(req: IncomingMessage): {
|
|
81
81
|
fleetWide: boolean;
|
|
82
82
|
gateOwner: string | null;
|
|
@@ -140,6 +140,12 @@ export interface PreparedTaskSubmission {
|
|
|
140
140
|
* 成功点**必须**把它交给 `commitPlacement`(唯一写入属主;类级机器门钉住每个调用点都接了)。
|
|
141
141
|
*/
|
|
142
142
|
bindingIntent?: import("./device-placement.js").DevicePlacementIntent;
|
|
143
|
+
/**
|
|
144
|
+
* 这次提交的**受理座**(`run-acceptance.ts`):标题器只在引擎受理之后起。提交门(`prepareSpec`)随 spec 一起铸;
|
|
145
|
+
* 三条提交腿只喂它(持流的形逐帧 `observe`,终局落行之后 `settle` 一次),不再各自判「什么时候起标题」。
|
|
146
|
+
* 不经提交门的内部车道(`/decide` 的宿主新 turn)铸一只不带标题器的座:不起标题。
|
|
147
|
+
*/
|
|
148
|
+
acceptance: import("../run-acceptance.js").RunAcceptance;
|
|
143
149
|
}
|
|
144
150
|
/**
|
|
145
151
|
* 一条会话的**续跑重建输入**(`checkpoint_ctx` 的行体)—— 提交腿写一次,续跑腿读它重建 spec。
|
|
@@ -384,7 +390,7 @@ export interface RouteLegs {
|
|
|
384
390
|
liveStream?: boolean;
|
|
385
391
|
}): Promise<PreparedTaskSubmission | null>;
|
|
386
392
|
/** 同步腿的终局记账(计费/配额/指标),tasks 域用。 */
|
|
387
|
-
finalizeTaskResult(result: TaskResult, principal: string | undefined, objective: string
|
|
393
|
+
finalizeTaskResult(result: TaskResult, principal: string | undefined, objective: string): void;
|
|
388
394
|
/** approvals 决策腿:session → pending checkpoint → markResuming CAS → 驱动续跑。
|
|
389
395
|
* `hostDecision` = **这次结算的事实**(谁结束了这次等待 + 通道自报的归属),必填:本腿有两个调用方
|
|
390
396
|
* (HTTP `/decide` = 人;内部 D-D SLA deny-sweep = 窗到期),而函数内部分辨不出谁在叫它 —— 必填参数把
|
|
@@ -461,7 +467,9 @@ export interface RouteLegs {
|
|
|
461
467
|
readonly prepared: PreparedPlanReview;
|
|
462
468
|
}>;
|
|
463
469
|
/** plan_review 三态腿的**驱动段**(S-609):只吃 prepare 的结果,用它选定的 token,不再选卡、不再读 ctx。 */
|
|
464
|
-
drivePlanReview(prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined,
|
|
470
|
+
drivePlanReview(prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined,
|
|
471
|
+
/** core 7.49.0 #1396:决策事实(谁决定的 + 归属),语义属主 = `resume-legs.ts` 的同名形参顶注。 */
|
|
472
|
+
hostDecision: HostDecision, req: IncomingMessage | undefined,
|
|
465
473
|
/** [ref] 件 S-1:透传给 {@link DriveResumeArgs.acceptEarly}(200 受理语义)。HTTP `/plan_review` 腿传 `true`。 */
|
|
466
474
|
acceptEarly?: boolean,
|
|
467
475
|
/** S-433:批准之后这条会话继续用的 permission mode(闭集 `PLAN_APPROVE_PERMISSION_MODES_AFTER`;只在把计划带出 plan 的决定
|
|
@@ -123,7 +123,11 @@ function asNonEmptyString(v, what, max) {
|
|
|
123
123
|
throw new RpcError(JSONRPC_INVALID_PARAMS, `${what} must be at most ${max} characters`);
|
|
124
124
|
return v;
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
function asString(v, what) {
|
|
127
|
+
if (typeof v !== "string")
|
|
128
|
+
throw new RpcError(JSONRPC_INVALID_PARAMS, `${what} must be a string`);
|
|
129
|
+
return v;
|
|
130
|
+
}
|
|
127
131
|
const MAX_MESSAGE_ID_CHARS = 200;
|
|
128
132
|
const MAX_TASK_ID_CHARS = 128;
|
|
129
133
|
const MAX_SKILL_ID_CHARS = 128;
|
|
@@ -224,7 +228,7 @@ async function handleMessageSend({ req, res, ctx, params, serve, runStore }) {
|
|
|
224
228
|
if (message.role !== "user")
|
|
225
229
|
throw new RpcError(JSONRPC_INVALID_PARAMS, `params.message.role must be "user" (this is the inbound leg — an agent-role message has no meaning here)`);
|
|
226
230
|
const objective = objectiveFromParts(message.parts);
|
|
227
|
-
const contextId = message.contextId === undefined ? undefined :
|
|
231
|
+
const contextId = message.contextId === undefined ? undefined : asString(message.contextId, "params.message.contextId");
|
|
228
232
|
const messageId = asNonEmptyString(message.messageId, "params.message.messageId", MAX_MESSAGE_ID_CHARS);
|
|
229
233
|
const skill = skillFromMetadata(message.metadata, serve);
|
|
230
234
|
const skillId = skill?.id;
|
|
@@ -238,19 +242,19 @@ async function handleMessageSend({ req, res, ctx, params, serve, runStore }) {
|
|
|
238
242
|
.update(skillId ?? "")
|
|
239
243
|
.digest("hex");
|
|
240
244
|
const submitter = caller.principal ?? undefined;
|
|
241
|
-
const
|
|
245
|
+
const idem = { key: idemKeyOf(fingerprint, caller, "run"), bodyFingerprint: fingerprint };
|
|
242
246
|
const body = {
|
|
243
247
|
objective,
|
|
244
248
|
taskId: durableIdempotencyTaskId(idemIdentityOf(caller), fingerprint),
|
|
245
249
|
...(contextId !== undefined ? { sessionId: contextId } : {}),
|
|
246
250
|
...(scenario !== undefined ? { scenario } : {}),
|
|
247
251
|
};
|
|
248
|
-
const keyed = (await runStore.getRunByIdem(
|
|
249
|
-
const replay = keyed ? await replayOfExistingRun(keyed, { principal: submitter, clientTaskId: body.taskId }, "durable", deps, runStore) : undefined;
|
|
252
|
+
const keyed = (await runStore.getRunByIdem(idem.key).catch(() => undefined)) ?? (await runStore.getRun(body.taskId).catch(() => undefined));
|
|
253
|
+
const replay = keyed ? await replayOfExistingRun(keyed, { principal: submitter, clientTaskId: body.taskId, idem }, "durable", deps, runStore) : undefined;
|
|
250
254
|
if (keyed && replay?.status === 202) {
|
|
251
255
|
return taskFromRun(keyed, isStaleRun(keyed, deps.config.runStaleSec));
|
|
252
256
|
}
|
|
253
|
-
const inflight = replay === undefined ? ctx.registry.idemCache.peek(inflightKeyOf(
|
|
257
|
+
const inflight = replay === undefined ? ctx.registry.idemCache.peek(inflightKeyOf(idem, "durable", body.taskId)) : undefined;
|
|
254
258
|
let receipt;
|
|
255
259
|
if (replay !== undefined) {
|
|
256
260
|
receipt = replay;
|
|
@@ -271,7 +275,7 @@ async function handleMessageSend({ req, res, ctx, params, serve, runStore }) {
|
|
|
271
275
|
throw rpcErrorFromTypedFailure(typed, skillId);
|
|
272
276
|
if (!prepared)
|
|
273
277
|
return undefined;
|
|
274
|
-
receipt = await createDurableRun(req, res, ctx, prepared,
|
|
278
|
+
receipt = await createDurableRun(req, res, ctx, prepared, idem);
|
|
275
279
|
}
|
|
276
280
|
if (!receipt)
|
|
277
281
|
return undefined;
|
|
@@ -31,10 +31,10 @@ const DECIDE_BODY_DECLARED_KEYS = [
|
|
|
31
31
|
const DECIDE_PROOF_COVERED_KEYS = ["answer", "boundCallId", "boundInputHash", "decision", "reason"];
|
|
32
32
|
const DECIDE_NOT_IN_PROOF_KEYS = ["checkpointToken", "decidedBy", "remember", "updatedInput"];
|
|
33
33
|
const PLAN_REVIEW_BODY_DECLARED_KEYS = [
|
|
34
|
-
"boundCallId", "decision", "editedPlan", "permissionModeAfter", "reason",
|
|
34
|
+
"boundCallId", "decidedBy", "decision", "editedPlan", "permissionModeAfter", "reason",
|
|
35
35
|
];
|
|
36
36
|
const PLAN_REVIEW_PROOF_COVERED_KEYS = ["boundCallId", "decision", "editedPlan", "permissionModeAfter", "reason"];
|
|
37
|
-
const PLAN_REVIEW_NOT_IN_PROOF_KEYS = [];
|
|
37
|
+
const PLAN_REVIEW_NOT_IN_PROOF_KEYS = ["decidedBy"];
|
|
38
38
|
const PROOF_SLOT_OF = {
|
|
39
39
|
answer: "answer",
|
|
40
40
|
boundCallId: "boundCallId",
|
|
@@ -522,6 +522,21 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
522
522
|
sendError(res, 400, "request.field_conflict", "decision 'edit' requires a non-empty editedPlan (the operator's revised plan)");
|
|
523
523
|
return;
|
|
524
524
|
}
|
|
525
|
+
const decidedByReading = readDecidedBy(body.decidedBy, deps.config.directDoorActive);
|
|
526
|
+
switch (decidedByReading.kind) {
|
|
527
|
+
case "not_in_proof":
|
|
528
|
+
sendError(res, 400, "decided_by_not_in_proof", DECIDED_BY_NOT_IN_PROOF);
|
|
529
|
+
return;
|
|
530
|
+
case "outside_closed_set":
|
|
531
|
+
sendError(res, 400, "request.field_invalid", DECIDED_BY_OUTSIDE_CLOSED_SET);
|
|
532
|
+
return;
|
|
533
|
+
case "accepted":
|
|
534
|
+
break;
|
|
535
|
+
default: {
|
|
536
|
+
const unreachable = decidedByReading;
|
|
537
|
+
throw new Error(`unhandled decidedBy reading: ${JSON.stringify(unreachable)}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
525
540
|
if (decision === "edit" && !isWellFormedUnicode(body.editedPlan)) {
|
|
526
541
|
sendError(res, 400, "request.field_invalid", "editedPlan must be well-formed Unicode text (a lone surrogate cannot be signed unambiguously)");
|
|
527
542
|
return;
|
|
@@ -626,7 +641,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
626
641
|
return;
|
|
627
642
|
}
|
|
628
643
|
}
|
|
629
|
-
const out = await drivePlanReview(plan.prepared, decision, decision === "edit" ? body.editedPlan : undefined, typeof body.reason === "string" ? body.reason : undefined, req, true, isPlanApprovePermissionModeAfter(body.permissionModeAfter) ? body.permissionModeAfter : undefined);
|
|
644
|
+
const out = await drivePlanReview(plan.prepared, decision, decision === "edit" ? body.editedPlan : undefined, typeof body.reason === "string" ? body.reason : undefined, hostDecisionFacts(decidedByReading.decidedBy, deciderPrincipal), req, true, isPlanApprovePermissionModeAfter(body.permissionModeAfter) ? body.permissionModeAfter : undefined);
|
|
630
645
|
sendResumeOutcome(res, out, deps.logger);
|
|
631
646
|
return;
|
|
632
647
|
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* `rateLimited`。能力位 `background: { listFace, exitFaces }` 与两口的 501 从同一个谓词推导(会话属主面在场)。
|
|
30
30
|
*
|
|
31
31
|
* 🔴 **停止口在无凭据写门内**(`credentialGated: ["POST"]`;车OL F1 = 7.102.0 合并复审 gpt-6 腿 [high]):门的两个合取项它都中
|
|
32
|
-
* —— ① 授权的唯一输入是 principal 头(属主判 `
|
|
32
|
+
* —— ① 授权的唯一输入是 principal 头(属主判 `sessionOwnerOk` 与 operator 臂 `explicitOperatorOk` 读的都是 `gatedPrincipal`,
|
|
33
33
|
* 无 service 凭证的部署上它收调用方自报的头);② 不可撤销、爆炸半径跨租户(知道会话 id + 自报它的属主 ⇒ 停掉那个会话的后台
|
|
34
34
|
* 进程,`includeRetained:true` 连显式 retain 的活服务一起停)。于是无 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` 的部署上它与
|
|
35
35
|
* 其余改写门同码同句 503 `auth.service_token_required`(在路由之前,`dispatch.ts` 那一道),同一只会话级收敛函数的另一个调用点
|
|
@@ -68,7 +68,7 @@ export async function handleBackground(req, res, match, ctx) {
|
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
const isOperator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
71
|
-
if (!isOperator && !ctx.helpers.
|
|
71
|
+
if (!isOperator && !ctx.helpers.sessionOwnerOk(req, res, owner))
|
|
72
72
|
return;
|
|
73
73
|
const scope = owner ?? "default";
|
|
74
74
|
switch (id) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PERMISSION_MODES, expandTiers, reasoningEffortLevelsOf, routePairingStatus } from "@sema-agent/core";
|
|
2
2
|
import { modelImageInput, isAtMentionable, selectableCatalog } from "../../model-select.js";
|
|
3
3
|
import { buildDroppedEntriesWire } from "../../web-admin-client/read-warnings.js";
|
|
4
|
+
import { buildCandidateRejectedWire } from "../../web-admin-client/candidate-gate.js";
|
|
4
5
|
import { modelKeyPoisonOf, resolveModelApiKey } from "../../key-resolver.js";
|
|
5
6
|
import { projectSealedKeyCapability } from "../../sealed-key.js";
|
|
6
7
|
import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
|
|
@@ -205,7 +206,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
205
206
|
manualCompact: facts.durableRuns(deps).available,
|
|
206
207
|
configCatalog: facts.configCatalog(deps).available,
|
|
207
208
|
sealedKey: projectSealedKeyCapability(deps.sealedKeyPublic),
|
|
208
|
-
workspaceSync: ((
|
|
209
|
+
workspaceSync: ((space, cache) => space.available || cache.available ? { ...(space.available ? space.policy : {}), ...(cache.available ? { cache: cache.cache } : {}) } : undefined)(facts.workspaceSpace(deps.config), facts.workspaceCache(deps.config)),
|
|
209
210
|
}));
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
@@ -256,7 +257,14 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
256
257
|
}));
|
|
257
258
|
const nonAlias = Object.entries(deps.config.models).filter(([n]) => n !== "default");
|
|
258
259
|
const defaultName = (nonAlias.find(([, m]) => m === deps.config.model) ?? nonAlias.find(([, m]) => m.id === deps.config.model.id))?.[0];
|
|
259
|
-
|
|
260
|
+
const candidateRejection = deps.candidateRejectionState?.();
|
|
261
|
+
sendJson(res, 200, {
|
|
262
|
+
models,
|
|
263
|
+
default: deps.config.model.id,
|
|
264
|
+
defaultModel: { id: deps.config.model.id, ...(defaultName ? { name: defaultName } : {}) },
|
|
265
|
+
droppedEntries: buildDroppedEntriesWire(deps.config.droppedCatalogEntries),
|
|
266
|
+
...(candidateRejection !== undefined ? { candidateRejected: buildCandidateRejectedWire(candidateRejection) } : {}),
|
|
267
|
+
});
|
|
260
268
|
return;
|
|
261
269
|
}
|
|
262
270
|
default:
|
|
@@ -94,7 +94,7 @@ export async function handleMcpReconnect(req, res, match, ctx) {
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
const isOperator = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
97
|
-
if (!isOperator && !ctx.helpers.
|
|
97
|
+
if (!isOperator && !ctx.helpers.sessionOwnerOk(req, res, owner))
|
|
98
98
|
return;
|
|
99
99
|
const activeTaskId = await runStore.getActiveTaskId(sessionId);
|
|
100
100
|
const live = activeTaskId === undefined ? undefined : ctx.registry.steerableRuns.get(activeTaskId);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { type RunIdemBinding } from "../../plugins/store-contracts.js";
|
|
2
3
|
import { ActorAssertionWire } from "@sema-agent/settings-schema";
|
|
3
4
|
import type { RouteCtx, PreparedTaskSubmission, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
4
5
|
/** 行上的 `decision_actor`(裸 JSON)投成回显形;形不认 ⇒ undefined(调用点省略该键,不 500)。
|
|
@@ -31,9 +32,9 @@ export declare function handleRunVerbs(req: IncomingMessage, res: ServerResponse
|
|
|
31
32
|
* 防御性复检(缺席 ⇒ 501),但两个调用方都在自己的入口先判过,那才是各自面上的正确错误形。
|
|
32
33
|
*/
|
|
33
34
|
export declare function createDurableRun(req: IncomingMessage, res: ServerResponse, ctx: RouteCtx, prepared: PreparedTaskSubmission,
|
|
34
|
-
/**
|
|
35
|
-
* +
|
|
36
|
-
|
|
35
|
+
/** 这次提交的账本绑定(键 `idemKeyOf(…, "run")` + 请求体指纹:`POST /v1/runs` = `Idempotency-Key` 头 + 线上收到的体;A2A =
|
|
36
|
+
* `message.messageId` + 内容指纹)—— 落 `task_run.idem_key` / `idem_body_fingerprint`,键同时是同进程在飞合流的键。undefined = 调用方没带键。 */
|
|
37
|
+
idem: RunIdemBinding | undefined): Promise<{
|
|
37
38
|
status: number;
|
|
38
39
|
body: unknown;
|
|
39
40
|
} | null>;
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { liveToolEndForBacklog } from "../../trace/tool-output-media.js";
|
|
1
2
|
import { redactHead, redactLedgerEventForRead } from "../../observability/run-terminal.js";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { uuidv7, mintCheckpointToken, CheckpointError, policyCannotAnswer, validatePendingSteer, TASK_OUTPUT_REPLAY_KINDS, isSystemInjectionPriority } from "@sema-agent/core";
|
|
@@ -7,7 +8,7 @@ import { redactSecrets } from "../../trace/redact.js";
|
|
|
7
8
|
import { HttpError, isUuidV7, encodeCheckpointScope } from "../../security.js";
|
|
8
9
|
import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
|
|
9
10
|
import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
|
|
10
|
-
import { idemCallerOf, idemKeyOf, inflightKeyOf } from "../idempotency.js";
|
|
11
|
+
import { idemCallerOf, idemKeyOf, inflightKeyOf, submissionIdemOf } from "../idempotency.js";
|
|
11
12
|
import { streamSseLog } from "../sse-log.js";
|
|
12
13
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
13
14
|
import { buildApprovalPreamble, buildApprovalPreambleSseFrames, readClosedWordSeat, storedCardDemandsAPerson } from "../../approval-card.js";
|
|
@@ -33,6 +34,7 @@ import { readResumeCtx } from "../resume-ctx-read.js";
|
|
|
33
34
|
import { storedBodyReplayAnswer, storedBodyReplayLogFields } from "../checkpoint-ctx.js";
|
|
34
35
|
import { serviceVersion } from "../../service-version.js";
|
|
35
36
|
import { storedBodyReplayRefusal } from "../stored-body-replay.js";
|
|
37
|
+
import { steerBodyKeyIssue } from "../../request-key-closure.js";
|
|
36
38
|
const STEER_PRIORITY_WORDS = INJECTION_TIERS.map((tier) => `'${tier}'`);
|
|
37
39
|
const STEER_PRIORITY_UNION = STEER_PRIORITY_WORDS.join(" | ");
|
|
38
40
|
const STEER_PRIORITY_CHOICES = STEER_PRIORITY_WORDS.length <= 2 ? STEER_PRIORITY_WORDS.join(" or ") : `${STEER_PRIORITY_WORDS.slice(0, -1).join(", ")}, or ${STEER_PRIORITY_WORDS[STEER_PRIORITY_WORDS.length - 1]}`;
|
|
@@ -188,19 +190,18 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
188
190
|
const runStore = runs.runStore;
|
|
189
191
|
const rawIdem = headerStr(req.headers["idempotency-key"]);
|
|
190
192
|
const submitter = gatedPrincipal(req, deps.config);
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
keyedBody = await readJson(req);
|
|
193
|
+
const keyedBody = rawIdem ? await readJson(req) : undefined;
|
|
194
|
+
const idem = rawIdem ? submissionIdemOf(rawIdem, idemCallerOf(req, ctx), keyedBody) : undefined;
|
|
195
|
+
if (idem) {
|
|
195
196
|
const clientTaskId = clientTaskIdOf(keyedBody);
|
|
196
197
|
if (clientTaskId === false) {
|
|
197
198
|
sendError(res, 400, "request.id_invalid", "body.taskId must be a uuidv7 string (caller-minted idempotency key)");
|
|
198
199
|
return;
|
|
199
200
|
}
|
|
200
|
-
const inflight = idemCache.peek(inflightKeyOf(
|
|
201
|
-
const keyedRow = inflight ? undefined : await runStore.getRunByIdem(
|
|
201
|
+
const inflight = idemCache.peek(inflightKeyOf(idem, "durable", clientTaskId));
|
|
202
|
+
const keyedRow = inflight ? undefined : await runStore.getRunByIdem(idem.key);
|
|
202
203
|
if (inflight || keyedRow) {
|
|
203
|
-
const resp = inflight ? await inflight : await replayOfExistingRun(keyedRow, { principal: submitter, ...(clientTaskId !== undefined ? { clientTaskId } : {}) }, "durable", deps, runStore);
|
|
204
|
+
const resp = inflight ? await inflight : await replayOfExistingRun(keyedRow, { principal: submitter, idem, ...(clientTaskId !== undefined ? { clientTaskId } : {}) }, "durable", deps, runStore);
|
|
204
205
|
sendJson(res, resp.status, resp.body);
|
|
205
206
|
return;
|
|
206
207
|
}
|
|
@@ -210,7 +211,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
210
211
|
const prepared = await prepareSpec(req, res, keyedBody);
|
|
211
212
|
if (!prepared)
|
|
212
213
|
return;
|
|
213
|
-
const resp = await createDurableRun(req, res, ctx, prepared,
|
|
214
|
+
const resp = await createDurableRun(req, res, ctx, prepared, idem);
|
|
214
215
|
if (!resp)
|
|
215
216
|
return;
|
|
216
217
|
sendJson(res, resp.status, resp.body);
|
|
@@ -414,6 +415,13 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
414
415
|
sendError(res, 400, "request.body_shape", `body must be { text: string (non-empty), mode?: 'all' | 'one-at-a-time', priority?: ${STEER_PRIORITY_UNION} }`);
|
|
415
416
|
return;
|
|
416
417
|
}
|
|
418
|
+
{
|
|
419
|
+
const keyIssue = steerBodyKeyIssue(body, "run");
|
|
420
|
+
if (keyIssue) {
|
|
421
|
+
sendError(res, 400, "request.body_shape", keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys });
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
417
425
|
if (body.mode !== undefined && body.mode !== "all" && body.mode !== "one-at-a-time") {
|
|
418
426
|
sendError(res, 400, "request.field_invalid", "mode must be 'all' or 'one-at-a-time' when present");
|
|
419
427
|
return;
|
|
@@ -738,6 +746,11 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
738
746
|
return;
|
|
739
747
|
}
|
|
740
748
|
text = interruptBody.text;
|
|
749
|
+
const keyIssue = steerBodyKeyIssue(interruptBody, "interrupt");
|
|
750
|
+
if (keyIssue) {
|
|
751
|
+
sendError(res, 400, "request.body_shape", keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys });
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
741
754
|
}
|
|
742
755
|
const interruptIdemRaw = headerStr(req.headers["idempotency-key"]);
|
|
743
756
|
const interruptInputId = interruptIdemRaw ? `idem-${idemKeyOf(interruptIdemRaw, idemCallerOf(req, ctx), "run").slice(0, 32)}` : uuidv7();
|
|
@@ -1041,7 +1054,8 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1041
1054
|
const n = await it.next();
|
|
1042
1055
|
if (n.done || closed || res.writableEnded)
|
|
1043
1056
|
break;
|
|
1044
|
-
|
|
1057
|
+
const frame = n.value.type === "tool_end" ? liveToolEndForBacklog(n.value, res.writableLength) : n.value;
|
|
1058
|
+
res.write(`event: forward\ndata: ${JSON.stringify(frame)}\n\n`);
|
|
1045
1059
|
if (n.value.type === "task_settled")
|
|
1046
1060
|
break;
|
|
1047
1061
|
}
|
|
@@ -1162,6 +1176,13 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1162
1176
|
sendError(res, 400, "request.body_shape", "body must be { content: string (non-empty) }");
|
|
1163
1177
|
return;
|
|
1164
1178
|
}
|
|
1179
|
+
{
|
|
1180
|
+
const keyIssue = steerBodyKeyIssue(body, "content");
|
|
1181
|
+
if (keyIssue) {
|
|
1182
|
+
sendError(res, 400, "request.body_shape", keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys });
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1165
1186
|
if (body.content.length > STEER_IN_MAX_REQUEST_CHARS) {
|
|
1166
1187
|
sendError(res, 413, "steer.content_too_large", `content exceeds ${STEER_IN_MAX_REQUEST_CHARS} characters (got ${body.content.length}); note only the first ${STEER_IN_MAX_CHARS} are delivered anyway`);
|
|
1167
1188
|
return;
|
|
@@ -1586,7 +1607,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1586
1607
|
}
|
|
1587
1608
|
return assertNever(match.decl.id);
|
|
1588
1609
|
}
|
|
1589
|
-
export async function createDurableRun(req, res, ctx, prepared,
|
|
1610
|
+
export async function createDurableRun(req, res, ctx, prepared, idem) {
|
|
1590
1611
|
const { deps } = ctx;
|
|
1591
1612
|
const { idemCache } = ctx.registry;
|
|
1592
1613
|
const { leaseDenied, usageWindowDenied } = ctx.helpers;
|
|
@@ -1603,7 +1624,7 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
|
1603
1624
|
return null;
|
|
1604
1625
|
}
|
|
1605
1626
|
const submitter = gatedPrincipal(req, deps.config);
|
|
1606
|
-
const caller = { principal: submitter, ...(clientTaskId !== undefined ? { clientTaskId } : {}) };
|
|
1627
|
+
const caller = { principal: submitter, idem, ...(clientTaskId !== undefined ? { clientTaskId } : {}) };
|
|
1607
1628
|
if (clientTaskId) {
|
|
1608
1629
|
const existing = await runStore.getRun(clientTaskId);
|
|
1609
1630
|
if (existing)
|
|
@@ -1611,11 +1632,11 @@ export async function createDurableRun(req, res, ctx, prepared, idemKey) {
|
|
|
1611
1632
|
}
|
|
1612
1633
|
if ((await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
|
|
1613
1634
|
return null;
|
|
1614
|
-
return await idemCache.run(
|
|
1635
|
+
return await idemCache.run(idem !== undefined ? inflightKeyOf(idem, "durable", clientTaskId) : undefined, async () => {
|
|
1615
1636
|
const out = await mintAndDriveDurableRun({ deps, registry: ctx.registry, helpers: ctx.helpers, local: ctx.local, legs: ctx.legs }, runStore, {
|
|
1616
1637
|
prepared,
|
|
1617
1638
|
...(clientTaskId !== undefined ? { clientTaskId } : {}),
|
|
1618
|
-
...(
|
|
1639
|
+
...(idem !== undefined ? { idem } : {}),
|
|
1619
1640
|
fleetScope: submitter ?? "default",
|
|
1620
1641
|
elicitOwner: submitter ?? null,
|
|
1621
1642
|
source,
|
|
@@ -44,7 +44,6 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
const { backend, sessionStorage, historyStore } = sync;
|
|
47
|
-
const ownerOf = sessionStorage.ownerOf.bind(sessionStorage);
|
|
48
47
|
const scope = sessionOwnerScopeForWrite(req);
|
|
49
48
|
if (!scope.fleetWide && scope.gateOwner === null) {
|
|
50
49
|
sendUnauthorized(res, "identity");
|
|
@@ -55,7 +54,8 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
55
54
|
sendError(res, 400, "request.id_invalid", "invalid session id (must be a canonical uuid)");
|
|
56
55
|
return;
|
|
57
56
|
}
|
|
58
|
-
const
|
|
57
|
+
const gateFace = await sessionStorage.sessionFaceOf(sessionId);
|
|
58
|
+
const owner = gateFace === undefined ? undefined : gateFace.owner;
|
|
59
59
|
const ownsOrFleet = (o) => scope.fleetWide || o === scope.gateOwner;
|
|
60
60
|
const pullGateOk = () => {
|
|
61
61
|
if (owner === undefined) {
|
|
@@ -75,13 +75,10 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
75
75
|
}
|
|
76
76
|
return true;
|
|
77
77
|
};
|
|
78
|
-
const replaySessionRow = async (row,
|
|
78
|
+
const replaySessionRow = async (row, face) => {
|
|
79
79
|
if (Object.keys(row).length === 0)
|
|
80
|
-
return;
|
|
81
|
-
|
|
82
|
-
if (face === undefined || face.owner !== importedOwner)
|
|
83
|
-
return;
|
|
84
|
-
await sessionStorage.patchSessionMeta(sessionId, row, face);
|
|
80
|
+
return true;
|
|
81
|
+
return face !== undefined && (await sessionStorage.patchSessionMeta(sessionId, row, face)) !== undefined;
|
|
85
82
|
};
|
|
86
83
|
if (match.decl.id === "session-sync-manifest") {
|
|
87
84
|
if (!pullGateOk())
|
|
@@ -331,7 +328,10 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
331
328
|
if (deps.resumeAnchorStore)
|
|
332
329
|
for (const a of anchors)
|
|
333
330
|
await deps.resumeAnchorStore.put(sessionId, a.eventId, a.entryId, importingP);
|
|
334
|
-
await replaySessionRow(row,
|
|
331
|
+
if (!(await replaySessionRow(row, gateFace))) {
|
|
332
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
335
|
sendJson(res, 200, {
|
|
336
336
|
relation: "identical",
|
|
337
337
|
basis: payloadVerified ? "entry-ids+digest" : "entry-ids",
|
|
@@ -521,9 +521,13 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
521
521
|
if (anchorStore)
|
|
522
522
|
for (const a of pending.anchors)
|
|
523
523
|
await anchorStore.put(sessionId, a.eventId, a.entryId, pending.owner);
|
|
524
|
-
await replaySessionRow(pending.row,
|
|
524
|
+
const rowLanded = await replaySessionRow(pending.row, committed.face);
|
|
525
525
|
sessionStorage.evict?.(sessionId);
|
|
526
526
|
cleanupStaging(stagingId);
|
|
527
|
+
if (!rowLanded) {
|
|
528
|
+
sendError(res, 409, "conflict", "sync conflict");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
527
531
|
sendJson(res, 200, { relation: committed.relation });
|
|
528
532
|
}
|
|
529
533
|
catch (e) {
|
|
@@ -567,7 +571,6 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
567
571
|
function sendSessionSyncUnavailable(res, why) {
|
|
568
572
|
switch (why) {
|
|
569
573
|
case "store_backend_absent":
|
|
570
|
-
case "session_owner_face_absent":
|
|
571
574
|
case "session_row_faces_absent":
|
|
572
575
|
sendError(res, 501, "capability.session_store_required", "session sync requires a durable session store");
|
|
573
576
|
return;
|