@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
|
@@ -4,49 +4,21 @@ import { usageSummary, usageSeries, usageBreakdown } from "../../usage-analytics
|
|
|
4
4
|
import { sleep } from "../sse-log.js";
|
|
5
5
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
6
6
|
import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
|
|
7
|
+
import { filterFingerprint } from "../cursor-fingerprint.js";
|
|
7
8
|
import { gatedPrincipal } from "../principal-gate.js";
|
|
8
|
-
// [3321] tool-results 读面**共用这一段守卫**([3318] 的字面承诺):新 sub 进的是同一个 `sub` 捕获组,
|
|
9
|
-
// 于是下方 fleetWide / gateOwner / per-id owner-404 三道门原样罩住它,不存在第二套门可以漂。
|
|
10
|
-
// ⚠️ 「both-or-neither」结构不可破:`:id` 与 `sub` 同在一个可选组里,所以 TRACE_RE 从不匹配裸
|
|
11
|
-
// `/v1/tasks/<seg>` —— 那正是 `POST /v1/tasks/stream` 不被本读面遮蔽的原因(见下方 source-summary 注)。
|
|
12
|
-
// 新臂写成 `sub` 的一个 alternation 分支(而不是新开捕获组),组号与既有两个捕获保持逐字不变。
|
|
13
9
|
const TRACE_RE = /^\/v1\/tasks(?:\/([^/]+)(\/turns|\/stream|\/artifacts|\/tool-results\/[^/]+))?$/;
|
|
14
|
-
/** tool-results 读面的 sub 前缀(路由判别 + 段切分单源)。 */
|
|
15
10
|
const TOOL_RESULTS_SUB = "/tool-results/";
|
|
16
|
-
/** `limit` 的 server 权威上限(chars)。缺席 = 不传给 store,用 store 自己的默认;超上限 = 400(不静默夹取:
|
|
17
|
-
* 夹取会让「我要 1M」与「我要 256K」返回同一段内容,调用方无从知道自己被截了)。 */
|
|
18
11
|
const TOOL_RESULT_LIMIT_CAP = 262_144;
|
|
19
|
-
/** `offset` 的上限 = **最窄后端**能表达的值(codex 复审 R1 medium,已核真)。PG 那半边发的是
|
|
20
|
-
* `SUBSTRING(content FROM $1::int …)` 且绑的是 `offset + 1`(`plugins/tool-result-store-sql.ts`),
|
|
21
|
-
* 于是 `offset = 2147483647` 过得了「非负整数」这一关,却在库里变成 `2147483648::int` ⇒ 范围错 ⇒ 500。
|
|
22
|
-
* 「坏输入回 400」是本面的承诺,承诺不能挑后端兑现,所以上限按 int32 - 1 收(留出 +1 的余量)。 */
|
|
23
12
|
const TOOL_RESULT_OFFSET_CAP = 2_147_483_646;
|
|
24
13
|
export async function handleTraceUsage(req, res, url, ctx) {
|
|
25
14
|
const miss = { fell: false };
|
|
26
15
|
await handleTraceUsageBody(req, res, url, ctx, miss);
|
|
27
16
|
return !miss.fell;
|
|
28
17
|
}
|
|
29
|
-
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
30
|
-
* = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
|
|
31
18
|
async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
32
19
|
const { deps } = ctx;
|
|
33
20
|
const { isFleetWide, safeDecode } = ctx.helpers;
|
|
34
|
-
// Task Trace API (S1, read-only): GET /v1/tasks[/:id/turns|/stream]. Authorized by EITHER the full authToken
|
|
35
|
-
// OR a read-only traceToken (sema-registry pulls traces fleet-wide with one read-only token). Handled BEFORE
|
|
36
|
-
// the global gate (like /metrics) so a traceToken-only caller isn't 401'd.
|
|
37
|
-
// ⚠️ Blast radius (council DESIGN-1): fleet-wide by design — traces are NOT per-run-owner-scoped here (the
|
|
38
|
-
// trace token is a deployment-held read-only credential, like metricsToken). Per-tenant/roster scoping is the
|
|
39
|
-
// sema-registry relay's job (admin-session + roster); the kill-switch is leaving TRACE_TOKEN unset.
|
|
40
|
-
// Per-door usage rollup (center systems-UI, SERVICE-PROPOSAL-SYSTEMS-DOMAIN): COUNT by (source,status)
|
|
41
|
-
// since ?sinceSec ago (default 24h, max 30d). Own route — TRACE_RE's :id group is both-or-neither (it
|
|
42
|
-
// deliberately never matches a bare /v1/tasks/<seg>, keeping POST /v1/tasks/stream unshadowed), so this
|
|
43
|
-
// CANNOT ride that block (caught live: the in-block branch was dead code → 404).
|
|
44
21
|
if (req.method === "GET" && url === "/v1/tasks/source-summary") {
|
|
45
|
-
// source-summary is a CROSS-TENANT aggregate (count-by-source/status across ALL tenants) with NO owner-scoped
|
|
46
|
-
// variant — so it is ops-only: fleet-wide callers (system/trace token) on any deployment, or dev-open on a
|
|
47
|
-
// single-user deployment. A multi-tenant deployment without a token must NOT see it (isFleetWide fail-closes —
|
|
48
|
-
// the SAME HIGH tenant-isolation fix as the trace/session gates; this endpoint had the un-migrated fail-open
|
|
49
|
-
// clause, caught by the systematic-audit review's dimension-2 sweep).
|
|
50
22
|
if (!isFleetWide(req)) {
|
|
51
23
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
52
24
|
return;
|
|
@@ -62,11 +34,8 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
|
62
34
|
}
|
|
63
35
|
const traceMatch = req.method === "GET" ? TRACE_RE.exec(url) : null;
|
|
64
36
|
if (traceMatch) {
|
|
65
|
-
// FLEET-WIDE (sees ALL tenants): a system authToken or the read-only traceToken (sema-registry ops
|
|
66
|
-
// relay), or dev-open on a SINGLE-USER deployment. A multi-tenant deployment without a token is owner-gated,
|
|
67
|
-
// NEVER fleet-wide — see isFleetWide (HIGH tenant-isolation audit fix). Otherwise a principal caller is owner-gated.
|
|
68
37
|
const fleetWide = isFleetWide(req);
|
|
69
|
-
const gateOwner = fleetWide ? null : (gatedPrincipal(req, deps.config) || null);
|
|
38
|
+
const gateOwner = fleetWide ? null : (gatedPrincipal(req, deps.config) || null);
|
|
70
39
|
if (!fleetWide && gateOwner === null) {
|
|
71
40
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
72
41
|
return;
|
|
@@ -77,11 +46,7 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
|
77
46
|
}
|
|
78
47
|
const query = new URL(req.url ?? "", "http://x").searchParams;
|
|
79
48
|
const taskId = traceMatch[1];
|
|
80
|
-
const sub = traceMatch[2];
|
|
81
|
-
// per-id owner gate for the principal path (404 for a non-owner — no existence oracle, parity with runOwnerOk).
|
|
82
|
-
// 🔴 该行**只在 principal 路径**被取(fleet 路径按设计不做 per-id 归属判定)——tool-results 面还要用
|
|
83
|
-
// `run.sessionId` 算 ref 前缀,所以这里把取到的行留住(`ownedRun`),fleet 路径由该面自己补一次取行:
|
|
84
|
-
// 少了那次取行,fleet caller 就能拿任意 taskId 配任意 ref 读走**别的 session** 的产物。
|
|
49
|
+
const sub = traceMatch[2];
|
|
85
50
|
let ownedRun;
|
|
86
51
|
if (taskId && gateOwner !== null) {
|
|
87
52
|
const run = await deps.runStore.getRun(taskId);
|
|
@@ -92,7 +57,7 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
|
92
57
|
ownedRun = run;
|
|
93
58
|
}
|
|
94
59
|
if (!taskId)
|
|
95
|
-
await handleTaskList(res, deps.runStore, query, gateOwner);
|
|
60
|
+
await handleTaskList(res, deps.runStore, query, gateOwner);
|
|
96
61
|
else if (sub === "/turns")
|
|
97
62
|
await handleTaskTurns(res, deps.runStore, taskId, query);
|
|
98
63
|
else if (sub === "/stream")
|
|
@@ -106,16 +71,6 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
|
106
71
|
sendError(res, 404, "not_found.route", "not found");
|
|
107
72
|
return;
|
|
108
73
|
}
|
|
109
|
-
// ── 用量统计查询面(web P4,registry AnalyticsSource "service-usage" 数据源)────────────────
|
|
110
|
-
// GET /v1/usage/{summary|series|breakdown}?from&to&…(ISO 或 epoch-ms)。鉴权双支持(回帖契约):
|
|
111
|
-
// - fleet 凭证(authToken/traceToken)= 全量视角,可用 principal 参数过滤任意主体;
|
|
112
|
-
// - registry JWT(1.141 鉴权桥,verifiedPrincipal)= 强制 owner=本人(参数里的 principal 被忽略);
|
|
113
|
-
// - 都没有 → 401。scope 参数 v1 不实装(principal 即租户键;registry 侧按需把 scope→principal 集合映射)。
|
|
114
|
-
// 聚合=usage-analytics 纯函数;store 扫窗触顶 → truncated:true 诚实上抛(绝不静默截断)。
|
|
115
|
-
// 🔴 位置(1.148 修):必须在全局 service-credential 门【之前】(与 trace 门/metrics 同款待遇)——
|
|
116
|
-
// 门函数 isFleetWide 认只读 traceToken,但配了 authToken 的部署里全局门只认 service token/SSO-JWT,
|
|
117
|
-
// usage 挂门后时 traceToken-only 调用(registry 用量中继的姿势)在全局门就 401=门内 traceToken 支持成死代码
|
|
118
|
-
// (a2 实机抓获)。本块所有命中路径自带 401/501 fail-closed 再 return,未命中 sub 落回后续路由(含全局门)。
|
|
119
74
|
if (url.startsWith("/v1/usage/") && req.method === "GET") {
|
|
120
75
|
const sub = url.slice("/v1/usage/".length).split("?")[0];
|
|
121
76
|
if (sub === "summary" || sub === "series" || sub === "breakdown") {
|
|
@@ -124,12 +79,12 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
|
124
79
|
return;
|
|
125
80
|
}
|
|
126
81
|
const fleetWide = isFleetWide(req);
|
|
127
|
-
const jwtOwner = fleetWide ? undefined : gatedPrincipal(req, deps.config);
|
|
82
|
+
const jwtOwner = fleetWide ? undefined : gatedPrincipal(req, deps.config);
|
|
128
83
|
if (!fleetWide && !jwtOwner) {
|
|
129
84
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
130
85
|
return;
|
|
131
86
|
}
|
|
132
|
-
const q = new URL(req.url ?? "", "http://x").searchParams;
|
|
87
|
+
const q = new URL(req.url ?? "", "http://x").searchParams;
|
|
133
88
|
const parseT = (v, dflt) => {
|
|
134
89
|
if (!v)
|
|
135
90
|
return dflt;
|
|
@@ -137,13 +92,12 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
|
137
92
|
return Number.isFinite(n) ? n : NaN;
|
|
138
93
|
};
|
|
139
94
|
const now = Date.now();
|
|
140
|
-
const from = parseT(q.get("from"), now - 7 * 86_400_000);
|
|
95
|
+
const from = parseT(q.get("from"), now - 7 * 86_400_000);
|
|
141
96
|
const to = parseT(q.get("to"), now);
|
|
142
97
|
if (Number.isNaN(from) || Number.isNaN(to) || from >= to) {
|
|
143
98
|
sendError(res, 400, "request.query_invalid", "invalid from/to (ISO-8601 or epoch-ms; from < to)");
|
|
144
99
|
return;
|
|
145
100
|
}
|
|
146
|
-
// owner 过滤:JWT 主体强制=本人;fleet 凭证可选 principal 参数。
|
|
147
101
|
const owner = jwtOwner ?? (q.get("principal") ?? undefined);
|
|
148
102
|
try {
|
|
149
103
|
const scan = await deps.runStore.usageScan(from, to, owner !== undefined ? { owner } : undefined);
|
|
@@ -166,7 +120,6 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
|
166
120
|
sendError(res, 400, "request.query_invalid", "dimension=principal|model");
|
|
167
121
|
return;
|
|
168
122
|
}
|
|
169
|
-
// JWT 主体查 dimension=principal 只会看到自己一桶(owner 已强制)——语义自然成立,无泄漏面。
|
|
170
123
|
sendJson(res, 200, { ...base, dimension, breakdown: usageBreakdown(scan.rows, dimension) });
|
|
171
124
|
}
|
|
172
125
|
}
|
|
@@ -179,44 +132,42 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
|
|
|
179
132
|
}
|
|
180
133
|
miss.fell = true;
|
|
181
134
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return Buffer.from(JSON.stringify(c)).toString("base64url");
|
|
135
|
+
function encodeCursor(c, f) {
|
|
136
|
+
return Buffer.from(JSON.stringify({ ...c, f })).toString("base64url");
|
|
185
137
|
}
|
|
186
|
-
function decodeCursor(s) {
|
|
138
|
+
function decodeCursor(s, expectF) {
|
|
187
139
|
if (!s)
|
|
188
140
|
return undefined;
|
|
189
141
|
try {
|
|
190
142
|
const o = JSON.parse(Buffer.from(s, "base64url").toString());
|
|
191
|
-
|
|
192
|
-
|
|
143
|
+
if (o.f !== undefined && (typeof o.f !== "string" || o.f !== expectF))
|
|
144
|
+
return "mismatch";
|
|
193
145
|
return o.createdAt && o.taskId && !Number.isNaN(Date.parse(o.createdAt)) ? { createdAt: o.createdAt, taskId: o.taskId } : undefined;
|
|
194
146
|
}
|
|
195
147
|
catch {
|
|
196
148
|
return undefined;
|
|
197
149
|
}
|
|
198
150
|
}
|
|
199
|
-
/** GET /v1/tasks — cheap list summary (contract 2.1), keyset-paginated newest-first. */
|
|
200
151
|
async function handleTaskList(res, runStore, query, forceOwner) {
|
|
201
152
|
const status = query.get("status") ?? undefined;
|
|
202
|
-
const jobId = query.get("jobId") ?? undefined;
|
|
203
|
-
const source = query.get("source") ?? undefined;
|
|
204
|
-
// forceOwner (set on the principal path) PINS the listing to the caller's own runs — it overrides any ?owner
|
|
205
|
-
// so a principal can't list another tenant. null/undefined (fleet-wide path) → honor the ?owner query (ops view).
|
|
153
|
+
const jobId = query.get("jobId") ?? undefined;
|
|
154
|
+
const source = query.get("source") ?? undefined;
|
|
206
155
|
const owner = forceOwner != null ? forceOwner : (query.get("owner") ?? undefined);
|
|
207
156
|
const limit = Math.min(100, Math.max(1, Number(query.get("limit") ?? 50) || 50));
|
|
208
|
-
const
|
|
157
|
+
const f = filterFingerprint([status, jobId, source, owner]);
|
|
158
|
+
const cursor = decodeCursor(query.get("cursor"), f);
|
|
159
|
+
if (cursor === "mismatch") {
|
|
160
|
+
sendError(res, 400, "request.cursor_filter_mismatch", "cursor was issued under a different filter (status/jobId/source/owner) — restart from the first page without a cursor");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
209
163
|
const rows = await runStore.listRuns({ ...(status ? { status } : {}), ...(jobId ? { jobId } : {}), ...(source ? { source } : {}), ...(owner ? { owner } : {}), ...(cursor ? { cursor } : {}), limit: limit + 1 });
|
|
210
164
|
const hasMore = rows.length > limit;
|
|
211
165
|
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
212
|
-
const tasks = page.map((r) => taskSummary(r));
|
|
166
|
+
const tasks = page.map((r) => taskSummary(r));
|
|
213
167
|
const last = page[page.length - 1];
|
|
214
|
-
const nextCursor = hasMore && last ? encodeCursor({ createdAt: last.createdAt, taskId: last.taskId }) : undefined;
|
|
168
|
+
const nextCursor = hasMore && last ? encodeCursor({ createdAt: last.createdAt, taskId: last.taskId }, f) : undefined;
|
|
215
169
|
sendJson(res, 200, { tasks, ...(nextCursor ? { nextCursor } : {}) });
|
|
216
170
|
}
|
|
217
|
-
/** GET /v1/tasks/:id/turns — lazy history (contract 2.2), backward-paginated by turn seq. */
|
|
218
|
-
/** GET /v1/tasks/:id/artifacts — what the agent produced (M2). A pure projection of the
|
|
219
|
-
* durable log (no new write path), so it works for every historical run. */
|
|
220
171
|
async function handleTaskArtifacts(res, runStore, taskId) {
|
|
221
172
|
const run = await runStore.getRun(taskId);
|
|
222
173
|
if (!run) {
|
|
@@ -226,85 +177,11 @@ async function handleTaskArtifacts(res, runStore, taskId) {
|
|
|
226
177
|
const events = await runStore.getEvents(taskId, 0);
|
|
227
178
|
sendJson(res, 200, { artifacts: projectArtifacts(events, { taskId, jobId: run.jobId }) });
|
|
228
179
|
}
|
|
229
|
-
|
|
230
|
-
* GET /v1/tasks/:id/tool-results/:ref?offset&limit —— core `ToolResultStore.get` 的 wire 镜像([3321])。
|
|
231
|
-
*
|
|
232
|
-
* 三件事,顺序本身就是契约:
|
|
233
|
-
*
|
|
234
|
-
* ① **query 先判**。坏 offset/limit ⇒ 400,且这一判**先于**任何 ref/归属/store 在场性判定 —— 否则
|
|
235
|
-
* 「坏 query + 已知 ref = 400」而「坏 query + 未知 ref = 404」,状态码之差立刻成了存在性 oracle。
|
|
236
|
-
* query 的合法性只取决于 query 自身,与调用方看不看得见这个 ref 无关,所以先判是安全的。
|
|
237
|
-
*
|
|
238
|
-
* ② **归属绑定 = 店里记的出处(`ownerOf`)与本 run 的 session/task 精确相等**(实现见 ②-a),不解析 ref、
|
|
239
|
-
* 不复制段转义规则,**且没有第二条腿**。
|
|
240
|
-
*
|
|
241
|
-
* 下面这一大段是这条判据的**病历**(前缀形 → 本 task 日志作证形 → 出处形),留着是因为「为什么不
|
|
242
|
-
* 照 ref 的字面去判」每隔一阵就被重新问一次;当前生效的判据只有 🟡 段之后的那一条。
|
|
243
|
-
*
|
|
244
|
-
* 先说为什么**不是**前缀判定([3321]② 的字面形、[3322] 的指定形):core 的 ref 形是
|
|
245
|
-
* `tr_<seg(session)>_<seg(toolCallId)>`,而 `_` 在两段里都合法 ⇒ session `team` 的前缀 `tr_team_`
|
|
246
|
-
* 同样命中 session `team_blue` 的 ref(`tr_team_blue_toolu_x`)。本仓的 sessionId 是**调用方自选的
|
|
247
|
-
* 任意 ≤64 字符串**(`http/server.ts` 的 submit 面只查长度,刻意宽松),所以租户 A 可以把自己的
|
|
248
|
-
* sessionId 取成租户 B 的前缀,拿自己的 taskId 配 B 的 ref 读走 B 的产物(codex 复审 R1 high,已核真)。
|
|
249
|
-
* 这在字符串层面**无解**:把余段当 toolCallId 重铸一遍会逐字相等(拼接本身有歧义),而按「更长的
|
|
250
|
-
* session 是否存在」去探,既堵不住折成 base+sha 的 sessionId(候选串 ≠ 真 id),又反手造出一个新的
|
|
251
|
-
* 跨租户**拒绝**面(别人注册一个 `<你的 session>_toolu` 就能让你自己的 ref 永远 404)——R2 复审两条
|
|
252
|
-
* 中标,已撤回该启发式。
|
|
253
|
-
*
|
|
254
|
-
* 当年的改判据(**已被 ②-a 取代,见下**):ref **必须由本 task 自己的耐久事件日志作证**。日志里每条 tool_start/tool_end 都带
|
|
255
|
-
* `toolCallId`(`trace/project.ts` 的 toolStartEventData / toolEndEventData 是唯一铸造点),用 core
|
|
256
|
-
* 的**同一个** `buildToolResultRef` 把它们铸成 ref 集合,再要求 `ref` 是其中一员 —— 精确相等,
|
|
257
|
-
* 零解析、零转义规则复制,歧义面结构上不存在(A 的 taskId 只作证 A 自己的 toolCallId)。
|
|
258
|
-
* 辖域(诚实写明,也正是本路由的语义):本面服务的是**这条 task 的 trace 里看得见的那些产物** ——
|
|
259
|
-
* trace 的 tool-result 块与本判据读的是同一份日志,所以「trace 上看得见 ⇒ 这里读得到」成立;
|
|
260
|
-
* 同 session 里**别的 task** 的 ref(或日志已过保留窗被逐出的)按 404 同形拒,请到那条 task 上读。
|
|
261
|
-
*
|
|
262
|
-
* 🟡 **两条残余的现状(7.14.0 合并窗复扫改口,下面原文保留作病历)**。core 按当年这里写的方向动了刀:
|
|
263
|
-
* `put` 带出处落库 + `ownerOf` 读回,读面改用出处判定(实现见 ②-a)。逐条对账:
|
|
264
|
-
* · (b) 拼接非单射的越权面**已彻底消失** —— ref 铸法换成 `~` 多段,`~` 不在段的合法字符集里。
|
|
265
|
-
* · (a) 合成 id 铸的 ref **只销了一半**:offload / budget / compaction 三个写点的出处只声明
|
|
266
|
-
* session ⇒ 现在读得到;而 `task-registry-monitor` 的 spill(`<stream>_seg<n>`)与
|
|
267
|
-
* `task-registry-agent` 的 spill(`c<cycle>`)在出处里**点名了 taskId,填的是 core TaskRegistry
|
|
268
|
-
* 的 handle id**(`b/w/a/m` 前缀 + 16 hex,见 core `task-registry.js`),与本路由段里的 server
|
|
269
|
-
* run id(uuidv7)是两个命名空间,恒不相等 ⇒ 这两族 ref 在本面**恒 404**(改前也 404,不是回归)。
|
|
270
|
-
* 而它们恰恰是 core 在成品文本里明写「call ReadToolResult with ref …」露给调用方的那批。
|
|
271
|
-
* 真解仍在 core 侧(出处该声明的是**调用方认得的** task 坐标,或干脆只声明 session);本仓不自行
|
|
272
|
-
* 放宽 taskId 那条腿 —— 那是单方面改判定语义,归属判据会与上游各漂一份。
|
|
273
|
-
* 日志重铸那条腿**已删**(见 ②-b:它对任何真实行都不可达,且每次未命中还要付一次全量日志读)。
|
|
274
|
-
*
|
|
275
|
-
* ⚠️ 两处**已知残余**,都要 core 侧动刀才能消,写在明处而不是让下一个人自己撞(codex 复审 R3):
|
|
276
|
-
*
|
|
277
|
-
* (a) **覆盖面**:core 除了「按工具调用 id 卸载」这条主路,还从**合成 id** 铸 ref ——
|
|
278
|
-
* `task-registry-monitor` 的 `<handle>_<stream>_seg<n>`、`task-registry-agent` 的 `<handle>_c<n>`、
|
|
279
|
-
* `runner/compaction-call-options` 的 `<toolCallId>_s<sha>`、budget 的 `idx<k>`。这些 id **不进**
|
|
280
|
-
* 耐久事件日志(日志记的是外层调用 id),所以那几类 ref 在本面一律 404 同形拒。
|
|
281
|
-
* 方向明确:**不**在本仓照抄那几条命名规则去反推(复制上游规则 = 两处各自漂,宪法禁),
|
|
282
|
-
* 真解是 core 在写入时带上 (session/task) 出处、或给出 core 属主的精确成员判定。
|
|
283
|
-
*
|
|
284
|
-
* (b) **注入性**:`buildToolResultRef` 对 `(session, toolCallId)` **不是单射**(`_` 两段都合法):
|
|
285
|
-
* `("team","blue_toolu_1")` 与 `("team_blue","toolu_1")` 铸出同一枚 ref。所以「本 task 日志作证过
|
|
286
|
-
* 一个恰好长成 `<受害 session 尾巴>_<受害 toolCallId>` 的 toolCallId」时,判据仍会放行。
|
|
287
|
-
* 与被撤掉的前缀形的差别是**门槛**:前缀形只要知道 ref 就能读,本形还要求攻击者能让**模型网关**
|
|
288
|
-
* 铸出那一枚精确的 toolCallId(provider 铸,本服务不控也不产)。同样只有 core 换成单射编码才真消。
|
|
289
|
-
* 这里**不留特征化测试**去钉这条通路 —— 把缺陷钉成契约是另一种病。
|
|
290
|
-
*
|
|
291
|
-
* ③ **同形 404**(unknown ref / 出处判不过(含无属主行)/ 本部署没有 durable store)。任何可区分的响应都是
|
|
292
|
-
* 缺陷:前两者之差会告诉调用方「这枚 ref 存在,只是不是你的」;第三者若回 501 则会把部署形告诉一个
|
|
293
|
-
* 连自己的 ref 都读不到的 caller。文案恒定且**不回显 ref**(回显=把输入原样反射进错误面)。
|
|
294
|
-
* 残余面(诚实写明):响应字节同形,**时序**并不同形 —— 三条臂到达的后端调用数不同。要抹平时序得让
|
|
295
|
-
* 每条臂都跑满全部后端调用,那是另一档代价/收益裁定,不在本批;本批的判据是响应本身不给 oracle。
|
|
296
|
-
*/
|
|
297
|
-
async function handleToolResult(res, store, runStore, taskId, ownedRun,
|
|
298
|
-
/** 已过 `safeDecode` 的 ref;`null` = %-序列坏(客户端错,但同形 404 —— 它同样不该成为一条可区分的信道)。
|
|
299
|
-
* 解码用调用点传下来的 helper 而不是本地 try/catch:那条规则(坏序列 ⇒ null,绝不 500)在本仓只有
|
|
300
|
-
* `safeDecode` 一个属主,复制一份等于把它变成两个。 */
|
|
301
|
-
ref, query) {
|
|
302
|
-
// ① query 校验(先于一切存在性判定 —— 见头注)。非负十进制整数,`Number()` 的宽松形(空串/前后空白/
|
|
303
|
-
// "1e3"/"0x10"/"1.5")一律不认:宽松解析会让两个不同的请求串静默映射到同一段内容。
|
|
180
|
+
async function handleToolResult(res, store, runStore, taskId, ownedRun, ref, query) {
|
|
304
181
|
const intParam = (name, cap) => {
|
|
305
182
|
const raw = query.get(name);
|
|
306
183
|
if (raw === null)
|
|
307
|
-
return undefined;
|
|
184
|
+
return undefined;
|
|
308
185
|
if (!/^\d+$/.test(raw))
|
|
309
186
|
return "bad";
|
|
310
187
|
const n = Number(raw);
|
|
@@ -324,46 +201,18 @@ ref, query) {
|
|
|
324
201
|
sendError(res, 400, "request.query_invalid", "limit must be a non-negative integer of at most 262144");
|
|
325
202
|
return;
|
|
326
203
|
}
|
|
327
|
-
// ③ 同形 404 的**唯一**下发点:每条臂都必须走这里,不许任何一条自己写一句(文案分家=oracle 复活)。
|
|
328
204
|
const notFound = () => sendError(res, 404, "not_found.tool_result", "tool result not found");
|
|
329
205
|
if (ref === null)
|
|
330
|
-
return notFound();
|
|
331
|
-
// fleet 路径没做 per-id 取行(见调用点注),这里补齐 —— 归属绑定要 run.sessionId,没有行就没有绑定面。
|
|
206
|
+
return notFound();
|
|
332
207
|
const run = ownedRun ?? (await runStore.getRun(taskId));
|
|
333
208
|
if (!run)
|
|
334
209
|
return notFound();
|
|
335
|
-
// ②-a 出处判定(#119,core 5.26.0 —— 上面 (a)/(b) 两条残余的**正解到货**)。
|
|
336
|
-
// 店里现在自己记着「这枚 ref 属于谁」(`put` 的第三参落库,`ownerOf` 读回),读面拿**自己已知的字段**
|
|
337
|
-
// 比对即可:零解析、零段转义规则复制,拼接歧义那条残余彻底消失,合成 id 那条**消了一半**
|
|
338
|
-
// (monitor/agent spill 两族的出处点名的 taskId 是 core handle id,与本函数收到的 server run id 不同
|
|
339
|
-
// 命名空间 ⇒ 恒判假;详见头注 🟡 段)。
|
|
340
|
-
//
|
|
341
|
-
// 🔴 为什么必须改而不是"锦上添花":5.26.0 起 offload ref 是多段单射形(内容坐标进 ref),服务端手里
|
|
342
|
-
// 没有内容 ⇒ 原来那条重铸判据**重铸不出来** ⇒ 每一枚真 offload ref 都会 404。读面整死。
|
|
343
|
-
//
|
|
344
|
-
// ⚠️ 辖域(有意的口径变化,写在明处):出处的粒度由 core 定 —— offload/budget/projection 三个写点只声明
|
|
345
|
-
// session,把 taskId 钉在那里会把「一个 session 的两条 task 共用一枚 ref」这个**设计内**的共享形变成拒绝
|
|
346
|
-
// (core d.ts 逐字裁过)。所以有出处时的判据是「同 session,且出处**若点名了** task 则必须是本 task」——
|
|
347
|
-
// 相对旧的「本 task 日志作证过」是**放宽到同 session**。放宽的边界仍在 owner 之内(run 归属门在本函数
|
|
348
|
-
// 之前已过,session 的属主就是这个 caller),换来的是这条读面在 5.26.0 上还活着。
|
|
349
210
|
const owner = store?.ownerOf ? await store.ownerOf(ref) : undefined;
|
|
350
211
|
const ownedByThisCaller = owner !== undefined && owner.sessionId === run.sessionId && (owner.taskId === undefined || owner.taskId === taskId);
|
|
351
|
-
// ②-b **没有**第二条腿:出处判不过就是 404(unowned 行也在内 —— core 的口径就是「读面对 unowned
|
|
352
|
-
// fail-closed」)。这里曾有一条「用本 task 耐久日志作证过的 toolCallId 经 `buildToolResultRef` 重铸再
|
|
353
|
-
// 精确相等」的兜底,7.14.0 合并窗复扫判定为**结构性死支**并整条删除,证据链写在明处:
|
|
354
|
-
// · 重铸只喂得起两个参数(服务端手里没有内容)⇒ 铸出的是**两段**形 `tr_<sid>~<callId>`;
|
|
355
|
-
// · core 5.27.0 的**每一个**落库写点铸的都是 ≥3 段(offload/budget/compaction 三段带内容摘要、
|
|
356
|
-
// detail 卸载四段、monitor/agent spill 三段带 `<stream>_seg<n>`/`c<cycle>`),而 `~` 不在段的合法
|
|
357
|
-
// 字符集里 ⇒ **段数不同的 ref 恒不相等** ⇒ 该判据对库里任何真实行都不可能成立;
|
|
358
|
-
// · 旧引擎(≤5.25.0)的 `tr_<sid>_<call>` 同样重铸不出来(分隔符换了),而按旧规则手写一个 legacy
|
|
359
|
-
// 编码器 = 复制上游规则(本文件头注自己禁掉的那件事);存量行的真处置是**删表重建**(见 CHANGELOG)。
|
|
360
|
-
// 删的另一半理由是**代价**:走到这条腿的前提是「出处缺席」,而任意乱写的未知 ref 恰好也落在这里,
|
|
361
|
-
// 于是每个必然 404 的请求都要付一次整份耐久事件日志读(已鉴权调用方可用随机 ref 放大后端读),
|
|
362
|
-
// 换不到任何放行。行为面只收窄不放宽:能放行的集合原本就是空集。
|
|
363
212
|
if (!ownedByThisCaller)
|
|
364
213
|
return notFound();
|
|
365
214
|
if (!store)
|
|
366
|
-
return notFound();
|
|
215
|
+
return notFound();
|
|
367
216
|
const slice = await store.get(ref, { ...(offset !== undefined ? { offset } : {}), ...(limit !== undefined ? { limit } : {}) });
|
|
368
217
|
if (!slice)
|
|
369
218
|
return notFound();
|
|
@@ -376,20 +225,15 @@ async function handleTaskTurns(res, runStore, taskId, query) {
|
|
|
376
225
|
return;
|
|
377
226
|
}
|
|
378
227
|
const limit = Math.min(100, Math.max(1, Number(query.get("limit") ?? 20) || 20));
|
|
379
|
-
const cursorSeq = Number(query.get("cursor") ?? 0) || 0;
|
|
228
|
+
const cursorSeq = Number(query.get("cursor") ?? 0) || 0;
|
|
380
229
|
const [events, retainedFrom] = await Promise.all([runStore.getEvents(taskId, 0), runStore.retainedFrom(taskId)]);
|
|
381
|
-
const all = projectEvents(events);
|
|
230
|
+
const all = projectEvents(events);
|
|
382
231
|
const eligible = cursorSeq > 0 ? all.filter((t) => t.seq < cursorSeq) : all;
|
|
383
|
-
const turns = eligible.slice(Math.max(0, eligible.length - limit));
|
|
232
|
+
const turns = eligible.slice(Math.max(0, eligible.length - limit));
|
|
384
233
|
const oldest = turns[0];
|
|
385
|
-
// The turns cursor is a plain turn-seq (turns are seq-ordered); it is opaque to the client per the contract
|
|
386
|
-
// (2.2), so it needn't match the list endpoint's base64url(createdAt,taskId) keyset cursor (council).
|
|
387
234
|
const nextCursor = eligible.length > limit && oldest ? String(oldest.seq) : undefined;
|
|
388
235
|
sendJson(res, 200, { turns, ...(nextCursor ? { nextCursor } : {}), retainedFrom });
|
|
389
236
|
}
|
|
390
|
-
/** GET /v1/tasks/:id/stream — resumable SSE (contract 2.3). Maps the durable event log to the contract event
|
|
391
|
-
* names, declares mode in the first event, 416s a resume point evicted past retention, heartbeats, and emits an
|
|
392
|
-
* explicit `error` on stall so the client never hangs. */
|
|
393
237
|
async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
|
|
394
238
|
const run0 = await runStore.getRun(taskId);
|
|
395
239
|
if (!run0) {
|
|
@@ -397,22 +241,17 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
|
|
|
397
241
|
return;
|
|
398
242
|
}
|
|
399
243
|
const lastId = Number(req.headers["last-event-id"] ?? new URL(req.url ?? "", "http://x").searchParams.get("from") ?? 0);
|
|
400
|
-
let from = Number.isFinite(lastId) ? lastId : 0;
|
|
244
|
+
let from = Number.isFinite(lastId) ? lastId : 0;
|
|
401
245
|
if (from > 0) {
|
|
402
246
|
const retainedFrom = await runStore.retainedFrom(taskId);
|
|
403
247
|
if (retainedFrom > from + 1) {
|
|
404
|
-
// The events the client wants to resume past were evicted → it must re-sync via GET /turns (contract 2.3).
|
|
405
248
|
sendError(res, 416, "limit.retention_evicted", "resume point evicted past retention", { retainedFrom });
|
|
406
249
|
return;
|
|
407
250
|
}
|
|
408
251
|
}
|
|
409
252
|
sseHeaders(res);
|
|
410
|
-
|
|
411
|
-
// SSE shape changed, e.g. when P1 adds token-delta granularity). resumeFrom echoes the honored Last-Event-ID.
|
|
412
|
-
res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, mode: "delta", resumeFrom: from })}\n\n`); // [2373]C-12:data.type 双发与内容帧一致
|
|
253
|
+
res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, mode: "delta", resumeFrom: from })}\n\n`);
|
|
413
254
|
let closed = false;
|
|
414
|
-
// design/245 件1:断连清理走共享助手(req+res 双挂 + 一次性闸)。只挂 `req` 时,请求流被读干的
|
|
415
|
-
// 形下这条旗永远翻不过来,泵会对着死 socket 一路轮询到 15 分钟帽 —— 理由全文在 `sse-lifecycle.ts` 顶注。
|
|
416
255
|
bindSseLifecycle(req, res, () => {
|
|
417
256
|
closed = true;
|
|
418
257
|
});
|
|
@@ -432,7 +271,7 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
|
|
|
432
271
|
if (!run)
|
|
433
272
|
break;
|
|
434
273
|
if (run.status !== "running") {
|
|
435
|
-
write(await runStore.getEvents(taskId, from));
|
|
274
|
+
write(await runStore.getEvents(taskId, from));
|
|
436
275
|
break;
|
|
437
276
|
}
|
|
438
277
|
const stale = Date.now() - new Date(run.updatedAt).getTime() > staleMs;
|
|
@@ -441,8 +280,6 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
|
|
|
441
280
|
break;
|
|
442
281
|
}
|
|
443
282
|
if (Date.now() - start > SSE_MAX_STREAM_MS) {
|
|
444
|
-
// BL-16: the stream hit its 15-min cap but the RUN is still going — emit a terminal event so the client
|
|
445
|
-
// can tell "reconnect to continue" from "task completed" (a silent close looked like completion).
|
|
446
283
|
res.write(`event: error\ndata: ${JSON.stringify({ type: "error", errorCode: "STREAM_MAX_DURATION", message: "trace stream reached its 15-minute cap — reconnect with Last-Event-ID to continue (the run is still active)" })}\n\n`);
|
|
447
284
|
break;
|
|
448
285
|
}
|