@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
|
@@ -3,35 +3,21 @@ import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from ".
|
|
|
3
3
|
import { redactSecrets } from "../../trace/redact.js";
|
|
4
4
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
5
5
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
6
|
-
const WORKFLOWS_RE = /^\/v1\/workflows(?:\/([^/]+)(\/stream|\/journal)?)?$/;
|
|
7
|
-
// SVC-5 (design/97 CORE-5 #6): steer a STILL-RUNNING workflow agent (ctx.agentStream) by runId + label. POST-only,
|
|
8
|
-
// so it never collides with the GET WORKFLOWS_RE above. The label is a non-slash segment (core mints `agent-N`
|
|
9
|
-
// for an unlabelled agent, else the caller's opts.label) — URL-decoded by the handler.
|
|
6
|
+
const WORKFLOWS_RE = /^\/v1\/workflows(?:\/([^/]+)(\/stream|\/journal)?)?$/;
|
|
10
7
|
const WORKFLOW_AGENT_STEER_RE = /^\/v1\/workflows\/([^/]+)\/agents\/([^/]+)\/steer$/;
|
|
11
8
|
export async function handleWorkflows(req, res, url, ctx) {
|
|
12
9
|
const miss = { fell: false };
|
|
13
10
|
await handleWorkflowsReadBody(req, res, url, ctx, miss);
|
|
14
11
|
return !miss.fell;
|
|
15
12
|
}
|
|
16
|
-
/** SVC-5 的 steer 路——`handle()` 里位置在 tasks/runs 之后(鉴权门之后),故是**独立**的域入口。 */
|
|
17
13
|
export async function handleWorkflowAgentSteer(req, res, url, ctx) {
|
|
18
14
|
const miss = { fell: false };
|
|
19
15
|
await handleWorkflowAgentSteerBody(req, res, url, ctx, miss);
|
|
20
16
|
return !miss.fell;
|
|
21
17
|
}
|
|
22
|
-
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
23
|
-
* = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
|
|
24
18
|
async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
|
|
25
19
|
const { deps } = ctx;
|
|
26
20
|
const { runSessionAcceptOk } = ctx.helpers;
|
|
27
|
-
// S8 self-orchestration runs (design/97/98): GET /v1/workflows (list) · /v1/workflows/:id (detail) ·
|
|
28
|
-
// /v1/workflows/:id/stream (live SSE). OWNER-GATED by scope (= the creating principal, prepare-task.js
|
|
29
|
-
// scope: spec.principal). list/detail need the run store; live stream is in-process (subscribeWorkflow).
|
|
30
|
-
// 🔐 Identity from gatedPrincipal (direct-door-secure single point — crypto-verified principal JWT when
|
|
31
|
-
// directDoorActive, transparently `principalFrom` otherwise), NEVER the spoofable x-agent-principal header:
|
|
32
|
-
// a direct-door worker MUST NOT trust the raw header for a per-tenant owner gate (same bypass class —
|
|
33
|
-
// parity with the trace owner-gate, the workflow steer POST, and the elicit route). This block returns BEFORE
|
|
34
|
-
// the service-credential gate below, so the per-tenant gate here is the sole tenant boundary for these reads.
|
|
35
21
|
const wfMatch = req.method === "GET" ? WORKFLOWS_RE.exec(url) : null;
|
|
36
22
|
if (wfMatch) {
|
|
37
23
|
const principal = gatedPrincipal(req, deps.config);
|
|
@@ -43,29 +29,19 @@ async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
|
|
|
43
29
|
sendError(res, 501, "capability.self_orchestration_required", "workflow runs require self-orchestration (SELF_ORCHESTRATION_ENABLED)");
|
|
44
30
|
return;
|
|
45
31
|
}
|
|
46
|
-
const scope = principal ?? "default";
|
|
32
|
+
const scope = principal ?? "default";
|
|
47
33
|
const wfId = wfMatch[1];
|
|
48
34
|
const isStream = wfMatch[2] === "/stream";
|
|
49
35
|
if (!wfId) {
|
|
50
36
|
const q = new URL(req.url ?? "", "http://x").searchParams;
|
|
51
37
|
const statusFilter = q.get("status");
|
|
52
38
|
const limit = Math.min(100, Math.max(1, Number(q.get("limit") ?? 50) || 50));
|
|
53
|
-
// [1512]§二 交付件接线(1.247 留的 list 缺口):`?session=` 过滤臂 —— present 即按
|
|
54
|
-
// `originatingSessionId` 严格等值筛。语义是**筛选**非 per-id 的接受拍:无字段行(pre-core-1.210)
|
|
55
|
-
// 不属于任何会话,筛选下剔除(per-id 面的 absent 放行是访问控制防误伤,两面语义刻意不同);
|
|
56
|
-
// 空串=无行可匹配=空列表;absent = 全量,字节不变。1.250:筛下推 store 侧(core 1.368 [1516]②
|
|
57
|
-
// `listByScope.opts.session`,limit **前**应用)——1.249 的「页内筛」升「全量筛」,忙 scope 下
|
|
58
|
-
// 比 newest-limit 窗口更老的会话行不再漏。直调 store(listWorkflowRuns 是纯透传,其 opts 类型
|
|
59
|
-
// 未收录 session)。
|
|
60
39
|
const sessionFilter = q.get("session");
|
|
61
40
|
const sessionRuns = await deps.workflowRunStore.listByScope(scope, {
|
|
62
41
|
...(statusFilter ? { status: statusFilter } : {}),
|
|
63
42
|
limit,
|
|
64
43
|
...(sessionFilter !== null ? { session: sessionFilter } : {}),
|
|
65
44
|
});
|
|
66
|
-
// K-2c LIST side (core 1.155 `WorkflowRunSummary` now carries name?/description?/currentPhase? from the
|
|
67
|
-
// script `meta` — flows via the pin bump): redact the LLM-authored label surfaces for parity with the DETAIL
|
|
68
|
-
// route (summarizeWorkflowDetail) + the fleet rows. The rest of the summary (status/counts/stats) rides as-is.
|
|
69
45
|
const redacted = sessionRuns.map((r) => ({
|
|
70
46
|
...r,
|
|
71
47
|
...(r.name !== undefined ? { name: redactSecrets(r.name) } : {}),
|
|
@@ -75,48 +51,24 @@ async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
|
|
|
75
51
|
sendJson(res, 200, { workflows: redacted });
|
|
76
52
|
return;
|
|
77
53
|
}
|
|
78
|
-
// per-id: owner-gate via the persisted run's scope (no existence oracle for non-owners → 404). 1.146:
|
|
79
|
-
// getWorkflowRun is scope-filtered IN CORE (returns null unless run.scope === scope) — defense-in-depth on
|
|
80
|
-
// top of our route gate; the `run.scope !== scope` below is now redundant but kept (belt-and-suspenders).
|
|
81
|
-
// The SSE branch streams ONLY after this gate, so the live stream is owner-gated too (core's subscribeWorkflow
|
|
82
|
-
// does not self-filter — this gate is its tenant boundary).
|
|
83
54
|
const run = await getWorkflowRun(deps.workflowRunStore, wfId, scope);
|
|
84
55
|
if (!run || run.scope !== scope) {
|
|
85
56
|
sendError(res, 404, "not_found.workflow", "workflow not found");
|
|
86
57
|
return;
|
|
87
58
|
}
|
|
88
|
-
// [1508] 1.247 — session ACCEPT phase on the workflow per-id faces ([1493]-class sibling: these deliver
|
|
89
|
-
// conversation-derived content — step prompts/outputs/live events — but were principal-gated only, while
|
|
90
|
-
// the durable WorkflowRun carries `originatingSessionId` since core 1.210). Same soft phase + helper as
|
|
91
|
-
// the run faces (present-mismatch → 404; absent → warn-once). The LIST face is NOT gated this拍:
|
|
92
|
-
// WorkflowRunSummary lacks the field (core ask, γ-batch alignment) — its rows stay redacted summaries.
|
|
93
59
|
if (!runSessionAcceptOk(req, res, { sessionId: run.originatingSessionId ?? null }, wfMatch[2] === "/journal" ? "workflow.journal" : isStream ? "workflow.stream" : "workflow.detail", "workflow not found"))
|
|
94
60
|
return;
|
|
95
61
|
if (wfMatch[2] === "/journal") {
|
|
96
|
-
// [1402]/[1400]④:per-agent journal 读面(云本地同构——三后端店同契约)。owner 门已过(上方 run
|
|
97
|
-
// scope 校验);投影=bounded+redact(result 是 LLM 产物,注入面纪律同 fleet 行),ordinal 序稳定。
|
|
98
62
|
if (!deps.workflowJournalStore) {
|
|
99
63
|
sendError(res, 501, "capability.workflow_store_required", "workflow journal requires the journal store (SELF_ORCHESTRATION_ENABLED + a store backend)");
|
|
100
64
|
return;
|
|
101
65
|
}
|
|
102
|
-
// codex R1-H2:hard bounds——limit 默认 20 cap 50(?limit=)、?offset= 游标;SQL 店走 loadPage
|
|
103
|
-
// (行数 LIMIT/OFFSET + 单行 64KiB LENGTH 门在库端,峰值 ≤50×64KiB);file/in-memory 店(单机形)
|
|
104
|
-
// 回落全量 load 后同界投影(内存峰值=店内容,记档取舍)。
|
|
105
66
|
const jq = new URL(req.url ?? "", "http://x").searchParams;
|
|
106
67
|
const jLimit = Math.min(50, Math.max(1, Number(jq.get("limit") ?? 20) || 20));
|
|
107
68
|
const jOffset = Math.max(0, Number(jq.get("offset") ?? 0) || 0);
|
|
108
69
|
const MAX_ROW_BYTES = 64 * 1024;
|
|
109
70
|
const firstLine = (t) => (t ? redactSecrets(t.split("\n")[0].slice(0, 300)) : undefined);
|
|
110
71
|
const MAX_RESULT_CHARS = 2000;
|
|
111
|
-
// A-002.6(披露双标,P1):>64KiB 的行诚实标 `truncated`/`resultBytes`(下方两臂),而 2000 字符~
|
|
112
|
-
// 64KiB 之间的行此前被 `slice(0, 2000)` **静默**截断 —— 同一个响应数组里一行说得出「我被截了」、
|
|
113
|
-
// 另一行说不出,消费者也无从区分「结果正好 2000 字」与「结果被截到 2000 字」。
|
|
114
|
-
// 🔴 键刻意**不复用** `truncated`:兄弟臂的 `truncated:true` 语义是「整行没内容,只有尺寸存根」,
|
|
115
|
-
// 盖到一行有内容的行上就是把兄弟臂的判别位改义(按它分支的消费者会把有内容的行当空存根)。
|
|
116
|
-
// `resultTruncated`/`resultChars` 描述的是 `result` **这个字段**,无歧义。原始长度量的是
|
|
117
|
-
// redact 之前的字符数(与兄弟臂 `resultBytes` 同精神,基准不同故不同名)。
|
|
118
|
-
// ⚠️ wire 记账:SDK spec 的 `WorkflowJournalEntry` 是 `additionalProperties: false`,两枚 additive
|
|
119
|
-
// 键需要 sema-sdk spec/openapi.yaml 同源补声明(SDK 件另走)。
|
|
120
72
|
const projectResult = (callKey, r) => ({
|
|
121
73
|
callKey,
|
|
122
74
|
ordinal: callKeyOrdinal(callKey),
|
|
@@ -129,8 +81,6 @@ async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
|
|
|
129
81
|
}
|
|
130
82
|
: {}),
|
|
131
83
|
...(r.stats ? { tokens: r.stats.tokens, turns: r.stats.turns } : {}),
|
|
132
|
-
// core 5.51 #210:status:"completed" 但停在人拒的唯一判别位——白名单不投它,journal 消费者
|
|
133
|
-
// 会把「用户拒了、在等指示」读成「自然跑完」(契约明写的误读形)。additive:自然结束不挂键。
|
|
134
84
|
...(r.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}),
|
|
135
85
|
});
|
|
136
86
|
const js = deps.workflowJournalStore;
|
|
@@ -139,7 +89,6 @@ async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
|
|
|
139
89
|
const page = await js.loadPage(wfId, scope, { offset: jOffset, limit: jLimit, maxResultBytes: MAX_ROW_BYTES });
|
|
140
90
|
projected = page.map((row) => {
|
|
141
91
|
if (row.resultJson === null) {
|
|
142
|
-
// 超单行门的条目:诚实标 truncated,不把 64KiB+ 的 TaskResult 拉进进程
|
|
143
92
|
return { callKey: row.callKey, ordinal: callKeyOrdinal(row.callKey), truncated: true, resultBytes: row.resultBytes };
|
|
144
93
|
}
|
|
145
94
|
try {
|
|
@@ -151,9 +100,6 @@ async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
|
|
|
151
100
|
});
|
|
152
101
|
}
|
|
153
102
|
else {
|
|
154
|
-
// fallback(file/in-memory,单机 TOC 形——多租户 durable 恒 SQL 店=loadPage 覆盖):load 全量的
|
|
155
|
-
// 内存/事件环成本=用户读自己盘的单机取舍(ARCHITECTURE.md 记档);投影侧与 SQL 店**同一诚实
|
|
156
|
-
// 语义**——超 64KiB 的行标 truncated/resultBytes,不做静默 2000 字投影(跨后端 API 形一致)。
|
|
157
103
|
const entries = await deps.workflowJournalStore.load(wfId, scope);
|
|
158
104
|
projected = entries
|
|
159
105
|
.sort((a, b) => callKeyOrdinal(a.callKey) - callKeyOrdinal(b.callKey))
|
|
@@ -169,57 +115,35 @@ async function handleWorkflowsReadBody(req, res, url, ctx, miss) {
|
|
|
169
115
|
return;
|
|
170
116
|
}
|
|
171
117
|
if (isStream)
|
|
172
|
-
await streamWorkflowRun(req, res, wfId, scope);
|
|
118
|
+
await streamWorkflowRun(req, res, wfId, scope);
|
|
173
119
|
else
|
|
174
|
-
sendJson(res, 200, summarizeWorkflowDetail(run));
|
|
120
|
+
sendJson(res, 200, summarizeWorkflowDetail(run));
|
|
175
121
|
return;
|
|
176
122
|
}
|
|
177
123
|
miss.fell = true;
|
|
178
124
|
}
|
|
179
|
-
/** 同上纪律(SVC-5 steer 段)。 */
|
|
180
125
|
async function handleWorkflowAgentSteerBody(req, res, url, ctx, miss) {
|
|
181
126
|
const { deps } = ctx;
|
|
182
127
|
const { readJson, rateLimited, quotaExceeded, leaseDenied, safeDecode, runSessionAcceptOk } = ctx.helpers;
|
|
183
|
-
// SVC-5 (design/97 CORE-5 #6): POST /v1/workflows/:id/agents/:label/steer — STEER a still-running workflow
|
|
184
|
-
// agent (`ctx.agentStream`) mid-flight. Unlike RUN steer (a whole task run), this targets ONE running agent
|
|
185
|
-
// inside a workflow, addressed by runId + label. Delivery is replica-local (the steerable handle is an
|
|
186
|
-
// in-memory bridge to the running TaskStream, like `steerableRuns`):
|
|
187
|
-
// • the handle is live on THIS replica → `handle.steer(redacted)` injects + returns the marker → 200.
|
|
188
|
-
// • not here but the workflow run is `running` (another replica) → 409 steering.not_running (cross-replica
|
|
189
|
-
// live-steer is the same fast-follow seam as RUN steer — single-replica-per-worker today; honest, never
|
|
190
|
-
// a silent drop). • terminal / no handle → 409 steering.not_running.
|
|
191
|
-
// 🔴 HANDLE-VISIBILITY SEAM: core returns a WorkflowAgentHandle ONLY from `WorkflowRunContext.agentStream`,
|
|
192
|
-
// and that host ctx structurally never enters the script runner (the LLM workflow sees the sterile
|
|
193
|
-
// `WorkflowPrimitives`, which has `agent` but not `agentStream`). The service runs workflows ONLY via core's
|
|
194
|
-
// `run_workflow` tool → primitives, so it never holds a handle to register. The registry/redaction/auth here
|
|
195
|
-
// are production-real (the populated path uses them unchanged); until core hands the deployment the handle
|
|
196
|
-
// (e.g. an onAgentStream lifecycle sink on run_workflow), the registry is genuinely empty and the route
|
|
197
|
-
// returns steering.not_running. See workflow-agent-steer.ts. Raised to core (SVC-5).
|
|
198
128
|
const wfSteerMatch = req.method === "POST" ? WORKFLOW_AGENT_STEER_RE.exec(url) : null;
|
|
199
129
|
if (wfSteerMatch) {
|
|
200
130
|
if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
|
|
201
|
-
return;
|
|
131
|
+
return;
|
|
202
132
|
if (!deps.workflowRunStore) {
|
|
203
133
|
sendError(res, 501, "capability.self_orchestration_required", "workflow runs require self-orchestration (SELF_ORCHESTRATION_ENABLED)");
|
|
204
134
|
return;
|
|
205
135
|
}
|
|
206
|
-
// Identity from gatedPrincipal (direct-door-secure single point), NEVER the spoofable header — `trusted`
|
|
207
|
-
// carries operator authority (a supervisor steering any tenant's agent), so deriving it from a header would
|
|
208
|
-
// be a privilege escalation (parity with RUN steer / preempt; HANDOFF 铁律 + core §3 inv #1).
|
|
209
136
|
const principal = gatedPrincipal(req, deps.config);
|
|
210
137
|
if (deps.config.requirePrincipal && principal === undefined) {
|
|
211
138
|
sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
|
|
212
139
|
return;
|
|
213
140
|
}
|
|
214
|
-
// safeDecode (not bare decodeURIComponent): a malformed %-sequence is the CALLER's error → 400, not a 500
|
|
215
|
-
// (parity with the approvals + session-sync routes' established handling).
|
|
216
141
|
const wfRunId = safeDecode(wfSteerMatch[1]);
|
|
217
142
|
const label = safeDecode(wfSteerMatch[2]);
|
|
218
143
|
if (wfRunId === null || label === null) {
|
|
219
144
|
sendError(res, 400, "request.path_malformed", "malformed workflow path (invalid percent-encoding)");
|
|
220
145
|
return;
|
|
221
146
|
}
|
|
222
|
-
// Parse + validate the body BEFORE the run lookup (a bad body is the caller's error regardless of state).
|
|
223
147
|
let body;
|
|
224
148
|
try {
|
|
225
149
|
body = (await readJson(req));
|
|
@@ -232,43 +156,21 @@ async function handleWorkflowAgentSteerBody(req, res, url, ctx, miss) {
|
|
|
232
156
|
sendError(res, 400, "request.body_shape", "body must be { content: string (non-empty) }");
|
|
233
157
|
return;
|
|
234
158
|
}
|
|
235
|
-
// 入参上限(见 `STEER_IN_MAX_REQUEST_CHARS` 旁注):脱敏门是**同步**的,烧的是事件循环 ⇒
|
|
236
|
-
// 一次超大 steer 卡住的是**整个副本**。**拒**而不是服务端先截 —— 先截会让引擎的
|
|
237
|
-
// `[+N chars]` 披露低报,而"诚实标记不得低报"是本仓另一条钉守着的性质。
|
|
238
159
|
if (body.content.length > STEER_IN_MAX_REQUEST_CHARS) {
|
|
239
160
|
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`);
|
|
240
161
|
return;
|
|
241
162
|
}
|
|
242
|
-
// trusted = explicit operator (NOT bare isOperator — its empty-list "true-for-all" would let any caller
|
|
243
|
-
// steer any tenant's agent). A normal owner steers only their own workflow.
|
|
244
163
|
const trusted = explicitOperatorOk(principal, deps.config.operatorPrincipals);
|
|
245
|
-
const ownerScope = principal ?? "default";
|
|
246
|
-
// Owner-gate via the persisted run, now scope-filtered IN CORE (1.146 getWorkflowRun(store, id, scope) →
|
|
247
|
-
// null unless run.scope === scope). For a NON-operator: a missing/other-tenant run is null → 404 (no
|
|
248
|
-
// existence oracle). An explicit OPERATOR (supervisor) may steer ANY tenant's workflow — but core's
|
|
249
|
-
// scope-filtered read can't fetch another tenant's run by the operator's own scope (→ null), so the operator
|
|
250
|
-
// is NOT gated on `wfRun` existence: its authority is the operator allowlist, and the steer target's
|
|
251
|
-
// existence+liveness is proven by the registry handle below (no handle → 409, never a wrong-tenant steer).
|
|
164
|
+
const ownerScope = principal ?? "default";
|
|
252
165
|
const wfRun = await getWorkflowRun(deps.workflowRunStore, wfRunId, ownerScope);
|
|
253
166
|
if (!trusted && !wfRun) {
|
|
254
167
|
sendError(res, 404, "not_found.workflow", "workflow not found");
|
|
255
168
|
return;
|
|
256
169
|
}
|
|
257
|
-
// [1508] 1.247 — same session accept-phase as the read faces (a steer ACTS on the conversation's
|
|
258
|
-
// workflow). Gated on the fetched run only: the operator leg (wfRun null by scope-filtered read) keeps
|
|
259
|
-
// its registry-handle-proven authority below, and runSessionAcceptOk's operator branch covers a
|
|
260
|
-
// same-scope operator anyway.
|
|
261
170
|
if (wfRun && !runSessionAcceptOk(req, res, { sessionId: wfRun.originatingSessionId ?? null }, "workflow.agent-steer", "workflow not found"))
|
|
262
171
|
return;
|
|
263
172
|
const sendNotRunningWf = (error) => sendError(res, 409, "steering.not_running", error);
|
|
264
|
-
// STEER-IN REDACTION GATE (SVC-5 信任门): treat the human/leader content as UNTRUSTED — redact host leaks +
|
|
265
|
-
// size-bound + fence — BEFORE it reaches the worker. core's `handle.steer` fences AGAIN inside its trusted
|
|
266
|
-
// marker framing (defense in depth). Applied regardless of `trusted`: an operator's copy-pasted steer can
|
|
267
|
-
// still leak a host path/token, and an authoritative-looking steer must not pose as system instructions.
|
|
268
173
|
const redacted = redactSteerIn(body.content, label);
|
|
269
|
-
// Same-replica live path: the steerable handle is registered here → inject now, return the marker.
|
|
270
|
-
// core does not enforce label uniqueness within a run, so resolve() reports the count: >1 = two live agents
|
|
271
|
-
// share this label → the target is ambiguous, refuse (409) rather than steer an arbitrary one.
|
|
272
174
|
const resolution = deps.workflowAgentRegistry?.resolve(wfRunId, label);
|
|
273
175
|
if (resolution && resolution.count > 1) {
|
|
274
176
|
sendError(res, 409, "steering.ambiguous_label", `${resolution.count} live agents share label '${label}' in this run — steer target is ambiguous`);
|
|
@@ -283,18 +185,13 @@ async function handleWorkflowAgentSteerBody(req, res, url, ctx, miss) {
|
|
|
283
185
|
}
|
|
284
186
|
catch (e) {
|
|
285
187
|
const code = e.code;
|
|
286
|
-
// The agent settled between the registry lookup and steer() → core rejects steering.not_running.
|
|
287
188
|
if (code === "steering.not_running") {
|
|
288
189
|
sendNotRunningWf("agent just finished — no longer accepting steers");
|
|
289
190
|
return;
|
|
290
191
|
}
|
|
291
|
-
throw e;
|
|
192
|
+
throw e;
|
|
292
193
|
}
|
|
293
194
|
}
|
|
294
|
-
// No live handle here. The workflow run is either terminal, or `running` on another replica (cross-replica
|
|
295
|
-
// live-steer = the same fast-follow seam as RUN steer; a workflow agent is in-memory steerable only on its
|
|
296
|
-
// running replica). Either way: honest 409, never a silent drop. `wfRun` may be null for an OPERATOR steering
|
|
297
|
-
// a cross-tenant run (core's scope-filtered read returned null) → a generic message (status unknowable here).
|
|
298
195
|
sendNotRunningWf(!wfRun
|
|
299
196
|
? "workflow agent is not running on this replica (no live handle)"
|
|
300
197
|
: wfRun.status === "running"
|
|
@@ -304,37 +201,14 @@ async function handleWorkflowAgentSteerBody(req, res, url, ctx, miss) {
|
|
|
304
201
|
}
|
|
305
202
|
miss.fell = true;
|
|
306
203
|
}
|
|
307
|
-
/** SVC-4 v1 — the STRUCTURED drill-down view of one persisted {@link WorkflowRun} (the historical detail the
|
|
308
|
-
* Phases panel + agent list + groups tree render, parity with CC's Workflow tool). Pure projection over the
|
|
309
|
-
* persisted run — no new source of truth, no gate input (the route already owner-gated by scope before this).
|
|
310
|
-
* Three derived shapes:
|
|
311
|
-
* - `phases`: each `run.phases` entry + a done/total of the agents that ran under that `phase` title (done =
|
|
312
|
-
* terminal status). Plus an `unphased` bucket counting agents with no phase.
|
|
313
|
-
* - `agents`: each agent flattened to `{ label, status, callKey, groupId, phase, tokens, turns, durationMs,
|
|
314
|
-
* replayed, prompt, output }`. `prompt`/`output` are core-redacted + size-bounded; this is a HUMAN view so
|
|
315
|
-
* they are surfaced AT REST WITHOUT a fence (a consumer that re-feeds them to an LLM must fence itself —
|
|
316
|
-
* core's `WorkflowAgentRun` doc). `tokens`/`turns`/`costMicroUsd` come from the agent's own `stats`.
|
|
317
|
-
* - `groups`: the nested tree rebuilt from `run.groups` (parentGroupId) — each node carries its direct agent
|
|
318
|
-
* callKeys so a UI can hang the agent list off the tree. Empty when the script used no `ctx.workflow` nesting.
|
|
319
|
-
* `stats` (own + nested, kept separate per core R-5) and the run-level fields pass through unchanged. */
|
|
320
204
|
function summarizeWorkflowDetail(run) {
|
|
321
205
|
const isDone = (s) => s === "completed" || s === "failed";
|
|
322
|
-
// MF-W queued (core 1.150): a WorkflowAgentRun's `startedAt` is now OPTIONAL — ABSENT while an agent is QUEUED
|
|
323
|
-
// (waiting on a concurrency slot) or if it was aborted/finalized before it ran. So duration is undefined unless
|
|
324
|
-
// BOTH ends are present (a queued agent has no running duration — the DoR fix). Phases still always carry startedAt.
|
|
325
206
|
const durationOf = (a) => a.startedAt === undefined || a.endedAt === undefined ? undefined : a.endedAt - a.startedAt;
|
|
326
|
-
// A real persisted run always carries these arrays; default defensively so a malformed/partial stored row
|
|
327
|
-
// projects cleanly instead of 500-ing the detail route (the view is read-only — never a gate input).
|
|
328
207
|
const runPhases = run.phases ?? [];
|
|
329
208
|
const runAgents = run.agents ?? [];
|
|
330
209
|
const runGroups = run.groups ?? [];
|
|
331
|
-
// [review] label-parity: an LLM-authored run_workflow script controls phase titles + agent labels + the per-agent
|
|
332
|
-
// phase tag (core stores them raw, unbounded), so redact them on EMISSION for the same reason as name/description
|
|
333
|
-
// (a secret the agent read could land in a label). Filter/bucket on the RAW values (below) so grouping is unchanged;
|
|
334
|
-
// redactSecrets is deterministic → a secret-bearing title and the matching agent.phase scrub to the SAME string,
|
|
335
|
-
// so the shell's phase grouping still lines up. (prompt/output are already core-redacted; model is catalog-sourced.)
|
|
336
210
|
const phases = runPhases.map((p) => {
|
|
337
|
-
const inPhase = runAgents.filter((a) => a.phase === p.title);
|
|
211
|
+
const inPhase = runAgents.filter((a) => a.phase === p.title);
|
|
338
212
|
return {
|
|
339
213
|
title: redactSecrets(p.title),
|
|
340
214
|
status: p.status,
|
|
@@ -345,50 +219,31 @@ function summarizeWorkflowDetail(run) {
|
|
|
345
219
|
total: inPhase.length,
|
|
346
220
|
};
|
|
347
221
|
});
|
|
348
|
-
// Agents with no phase title, OR a phase title that matches no registered `run.phases` entry (a stray
|
|
349
|
-
// `opts.phase` with no enclosing `phase()` block) — surfaced so the panel's counts are EXHAUSTIVE (an agent is
|
|
350
|
-
// in exactly one bucket: its registered phase, or `unphased`). Without the `titleSet` guard a stray-phase agent
|
|
351
|
-
// would fall into NO bucket (under-count). Empty-string phase is also unphased.
|
|
352
222
|
const phaseTitleSet = new Set(runPhases.map((p) => p.title));
|
|
353
223
|
const unphased = runAgents.filter((a) => a.phase === undefined || a.phase === "" || !phaseTitleSet.has(a.phase));
|
|
354
224
|
const unphasedView = unphased.length > 0
|
|
355
225
|
? { done: unphased.filter((a) => isDone(a.status)).length, total: unphased.length }
|
|
356
226
|
: undefined;
|
|
357
227
|
const agents = runAgents.map((a) => ({
|
|
358
|
-
label: redactSecrets(a.label),
|
|
228
|
+
label: redactSecrets(a.label),
|
|
359
229
|
status: a.status,
|
|
360
|
-
// MF-W (1.149 CORE-shared): the canonical display status (running|queued|done|failed|interrupted) via core's
|
|
361
|
-
// deriveAgentDisplayStatus — the SAME pure fn the shell renders, so the agent-state glyph can't drift between
|
|
362
|
-
// shells (one source of truth in core). `interrupted` = a non-terminal agent under a terminal run. The live
|
|
363
|
-
// `idle` overlay (a running agent with no recent activity) stays a SHELL wall-clock concern — since core 1.295
|
|
364
|
-
// ToolActivity carries `at`, this view exposes the raw timestamps (activity[].at + lastActivityAt below) and
|
|
365
|
-
// the shell derives idleness against its own clock (a served idleMs would drift on a historical view).
|
|
366
230
|
displayStatus: deriveAgentDisplayStatus(a, run.status),
|
|
367
231
|
taskStatus: a.taskStatus,
|
|
368
232
|
callKey: a.callKey,
|
|
369
233
|
groupId: a.groupId,
|
|
370
|
-
phase: a.phase !== undefined ? redactSecrets(a.phase) : undefined,
|
|
371
|
-
model: a.model,
|
|
234
|
+
phase: a.phase !== undefined ? redactSecrets(a.phase) : undefined,
|
|
235
|
+
model: a.model,
|
|
372
236
|
tokens: a.stats?.tokens,
|
|
373
237
|
turns: a.stats?.turns,
|
|
374
|
-
toolCalls: a.toolCalls,
|
|
375
|
-
// SVC-4 v2 / CORE-8 ③: the LAST-N tool-call beats (structural — phase/toolName/ids, no args/output). The live
|
|
376
|
-
// detail SSE (subscribeWorkflow → streamWorkflowRun) already forwards the per-callKey `agent_activity` events
|
|
377
|
-
// verbatim for a running drill-down; this is the persisted tail a historical view renders.
|
|
378
|
-
// [843]④a (core 1.295): each beat now carries `at` (epoch ms, stamped on BOTH start and end) — verbatim
|
|
379
|
-
// passthrough here, so idle derives shell-side as the FRAME GAP (end.at → next start.at), the [879] ruling.
|
|
238
|
+
toolCalls: a.toolCalls,
|
|
380
239
|
activity: a.activity,
|
|
381
|
-
// [843]④a convenience: the tail beat's timestamp, so a shell can grey a stale agent without walking the
|
|
382
|
-
// array. TIMESTAMP not a duration on purpose — this detail is a HISTORICAL view, so a served `idleMs`
|
|
383
|
-
// (Date.now()-derived) would drift on every re-read; the shell owns the clock. Absent for pre-1.295 runs
|
|
384
|
-
// whose beats carry no `at` (undefined → key omitted by JSON, same back-compat shape as agentFailures).
|
|
385
240
|
lastActivityAt: a.activity && a.activity.length > 0 ? a.activity[a.activity.length - 1].at : undefined,
|
|
386
241
|
durationMs: durationOf(a),
|
|
387
|
-
queuedAt: a.queuedAt,
|
|
388
|
-
startedAt: a.startedAt,
|
|
242
|
+
queuedAt: a.queuedAt,
|
|
243
|
+
startedAt: a.startedAt,
|
|
389
244
|
endedAt: a.endedAt,
|
|
390
245
|
replayed: a.replayed,
|
|
391
|
-
prompt: a.prompt,
|
|
246
|
+
prompt: a.prompt,
|
|
392
247
|
output: a.output,
|
|
393
248
|
}));
|
|
394
249
|
const byId = new Map();
|
|
@@ -404,9 +259,6 @@ function summarizeWorkflowDetail(run) {
|
|
|
404
259
|
children: [],
|
|
405
260
|
});
|
|
406
261
|
}
|
|
407
|
-
// Walk up `parentGroupId` from `node`; true if the chain LOOPS (self-parent, or a multi-node cycle A→B→A)
|
|
408
|
-
// rather than terminating at a top-level (`undefined`) or dangling (absent) parent. Bounded by the seen-set, so
|
|
409
|
-
// a malformed run can neither hang nor build a circular object that `JSON.stringify` would throw 500 on.
|
|
410
262
|
const inCycle = (start) => {
|
|
411
263
|
const seen = new Set();
|
|
412
264
|
let cur = start;
|
|
@@ -421,27 +273,18 @@ function summarizeWorkflowDetail(run) {
|
|
|
421
273
|
const roots = [];
|
|
422
274
|
for (const node of byId.values()) {
|
|
423
275
|
const parent = node.parentGroupId !== undefined ? byId.get(node.parentGroupId) : undefined;
|
|
424
|
-
// Link to parent ONLY when that can't form a cycle — a self/multi-node cycle member grafts onto root, so the
|
|
425
|
-
// tree is always acyclic (JSON-safe) and no node is ever dropped.
|
|
426
276
|
if (parent && parent !== node && !inCycle(node))
|
|
427
277
|
parent.children.push(node);
|
|
428
278
|
else
|
|
429
|
-
roots.push(node);
|
|
279
|
+
roots.push(node);
|
|
430
280
|
}
|
|
431
281
|
return {
|
|
432
282
|
id: run.id,
|
|
433
283
|
scope: run.scope,
|
|
434
284
|
status: run.status,
|
|
435
|
-
// K-2c (shell §K): the workflow header label + subtitle the MF-W detail view needs — from the script's
|
|
436
|
-
// `export const meta = {name, description}` (core WorkflowRun.name/description). REDACTED for label parity with
|
|
437
|
-
// the fleet run/subagent names (an LLM-authored meta is task-controlled → could carry a secret shape). Per-phase
|
|
438
|
-
// title is already on each `phases[].title`. Omitted when absent (back-compat: older runs have neither).
|
|
439
285
|
...(run.name !== undefined ? { name: redactSecrets(run.name) } : {}),
|
|
440
286
|
...(run.description !== undefined ? { description: redactSecrets(run.description) } : {}),
|
|
441
287
|
stats: run.stats,
|
|
442
|
-
// core 1.293: run-level failed-agent count (agents whose status ended "failed"), stamped at run end only
|
|
443
|
-
// when > 0. A plain number (not task-controlled text) → no redaction. Omitted when absent (back-compat:
|
|
444
|
-
// pre-1.293 runs / zero failures) — [843]④a: the detail view had no failure count to render.
|
|
445
288
|
...(run.agentFailures !== undefined ? { agentFailures: run.agentFailures } : {}),
|
|
446
289
|
startedAt: run.startedAt,
|
|
447
290
|
endedAt: run.endedAt,
|
|
@@ -449,10 +292,6 @@ function summarizeWorkflowDetail(run) {
|
|
|
449
292
|
durationMs: durationOf(run),
|
|
450
293
|
rev: run.rev,
|
|
451
294
|
error: run.error,
|
|
452
|
-
// core 1.234: the script's RETURN VALUE — bounded + redacted AT THE SOURCE (completion-time,
|
|
453
|
-
// same egress discipline as the notifier), but re-redacted here for defence-in-depth parity with
|
|
454
|
-
// name/description (this projection's whitelist predates the field — live-dig T8 caught the omission:
|
|
455
|
-
// TaskOutput carried the result while this detail view silently dropped it). Absent on failed/pre-1.234 runs.
|
|
456
295
|
...(run.result !== undefined ? { result: redactSecrets(run.result) } : {}),
|
|
457
296
|
phases,
|
|
458
297
|
...(unphasedView ? { unphased: unphasedView } : {}),
|
|
@@ -460,27 +299,14 @@ function summarizeWorkflowDetail(run) {
|
|
|
460
299
|
groups: roots,
|
|
461
300
|
};
|
|
462
301
|
}
|
|
463
|
-
/** SSE the live progress of a RUNNING workflow (S8). subscribeWorkflow is IN-PROCESS (replica-local): the
|
|
464
|
-
* iterable ends when the run's channel closes at run_end, or when the client disconnects. A run with no live
|
|
465
|
-
* channel (already finished, or on another replica) yields nothing → the stream ends immediately (the
|
|
466
|
-
* historical view is GET /v1/workflows/:id). Owner-gating (run.scope === principal) is done by the caller;
|
|
467
|
-
* core 1.366 (B-3) additionally doors the subscription itself on scope — a mismatched scope yields an empty
|
|
468
|
-
* stream (same shape as not-found), so the caller passes the SAME scope it owner-gated with. */
|
|
469
302
|
async function streamWorkflowRun(req, res, runId, scope) {
|
|
470
303
|
sseHeaders(res);
|
|
471
|
-
// `type: "meta"` 与 `event: meta` 同名双发(tasks/trace-usage/approvals 三流同款):部分代理只转发
|
|
472
|
-
// `data:` 行,按 data.type 分派的消费端否则认不出首帧。additive,老消费端忽略未知键。
|
|
473
304
|
res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, runId })}\n\n`);
|
|
474
305
|
let closed = false;
|
|
475
|
-
// subscribeWorkflow is a PUSH iterator: a parked `next()` on an idle-but-alive workflow does NOT observe a
|
|
476
|
-
// `closed` flag, so a disconnected client would leave the subscriber queue registered in core's channels and
|
|
477
|
-
// publishWorkflowEvent would buffer events for the dead peer forever (a leak). Drive the iterator explicitly so
|
|
478
|
-
// disconnect can call `it.return()` — which unwinds the parked next() AND runs core's finally to prune the queue.
|
|
479
306
|
const it = subscribeWorkflow(runId, scope)[Symbol.asyncIterator]();
|
|
480
|
-
// Heartbeat so an idle proxy/mesh doesn't drop the connection (and a dead TCP peer is detected → "close").
|
|
481
307
|
const hb = setInterval(() => {
|
|
482
308
|
if (!res.writableEnded)
|
|
483
|
-
res.write(`event: heartbeat\ndata: {}\n\n`);
|
|
309
|
+
res.write(`event: heartbeat\ndata: {}\n\n`);
|
|
484
310
|
}, 15_000);
|
|
485
311
|
if (typeof hb.unref === "function")
|
|
486
312
|
hb.unref();
|
|
@@ -489,12 +315,12 @@ async function streamWorkflowRun(req, res, runId, scope) {
|
|
|
489
315
|
return;
|
|
490
316
|
closed = true;
|
|
491
317
|
clearInterval(hb);
|
|
492
|
-
void it.return?.(undefined);
|
|
318
|
+
void it.return?.(undefined);
|
|
493
319
|
if (!res.writableEnded)
|
|
494
320
|
res.end();
|
|
495
321
|
};
|
|
496
322
|
req.on("close", shutdown);
|
|
497
|
-
res.on("close", shutdown);
|
|
323
|
+
res.on("close", shutdown);
|
|
498
324
|
try {
|
|
499
325
|
for (let next = await it.next(); !next.done; next = await it.next()) {
|
|
500
326
|
if (closed || res.writableEnded)
|
|
@@ -504,8 +330,6 @@ async function streamWorkflowRun(req, res, runId, scope) {
|
|
|
504
330
|
}
|
|
505
331
|
}
|
|
506
332
|
catch {
|
|
507
|
-
// A1/A6/B:归一 error 帧形(`type` + 机器码)。此前只发人话 message ⇒ 消费端要么锚文案、要么把
|
|
508
|
-
// "流断了"和"流正常结束"混为一谈;`workflow.stream_error` 是本帧的铸码(附录 A `*.stream_error` 族)。
|
|
509
333
|
if (!closed && !res.writableEnded)
|
|
510
334
|
res.write(`event: error\ndata: ${JSON.stringify({ type: "error", errorCode: "workflow.stream_error", message: "workflow stream error" })}\n\n`);
|
|
511
335
|
}
|
package/dist/http/run-meta.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { redactSecrets } from "../trace/redact.js";
|
|
2
|
-
/** The denormalized run-row metadata captured at submit: correlation (jobId), credential-derived source,
|
|
3
|
-
* and a redacted objective glance for lists. One place so the three submit paths can't drift. */
|
|
4
2
|
export function runMeta(prepared, source) {
|
|
5
3
|
const obj = prepared.spec.objective ?? "";
|
|
6
4
|
return {
|
|
7
5
|
jobId: prepared.jobId ?? null,
|
|
8
6
|
source,
|
|
9
|
-
// redactSecrets is best-effort (known key/token shapes only — NOT exhaustive; see redact.ts). Acceptable
|
|
10
|
-
// here: the preview rides the fleet-token-gated trace surface, not an end-user-readable channel (council #1).
|
|
11
7
|
objectivePreview: obj ? redactSecrets(obj).slice(0, 120) : null,
|
|
12
8
|
};
|
|
13
9
|
}
|
|
14
|
-
/** Build the cascade config from the operator ladder. Default gate = `status==="completed"` (escalate
|
|
15
|
-
* on a cheap-rung failure); cost-ceiling from the task's budget bounds the cold-rerun tax. */
|
|
16
10
|
export function cascadeConfig(ladder, maxCostUsd) {
|
|
17
11
|
return {
|
|
18
12
|
ladder: ladder.map((model) => ({ model })),
|
package/dist/http/send.js
CHANGED
|
@@ -6,47 +6,15 @@ export function sseHeaders(res, extra) {
|
|
|
6
6
|
...extra,
|
|
7
7
|
});
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
10
|
-
* 一条 SSE 连接的墙钟上限。到点发一帧 `error`/`STREAM_MAX_DURATION`(**不是**静默关闭 —— 客户端分不出
|
|
11
|
-
* 「到帽了请重连」和「跑完了」)然后收流。
|
|
12
|
-
*
|
|
13
|
-
* 🔴 为什么共享(2026-08-08,#176 二轮扫描 finding):这个数与下面的心跳间隔此前在**三处**各手抄一份
|
|
14
|
-
* —— `http/sse-log.ts`(task_run + image_bake 共用的那台泵)、`http/routes/trace-usage.ts`、
|
|
15
|
-
* `http/routes/approvals-assistant.ts`(它的顶注还自称「parity with streamTaskTrace」)。三处各自有测试
|
|
16
|
-
* 钉着**自己那一份**,没有任何跨副本的一致性钉 ⇒ 单边调帽后「parity」只剩注释里那句话。
|
|
17
|
-
*
|
|
18
|
-
* ⚠️ 改这个数请连**文案**一起改:三处的 `STREAM_MAX_DURATION` 消息里把「15-minute」写死在散文里
|
|
19
|
-
* (文案面另有 freeze 门,故此处不做插值——散文与常量的同步靠这行告诫,不靠机器)。
|
|
20
|
-
*/
|
|
21
9
|
export const SSE_MAX_STREAM_MS = 15 * 60 * 1000;
|
|
22
|
-
/**
|
|
23
|
-
* 长时间无事件时的心跳间隔(防中继的 idle timeout 掐流)。与 {@link SSE_MAX_STREAM_MS} 同批收口。
|
|
24
|
-
*
|
|
25
|
-
* ⚠️ 收口的只有**间隔**,**不含帧形**:`sse-log` / `trace-usage` 发 `data: {}`,`approvals-assistant`
|
|
26
|
-
* 发 `data: {"type":"heartbeat"}`。这是真分歧不是笔误 —— trace-usage 自己的 meta 帧还引着 [2373]C-12
|
|
27
|
-
* 「每个 data payload 都带 type」的约定,而它的心跳帧没有 type(probe-sse-1 的注释把这条记成「按
|
|
28
|
-
* data.type 分派的客户端会拿到 undefined」)。统一帧形是**行为面**改动(要么给两处加 type=多发一个键,
|
|
29
|
-
* 要么给 approvals 减 type=消费端破坏),属候裁,不在本批的零行为码范围内。
|
|
30
|
-
*/
|
|
31
10
|
export const SSE_HEARTBEAT_IDLE_MS = 15_000;
|
|
32
11
|
export function sendJson(res, status, body) {
|
|
33
12
|
res.writeHead(status, { "content-type": "application/json" });
|
|
34
13
|
res.end(JSON.stringify(body));
|
|
35
14
|
}
|
|
36
|
-
/** A6(design/158 lens1 §C3/lens3 §C3a): canonical error-response sender for sites that already carry a
|
|
37
|
-
* machine `errorCode` — `errorCode` is a required positional arg (not optional-bag) so a call site
|
|
38
|
-
* cannot regress to the "code but no errorCode" shape the error-code-key-gate forbids. Body shape is
|
|
39
|
-
* `{ error: message, errorCode, ...extra }`; a site that used to ALSO emit a legacy `code` key keeps it
|
|
40
|
-
* by passing `{ code: ... }` inside `extra` — this is a pure call-site migration (zero wire-contract
|
|
41
|
-
* change), not a new response shape. Sites where the two machine-code channels genuinely disagree, or
|
|
42
|
-
* where `error`/`errorCode` aren't cleanly separable from the rest of the body, are left on raw
|
|
43
|
-
* `sendJson` (see the A6 migration notes for the callers still doing that on purpose). */
|
|
44
15
|
export function sendError(res, status, errorCode, message, extra) {
|
|
45
16
|
sendJson(res, status, { error: message, errorCode, ...extra });
|
|
46
17
|
}
|
|
47
|
-
/** B7(design/158 lens1 §C3):`HttpError` 的兜底 catch-all 站点没有自己的语义分支——`err.code` 在 42 个
|
|
48
|
-
* throw 点里只有 3 个带。为了让「errorCode 恒在」在这些站点也成立(而不是靠豁免表放行),按**状态码**
|
|
49
|
-
* 给一个诚实的粗码:它只声明「这类拒绝」,不冒充比 throw 点更细的语义。带 `code` 的 throw 点原样优先。 */
|
|
50
18
|
export function httpErrorCode(status, code) {
|
|
51
19
|
if (code)
|
|
52
20
|
return code;
|