oh-my-codex 0.20.3 → 0.20.5
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/Cargo.lock +35 -18
- package/Cargo.toml +1 -1
- package/README.md +2 -2
- package/crates/omx-runtime/Cargo.toml +3 -0
- package/crates/omx-runtime/src/main.rs +390 -0
- package/crates/omx-runtime/tests/execution.rs +264 -0
- package/dist/adapt/__tests__/herdr-target.test.d.ts +2 -0
- package/dist/adapt/__tests__/herdr-target.test.d.ts.map +1 -0
- package/dist/adapt/__tests__/herdr-target.test.js +72 -0
- package/dist/adapt/__tests__/herdr-target.test.js.map +1 -0
- package/dist/adapt/contracts.d.ts +1 -1
- package/dist/adapt/contracts.d.ts.map +1 -1
- package/dist/adapt/contracts.js +1 -1
- package/dist/adapt/contracts.js.map +1 -1
- package/dist/adapt/herdr/__tests__/authority.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/authority.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/authority.test.js +49 -0
- package/dist/adapt/herdr/__tests__/authority.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/bridge.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/bridge.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/bridge.test.js +176 -0
- package/dist/adapt/herdr/__tests__/bridge.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/sanitize.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/sanitize.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/sanitize.test.js +45 -0
- package/dist/adapt/herdr/__tests__/sanitize.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/semantic.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/semantic.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/semantic.test.js +130 -0
- package/dist/adapt/herdr/__tests__/semantic.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/seq-store.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/seq-store.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/seq-store.test.js +54 -0
- package/dist/adapt/herdr/__tests__/seq-store.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/socket-live.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/socket-live.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/socket-live.test.js +104 -0
- package/dist/adapt/herdr/__tests__/socket-live.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/team-map.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/team-map.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/team-map.test.js +46 -0
- package/dist/adapt/herdr/__tests__/team-map.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/transport.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/transport.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/transport.test.js +160 -0
- package/dist/adapt/herdr/__tests__/transport.test.js.map +1 -0
- package/dist/adapt/herdr/__tests__/wiring.test.d.ts +2 -0
- package/dist/adapt/herdr/__tests__/wiring.test.d.ts.map +1 -0
- package/dist/adapt/herdr/__tests__/wiring.test.js +118 -0
- package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -0
- package/dist/adapt/herdr/authority.d.ts +34 -0
- package/dist/adapt/herdr/authority.d.ts.map +1 -0
- package/dist/adapt/herdr/authority.js +71 -0
- package/dist/adapt/herdr/authority.js.map +1 -0
- package/dist/adapt/herdr/bridge.d.ts +84 -0
- package/dist/adapt/herdr/bridge.d.ts.map +1 -0
- package/dist/adapt/herdr/bridge.js +222 -0
- package/dist/adapt/herdr/bridge.js.map +1 -0
- package/dist/adapt/herdr/sanitize.d.ts +20 -0
- package/dist/adapt/herdr/sanitize.d.ts.map +1 -0
- package/dist/adapt/herdr/sanitize.js +70 -0
- package/dist/adapt/herdr/sanitize.js.map +1 -0
- package/dist/adapt/herdr/semantic.d.ts +67 -0
- package/dist/adapt/herdr/semantic.d.ts.map +1 -0
- package/dist/adapt/herdr/semantic.js +170 -0
- package/dist/adapt/herdr/semantic.js.map +1 -0
- package/dist/adapt/herdr/seq-store.d.ts +32 -0
- package/dist/adapt/herdr/seq-store.d.ts.map +1 -0
- package/dist/adapt/herdr/seq-store.js +126 -0
- package/dist/adapt/herdr/seq-store.js.map +1 -0
- package/dist/adapt/herdr/team-map.d.ts +30 -0
- package/dist/adapt/herdr/team-map.d.ts.map +1 -0
- package/dist/adapt/herdr/team-map.js +37 -0
- package/dist/adapt/herdr/team-map.js.map +1 -0
- package/dist/adapt/herdr/transport.d.ts +115 -0
- package/dist/adapt/herdr/transport.d.ts.map +1 -0
- package/dist/adapt/herdr/transport.js +260 -0
- package/dist/adapt/herdr/transport.js.map +1 -0
- package/dist/adapt/herdr/wiring.d.ts +24 -0
- package/dist/adapt/herdr/wiring.d.ts.map +1 -0
- package/dist/adapt/herdr/wiring.js +62 -0
- package/dist/adapt/herdr/wiring.js.map +1 -0
- package/dist/adapt/herdr.d.ts +19 -0
- package/dist/adapt/herdr.d.ts.map +1 -0
- package/dist/adapt/herdr.js +115 -0
- package/dist/adapt/herdr.js.map +1 -0
- package/dist/adapt/index.d.ts +4 -0
- package/dist/adapt/index.d.ts.map +1 -1
- package/dist/adapt/index.js +48 -0
- package/dist/adapt/index.js.map +1 -1
- package/dist/adapt/registry.d.ts.map +1 -1
- package/dist/adapt/registry.js +11 -0
- package/dist/adapt/registry.js.map +1 -1
- package/dist/auth/__tests__/hotswap.test.js +60 -1
- package/dist/auth/__tests__/hotswap.test.js.map +1 -1
- package/dist/auth/__tests__/storage.test.js +33 -1
- package/dist/auth/__tests__/storage.test.js.map +1 -1
- package/dist/auth/hotswap.d.ts +16 -2
- package/dist/auth/hotswap.d.ts.map +1 -1
- package/dist/auth/hotswap.js +22 -7
- package/dist/auth/hotswap.js.map +1 -1
- package/dist/auth/storage.js +1 -1
- package/dist/auth/storage.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +86 -1333
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
- package/dist/autopilot/ralplan-gate.js +7 -6
- package/dist/autopilot/ralplan-gate.js.map +1 -1
- package/dist/cli/__tests__/agents-init.test.js +4 -23
- package/dist/cli/__tests__/agents-init.test.js.map +1 -1
- package/dist/cli/__tests__/api.test.js +61 -24
- package/dist/cli/__tests__/api.test.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js +3 -1
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/codex-feature-probe.test.js +66 -1
- package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +70 -2
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/detached-leader-teardown.test.d.ts +2 -0
- package/dist/cli/__tests__/detached-leader-teardown.test.d.ts.map +1 -0
- package/dist/cli/__tests__/detached-leader-teardown.test.js +473 -0
- package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-process-identity-readiness.test.js +103 -0
- package/dist/cli/__tests__/doctor-process-identity-readiness.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.js +139 -0
- package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +35 -3
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +75 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +621 -460
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +1020 -143
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +4 -3
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/native-assets.test.js +398 -16
- package/dist/cli/__tests__/native-assets.test.js.map +1 -1
- package/dist/cli/__tests__/native-hook-claim-journal.test.js +53 -5
- package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +2 -2
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +57 -57
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -1
- package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan-child-binding-3212.test.js +56 -0
- package/dist/cli/__tests__/ralplan-child-binding-3212.test.js.map +1 -0
- package/dist/cli/__tests__/ralplan.test.js +84 -12
- package/dist/cli/__tests__/ralplan.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +1118 -43
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +8 -55
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +183 -4
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +93 -41
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +45 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +117 -2
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +50 -5
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +669 -177
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +32 -2
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/api.d.ts.map +1 -1
- package/dist/cli/api.js +9 -10
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/codex-feature-probe.d.ts +16 -0
- package/dist/cli/codex-feature-probe.d.ts.map +1 -1
- package/dist/cli/codex-feature-probe.js +44 -0
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/codex-home.d.ts.map +1 -1
- package/dist/cli/codex-home.js +7 -4
- package/dist/cli/codex-home.js.map +1 -1
- package/dist/cli/doctor.d.ts +17 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +277 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +231 -12
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2298 -735
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/native-assets.d.ts +25 -0
- package/dist/cli/native-assets.d.ts.map +1 -1
- package/dist/cli/native-assets.js +448 -51
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/native-hook-claim-journal.d.ts +8 -4
- package/dist/cli/native-hook-claim-journal.d.ts.map +1 -1
- package/dist/cli/native-hook-claim-journal.js +30 -8
- package/dist/cli/native-hook-claim-journal.js.map +1 -1
- package/dist/cli/package-manager-ownership.d.ts +63 -0
- package/dist/cli/package-manager-ownership.d.ts.map +1 -0
- package/dist/cli/package-manager-ownership.js +323 -0
- package/dist/cli/package-manager-ownership.js.map +1 -0
- package/dist/cli/ralph.js +2 -2
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/ralplan.d.ts +4 -2
- package/dist/cli/ralplan.d.ts.map +1 -1
- package/dist/cli/ralplan.js +76 -34
- package/dist/cli/ralplan.js.map +1 -1
- package/dist/cli/setup-preferences.d.ts +15 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +27 -1
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +55 -32
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +52 -9
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +2 -2
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +27 -28
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update-worker.d.ts +2 -0
- package/dist/cli/update-worker.d.ts.map +1 -0
- package/dist/cli/update-worker.js +142 -0
- package/dist/cli/update-worker.js.map +1 -0
- package/dist/cli/update.d.ts +14 -22
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +192 -241
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +173 -29
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +3 -1
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/generator.d.ts +25 -3
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +113 -35
- package/dist/config/generator.js.map +1 -1
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +25 -0
- package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +2 -0
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +3 -1
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -4
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts +2 -0
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js +172 -0
- package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js.map +1 -0
- package/dist/hooks/__tests__/keyword-detector.test.js +172 -33
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +173 -11
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +1 -0
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +65 -17
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +27 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +10 -118
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js +7 -0
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -2
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +4 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js +2 -1
- package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +1 -0
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js +9 -0
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +2976 -557
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/agents-overlay.js +1 -1
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js +108 -2
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/sdk.test.js +151 -1
- package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +20 -3
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner.js +1 -0
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
- package/dist/hooks/extensibility/runtime.js.map +1 -1
- package/dist/hooks/extensibility/sdk/paths.d.ts +1 -1
- package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk/paths.js +2 -2
- package/dist/hooks/extensibility/sdk/paths.js.map +1 -1
- package/dist/hooks/extensibility/sdk/runtime-state.d.ts +1 -1
- package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk/runtime-state.js +55 -15
- package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
- package/dist/hooks/extensibility/sdk.d.ts +1 -0
- package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk.js +1 -1
- package/dist/hooks/extensibility/sdk.js.map +1 -1
- package/dist/hooks/extensibility/types.d.ts +7 -0
- package/dist/hooks/extensibility/types.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +10 -3
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +116 -20
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/session.d.ts +180 -14
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1402 -281
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +6 -6
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +26 -16
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +83 -78
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +36 -31
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +155 -198
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/tmux.d.ts +42 -4
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +1017 -139
- package/dist/hud/tmux.js.map +1 -1
- package/dist/leader/__tests__/contract.test.js +71 -34
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +1 -6
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +25 -24
- package/dist/leader/contract.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.js +17 -0
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js +4 -4
- package/dist/mcp/__tests__/state-paths.test.js +735 -4
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +1 -1
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/__tests__/trace-server.test.js +2 -2
- package/dist/mcp/__tests__/trace-server.test.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts.map +1 -1
- package/dist/mcp/hermes-bridge.js +2 -2
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +78 -4
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +543 -40
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/modes/__tests__/base-autopilot-gates.test.js +36 -7
- package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
- package/dist/modes/__tests__/base-multi-state-compat.test.js +1 -1
- package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +17 -8
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts +5 -5
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +76 -21
- package/dist/modes/base.js.map +1 -1
- package/dist/native-assets/archive.d.ts +17 -0
- package/dist/native-assets/archive.d.ts.map +1 -0
- package/dist/native-assets/archive.js +238 -0
- package/dist/native-assets/archive.js.map +1 -0
- package/dist/native-assets/policy.d.ts +42 -0
- package/dist/native-assets/policy.d.ts.map +1 -0
- package/dist/native-assets/policy.js +155 -0
- package/dist/native-assets/policy.js.map +1 -0
- package/dist/pipeline/__tests__/orchestrator.test.js +162 -40
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +66 -149
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +124 -18
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +36 -8
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +70 -1437
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/documented-leader-preflight.test.js +328 -39
- package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +82 -102
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +9 -1
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +40 -304
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/documented-leader-preflight.d.ts +15 -8
- package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
- package/dist/ralplan/documented-leader-preflight.js +356 -54
- package/dist/ralplan/documented-leader-preflight.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +1 -1
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +68 -173
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/run-state.d.ts +5 -1
- package/dist/runtime/run-state.d.ts.map +1 -1
- package/dist/runtime/run-state.js +15 -8
- package/dist/runtime/run-state.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +14071 -8859
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js +225 -0
- package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js +188 -0
- package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js +259 -0
- package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js +491 -0
- package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js +708 -0
- package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js.map +1 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts +2 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js +208 -0
- package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-state-io.test.js +26 -0
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.js +23 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -1
- package/dist/scripts/__tests__/postinstall.test.js +19 -1
- package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +25 -8
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -1
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +60 -25
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +155 -16
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts +2 -0
- package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/verify-native-release-assets.test.js +235 -0
- package/dist/scripts/__tests__/verify-native-release-assets.test.js.map +1 -0
- package/dist/scripts/codex-native-hook.d.ts +50 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +3559 -451
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts +9 -0
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +35 -12
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/generate-native-release-manifest.js +80 -51
- package/dist/scripts/generate-native-release-manifest.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +28 -39
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +5 -0
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +32 -3
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +23 -3
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +45 -5
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -0
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +4 -1
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +78 -82
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/postinstall-advisory.d.ts +14 -0
- package/dist/scripts/postinstall-advisory.d.ts.map +1 -0
- package/dist/scripts/postinstall-advisory.js +49 -0
- package/dist/scripts/postinstall-advisory.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +1 -1
- package/dist/scripts/postinstall.d.ts.map +1 -1
- package/dist/scripts/postinstall.js +1 -1
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +32 -1
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +413 -60
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +16 -1
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-release-assets.js +37 -23
- package/dist/scripts/verify-native-release-assets.js.map +1 -1
- package/dist/state/__tests__/mode-state-context.test.js +12 -5
- package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +460 -850
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +400 -2
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/mode-state-context.d.ts +3 -0
- package/dist/state/mode-state-context.d.ts.map +1 -1
- package/dist/state/mode-state-context.js +27 -13
- package/dist/state/mode-state-context.js.map +1 -1
- package/dist/state/operations.d.ts +3 -0
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +144 -40
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts +18 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +420 -23
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts +3 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +10 -4
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +3 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +27 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -1
- package/dist/subagents/__tests__/tracker.test.js +837 -1048
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/tracker.d.ts +64 -60
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +809 -358
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +6 -7
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/mcp-comm.test.js +104 -3
- package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
- package/dist/team/__tests__/notice-ledger.test.d.ts +2 -0
- package/dist/team/__tests__/notice-ledger.test.d.ts.map +1 -0
- package/dist/team/__tests__/notice-ledger.test.js +131 -0
- package/dist/team/__tests__/notice-ledger.test.js.map +1 -0
- package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts +2 -0
- package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts.map +1 -0
- package/dist/team/__tests__/pane-teardown-residue-e2e.test.js +410 -0
- package/dist/team/__tests__/pane-teardown-residue-e2e.test.js.map +1 -0
- package/dist/team/__tests__/phase-controller.test.js +20 -0
- package/dist/team/__tests__/phase-controller.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +725 -59
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +222 -237
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +117 -2
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +887 -92
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts +2 -0
- package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts.map +1 -0
- package/dist/team/__tests__/tmux-source-authority-realtmux.test.js +149 -0
- package/dist/team/__tests__/tmux-source-authority-realtmux.test.js.map +1 -0
- package/dist/team/__tests__/tmux-test-fixture.d.ts +69 -0
- package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.js +242 -15
- package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.test.js +177 -1
- package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +45 -72
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/notice-ledger.d.ts +54 -0
- package/dist/team/notice-ledger.d.ts.map +1 -0
- package/dist/team/notice-ledger.js +259 -0
- package/dist/team/notice-ledger.js.map +1 -0
- package/dist/team/phase-controller.d.ts.map +1 -1
- package/dist/team/phase-controller.js +2 -0
- package/dist/team/phase-controller.js.map +1 -1
- package/dist/team/runtime.d.ts +3 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +314 -142
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +161 -91
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/monitor.d.ts +10 -0
- package/dist/team/state/monitor.d.ts.map +1 -1
- package/dist/team/state/monitor.js +13 -0
- package/dist/team/state/monitor.js.map +1 -1
- package/dist/team/state/types.d.ts +15 -0
- package/dist/team/state/types.d.ts.map +1 -1
- package/dist/team/state/types.js.map +1 -1
- package/dist/team/state-root.d.ts +12 -0
- package/dist/team/state-root.d.ts.map +1 -1
- package/dist/team/state-root.js +177 -0
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.d.ts +18 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +36 -0
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts +40 -4
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +513 -166
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +950 -2
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +1 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +35 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +232 -46
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/utils/__tests__/file-durability.test.js +58 -1
- package/dist/utils/__tests__/file-durability.test.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +21 -1
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/file-durability.d.ts +10 -0
- package/dist/utils/file-durability.d.ts.map +1 -1
- package/dist/utils/file-durability.js +34 -4
- package/dist/utils/file-durability.js.map +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +14 -6
- package/dist/utils/paths.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +49 -4
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js +60 -0
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
- package/dist/verification/__tests__/native-release-manifest.test.js +157 -4
- package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -1
- package/package.json +13 -5
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +81 -17
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -5
- package/plugins/oh-my-codex/skills/cancel/SKILL.md +30 -85
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +5 -5
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +15 -17
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +3 -2
- package/skills/autopilot/SKILL.md +13 -5
- package/skills/cancel/SKILL.md +30 -85
- package/skills/ralph/SKILL.md +5 -5
- package/skills/ralplan/SKILL.md +15 -17
- package/skills/ultragoal/SKILL.md +3 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +6091 -540
- package/src/scripts/__tests__/issue-3293-callsite-parity.test.ts +187 -0
- package/src/scripts/__tests__/issue-3293-cancel-transaction.test.ts +189 -0
- package/src/scripts/__tests__/issue-3293-hook-owned-cancel.test.ts +187 -0
- package/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts +591 -0
- package/src/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.ts +701 -0
- package/src/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.ts +211 -0
- package/src/scripts/__tests__/notify-state-io.test.ts +29 -0
- package/src/scripts/__tests__/notify-tmux-injection.test.ts +26 -1
- package/src/scripts/__tests__/postinstall.test.ts +23 -1
- package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +27 -8
- package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +61 -27
- package/src/scripts/__tests__/smoke-packed-install.test.ts +209 -18
- package/src/scripts/__tests__/verify-native-release-assets.test.ts +240 -0
- package/src/scripts/codex-native-hook.ts +3638 -475
- package/src/scripts/codex-native-pre-post.ts +43 -12
- package/src/scripts/generate-native-release-manifest.ts +83 -79
- package/src/scripts/notify-fallback-watcher.ts +38 -38
- package/src/scripts/notify-hook/state-io.ts +11 -0
- package/src/scripts/notify-hook/team-leader-nudge.ts +32 -4
- package/src/scripts/notify-hook/team-worker-stop.ts +23 -4
- package/src/scripts/notify-hook/team-worker.ts +43 -5
- package/src/scripts/notify-hook/tmux-injection.ts +5 -1
- package/src/scripts/notify-hook.ts +83 -82
- package/src/scripts/postinstall-advisory.ts +67 -0
- package/src/scripts/postinstall.ts +1 -1
- package/src/scripts/smoke-packed-install.ts +446 -59
- package/src/scripts/sync-plugin-mirror.ts +17 -1
- package/src/scripts/verify-native-release-assets.ts +39 -31
- package/templates/AGENTS.md +1 -0
|
@@ -1,20 +1,54 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { closeSync, existsSync, ftruncateSync, linkSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, writeSync } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { hostname } from 'node:os';
|
|
5
5
|
import { basename, dirname, join } from 'node:path';
|
|
6
6
|
import { AGENT_DEFINITIONS } from '../agents/definitions.js';
|
|
7
|
-
import { getBaseStateDir
|
|
8
|
-
import { canonicalizeOriginCwd } from '../leader/contract.js';
|
|
7
|
+
import { getBaseStateDir } from '../state/paths.js';
|
|
9
8
|
import { codexAgentsDir, projectCodexAgentsDir } from '../utils/paths.js';
|
|
10
9
|
export const SUBAGENT_TRACKING_SCHEMA_VERSION = 1;
|
|
11
10
|
export const DEFAULT_SUBAGENT_ACTIVE_WINDOW_MS = 120_000;
|
|
12
|
-
export const OMX_ADAPTED_PROVENANCE = 'omx_adapted';
|
|
13
11
|
export const NATIVE_SUBAGENT_PROVENANCE = 'native_subagent';
|
|
12
|
+
// Legacy descriptive provenance written by pre-authority releases. Tolerated as
|
|
13
|
+
// descriptive-only evidence: it never grants reopen authority and never blocks a
|
|
14
|
+
// newly attested valid direct child, but it is preserved so authority-consistency
|
|
15
|
+
// checks can distinguish recognized legacy data from unknown descriptive views.
|
|
16
|
+
export const DESCRIPTIVE_ADAPTED_PROVENANCE = 'adapted';
|
|
14
17
|
const KNOWN_TYPED_AGENT_ROLES = new Set(Object.keys(AGENT_DEFINITIONS).map((role) => role.toLowerCase()));
|
|
15
18
|
export function subagentTrackingPath(cwd) {
|
|
16
19
|
return join(getBaseStateDir(cwd), 'subagent-tracking.json');
|
|
17
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Authority-relevant projection of a tracker state. The generic descriptive
|
|
23
|
+
* writer is allowed to publish descriptive churn, but it must never roll back
|
|
24
|
+
* an authority decision that another process committed after the caller read
|
|
25
|
+
* its snapshot. Comparing this projection is the durable equivalent of a CAS
|
|
26
|
+
* on the authority surface without forcing every descriptive caller to hold
|
|
27
|
+
* the tracker lock across its own read/modify/write.
|
|
28
|
+
*/
|
|
29
|
+
function authorityRevisionOf(state) {
|
|
30
|
+
const sessions = Object.keys(state.sessions).sort().map((sessionId) => {
|
|
31
|
+
const session = state.sessions[sessionId];
|
|
32
|
+
const threads = Object.keys(session.threads).sort().map((threadId) => {
|
|
33
|
+
const thread = session.threads[threadId];
|
|
34
|
+
return [
|
|
35
|
+
threadId,
|
|
36
|
+
thread.kind,
|
|
37
|
+
thread.provenance_kind ?? '',
|
|
38
|
+
thread.direct_child_root_id ?? '',
|
|
39
|
+
thread.direct_child_parent_id ?? '',
|
|
40
|
+
thread.reopen_authority_revoked === true ? '1' : '',
|
|
41
|
+
thread.reopen_authority_conflict_reason ?? '',
|
|
42
|
+
thread.reopen_authority_conflict_at ?? '',
|
|
43
|
+
// Availability gates whether an eligible child is actually emitted as
|
|
44
|
+
// a reopen target, so it is part of the authority surface.
|
|
45
|
+
thread.status ?? '',
|
|
46
|
+
].join('\u0001');
|
|
47
|
+
});
|
|
48
|
+
return [sessionId, session.leader_thread_id ?? '', ...threads].join('\u0002');
|
|
49
|
+
});
|
|
50
|
+
return sessions.join('\u0003');
|
|
51
|
+
}
|
|
18
52
|
export function resolveInstalledRoleName(role, codexHomeOverride, projectRootOverride) {
|
|
19
53
|
const normalizedRole = role.trim().toLowerCase();
|
|
20
54
|
if (!normalizedRole)
|
|
@@ -41,7 +75,6 @@ export function createSubagentTrackingState() {
|
|
|
41
75
|
return {
|
|
42
76
|
schemaVersion: SUBAGENT_TRACKING_SCHEMA_VERSION,
|
|
43
77
|
sessions: {},
|
|
44
|
-
pending_role_intents: [],
|
|
45
78
|
};
|
|
46
79
|
}
|
|
47
80
|
function normalizeSubagentStatus(value) {
|
|
@@ -119,40 +152,6 @@ export function isTrustedSubagentThread(session, threadId) {
|
|
|
119
152
|
return false;
|
|
120
153
|
return session.threads[normalizedThreadId]?.kind === 'subagent';
|
|
121
154
|
}
|
|
122
|
-
function normalizePendingRoleIntent(value) {
|
|
123
|
-
if (!value || typeof value !== 'object')
|
|
124
|
-
return null;
|
|
125
|
-
const candidate = value;
|
|
126
|
-
const role = readOptionalTrimmedString(candidate.role);
|
|
127
|
-
const sessionId = readOptionalTrimmedString(candidate.session_id);
|
|
128
|
-
const parentThreadId = readOptionalTrimmedString(candidate.parent_thread_id);
|
|
129
|
-
const createdAt = readOptionalTrimmedString(candidate.created_at);
|
|
130
|
-
const expiresAt = readOptionalTrimmedString(candidate.expires_at);
|
|
131
|
-
if (!role || !sessionId || !parentThreadId || !Object.hasOwn(candidate, 'correlation_token') || !createdAt || !expiresAt)
|
|
132
|
-
return null;
|
|
133
|
-
if (!Number.isFinite(Date.parse(createdAt)) || !Number.isFinite(Date.parse(expiresAt)))
|
|
134
|
-
return null;
|
|
135
|
-
const bindingState = candidate.binding_state === 'bound' ? 'bound' : undefined;
|
|
136
|
-
const boundAt = readOptionalTrimmedString(candidate.bound_at);
|
|
137
|
-
const hasValidBoundAt = Boolean(boundAt && Number.isFinite(Date.parse(boundAt)));
|
|
138
|
-
const originCwd = readOptionalTrimmedString(candidate.origin_cwd);
|
|
139
|
-
return {
|
|
140
|
-
role,
|
|
141
|
-
session_id: sessionId,
|
|
142
|
-
parent_thread_id: parentThreadId,
|
|
143
|
-
// Bound journals are durable security records. Retain malformed credentials verbatim so
|
|
144
|
-
// completion can reject them rather than silently converting them into claimant-less data.
|
|
145
|
-
correlation_token: candidate.correlation_token,
|
|
146
|
-
created_at: createdAt,
|
|
147
|
-
expires_at: expiresAt,
|
|
148
|
-
...(bindingState ? { binding_state: bindingState } : {}),
|
|
149
|
-
...(bindingState && Object.hasOwn(candidate, 'binding_claimant_token')
|
|
150
|
-
? { binding_claimant_token: candidate.binding_claimant_token }
|
|
151
|
-
: {}),
|
|
152
|
-
...(bindingState && hasValidBoundAt ? { bound_at: boundAt } : {}),
|
|
153
|
-
...(originCwd ? { origin_cwd: originCwd } : {}),
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
155
|
export function normalizeSubagentTrackingState(input) {
|
|
157
156
|
const base = createSubagentTrackingState();
|
|
158
157
|
if (!input || typeof input !== 'object')
|
|
@@ -190,9 +189,11 @@ export function normalizeSubagentTrackingState(input) {
|
|
|
190
189
|
turn_count: typeof candidate.turn_count === 'number' && Number.isFinite(candidate.turn_count) && candidate.turn_count > 0 ? candidate.turn_count : 1,
|
|
191
190
|
...(typeof candidate.mode === 'string' && candidate.mode.trim().length > 0 ? { mode: candidate.mode } : {}),
|
|
192
191
|
...(typeof candidate.role === 'string' && candidate.role.trim().length > 0 ? { role: candidate.role.trim() } : {}),
|
|
193
|
-
...(
|
|
194
|
-
? { provenance_kind:
|
|
195
|
-
:
|
|
192
|
+
...(candidate.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
|
|
193
|
+
? { provenance_kind: NATIVE_SUBAGENT_PROVENANCE }
|
|
194
|
+
: candidate.provenance_kind === DESCRIPTIVE_ADAPTED_PROVENANCE
|
|
195
|
+
? { provenance_kind: DESCRIPTIVE_ADAPTED_PROVENANCE }
|
|
196
|
+
: {}),
|
|
196
197
|
...(typeof candidate.lane_id === 'string' && candidate.lane_id.trim().length > 0 ? { lane_id: candidate.lane_id.trim() } : {}),
|
|
197
198
|
...(typeof candidate.scope === 'string' && candidate.scope.trim().length > 0 ? { scope: candidate.scope.trim() } : {}),
|
|
198
199
|
...(typeof candidate.agent_nickname === 'string' && candidate.agent_nickname.trim().length > 0
|
|
@@ -217,6 +218,19 @@ export function normalizeSubagentTrackingState(input) {
|
|
|
217
218
|
...(typeof candidate.resume_failure_reason === 'string' && candidate.resume_failure_reason.trim().length > 0
|
|
218
219
|
? { resume_failure_reason: candidate.resume_failure_reason.trim() }
|
|
219
220
|
: {}),
|
|
221
|
+
...(typeof candidate.direct_child_root_id === 'string' && candidate.direct_child_root_id.trim().length > 0
|
|
222
|
+
? { direct_child_root_id: candidate.direct_child_root_id.trim() }
|
|
223
|
+
: {}),
|
|
224
|
+
...(typeof candidate.direct_child_parent_id === 'string' && candidate.direct_child_parent_id.trim().length > 0
|
|
225
|
+
? { direct_child_parent_id: candidate.direct_child_parent_id.trim() }
|
|
226
|
+
: {}),
|
|
227
|
+
...(candidate.reopen_authority_revoked === true ? { reopen_authority_revoked: true } : {}),
|
|
228
|
+
...(typeof candidate.reopen_authority_conflict_reason === 'string' && candidate.reopen_authority_conflict_reason.trim()
|
|
229
|
+
? { reopen_authority_conflict_reason: candidate.reopen_authority_conflict_reason.trim() }
|
|
230
|
+
: {}),
|
|
231
|
+
...(typeof candidate.reopen_authority_conflict_at === 'string' && candidate.reopen_authority_conflict_at.trim()
|
|
232
|
+
? { reopen_authority_conflict_at: candidate.reopen_authority_conflict_at.trim() }
|
|
233
|
+
: {}),
|
|
220
234
|
};
|
|
221
235
|
}
|
|
222
236
|
const sessionCandidate = rawSession;
|
|
@@ -226,20 +240,95 @@ export function normalizeSubagentTrackingState(input) {
|
|
|
226
240
|
: new Date(0).toISOString();
|
|
227
241
|
sessions[sessionId] = {
|
|
228
242
|
session_id: sessionId,
|
|
229
|
-
leader_thread_id: leaderThreadId,
|
|
243
|
+
...(leaderThreadId ? { leader_thread_id: leaderThreadId } : {}),
|
|
230
244
|
updated_at: updatedAt,
|
|
231
245
|
threads,
|
|
232
246
|
};
|
|
233
247
|
}
|
|
234
|
-
const pendingRoleIntents = Array.isArray(parsed.pending_role_intents)
|
|
235
|
-
? parsed.pending_role_intents.map((intent) => normalizePendingRoleIntent(intent)).filter((intent) => intent !== null)
|
|
236
|
-
: [];
|
|
237
248
|
return {
|
|
238
249
|
schemaVersion: SUBAGENT_TRACKING_SCHEMA_VERSION,
|
|
239
250
|
sessions,
|
|
240
|
-
pending_role_intents: pendingRoleIntents,
|
|
241
251
|
};
|
|
242
252
|
}
|
|
253
|
+
function parseStrictSubagentTrackingState(raw) {
|
|
254
|
+
let parsed;
|
|
255
|
+
try {
|
|
256
|
+
parsed = JSON.parse(raw);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
if (!parsed || typeof parsed !== 'object')
|
|
262
|
+
return null;
|
|
263
|
+
const candidate = parsed;
|
|
264
|
+
if (candidate.schemaVersion !== SUBAGENT_TRACKING_SCHEMA_VERSION || !candidate.sessions || typeof candidate.sessions !== 'object' || Array.isArray(candidate.sessions))
|
|
265
|
+
return null;
|
|
266
|
+
const normalizedSessionIds = new Set();
|
|
267
|
+
for (const [sessionId, sessionValue] of Object.entries(candidate.sessions)) {
|
|
268
|
+
if (!sessionId || sessionId.trim() !== sessionId || normalizedSessionIds.has(sessionId.trim()))
|
|
269
|
+
return null;
|
|
270
|
+
normalizedSessionIds.add(sessionId.trim());
|
|
271
|
+
if (!sessionValue || typeof sessionValue !== 'object' || Array.isArray(sessionValue))
|
|
272
|
+
return null;
|
|
273
|
+
const session = sessionValue;
|
|
274
|
+
if (session.session_id !== sessionId || typeof session.updated_at !== 'string' || !session.updated_at.trim())
|
|
275
|
+
return null;
|
|
276
|
+
if (!session.threads || typeof session.threads !== 'object' || Array.isArray(session.threads))
|
|
277
|
+
return null;
|
|
278
|
+
if (session.leader_thread_id !== undefined && (typeof session.leader_thread_id !== 'string' || !session.leader_thread_id.trim() || session.leader_thread_id.trim() !== session.leader_thread_id))
|
|
279
|
+
return null;
|
|
280
|
+
const normalizedThreadIds = new Set();
|
|
281
|
+
for (const [threadId, threadValue] of Object.entries(session.threads)) {
|
|
282
|
+
if (!threadId || threadId.trim() !== threadId || normalizedThreadIds.has(threadId.trim()))
|
|
283
|
+
return null;
|
|
284
|
+
normalizedThreadIds.add(threadId.trim());
|
|
285
|
+
if (!threadValue || typeof threadValue !== 'object' || Array.isArray(threadValue))
|
|
286
|
+
return null;
|
|
287
|
+
const thread = threadValue;
|
|
288
|
+
if (thread.thread_id !== threadId || (thread.kind !== 'leader' && thread.kind !== 'subagent'))
|
|
289
|
+
return null;
|
|
290
|
+
if (typeof thread.first_seen_at !== 'string' || !thread.first_seen_at.trim())
|
|
291
|
+
return null;
|
|
292
|
+
if (typeof thread.last_seen_at !== 'string' || !thread.last_seen_at.trim())
|
|
293
|
+
return null;
|
|
294
|
+
if (typeof thread.turn_count !== 'number' || !Number.isFinite(thread.turn_count) || thread.turn_count < 1)
|
|
295
|
+
return null;
|
|
296
|
+
const hasAuthorityEvidence = thread.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
|
|
297
|
+
|| thread.direct_child_root_id !== undefined
|
|
298
|
+
|| thread.direct_child_parent_id !== undefined
|
|
299
|
+
|| thread.reopen_authority_revoked !== undefined
|
|
300
|
+
|| thread.reopen_authority_conflict_reason !== undefined
|
|
301
|
+
|| thread.reopen_authority_conflict_at !== undefined;
|
|
302
|
+
if (hasAuthorityEvidence && thread.status !== undefined && normalizeSubagentStatus(thread.status) === undefined)
|
|
303
|
+
return null;
|
|
304
|
+
if (hasAuthorityEvidence && thread.provenance_kind !== undefined && thread.provenance_kind !== NATIVE_SUBAGENT_PROVENANCE)
|
|
305
|
+
return null;
|
|
306
|
+
for (const field of ['direct_child_root_id', 'direct_child_parent_id', 'reopen_authority_conflict_reason', 'reopen_authority_conflict_at']) {
|
|
307
|
+
if (thread[field] !== undefined && (typeof thread[field] !== 'string' || !thread[field].trim() || thread[field].trim() !== thread[field]))
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
if (thread.reopen_authority_revoked !== undefined && typeof thread.reopen_authority_revoked !== 'boolean')
|
|
311
|
+
return null;
|
|
312
|
+
if ((thread.direct_child_root_id === undefined) !== (thread.direct_child_parent_id === undefined))
|
|
313
|
+
return null;
|
|
314
|
+
if ((thread.reopen_authority_conflict_reason !== undefined || thread.reopen_authority_conflict_at !== undefined) && thread.reopen_authority_revoked !== true)
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return normalizeSubagentTrackingState(parsed);
|
|
319
|
+
}
|
|
320
|
+
function readSubagentTrackingStateStrictSync(cwd) {
|
|
321
|
+
const path = subagentTrackingPath(cwd);
|
|
322
|
+
if (!existsSync(path))
|
|
323
|
+
return { ok: true, state: createSubagentTrackingState() };
|
|
324
|
+
try {
|
|
325
|
+
const state = parseStrictSubagentTrackingState(readFileSync(path, 'utf-8'));
|
|
326
|
+
return state ? { ok: true, state } : { ok: false };
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return { ok: false };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
243
332
|
function atomicTrackingTempPath(path) {
|
|
244
333
|
return `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
245
334
|
}
|
|
@@ -579,16 +668,12 @@ export function withCrossProcessFileLockSync(resourcePath, operation, options =
|
|
|
579
668
|
releaseCrossProcessFileLock(lockPath, token);
|
|
580
669
|
}
|
|
581
670
|
}
|
|
582
|
-
function
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
catch {
|
|
590
|
-
return createSubagentTrackingState();
|
|
591
|
-
}
|
|
671
|
+
function threadIsTrackedAsSubagent(state, threadId) {
|
|
672
|
+
const id = threadId.trim();
|
|
673
|
+
return Boolean(id) && Object.values(state.sessions).some((session) => isTrustedSubagentThread(session, id));
|
|
674
|
+
}
|
|
675
|
+
export function hasLeaderSubagentCollision(state, leaderThreadId) {
|
|
676
|
+
return threadIsTrackedAsSubagent(state, leaderThreadId);
|
|
592
677
|
}
|
|
593
678
|
// Strict reads preserve fail-closed behavior for security-sensitive tracker decisions.
|
|
594
679
|
export async function readSubagentTrackingStateStrict(cwd) {
|
|
@@ -602,12 +687,8 @@ export async function readSubagentTrackingStateStrict(cwd) {
|
|
|
602
687
|
return { ok: true, state: createSubagentTrackingState() };
|
|
603
688
|
return { ok: false };
|
|
604
689
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
catch {
|
|
609
|
-
return { ok: false };
|
|
610
|
-
}
|
|
690
|
+
const state = parseStrictSubagentTrackingState(raw);
|
|
691
|
+
return state ? { ok: true, state } : { ok: false };
|
|
611
692
|
}
|
|
612
693
|
function writeSubagentTrackingStateSync(cwd, state, publish) {
|
|
613
694
|
const normalized = normalizeSubagentTrackingState(state);
|
|
@@ -635,300 +716,61 @@ export async function readSubagentTrackingState(cwd) {
|
|
|
635
716
|
}
|
|
636
717
|
}
|
|
637
718
|
export async function writeSubagentTrackingState(cwd, state) {
|
|
638
|
-
const normalized = normalizeSubagentTrackingState(state);
|
|
639
719
|
const path = subagentTrackingPath(cwd);
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const
|
|
653
|
-
return
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
? canonicalizeOriginCwd(intent.origin_cwd) === canonicalOrigin
|
|
659
|
-
: isCwdPartitionedStateRoot);
|
|
660
|
-
const shouldPruneExpired = (intent) => (isOwn(intent)
|
|
661
|
-
&& intent.binding_state !== 'bound'
|
|
662
|
-
&& isExpiredPendingRoleIntent(intent, nowMs));
|
|
663
|
-
return { isOwn, shouldPruneExpired };
|
|
664
|
-
}
|
|
665
|
-
export function isRoleIntentOwnedByCwd(cwd, intent) {
|
|
666
|
-
return pendingRoleIntentPredicates(cwd, canonicalizeOriginCwd(cwd), Date.now()).isOwn(intent);
|
|
667
|
-
}
|
|
668
|
-
function sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken) {
|
|
669
|
-
return (intent.session_id === sessionId
|
|
670
|
-
&& intent.parent_thread_id === parentThreadId
|
|
671
|
-
&& (correlationToken === undefined || intent.correlation_token === correlationToken));
|
|
672
|
-
}
|
|
673
|
-
export function isCanonicalCorrelationToken(value) {
|
|
674
|
-
return typeof value === 'string' && /^[0-9a-f]{32}$/.test(value);
|
|
675
|
-
}
|
|
676
|
-
export function isCanonicalClaimantToken(value) {
|
|
677
|
-
return typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
|
|
678
|
-
}
|
|
679
|
-
function hasOwnBoundLogicalIntent(all, isOwn, sessionId, parentThreadId, correlationToken) {
|
|
680
|
-
return all.some((intent) => (isOwn(intent)
|
|
681
|
-
&& intent.binding_state === 'bound'
|
|
682
|
-
&& sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken)));
|
|
683
|
-
}
|
|
684
|
-
function selectDominantRoleIntent(candidates, canonicalOrigin) {
|
|
685
|
-
return [...candidates].sort((left, right) => {
|
|
686
|
-
const leftIsBound = left.binding_state === 'bound';
|
|
687
|
-
const rightIsBound = right.binding_state === 'bound';
|
|
688
|
-
if (leftIsBound !== rightIsBound)
|
|
689
|
-
return leftIsBound ? -1 : 1;
|
|
690
|
-
const leftIsExactOrigin = left.origin_cwd !== undefined
|
|
691
|
-
&& canonicalizeOriginCwd(left.origin_cwd) === canonicalOrigin;
|
|
692
|
-
const rightIsExactOrigin = right.origin_cwd !== undefined
|
|
693
|
-
&& canonicalizeOriginCwd(right.origin_cwd) === canonicalOrigin;
|
|
694
|
-
if (leftIsExactOrigin !== rightIsExactOrigin)
|
|
695
|
-
return leftIsExactOrigin ? -1 : 1;
|
|
696
|
-
const leftStableKey = [
|
|
697
|
-
left.role,
|
|
698
|
-
left.correlation_token,
|
|
699
|
-
left.created_at,
|
|
700
|
-
left.expires_at,
|
|
701
|
-
left.binding_state ?? '',
|
|
702
|
-
left.binding_claimant_token ?? '',
|
|
703
|
-
left.bound_at ?? '',
|
|
704
|
-
left.origin_cwd ?? '',
|
|
705
|
-
].join('\u0000');
|
|
706
|
-
const rightStableKey = [
|
|
707
|
-
right.role,
|
|
708
|
-
right.correlation_token,
|
|
709
|
-
right.created_at,
|
|
710
|
-
right.expires_at,
|
|
711
|
-
right.binding_state ?? '',
|
|
712
|
-
right.binding_claimant_token ?? '',
|
|
713
|
-
right.bound_at ?? '',
|
|
714
|
-
right.origin_cwd ?? '',
|
|
715
|
-
].join('\u0000');
|
|
716
|
-
return leftStableKey.localeCompare(rightStableKey);
|
|
717
|
-
})[0] ?? null;
|
|
718
|
-
}
|
|
719
|
-
export function recordPendingRoleIntent(cwd, input) {
|
|
720
|
-
const role = resolveInstalledRoleName(input.role);
|
|
721
|
-
if (!role)
|
|
722
|
-
return { ok: false, reason: 'unknown_role' };
|
|
723
|
-
const correlationToken = input.correlationToken;
|
|
724
|
-
if (!isCanonicalCorrelationToken(correlationToken)) {
|
|
725
|
-
return { ok: false, reason: 'invalid_correlation_token' };
|
|
726
|
-
}
|
|
727
|
-
const nowMs = normalizeNowMs(input.nowMs);
|
|
728
|
-
const sessionId = input.sessionId.trim();
|
|
729
|
-
const parentThreadId = input.parentThreadId.trim();
|
|
730
|
-
const canonicalOrigin = canonicalizeOriginCwd(cwd);
|
|
731
|
-
if (canonicalOrigin === null)
|
|
732
|
-
return { ok: false, reason: 'invalid_origin' };
|
|
733
|
-
const { isOwn, shouldPruneExpired } = pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs);
|
|
734
|
-
return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
|
|
735
|
-
const state = readSubagentTrackingStateSync(cwd);
|
|
736
|
-
const all = state.pending_role_intents;
|
|
737
|
-
if (all.some((intent) => (isOwn(intent)
|
|
738
|
-
&& intent.session_id === sessionId
|
|
739
|
-
&& intent.parent_thread_id === parentThreadId
|
|
740
|
-
&& (intent.binding_state === 'bound' || !isExpiredPendingRoleIntent(intent, nowMs))))) {
|
|
741
|
-
return { ok: false, reason: 'single_flight_conflict' };
|
|
742
|
-
}
|
|
743
|
-
const ttlMs = typeof input.ttlMs === 'number' && Number.isFinite(input.ttlMs) ? input.ttlMs : 10 * 60_000;
|
|
744
|
-
const intent = {
|
|
745
|
-
role,
|
|
746
|
-
session_id: sessionId,
|
|
747
|
-
parent_thread_id: parentThreadId,
|
|
748
|
-
correlation_token: correlationToken,
|
|
749
|
-
created_at: new Date(nowMs).toISOString(),
|
|
750
|
-
expires_at: new Date(nowMs + ttlMs).toISOString(),
|
|
751
|
-
// Store the canonical origin workspace so it authenticates future bind/complete/recover.
|
|
752
|
-
...(canonicalOrigin ? { origin_cwd: canonicalOrigin } : {}),
|
|
753
|
-
};
|
|
754
|
-
state.pending_role_intents = [...all.filter((candidate) => !shouldPruneExpired(candidate)), intent];
|
|
755
|
-
context.assertOwnership();
|
|
756
|
-
writeSubagentTrackingStateSync(cwd, state, context.publish);
|
|
757
|
-
return { ok: true, intent };
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
export function bindPendingRoleIntentUnderLock(cwd, input, bind) {
|
|
761
|
-
const nowMs = normalizeNowMs(input.nowMs);
|
|
762
|
-
const sessionId = input.sessionId.trim();
|
|
763
|
-
const parentThreadId = input.parentThreadId.trim();
|
|
764
|
-
const correlationToken = input.correlationToken;
|
|
765
|
-
if (!isCanonicalCorrelationToken(correlationToken))
|
|
766
|
-
return null;
|
|
767
|
-
// Fail-closed origin authentication: establish the caller's canonical origin workspace up
|
|
768
|
-
// front. A malformed/unavailable origin can never disclose role/claimant, run the bind
|
|
769
|
-
// callback, mutate pending->bound, or acquire the lock.
|
|
770
|
-
const canonicalOrigin = canonicalizeOriginCwd(cwd);
|
|
771
|
-
if (canonicalOrigin === null)
|
|
772
|
-
return null;
|
|
773
|
-
return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
|
|
774
|
-
const state = readSubagentTrackingStateSync(cwd);
|
|
775
|
-
const all = state.pending_role_intents;
|
|
776
|
-
const { isOwn, shouldPruneExpired } = pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs);
|
|
777
|
-
const matchedIntent = selectDominantRoleIntent(all.filter((intent) => (isOwn(intent)
|
|
778
|
-
&& correlationToken !== undefined
|
|
779
|
-
&& sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken)
|
|
780
|
-
&& (intent.binding_state === 'bound' || !isExpiredPendingRoleIntent(intent, nowMs)))), canonicalOrigin);
|
|
781
|
-
if (!matchedIntent) {
|
|
782
|
-
const retained = all.filter((intent) => !shouldPruneExpired(intent));
|
|
783
|
-
if (retained.length !== all.length) {
|
|
784
|
-
state.pending_role_intents = retained;
|
|
785
|
-
context.assertOwnership();
|
|
786
|
-
writeSubagentTrackingStateSync(cwd, state, context.publish);
|
|
787
|
-
}
|
|
788
|
-
return null;
|
|
789
|
-
}
|
|
790
|
-
const adaptedIntent = {
|
|
791
|
-
role: matchedIntent.role,
|
|
792
|
-
provenanceKind: OMX_ADAPTED_PROVENANCE,
|
|
793
|
-
};
|
|
794
|
-
if (matchedIntent.binding_state === 'bound') {
|
|
795
|
-
const next = all
|
|
796
|
-
.filter((intent) => (intent.binding_state === 'bound'
|
|
797
|
-
|| !(isOwn(intent) && sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken))))
|
|
798
|
-
.map((intent) => (intent === matchedIntent && !intent.origin_cwd
|
|
799
|
-
? { ...intent, origin_cwd: canonicalOrigin }
|
|
800
|
-
: intent));
|
|
801
|
-
if (next.length !== all.length || !matchedIntent.origin_cwd) {
|
|
802
|
-
state.pending_role_intents = next;
|
|
803
|
-
context.assertOwnership();
|
|
804
|
-
writeSubagentTrackingStateSync(cwd, state, context.publish);
|
|
720
|
+
// Fail closed on every side of the write. The proposed state must survive
|
|
721
|
+
// strict authority validation (tolerant inputs must not launder malformed
|
|
722
|
+
// authority into a valid-looking attestation); existing on-disk bytes must
|
|
723
|
+
// not be overwritten when they fail strict parsing; and the publication must
|
|
724
|
+
// not roll back an authority decision committed after the caller read its
|
|
725
|
+
// snapshot. The whole read/validate/compare/publish transaction is
|
|
726
|
+
// serialized under the tracker lock.
|
|
727
|
+
const proposedRaw = JSON.stringify(state);
|
|
728
|
+
if (!parseStrictSubagentTrackingState(proposedRaw))
|
|
729
|
+
throw new Error('Malformed subagent tracker authority state');
|
|
730
|
+
const normalized = normalizeSubagentTrackingState(state);
|
|
731
|
+
const contents = `${JSON.stringify(normalized, null, 2)}\n`;
|
|
732
|
+
const proposedAuthorityRevision = authorityRevisionOf(normalized);
|
|
733
|
+
return withCrossProcessFileLockSync(path, (lock) => {
|
|
734
|
+
if (existsSync(path)) {
|
|
735
|
+
let raw;
|
|
736
|
+
try {
|
|
737
|
+
raw = readFileSync(path, 'utf-8');
|
|
805
738
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
claimantToken: undefined,
|
|
809
|
-
alreadyBound: true,
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
const claimantToken = randomUUID();
|
|
813
|
-
const boundState = bind(state, adaptedIntent);
|
|
814
|
-
boundState.pending_role_intents = all
|
|
815
|
-
.filter((intent) => !shouldPruneExpired(intent))
|
|
816
|
-
.filter((intent) => (intent === matchedIntent
|
|
817
|
-
|| !(isOwn(intent) && sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken))))
|
|
818
|
-
.map((intent) => (intent === matchedIntent
|
|
819
|
-
? {
|
|
820
|
-
...matchedIntent,
|
|
821
|
-
binding_state: 'bound',
|
|
822
|
-
binding_claimant_token: claimantToken,
|
|
823
|
-
bound_at: new Date(nowMs).toISOString(),
|
|
824
|
-
origin_cwd: matchedIntent.origin_cwd ?? canonicalOrigin,
|
|
739
|
+
catch {
|
|
740
|
+
throw new Error('Malformed subagent tracker authority state');
|
|
825
741
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
return null;
|
|
839
|
-
const canonicalOrigin = canonicalizeOriginCwd(cwd);
|
|
840
|
-
if (canonicalOrigin === null)
|
|
841
|
-
return null;
|
|
842
|
-
return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
|
|
843
|
-
const state = readSubagentTrackingStateSync(cwd);
|
|
844
|
-
const all = state.pending_role_intents;
|
|
845
|
-
const { isOwn, shouldPruneExpired } = pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs);
|
|
846
|
-
if (hasOwnBoundLogicalIntent(all, isOwn, sessionId, parentThreadId, correlationToken))
|
|
847
|
-
return null;
|
|
848
|
-
const consumed = selectDominantRoleIntent(all.filter((intent) => (isOwn(intent)
|
|
849
|
-
&& intent.binding_state !== 'bound'
|
|
850
|
-
&& !isExpiredPendingRoleIntent(intent, nowMs)
|
|
851
|
-
&& correlationToken !== undefined
|
|
852
|
-
&& sameLogicalRoleIntent(intent, sessionId, parentThreadId, correlationToken))), canonicalOrigin);
|
|
853
|
-
if (!consumed) {
|
|
854
|
-
const retained = all.filter((intent) => !shouldPruneExpired(intent));
|
|
855
|
-
if (retained.length !== all.length) {
|
|
856
|
-
state.pending_role_intents = retained;
|
|
857
|
-
context.assertOwnership();
|
|
858
|
-
writeSubagentTrackingStateSync(cwd, state, context.publish);
|
|
742
|
+
const current = parseStrictSubagentTrackingState(raw);
|
|
743
|
+
if (!current)
|
|
744
|
+
throw new Error('Malformed subagent tracker authority state');
|
|
745
|
+
// The generic writer is descriptive-only: it may publish descriptive
|
|
746
|
+
// churn, but it must never CHANGE the authority surface in any
|
|
747
|
+
// direction. Granting, revoking, or rolling back authority is reserved
|
|
748
|
+
// for the lock-owning authority transactions. Requiring the proposed
|
|
749
|
+
// authority projection to equal the current on-disk projection makes a
|
|
750
|
+
// stale snapshot, a fresh-read escalation, and a concurrent interleaving
|
|
751
|
+
// all fail closed with one rule.
|
|
752
|
+
if (authorityRevisionOf(current) !== proposedAuthorityRevision) {
|
|
753
|
+
throw new Error('Stale subagent tracker authority state');
|
|
859
754
|
}
|
|
860
|
-
return null;
|
|
861
755
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const correlationToken = input.correlationToken;
|
|
876
|
-
const claimantToken = input.claimantToken;
|
|
877
|
-
const canonicalOrigin = canonicalizeOriginCwd(cwd);
|
|
878
|
-
if (canonicalOrigin === null)
|
|
879
|
-
return 'not_found';
|
|
880
|
-
return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
|
|
881
|
-
const state = readSubagentTrackingStateSync(cwd);
|
|
882
|
-
const all = state.pending_role_intents;
|
|
883
|
-
const { isOwn, shouldPruneExpired } = pendingRoleIntentPredicates(cwd, canonicalOrigin, nowMs);
|
|
884
|
-
// Select the owned bound scope before authenticating it. Credentials are not a lookup key:
|
|
885
|
-
// otherwise an invalid dominant duplicate could be bypassed by a lower valid duplicate.
|
|
886
|
-
const boundIntent = selectDominantRoleIntent(all.filter((intent) => (isOwn(intent)
|
|
887
|
-
&& intent.binding_state === 'bound'
|
|
888
|
-
&& sameLogicalRoleIntent(intent, sessionId, parentThreadId))), canonicalOrigin);
|
|
889
|
-
if (!boundIntent) {
|
|
890
|
-
const retained = all.filter((intent) => !shouldPruneExpired(intent));
|
|
891
|
-
if (retained.length !== all.length) {
|
|
892
|
-
state.pending_role_intents = retained;
|
|
893
|
-
context.assertOwnership();
|
|
894
|
-
writeSubagentTrackingStateSync(cwd, state, context.publish);
|
|
895
|
-
}
|
|
896
|
-
return 'not_found';
|
|
756
|
+
else if (proposedAuthorityRevision !== authorityRevisionOf(createSubagentTrackingState())) {
|
|
757
|
+
// Creating the tracker from nothing must not mint reopen authority.
|
|
758
|
+
// `leader_thread_id` is deliberately NOT included: it is ordinary
|
|
759
|
+
// descriptive lifecycle data that legitimate first writers set, and it
|
|
760
|
+
// never by itself makes a thread an eligible reopen target.
|
|
761
|
+
const mintsAuthority = Object.values(normalized.sessions).some((session) => Object.values(session.threads).some((thread) => thread.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
|
|
762
|
+
|| thread.direct_child_root_id !== undefined
|
|
763
|
+
|| thread.direct_child_parent_id !== undefined
|
|
764
|
+
|| thread.reopen_authority_revoked !== undefined
|
|
765
|
+
|| thread.reopen_authority_conflict_reason !== undefined
|
|
766
|
+
|| thread.reopen_authority_conflict_at !== undefined));
|
|
767
|
+
if (mintsAuthority)
|
|
768
|
+
throw new Error('Stale subagent tracker authority state');
|
|
897
769
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|| (hasClaimant && (!isCanonicalClaimantToken(boundIntent.binding_claimant_token)
|
|
903
|
-
|| !isCanonicalClaimantToken(claimantToken)
|
|
904
|
-
|| boundIntent.binding_claimant_token !== claimantToken)))
|
|
905
|
-
return 'claimant_mismatch';
|
|
906
|
-
state.pending_role_intents = all
|
|
907
|
-
.filter((intent) => !shouldPruneExpired(intent))
|
|
908
|
-
.filter((intent) => !(isOwn(intent) && sameLogicalRoleIntent(intent, sessionId, parentThreadId, boundIntent.correlation_token)));
|
|
909
|
-
context.assertOwnership();
|
|
910
|
-
writeSubagentTrackingStateSync(cwd, state, context.publish);
|
|
911
|
-
return 'completed';
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
export function listBoundAdaptedRoleIntents(cwd, _nowMs, ownedDominant = false) {
|
|
915
|
-
const allBound = readSubagentTrackingStateSync(cwd).pending_role_intents.filter((intent) => intent.binding_state === 'bound');
|
|
916
|
-
if (!ownedDominant)
|
|
917
|
-
return allBound;
|
|
918
|
-
const canonicalOrigin = canonicalizeOriginCwd(cwd);
|
|
919
|
-
if (canonicalOrigin === null)
|
|
920
|
-
return [];
|
|
921
|
-
const { isOwn } = pendingRoleIntentPredicates(cwd, canonicalOrigin, Date.now());
|
|
922
|
-
const scopes = new Map();
|
|
923
|
-
for (const intent of allBound) {
|
|
924
|
-
if (!isOwn(intent))
|
|
925
|
-
continue;
|
|
926
|
-
const key = `${intent.session_id}\u0000${intent.parent_thread_id}`;
|
|
927
|
-
scopes.set(key, [...(scopes.get(key) ?? []), intent]);
|
|
928
|
-
}
|
|
929
|
-
return [...scopes.values()].flatMap((candidates) => {
|
|
930
|
-
const dominant = selectDominantRoleIntent(candidates, canonicalOrigin);
|
|
931
|
-
return dominant ? [dominant] : [];
|
|
770
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
771
|
+
lock.assertOwnership();
|
|
772
|
+
lock.publish(contents);
|
|
773
|
+
return path;
|
|
932
774
|
});
|
|
933
775
|
}
|
|
934
776
|
export function recordSubagentTurn(state, input) {
|
|
@@ -996,11 +838,13 @@ export function recordSubagentTurn(state, input) {
|
|
|
996
838
|
: preservedCompletion),
|
|
997
839
|
...(input.mode?.trim() ? { mode: input.mode.trim() } : existingThread?.mode ? { mode: existingThread.mode } : {}),
|
|
998
840
|
...(input.role?.trim() ? { role: input.role.trim() } : existingThread?.role ? { role: existingThread.role } : {}),
|
|
999
|
-
...(input.provenanceKind
|
|
1000
|
-
? { provenance_kind:
|
|
1001
|
-
: existingThread?.provenance_kind
|
|
1002
|
-
? { provenance_kind:
|
|
1003
|
-
:
|
|
841
|
+
...(input.provenanceKind === NATIVE_SUBAGENT_PROVENANCE
|
|
842
|
+
? { provenance_kind: NATIVE_SUBAGENT_PROVENANCE }
|
|
843
|
+
: existingThread?.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
|
|
844
|
+
? { provenance_kind: NATIVE_SUBAGENT_PROVENANCE }
|
|
845
|
+
: existingThread?.provenance_kind === DESCRIPTIVE_ADAPTED_PROVENANCE
|
|
846
|
+
? { provenance_kind: DESCRIPTIVE_ADAPTED_PROVENANCE }
|
|
847
|
+
: {}),
|
|
1004
848
|
...(input.laneId?.trim() ? { lane_id: input.laneId.trim() } : existingThread?.lane_id ? { lane_id: existingThread.lane_id } : {}),
|
|
1005
849
|
...(input.scope?.trim() ? { scope: input.scope.trim() } : existingThread?.scope ? { scope: existingThread.scope } : {}),
|
|
1006
850
|
...(input.agentNickname?.trim()
|
|
@@ -1034,6 +878,11 @@ export function recordSubagentTurn(state, input) {
|
|
|
1034
878
|
: existingThread?.resume_failure_reason
|
|
1035
879
|
? { resume_failure_reason: existingThread.resume_failure_reason }
|
|
1036
880
|
: {}),
|
|
881
|
+
...(existingThread?.direct_child_root_id ? { direct_child_root_id: existingThread.direct_child_root_id } : {}),
|
|
882
|
+
...(existingThread?.direct_child_parent_id ? { direct_child_parent_id: existingThread.direct_child_parent_id } : {}),
|
|
883
|
+
...(existingThread?.reopen_authority_revoked ? { reopen_authority_revoked: true } : {}),
|
|
884
|
+
...(existingThread?.reopen_authority_conflict_reason ? { reopen_authority_conflict_reason: existingThread.reopen_authority_conflict_reason } : {}),
|
|
885
|
+
...(existingThread?.reopen_authority_conflict_at ? { reopen_authority_conflict_at: existingThread.reopen_authority_conflict_at } : {}),
|
|
1037
886
|
};
|
|
1038
887
|
const threads = {
|
|
1039
888
|
...existingSession.threads,
|
|
@@ -1055,13 +904,615 @@ export function recordSubagentTurn(state, input) {
|
|
|
1055
904
|
}
|
|
1056
905
|
export async function recordSubagentTurnForSession(cwd, input) {
|
|
1057
906
|
return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
|
|
1058
|
-
const
|
|
1059
|
-
|
|
907
|
+
const strict = readSubagentTrackingStateStrictSync(cwd);
|
|
908
|
+
if (!strict.ok)
|
|
909
|
+
throw new Error('Malformed subagent tracker authority state');
|
|
910
|
+
const next = recordSubagentTurn(strict.state, input);
|
|
1060
911
|
context.assertOwnership();
|
|
1061
912
|
writeSubagentTrackingStateSync(cwd, next, context.publish);
|
|
1062
913
|
return next;
|
|
1063
914
|
});
|
|
1064
915
|
}
|
|
916
|
+
export function recordNativeSubagentAuthorityObservation(cwd, observation) {
|
|
917
|
+
const requestedSessionIds = [...new Set(observation.sessionIds.map((value) => value.trim()).filter(Boolean))];
|
|
918
|
+
const childThreadId = observation.childThreadId.trim();
|
|
919
|
+
const parentThreadId = observation.parentThreadId.trim();
|
|
920
|
+
const rootNativeSessionId = observation.rootNativeSessionId?.trim() ?? '';
|
|
921
|
+
const implicatedChildThreadIds = [...new Set([
|
|
922
|
+
childThreadId,
|
|
923
|
+
...(observation.implicatedChildThreadIds ?? []).map((value) => value.trim()),
|
|
924
|
+
].filter(Boolean))];
|
|
925
|
+
return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
|
|
926
|
+
const strict = readSubagentTrackingStateStrictSync(cwd);
|
|
927
|
+
if (!strict.ok)
|
|
928
|
+
throw new Error('Malformed subagent tracker authority state');
|
|
929
|
+
let next = strict.state;
|
|
930
|
+
const existingSessionIds = Object.entries(next.sessions)
|
|
931
|
+
.filter(([, session]) => implicatedChildThreadIds.some((id) => Boolean(session.threads[id]?.direct_child_root_id || session.threads[id]?.reopen_authority_revoked)))
|
|
932
|
+
.map(([sessionId]) => sessionId);
|
|
933
|
+
const sessionIds = [...new Set([...requestedSessionIds, ...existingSessionIds])];
|
|
934
|
+
const existingBindings = Object.values(next.sessions)
|
|
935
|
+
.map((session) => session.threads[childThreadId])
|
|
936
|
+
.filter((thread) => Boolean(thread?.direct_child_root_id));
|
|
937
|
+
const authorityEvidence = observation.authorityEvidence ?? (rootNativeSessionId ? 'valid' : 'absent');
|
|
938
|
+
const globalConflict = authorityEvidence === 'untrusted' && existingBindings.length > 0
|
|
939
|
+
|| authorityEvidence === 'valid' && existingBindings.some((thread) => thread.direct_child_root_id !== rootNativeSessionId || thread.direct_child_parent_id !== parentThreadId);
|
|
940
|
+
const timestamp = observation.timestamp ?? new Date().toISOString();
|
|
941
|
+
if (authorityEvidence === 'untrusted') {
|
|
942
|
+
for (const session of Object.values(next.sessions)) {
|
|
943
|
+
for (const implicatedId of implicatedChildThreadIds) {
|
|
944
|
+
const implicatedThread = session.threads[implicatedId];
|
|
945
|
+
if (!implicatedThread?.direct_child_root_id && !implicatedThread?.reopen_authority_revoked)
|
|
946
|
+
continue;
|
|
947
|
+
implicatedThread.reopen_authority_revoked = true;
|
|
948
|
+
implicatedThread.reopen_authority_conflict_reason = 'identity_untrusted';
|
|
949
|
+
implicatedThread.reopen_authority_conflict_at = timestamp;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
for (const sessionId of sessionIds) {
|
|
954
|
+
if (parentThreadId && parentThreadId !== childThreadId) {
|
|
955
|
+
next = recordSubagentTurn(next, { sessionId, threadId: parentThreadId, kind: 'leader', timestamp });
|
|
956
|
+
}
|
|
957
|
+
next = recordSubagentTurn(next, {
|
|
958
|
+
sessionId,
|
|
959
|
+
threadId: childThreadId,
|
|
960
|
+
kind: 'subagent',
|
|
961
|
+
...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
|
|
962
|
+
mode: observation.mode,
|
|
963
|
+
timestamp,
|
|
964
|
+
});
|
|
965
|
+
const thread = next.sessions[sessionId]?.threads[childThreadId];
|
|
966
|
+
if (!thread)
|
|
967
|
+
continue;
|
|
968
|
+
const mayAttest = authorityEvidence === 'valid'
|
|
969
|
+
&& Boolean(rootNativeSessionId)
|
|
970
|
+
&& childThreadId !== rootNativeSessionId
|
|
971
|
+
&& parentThreadId === rootNativeSessionId;
|
|
972
|
+
if (globalConflict) {
|
|
973
|
+
thread.reopen_authority_revoked = true;
|
|
974
|
+
thread.reopen_authority_conflict_reason = 'root_or_parent_mismatch';
|
|
975
|
+
thread.reopen_authority_conflict_at = timestamp;
|
|
976
|
+
}
|
|
977
|
+
else if (mayAttest) {
|
|
978
|
+
thread.direct_child_root_id = rootNativeSessionId;
|
|
979
|
+
thread.direct_child_parent_id = parentThreadId;
|
|
980
|
+
thread.provenance_kind = NATIVE_SUBAGENT_PROVENANCE;
|
|
981
|
+
delete thread.reopen_authority_revoked;
|
|
982
|
+
delete thread.reopen_authority_conflict_reason;
|
|
983
|
+
delete thread.reopen_authority_conflict_at;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
context.assertOwnership();
|
|
987
|
+
writeSubagentTrackingStateSync(cwd, next, context.publish);
|
|
988
|
+
return next;
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Durable fence for authority revocations that could not be published.
|
|
993
|
+
*
|
|
994
|
+
* A negative identity observation is only meaningful if reopen consumption can
|
|
995
|
+
* see it. When the revocation transaction fails (lock exhaustion, malformed
|
|
996
|
+
* bytes, I/O error) the affected ids are recorded here instead of being
|
|
997
|
+
* silently dropped, and `consumeDirectChildReopenContext` treats any fenced id
|
|
998
|
+
* as denied until the revocation is durably applied.
|
|
999
|
+
*/
|
|
1000
|
+
function authorityFencePath(cwd) {
|
|
1001
|
+
return `${subagentTrackingPath(cwd)}.authority-fence.json`;
|
|
1002
|
+
}
|
|
1003
|
+
/** Read the fenced ids. An unreadable/malformed fence fails closed for all ids. */
|
|
1004
|
+
function readAuthorityFence(cwd) {
|
|
1005
|
+
const path = authorityFencePath(cwd);
|
|
1006
|
+
if (!existsSync(path))
|
|
1007
|
+
return { ok: true, ids: new Set() };
|
|
1008
|
+
try {
|
|
1009
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
1010
|
+
if (!Array.isArray(parsed.ids))
|
|
1011
|
+
return { ok: false, ids: new Set() };
|
|
1012
|
+
const ids = parsed.ids.filter((id) => typeof id === 'string' && Boolean(id.trim()));
|
|
1013
|
+
if (ids.length !== parsed.ids.length)
|
|
1014
|
+
return { ok: false, ids: new Set() };
|
|
1015
|
+
return { ok: true, ids: new Set(ids.map((id) => id.trim())) };
|
|
1016
|
+
}
|
|
1017
|
+
catch {
|
|
1018
|
+
return { ok: false, ids: new Set() };
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
/** Upper bound on fenced ids, so repeated failures cannot grow the fence without limit. */
|
|
1022
|
+
export const AUTHORITY_FENCE_MAX_IDS = 512;
|
|
1023
|
+
/**
|
|
1024
|
+
* Record ids whose revocation could not be persisted, so reopen stays denied.
|
|
1025
|
+
*
|
|
1026
|
+
* Every fence mutation runs under a dedicated cross-process lock on the fence
|
|
1027
|
+
* path so read/merge/publish is atomic with respect to concurrent fencing and
|
|
1028
|
+
* clearing. Returns true when the denial is durable; the caller must treat
|
|
1029
|
+
* false as an unresolved fail-closed condition.
|
|
1030
|
+
*/
|
|
1031
|
+
export function fenceNativeSubagentAuthorities(cwd, childThreadIds, reason) {
|
|
1032
|
+
const ids = childThreadIds.map((value) => value.trim()).filter(Boolean);
|
|
1033
|
+
if (!ids.length)
|
|
1034
|
+
return true;
|
|
1035
|
+
const path = authorityFencePath(cwd);
|
|
1036
|
+
try {
|
|
1037
|
+
return withCrossProcessFileLockSync(path, (lock) => {
|
|
1038
|
+
const existing = readAuthorityFence(cwd);
|
|
1039
|
+
// An unreadable fence already denies every id; do not weaken it by
|
|
1040
|
+
// rewriting it into a narrower explicit id list.
|
|
1041
|
+
if (!existing.ok)
|
|
1042
|
+
return true;
|
|
1043
|
+
const merged = [...new Set([...existing.ids, ...ids])].sort();
|
|
1044
|
+
if (merged.length > AUTHORITY_FENCE_MAX_IDS) {
|
|
1045
|
+
// Refuse to grow past the cap by writing a deliberately unreadable
|
|
1046
|
+
// sentinel: a global denial is strictly stronger than a partial list.
|
|
1047
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1048
|
+
lock.assertOwnership();
|
|
1049
|
+
lock.publish(`${JSON.stringify({ ids: 'all', reason: 'fence_capacity_exceeded', fenced_at: new Date().toISOString() }, null, 2)}\n`);
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1053
|
+
lock.assertOwnership();
|
|
1054
|
+
lock.publish(`${JSON.stringify({ ids: merged, reason, fenced_at: new Date().toISOString() }, null, 2)}\n`);
|
|
1055
|
+
return true;
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
// The denial could not be made durable. The caller must surface this
|
|
1060
|
+
// rather than continue as if the revocation had been applied.
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Last-resort global denial used when BOTH the tracker revocation transaction
|
|
1066
|
+
* and the locked fence publication fail. It deliberately avoids the fence lock
|
|
1067
|
+
* (which may be exactly what is unavailable) and writes an intentionally
|
|
1068
|
+
* unreadable sentinel directly, because `readAuthorityFence` treats any
|
|
1069
|
+
* unreadable fence as a denial of every id. Returns true when the denial is
|
|
1070
|
+
* durable on disk.
|
|
1071
|
+
*/
|
|
1072
|
+
export function forceGlobalAuthorityDenial(cwd, reason) {
|
|
1073
|
+
const path = authorityFencePath(cwd);
|
|
1074
|
+
const contents = `${JSON.stringify({ ids: 'all', reason, fenced_at: new Date().toISOString() }, null, 2)}\n`;
|
|
1075
|
+
try {
|
|
1076
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1077
|
+
const temporaryPath = atomicTrackingTempPath(path);
|
|
1078
|
+
writeFileSync(temporaryPath, contents);
|
|
1079
|
+
renameSync(temporaryPath, path);
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
catch {
|
|
1083
|
+
try {
|
|
1084
|
+
// Even a partial/truncated write is safe here: unreadable means denied.
|
|
1085
|
+
writeFileSync(path, contents);
|
|
1086
|
+
return true;
|
|
1087
|
+
}
|
|
1088
|
+
catch {
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Drop ids from the fence once their revocation is durably published.
|
|
1095
|
+
*
|
|
1096
|
+
* Runs under the fence lock and re-reads inside it, so it can never clear
|
|
1097
|
+
* based on a stale snapshot. An unreadable fence is a global denial and is
|
|
1098
|
+
* NEVER destroyed here: only an explicit, fully reconciled id list is removed.
|
|
1099
|
+
*/
|
|
1100
|
+
function clearAuthorityFence(cwd, childThreadIds) {
|
|
1101
|
+
const path = authorityFencePath(cwd);
|
|
1102
|
+
if (!existsSync(path))
|
|
1103
|
+
return;
|
|
1104
|
+
const cleared = new Set(childThreadIds.map((value) => value.trim()).filter(Boolean));
|
|
1105
|
+
if (!cleared.size)
|
|
1106
|
+
return;
|
|
1107
|
+
try {
|
|
1108
|
+
withCrossProcessFileLockSync(path, (lock) => {
|
|
1109
|
+
const existing = readAuthorityFence(cwd);
|
|
1110
|
+
// Preserve an unreadable/global denial. Reconciling it requires explicit
|
|
1111
|
+
// operator action, not an incidental unrelated revocation.
|
|
1112
|
+
if (!existing.ok)
|
|
1113
|
+
return;
|
|
1114
|
+
const remaining = [...existing.ids].filter((id) => !cleared.has(id)).sort();
|
|
1115
|
+
if (remaining.length === existing.ids.size)
|
|
1116
|
+
return;
|
|
1117
|
+
lock.assertOwnership();
|
|
1118
|
+
if (!remaining.length) {
|
|
1119
|
+
unlinkSync(path);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
lock.publish(`${JSON.stringify({ ids: remaining, reason: 'partial_clear', fenced_at: new Date().toISOString() }, null, 2)}\n`);
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
catch {
|
|
1126
|
+
// Leaving a stale fence entry is fail-closed and therefore acceptable.
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
export function revokeNativeSubagentAuthorities(cwd, childThreadIds, reason = 'identity_untrusted') {
|
|
1130
|
+
const ids = [...new Set(childThreadIds.map((value) => value.trim()).filter(Boolean))];
|
|
1131
|
+
return withCrossProcessFileLockSync(subagentTrackingPath(cwd), (context) => {
|
|
1132
|
+
const strict = readSubagentTrackingStateStrictSync(cwd);
|
|
1133
|
+
if (!strict.ok)
|
|
1134
|
+
throw new Error('Malformed subagent tracker authority state');
|
|
1135
|
+
const next = strict.state;
|
|
1136
|
+
const timestamp = new Date().toISOString();
|
|
1137
|
+
let changed = false;
|
|
1138
|
+
for (const session of Object.values(next.sessions)) {
|
|
1139
|
+
for (const id of ids) {
|
|
1140
|
+
const thread = session.threads[id];
|
|
1141
|
+
if (!thread?.direct_child_root_id && !thread?.reopen_authority_revoked)
|
|
1142
|
+
continue;
|
|
1143
|
+
thread.reopen_authority_revoked = true;
|
|
1144
|
+
thread.reopen_authority_conflict_reason = reason;
|
|
1145
|
+
thread.reopen_authority_conflict_at = timestamp;
|
|
1146
|
+
changed = true;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
if (changed) {
|
|
1150
|
+
context.assertOwnership();
|
|
1151
|
+
writeSubagentTrackingStateSync(cwd, next, context.publish);
|
|
1152
|
+
}
|
|
1153
|
+
// The revocation is now durable for these ids (either just published, or
|
|
1154
|
+
// already present on disk), so any prior fence for them can be released.
|
|
1155
|
+
clearAuthorityFence(cwd, ids);
|
|
1156
|
+
return next;
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
function formatReopenMetadata(entry) {
|
|
1160
|
+
const values = [
|
|
1161
|
+
entry.role ? `role: ${entry.role}` : null,
|
|
1162
|
+
entry.laneId ? `lane: ${entry.laneId}` : null,
|
|
1163
|
+
entry.scope ? `scope: ${entry.scope}` : null,
|
|
1164
|
+
`status: ${entry.status}`,
|
|
1165
|
+
entry.lastHandoffSummary ? `handoff: ${entry.lastHandoffSummary.slice(0, 120)}` : null,
|
|
1166
|
+
entry.resumeFailureReason ? `last failure: ${entry.resumeFailureReason.slice(0, 120)}` : null,
|
|
1167
|
+
].filter((value) => Boolean(value));
|
|
1168
|
+
return values.length ? ` (${values.join('; ')})` : '';
|
|
1169
|
+
}
|
|
1170
|
+
function persistedReopenAuthorityWarning(reason) {
|
|
1171
|
+
return [
|
|
1172
|
+
'[Persisted subagent reopen]',
|
|
1173
|
+
`- Warning: persisted subagent authority was excluded (${reason}); no resume authority or bookkeeping was granted.`,
|
|
1174
|
+
'- Silver rule: do not spawn a same-role/same-lane replacement solely because persisted reopen authority was unavailable; continue in the root or another compatible existing lane.',
|
|
1175
|
+
].join('\n');
|
|
1176
|
+
}
|
|
1177
|
+
function persistedReopenLegacyNotice(excludedCount, source) {
|
|
1178
|
+
return [
|
|
1179
|
+
'[Persisted subagent reopen]',
|
|
1180
|
+
`- SessionStart source: ${source}; saved subagent ids found: 0.`,
|
|
1181
|
+
`- Notice: ${excludedCount} persisted subagent record(s) were excluded as non-authoritative or legacy; no resume authority or bookkeeping was granted.`,
|
|
1182
|
+
'- Silver rule: do not spawn a same-role/same-lane replacement solely because persisted reopen authority was unavailable; continue in the root or another compatible existing lane.',
|
|
1183
|
+
].join('\n');
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* True when a stored partition is provably about the current root, and not an
|
|
1187
|
+
* unrelated/historical workspace session that merely happens to contain a
|
|
1188
|
+
* thread id equal to the current canonical session key. Canonical-key collision
|
|
1189
|
+
* quarantine is scoped to correlated partitions so it cannot destroy authority
|
|
1190
|
+
* or resume bookkeeping belonging to a different root.
|
|
1191
|
+
*/
|
|
1192
|
+
function partitionCorrelatesToRoot(session, sessionId, rootNativeSessionId) {
|
|
1193
|
+
if (session.session_id === sessionId || session.session_id === rootNativeSessionId)
|
|
1194
|
+
return true;
|
|
1195
|
+
if (session.leader_thread_id === rootNativeSessionId)
|
|
1196
|
+
return true;
|
|
1197
|
+
if (session.threads[rootNativeSessionId] !== undefined)
|
|
1198
|
+
return true;
|
|
1199
|
+
return Object.values(session.threads).some((thread) => thread.direct_child_root_id === rootNativeSessionId
|
|
1200
|
+
|| thread.direct_child_parent_id === rootNativeSessionId);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Repair persisted root-as-subagent identity inversion inside an already-parsed
|
|
1204
|
+
* state. Only a thread whose id equals the exact native root id is conclusively
|
|
1205
|
+
* the root and is reclassified as leader. A thread whose id equals only the
|
|
1206
|
+
* canonical session key is always ambiguous — the storage key is not identity
|
|
1207
|
+
* proof — so it is never destructively reclassified: authority-bearing records
|
|
1208
|
+
* are revoked (fail closed) and every ambiguous record loses resume bookkeeping.
|
|
1209
|
+
* That ambiguity quarantine only applies inside partitions that correlate to
|
|
1210
|
+
* this root; unrelated partitions are left untouched.
|
|
1211
|
+
* Returns true when any record changed.
|
|
1212
|
+
*/
|
|
1213
|
+
function repairPersistedRootIdentityInState(state, sessionId, rootNativeSessionId, repairTimestamp) {
|
|
1214
|
+
let changed = false;
|
|
1215
|
+
for (const correlatedSession of Object.values(state.sessions)) {
|
|
1216
|
+
let sessionChanged = false;
|
|
1217
|
+
const correlated = partitionCorrelatesToRoot(correlatedSession, sessionId, rootNativeSessionId);
|
|
1218
|
+
for (const rootId of new Set([sessionId, rootNativeSessionId])) {
|
|
1219
|
+
const rootThread = correlatedSession.threads[rootId];
|
|
1220
|
+
if (!rootThread || rootThread.kind !== 'subagent')
|
|
1221
|
+
continue;
|
|
1222
|
+
const ambiguousCanonicalCollision = rootId === sessionId && sessionId !== rootNativeSessionId;
|
|
1223
|
+
if (ambiguousCanonicalCollision) {
|
|
1224
|
+
// An uncorrelated partition's identically-named thread belongs to a
|
|
1225
|
+
// different root: leave its authority and bookkeeping alone.
|
|
1226
|
+
if (!correlated)
|
|
1227
|
+
continue;
|
|
1228
|
+
const carriesAuthority = rootThread.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
|
|
1229
|
+
|| rootThread.direct_child_root_id !== undefined
|
|
1230
|
+
|| rootThread.direct_child_parent_id !== undefined;
|
|
1231
|
+
if (carriesAuthority && rootThread.reopen_authority_revoked !== true) {
|
|
1232
|
+
// Fail closed without rewriting identity: the id collides with the
|
|
1233
|
+
// canonical root key but carries child-shaped authority evidence, so
|
|
1234
|
+
// it stays a subagent whose reopen authority is revoked until root
|
|
1235
|
+
// identity is independently established.
|
|
1236
|
+
rootThread.reopen_authority_revoked = true;
|
|
1237
|
+
rootThread.reopen_authority_conflict_reason = 'canonical_root_collision';
|
|
1238
|
+
rootThread.reopen_authority_conflict_at = repairTimestamp;
|
|
1239
|
+
sessionChanged = true;
|
|
1240
|
+
}
|
|
1241
|
+
if (rootThread.resume_requested_at !== undefined
|
|
1242
|
+
|| rootThread.resume_completed_at !== undefined
|
|
1243
|
+
|| rootThread.resume_failed_at !== undefined
|
|
1244
|
+
|| rootThread.resume_failure_reason !== undefined) {
|
|
1245
|
+
delete rootThread.resume_requested_at;
|
|
1246
|
+
delete rootThread.resume_completed_at;
|
|
1247
|
+
delete rootThread.resume_failed_at;
|
|
1248
|
+
delete rootThread.resume_failure_reason;
|
|
1249
|
+
sessionChanged = true;
|
|
1250
|
+
}
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
if (rootId !== rootNativeSessionId)
|
|
1254
|
+
continue;
|
|
1255
|
+
rootThread.kind = 'leader';
|
|
1256
|
+
delete rootThread.direct_child_root_id;
|
|
1257
|
+
delete rootThread.direct_child_parent_id;
|
|
1258
|
+
delete rootThread.reopen_authority_revoked;
|
|
1259
|
+
delete rootThread.reopen_authority_conflict_reason;
|
|
1260
|
+
delete rootThread.reopen_authority_conflict_at;
|
|
1261
|
+
delete rootThread.resume_requested_at;
|
|
1262
|
+
delete rootThread.resume_completed_at;
|
|
1263
|
+
delete rootThread.resume_failed_at;
|
|
1264
|
+
delete rootThread.resume_failure_reason;
|
|
1265
|
+
sessionChanged = true;
|
|
1266
|
+
}
|
|
1267
|
+
if (correlatedSession.threads[rootNativeSessionId] && correlatedSession.leader_thread_id !== rootNativeSessionId) {
|
|
1268
|
+
correlatedSession.leader_thread_id = rootNativeSessionId;
|
|
1269
|
+
sessionChanged = true;
|
|
1270
|
+
}
|
|
1271
|
+
if (sessionChanged) {
|
|
1272
|
+
correlatedSession.updated_at = repairTimestamp;
|
|
1273
|
+
changed = true;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return changed;
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Pointer-authoritative root identity repair, decoupled from reopen candidate
|
|
1280
|
+
* output. Runs under the tracker lock and fails closed on malformed bytes.
|
|
1281
|
+
*/
|
|
1282
|
+
export function repairPersistedRootIdentity(cwd, context) {
|
|
1283
|
+
const sessionId = context.sessionId.trim();
|
|
1284
|
+
const rootNativeSessionId = context.rootNativeSessionId.trim();
|
|
1285
|
+
if (!sessionId || !rootNativeSessionId)
|
|
1286
|
+
return false;
|
|
1287
|
+
const path = subagentTrackingPath(cwd);
|
|
1288
|
+
return withCrossProcessFileLockSync(path, (lock) => {
|
|
1289
|
+
let raw;
|
|
1290
|
+
try {
|
|
1291
|
+
raw = readFileSync(path, 'utf-8');
|
|
1292
|
+
}
|
|
1293
|
+
catch {
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
const state = parseStrictSubagentTrackingState(raw);
|
|
1297
|
+
if (!state)
|
|
1298
|
+
return false;
|
|
1299
|
+
const changed = repairPersistedRootIdentityInState(state, sessionId, rootNativeSessionId, new Date().toISOString());
|
|
1300
|
+
if (changed) {
|
|
1301
|
+
lock.assertOwnership();
|
|
1302
|
+
writeSubagentTrackingStateSync(cwd, state, lock.publish);
|
|
1303
|
+
}
|
|
1304
|
+
return changed;
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Quarantine reopen authority for a root that is persisted as a subagent when
|
|
1309
|
+
* the identity evidence is contradictory (for example a self-parented
|
|
1310
|
+
* transcript marker). Unlike full repair this never asserts leader identity, so
|
|
1311
|
+
* legitimate descriptive subagent evidence is preserved; it only strips the
|
|
1312
|
+
* authority and resume bookkeeping that a root must never carry. Returns true
|
|
1313
|
+
* when any record changed.
|
|
1314
|
+
*/
|
|
1315
|
+
export function quarantinePersistedRootAuthority(cwd, context) {
|
|
1316
|
+
const sessionId = context.sessionId.trim();
|
|
1317
|
+
const rootNativeSessionId = context.rootNativeSessionId.trim();
|
|
1318
|
+
if (!sessionId || !rootNativeSessionId)
|
|
1319
|
+
return false;
|
|
1320
|
+
const path = subagentTrackingPath(cwd);
|
|
1321
|
+
return withCrossProcessFileLockSync(path, (lock) => {
|
|
1322
|
+
let raw;
|
|
1323
|
+
try {
|
|
1324
|
+
raw = readFileSync(path, 'utf-8');
|
|
1325
|
+
}
|
|
1326
|
+
catch {
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
const state = parseStrictSubagentTrackingState(raw);
|
|
1330
|
+
if (!state)
|
|
1331
|
+
return false;
|
|
1332
|
+
const timestamp = new Date().toISOString();
|
|
1333
|
+
let changed = false;
|
|
1334
|
+
for (const session of Object.values(state.sessions)) {
|
|
1335
|
+
if (!partitionCorrelatesToRoot(session, sessionId, rootNativeSessionId))
|
|
1336
|
+
continue;
|
|
1337
|
+
const rootThread = session.threads[rootNativeSessionId];
|
|
1338
|
+
if (!rootThread || rootThread.kind !== 'subagent')
|
|
1339
|
+
continue;
|
|
1340
|
+
const carriesAuthority = rootThread.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
|
|
1341
|
+
|| rootThread.direct_child_root_id !== undefined
|
|
1342
|
+
|| rootThread.direct_child_parent_id !== undefined;
|
|
1343
|
+
let sessionChanged = false;
|
|
1344
|
+
if (carriesAuthority && rootThread.reopen_authority_revoked !== true) {
|
|
1345
|
+
rootThread.reopen_authority_revoked = true;
|
|
1346
|
+
rootThread.reopen_authority_conflict_reason = 'contradictory_root_child_evidence';
|
|
1347
|
+
rootThread.reopen_authority_conflict_at = timestamp;
|
|
1348
|
+
sessionChanged = true;
|
|
1349
|
+
}
|
|
1350
|
+
if (rootThread.resume_requested_at !== undefined
|
|
1351
|
+
|| rootThread.resume_completed_at !== undefined
|
|
1352
|
+
|| rootThread.resume_failed_at !== undefined
|
|
1353
|
+
|| rootThread.resume_failure_reason !== undefined) {
|
|
1354
|
+
delete rootThread.resume_requested_at;
|
|
1355
|
+
delete rootThread.resume_completed_at;
|
|
1356
|
+
delete rootThread.resume_failed_at;
|
|
1357
|
+
delete rootThread.resume_failure_reason;
|
|
1358
|
+
sessionChanged = true;
|
|
1359
|
+
}
|
|
1360
|
+
if (sessionChanged) {
|
|
1361
|
+
session.updated_at = timestamp;
|
|
1362
|
+
changed = true;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
if (changed) {
|
|
1366
|
+
lock.assertOwnership();
|
|
1367
|
+
writeSubagentTrackingStateSync(cwd, state, lock.publish);
|
|
1368
|
+
}
|
|
1369
|
+
return changed;
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
/** Strict, lock-serialized authority consumption for SessionStart persisted reopen output. */
|
|
1373
|
+
export function consumeDirectChildReopenContext(cwd, context) {
|
|
1374
|
+
const sessionId = context.sessionId.trim();
|
|
1375
|
+
const rootNativeSessionId = context.rootNativeSessionId.trim();
|
|
1376
|
+
if (!sessionId || !rootNativeSessionId)
|
|
1377
|
+
return null;
|
|
1378
|
+
const path = subagentTrackingPath(cwd);
|
|
1379
|
+
return withCrossProcessFileLockSync(path, (lock) => {
|
|
1380
|
+
let raw;
|
|
1381
|
+
try {
|
|
1382
|
+
raw = readFileSync(path, 'utf-8');
|
|
1383
|
+
}
|
|
1384
|
+
catch (error) {
|
|
1385
|
+
if (error.code === 'ENOENT')
|
|
1386
|
+
return null;
|
|
1387
|
+
return persistedReopenAuthorityWarning('tracker_read_failed');
|
|
1388
|
+
}
|
|
1389
|
+
const state = parseStrictSubagentTrackingState(raw);
|
|
1390
|
+
if (!state)
|
|
1391
|
+
return persistedReopenAuthorityWarning('malformed_tracker_state');
|
|
1392
|
+
// A revocation that could not be published leaves a durable fence. Fenced
|
|
1393
|
+
// ids stay denied here even though the on-disk record still looks valid,
|
|
1394
|
+
// so a failed negative observation can never leave old authority usable.
|
|
1395
|
+
const fence = readAuthorityFence(cwd);
|
|
1396
|
+
if (!fence.ok)
|
|
1397
|
+
return persistedReopenAuthorityWarning('unreadable_authority_fence');
|
|
1398
|
+
const repairTimestamp = new Date().toISOString();
|
|
1399
|
+
// Root identity repair runs before candidate eligibility so a missing
|
|
1400
|
+
// canonical partition cannot leave a known root inversion unrepaired.
|
|
1401
|
+
const changed = repairPersistedRootIdentityInState(state, sessionId, rootNativeSessionId, repairTimestamp);
|
|
1402
|
+
const session = state.sessions[sessionId];
|
|
1403
|
+
if (!session) {
|
|
1404
|
+
if (changed) {
|
|
1405
|
+
lock.assertOwnership();
|
|
1406
|
+
writeSubagentTrackingStateSync(cwd, state, lock.publish);
|
|
1407
|
+
}
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
const savedSubagents = Object.values(session.threads)
|
|
1411
|
+
.filter((thread) => {
|
|
1412
|
+
if (thread.kind !== 'subagent'
|
|
1413
|
+
|| thread.thread_id === sessionId
|
|
1414
|
+
|| thread.thread_id === rootNativeSessionId
|
|
1415
|
+
|| session.leader_thread_id === thread.thread_id
|
|
1416
|
+
|| fence.ids.has(thread.thread_id)
|
|
1417
|
+
|| thread.provenance_kind !== NATIVE_SUBAGENT_PROVENANCE
|
|
1418
|
+
|| thread.direct_child_root_id !== rootNativeSessionId
|
|
1419
|
+
|| thread.direct_child_parent_id !== rootNativeSessionId
|
|
1420
|
+
|| thread.reopen_authority_revoked === true)
|
|
1421
|
+
return false;
|
|
1422
|
+
return Object.values(state.sessions).every((correlationSession) => {
|
|
1423
|
+
const view = correlationSession.threads[thread.thread_id];
|
|
1424
|
+
if (!view)
|
|
1425
|
+
return true;
|
|
1426
|
+
// Recognized legacy adapted records are descriptive-only: they never
|
|
1427
|
+
// grant authority and never block a newly attested valid child. The
|
|
1428
|
+
// tolerance is shape-constrained: an adapted view may only waive
|
|
1429
|
+
// missing attestation for a non-leader subagent view and never waives
|
|
1430
|
+
// leader-kind or leader_thread_id contradictions.
|
|
1431
|
+
if (view.provenance_kind === DESCRIPTIVE_ADAPTED_PROVENANCE
|
|
1432
|
+
&& view.kind === 'subagent'
|
|
1433
|
+
&& correlationSession.leader_thread_id !== thread.thread_id)
|
|
1434
|
+
return true;
|
|
1435
|
+
return correlationSession.leader_thread_id !== thread.thread_id
|
|
1436
|
+
&& view.kind === 'subagent'
|
|
1437
|
+
&& view.provenance_kind === NATIVE_SUBAGENT_PROVENANCE
|
|
1438
|
+
&& view.direct_child_root_id === rootNativeSessionId
|
|
1439
|
+
&& view.direct_child_parent_id === rootNativeSessionId
|
|
1440
|
+
&& view.reopen_authority_revoked !== true;
|
|
1441
|
+
});
|
|
1442
|
+
})
|
|
1443
|
+
.map((thread) => normalizeLedgerEntry(thread, thread.status ?? 'closed'))
|
|
1444
|
+
.sort(compareResumeEntries);
|
|
1445
|
+
if (!savedSubagents.length) {
|
|
1446
|
+
if (changed) {
|
|
1447
|
+
session.updated_at = new Date().toISOString();
|
|
1448
|
+
lock.assertOwnership();
|
|
1449
|
+
writeSubagentTrackingStateSync(cwd, state, lock.publish);
|
|
1450
|
+
}
|
|
1451
|
+
// Surface intentional exclusion of legacy/non-authoritative records instead
|
|
1452
|
+
// of silently returning no context. The notice fires only when every
|
|
1453
|
+
// relevant view across every correlation partition is genuinely
|
|
1454
|
+
// descriptive/legacy: any authority-bearing, leader-kind, or
|
|
1455
|
+
// leader_thread_id-colliding view keeps the denial fail-closed null.
|
|
1456
|
+
const excludedIds = new Set(Object.values(session.threads)
|
|
1457
|
+
.filter((thread) => thread.kind === 'subagent')
|
|
1458
|
+
.map((thread) => thread.thread_id));
|
|
1459
|
+
const everyRelevantViewIsLegacy = excludedIds.size > 0
|
|
1460
|
+
&& Object.values(state.sessions).every((correlationSession) => {
|
|
1461
|
+
if (correlationSession.leader_thread_id && excludedIds.has(correlationSession.leader_thread_id))
|
|
1462
|
+
return false;
|
|
1463
|
+
return [...excludedIds].every((threadId) => {
|
|
1464
|
+
const view = correlationSession.threads[threadId];
|
|
1465
|
+
if (!view)
|
|
1466
|
+
return true;
|
|
1467
|
+
return view.kind === 'subagent'
|
|
1468
|
+
&& view.provenance_kind !== NATIVE_SUBAGENT_PROVENANCE
|
|
1469
|
+
&& view.direct_child_root_id === undefined
|
|
1470
|
+
&& view.direct_child_parent_id === undefined
|
|
1471
|
+
&& view.reopen_authority_revoked !== true;
|
|
1472
|
+
});
|
|
1473
|
+
});
|
|
1474
|
+
if (everyRelevantViewIsLegacy) {
|
|
1475
|
+
return persistedReopenLegacyNotice(excludedIds.size, context.source);
|
|
1476
|
+
}
|
|
1477
|
+
return null;
|
|
1478
|
+
}
|
|
1479
|
+
const reopenTargets = savedSubagents.filter((entry) => entry.status !== 'unavailable');
|
|
1480
|
+
const unavailableTargets = savedSubagents.filter((entry) => entry.status === 'unavailable');
|
|
1481
|
+
const failedTargets = savedSubagents.filter((entry) => entry.resumeFailedAt || entry.resumeFailureReason);
|
|
1482
|
+
const now = new Date().toISOString();
|
|
1483
|
+
for (const target of reopenTargets) {
|
|
1484
|
+
session.threads[target.threadId] = { ...session.threads[target.threadId], resume_requested_at: now };
|
|
1485
|
+
}
|
|
1486
|
+
if (reopenTargets.length || changed) {
|
|
1487
|
+
session.updated_at = now;
|
|
1488
|
+
lock.assertOwnership();
|
|
1489
|
+
writeSubagentTrackingStateSync(cwd, state, lock.publish);
|
|
1490
|
+
}
|
|
1491
|
+
const lines = [
|
|
1492
|
+
'[Persisted subagent reopen]',
|
|
1493
|
+
`- SessionStart source: ${context.source}; saved subagent ids found: ${savedSubagents.length}.`,
|
|
1494
|
+
];
|
|
1495
|
+
if (reopenTargets.length) {
|
|
1496
|
+
lines.push('- Reopen these persisted subagents by id before continuing work or spawning any same-role/same-lane replacement:');
|
|
1497
|
+
lines.push(...reopenTargets.slice(0, 12).map((entry) => ` - resume_agent(${JSON.stringify(entry.agentId)})${formatReopenMetadata(entry)}`));
|
|
1498
|
+
if (reopenTargets.length > 12)
|
|
1499
|
+
lines.push(` - ... ${reopenTargets.length - 12} more saved subagent id(s) omitted from this compact SessionStart context; consult .omx/state/subagent-tracking.json before spawning replacements.`);
|
|
1500
|
+
}
|
|
1501
|
+
else {
|
|
1502
|
+
lines.push('- No compatible saved subagent id is currently marked reopenable; do not spawn a replacement merely because reopen was unavailable.');
|
|
1503
|
+
}
|
|
1504
|
+
lines.push('- Silver rule: when follow-up work targets an existing role/lane, reuse the matching reopened id; avoid duplicate same-type subagent spawns.');
|
|
1505
|
+
lines.push('- If resume_agent fails, surface a clear warning with the id and reason, then continue in the root or another compatible existing lane; do not spawn a new agent solely because reopen failed.');
|
|
1506
|
+
const warnings = [...new Map([...unavailableTargets, ...failedTargets].map((entry) => [entry.agentId, entry])).values()];
|
|
1507
|
+
if (warnings.length) {
|
|
1508
|
+
lines.push('- Reopen warnings:');
|
|
1509
|
+
lines.push(...warnings.slice(0, 8).map((entry) => ` - ${entry.agentId}${formatReopenMetadata(entry)}`));
|
|
1510
|
+
if (warnings.length > 8)
|
|
1511
|
+
lines.push(` - ... ${warnings.length - 8} more warning(s) omitted from this compact SessionStart context.`);
|
|
1512
|
+
}
|
|
1513
|
+
return lines.join('\n');
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1065
1516
|
export function summarizeSubagentSession(state, sessionId, options = {}) {
|
|
1066
1517
|
const normalized = normalizeSubagentTrackingState(state);
|
|
1067
1518
|
const session = normalized.sessions[sessionId];
|