@sema-agent/server 7.43.0 → 7.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
package/dist/fleet/fleet-bus.js
CHANGED
|
@@ -1,74 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MF-Fleet (shell-host data-contract, the dynamic-stream obligation) — the SERVICE-side live fleet
|
|
3
|
-
* aggregation bus + state, backing `GET /v1/fleet/stream`.
|
|
4
|
-
*
|
|
5
|
-
* The fleet view (CC 187 0616_053) animates a MULTI-ROW tree: every active task's `status/elapsedMs/tokens/
|
|
6
|
-
* queuedCount` and every workflow's `doneCount N/M · failedCount` TRANSITION over time. That is a *push* stream,
|
|
7
|
-
* not a one-shot read: the backend must emit every transition for as long as a run is live, with STABLE per-row
|
|
8
|
-
* identity so the UI updates the right row in place.
|
|
9
|
-
*
|
|
10
|
-
* This bus is the in-process aggregator: the run lifecycle (createRun → queued/running, per-turn token accrual,
|
|
11
|
-
* terminal → completed/failed) and the workflow lifecycle (WorkflowRun put/update → doneCount/status) publish
|
|
12
|
-
* their per-row deltas here; `GET /v1/fleet/stream` subscribes, sends a SNAPSHOT on connect, then forwards every
|
|
13
|
-
* delta. Identity: tasks keyed by `id` (the durable runId/taskId), workflows by their workflow runId, subagent
|
|
14
|
-
* sub-rows (round 5, pending core's `task_progress` arm) by `parentId`+agent id (E2 `parentToolCallId`).
|
|
15
|
-
*
|
|
16
|
-
* 🔒 boundary (design/99 §0): the FLEET aggregation + the SSE wire is **service** (UI/deployment-state view over
|
|
17
|
-
* the runs ledger + workflow store). The engine semantics (a run's own events, a subagent's usage ticks) are
|
|
18
|
-
* core's — the bus only AGGREGATES what the lifecycle already produces. It carries NO new engine state.
|
|
19
|
-
*
|
|
20
|
-
* NOT durable / NOT cross-replica: a live fleet is this replica's active runs (mirrors `steerableRuns` —
|
|
21
|
-
* replica-local). A cross-replica fleet roll-up is a fleet-token-gated trace-API concern (separate), not this.
|
|
22
|
-
*/
|
|
23
1
|
import { EventEmitter } from "node:events";
|
|
24
2
|
import { deriveAgentDisplayStatus, isDelegatedAgentTerminal } from "@sema-agent/core";
|
|
25
3
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
26
4
|
import { redactSecrets } from "../trace/redact.js";
|
|
27
|
-
/** [2687-cli] 幽灵行案的单源判别:一条 `task_notification` 只有在 **agent 族 × 终态** 时才允许打
|
|
28
|
-
* `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
|
|
29
|
-
* 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
|
|
30
|
-
* agent 形瞬态终帧,壳终态留存池把这帧渲 60s = footer 幽灵子行;monitor 的 `status:"event"` 还被
|
|
31
|
-
* 终态映射折成 `failed`。
|
|
32
|
-
*
|
|
33
|
-
* 🔴 A-033.1(#256):判别**本体已换 core 的 {@link isDelegatedAgentTerminal}**。此前这里是一份手写语义
|
|
34
|
-
* 孪生(自己枚举 `completed|failed|killed|cancelled`),头注自陈「与 core 语义同源——core 根导出后换装
|
|
35
|
-
* import 删本镜像」。core 现已从包根导出(`index.d.ts:109`),且它的终态集与 pending 店的驱逐偏好共用
|
|
36
|
-
* 同一份 `TERMINAL_STATUSES`(core 原话:「so "terminal" can never mean two things in this module」)——
|
|
37
|
-
* 孪生留在这里等于让 fleet 面板对「终态」的理解可以和引擎分家。本函数保留为**具名的 fleet 语义位**
|
|
38
|
-
* (调用点读的是「这条通知该不该打 fleet 的 onChildTerminal」,不是「core 怎么判终态」)。 */
|
|
39
5
|
export function isFleetAgentTerminalNotification(n) {
|
|
40
6
|
return isDelegatedAgentTerminal(n);
|
|
41
7
|
}
|
|
42
|
-
/**
|
|
43
|
-
* durable {@link WorkflowRun} → 一条 fleet workflow 行的**纯投影**(`build*`=纯数据,无行为、不发布)。
|
|
44
|
-
*
|
|
45
|
-
* 从 `JournalingWorkflowRunStore.publishFleet` **原样**抽出,因为它现在有两个调用点,而两处各写一遍
|
|
46
|
-
* 必然漂移(本仓在 startedCount 口径上已吃过同款):
|
|
47
|
-
* ① 活写路径 —— put/update 的写观察点(行的唯一写者,见该类注);
|
|
48
|
-
* ② `/v1/fleet/stream` 连接时快照的**有界终态行窗**(#189 修方向 1)—— 引擎重启后 boot 扫描把前世
|
|
49
|
-
* running run 判死,终帧与撤行在同一同步栈内背靠背发出,之后才连上的客户端连快照都看不见那一行。
|
|
50
|
-
*/
|
|
51
8
|
export function buildFleetWorkflowRow(id, run) {
|
|
52
9
|
const agents = run.agents ?? [];
|
|
53
|
-
// [2336] doneCount 与 failedCount **不相交**:done 只数 completed。契约以此为前提(本文件 startedCount
|
|
54
|
-
// 注的回退式 done+failed ≤ started);把 failed 也计进 done 会让全失败 workflow 的终帧渲成 "N done"
|
|
55
|
-
// (cli 4.1.3 实测 done=2 failed=2 started=2)。
|
|
56
10
|
const done = agents.filter((a) => a.status === "completed").length;
|
|
57
11
|
const failed = agents.filter((a) => a.status === "failed").length;
|
|
58
|
-
// cli [1726] 二①:CC 规模告警的分母是 **started**(已启动),而 `totalCount`(= agents.length)是**计划总数**
|
|
59
|
-
// (含排队中)。判别口径走 core 导出的 `deriveAgentDisplayStatus` —— 一个 agent 已在 `run.agents` 里但
|
|
60
|
-
// `startedAt` 未置就是排队中。**不自己写 `startedAt !== undefined`**:那是 core 的显示契约,两端各写一遍
|
|
61
|
-
// 必然漂移,用它的函数则 core 一改、两端同时跟随。
|
|
62
12
|
const started = agents.filter((a) => deriveAgentDisplayStatus(a, run.status) !== "queued").length;
|
|
63
13
|
return {
|
|
64
14
|
id,
|
|
65
|
-
// [WF2-A parity] redact the workflow label surfaces for parity with the run + subagent-child names (below):
|
|
66
|
-
// a tool-launched (LLM-authored) workflow's meta.name/description is task-controlled and could carry a secret shape.
|
|
67
15
|
name: redactSecrets(run.name ?? "Dynamic workflow"),
|
|
68
16
|
...(run.description ? { description: redactSecrets(run.description) } : {}),
|
|
69
17
|
scope: run.scope,
|
|
70
|
-
// codex-6 F2:sessionId 必须随行——streamFleet 对无 sessionId 的行按「同 principal 全会话可见」
|
|
71
|
-
// 兜底,漏发=A 会话的 workflow 名/进度/token 泄进 B 会话的 ?session= 过滤流。
|
|
72
18
|
...(run.originatingSessionId ? { sessionId: run.originatingSessionId } : {}),
|
|
73
19
|
status: run.status,
|
|
74
20
|
doneCount: done,
|
|
@@ -76,86 +22,45 @@ export function buildFleetWorkflowRow(id, run) {
|
|
|
76
22
|
failedCount: failed,
|
|
77
23
|
startedCount: started,
|
|
78
24
|
tokens: (run.stats?.tokens ?? 0) + (run.stats?.nested?.tokens ?? 0),
|
|
79
|
-
// [1294]:跑动中也带时长(1.232 只在 endedAt 后带——clay 验收轮实锚面板恒显 0s)。终态用
|
|
80
|
-
// endedAt 定格,活跑用 now-startedAt(每次 put/update 观察点刷新,壳侧读帧即当前时长)。
|
|
81
25
|
elapsedMs: (run.endedAt ?? Date.now()) - run.startedAt,
|
|
82
26
|
};
|
|
83
27
|
}
|
|
84
|
-
/**
|
|
85
|
-
* The process-local fleet aggregation bus. Holds the current active set + fans out deltas to SSE subscribers.
|
|
86
|
-
* Upserts MERGE (a partial delta patches the existing row), so a publisher can emit just the field that changed
|
|
87
|
-
* (e.g. only `tokens`) and the row stays whole. Removal drops the row (terminal). Subscribers get a snapshot of
|
|
88
|
-
* the current set immediately, then every subsequent delta.
|
|
89
|
-
*/
|
|
90
28
|
export class FleetEventBus {
|
|
91
29
|
now;
|
|
92
30
|
emitter = new EventEmitter();
|
|
93
31
|
tasks = new Map();
|
|
94
32
|
workflows = new Map();
|
|
95
|
-
/** #261 / codex R1-F2:**代际墓碑** —— 已离场行的最后已知 `cycleSeq`(行本身已从 `tasks` 删掉,
|
|
96
|
-
* 代际状态却必须活得比行久一点,否则前代迟到帧会在空 Map 上把幽灵行重铸出来)。有界 LRU。 */
|
|
97
33
|
retiredGen = new Map();
|
|
98
34
|
static RETIRED_GEN_MAX = 4096;
|
|
99
35
|
constructor(now = () => Date.now()) {
|
|
100
36
|
this.now = now;
|
|
101
|
-
this.emitter.setMaxListeners(0);
|
|
37
|
+
this.emitter.setMaxListeners(0);
|
|
102
38
|
}
|
|
103
|
-
/** Upsert a task row (MERGE onto any existing row by `id`) + fan out the merged row. Publishers may send a
|
|
104
|
-
* partial `{ id, tokens }` etc.; the row keeps its other fields. */
|
|
105
39
|
publishTask(delta) {
|
|
106
40
|
const existing = this.tasks.get(delta.id);
|
|
107
|
-
// 🔴 #261 §2 判代闸(**merge 前判**):同 id 上一个**更小**代际的帧 = 前代迟到帧 ⇒ 整帧忽略,
|
|
108
|
-
// 既不 merge 也不扇出。为什么必须在 merge 之前:MERGE 语义下一个迟到的前代 delta 会把行内累计量
|
|
109
|
-
// (tokens / elapsedMs / toolUses)**回退**到上一代的读数,而回退过的行再也没人会修正它 ——
|
|
110
|
-
// 消费端看到的是一条"复活之后 token 越跑越少"的行,且无从判别。判据只在**两边都有**代际号时生效:
|
|
111
|
-
// 缺席 = 「无此概念」(顶层 run 行恒缺席),不参与比较,行为逐字回落到本闸之前。
|
|
112
41
|
if (delta.cycleSeq !== undefined && existing?.cycleSeq !== undefined && delta.cycleSeq < existing.cycleSeq)
|
|
113
42
|
return;
|
|
114
|
-
// 🔴 同一条判据在**行已离场**之后的半场(codex 对抗复审 R1-F2 [high],红先复现)。上面那条闸只跟
|
|
115
|
-
// **内存行**比对,而 `removeTask` 顺手把行连同它的代际状态一起删了 —— 于是 `retire(N)` 之后一条
|
|
116
|
-
// `frame(N)` 会在空 Map 上把行**重新铸出来**,而那条行的发布方恰恰是已经死了(那就是我们退它的理由),
|
|
117
|
-
// 面板上于是又立起一条永不离场的幽灵行。墓碑补上这一半:行缺席时与**被退那一代**比,不前进的帧一律
|
|
118
|
-
// 不铸;更大代际(真复活)照常出生。
|
|
119
|
-
// ⚠️ 例外恰好一个:带 `retiredBy` 的帧**就是**对账腿对"被退那一代"的结算(行已不在 bus,故它必须
|
|
120
|
-
// 先瞬时重铸再撤 —— 见 fleet-reconciler.ts 的候补窗 flip-through)。它与墓碑说的是**同一代**,拦掉
|
|
121
|
-
// 它等于让"陈述级退场 → 后来 durable 翻终局 → 补发成对终帧"这条设计承诺静默失效(红先复现)。
|
|
122
43
|
if (existing === undefined && delta.retiredBy === undefined) {
|
|
123
44
|
const tomb = this.retiredGen.get(delta.id);
|
|
124
45
|
if (tomb !== undefined && delta.cycleSeq !== undefined && delta.cycleSeq <= tomb)
|
|
125
46
|
return;
|
|
126
47
|
}
|
|
127
|
-
// [2070]③:懒铸底座**不带 name** —— 一条只有 `{id,…}` 的首帧此前会把行 id 铸成显示名,而这里
|
|
128
|
-
// 恰恰是「谁都没给过名字」的位置。真名后到时照常 MERGE 上行(见本文件末的 [2070]③ 第 4 条钉)。
|
|
129
48
|
const merged = { ...(existing ?? { id: delta.id, status: "running" }), ...delta };
|
|
130
49
|
this.tasks.set(delta.id, merged);
|
|
131
50
|
this.emit({ type: "task", row: merged, ts: this.now() });
|
|
132
51
|
}
|
|
133
|
-
/** 推一帧 hook 判定未完成的观测通知(事件语义,不进 snapshot —— 与 `bg_notification` 同族)。 */
|
|
134
52
|
publishHookNotice(notice) {
|
|
135
53
|
this.emit({ type: "hook_notice", notice, ts: this.now() });
|
|
136
54
|
}
|
|
137
|
-
/** Drop a task row (terminal + swept) + fan out the removal. Idempotent (a no-op if already gone).
|
|
138
|
-
*
|
|
139
|
-
* `opts`(#261,两键均 ADDITIVE):`removeReason:"orphaned"` = **陈述级退场**(没有结算语义,见
|
|
140
|
-
* {@link FleetFrame} 的 `task_remove` 臂);`cycleSeq` = 被退那一代的代际号。发布方的常规清场
|
|
141
|
-
* (`onTerminal`/`onChildTerminal`)不传 opts ⇒ 帧逐字节与本改动之前相同。 */
|
|
142
55
|
removeTask(id, opts) {
|
|
143
56
|
const existing = this.tasks.get(id);
|
|
144
|
-
// 🔴 **代际条件删**(codex 对抗复审 R1-F2 反向臂 [high],红先复现):调用方拿的是一份快照,而它
|
|
145
|
-
// 在 await 里等 durable 读的那几十毫秒里,同一 id 完全可能已经**复活成新一代**(BCE revive spawn /
|
|
146
|
-
// run-leg 新腿)。无条件删就是拿过期快照把一条**活着的新行**从面板上摘掉 —— 而顶层 run 行没有代际号,
|
|
147
|
-
// 那一格由调用方的 await-后复核兜(见 fleet-reconciler.ts);有代际号的子代行由这条闸兜。
|
|
148
|
-
// 请求方没带代际(发布方的常规清场:它就是行的属主,不存在"拿旧快照删新行")⇒ 照旧无条件删。
|
|
149
57
|
if (existing !== undefined && opts?.cycleSeq !== undefined && existing.cycleSeq !== undefined && existing.cycleSeq > opts.cycleSeq)
|
|
150
58
|
return;
|
|
151
59
|
if (this.tasks.delete(id)) {
|
|
152
|
-
// 代际墓碑:记下**被退那一代**(优先取行上的真值,退化取请求值)。见 publishTask 的墓碑臂。
|
|
153
60
|
const gen = existing?.cycleSeq ?? opts?.cycleSeq;
|
|
154
61
|
if (gen !== undefined) {
|
|
155
62
|
this.retiredGen.delete(id);
|
|
156
63
|
this.retiredGen.set(id, gen);
|
|
157
|
-
// 容量兜底:逐最老。丢一条墓碑 = 那个 id 退回本闸之前的行为(迟到帧可重铸,下一个对账周期再退它一次)
|
|
158
|
-
// —— 可见性回退,不是错行,与 bceClaims 同一条取舍。
|
|
159
64
|
if (this.retiredGen.size > FleetEventBus.RETIRED_GEN_MAX) {
|
|
160
65
|
const oldest = this.retiredGen.keys().next();
|
|
161
66
|
if (!oldest.done)
|
|
@@ -171,81 +76,54 @@ export class FleetEventBus {
|
|
|
171
76
|
});
|
|
172
77
|
}
|
|
173
78
|
}
|
|
174
|
-
|
|
175
|
-
// 为什么登记簿而不是"查快照有没有 a* 行":两条车道用**不同 id 域**给同一个 agent 建行(BCE=`a*`/`wa*`
|
|
176
|
-
// 稳定 handle;run-leg=`${runId} ${childUuid}` 复合键),快照里没有可直接比对的键。join 键 = core 在
|
|
177
|
-
// BCE 帧上给的 `progressTaskId`(= run-leg tick 的 `taskId`,即子代 uuid),[2080] 明确它已在 tick/terminal
|
|
178
|
-
// 帧上。BCE 车道学到 uuid 即 claim,terminal 即 release;run-leg 车道 claim 在场就让位不铸。
|
|
179
|
-
// 单向 + 只增可见性:claim 丢了(容量逐出/旧 core 不发 progressTaskId)= 退回双生行的旧行为,不会错杀。
|
|
180
|
-
bceClaims = new Map(); // `${scope}\u0000${childUuid}` → a* 行 id
|
|
79
|
+
bceClaims = new Map();
|
|
181
80
|
static BCE_CLAIM_MAX = 4096;
|
|
182
81
|
static bceKey(scope, childUuid) { return `${scope ?? "default"}\u0000${childUuid}`; }
|
|
183
|
-
/** BCE 车道:本车道已为该子代 uuid 建了真行 —— run-leg 车道自此让位。幂等。 */
|
|
184
82
|
claimBackgroundChildLane(scope, childUuid, rowId) {
|
|
185
83
|
const k = FleetEventBus.bceKey(scope, childUuid);
|
|
186
84
|
this.bceClaims.delete(k);
|
|
187
85
|
this.bceClaims.set(k, rowId);
|
|
188
|
-
// 容量兜底:逐最老。丢一条 claim 只是让那个 agent 退回"双生行"旧行为(可见性回退,不是错行),
|
|
189
|
-
// 所以这里可以简单逐出——与 Meta 表"活溯源绝不逐出"的取舍不同,因为丢 claim 无归因损失。
|
|
190
86
|
if (this.bceClaims.size > FleetEventBus.BCE_CLAIM_MAX) {
|
|
191
87
|
const oldest = this.bceClaims.keys().next();
|
|
192
88
|
if (!oldest.done)
|
|
193
89
|
this.bceClaims.delete(oldest.value);
|
|
194
90
|
}
|
|
195
91
|
}
|
|
196
|
-
/** BCE 车道:该子代已终态离场 —— 让位解除(后续同 uuid 的迟到 tick 回到旧行为)。幂等。 */
|
|
197
92
|
releaseBackgroundChildLane(scope, childUuid) {
|
|
198
93
|
this.bceClaims.delete(FleetEventBus.bceKey(scope, childUuid));
|
|
199
94
|
}
|
|
200
|
-
/** run-leg 车道:该子代是否已有 BCE 真行(有 ⇒ 让位,不铸复合 id 行)。 */
|
|
201
95
|
backgroundChildLaneRow(scope, childUuid) {
|
|
202
96
|
return this.bceClaims.get(FleetEventBus.bceKey(scope, childUuid));
|
|
203
97
|
}
|
|
204
|
-
/** [2268] 让位迁移料的读面:迁移「只补缺席位」需要看得见存活行现有什么(BCE 是权威车道,不覆盖)。 */
|
|
205
98
|
taskRow(id) {
|
|
206
99
|
return this.tasks.get(id);
|
|
207
100
|
}
|
|
208
|
-
// └────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
209
|
-
/** Upsert a workflow row (MERGE by `id`) + fan out. */
|
|
210
101
|
publishWorkflow(delta) {
|
|
211
102
|
const merged = { ...(this.workflows.get(delta.id) ?? { id: delta.id, status: "running" }), ...delta };
|
|
212
103
|
this.workflows.set(delta.id, merged);
|
|
213
104
|
this.emit({ type: "workflow", row: merged, ts: this.now() });
|
|
214
105
|
}
|
|
215
|
-
/** Drop a workflow row + fan out the removal. Idempotent. */
|
|
216
106
|
removeWorkflow(id) {
|
|
217
107
|
if (this.workflows.delete(id))
|
|
218
108
|
this.emit({ type: "workflow_remove", id, ts: this.now() });
|
|
219
109
|
}
|
|
220
|
-
/** design/129-B 缺口②: fan out a background-child completion notification (EVENT — not stored, never in the
|
|
221
|
-
* snapshot; a subscriber that connects later reads the durable inbox instead). */
|
|
222
110
|
publishBgNotification(notification) {
|
|
223
111
|
this.emit({ type: "bg_notification", notification, ts: this.now() });
|
|
224
112
|
}
|
|
225
|
-
/** The current full state (the connect-time snapshot frame). */
|
|
226
113
|
snapshot() {
|
|
227
114
|
return { type: "snapshot", tasks: [...this.tasks.values()], workflows: [...this.workflows.values()], ts: this.now() };
|
|
228
115
|
}
|
|
229
|
-
/** Subscribe to deltas. Returns an unsubscribe thunk. The caller typically sends `snapshot()` first, then every
|
|
230
|
-
* delta this delivers — the snapshot+delta sequence is the live fleet view.
|
|
231
|
-
*
|
|
232
|
-
* 每个回调各自隔离(#183 复审 R3 HIGH):扇出是同步的,修前一个抛错的订阅方会把异常传回**发布方**——
|
|
233
|
-
* 而发布方是 JournalingWorkflowRunStore.put/update 的投影点,core 持久化对 put 抛错 catch{} 且不推进
|
|
234
|
-
* storeRev ⇒ durable 行冻在 running 而 notify 已 ack,无恢复路径。投影消费方永远不该能楔死 durable
|
|
235
|
-
* 写路;抛错记 {@link recordFailOpen}(F 类,登记闸),不上抛、不影响其余订阅方。 */
|
|
236
116
|
subscribe(cb) {
|
|
237
117
|
const isolated = (frame) => {
|
|
238
118
|
try {
|
|
239
119
|
cb(frame);
|
|
240
120
|
}
|
|
241
121
|
catch (err) {
|
|
242
|
-
// detail 构造自身也包住:抛出值若带会抛错的 toString,String() 本身会抛并逃出隔离层(fresh 复审观察项)。
|
|
243
122
|
let detail = "subscriber threw a value whose String() also threw";
|
|
244
123
|
try {
|
|
245
124
|
detail = String(err);
|
|
246
125
|
}
|
|
247
126
|
catch {
|
|
248
|
-
/* keep the fallback detail */
|
|
249
127
|
}
|
|
250
128
|
recordFailOpen("server.fleet.subscriber-callback-threw", detail);
|
|
251
129
|
}
|
|
@@ -253,7 +131,6 @@ export class FleetEventBus {
|
|
|
253
131
|
this.emitter.on("frame", isolated);
|
|
254
132
|
return () => this.emitter.off("frame", isolated);
|
|
255
133
|
}
|
|
256
|
-
/** Live counts (test/observability). */
|
|
257
134
|
get size() {
|
|
258
135
|
return { tasks: this.tasks.size, workflows: this.workflows.size };
|
|
259
136
|
}
|
|
@@ -261,75 +138,33 @@ export class FleetEventBus {
|
|
|
261
138
|
this.emitter.emit("frame", frame);
|
|
262
139
|
}
|
|
263
140
|
}
|
|
264
|
-
/** A run-scoped lifecycle publisher — wire it into a run's TaskEvent loop so the fleet row TRANSITIONS with the
|
|
265
|
-
* run. `onStart()` adds the top-level row (running); `onEvent(ev)` accrues tokens (turn_end usage) + adds/updates
|
|
266
|
-
* subagent CHILD rows (core `task_progress`, keyed by its `taskId`, nested under the run via `parentId`);
|
|
267
|
-
* `onTerminal(status)` flips the row terminal + removes it (and its children) from the active set. No-op when no
|
|
268
|
-
* bus is wired. The token accrual is CUMULATIVE across the run's turns (turn_end carries a per-turn delta). */
|
|
269
141
|
export function fleetRunPublisher(bus, run) {
|
|
270
142
|
if (!bus)
|
|
271
143
|
return { onStart: () => undefined, onEvent: () => undefined, onForwardEvent: () => undefined, onChildTerminal: () => false, onTerminal: () => undefined };
|
|
272
144
|
let tokens = 0;
|
|
273
145
|
const startedAt = Date.now();
|
|
274
|
-
// #3 fleet per-session scope (clay 2026-06-30): tag every row of THIS run with its rootTaskId (= sessionId) so
|
|
275
|
-
// /v1/fleet/stream can filter to the caller's CURRENT session (a TUI footer no longer shows other sessions of the
|
|
276
|
-
// same principal). publishTask MERGEs, so tagging the CREATE frame suffices — status/token updates keep the field.
|
|
277
146
|
const sessionField = run.rootTaskId ? { sessionId: run.rootTaskId } : {};
|
|
278
|
-
const children = new Set();
|
|
279
|
-
const childStartedAt = new Map();
|
|
280
|
-
// codex R2:spawning tool-call id → cid。core 的失败/killed 收尾通知不带 sessionId(成功路径才有条件带),
|
|
281
|
-
// 此时 task_id(a*)解不到 uuid 键的 tick 行——但 tick 的 ident() 带 parentToolCallId,通知带同源 toolUseId,
|
|
282
|
-
// 这是两 id 域之外唯一贯穿 spawn→terminal 的关联轴。一次 Task 调用只 spawn 一个 child,键不撞。
|
|
147
|
+
const children = new Set();
|
|
148
|
+
const childStartedAt = new Map();
|
|
283
149
|
const childByToolCall = new Map();
|
|
284
|
-
// Once the leg SETTLED, a LATE subagent tick must not resurrect a child
|
|
285
|
-
// row — the settle's removeTask IS the shell's release signal (auto-exit watches the evict); a post-settle
|
|
286
|
-
// re-publish left a zombie "running" row with no terminal/remove ever coming. Freeze all publishes after
|
|
287
|
-
// onTerminal (a resume runs on a FRESH publisher — this leg's handle is done).
|
|
288
150
|
let settledLeg = false;
|
|
289
151
|
const childId = (taskId) => `${run.runId} ${taskId}`;
|
|
290
|
-
/** [1415]③:task_progress.status 值域映射(1.354 settle 终态 tick)——completed/failed 直译,其余(含
|
|
291
|
-
* undefined/"running"/suspended 不发的诚实面)恒 running(旧 core 行为逐位不变)。 */
|
|
292
152
|
const tickStatus = (st) => (st === "completed" ? "completed" : st === "failed" ? "failed" : "running");
|
|
293
|
-
/** [1748] 从一拍 usage 里取**累计**工具调用次数。与 `sumTokens` 同样防御式取值(该位在这两处的
|
|
294
|
-
* 静态类型是 `{}`)。⚠️ 缺席就返回 undefined —— 调用点据此**不发这个键**(缺席 ≠ 0)。 */
|
|
295
153
|
const toolUsesOf = (usage) => {
|
|
296
154
|
const t = usage?.toolUses;
|
|
297
155
|
return typeof t === "number" ? t : undefined;
|
|
298
156
|
};
|
|
299
|
-
/**
|
|
300
|
-
* 🔴 [2070]①/[2080] 让位判据(run-leg 侧半场,server 3.6.0)。
|
|
301
|
-
*
|
|
302
|
-
* 病灶(cli [2070]① 实测):本车道给**每一条**子代 tick 铸复合 id 行 `${runId} ${childTaskId}`,而
|
|
303
|
-
* BCE 车道(`fleetBackgroundChildPublisher`)对同一个 agent 已有 `a*`/`wa*` 稳定 handle 真行 ⇒ 同屏两行
|
|
304
|
-
* (424 次采样 180 次 max=2,连续可见窗约 15s)。BCE 侧此前只有**反应式**清扫(首 tick 一次性后缀扫),
|
|
305
|
-
* 清完本车道下一拍又铸回来,所以 6 双生帧 : 7 真帧。
|
|
306
|
-
*
|
|
307
|
-
* [2080] 裁定=**双生臂让位、不铸新 id 域**(join 键 progressTaskId 已在 BCE 帧上)。本函数即让位闸:
|
|
308
|
-
* 同 (scope, childTaskId) 已有 BCE claim ⇒ 返回 true,调用点**不发行帧**;并把本车道**先前**可能已铸的
|
|
309
|
-
* 复合行清场(claim 可能晚于我们第一拍到达),此后该 agent 单源。
|
|
310
|
-
*
|
|
311
|
-
* 为什么不是"直接删掉复合行铸造":前台 delegation(同步腿委派的子代)**不过** background-child
|
|
312
|
-
* registry ⇒ 永远没有 BCE 真行,让位就等于让这些 agent 从面板消失。那一形保留复合行,并在帧上带
|
|
313
|
-
* `sourceLane:"run-leg"` 明示这是过渡形——消费端不必再自己发明集合级去重判据。
|
|
314
|
-
*/
|
|
315
157
|
const yieldToBackgroundChildLane = (childTaskId, cid, tickName) => {
|
|
316
158
|
const bceRowId = bus.backgroundChildLaneRow(run.scope, childTaskId);
|
|
317
159
|
if (bceRowId === undefined)
|
|
318
160
|
return false;
|
|
319
161
|
if (children.has(cid)) {
|
|
320
|
-
// 我们早于 claim 铸过一行 —— 清场(BCE 侧的后缀扫是同一意图的反应式兜底,两者幂等叠加无害)
|
|
321
162
|
bus.removeTask(cid);
|
|
322
163
|
children.delete(cid);
|
|
323
|
-
// ⚠️ childStartedAt 故意不删 —— 它是下面 elapsed 供给的计时基准([2268])
|
|
324
164
|
for (const [k, v] of childByToolCall)
|
|
325
165
|
if (v === cid)
|
|
326
166
|
childByToolCall.delete(k);
|
|
327
167
|
}
|
|
328
|
-
// 🔴 [2268] 迁移料([2269] 认领「迁移」臂):此前让位=删「有名的 run-leg 行」留「无名的 BCE 行」
|
|
329
|
-
// ((unnamed)/0s/详情空白三症状同源)。tick 手上的显示料补给存活行,**只补缺席位** —— BCE 是权威
|
|
330
|
-
// 车道,它已有的 name/真 elapsed 一律不碰。elapsed 供给是持续的(本闸每拍都过),不是一次性冻结;
|
|
331
|
-
// elapsedDonor 记住「这行的 elapsed 是我们供的」,避免把 BCE 自己的真 elapsed 误当缺席重置。
|
|
332
|
-
// transcriptId 不在 run-leg tick 手上,迁不了(诚实范围;那半归 BCE 行自身后续 tick)。
|
|
333
168
|
const row = bus.taskRow(bceRowId);
|
|
334
169
|
if (row) {
|
|
335
170
|
const donorName = row.name === undefined && tickName !== undefined ? redactSecrets(tickName) : undefined;
|
|
@@ -339,9 +174,6 @@ export function fleetRunPublisher(bus, run) {
|
|
|
339
174
|
elapsedDonor.add(cid);
|
|
340
175
|
const elapsed = elapsedDonor.has(cid) ? Date.now() - childStartedAt.get(cid) : undefined;
|
|
341
176
|
if (donorName !== undefined || elapsed !== undefined) {
|
|
342
|
-
// cli [2297]①:donorName 只进 `name` 位。run-leg tick 手上没有独立的类型名([1364]③/core 1.350:
|
|
343
|
-
// name=display 标签、agentType=类型名),把 display 值同时写进 agentType 会让 fleet 的 TYPE 列显示
|
|
344
|
-
// description。缺源的键不铸(与「匿名行不铸假名」同一条原则);BCE 行自己的帧带真 agentType 时自然补上。
|
|
345
177
|
bus.publishTask({
|
|
346
178
|
id: bceRowId,
|
|
347
179
|
...(donorName !== undefined ? { name: donorName } : {}),
|
|
@@ -351,24 +183,13 @@ export function fleetRunPublisher(bus, run) {
|
|
|
351
183
|
}
|
|
352
184
|
return true;
|
|
353
185
|
};
|
|
354
|
-
const elapsedDonor = new Set();
|
|
186
|
+
const elapsedDonor = new Set();
|
|
355
187
|
const sumTokens = (usage) => {
|
|
356
188
|
const u = usage;
|
|
357
189
|
if (!u)
|
|
358
190
|
return 0;
|
|
359
191
|
if (typeof u.totalTokens === "number")
|
|
360
192
|
return u.totalTokens;
|
|
361
|
-
// core 3.0.0([2296] 装车单 S2, [2298] R#9 二次复审并入):`turn_end.usage.inputTokens` 的语义在 3.0.0
|
|
362
|
-
// **翻转**——此前(core ≤2.x)它是 NORMALIZED TOTAL INPUT *INCLUDING CACHE*(usage-accounting
|
|
363
|
-
// promptTokensOf:Anthropic 族 = input+cacheRead+cacheWrite,OpenAI 族已把 cache 折进 input),本函数当时
|
|
364
|
-
// 直接 `inputTokens + outputTokens` 就是整轮总量;3.0.0 起 `inputTokens` 恒为 cache-MISS 分量,新必填的
|
|
365
|
-
// `totalInputTokens` 才是那个「含 cache 的归一化总量」(旧 inputTokens 语义的正统继承者)。继续只加
|
|
366
|
-
// `inputTokens` 会在有 cache 命中时**漏计**(leader 行的展示 token 数偏低)——故总量优先取
|
|
367
|
-
// `totalInputTokens`,旧核帧(尚未升级到 3.0.0、该键缺席)回落 `inputTokens`(防御性:本引擎恒 ≥3.0.0
|
|
368
|
-
// 时两键并存;异常帧宁少算不 NaN)。Display-only(leader fleet 行的 token 数);billing(costMicroUsd)+
|
|
369
|
-
// context/compaction 走 totalTokens,不受影响;child 行走 task_progress.usage.totalTokens(上面的早退臂)。
|
|
370
|
-
// [2354] 兼容面全清:旧核帧回落臂(?? u.inputTokens)已删——floor ^4.0.0 下 totalInputTokens 恒在;
|
|
371
|
-
// 键缺席=越契约帧,按 0 计(宁少算不借异语义键充数)。
|
|
372
193
|
return (u.totalInputTokens ?? 0) + (u.outputTokens ?? 0);
|
|
373
194
|
};
|
|
374
195
|
return {
|
|
@@ -377,111 +198,48 @@ export function fleetRunPublisher(bus, run) {
|
|
|
377
198
|
},
|
|
378
199
|
onEvent(ev) {
|
|
379
200
|
if (settledLeg)
|
|
380
|
-
return;
|
|
201
|
+
return;
|
|
381
202
|
if (ev.type === "turn_end" && !ev.parentToolCallId) {
|
|
382
|
-
|
|
383
|
-
// turns onto the run row. A turn_end WITH a parentToolCallId is an orchestration/subagent SUB-turn (a
|
|
384
|
-
// run_workflow agent or a spawned subagent) — its usage belongs to that child's own row / the workflow's
|
|
385
|
-
// budget, NOT the leader. Before core threaded the identity onto turn_end (1.121 left it the only boundary
|
|
386
|
-
// event without it), the leader row over-counted sub-turn usage on self-orchestration runs.
|
|
387
|
-
tokens += sumTokens(ev.usage); // turn_end carries a per-turn usage delta → accumulate
|
|
203
|
+
tokens += sumTokens(ev.usage);
|
|
388
204
|
bus.publishTask({ id: run.runId, tokens, elapsedMs: Date.now() - startedAt });
|
|
389
205
|
}
|
|
390
206
|
else if (ev.type === "task_progress" && ev.taskId) {
|
|
391
|
-
// core 1.147 subagent usage tick (per-turn, cumulative) → a CHILD fleet row nested under the run.
|
|
392
207
|
const cid = childId(ev.taskId);
|
|
393
208
|
if (yieldToBackgroundChildLane(ev.taskId, cid, ev.name))
|
|
394
|
-
return;
|
|
209
|
+
return;
|
|
395
210
|
children.add(cid);
|
|
396
|
-
// BC-2 (core 1.151): use the subagent's sanitized display `name` (taskName/agent-type) for the child row.
|
|
397
|
-
// [2070]③:name 缺席时**不铸**(此前回落 taskId=子代 uuid,人眼乱码)——行 IDENTITY 一直是 `cid`
|
|
398
|
-
// (runId+taskId),显示名与身份是两件事,没有显示名就让客户端渲占位。
|
|
399
|
-
// [WF2-A] redact the subagent display name for PARITY with the top-level run name (fleetRunLabels): core
|
|
400
|
-
// sanitizes it for RENDERING (ANSI/NUL strip + truncate) but does NOT secret-redact, so a secret-shaped
|
|
401
|
-
// taskName would leak verbatim on the child row without this.
|
|
402
|
-
// §K-7: `ev.name` IS the subagent TYPE (BC-2: taskName / selected agent-type) → ALSO surface it as
|
|
403
|
-
// `agentType` (the picker's short-label field), not only the display `name`. Absent name (anonymous
|
|
404
|
-
// subagent) → 两位一起缺席(name/agentType 同源同命)。One redacted value (name === agentType).
|
|
405
211
|
const childName = ev.name !== undefined ? redactSecrets(ev.name) : undefined;
|
|
406
212
|
if (!childStartedAt.has(cid))
|
|
407
213
|
childStartedAt.set(cid, Date.now());
|
|
408
214
|
if (ev.parentToolCallId)
|
|
409
215
|
childByToolCall.set(ev.parentToolCallId, cid);
|
|
410
|
-
// [1415]③ 点亮半场:core 1.354 起 settle 时补终态 tick(status 值域 additive 扩 completed|failed;
|
|
411
|
-
// 此前恒 "running"=行为逐位不变)——workflow 子的 uuid 行终于有完成态可见(running→removed 的
|
|
412
|
-
// [1414]#3 形就此闭)。
|
|
413
|
-
// #261 §2:`seq` 透传成行的 `cycleSeq`(**缺席不铸** —— 旧 core 不发这一位,而「缺席禁读作 cycle 1」)。
|
|
414
216
|
bus.publishTask({ id: cid, sourceLane: "run-leg", ...(childName !== undefined ? { name: childName, agentType: childName } : {}), parentId: run.runId, scope: run.scope, ...sessionField, status: tickStatus(ev.status), tokens: sumTokens(ev.usage), ...(typeof ev.seq === "number" ? { cycleSeq: ev.seq } : {}), ...(() => { const t = toolUsesOf(ev.usage); return t !== undefined ? { toolUses: t } : {}; })(), elapsedMs: Date.now() - childStartedAt.get(cid) });
|
|
415
217
|
}
|
|
416
218
|
else if (ev.type === "tool_start" && ev.toolName) {
|
|
417
|
-
|
|
418
|
-
// the objective. `name` already carries an objective preview (fleetRunLabels); ALSO putting the objective in
|
|
419
|
-
// `description` showed it TWICE (the dup the shell flagged). So `description` tracks the current tool, CC-like.
|
|
420
|
-
// Tool NAME only — args are untrusted/redacted, a row label must never leak them. Subagent CHILD rows keep
|
|
421
|
-
// their task_progress display name (BC-2); this updates the TOP-LEVEL run row only.
|
|
422
|
-
// PascalCase double-belt (core 1.176): canonicalize so a legacy snake CONTROL verb (e.g. a
|
|
423
|
-
// pre-1.176 emit / a stray report_blocked) renders as its CC name (ReportBlocked) and the shell's PascalCase
|
|
424
|
-
// suppression Set still hides it — never re-leaking a control verb as fleet-row activity.
|
|
425
|
-
bus.publishTask({ id: run.runId, description: ev.toolName }); // RAW(5.0.0):live 名恒 canonical
|
|
219
|
+
bus.publishTask({ id: run.runId, description: ev.toolName });
|
|
426
220
|
}
|
|
427
221
|
},
|
|
428
222
|
onForwardEvent(ev) {
|
|
429
|
-
// core 1.154 (design/99 nested-subagent live tree): a SUBAGENT's `task_progress` tick forwarded from its
|
|
430
|
-
// ISOLATED stream to the top run's opt-in sink (`RunInternals.onForwardEvent`). This is the ONLY path a
|
|
431
|
-
// subagent tick reaches the top consumer — the top run's own stream does NOT carry it (the tick lives in the
|
|
432
|
-
// child's stream, consumed by the parent's runTask). So this builds the fleet's per-subagent CHILD rows at ANY
|
|
433
|
-
// depth: `parentTaskId` names the spawner — the TOP run (= `rootTaskId` → nest under run.runId) or another
|
|
434
|
-
// subagent (→ nest under that subagent's row). core sets a child's `parentTaskId` = its spawner's task_progress
|
|
435
|
-
// `taskId` (prepare-task.ts), so the tree keys line up. Forwarded ticks are UNTRUSTED display hints (name is
|
|
436
|
-
// core-sanitized; we carry NAME + token rollup only, never args).
|
|
437
|
-
// 🟡 parentTaskId is NOT validated against known runs (documented as LOW). It needn't be: the
|
|
438
|
-
// isolation guarantee is the SCOPE BOUND, not a parentTaskId check — every child row we publish is tagged
|
|
439
|
-
// `scope: run.scope` (this run's owner) and id-prefixed with the real `run.runId`. So a tool forging a
|
|
440
|
-
// `parentTaskId` can at worst MIS-NEST a row WITHIN this run's own scope view (cosmetic); it can never cross
|
|
441
|
-
// into another run / tenant. (core corrected its prepare-task note that had over-promised "deployment
|
|
442
|
-
// validates parentTaskId" — the bound is structural here.) Validation would be optional defense-in-depth only.
|
|
443
223
|
if (settledLeg)
|
|
444
|
-
return;
|
|
224
|
+
return;
|
|
445
225
|
if (ev.type !== "task_progress" || !ev.taskId)
|
|
446
226
|
return;
|
|
447
227
|
const cid = childId(ev.taskId);
|
|
448
228
|
if (yieldToBackgroundChildLane(ev.taskId, cid, ev.name))
|
|
449
|
-
return;
|
|
229
|
+
return;
|
|
450
230
|
children.add(cid);
|
|
451
231
|
const parentId = ev.parentTaskId && ev.parentTaskId !== run.rootTaskId ? childId(ev.parentTaskId) : run.runId;
|
|
452
|
-
// [WF2-A] redact the forwarded subagent name for parity with the top-level run name (see onEvent above).
|
|
453
|
-
// §K-7: ev.name IS the subagent type → also surface it as `agentType` (picker short label). Reuse the value.
|
|
454
|
-
// [2070]③ 同 onEvent 臂:无 name ⇒ name/agentType 两位一起缺席(不拿 taskId 冒充显示名)。
|
|
455
232
|
const fwdName = ev.name !== undefined ? redactSecrets(ev.name) : undefined;
|
|
456
233
|
if (!childStartedAt.has(cid))
|
|
457
234
|
childStartedAt.set(cid, Date.now());
|
|
458
235
|
if (ev.parentToolCallId)
|
|
459
236
|
childByToolCall.set(ev.parentToolCallId, cid);
|
|
460
|
-
bus.publishTask({ id: cid, sourceLane: "run-leg", ...(fwdName !== undefined ? { name: fwdName, agentType: fwdName } : {}), parentId, scope: run.scope, ...sessionField, status: tickStatus(ev.status), tokens: sumTokens(ev.usage), ...(typeof ev.seq === "number" ? { cycleSeq: ev.seq } : {}), ...(() => { const t = toolUsesOf(ev.usage); return t !== undefined ? { toolUses: t } : {}; })(), elapsedMs: Date.now() - childStartedAt.get(cid) });
|
|
237
|
+
bus.publishTask({ id: cid, sourceLane: "run-leg", ...(fwdName !== undefined ? { name: fwdName, agentType: fwdName } : {}), parentId, scope: run.scope, ...sessionField, status: tickStatus(ev.status), tokens: sumTokens(ev.usage), ...(typeof ev.seq === "number" ? { cycleSeq: ev.seq } : {}), ...(() => { const t = toolUsesOf(ev.usage); return t !== undefined ? { toolUses: t } : {}; })(), elapsedMs: Date.now() - childStartedAt.get(cid) });
|
|
461
238
|
},
|
|
462
239
|
onChildTerminal(taskId, status, altId, toolUseId) {
|
|
463
|
-
// A background subagent/bash CHILD settles — task_progress never emits a terminal tick,
|
|
464
|
-
// so without this the child row sat "running" until the PARENT leg settled (the shell measured 93s of
|
|
465
|
-
// phantom running; its auto-exit release condition never fired). Flip the status so subscribers SEE the
|
|
466
|
-
// terminal frame, then remove (mirroring onTerminal's leave-the-fleet posture). Idempotent on an unknown
|
|
467
|
-
// child. ⚠️ the notification's task_id is the a* domain while ticks key by the child uuid — a live
|
|
468
|
-
// mid-turn flip can therefore miss the tick-built row (flip-through mints the a* row instead); id-domain
|
|
469
|
-
// unification is core's half. Post-settle: rows are ALREADY removed (the settle evict is the
|
|
470
|
-
// shell's release signal) — a flip-through here would mint a fresh orphan row. No-op.
|
|
471
240
|
if (settledLeg)
|
|
472
241
|
return false;
|
|
473
|
-
// [1332]①:id 双域候选——tick 行键域(child sessionId uuid)与通知键域(a* task_id)因 core 版本/
|
|
474
|
-
// lane 而异,单键押注押错=真行永不落 completed(绿点不回,行滞留 running 到父 settle)。两候选谁
|
|
475
|
-
// 有活行翻谁;都没有才按首候选合成瞬态终帧(bash 无 tick 形,原语义)。
|
|
476
|
-
// codex R3:killed 保桶(与 a* 车道 fleetBackgroundChildPublisher 的终态映射一致)——操作员/系统
|
|
477
|
-
// 取消不得错报为执行失败;同一 child 两车道给订阅者的终态必须同色。
|
|
478
242
|
const fleetStatus = status === "completed" ? "completed" : status === "killed" || status === "cancelled" ? "killed" : "failed";
|
|
479
|
-
// codex F1:两 id 域可能**同时**各有一行(重复/版本偏斜投递,onEvent 与 onForwardEvent 各按一域建)
|
|
480
|
-
// ——单选一行会漏掉另一域的行,滞留 running 到父 settle(绿点不回复发)。翻**全部**属本 publisher 的
|
|
481
|
-
// 候选行,逐一终帧+离场。
|
|
482
|
-
// codex R2:第三候选 = 通知 toolUseId 经 spawn 关联解出的 cid。core 的失败/killed 收尾通知缺 sessionId
|
|
483
|
-
// (subagent.js catch 分支两处只带 task_id;成功路径才条件带)——此时前两候选同为 a* 域,tick 建的 uuid
|
|
484
|
-
// 行押不中;toolUseId↔tick.parentToolCallId 是同一次 Task 调用的同源 id,把真行找回来。
|
|
485
243
|
const candidateCids = [childId(taskId)];
|
|
486
244
|
if (altId !== undefined && altId !== taskId)
|
|
487
245
|
candidateCids.push(childId(altId));
|
|
@@ -490,18 +248,11 @@ export function fleetRunPublisher(bus, run) {
|
|
|
490
248
|
candidateCids.push(viaToolCall);
|
|
491
249
|
const liveCids = candidateCids.filter((c) => children.has(c));
|
|
492
250
|
const hadRow = liveCids.length > 0;
|
|
493
|
-
// 🔴 F1(2026-07-30 复审):无活行时先查 BCE claim——claim 在场 = 该 agent 的全部帧归 BCE 车道
|
|
494
|
-
// (run-leg 的 tick 臂已全程让位,children 恒空),终态臂同样让位:合成复合 id 瞬态终帧会让
|
|
495
|
-
// sourceLane 文档承诺废除的「同 agent 双 id 域帧」在终态那一拍回潮(且合成帧不带 sourceLane)。
|
|
496
|
-
// 返回 true=已被处理(BCE 车道自己发 a* 终帧);无 claim 的 bash 无 tick 形走下方原语义。
|
|
497
251
|
if (!hadRow) {
|
|
498
252
|
const claimed = [taskId, altId].some((t) => t !== undefined && bus.backgroundChildLaneRow(run.scope, t) !== undefined);
|
|
499
253
|
if (claimed)
|
|
500
254
|
return true;
|
|
501
255
|
}
|
|
502
|
-
// 都没活行 = bash 无 tick 形:按首候选合成瞬态终帧(建即离,诚实且无害)。
|
|
503
|
-
// [2070]③:合成帧**不铸 name** —— 这条「瞬态」帧会被壳的终态留存池渲 60s,顶上去的 a\* handle
|
|
504
|
-
// 在那 60s 里就是用户看到的名字。父链接/租户/会话三位照发(它们是归属事实,不是显示名)。
|
|
505
256
|
for (const cid of hadRow ? liveCids : [childId(taskId)]) {
|
|
506
257
|
const startedAtChild = childStartedAt.get(cid);
|
|
507
258
|
bus.publishTask({ id: cid, ...(children.has(cid) ? {} : { parentId: run.runId, scope: run.scope, ...sessionField }), status: fleetStatus, ...(startedAtChild !== undefined ? { elapsedMs: Date.now() - startedAtChild } : {}) });
|
|
@@ -515,10 +266,9 @@ export function fleetRunPublisher(bus, run) {
|
|
|
515
266
|
return hadRow;
|
|
516
267
|
},
|
|
517
268
|
onTerminal(status, residuals) {
|
|
518
|
-
settledLeg = true;
|
|
269
|
+
settledLeg = true;
|
|
519
270
|
const fleetStatus = runStatusToFleet(status);
|
|
520
|
-
|
|
521
|
-
const terminal = isTerminalFleetStatus(fleetStatus); // A-032 P1-④:removal 集不再手抄(穷举属主谓词)
|
|
271
|
+
const terminal = isTerminalFleetStatus(fleetStatus);
|
|
522
272
|
const res = terminal && residuals
|
|
523
273
|
? {
|
|
524
274
|
...(residuals.usage ? { usage: residuals.usage } : {}),
|
|
@@ -527,27 +277,17 @@ export function fleetRunPublisher(bus, run) {
|
|
|
527
277
|
}
|
|
528
278
|
: {};
|
|
529
279
|
bus.publishTask({ id: run.runId, status: fleetStatus, tokens, elapsedMs: Date.now() - startedAt, ...res });
|
|
530
|
-
// Subagent CHILD rows leave on ANY settle — terminal OR parked (TOC review #8: a suspended/needs_review run
|
|
531
|
-
// keeps its row, but its subagents are NOT running while the parent is parked — their work is checkpointed; a
|
|
532
|
-
// task_progress never emits a terminal, so without this the child rows leak forever as stale "running" under a
|
|
533
|
-
// parked parent). On resume, fresh task_progress/onForwardEvent re-adds the active children.
|
|
534
280
|
for (const cid of children)
|
|
535
281
|
bus.removeTask(cid);
|
|
536
282
|
children.clear();
|
|
537
|
-
childStartedAt.clear();
|
|
538
|
-
childByToolCall.clear();
|
|
539
|
-
// A truly-terminal run LEAVES the fleet (the shell saw the terminal frame first). A PARKED run
|
|
540
|
-
// (suspended→waiting / needs_review→awaiting approval) is still active → its ROW STAYS in the fleet.
|
|
283
|
+
childStartedAt.clear();
|
|
284
|
+
childByToolCall.clear();
|
|
541
285
|
if (terminal) {
|
|
542
286
|
bus.removeTask(run.runId);
|
|
543
287
|
}
|
|
544
288
|
},
|
|
545
289
|
};
|
|
546
290
|
}
|
|
547
|
-
/** [1839]①/(a) 案:TaskResult → 终态行帧残局键映射(sync/background/resume 三条腿各自 settle,共用一处
|
|
548
|
-
* 映射——键名映射写三遍必漂移)。usage 与 {@link BgNotification.usage} 同形:stats.tokens→totalTokens、
|
|
549
|
-
* stats.toolCalls→toolUses(名对齐,消费端一个形状)。errorCode "cancelled" ⇒ stoppedBy:"user"(与
|
|
550
|
-
* markStopSource 的归因值同源);transcriptId 顶层 = run 的 sessionId([1840]§二拍)。缺源的键不铸。 */
|
|
551
291
|
export function fleetRunResiduals(result) {
|
|
552
292
|
if (!result)
|
|
553
293
|
return {};
|
|
@@ -566,22 +306,6 @@ export function fleetRunResiduals(result) {
|
|
|
566
306
|
...(result.sessionId ? { transcriptId: result.sessionId } : {}),
|
|
567
307
|
};
|
|
568
308
|
}
|
|
569
|
-
/**
|
|
570
|
-
* design/129-B (core 1.240.0) — the PROCESS-LEVEL background-child fleet publisher, wired once
|
|
571
|
-
* onto `RunnerDeps.onBackgroundChildEvent`. Unlike {@link fleetRunPublisher} (per-leg: dies at leg settle —
|
|
572
|
-
* the root cause: a session-scoped bg child outliving its turn had NO forward path, fleet showed zero
|
|
573
|
-
* child frames), this observer lives as long as the process and covers the child's WHOLE lifecycle:
|
|
574
|
-
* spawn → row appears the moment the child registers (launch→turn-tail 零帧 fixed: no more waiting for a
|
|
575
|
-
* first tick that a slow child never emits inside the turn);
|
|
576
|
-
* tick → token rollup onto the row; ALSO best-effort removes the per-leg uuid-keyed TWIN row the leg's
|
|
577
|
-
* onForwardEvent built for the same child (`${parentTaskId} ${progressTaskId}` composite — the
|
|
578
|
-
* per-leg childId shape), so the child shows ONCE, keyed by its stable a* handle;
|
|
579
|
-
* terminal → terminal frame under the a* row id, row leaves the fleet, and the contract
|
|
580
|
-
* `bg_notification` frame rides the always-open stream (缺口②'s immediate idle delivery).
|
|
581
|
-
* Rows are tagged `scope` (tenant filter) from the registry scope and `sessionId` (the `?session=` filter)
|
|
582
|
-
* from the registry owner when session-scoped (owner IS the host sessionId; task-scoped children have no
|
|
583
|
-
* session attribution → unscoped, back-compat visible). Names ride the same redactSecrets discipline.
|
|
584
|
-
*/
|
|
585
309
|
export function fleetBackgroundChildPublisher(bus, log) {
|
|
586
310
|
if (!bus)
|
|
587
311
|
return () => undefined;
|
|
@@ -590,7 +314,6 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
590
314
|
log?.(msg, fields);
|
|
591
315
|
}
|
|
592
316
|
catch {
|
|
593
|
-
/* observability must never fault the observer */
|
|
594
317
|
}
|
|
595
318
|
};
|
|
596
319
|
const meta = new Map();
|
|
@@ -600,11 +323,6 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
600
323
|
meta.delete(id);
|
|
601
324
|
meta.set(id, m);
|
|
602
325
|
if (meta.size > MAX) {
|
|
603
|
-
// codex 两轮([1338]④ 复审):逐出**只打墓碑**(done)。R1:逐最老会把在跑 child 的 spawnedAt 挤掉,
|
|
604
|
-
// 下个 tick lazy-mint 重起表=elapsedMs 归零。R2(更重):全活时垫底逐活儿,重生条目 tenantScope 落
|
|
605
|
-
// "default"、owner/parent 全失——**跨租户错归因**(scoped 流按 "default" 键还能收到别家 child 的终态
|
|
606
|
-
// 通知残料)。活溯源绝不逐出:无墓碑=不逐,活基数由 registry 真并发界定(每条 Meta 极小);病态膨胀
|
|
607
|
-
// 打一次 warn 供观测。墓碑仍受 MAX 界(terminal 后条目=墓碑,自然轮换)。
|
|
608
326
|
let victim;
|
|
609
327
|
for (const [k, v] of meta) {
|
|
610
328
|
if (v.done) {
|
|
@@ -621,46 +339,18 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
621
339
|
}
|
|
622
340
|
}
|
|
623
341
|
};
|
|
624
|
-
/**
|
|
625
|
-
* [2070]①/[2080] 让位登记(BCE 侧半场)。把「本车道已为这个子代 uuid 建了真行」登记到 bus 上,
|
|
626
|
-
* run-leg 车道的 task_progress 臂据此让位(不铸 `${runId} ${uuid}` 复合行)。
|
|
627
|
-
*
|
|
628
|
-
* uuid 从哪来(两源,先到先得):①`e.sessionId` —— fork 形 spawn 就带(= forkedId);
|
|
629
|
-
* ②`e.progressTaskId` —— core 在 tick/terminal 帧上给的 join 键([2080] 逐字确认已在场),普通 bg
|
|
630
|
-
* producer 要等首 tick 才现。两者都是**同一个** run-leg tick 的 `taskId`,这就是 join 的依据。
|
|
631
|
-
*
|
|
632
|
-
* `progressParentTaskId` 判别照 suppressedTwin 同款:只有**本 child 自己**的 tick 才算数——嵌套孙代的
|
|
633
|
-
* 帧带的是父的 uuid,拿它 claim 会把父的 run-leg 行误让位掉(父在前台 delegation 形下没有 BCE 真行)。
|
|
634
|
-
* 旧 core 不发这两键 ⇒ 无 claim ⇒ 退回双生行旧行为(可见性回退,不错杀)。
|
|
635
|
-
*/
|
|
636
342
|
const claimIfKnown = (e, m) => {
|
|
637
343
|
const own = !e.progressParentTaskId || e.progressParentTaskId === (m.parentTaskId ?? m.parentSessionLink);
|
|
638
344
|
const uuid = e.sessionId ?? (own ? e.progressTaskId : undefined);
|
|
639
345
|
if (uuid)
|
|
640
346
|
bus.claimBackgroundChildLane(m.tenantScope, uuid, e.taskId);
|
|
641
347
|
};
|
|
642
|
-
// [1338]③ 换算臂:core spawn emit 现把 hostSessionId 装进 parentTaskId(会话 id,非行 id)→ 子行
|
|
643
|
-
// parentId 指向不存在的行=孤儿伪影(值域修在 core)。server 兜:parentTaskId 不是现存行 id、却恰是
|
|
644
|
-
// 某 TOP-LEVEL run 行的 sessionId 时,换算成该 run 行 id。guard 式——core 修后 parentTaskId 直接命中
|
|
645
|
-
// 行 id,此臂自然失活;无命中(run 行未起/已离场)保持原值,孤儿顶格渲染,不误挂。
|
|
646
|
-
// codex R3(M1):首次**成功**解析即冻结进 Meta.resolvedParentId——逐拍重解会把跨 run 存活的长寿
|
|
647
|
-
// child 重挂到同会话后起的 run 行(launch 溯源被静默改写);孤儿(未成功)下拍再试。
|
|
648
348
|
const resolveParentId = (parentInput, domain, childScope) => {
|
|
649
|
-
// codex R5:**每一条**解析分支都同租户校验(r.scope === childScope)——submit 收任意 sessionId,
|
|
650
|
-
// 碰撞可让 A 家的孙冻结到 B 家的行(fleet-wide 视图伪嵌套)。跨租户一律按未命中:孤儿顶格,fail-closed。
|
|
651
|
-
// codex(1.350 四轮):解析带**域标**——session 域链接(parentSessionLink)跳过行 id 直查与 uuid 别名
|
|
652
|
-
// 两分支(会话 id 由调用方任报,与既有 run 行 id 撞值时直查会把孩子冻到无关 run 上),只走 sessionId
|
|
653
|
-
// 匹配译臂;task 域三分支全走。
|
|
654
349
|
const rows = bus.snapshot().tasks;
|
|
655
350
|
if (domain === "task") {
|
|
656
351
|
if (rows.some((r) => r.id === parentInput && r.scope === childScope))
|
|
657
352
|
return { id: parentInput, resolved: true };
|
|
658
353
|
}
|
|
659
|
-
// codex R4(M-B)+ PAIR-REVIEW F-1:嵌套 bg 孙的父链接=父 **agent 的 uuid**——core 1.350+ 真实帧形里
|
|
660
|
-
// 该值落 **parentSessionId**(session 域;[1344]② 钳掉了洗域 parentTaskId),所以别名逆查必须对
|
|
661
|
-
// **两个域**开放(此前只挂 task 域=1.351 下三臂死代码,PAIR-REVIEW F-1 CONFIRMED)。scope-safe:
|
|
662
|
-
// 别名命中恒要求同租户(R5 的禁令针对**行 id 直查**分支——那才是任报值撞行的面;别名表是本
|
|
663
|
-
// publisher 自记的 spawn 事实,加租户校验后无跨户面)。
|
|
664
354
|
for (const [aStar, pm] of meta) {
|
|
665
355
|
if (pm.aliasUuid === parentInput && pm.tenantScope === childScope) {
|
|
666
356
|
if (rows.some((r) => r.id === aStar && r.scope === childScope))
|
|
@@ -669,20 +359,14 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
669
359
|
}
|
|
670
360
|
let match;
|
|
671
361
|
for (const r of rows) {
|
|
672
|
-
// codex R7(high):会话译只认**真 run 行**——排除一切本 publisher 记账的 a* child 行(meta 键)。
|
|
673
|
-
// session-scoped a* 孤儿行同样带 sessionId、R6 后无 parentId、同 scope,不排除则孤儿 tick 会把
|
|
674
|
-
// **自己**(或同门孤儿)选成父并冻结=自环树,晚到的真 run 行永远接不上。
|
|
675
362
|
if (r.sessionId === parentInput && r.parentId === undefined && r.scope === childScope && !meta.has(r.id))
|
|
676
|
-
match = r.id;
|
|
363
|
+
match = r.id;
|
|
677
364
|
}
|
|
678
365
|
return match !== undefined ? { id: match, resolved: true } : { id: parentInput, resolved: false };
|
|
679
366
|
};
|
|
680
367
|
const rowTags = (m) => {
|
|
681
|
-
// codex R6:未解析成功=**不发 parentId**(原值只留 Meta 供下拍重试)。发原值就是 [1338]③ 伪影本身
|
|
682
|
-
// (悬空 parentId 指向不存在的行),且 fleet-wide 视图上一个撞值的外租户行 id 会在渲染端伪嵌套——
|
|
683
|
-
// 行早发孤儿顶格、解析成功后 MERGE 升级补 parentId,两边都诚实。
|
|
684
368
|
let parentId;
|
|
685
|
-
const parentInput = m.parentTaskId ?? m.parentSessionLink;
|
|
369
|
+
const parentInput = m.parentTaskId ?? m.parentSessionLink;
|
|
686
370
|
if (parentInput) {
|
|
687
371
|
if (m.resolvedParentId !== undefined) {
|
|
688
372
|
parentId = m.resolvedParentId;
|
|
@@ -699,8 +383,8 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
699
383
|
...(m.tenantScope ? { scope: m.tenantScope } : {}),
|
|
700
384
|
...(m.hostSessionId ? { sessionId: m.hostSessionId } : {}),
|
|
701
385
|
...(parentId !== undefined ? { parentId } : {}),
|
|
702
|
-
...(m.workflowRunId !== undefined ? { workflowRunId: m.workflowRunId } : {}),
|
|
703
|
-
...(m.parentToolCallId !== undefined ? { parentToolCallId: m.parentToolCallId } : {}),
|
|
386
|
+
...(m.workflowRunId !== undefined ? { workflowRunId: m.workflowRunId } : {}),
|
|
387
|
+
...(m.parentToolCallId !== undefined ? { parentToolCallId: m.parentToolCallId } : {}),
|
|
704
388
|
...(m.spawnedAt !== undefined ? { startedAt: m.spawnedAt, elapsedMs: Date.now() - m.spawnedAt } : {}),
|
|
705
389
|
};
|
|
706
390
|
};
|
|
@@ -713,121 +397,39 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
713
397
|
return u.tokens;
|
|
714
398
|
return undefined;
|
|
715
399
|
};
|
|
716
|
-
// design/158 A13: the three event-kind branches below are named local functions — same closure scope/capture
|
|
717
|
-
// as when they were inline `if (e.kind === "...") { ... }` blocks in the returned dispatcher (behavior
|
|
718
|
-
// identical), alongside the already-named helpers above (dlog/remember/resolveParentId/rowTags/sumUsage).
|
|
719
400
|
const handleSpawn = (e) => {
|
|
720
|
-
// PAIR-REVIEW F-3:revive spawn(spawn-after-tombstone)缺父字段时**继承墓碑 Meta 的父链**——
|
|
721
|
-
// 上一周期已解析冻结的 parentTaskId/resolvedParentId 不因重建丢失(core 1.351 revive spawn 不回放
|
|
722
|
-
// 父链;1.352 回放件到货后此臂自然让位,双保险)。只在新帧零父字段时继承,新值恒赢。
|
|
723
401
|
const tomb = meta.get(e.taskId);
|
|
724
402
|
const inheritParent = !e.parentTaskId && !e.parentSessionId && tomb?.done === true;
|
|
725
403
|
const m = {
|
|
726
|
-
// codex 两轮(1.350 适配):core 1.350 起 fallback 形不再发洗域 parentTaskId([1364]②),补偿链接改走
|
|
727
|
-
// 声明字段 parentSessionId(/v1/tasks/stream 主车道 spec.taskId 未设=恰此形)。**两域分存**——
|
|
728
|
-
// parentTaskId=真任务域(可直出 wire),parentSessionLink=会话域(仅作行嵌套解析输入,绝不冒充
|
|
729
|
-
// 任务 id 出 bg_notification)。
|
|
730
404
|
...(e.parentTaskId ? { parentTaskId: e.parentTaskId } : {}),
|
|
731
405
|
...(!e.parentTaskId && e.parentSessionId ? { parentSessionLink: e.parentSessionId } : {}),
|
|
732
406
|
...(inheritParent && tomb?.parentTaskId !== undefined ? { parentTaskId: tomb.parentTaskId } : {}),
|
|
733
407
|
...(inheritParent && tomb?.parentSessionLink !== undefined ? { parentSessionLink: tomb.parentSessionLink } : {}),
|
|
734
408
|
...(inheritParent && tomb?.resolvedParentId !== undefined ? { resolvedParentId: tomb.resolvedParentId } : {}),
|
|
735
|
-
// session-scoped: registry owner IS the host sessionId (design/129 owner key); fork spawns also carry
|
|
736
|
-
// the child's uuid sessionId — that is the a*↔uuid cross-system anchor, NOT the host session, so it is not used here.
|
|
737
|
-
// [1522] LOW4 root-first。[2378]/[2380] RB-478 到货:三条 revive 发射腿全部无条件带
|
|
738
|
-
// `root ?? parent` 链 ⇒ `?? e.owner` 回落臂成纯死臂,删([2370] 补审时显式排除在全清宣称外
|
|
739
|
-
// 等的就是这一天)。rootSessionId 缺席=不打 host 标(over-filter 方向,durable drain 兜)。
|
|
740
409
|
...(e.sessionScoped && e.owner && e.rootSessionId ? { hostSessionId: e.rootSessionId } : {}),
|
|
741
|
-
// 🔴 scope DOMAIN mapping: core's registry scope is `ctx.principal ?? deployment scope` —
|
|
742
|
-
// UNDEFINED on a single-user no-principal deployment — while the service fleet domain keys the
|
|
743
|
-
// no-principal caller as "default" (`gatedPrincipal ?? "default"`, streamFleet's callerScope). Without
|
|
744
|
-
// this map the row/notification carried NO scope and the scoped stream dropped EVERYTHING (fail-closed
|
|
745
|
-
// — the "terminal/bg_notification 零帧" repro). Same principal string in both domains when
|
|
746
|
-
// a principal exists, so the map is exact.
|
|
747
410
|
tenantScope: e.scope ?? "default",
|
|
748
411
|
sessionScoped: e.sessionScoped,
|
|
749
|
-
// [2354] 兼容面全清:旧 core 帧「回落收帧时刻」臂已删——floor ^4.0.0 下 startedAt 恒在;
|
|
750
|
-
// 缺席=诚实不发(行键 optional,消费端本就容忍缺席)。
|
|
751
412
|
...(e.startedAt !== undefined ? { spawnedAt: e.startedAt } : {}),
|
|
752
413
|
...(e.rootSessionId ? { rootSessionId: e.rootSessionId } : {}),
|
|
753
|
-
...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}),
|
|
754
|
-
...(e.sessionId ? { aliasUuid: e.sessionId } : {}),
|
|
755
|
-
...(e.workflowRunId ? { workflowRunId: e.workflowRunId } : {}),
|
|
756
|
-
// codex 1.240-R1/R2(H 行半场):wa\* 帧 sessionScoped:false 无 host owner → 行无 sessionId=
|
|
757
|
-
// 「absent=同 principal 全会话可见」(bg 兼容语义,对 wa\* 是泄露面)。归属源两级:①**信任源**
|
|
758
|
-
// core 帧的 parentSessionId(发起会话,1.350 声明字段——R2:无 runStore 的瞬态 sync 部署没有宿主
|
|
759
|
-
// fleet 行,行查 fallback 必空);②宿主行 sessionId 查表(同租户)兜底。
|
|
414
|
+
...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}),
|
|
415
|
+
...(e.sessionId ? { aliasUuid: e.sessionId } : {}),
|
|
416
|
+
...(e.workflowRunId ? { workflowRunId: e.workflowRunId } : {}),
|
|
760
417
|
...(e.workflowRunId && !(e.sessionScoped && e.owner)
|
|
761
|
-
// [2378]/[2380] RB-478:wa* 三级回落(root→parentSessionId→行查表)收单级——core 三条发射腿
|
|
762
|
-
// 已无条件带 root 锚,后两级成死臂;缺席=不打标(over-filter,durable 兜),不再猜。
|
|
763
418
|
? (e.rootSessionId ? { hostSessionId: e.rootSessionId } : {})
|
|
764
419
|
: {}),
|
|
765
|
-
// [1493] cross-session isolation: a NON-session-scoped, non-wa* bg child (owner = the run taskId — the
|
|
766
|
-
// norm when backgroundScope!="session", i.e. every multi-tenant deployment, and any local path where the
|
|
767
|
-
// session isn't adopted at spawn) STILL carries a host session on core's declared parentSessionId
|
|
768
|
-
// (subagent.js registerBackgroundAgent). Without promoting it to hostSessionId the row/notification is
|
|
769
|
-
// session-UNTAGGED → streamFleet's `?session=` filter fails OPEN → the completion is broadcast (and
|
|
770
|
-
// model-injected) into every same-principal shell (clay's dual-shell repro). Promote it so the filter
|
|
771
|
-
// narrows precisely. (Session-scoped children already tag via the `owner` arm above; wa* via its own arm
|
|
772
|
-
// just above — both are excluded by this condition, so no double-write.) The terminal event does NOT
|
|
773
|
-
// repeat parentSessionId (core emits it on spawn only), so this MUST be captured into Meta here at spawn.
|
|
774
|
-
//
|
|
775
|
-
// codex R1 (nested-agent mis-tag): parentSessionId is core's `ctx.sessionId`, which for a NESTED bg child
|
|
776
|
-
// (spawned BY a subagent) is the PARENT AGENT's child-session uuid, NOT the shell's root session. Tagging
|
|
777
|
-
// that uuid as hostSessionId would make the outer host shell FAIL the ownerSessionId check (row +
|
|
778
|
-
// completion suppressed, durable ack fence keyed wrong). So resolve through the alias table (same reverse
|
|
779
|
-
// lookup resolveParentId uses): if some agent Meta's aliasUuid === parentSessionId, this is nested —
|
|
780
|
-
// inherit THAT agent's already-resolved hostSessionId (the root shell session), or stay UNTAGGED when the
|
|
781
|
-
// parent itself is untagged. Only a TOP-LEVEL bg child (parentSessionId is the shell root, aliased by no
|
|
782
|
-
// agent) uses parentSessionId directly. Tenant-scoped like every other alias/parent resolution (R5).
|
|
783
|
-
// fable5 R1 (honest residual): a nested child whose parent Meta is ABSENT (restart lazy-mint edge — live
|
|
784
|
-
// Meta is never evicted, so within one process the parent always exists at spawn time) is
|
|
785
|
-
// INDISTINGUISHABLE from top-level and gets tagged with the given uuid. That is still SAFE: a
|
|
786
|
-
// child-session uuid is never any shell's own session, so no scoped subscriber matches it — the failure
|
|
787
|
-
// direction is over-filtering (the true owner misses live delivery; durable inbox/stream-open drain
|
|
788
|
-
// compensate), never cross-delivery. Root fix = core carrying an explicit root-host-session (or a
|
|
789
|
-
// top-level/nested discriminator) on the spawn event — asked as a core seam follow-up ([1493]).
|
|
790
|
-
// core 1.367 δ:[1493] fable5-R1 注里请的 root fix **到货**——spawn 帧显式带 rootSessionId
|
|
791
|
-
// (固定点语义,嵌套孙代恒=root shell 会话),信任源直用;alias 反查臂降为旧 core 帧回落。
|
|
792
|
-
// [2354] 兼容面全清:alias 反查臂(旧 core 帧无 rootSessionId 时经 meta 反查 parentSessionId)已删——
|
|
793
|
-
// floor ^4.0.0 下 spawn 帧恒带 rootSessionId(core 1.367 δ 固定点语义);缺席=不打 host 标,
|
|
794
|
-
// 失败方向仍是 over-filtering(durable inbox/stream-open drain 兜),永不 cross-delivery。
|
|
795
420
|
...(!(e.sessionScoped && e.owner) && !e.workflowRunId && e.rootSessionId
|
|
796
421
|
? { hostSessionId: e.rootSessionId }
|
|
797
422
|
: {}),
|
|
798
423
|
};
|
|
799
424
|
remember(e.taskId, m);
|
|
800
|
-
// [2070]① 让位登记(spawn 臂):fork 形 spawn 就带 sessionId=子代 uuid;普通 bg producer 的 spawn
|
|
801
|
-
// 不带,要等首 tick 的 progressTaskId(下方 handleTick 补 claim)。
|
|
802
425
|
claimIfKnown(e, m);
|
|
803
|
-
// [1364]③:spawn 帧 1.349+ 也带 agentType(类型名)/name——行 name 位保 description(display 标签,
|
|
804
|
-
// 历史语义),TYPE 列自 spawn 即亮(此前要等首 tick 自愈)。
|
|
805
|
-
// [1827]①(cli):spawn 行也透传 transcriptId(委派 prompt 的映射,详情页运行中即可取件)。
|
|
806
|
-
// 现状 core 只在 terminal 事件带此键(三发射点亲核)⇒ 本透传对旧 core 惰性;core 停 spawn/tick
|
|
807
|
-
// 源头补上即通,tick 臂(下方 :78x)早已同姿。server 不自行以 sessionId 推导——fork lane 的
|
|
808
|
-
// forkTranscriptId 与 sessionId 的等价关系归 core 所有,不在消费端烙死。
|
|
809
|
-
// [2400] FLEET-WORKFLOWS-7:[2070]③「名不铸」的第四处——spawn 双缺 description/name 时此前 `?? e.taskId`
|
|
810
|
-
// 拿 a* handle 冒充显示名;tick 臂只在 name 在场才发 + 总线 MERGE ⇒ 假名永久留行。缺席不铸,与其余三处同判。
|
|
811
426
|
const spawnName = e.description ?? e.name;
|
|
812
|
-
// #261 §2:core 5.36.0(#258)把 `seq` 从「只在 terminal 帧」放宽到 **spawn/tick 也带**(registry 行
|
|
813
|
-
// 的 stop-cycle 代际)。本车道是设计稿点名的第二条供给点 —— 三帧一律透传成行的 `cycleSeq`,**缺席不铸**。
|
|
814
427
|
bus.publishTask({ id: e.taskId, ...(spawnName !== undefined ? { name: redactSecrets(spawnName) } : {}), ...(e.agentType ? { agentType: redactSecrets(e.agentType) } : {}), ...rowTags(m), status: "running", tokens: 0, ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
|
|
815
428
|
dlog("bg_child_event", { kind: "spawn", taskId: e.taskId, sessionScoped: e.sessionScoped, scope: m.tenantScope, hostSessionId: m.hostSessionId ?? null, parentTaskId: m.parentTaskId ?? null });
|
|
816
429
|
};
|
|
817
430
|
const handleTick = (e, m) => {
|
|
818
|
-
// [2070]① 让位登记(tick 臂):普通 bg 形的 uuid 首次可见就在这里。**在**下面任何发布之前 claim,
|
|
819
|
-
// 这样即便 run-leg 的 forward 与本 tick 同拍到达,run-leg 也已经能看到让位信号。
|
|
820
431
|
claimIfKnown(e, m);
|
|
821
|
-
// dedup the per-leg uuid-keyed twin ONCE: the leg's onForwardEvent keys the same child's rows by
|
|
822
|
-
// `${runId} ${uuid}` and on the /v1/runs path runId === the host canonical taskId (spec.taskId is the
|
|
823
|
-
// durable run id). The tick that carries the child's OWN frame has progressParentTaskId === the host
|
|
824
|
-
// taskId (a NESTED grandchild carries the child's uuid instead — never suppressed here). A miss is a
|
|
825
|
-
// no-op remove (best-effort; worst case = a transient duplicate row until the leg settles, old behavior).
|
|
826
432
|
if (!m.suppressedTwin && e.progressTaskId && (!e.progressParentTaskId || e.progressParentTaskId === (m.parentTaskId ?? m.parentSessionLink))) {
|
|
827
|
-
// The per-leg childId is `${run.runId} ${uuid}` and runId ≠ the core hostTaskId on the
|
|
828
|
-
// sync-stream/resume paths (durableTaskId vs canonical sessionId), so a composite-key guess misses there.
|
|
829
|
-
// Scan-by-suffix instead: the uuid tail uniquely names the child across every leg's runId prefix. One-shot
|
|
830
|
-
// (suppressedTwin) so the O(rows) scan runs once per child.
|
|
831
433
|
const tail = ` ${e.progressTaskId}`;
|
|
832
434
|
for (const row of bus.snapshot().tasks) {
|
|
833
435
|
if (row.id.endsWith(tail))
|
|
@@ -835,13 +437,6 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
835
437
|
}
|
|
836
438
|
m.suppressedTwin = true;
|
|
837
439
|
}
|
|
838
|
-
// codex R8:普通(非 fork)bg producer 的 spawn 不带 sessionId,uuid 别名要等 terminal 才现——嵌套孙
|
|
839
|
-
// 在父运行期解析全 miss。但 child 本人的 tick 恒带 progressTaskId=其 uuid(与双生抑制同一判别形),
|
|
840
|
-
// 首 tick 即学别名;孙侧 retry-until-success 语义随后一拍自然接上(spawn 路径已带 sessionId 的 fork 形
|
|
841
|
-
// 不受影响,??= 只补缺)。
|
|
842
|
-
// codex R9:别名习得**即回填**——快孙可能已无后续帧(spawn+tick+terminal 全在父首 tick 前),等孙自己
|
|
843
|
-
// retry 就永远孤儿。习得此刻主动扫在场未解析孤儿,替它们把行升级(rowTags 内解析+冻结)。彻底闭窗
|
|
844
|
-
// (孙整个生命周期都在别名前走完的瞬态孤儿)在 core:普通形 spawn emit 补 sessionId(fork 形已带)。
|
|
845
440
|
if (m.aliasUuid === undefined && e.progressTaskId && (!e.progressParentTaskId || e.progressParentTaskId === (m.parentTaskId ?? m.parentSessionLink))) {
|
|
846
441
|
m.aliasUuid = e.progressTaskId;
|
|
847
442
|
for (const [cid, cm] of meta) {
|
|
@@ -852,33 +447,18 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
852
447
|
}
|
|
853
448
|
const tokens = sumUsage(e.usage);
|
|
854
449
|
const name = e.name ? redactSecrets(e.name) : undefined;
|
|
855
|
-
// [1364]③ K-7 换位:TYPE 列取 tick 帧的行级 `agentType`(core 1.350 定谳)。[2354] 兼容面全清:
|
|
856
|
-
// 「缺省回落 name」旧 core 兼容臂已删——floor ^4.0.0 下 agentType 恒在;缺席=诚实不发(行键 optional)。
|
|
857
450
|
const agentType = e.agentType ? redactSecrets(e.agentType) : undefined;
|
|
858
|
-
// The sink tick carries core's currentAction (its only attachment point today) —
|
|
859
|
-
// UNTRUSTED (tool argument head) → redact, same discipline as name. Additive; absent until a tool runs.
|
|
860
451
|
const currentAction = e.currentAction ? redactSecrets(e.currentAction) : undefined;
|
|
861
|
-
// [1827]②/core 1.429:结构化同源体。字段 PICKED(不整对象 spread——future core 键不得裸骑);
|
|
862
|
-
// toolName verbatim,target 与 currentAction 同边界(redact + 300 bound,brainStatus.detail 同惯例)。
|
|
863
452
|
const currentTool = e.currentTool
|
|
864
453
|
? { toolName: e.currentTool.toolName, ...(e.currentTool.target !== undefined ? { target: redactSecrets(e.currentTool.target).slice(0, 300) } : {}) }
|
|
865
454
|
: undefined;
|
|
866
|
-
|
|
867
|
-
// 缺席时**不发这个键**(消费方不得把缺席当 0)。`transcriptId` 是委派 prompt 的**映射** ——
|
|
868
|
-
// prompt 本身不在 `BackgroundChildEvent` 上,server 收不到就不造。
|
|
869
|
-
bus.publishTask({ id: e.taskId, ...rowTags(m), status: "running", ...(tokens !== undefined ? { tokens } : {}), ...(name ? { name } : {}), ...(agentType ? { agentType } : {}), ...(currentAction ? { currentAction } : {}), ...(currentTool ? { currentTool } : {}), ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.usage?.toolUses !== undefined ? { toolUses: e.usage.toolUses } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) }); // #261 seq→cycleSeq 缺席不铸
|
|
455
|
+
bus.publishTask({ id: e.taskId, ...rowTags(m), status: "running", ...(tokens !== undefined ? { tokens } : {}), ...(name ? { name } : {}), ...(agentType ? { agentType } : {}), ...(currentAction ? { currentAction } : {}), ...(currentTool ? { currentTool } : {}), ...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}), ...(e.usage?.toolUses !== undefined ? { toolUses: e.usage.toolUses } : {}), ...(e.transcriptId ? { transcriptId: e.transcriptId } : {}) });
|
|
870
456
|
};
|
|
871
457
|
const handleTerminal = (e, m) => {
|
|
872
|
-
// terminal: flip the row so subscribers SEE the terminal frame, then leave the fleet — and push the
|
|
873
|
-
// contract bg_notification on the always-open lane (缺口②: idle delivery is immediate; the durable
|
|
874
|
-
// inbox park stays as the offline/replay belt, shell dedups by (taskId,status)).
|
|
875
458
|
const status = e.status === "completed" ? "completed" : e.status === "killed" ? "killed" : "failed";
|
|
876
|
-
// [1839]①/(a) 案:终态**行帧**同带残局键——此前只有 {id,status},看行卡(不订通知帧)的 idle 观察者
|
|
877
|
-
// 拿不到终局。与下方 bg_notification 同源同姿势:键逐挑不 spread、editedFiles path 过 redactSecrets
|
|
878
|
-
// + 100 界、缺席不铸。(recentSteps 不进行帧——通知卡语义,行卡不渲染步骤列表。)
|
|
879
459
|
bus.publishTask({
|
|
880
460
|
id: e.taskId, ...rowTags(m), status,
|
|
881
|
-
...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}),
|
|
461
|
+
...(typeof e.seq === "number" ? { cycleSeq: e.seq } : {}),
|
|
882
462
|
...(e.stoppedBy ? { stoppedBy: e.stoppedBy } : {}),
|
|
883
463
|
...(e.resumable !== undefined ? { resumable: e.resumable } : {}),
|
|
884
464
|
...(e.transcriptId ? { transcriptId: e.transcriptId } : {}),
|
|
@@ -897,12 +477,8 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
897
477
|
: {}),
|
|
898
478
|
});
|
|
899
479
|
bus.removeTask(e.taskId);
|
|
900
|
-
// [2070]① 让位解除:本子代已终态离场,同 uuid 的迟到帧回到旧行为(claim 只在活期有效)。
|
|
901
480
|
if (m.aliasUuid)
|
|
902
481
|
bus.releaseBackgroundChildLane(m.tenantScope, m.aliasUuid);
|
|
903
|
-
// codex 1.240-R1(M2):1.356 的回调序=bceTick 先于 host forward——tick 处的双生抑制扫在 uuid 双生
|
|
904
|
-
// 行发布**之前**,one-shot 空扫后置旗,双生行永存。terminal 补扫一次(aliasUuid=tick 学到的 child
|
|
905
|
-
// uuid),把 run-leg 的复合键行清场。
|
|
906
482
|
if (m.aliasUuid) {
|
|
907
483
|
const tail = ` ${m.aliasUuid}`;
|
|
908
484
|
for (const row of bus.snapshot().tasks) {
|
|
@@ -910,32 +486,19 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
910
486
|
bus.removeTask(row.id);
|
|
911
487
|
}
|
|
912
488
|
}
|
|
913
|
-
// codex 1.240-R1(H1):wa\*(workflowRunId 在)不走 bg_notification 契约——该帧族=后台任务完成通知
|
|
914
|
-
// (壳注入模型),workflow 的完成通知已有 workflow_complete 专链;且 1.356 wa\* 帧 sessionScoped:false
|
|
915
|
-
// 无 host owner → 通知无 ownerSessionId=同租户**全会话**可见(streamFleet 的 absent-放行是 bg 兼容位,
|
|
916
|
-
// 不是 wa\* 的授权)。per-agent 完成本就不该打断模型(CC 亦无此形)。
|
|
917
489
|
if (m.workflowRunId !== undefined) {
|
|
918
|
-
m.done = true;
|
|
490
|
+
m.done = true;
|
|
919
491
|
dlog("bg_child_event", { kind: "terminal-wa-no-notification", taskId: e.taskId, workflowRunId: m.workflowRunId });
|
|
920
492
|
return;
|
|
921
493
|
}
|
|
922
494
|
bus.publishBgNotification({
|
|
923
495
|
taskId: e.taskId,
|
|
924
496
|
...(e.sessionId ? { sessionId: e.sessionId } : {}),
|
|
925
|
-
// [1392]①(PAIR-REVIEW F-1@cli 的 wire 前置):终帧 seq(core 1.352 BackgroundChildEvent.terminal 补位,
|
|
926
|
-
// tolerate-absent——旧 core 无此位=不发,cli 键折 seq 的修依赖它区分 revive 周期)。
|
|
927
497
|
...(typeof e.seq === "number" ? { seq: e.seq } : {}),
|
|
928
498
|
status: e.status ?? "failed",
|
|
929
499
|
...(e.summary ? { summary: redactSecrets(e.summary) } : {}),
|
|
930
500
|
scope: (m.sessionScoped || e.sessionScoped) ? "session" : "task",
|
|
931
|
-
// codex(1.350 二轮):wire 的 parentTaskId 位只出真任务域;fallback 形出解析成功的 run 行 id
|
|
932
|
-
// (=durable run id,消费者拿它绑父行),解析未成=省略,绝不让会话 id 冒充任务 id 出 wire。
|
|
933
501
|
...(m.parentTaskId ? { parentTaskId: m.parentTaskId } : m.resolvedParentId ? { parentTaskId: m.resolvedParentId } : {}),
|
|
934
|
-
// The residual fields must ride THIS lane too (the "kill across turns" main line renders
|
|
935
|
-
// off it). Same posture as project.ts/inbox; fields picked, arrays defensively bounded.
|
|
936
|
-
// P1-3(core 1.452,[1925] 一轮批):completionId=子代完成的幂等锚,消费端(cli)按它对同一完成的
|
|
937
|
-
// 多路通知(bg_notification/task_notification/inbox 重放)去重。verbatim 透传,仅通知帧带
|
|
938
|
-
// (行帧不带——行的身份是 taskId,幂等锚是通知语义)。
|
|
939
502
|
...(e.completionId ? { completionId: e.completionId } : {}),
|
|
940
503
|
...(e.stoppedBy ? { stoppedBy: e.stoppedBy } : {}),
|
|
941
504
|
...(e.resumable !== undefined ? { resumable: e.resumable } : {}),
|
|
@@ -943,7 +506,6 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
943
506
|
? { recentSteps: e.recentSteps.slice(0, 10).map((s) => ({ tool: s.tool, target: redactSecrets(s.target), outcome: redactSecrets(s.outcome) })) }
|
|
944
507
|
: {}),
|
|
945
508
|
...(e.editedFiles !== undefined ? { editedFiles: e.editedFiles.slice(0, 100).map((f) => ({ path: redactSecrets(f.path), edits: f.edits })) } : {}),
|
|
946
|
-
// [1543]§二 LOW:usage/transcriptId 补齐(fields picked——usage 六数值键逐一挑,不整对象 spread)。
|
|
947
509
|
...(e.usage
|
|
948
510
|
? {
|
|
949
511
|
usage: {
|
|
@@ -957,14 +519,13 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
957
519
|
}
|
|
958
520
|
: {}),
|
|
959
521
|
...(e.transcriptId ? { transcriptId: e.transcriptId } : {}),
|
|
960
|
-
...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}),
|
|
522
|
+
...(e.parentToolCallId ? { parentToolCallId: e.parentToolCallId } : {}),
|
|
961
523
|
...(m.tenantScope ? { ownerScope: m.tenantScope } : {}),
|
|
962
524
|
...(m.hostSessionId ? { ownerSessionId: m.hostSessionId } : {}),
|
|
963
525
|
...(m.rootSessionId ? { rootSessionId: m.rootSessionId } : {}),
|
|
964
526
|
});
|
|
965
|
-
// One info line per terminal so the emit-vs-filter bisect is a log-grep, not a rerun.
|
|
966
527
|
dlog("bg_child_event", { kind: "terminal", taskId: e.taskId, status, scope: m.tenantScope ?? null, hostSessionId: m.hostSessionId ?? null, childSessionId: e.sessionId ?? null, bgNotificationPublished: true });
|
|
967
|
-
m.done = true;
|
|
528
|
+
m.done = true;
|
|
968
529
|
m.suppressedTwin = true;
|
|
969
530
|
};
|
|
970
531
|
return (e) => {
|
|
@@ -972,22 +533,18 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
972
533
|
handleSpawn(e);
|
|
973
534
|
return;
|
|
974
535
|
}
|
|
975
|
-
|
|
976
|
-
// 诚实缺席,行键 optional;tick/terminal 先到=restart/丢 spawn 边缘,收帧时刻不是 launch 时刻)。
|
|
977
|
-
const m = meta.get(e.taskId) ?? { sessionScoped: e.sessionScoped, tenantScope: e.scope ?? "default" }; // scope domain map holds on the lazily-minted path too([1338]④ 首见起表);F2(2026-07-30 复审):lazy-mint 曾硬编码 "default",scoped 部署下 claim 落错租户键 ⇒ 让位失效退回双生行
|
|
536
|
+
const m = meta.get(e.taskId) ?? { sessionScoped: e.sessionScoped, tenantScope: e.scope ?? "default" };
|
|
978
537
|
if (!meta.has(e.taskId)) {
|
|
979
|
-
remember(e.taskId, m);
|
|
538
|
+
remember(e.taskId, m);
|
|
980
539
|
dlog("bg_child_event", { kind: `${e.kind}-lazy-mint`, taskId: e.taskId, sessionScoped: e.sessionScoped });
|
|
981
540
|
}
|
|
982
541
|
else {
|
|
983
|
-
remember(e.taskId, m);
|
|
542
|
+
remember(e.taskId, m);
|
|
984
543
|
}
|
|
985
544
|
if (m.done) {
|
|
986
|
-
// tombstone: this child already settled — a late tick must not resurrect a running row (settle-freeze 同族).
|
|
987
545
|
dlog("bg_child_event", { kind: `${e.kind}-after-terminal-ignored`, taskId: e.taskId });
|
|
988
546
|
return;
|
|
989
547
|
}
|
|
990
|
-
// P1-2(core 1.432):lazy-mint/spawn 前旧 core 帧路径——parentToolCallId「出生就在场」,首见即补
|
|
991
548
|
if (m.parentToolCallId === undefined && e.parentToolCallId)
|
|
992
549
|
m.parentToolCallId = e.parentToolCallId;
|
|
993
550
|
if (e.kind === "tick") {
|
|
@@ -997,32 +554,15 @@ export function fleetBackgroundChildPublisher(bus, log) {
|
|
|
997
554
|
handleTerminal(e, m);
|
|
998
555
|
};
|
|
999
556
|
}
|
|
1000
|
-
/** BC-1 (clay-decided 2026-06-27): a top-level run's fleet-row `name` = a SHORT, single-line **objective preview**
|
|
1001
|
-
* (the readable "what is this task" label) — NOT the full objective dumped in, and NOT duplicated into `description`.
|
|
1002
|
-
* `description` is left to the publisher's LIVE-ACTIVITY update (`onEvent` tool_start → the current tool), so the row
|
|
1003
|
-
* shows `name`=objective + `description`=current activity (two DISTINCT things, killing the earlier "objective shown
|
|
1004
|
-
* twice" dup). Whitespace-collapsed + capped ≤48. (A subagent CHILD row's name = its agent-type — BC-2.)
|
|
1005
|
-
* SECRET-SAFE: redact secret shapes (redactSecrets) BEFORE truncating — a secret in the objective must not leak in
|
|
1006
|
-
* the row name. The durable objective is already redacted; the name preview was asymmetric (raw) until this. */
|
|
1007
557
|
export function fleetRunLabels(objective) {
|
|
1008
558
|
const oneLine = redactSecrets((objective ?? "").replace(/\s+/g, " ")).trim();
|
|
1009
559
|
if (!oneLine)
|
|
1010
560
|
return { name: "task" };
|
|
1011
|
-
// [1414]④:控制帧形 objective(注入 turn 的 `<task-notification>…` 等 XML 头)不得当行名——用户底部
|
|
1012
|
-
// 会看到「<task-notifica…」尖括号伪影。以 `<` 开头且形如 XML tag 的 objective 给中性名;真实用户
|
|
1013
|
-
// objective 以 `<` 开头的形(粘贴 HTML 片段)极罕且中性名无害。
|
|
1014
561
|
if (/^<[a-z][a-z0-9_-]*[\s>/]/i.test(oneLine))
|
|
1015
562
|
return { name: "(notification turn)" };
|
|
1016
563
|
const name = oneLine.length > 48 ? `${oneLine.slice(0, 47)}…` : oneLine;
|
|
1017
564
|
return { name };
|
|
1018
565
|
}
|
|
1019
|
-
/**
|
|
1020
|
-
* fleet 行的**离场**判据(`onTerminal` 的 removal 集)—— `FleetTaskStatus` 的穷举 switch。
|
|
1021
|
-
*
|
|
1022
|
-
* 与 {@link runStatusToFleet} 同族(A-032 P1-④ 证据段点名):此前是消费点手抄的
|
|
1023
|
-
* `=== "completed" || === "failed" || === "killed"` 三词字面比对,fleet 词表加成员时同样没有编译期钉。
|
|
1024
|
-
* park 态(`waiting` / `awaiting approval`)判 false 是承重的:行还活着,必须留在 fleet 上。
|
|
1025
|
-
*/
|
|
1026
566
|
export function isTerminalFleetStatus(status) {
|
|
1027
567
|
switch (status) {
|
|
1028
568
|
case "completed":
|
|
@@ -1037,16 +577,12 @@ export function isTerminalFleetStatus(status) {
|
|
|
1037
577
|
case "idle":
|
|
1038
578
|
return false;
|
|
1039
579
|
default: {
|
|
1040
|
-
const unhandled = status;
|
|
580
|
+
const unhandled = status;
|
|
1041
581
|
void unhandled;
|
|
1042
|
-
// 运行期保守:未知 ⇒ 不摘行(误摘 = 把一条还在跑的任务从面板上抹掉;漏摘只是多一行)。
|
|
1043
|
-
// 这条臂与 runStatusToFleet 的 miss 臂**不同源**:`FleetTaskStatus` 是本仓自有词表,只会被本仓
|
|
1044
|
-
// 自己加成员,而那是编译期就红的事 —— 所以这里不接留痕(没有"运行期冒出个新词"的来路)。
|
|
1045
582
|
return false;
|
|
1046
583
|
}
|
|
1047
584
|
}
|
|
1048
585
|
}
|
|
1049
|
-
/** Map a service run status onto the neutral fleet vocabulary the shell renders. */
|
|
1050
586
|
export function runStatusToFleet(status, opts) {
|
|
1051
587
|
if (opts?.awaitingPlanApproval)
|
|
1052
588
|
return "awaiting approval";
|
|
@@ -1059,26 +595,17 @@ export function runStatusToFleet(status, opts) {
|
|
|
1059
595
|
return "completed";
|
|
1060
596
|
case "failed":
|
|
1061
597
|
case "error":
|
|
1062
|
-
case "blocked":
|
|
1063
|
-
// neutral fleet vocab has no "blocked"). CRUCIAL: this also lets onTerminal REMOVE the row — the removal set is
|
|
1064
|
-
// {completed,failed,killed}, so without mapping "blocked" here it fell through to the "idle" default and a
|
|
1065
|
-
// terminal blocked run LEAKED a permanent stale "idle" row on the fleet (caught by on-box e2e, 2026-06-28).
|
|
598
|
+
case "blocked":
|
|
1066
599
|
return "failed";
|
|
1067
600
|
case "suspended":
|
|
1068
|
-
return "waiting";
|
|
601
|
+
return "waiting";
|
|
1069
602
|
case "needs_review":
|
|
1070
603
|
return "awaiting approval";
|
|
1071
604
|
case "killed":
|
|
1072
605
|
case "cancelled":
|
|
1073
606
|
return "killed";
|
|
1074
607
|
default: {
|
|
1075
|
-
// 🔴 编译期执法点:词表加成员 ⇒ 这里收不敛到 `never` ⇒ tsc 红,加成员的人必须当场回答
|
|
1076
|
-
// 「这个新态在 fleet 上长什么样、行该不该离场」。
|
|
1077
608
|
const unhandled = status;
|
|
1078
|
-
// 运行期防线(#157:runtime miss 臂必须自陈)。这条臂**运行期仍可达**:`rowToRun` 把无约束的
|
|
1079
|
-
// SQL 文本列裸 cast 成联合,滚动升级里更新的副本写入的新状态词会被旧副本读到。方向照旧折 `idle`
|
|
1080
|
-
// (不改行为,改的是「静默」),但每次 miss 必须留痕 —— 否则「fleet 上为什么有一行永远不走的 idle」
|
|
1081
|
-
// 在遥测里没有任何痕迹,只能靠人眼在面板上发现。
|
|
1082
609
|
recordFailOpen("server.fleet.run-status-unmapped", String(unhandled));
|
|
1083
610
|
return "idle";
|
|
1084
611
|
}
|