@gajae-code/coding-agent 0.13.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +215 -8
- package/README.md +2 -0
- package/bin/gajaessi.js +4 -0
- package/dist/types/async/job-manager.d.ts +55 -2
- package/dist/types/async/support.d.ts +14 -0
- package/dist/types/capability/mcp.d.ts +6 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/accounts-cli.d.ts +19 -0
- package/dist/types/cli/args.d.ts +3 -0
- package/dist/types/cli/config-cli.d.ts +6 -0
- package/dist/types/cli/crash-cli.d.ts +10 -0
- package/dist/types/cli/customize-doctor.d.ts +145 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/mcp-cli.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/root-flags.d.ts +12 -0
- package/dist/types/cli/setup-cli.d.ts +3 -1
- package/dist/types/cli/skills-cli.d.ts +2 -1
- package/dist/types/cli/update-cli.d.ts +19 -1
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/accounts.d.ts +40 -0
- package/dist/types/commands/crash.d.ts +24 -0
- package/dist/types/commands/customize.d.ts +22 -0
- package/dist/types/commands/daemon.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/commands/skills.d.ts +6 -1
- package/dist/types/commands/update.d.ts +7 -0
- package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
- package/dist/types/config/config-schema-version.d.ts +10 -0
- package/dist/types/config/file-lock.d.ts +18 -0
- package/dist/types/config/model-profile-activation.d.ts +3 -1
- package/dist/types/config/model-registry.d.ts +10 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +73 -9
- package/dist/types/config/settings.d.ts +63 -0
- package/dist/types/config/skill-settings-defaults.d.ts +26 -0
- package/dist/types/config/task-delegation.d.ts +39 -0
- package/dist/types/config/update-channel.d.ts +10 -0
- package/dist/types/coordinator-mcp/server.d.ts +5 -1
- package/dist/types/crash/index-store.d.ts +87 -0
- package/dist/types/crash/nudge.d.ts +50 -0
- package/dist/types/crash/record-loader.d.ts +17 -0
- package/dist/types/crash/report.d.ts +134 -0
- package/dist/types/crash/sanitize.d.ts +25 -0
- package/dist/types/cursor.d.ts +13 -2
- package/dist/types/customization/import.d.ts +37 -0
- package/dist/types/customization/index.d.ts +4 -0
- package/dist/types/customization/inventory.d.ts +25 -0
- package/dist/types/customization/mutations.d.ts +32 -0
- package/dist/types/customization/types.d.ts +115 -0
- package/dist/types/daemon/control-types.d.ts +8 -1
- package/dist/types/daemon/operator-contract.d.ts +4 -4
- package/dist/types/discovery/claude.d.ts +39 -1
- package/dist/types/discovery/codex.d.ts +39 -1
- package/dist/types/discovery/index.d.ts +2 -0
- package/dist/types/discovery/mcp-compat.d.ts +30 -0
- package/dist/types/edit/renderer.d.ts +23 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -4
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
- package/dist/types/extensibility/hooks/loader.d.ts +19 -5
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
- package/dist/types/extensibility/skill-management.d.ts +107 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
- package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
- package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
- package/dist/types/hooks/events.d.ts +60 -0
- package/dist/types/hooks/normalize.d.ts +83 -0
- package/dist/types/hooks/skill-state.d.ts +9 -2
- package/dist/types/main.d.ts +2 -1
- package/dist/types/modes/acp/abort-scope.d.ts +13 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -4
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
- package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
- package/dist/types/modes/components/customization/index.d.ts +1 -0
- package/dist/types/modes/components/dynamic-theme-text.d.ts +9 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +6 -28
- package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
- package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -4
- package/dist/types/modes/components/pet-capability.d.ts +7 -21
- package/dist/types/modes/components/settings-selector.d.ts +5 -0
- package/dist/types/modes/components/status-line/gh.d.ts +6 -2
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
- package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/theme/defaults/index.d.ts +118 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
- package/dist/types/runtime-mcp/config.d.ts +9 -0
- package/dist/types/runtime-mcp/manager.d.ts +26 -1
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
- package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
- package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
- package/dist/types/runtime-mcp/protocol.d.ts +278 -0
- package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
- package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
- package/dist/types/runtime-mcp/types.d.ts +102 -10
- package/dist/types/sdk/acp/adapter.d.ts +26 -5
- package/dist/types/sdk/broker/broker.d.ts +3 -55
- package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
- package/dist/types/sdk/broker/session-index.d.ts +14 -5
- package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
- package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
- package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
- package/dist/types/sdk/bus/index.d.ts +85 -99
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
- package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
- package/dist/types/sdk/bus/notification-service.d.ts +52 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
- package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
- package/dist/types/sdk/bus/session-control.d.ts +25 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
- package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
- package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
- package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
- package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
- package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
- package/dist/types/sdk/cli/rows.d.ts +2 -11
- package/dist/types/sdk/cli/session-cli.d.ts +15 -25
- package/dist/types/sdk/client/client.d.ts +97 -1
- package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +18 -5
- package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
- package/dist/types/sdk/host/host.d.ts +15 -1
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/host/session-runtime.d.ts +127 -7
- package/dist/types/sdk/host/types.d.ts +1 -1
- package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
- package/dist/types/sdk/index.d.ts +2 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
- package/dist/types/sdk/lifecycle/client.d.ts +59 -0
- package/dist/types/sdk/lifecycle/index.d.ts +3 -0
- package/dist/types/sdk/lifecycle/service.d.ts +232 -0
- package/dist/types/sdk/mcp/server.d.ts +9 -6
- package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
- package/dist/types/sdk/prompt-status.d.ts +10 -3
- package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
- package/dist/types/sdk/receipt-state.d.ts +17 -0
- package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
- package/dist/types/sdk/router/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +152 -0
- package/dist/types/sdk/session-list.d.ts +22 -0
- package/dist/types/sdk/session-reconnect.d.ts +43 -1
- package/dist/types/sdk/session.d.ts +34 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
- package/dist/types/session/account-inventory.d.ts +86 -0
- package/dist/types/session/agent-session.d.ts +145 -6
- package/dist/types/session/agent-storage.d.ts +10 -0
- package/dist/types/session/coordinator-tool-label.d.ts +28 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/secure-token-file.d.ts +3 -0
- package/dist/types/session/session-manager.d.ts +47 -1
- package/dist/types/session/startup-auth-config.d.ts +44 -0
- package/dist/types/session/terminal-abort.d.ts +378 -0
- package/dist/types/session/yield-queue.d.ts +7 -0
- package/dist/types/session-import/claude.d.ts +26 -0
- package/dist/types/session-import/codex.d.ts +88 -0
- package/dist/types/session-import/command.d.ts +35 -0
- package/dist/types/session-import/detect.d.ts +22 -0
- package/dist/types/session-import/index.d.ts +7 -0
- package/dist/types/session-import/provider-codex.d.ts +22 -0
- package/dist/types/session-import/provider-service.d.ts +38 -0
- package/dist/types/session-import/redact.d.ts +23 -0
- package/dist/types/session-import/service.d.ts +47 -0
- package/dist/types/session-import/types.d.ts +120 -0
- package/dist/types/setup/paseo/check.d.ts +16 -0
- package/dist/types/setup/paseo/install-saga.d.ts +115 -0
- package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
- package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
- package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
- package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
- package/dist/types/setup/paseo/provider-config.d.ts +35 -0
- package/dist/types/setup/paseo/remove.d.ts +14 -0
- package/dist/types/setup/paseo/result-types.d.ts +59 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
- package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
- package/dist/types/skill-state/active-state.d.ts +5 -1
- package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
- package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
- package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
- package/dist/types/slash-commands/types.d.ts +7 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/tools/ask-answer-registry.d.ts +6 -3
- package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
- package/dist/types/tools/bash.d.ts +2 -6
- package/dist/types/tools/browser/attach.d.ts +11 -0
- package/dist/types/tools/browser/launch.d.ts +20 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
- package/dist/types/tools/browser/render.d.ts +1 -0
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +10 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/monitor.d.ts +1 -1
- package/dist/types/tools/skill-discovery.d.ts +8 -1
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
- package/dist/types/utils/herdr-pane.d.ts +76 -0
- package/package.json +19 -9
- package/scripts/build-binary.ts +2 -1
- package/scripts/build-sdk-package-smoke.ts +2 -13
- package/scripts/capture-sticky-viewport-showcase.ts +0 -1
- package/scripts/compile-args.ts +2 -0
- package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
- package/scripts/generate-sdk-operation-inventory.ts +28 -0
- package/scripts/generate-telegram-baseline-manifest.ts +1 -0
- package/scripts/generate-tool-catalog.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
- package/src/async/job-manager.ts +352 -17
- package/src/async/support.ts +25 -0
- package/src/autoresearch/dashboard.ts +28 -13
- package/src/capability/index.ts +3 -1
- package/src/capability/mcp.ts +6 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/accounts-cli.ts +395 -0
- package/src/cli/args.ts +23 -0
- package/src/cli/auth-broker-cli.ts +136 -66
- package/src/cli/auth-gateway-cli.ts +94 -70
- package/src/cli/config-cli.ts +27 -2
- package/src/cli/crash-cli.ts +78 -0
- package/src/cli/customize-doctor.ts +1605 -0
- package/src/cli/daemon-cli.ts +12 -3
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/mcp-cli.ts +128 -60
- package/src/cli/notify-cli.ts +8 -14
- package/src/cli/root-flags.ts +8 -0
- package/src/cli/setup-cli.ts +145 -0
- package/src/cli/skills-cli.ts +38 -2
- package/src/cli/update-cli.ts +167 -12
- package/src/cli.ts +4 -0
- package/src/commands/accounts.ts +71 -0
- package/src/commands/crash.ts +35 -0
- package/src/commands/customize.ts +84 -0
- package/src/commands/daemon.ts +2 -2
- package/src/commands/deep-interview.ts +2 -0
- package/src/commands/launch.ts +1 -0
- package/src/commands/mcp.ts +12 -7
- package/src/commands/ralplan.ts +2 -0
- package/src/commands/sdk.ts +65 -32
- package/src/commands/setup.ts +5 -0
- package/src/commands/skills.ts +14 -4
- package/src/commands/ultragoal.ts +10 -1
- package/src/commands/update.ts +26 -15
- package/src/config/atomic-yaml-patch-worker.ts +57 -0
- package/src/config/atomic-yaml-patch.ts +769 -29
- package/src/config/config-schema-version.ts +10 -0
- package/src/config/file-lock.ts +70 -1
- package/src/config/mcp-schema.json +52 -14
- package/src/config/model-profile-activation.ts +23 -2
- package/src/config/model-profiles.ts +88 -21
- package/src/config/model-registry.ts +147 -21
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +77 -7
- package/src/config/settings.ts +4038 -199
- package/src/config/skill-settings-defaults.ts +41 -3
- package/src/config/task-delegation.ts +87 -0
- package/src/config/update-channel.ts +23 -0
- package/src/coordinator-mcp/server.ts +377 -308
- package/src/crash/index-store.ts +723 -0
- package/src/crash/nudge.ts +89 -0
- package/src/crash/record-loader.ts +149 -0
- package/src/crash/report.ts +551 -0
- package/src/crash/sanitize.ts +129 -0
- package/src/cursor.ts +36 -10
- package/src/customization/import.ts +946 -0
- package/src/customization/index.ts +4 -0
- package/src/customization/inventory.ts +335 -0
- package/src/customization/mutations.ts +141 -0
- package/src/customization/types.ts +223 -0
- package/src/daemon/control-types.ts +8 -1
- package/src/daemon/operator-contract.ts +6 -6
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
- package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
- package/src/discovery/claude-hooks.ts +38 -0
- package/src/discovery/claude.ts +142 -209
- package/src/discovery/codex-hooks.ts +38 -0
- package/src/discovery/codex.ts +132 -198
- package/src/discovery/helpers.ts +8 -1
- package/src/discovery/index.ts +2 -0
- package/src/discovery/mcp-compat.ts +234 -0
- package/src/discovery/mcp-json.ts +6 -0
- package/src/edit/renderer.ts +40 -0
- package/src/edit/streaming.ts +4 -2
- package/src/exec/bash-executor.ts +24 -20
- package/src/export/html/index.ts +51 -2
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +17 -4
- package/src/extensibility/extensions/types.ts +31 -6
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
- package/src/extensibility/gjc-plugins/installer.ts +9 -5
- package/src/extensibility/gjc-plugins/observability.ts +8 -2
- package/src/extensibility/gjc-plugins/registry.ts +13 -2
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
- package/src/extensibility/gjc-plugins/schema.ts +222 -39
- package/src/extensibility/hooks/loader.ts +154 -10
- package/src/extensibility/runtime-skill-discovery.ts +257 -60
- package/src/extensibility/skill-management.ts +368 -0
- package/src/extensibility/skills.ts +52 -16
- package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
- package/src/gjc-runtime/launch-worktree.ts +56 -2
- package/src/gjc-runtime/linux-proc.ts +7 -1
- package/src/gjc-runtime/ralplan-runtime.ts +100 -164
- package/src/gjc-runtime/session-state-lock.ts +992 -0
- package/src/gjc-runtime/session-state-sidecar.ts +644 -142
- package/src/gjc-runtime/state-writer.ts +39 -14
- package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
- package/src/gjc-runtime/ultragoal-guard.ts +11 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
- package/src/gjc-runtime/workflow-settings.ts +596 -0
- package/src/goals/tools/goal-tool.ts +6 -1
- package/src/hooks/events.ts +291 -0
- package/src/hooks/native-skill-hook.ts +4 -0
- package/src/hooks/normalize.ts +369 -0
- package/src/hooks/skill-state.ts +165 -46
- package/src/internal-urls/docs-index.generated.ts +41 -32
- package/src/main.ts +36 -5
- package/src/migrate/mcp-mapper.ts +25 -2
- package/src/modes/acp/abort-scope.ts +29 -0
- package/src/modes/acp/acp-agent.ts +423 -98
- package/src/modes/components/assistant-message.ts +6 -1
- package/src/modes/components/chrome.ts +4 -3
- package/src/modes/components/customization/customization-dashboard.ts +443 -0
- package/src/modes/components/customization/import-wizard.ts +372 -0
- package/src/modes/components/customization/index.ts +1 -0
- package/src/modes/components/dynamic-theme-text.ts +22 -0
- package/src/modes/components/gajae-pet-widget.ts +607 -193
- package/src/modes/components/irc-sidebar.ts +11 -0
- package/src/modes/components/iterm-pet-transport.ts +585 -0
- package/src/modes/components/model-selector.ts +1 -6
- package/src/modes/components/oauth-selector.ts +277 -86
- package/src/modes/components/pet-capability.ts +35 -31
- package/src/modes/components/settings-selector.ts +29 -5
- package/src/modes/components/status-line/gh.ts +45 -1
- package/src/modes/components/status-line/segments.ts +19 -4
- package/src/modes/components/tool-status-header.ts +13 -3
- package/src/modes/controllers/command-controller.ts +20 -8
- package/src/modes/controllers/extension-ui-controller.ts +56 -257
- package/src/modes/controllers/input-controller.ts +10 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
- package/src/modes/controllers/selector-controller.ts +238 -77
- package/src/modes/controllers/stt-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +88 -16
- package/src/modes/print-mode.ts +35 -6
- package/src/modes/runtime-init.ts +21 -68
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/defaults/ouroboros.json +118 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +25 -0
- package/src/modes/utils/terminal-bell.ts +15 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/critic.md +1 -1
- package/src/prompts/system/rlm-research.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -2
- package/src/prompts/tools/bash.md +7 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +6 -6
- package/src/prompts/tools/read.md +0 -2
- package/src/prompts/tools/skill-discovery.md +4 -2
- package/src/runtime-mcp/client.ts +427 -23
- package/src/runtime-mcp/config.ts +37 -5
- package/src/runtime-mcp/manager-pool.test.ts +56 -0
- package/src/runtime-mcp/manager.ts +52 -2
- package/src/runtime-mcp/oauth-discovery.ts +13 -0
- package/src/runtime-mcp/oauth-flow.ts +46 -1
- package/src/runtime-mcp/pool-key.ts +5 -1
- package/src/runtime-mcp/pool.test.ts +9 -0
- package/src/runtime-mcp/protocol.test.ts +353 -0
- package/src/runtime-mcp/protocol.ts +624 -0
- package/src/runtime-mcp/scope-config.ts +78 -0
- package/src/runtime-mcp/tool-bridge.ts +44 -10
- package/src/runtime-mcp/transports/http.ts +84 -13
- package/src/runtime-mcp/types.ts +123 -12
- package/src/sdk/acp/adapter.ts +328 -97
- package/src/sdk/broker/broker.ts +160 -741
- package/src/sdk/broker/ensure.ts +9 -1
- package/src/sdk/broker/lifecycle.ts +510 -116
- package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
- package/src/sdk/broker/process-incarnation.ts +19 -0
- package/src/sdk/broker/session-index.ts +516 -117
- package/src/sdk/broker/startup-failure.ts +28 -5
- package/src/sdk/broker/transport.ts +1 -13
- package/src/sdk/bus/chat-daemon-cli.ts +4 -2
- package/src/sdk/bus/chat-daemon-control.ts +67 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
- package/src/sdk/bus/config.ts +13 -0
- package/src/sdk/bus/conversation-store.ts +59 -171
- package/src/sdk/bus/daemon-paths.ts +20 -0
- package/src/sdk/bus/discord-conversation.ts +2 -0
- package/src/sdk/bus/discord-daemon.ts +524 -126
- package/src/sdk/bus/discord-live-provider.ts +206 -79
- package/src/sdk/bus/existing-thread-readiness.ts +4 -97
- package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
- package/src/sdk/bus/index.ts +2480 -456
- package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
- package/src/sdk/bus/lifecycle-commands.ts +2 -2
- package/src/sdk/bus/managed-daemon.ts +2 -145
- package/src/sdk/bus/notification-service.ts +196 -0
- package/src/sdk/bus/operator-runtime.ts +1 -1
- package/src/sdk/bus/prompt-reconciliation.ts +24 -15
- package/src/sdk/bus/reconciliation-store.ts +553 -24
- package/src/sdk/bus/session-control.ts +49 -3
- package/src/sdk/bus/slack-conversation.ts +4 -0
- package/src/sdk/bus/slack-daemon.ts +733 -134
- package/src/sdk/bus/slack-live-provider.ts +65 -15
- package/src/sdk/bus/slack-provider.ts +4 -0
- package/src/sdk/bus/slack-thread-binding.ts +9 -104
- package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
- package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
- package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
- package/src/sdk/bus/telegram-daemon-control.ts +3 -13
- package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
- package/src/sdk/bus/telegram-daemon.ts +2552 -2280
- package/src/sdk/bus/telegram-reference.ts +1 -226
- package/src/sdk/bus/telegram-setup.ts +2 -2
- package/src/sdk/bus/threaded-render.ts +1 -0
- package/src/sdk/bus/topic-registry.ts +279 -146
- package/src/sdk/cli/rows.ts +2 -35
- package/src/sdk/cli/session-cli.ts +776 -700
- package/src/sdk/client/client.ts +892 -1
- package/src/sdk/host/control/dispatch.ts +93 -58
- package/src/sdk/host/control/index.ts +1 -1
- package/src/sdk/host/control/operations.ts +18 -10
- package/src/sdk/host/control/runtime-gate.ts +40 -0
- package/src/sdk/host/host.ts +63 -12
- package/src/sdk/host/query/handlers.ts +66 -7
- package/src/sdk/host/reverse-leases.ts +2 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
- package/src/sdk/host/session-runtime.test.ts +2787 -34
- package/src/sdk/host/session-runtime.ts +1489 -101
- package/src/sdk/host/surface-policy.ts +12 -8
- package/src/sdk/host/types.ts +1 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
- package/src/sdk/host/websocket-transport.ts +85 -50
- package/src/sdk/index.ts +2 -1
- package/src/sdk/lifecycle/broker-client.ts +53 -0
- package/src/sdk/lifecycle/client.ts +234 -0
- package/src/sdk/lifecycle/index.ts +3 -0
- package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
- package/src/sdk/lifecycle/service.ts +720 -0
- package/src/sdk/mcp/server.ts +226 -188
- package/src/sdk/prompt-deadline-lease.ts +55 -0
- package/src/sdk/prompt-deadline-manager.ts +148 -0
- package/src/sdk/prompt-status.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +272 -129
- package/src/sdk/protocol/operation-registry.ts +71 -8
- package/src/sdk/receipt-state.ts +43 -0
- package/src/sdk/reconciliation-extensions.ts +14 -0
- package/src/sdk/router/index.ts +1 -0
- package/src/sdk/router/session-router.ts +1658 -0
- package/src/sdk/session-list.ts +93 -0
- package/src/sdk/session-reconnect.ts +45 -1
- package/src/sdk/session.ts +716 -120
- package/src/sdk/transport/serve-cli.ts +18 -34
- package/src/session/account-inventory.ts +603 -0
- package/src/session/agent-session.ts +2873 -559
- package/src/session/agent-storage.ts +35 -0
- package/src/session/blob-store.ts +32 -1
- package/src/session/coordinator-tool-label.ts +40 -0
- package/src/session/internal/managed-session-scope.ts +4 -0
- package/src/session/internal/managed-session-storage.ts +199 -31
- package/src/session/messages.ts +1 -1
- package/src/session/secure-token-file.ts +80 -0
- package/src/session/session-manager.ts +757 -57
- package/src/session/session-storage.ts +25 -16
- package/src/session/startup-auth-config.ts +297 -0
- package/src/session/terminal-abort.ts +1253 -0
- package/src/session/yield-queue.ts +57 -15
- package/src/session-import/claude.ts +334 -0
- package/src/session-import/codex.ts +1033 -0
- package/src/session-import/command.ts +77 -0
- package/src/session-import/detect.ts +133 -0
- package/src/session-import/index.ts +7 -0
- package/src/session-import/provider-codex.ts +327 -0
- package/src/session-import/provider-service.ts +457 -0
- package/src/session-import/redact.ts +128 -0
- package/src/session-import/service.ts +1264 -0
- package/src/session-import/types.ts +148 -0
- package/src/setup/paseo/check.ts +213 -0
- package/src/setup/paseo/install-saga.ts +293 -0
- package/src/setup/paseo/json-publisher.ts +263 -0
- package/src/setup/paseo/orchestration-preferences.ts +86 -0
- package/src/setup/paseo/paseo-ownership.ts +224 -0
- package/src/setup/paseo/paseo-setup.ts +253 -0
- package/src/setup/paseo/provider-config.ts +139 -0
- package/src/setup/paseo/remove.ts +183 -0
- package/src/setup/paseo/result-types.ts +79 -0
- package/src/setup/paseo/setup-deps.ts +175 -0
- package/src/setup/paseo/skills-bridge.ts +260 -0
- package/src/skill-state/active-state.ts +21 -11
- package/src/slash-commands/acp-builtins.ts +9 -1
- package/src/slash-commands/builtin-registry.ts +117 -19
- package/src/slash-commands/helpers/credential-switch.ts +143 -0
- package/src/slash-commands/helpers/mcp.ts +9 -3
- package/src/slash-commands/helpers/usage-report.ts +61 -56
- package/src/slash-commands/types.ts +7 -2
- package/src/task/executor.ts +30 -11
- package/src/task/index.ts +66 -7
- package/src/task/render.ts +23 -9
- package/src/tools/ask-answer-registry.ts +23 -7
- package/src/tools/ask.ts +8 -4
- package/src/tools/bash-sleep-advisory.ts +115 -0
- package/src/tools/bash.ts +98 -18
- package/src/tools/browser/attach.ts +71 -3
- package/src/tools/browser/launch.ts +86 -13
- package/src/tools/browser/profile-discovery.ts +57 -7
- package/src/tools/browser/render.ts +8 -1
- package/src/tools/browser.ts +130 -21
- package/src/tools/computer.enforcement.test.ts +1 -1
- package/src/tools/computer.ts +3 -2
- package/src/tools/cron.ts +12 -1
- package/src/tools/image-gen.ts +349 -142
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +33 -6
- package/src/tools/monitor.ts +41 -2
- package/src/tools/skill-discovery.ts +21 -29
- package/src/tools/subagent.ts +142 -24
- package/src/tools/todo-contract.ts +55 -3
- package/src/tools/todo-write.ts +4 -3
- package/src/tools/tool-catalog.generated.ts +6 -6
- package/src/tools/ultragoal-ask-guard.ts +10 -3
- package/src/utils/herdr-pane.ts +345 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/scrapers/index.ts +6 -4
- package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
- package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
- package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
- package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
- package/dist/types/runtime/lazy-service.test.d.ts +0 -1
- package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
- package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
- package/dist/types/runtime-mcp/config.test.d.ts +0 -1
- package/dist/types/runtime-mcp/http.test.d.ts +0 -1
- package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
- package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
- package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
- package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
- package/dist/types/sdk/elevation/audit.d.ts +0 -93
- package/dist/types/sdk/elevation/capability.d.ts +0 -3
- package/dist/types/sdk/elevation/digest.d.ts +0 -72
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
- package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
- package/dist/types/sdk/elevation/owner.d.ts +0 -48
- package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
- package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
- package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
- package/dist/types/session/auth-broker-config.d.ts +0 -13
- package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
- package/dist/types/skills/skills.test.d.ts +0 -1
- package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
- package/dist/types/task/token-log.test.d.ts +0 -1
- package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
- package/dist/types/tools/descriptors.test.d.ts +0 -1
- package/scripts/g005-daemon-smoke.ts +0 -180
- package/scripts/g011-daemon-path-smoke.ts +0 -153
- package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
- package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
- package/src/sdk/bus/telegram-cli.ts +0 -212
- package/src/sdk/elevation/allowlist.ts +0 -54
- package/src/sdk/elevation/audit.ts +0 -208
- package/src/sdk/elevation/capability.ts +0 -41
- package/src/sdk/elevation/digest.ts +0 -129
- package/src/sdk/elevation/dispatch-receipt.ts +0 -184
- package/src/sdk/elevation/grant-ledger.ts +0 -1142
- package/src/sdk/elevation/owner.ts +0 -109
- package/src/session/auth-broker-config.ts +0 -109
- /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
- /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
- /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
|
@@ -5,7 +5,10 @@ import * as path from "node:path";
|
|
|
5
5
|
import type { AssistantMessage } from "@gajae-code/ai/core";
|
|
6
6
|
import { normalizePathForComparison, postmortem } from "@gajae-code/utils";
|
|
7
7
|
import { withFileLock } from "../config/file-lock";
|
|
8
|
+
import { reduceTerminalReceiptState } from "../sdk/receipt-state";
|
|
9
|
+
import { TOOL_CATALOG } from "../tools/tool-catalog.generated";
|
|
8
10
|
import { sessionRoot, sessionRuntimeDir } from "./session-layout";
|
|
11
|
+
import { SessionStateLockUnavailableError, withSessionStateFileLock } from "./session-state-lock";
|
|
9
12
|
import {
|
|
10
13
|
isValidOwnerIntent,
|
|
11
14
|
lifecyclePaths,
|
|
@@ -45,11 +48,31 @@ export type RuntimeState = "ready_for_input" | "running" | "needs_user_input" |
|
|
|
45
48
|
type FinalResponseSource = "agent_end" | "launch_error";
|
|
46
49
|
const MAX_PUBLIC_ERROR_MESSAGE_LENGTH = 2000;
|
|
47
50
|
const HEARTBEAT_MS = 1000;
|
|
48
|
-
const MAX_PUBLIC_ACTIVE_TOOLS = 8;
|
|
49
|
-
const SAFE_TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Bound on the PUBLIC tool-activity list persisted in the coordinator-shared state file.
|
|
54
|
+
* The list is a "what is running right now" snapshot, not a log: an unbounded public list
|
|
55
|
+
* would let a fan-out turn grow the payload every coordinator reader must parse.
|
|
56
|
+
*
|
|
57
|
+
* Only the public projection is capped. The private `in_flight` table is exact current
|
|
58
|
+
* state — capping it would silently drop a live correlation and make `active_tool_count`
|
|
59
|
+
* a lie — so it holds every currently in-flight call and is emptied by their own ends.
|
|
60
|
+
*/
|
|
61
|
+
const MAX_ACTIVE_TOOL_ENTRIES = 8;
|
|
62
|
+
/** Recorded whenever the caller could not prove a canonical public label for a tool. */
|
|
63
|
+
export const UNPROVEN_TOOL_LABEL = "custom";
|
|
64
|
+
/**
|
|
65
|
+
* The closed vocabulary a persisted or published tool label may draw from: `custom`, or
|
|
66
|
+
* the canonical name of a built-in tool descriptor.
|
|
67
|
+
*
|
|
68
|
+
* A pattern test is not a safety property — `cat`, `curl`, or any other token-shaped
|
|
69
|
+
* string passes one — so an arbitrary label is never accepted merely because it looks
|
|
70
|
+
* safe. Platform-excluded descriptors are included so a snapshot written on one host
|
|
71
|
+
* stays readable on another; the writer still only ever produces labels for the tools it
|
|
72
|
+
* actually resolved.
|
|
73
|
+
*/
|
|
74
|
+
const RUNTIME_TOOL_LABELS: ReadonlySet<string> = new Set([UNPROVEN_TOOL_LABEL, ...Object.keys(TOOL_CATALOG)]);
|
|
75
|
+
|
|
53
76
|
const stateFileWriteChains = new Map<string, Promise<void>>();
|
|
54
77
|
|
|
55
78
|
/** Test-only counters for runtime sidecar hot-path assertions. */
|
|
@@ -64,10 +87,452 @@ interface RuntimeStateEvent {
|
|
|
64
87
|
type: string;
|
|
65
88
|
messages?: unknown[];
|
|
66
89
|
toolCallId?: unknown;
|
|
67
|
-
toolName?: unknown;
|
|
68
90
|
isError?: unknown;
|
|
69
91
|
}
|
|
70
92
|
|
|
93
|
+
export type RuntimeToolActivityPhase = "started" | "finished";
|
|
94
|
+
/**
|
|
95
|
+
* A finished call's outcome. `cancelled` is the only honest answer for a call that was
|
|
96
|
+
* still in flight when the session settled: it neither succeeded nor failed, and nothing
|
|
97
|
+
* observed its end.
|
|
98
|
+
*/
|
|
99
|
+
export type RuntimeToolActivityOutcome = "success" | "failure" | "cancelled";
|
|
100
|
+
const RUNTIME_TOOL_ACTIVITY_OUTCOMES: readonly RuntimeToolActivityOutcome[] = ["success", "failure", "cancelled"];
|
|
101
|
+
/** Full SHA-256 hex: a prefix would let distinct calls collide into one correlation slot. */
|
|
102
|
+
const TOOL_CALL_DIGEST_PATTERN = /^[0-9a-f]{64}$/;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* What a session is doing right now, split into a bounded public projection and the
|
|
106
|
+
* minimum private state needed to keep that projection exact.
|
|
107
|
+
*
|
|
108
|
+
* Deliberately carries no tool arguments, results, command text, paths, output, prompt or
|
|
109
|
+
* model text, environment values, or credentials — only a proven public tool label, a
|
|
110
|
+
* phase, and timing.
|
|
111
|
+
*/
|
|
112
|
+
export interface RuntimeToolActivity {
|
|
113
|
+
seq: number;
|
|
114
|
+
last_activity_at: string;
|
|
115
|
+
tool: string;
|
|
116
|
+
phase: RuntimeToolActivityPhase;
|
|
117
|
+
outcome: RuntimeToolActivityOutcome | null;
|
|
118
|
+
elapsed_ms: number | null;
|
|
119
|
+
/** Exact number of in-flight calls; derived with `active_tools`, so it can never be smaller. */
|
|
120
|
+
active_tool_count: number;
|
|
121
|
+
/** Public projection of the newest in-flight calls, capped at `MAX_ACTIVE_TOOL_ENTRIES`. */
|
|
122
|
+
active_tools: Array<{ tool: string; started_at: string }>;
|
|
123
|
+
/**
|
|
124
|
+
* Private exact correlation state. Each digest is one-way over the session id and the
|
|
125
|
+
* tool call id, so no raw call id is persisted, and the whole field is stripped by
|
|
126
|
+
* `publicRuntimeToolActivity` before any coordinator reader sees the snapshot.
|
|
127
|
+
*/
|
|
128
|
+
in_flight: Array<{ digest: string; tool: string; started_at: string }>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Collapses anything the caller could not prove into `custom`.
|
|
133
|
+
*
|
|
134
|
+
* Public safety comes from the caller's proof plus this closed vocabulary, never from a
|
|
135
|
+
* syntax check: a label that merely looks like a token is still model-influenced text.
|
|
136
|
+
*/
|
|
137
|
+
export function safeRuntimeToolLabel(value: unknown): string {
|
|
138
|
+
return typeof value === "string" && RUNTIME_TOOL_LABELS.has(value) ? value : UNPROVEN_TOOL_LABEL;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Whether a label read back from disk is already one this writer could have produced.
|
|
143
|
+
* Persisted text is never normalized into `custom`: silently rewriting arbitrary disk
|
|
144
|
+
* content into a valid-looking label would publish a value nothing ever proved.
|
|
145
|
+
*/
|
|
146
|
+
function isSafeRuntimeToolLabel(value: unknown): value is string {
|
|
147
|
+
return typeof value === "string" && RUNTIME_TOOL_LABELS.has(value);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Exact own-key sets. An unknown key means the row was written by something else. */
|
|
151
|
+
const RUNTIME_TOOL_ACTIVITY_KEYS: readonly string[] = [
|
|
152
|
+
"seq",
|
|
153
|
+
"last_activity_at",
|
|
154
|
+
"tool",
|
|
155
|
+
"phase",
|
|
156
|
+
"outcome",
|
|
157
|
+
"elapsed_ms",
|
|
158
|
+
"active_tool_count",
|
|
159
|
+
"active_tools",
|
|
160
|
+
"in_flight",
|
|
161
|
+
];
|
|
162
|
+
const IN_FLIGHT_TOOL_CALL_KEYS: readonly string[] = ["digest", "tool", "started_at"];
|
|
163
|
+
const ACTIVE_TOOL_KEYS: readonly string[] = ["tool", "started_at"];
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* An object read back from disk must carry EXACTLY the keys this writer emits.
|
|
167
|
+
*
|
|
168
|
+
* Tolerating extra keys would let a hand-edited or foreign writer smuggle `args`,
|
|
169
|
+
* `result`, `raw_id`, or a credential through every projection: the validator would
|
|
170
|
+
* ignore them and the reader would still see them in the file. Unknown keys make the
|
|
171
|
+
* whole activity malformed instead.
|
|
172
|
+
*/
|
|
173
|
+
function hasExactOwnKeys(value: object, keys: readonly string[]): boolean {
|
|
174
|
+
const own = Object.keys(value);
|
|
175
|
+
return own.length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** A timestamp is accepted only in the exact canonical form this writer emits. */
|
|
179
|
+
function isCanonicalIsoTimestamp(value: unknown): value is string {
|
|
180
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
181
|
+
const parsed = new Date(value);
|
|
182
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function toolActivityPhaseForEvent(event: RuntimeStateEvent): RuntimeToolActivityPhase | null {
|
|
186
|
+
if (event.type === "tool_execution_start") return "started";
|
|
187
|
+
if (event.type === "tool_execution_end") return "finished";
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Length-delimited UTF-16 code units of one identity field.
|
|
193
|
+
*
|
|
194
|
+
* JavaScript strings are UTF-16 code-unit sequences, and a call id may legitimately be
|
|
195
|
+
* any of them. Hashing the default UTF-8 encoding would map every distinct LONE
|
|
196
|
+
* SURROGATE to the same replacement character, so two different call ids would produce
|
|
197
|
+
* one digest and collide into a single in-flight correlation slot. Every code unit is
|
|
198
|
+
* hashed exactly, big-endian, behind its own uint32 length so no concatenation of two
|
|
199
|
+
* fields can be reinterpreted as a different pair.
|
|
200
|
+
*/
|
|
201
|
+
function utf16IdentityField(value: string): Uint8Array {
|
|
202
|
+
const bytes = new Uint8Array(4 + value.length * 2);
|
|
203
|
+
const view = new DataView(bytes.buffer);
|
|
204
|
+
view.setUint32(0, value.length, false);
|
|
205
|
+
for (let index = 0; index < value.length; index++) view.setUint16(4 + index * 2, value.charCodeAt(index), false);
|
|
206
|
+
return bytes;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function toolCallDigest(sessionId: string, toolCallId: unknown): string | null {
|
|
210
|
+
// An all-whitespace id carries no correlation, but a non-empty one is hashed over its
|
|
211
|
+
// exact code units: trimming first would collapse ` a` and `a ` into one in-flight slot.
|
|
212
|
+
if (typeof toolCallId !== "string" || toolCallId.trim().length === 0) return null;
|
|
213
|
+
return createHash("sha256")
|
|
214
|
+
.update(utf16IdentityField(sessionId))
|
|
215
|
+
.update(utf16IdentityField(toolCallId))
|
|
216
|
+
.digest("hex");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function validInFlightToolCall(value: unknown): value is RuntimeToolActivity["in_flight"][number] {
|
|
220
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
221
|
+
if (!hasExactOwnKeys(value, IN_FLIGHT_TOOL_CALL_KEYS)) return false;
|
|
222
|
+
const entry = value as Record<string, unknown>;
|
|
223
|
+
return (
|
|
224
|
+
typeof entry.digest === "string" &&
|
|
225
|
+
TOOL_CALL_DIGEST_PATTERN.test(entry.digest) &&
|
|
226
|
+
isSafeRuntimeToolLabel(entry.tool) &&
|
|
227
|
+
isCanonicalIsoTimestamp(entry.started_at)
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function validPublicActiveTool(value: unknown): boolean {
|
|
232
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
233
|
+
if (!hasExactOwnKeys(value, ACTIVE_TOOL_KEYS)) return false;
|
|
234
|
+
const entry = value as Record<string, unknown>;
|
|
235
|
+
return isSafeRuntimeToolLabel(entry.tool) && isCanonicalIsoTimestamp(entry.started_at);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The count and the capped list are always derived together, so they cannot contradict. */
|
|
239
|
+
function projectedActiveTools(
|
|
240
|
+
inFlight: RuntimeToolActivity["in_flight"],
|
|
241
|
+
): Pick<RuntimeToolActivity, "active_tool_count" | "active_tools"> {
|
|
242
|
+
return {
|
|
243
|
+
active_tool_count: inFlight.length,
|
|
244
|
+
// Newest wins on overflow: the snapshot answers "what is running now".
|
|
245
|
+
active_tools: inFlight
|
|
246
|
+
.slice(-MAX_ACTIVE_TOOL_ENTRIES)
|
|
247
|
+
.map(entry => ({ tool: entry.tool, started_at: entry.started_at })),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export type RuntimeToolActivityReadout =
|
|
252
|
+
| { kind: "absent" }
|
|
253
|
+
| { kind: "malformed" }
|
|
254
|
+
| { kind: "valid"; activity: RuntimeToolActivity };
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Re-validates an activity snapshot read back from disk.
|
|
258
|
+
*
|
|
259
|
+
* `absent` and `malformed` are deliberately distinct outcomes: an absent snapshot may be
|
|
260
|
+
* seeded from sequence 1, while a malformed one must never be replaced by a lower
|
|
261
|
+
* sequence. The public counters are re-derived from the private in-flight table rather
|
|
262
|
+
* than trusted from disk, so a hand-edited file cannot publish a contradiction.
|
|
263
|
+
*/
|
|
264
|
+
export function classifyRuntimeToolActivity(value: unknown): RuntimeToolActivityReadout {
|
|
265
|
+
if (value === undefined) return { kind: "absent" };
|
|
266
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { kind: "malformed" };
|
|
267
|
+
if (!hasExactOwnKeys(value, RUNTIME_TOOL_ACTIVITY_KEYS)) return { kind: "malformed" };
|
|
268
|
+
const activity = value as Record<string, unknown>;
|
|
269
|
+
if (typeof activity.seq !== "number" || !Number.isSafeInteger(activity.seq) || activity.seq < 1)
|
|
270
|
+
return { kind: "malformed" };
|
|
271
|
+
if (!isCanonicalIsoTimestamp(activity.last_activity_at)) return { kind: "malformed" };
|
|
272
|
+
if (!isSafeRuntimeToolLabel(activity.tool)) return { kind: "malformed" };
|
|
273
|
+
if (activity.phase !== "started" && activity.phase !== "finished") return { kind: "malformed" };
|
|
274
|
+
// A started call has not finished, so it can carry neither an outcome nor a duration.
|
|
275
|
+
// A finished one must name an allowlisted outcome, and only a matched success/failure
|
|
276
|
+
// may carry a duration: an unmatched or cancelled call has no measured interval.
|
|
277
|
+
if (activity.phase === "started") {
|
|
278
|
+
if (activity.outcome !== null || activity.elapsed_ms !== null) return { kind: "malformed" };
|
|
279
|
+
} else {
|
|
280
|
+
if (!RUNTIME_TOOL_ACTIVITY_OUTCOMES.includes(activity.outcome as RuntimeToolActivityOutcome))
|
|
281
|
+
return { kind: "malformed" };
|
|
282
|
+
if (activity.elapsed_ms !== null) {
|
|
283
|
+
if (activity.outcome === "cancelled") return { kind: "malformed" };
|
|
284
|
+
// A duration is milliseconds of wall clock. Beyond the safe-integer range it is
|
|
285
|
+
// no longer an exact value, so it can never be one this writer measured.
|
|
286
|
+
if (
|
|
287
|
+
typeof activity.elapsed_ms !== "number" ||
|
|
288
|
+
!Number.isSafeInteger(activity.elapsed_ms) ||
|
|
289
|
+
activity.elapsed_ms < 0
|
|
290
|
+
)
|
|
291
|
+
return { kind: "malformed" };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// One invalid private row makes the whole snapshot malformed. Filtering rows and
|
|
295
|
+
// continuing would publish an exact-looking count derived from a file that was
|
|
296
|
+
// already corrupt.
|
|
297
|
+
if (!Array.isArray(activity.in_flight) || !activity.in_flight.every(validInFlightToolCall))
|
|
298
|
+
return { kind: "malformed" };
|
|
299
|
+
// The public rows are validated in their own right, not merely re-derived: an extra
|
|
300
|
+
// key on a public row is exactly what a coordinator reader would receive.
|
|
301
|
+
if (!Array.isArray(activity.active_tools) || !activity.active_tools.every(validPublicActiveTool))
|
|
302
|
+
return { kind: "malformed" };
|
|
303
|
+
const inFlight = (activity.in_flight as RuntimeToolActivity["in_flight"]).map(entry => ({
|
|
304
|
+
digest: entry.digest,
|
|
305
|
+
tool: entry.tool,
|
|
306
|
+
started_at: entry.started_at,
|
|
307
|
+
}));
|
|
308
|
+
if (new Set(inFlight.map(entry => entry.digest)).size !== inFlight.length) return { kind: "malformed" };
|
|
309
|
+
// A cancelled outcome is terminal settlement: it is only ever written together with an
|
|
310
|
+
// emptied in-flight set, so a cancelled snapshot that still claims live calls was not
|
|
311
|
+
// produced here.
|
|
312
|
+
if (activity.outcome === "cancelled" && inFlight.length > 0) return { kind: "malformed" };
|
|
313
|
+
// The persisted public counters must already equal the projection of the private set;
|
|
314
|
+
// repairing a contradiction here would republish a hand-edited file as authoritative.
|
|
315
|
+
const projected = projectedActiveTools(inFlight);
|
|
316
|
+
if (
|
|
317
|
+
activity.active_tool_count !== projected.active_tool_count ||
|
|
318
|
+
JSON.stringify(activity.active_tools) !== JSON.stringify(projected.active_tools)
|
|
319
|
+
)
|
|
320
|
+
return { kind: "malformed" };
|
|
321
|
+
return {
|
|
322
|
+
kind: "valid",
|
|
323
|
+
activity: {
|
|
324
|
+
seq: activity.seq,
|
|
325
|
+
last_activity_at: activity.last_activity_at,
|
|
326
|
+
tool: activity.tool,
|
|
327
|
+
phase: activity.phase,
|
|
328
|
+
outcome: (activity.outcome as RuntimeToolActivityOutcome | null) ?? null,
|
|
329
|
+
elapsed_ms: activity.elapsed_ms as number | null,
|
|
330
|
+
...projected,
|
|
331
|
+
in_flight: inFlight,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function normalizedRuntimeToolActivity(value: unknown): RuntimeToolActivity | null {
|
|
337
|
+
const readout = classifyRuntimeToolActivity(value);
|
|
338
|
+
return readout.kind === "valid" ? readout.activity : null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** The lifecycle states that are authority that nothing can still be running. */
|
|
342
|
+
const TERMINAL_LIFECYCLE_STATES: ReadonlySet<string> = new Set(["completed", "errored"]);
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Whether an individually valid snapshot is consistent with the lifecycle state it would
|
|
346
|
+
* be published NEXT TO.
|
|
347
|
+
*
|
|
348
|
+
* The two fields are written by different authorities — the runtime sidecar annotates
|
|
349
|
+
* activity, the Coordinator writes lifecycle — so each can be valid while the pair is a
|
|
350
|
+
* contradiction: a settled session that still claims a tool is starting, or a live one
|
|
351
|
+
* whose calls were cancelled by a terminal it never reached. Publishing either would tell
|
|
352
|
+
* a coordinator reader something no writer ever observed.
|
|
353
|
+
*
|
|
354
|
+
* A terminal snapshot may still describe the call that just ended, and the terminal
|
|
355
|
+
* `cancelled` shape is exactly how settlement reports orphaned calls, so both stay
|
|
356
|
+
* publishable.
|
|
357
|
+
*/
|
|
358
|
+
function activityMatchesLifecycle(activity: RuntimeToolActivity, lifecycleState: unknown): boolean {
|
|
359
|
+
if (typeof lifecycleState === "string" && TERMINAL_LIFECYCLE_STATES.has(lifecycleState)) {
|
|
360
|
+
if (activity.phase === "started") return false;
|
|
361
|
+
return activity.in_flight.length === 0 && activity.active_tool_count === 0;
|
|
362
|
+
}
|
|
363
|
+
return activity.outcome !== "cancelled";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* The only activity shape a coordinator reader may see: the private correlation digests
|
|
368
|
+
* are dropped here, so no projection can leak them, and a snapshot that contradicts the
|
|
369
|
+
* lifecycle state it accompanies is withheld entirely.
|
|
370
|
+
*
|
|
371
|
+
* Withheld, never repaired: the bytes on disk are preserved exactly as written — a
|
|
372
|
+
* malformed or contradictory snapshot is evidence — while every public projection shows
|
|
373
|
+
* nothing rather than a reconciled guess.
|
|
374
|
+
*/
|
|
375
|
+
export function publicRuntimeToolActivity(value: unknown, lifecycleState: unknown): Record<string, unknown> | null {
|
|
376
|
+
const activity = normalizedRuntimeToolActivity(value);
|
|
377
|
+
if (!activity || !activityMatchesLifecycle(activity, lifecycleState)) return null;
|
|
378
|
+
return {
|
|
379
|
+
seq: activity.seq,
|
|
380
|
+
last_activity_at: activity.last_activity_at,
|
|
381
|
+
tool: activity.tool,
|
|
382
|
+
phase: activity.phase,
|
|
383
|
+
outcome: activity.outcome,
|
|
384
|
+
elapsed_ms: activity.elapsed_ms,
|
|
385
|
+
active_tool_count: activity.active_tool_count,
|
|
386
|
+
active_tools: activity.active_tools,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* What the caller observed at the exact moment the agent event was dispatched.
|
|
392
|
+
*
|
|
393
|
+
* Both the timestamp and the label are captured at that synchronous boundary, never
|
|
394
|
+
* re-derived here: a later lookup would see a tool registry that may already have been
|
|
395
|
+
* replaced, and a later clock reading would fold subscriber and lock latency into the
|
|
396
|
+
* measured interval.
|
|
397
|
+
*/
|
|
398
|
+
export interface CoordinatorToolObservation {
|
|
399
|
+
/** Canonical public label proven at the observation boundary; anything else is `custom`. */
|
|
400
|
+
label: string;
|
|
401
|
+
/** Wall clock captured when the agent event was observed, before any queueing or lock wait. */
|
|
402
|
+
observedAt: string;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
interface RuntimeToolObservation {
|
|
406
|
+
phase: RuntimeToolActivityPhase;
|
|
407
|
+
/** Canonical public label proven by the caller; anything else records `custom`. */
|
|
408
|
+
label: string | undefined;
|
|
409
|
+
/** One-way correlation digest, or null when the event carried no usable call id. */
|
|
410
|
+
digest: string | null;
|
|
411
|
+
isError: boolean;
|
|
412
|
+
/** Wall clock captured when the event was observed, before any queueing or lock wait. */
|
|
413
|
+
observedAt: string;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function nextInFlightToolCalls(
|
|
417
|
+
previous: RuntimeToolActivity["in_flight"],
|
|
418
|
+
observation: RuntimeToolObservation,
|
|
419
|
+
tool: string,
|
|
420
|
+
): RuntimeToolActivity["in_flight"] {
|
|
421
|
+
// An event without a usable call id cannot be correlated, so it must neither add nor
|
|
422
|
+
// remove exact accounting state.
|
|
423
|
+
if (observation.digest === null) return previous;
|
|
424
|
+
if (observation.phase === "started") {
|
|
425
|
+
// A valid start is idempotent by digest, and a repeat keeps the first observation.
|
|
426
|
+
if (previous.some(entry => entry.digest === observation.digest)) return previous;
|
|
427
|
+
// Never capped: every live correlation is kept so the count stays exact.
|
|
428
|
+
return [...previous, { digest: observation.digest, tool, started_at: observation.observedAt }];
|
|
429
|
+
}
|
|
430
|
+
// Only a confirmed active call is closed; a duplicate or unmatched end changes nothing.
|
|
431
|
+
const index = previous.findIndex(entry => entry.digest === observation.digest);
|
|
432
|
+
if (index < 0) return previous;
|
|
433
|
+
return previous.filter((_entry, position) => position !== index);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Sequence is monotonic; at the safe-integer ceiling it stops rather than wrapping. */
|
|
437
|
+
function nextRuntimeToolActivitySeq(previous: number): number {
|
|
438
|
+
return Number.isSafeInteger(previous + 1) ? previous + 1 : previous;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Milliseconds between two observed events, or `null` when there is no exact answer.
|
|
443
|
+
*
|
|
444
|
+
* The reader accepts only a non-negative safe integer, because past that a millisecond
|
|
445
|
+
* count is no longer an exact value and so cannot be one anything measured. The writer
|
|
446
|
+
* therefore has to reach the same verdict on the same rule, or it can persist a number its
|
|
447
|
+
* own validator calls malformed — and a malformed snapshot fences out every later activity
|
|
448
|
+
* write on that session.
|
|
449
|
+
*
|
|
450
|
+
* Two canonical timestamps really can be that far apart: the representable `Date` range
|
|
451
|
+
* spans 1.728e16 ms, nearly twice `MAX_SAFE_INTEGER`. Saturating at the ceiling would
|
|
452
|
+
* publish a duration nothing observed, so an unmeasurable interval is reported as exactly
|
|
453
|
+
* that — the outcome still stands, only the interval is absent.
|
|
454
|
+
*/
|
|
455
|
+
function measuredElapsedMs(startedAtMs: number, observedAtMs: number): number | null {
|
|
456
|
+
if (!Number.isFinite(startedAtMs) || !Number.isFinite(observedAtMs)) return null;
|
|
457
|
+
const elapsed = Math.round(observedAtMs - startedAtMs);
|
|
458
|
+
return Number.isSafeInteger(elapsed) && elapsed >= 0 ? elapsed : null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function nextRuntimeToolActivity(
|
|
462
|
+
previous: RuntimeToolActivity | null,
|
|
463
|
+
observation: RuntimeToolObservation,
|
|
464
|
+
): RuntimeToolActivity {
|
|
465
|
+
const priorInFlight = previous?.in_flight ?? [];
|
|
466
|
+
const match =
|
|
467
|
+
observation.phase === "finished" && observation.digest !== null
|
|
468
|
+
? priorInFlight.find(entry => entry.digest === observation.digest)
|
|
469
|
+
: undefined;
|
|
470
|
+
// A matched end reports the label its own START proved. Re-labelling from the end
|
|
471
|
+
// event would read a registry that may have been replaced mid-call, so one call could
|
|
472
|
+
// open as `bash` and close as something else. An unmatched end proved nothing.
|
|
473
|
+
const tool = match ? match.tool : safeRuntimeToolLabel(observation.label);
|
|
474
|
+
const startedAtMs = match ? Date.parse(match.started_at) : Number.NaN;
|
|
475
|
+
const inFlight = nextInFlightToolCalls(priorInFlight, observation, tool);
|
|
476
|
+
return {
|
|
477
|
+
seq: nextRuntimeToolActivitySeq(previous?.seq ?? 0),
|
|
478
|
+
last_activity_at: observation.observedAt,
|
|
479
|
+
tool,
|
|
480
|
+
phase: observation.phase,
|
|
481
|
+
outcome: observation.phase === "started" ? null : observation.isError ? "failure" : "success",
|
|
482
|
+
// Both ends are event observation times, so lock contention cannot compress a real
|
|
483
|
+
// interval into near zero.
|
|
484
|
+
elapsed_ms: measuredElapsedMs(startedAtMs, Date.parse(observation.observedAt)),
|
|
485
|
+
...projectedActiveTools(inFlight),
|
|
486
|
+
in_flight: inFlight,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Settle the activity snapshot a terminal lifecycle transition inherits.
|
|
492
|
+
*
|
|
493
|
+
* A settled session cannot still be running a tool, and later tool events are fenced out,
|
|
494
|
+
* so an unmatched start would otherwise leave a nonzero `active_tool_count` forever. The
|
|
495
|
+
* orphans are represented honestly — finished, `cancelled`, no elapsed interval — rather
|
|
496
|
+
* than claimed as a success or a failure nothing observed.
|
|
497
|
+
*
|
|
498
|
+
* The same helper serves the runtime `agent_end`/postmortem path and the Coordinator's
|
|
499
|
+
* canonical terminal repair so the two writers cannot settle differently.
|
|
500
|
+
*
|
|
501
|
+
* Two shapes need settling, not one. An orphaned in-flight call is the obvious case. The
|
|
502
|
+
* other is a `phase: started` snapshot with NOTHING correlated — a start whose call id was
|
|
503
|
+
* missing or blank never entered the in-flight table, so no end can ever close it — and
|
|
504
|
+
* leaving it would write the prohibited terminal+started pair, a settled session that
|
|
505
|
+
* still claims a tool is starting. An already-finished snapshot with nothing in flight is
|
|
506
|
+
* returned unchanged: a normal terminal transition is not an activity event.
|
|
507
|
+
*/
|
|
508
|
+
export function terminallySettledRuntimeToolActivity(value: unknown, observedAt: string): RuntimeToolActivityReadout {
|
|
509
|
+
const readout = classifyRuntimeToolActivity(value);
|
|
510
|
+
if (readout.kind !== "valid") return readout;
|
|
511
|
+
const previous = readout.activity;
|
|
512
|
+
if (previous.in_flight.length === 0 && previous.phase !== "started") return readout;
|
|
513
|
+
const previousMs = Date.parse(previous.last_activity_at);
|
|
514
|
+
const observedMs = Date.parse(observedAt);
|
|
515
|
+
// Terminal authority wins even at the sequence ceiling, where seq stays put; the
|
|
516
|
+
// last-observation timestamp never moves backwards.
|
|
517
|
+
const lastActivityAt =
|
|
518
|
+
isCanonicalIsoTimestamp(observedAt) && (!Number.isFinite(previousMs) || observedMs >= previousMs)
|
|
519
|
+
? observedAt
|
|
520
|
+
: previous.last_activity_at;
|
|
521
|
+
return {
|
|
522
|
+
kind: "valid",
|
|
523
|
+
activity: {
|
|
524
|
+
seq: nextRuntimeToolActivitySeq(previous.seq),
|
|
525
|
+
last_activity_at: lastActivityAt,
|
|
526
|
+
tool: previous.tool,
|
|
527
|
+
phase: "finished",
|
|
528
|
+
outcome: "cancelled",
|
|
529
|
+
elapsed_ms: null,
|
|
530
|
+
...projectedActiveTools([]),
|
|
531
|
+
in_flight: [],
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
71
536
|
export interface OwnerTerminalContext {
|
|
72
537
|
generation: string;
|
|
73
538
|
stateDir: string;
|
|
@@ -373,96 +838,9 @@ export function stateForEvent(event: RuntimeStateEvent): RuntimeState | null {
|
|
|
373
838
|
return null;
|
|
374
839
|
}
|
|
375
840
|
|
|
841
|
+
/** True for every event the coordinator-shared file records: lifecycle state or tool activity. */
|
|
376
842
|
export function eventAffectsCoordinatorRuntimeState(event: RuntimeStateEvent): boolean {
|
|
377
|
-
return stateForEvent(event) !== null ||
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function safeToolName(value: unknown): string {
|
|
381
|
-
return typeof value === "string" && SAFE_TOOL_NAME.test(value) ? value : "custom";
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function toolActivityForEvent(
|
|
385
|
-
event: RuntimeStateEvent,
|
|
386
|
-
):
|
|
387
|
-
| { phase: "started"; toolCallKey: string; toolName: string }
|
|
388
|
-
| { phase: "finished"; toolCallKey: string; toolName: string; outcome: "succeeded" | "failed" }
|
|
389
|
-
| null {
|
|
390
|
-
if (event.type !== "tool_execution_start" && event.type !== "tool_execution_end") return null;
|
|
391
|
-
if (typeof event.toolCallId !== "string") return null;
|
|
392
|
-
const toolCallKey = createHash("sha256").update(event.toolCallId).digest("hex");
|
|
393
|
-
if (event.type === "tool_execution_start")
|
|
394
|
-
return { phase: "started", toolCallKey, toolName: safeToolName(event.toolName) };
|
|
395
|
-
return {
|
|
396
|
-
phase: "finished",
|
|
397
|
-
toolCallKey,
|
|
398
|
-
toolName: safeToolName(event.toolName),
|
|
399
|
-
outcome: event.isError === true ? "failed" : "succeeded",
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function runtimeStateFromPrevious(value: unknown): RuntimeState {
|
|
404
|
-
return value === "ready_for_input" ||
|
|
405
|
-
value === "running" ||
|
|
406
|
-
value === "needs_user_input" ||
|
|
407
|
-
value === "completed" ||
|
|
408
|
-
value === "errored"
|
|
409
|
-
? value
|
|
410
|
-
: "running";
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function activityFieldsForEvent(
|
|
414
|
-
previous: Record<string, unknown>,
|
|
415
|
-
activityEvent: NonNullable<ReturnType<typeof toolActivityForEvent>>,
|
|
416
|
-
now: string,
|
|
417
|
-
nowMs: number,
|
|
418
|
-
): Record<string, unknown> {
|
|
419
|
-
const activeToolCalls: Record<string, { tool_name: string; started_at: string }> = {};
|
|
420
|
-
const existing = previous.active_tool_calls;
|
|
421
|
-
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
422
|
-
for (const [key, value] of Object.entries(existing)) {
|
|
423
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
424
|
-
const entry = value as Record<string, unknown>;
|
|
425
|
-
if (typeof entry.tool_name !== "string" || typeof entry.started_at !== "string") continue;
|
|
426
|
-
if (!Number.isFinite(Date.parse(entry.started_at))) continue;
|
|
427
|
-
activeToolCalls[key] = { tool_name: safeToolName(entry.tool_name), started_at: entry.started_at };
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
const prior = activeToolCalls[activityEvent.toolCallKey];
|
|
431
|
-
if (activityEvent.phase === "started")
|
|
432
|
-
activeToolCalls[activityEvent.toolCallKey] = { tool_name: activityEvent.toolName, started_at: now };
|
|
433
|
-
else delete activeToolCalls[activityEvent.toolCallKey];
|
|
434
|
-
const previousActivity =
|
|
435
|
-
previous.activity && typeof previous.activity === "object" && !Array.isArray(previous.activity)
|
|
436
|
-
? (previous.activity as Record<string, unknown>)
|
|
437
|
-
: null;
|
|
438
|
-
const sequence =
|
|
439
|
-
previousActivity &&
|
|
440
|
-
typeof previousActivity.sequence === "number" &&
|
|
441
|
-
Number.isSafeInteger(previousActivity.sequence) &&
|
|
442
|
-
previousActivity.sequence >= 0
|
|
443
|
-
? Math.min(Number.MAX_SAFE_INTEGER, previousActivity.sequence + 1)
|
|
444
|
-
: 1;
|
|
445
|
-
const activeTools = Object.values(activeToolCalls)
|
|
446
|
-
.sort((left, right) => left.started_at.localeCompare(right.started_at))
|
|
447
|
-
.slice(0, MAX_PUBLIC_ACTIVE_TOOLS);
|
|
448
|
-
const startedAt = activityEvent.phase === "started" ? now : prior?.started_at;
|
|
449
|
-
const elapsedMs = startedAt ? Math.max(0, nowMs - Date.parse(startedAt)) : undefined;
|
|
450
|
-
return {
|
|
451
|
-
last_activity_at: now,
|
|
452
|
-
activity: {
|
|
453
|
-
sequence,
|
|
454
|
-
kind: "tool_execution",
|
|
455
|
-
phase: activityEvent.phase,
|
|
456
|
-
tool_name: activityEvent.toolName,
|
|
457
|
-
observed_at: now,
|
|
458
|
-
...(startedAt ? { started_at: startedAt } : {}),
|
|
459
|
-
...(elapsedMs === undefined ? {} : { elapsed_ms: elapsedMs }),
|
|
460
|
-
...(activityEvent.phase === "finished" ? { outcome: activityEvent.outcome } : {}),
|
|
461
|
-
active_tool_count: Object.keys(activeToolCalls).length,
|
|
462
|
-
},
|
|
463
|
-
active_tools: activeTools,
|
|
464
|
-
active_tool_calls: activeToolCalls,
|
|
465
|
-
};
|
|
843
|
+
return stateForEvent(event) !== null || toolActivityPhaseForEvent(event) !== null;
|
|
466
844
|
}
|
|
467
845
|
|
|
468
846
|
class PreviousRuntimeStateReadError extends Error {
|
|
@@ -472,6 +850,13 @@ class PreviousRuntimeStateReadError extends Error {
|
|
|
472
850
|
}
|
|
473
851
|
}
|
|
474
852
|
|
|
853
|
+
class RuntimeToolActivityRefusedError extends Error {
|
|
854
|
+
constructor(reason: string) {
|
|
855
|
+
super(`Refusing to overwrite the coordinator tool-activity snapshot: ${reason}.`);
|
|
856
|
+
this.name = "RuntimeToolActivityRefusedError";
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
475
860
|
function isAbsentStateFileError(error: unknown): boolean {
|
|
476
861
|
return (error as { code?: unknown }).code === "ENOENT";
|
|
477
862
|
}
|
|
@@ -520,7 +905,7 @@ function validPreviousRuntimeStatePayload(value: unknown): value is Record<strin
|
|
|
520
905
|
)
|
|
521
906
|
return false;
|
|
522
907
|
if (payload.ready_for_input !== undefined) {
|
|
523
|
-
const expectedReady = payload.state === "ready_for_input"
|
|
908
|
+
const expectedReady = payload.state === "ready_for_input";
|
|
524
909
|
if (payload.ready_for_input !== expectedReady) return false;
|
|
525
910
|
}
|
|
526
911
|
if (payload.live !== undefined && payload.live !== null && payload.live !== (payload.state === "running"))
|
|
@@ -533,40 +918,38 @@ function readPreviousPayload(stateFile: string): Record<string, unknown> {
|
|
|
533
918
|
try {
|
|
534
919
|
raw = fsSync.readFileSync(stateFile, "utf8");
|
|
535
920
|
} catch (error) {
|
|
536
|
-
lastPayloadByStateFile.delete(stateFile);
|
|
537
921
|
if (isAbsentStateFileError(error)) return {};
|
|
538
922
|
throw new PreviousRuntimeStateReadError();
|
|
539
923
|
}
|
|
540
924
|
try {
|
|
541
925
|
return parsePreviousPayload(raw);
|
|
542
926
|
} catch (error) {
|
|
543
|
-
lastPayloadByStateFile.delete(stateFile);
|
|
544
927
|
if (error instanceof PreviousRuntimeStateReadError) throw error;
|
|
545
928
|
throw new PreviousRuntimeStateReadError();
|
|
546
929
|
}
|
|
547
930
|
}
|
|
548
931
|
|
|
932
|
+
/**
|
|
933
|
+
* Reads the authoritative bytes on disk, every time, inside the state-file lock.
|
|
934
|
+
*
|
|
935
|
+
* There is deliberately no metadata (mtime+size) cache here: another process holding the
|
|
936
|
+
* same lock can rewrite this file to a same-size terminal payload within one filesystem
|
|
937
|
+
* timestamp tick, and a cache hit would then let a late tool event overwrite a settled
|
|
938
|
+
* session back into a running-looking one. The file is small and the read happens once
|
|
939
|
+
* per lock acquisition.
|
|
940
|
+
*/
|
|
549
941
|
async function readPreviousPayloadForEvent(stateFile: string): Promise<Record<string, unknown>> {
|
|
550
942
|
let stat: fsSync.Stats;
|
|
551
943
|
try {
|
|
552
944
|
stat = await fs.stat(stateFile);
|
|
553
945
|
} catch (error) {
|
|
554
|
-
lastPayloadByStateFile.delete(stateFile);
|
|
555
946
|
if (isAbsentStateFileError(error)) return {};
|
|
556
947
|
throw new PreviousRuntimeStateReadError();
|
|
557
948
|
}
|
|
558
|
-
if (!stat.isFile())
|
|
559
|
-
lastPayloadByStateFile.delete(stateFile);
|
|
560
|
-
throw new PreviousRuntimeStateReadError();
|
|
561
|
-
}
|
|
562
|
-
const cached = lastPayloadByStateFile.get(stateFile);
|
|
563
|
-
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) return cached.payload;
|
|
949
|
+
if (!stat.isFile()) throw new PreviousRuntimeStateReadError();
|
|
564
950
|
try {
|
|
565
|
-
|
|
566
|
-
lastPayloadByStateFile.set(stateFile, { mtimeMs: stat.mtimeMs, size: stat.size, payload });
|
|
567
|
-
return payload;
|
|
951
|
+
return parsePreviousPayload(await fs.readFile(stateFile, "utf8"));
|
|
568
952
|
} catch (error) {
|
|
569
|
-
lastPayloadByStateFile.delete(stateFile);
|
|
570
953
|
if (error instanceof PreviousRuntimeStateReadError) throw error;
|
|
571
954
|
throw new PreviousRuntimeStateReadError();
|
|
572
955
|
}
|
|
@@ -591,14 +974,6 @@ function shouldSkipRuntimeStateWrite(
|
|
|
591
974
|
return nowMs - previousUpdatedAt < HEARTBEAT_MS;
|
|
592
975
|
}
|
|
593
976
|
|
|
594
|
-
function rememberWrittenPayload(stateFile: string, payload: Record<string, unknown>): void {
|
|
595
|
-
try {
|
|
596
|
-
const stat = fsSync.statSync(stateFile);
|
|
597
|
-
lastPayloadByStateFile.set(stateFile, { mtimeMs: stat.mtimeMs, size: stat.size, payload });
|
|
598
|
-
} catch {
|
|
599
|
-
lastPayloadByStateFile.delete(stateFile);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
977
|
function shouldPreserveTerminalPayload(previous: RuntimeStateSidecarPayload, input: RuntimeStateIdentity): boolean {
|
|
603
978
|
if (!validRuntimeStateMarker(previous)) return false;
|
|
604
979
|
if (previous.state !== "completed" && previous.state !== "errored") return false;
|
|
@@ -661,11 +1036,22 @@ function basePayload(input: {
|
|
|
661
1036
|
}): Record<string, unknown> {
|
|
662
1037
|
const identity = normalizedIdentity(input.context);
|
|
663
1038
|
if (identity.sessionId !== input.sessionId) throw new PreviousRuntimeStateReadError();
|
|
1039
|
+
// A lifecycle transition says nothing about tool activity, so a valid snapshot is
|
|
1040
|
+
// carried forward and a malformed one verbatim: dropping it would silently reset the
|
|
1041
|
+
// per-session sequence, and the public projection refuses to publish it either way.
|
|
1042
|
+
// A TERMINAL transition is the one exception — it is authority that nothing can still
|
|
1043
|
+
// be running, so any orphaned in-flight call is settled here.
|
|
1044
|
+
const terminal = input.state === "completed" || input.state === "errored";
|
|
1045
|
+
const readout = terminal
|
|
1046
|
+
? terminallySettledRuntimeToolActivity(input.previous.activity, input.now)
|
|
1047
|
+
: classifyRuntimeToolActivity(input.previous.activity);
|
|
1048
|
+
const activity =
|
|
1049
|
+
readout.kind === "absent" ? undefined : readout.kind === "valid" ? readout.activity : input.previous.activity;
|
|
664
1050
|
return {
|
|
665
1051
|
schema_version: 1,
|
|
666
1052
|
session_id: identity.sessionId,
|
|
667
1053
|
state: input.state,
|
|
668
|
-
ready_for_input: input.state === "
|
|
1054
|
+
ready_for_input: input.state === "ready_for_input",
|
|
669
1055
|
updated_at: input.now,
|
|
670
1056
|
current_turn_id: typeof input.previous.current_turn_id === "string" ? input.previous.current_turn_id : null,
|
|
671
1057
|
last_turn_id: typeof input.previous.last_turn_id === "string" ? input.previous.last_turn_id : null,
|
|
@@ -677,16 +1063,7 @@ function basePayload(input: {
|
|
|
677
1063
|
workdir: identity.workdir,
|
|
678
1064
|
branch: branchForContext(input.context),
|
|
679
1065
|
session_file: identity.sessionFile,
|
|
680
|
-
...(
|
|
681
|
-
? { last_activity_at: input.previous.last_activity_at }
|
|
682
|
-
: {}),
|
|
683
|
-
...(input.previous.activity && typeof input.previous.activity === "object"
|
|
684
|
-
? { activity: input.previous.activity }
|
|
685
|
-
: {}),
|
|
686
|
-
...(Array.isArray(input.previous.active_tools) ? { active_tools: input.previous.active_tools } : {}),
|
|
687
|
-
...(input.previous.active_tool_calls && typeof input.previous.active_tool_calls === "object"
|
|
688
|
-
? { active_tool_calls: input.previous.active_tool_calls }
|
|
689
|
-
: {}),
|
|
1066
|
+
...(activity === undefined ? {} : { activity }),
|
|
690
1067
|
...(input.context.ownerTerminal ? { owner_generation: input.context.ownerTerminal.generation } : {}),
|
|
691
1068
|
};
|
|
692
1069
|
}
|
|
@@ -849,13 +1226,20 @@ async function writeStateFileSync(stateFile: string, payload: Record<string, unk
|
|
|
849
1226
|
await writeStateFile(stateFile, payload);
|
|
850
1227
|
}
|
|
851
1228
|
|
|
1229
|
+
/**
|
|
1230
|
+
* The state-file critical section, shared byte-for-byte with the Coordinator MCP writer.
|
|
1231
|
+
*
|
|
1232
|
+
* The shared implementation writes the base Coordinator's regular-file `<file>.lock`
|
|
1233
|
+
* owner JSON, so both writers recognize each other's owners. The base RUNTIME guarded
|
|
1234
|
+
* this same path with the generic directory-style lock instead, so a leftover
|
|
1235
|
+
* `<file>.lock/` directory is also recognized and reclaimed under its own protocol rather
|
|
1236
|
+
* than faulting forever.
|
|
1237
|
+
*/
|
|
852
1238
|
async function withStateFileLock<T>(stateFile: string, operation: () => Promise<T>): Promise<T> {
|
|
853
1239
|
try {
|
|
854
|
-
return await
|
|
1240
|
+
return await withSessionStateFileLock(stateFile, operation);
|
|
855
1241
|
} catch (error) {
|
|
856
|
-
if (error instanceof
|
|
857
|
-
throw new PreviousRuntimeStateReadError();
|
|
858
|
-
}
|
|
1242
|
+
if (error instanceof SessionStateLockUnavailableError) throw new PreviousRuntimeStateReadError();
|
|
859
1243
|
throw error;
|
|
860
1244
|
}
|
|
861
1245
|
}
|
|
@@ -864,14 +1248,28 @@ function coordinatorTransactionLockFile(stateFile: string): string {
|
|
|
864
1248
|
return path.resolve(path.dirname(stateFile), "..", "locks", "mutation.lock");
|
|
865
1249
|
}
|
|
866
1250
|
|
|
1251
|
+
/**
|
|
1252
|
+
* The outer namespace-wide transaction lock. It guards no single JSON document of its
|
|
1253
|
+
* own, so it stays on the generic directory-style lock rather than the state-file owner
|
|
1254
|
+
* protocol.
|
|
1255
|
+
*/
|
|
867
1256
|
async function withCoordinatorTransactionLock<T>(stateFile: string, operation: () => Promise<T>): Promise<T> {
|
|
868
|
-
|
|
1257
|
+
try {
|
|
1258
|
+
return await withFileLock(coordinatorTransactionLockFile(stateFile), operation, {
|
|
1259
|
+
staleMs: 30_000,
|
|
1260
|
+
retries: 12_000,
|
|
1261
|
+
retryDelayMs: 5,
|
|
1262
|
+
});
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
if (error instanceof Error && error.message.startsWith("Failed to acquire lock"))
|
|
1265
|
+
throw new PreviousRuntimeStateReadError();
|
|
1266
|
+
throw error;
|
|
1267
|
+
}
|
|
869
1268
|
}
|
|
870
1269
|
|
|
871
1270
|
async function writeStateFile(stateFile: string, payload: Record<string, unknown>): Promise<void> {
|
|
872
1271
|
await fs.mkdir(path.dirname(stateFile), { recursive: true });
|
|
873
1272
|
await Bun.write(stateFile, `${JSON.stringify(payload)}\n`);
|
|
874
|
-
rememberWrittenPayload(stateFile, payload);
|
|
875
1273
|
}
|
|
876
1274
|
|
|
877
1275
|
function contextWithManagedOwnerGeneration(context: RuntimeStateContext): RuntimeStateContext {
|
|
@@ -881,15 +1279,93 @@ function contextWithManagedOwnerGeneration(context: RuntimeStateContext): Runtim
|
|
|
881
1279
|
return ownerTerminal ? { ...context, ownerTerminal } : context;
|
|
882
1280
|
}
|
|
883
1281
|
|
|
1282
|
+
/**
|
|
1283
|
+
* Annotates the existing coordinator-shared runtime state with a tool-activity snapshot.
|
|
1284
|
+
*
|
|
1285
|
+
* Activity is an annotation on a lifecycle state, never a lifecycle state of its own: it
|
|
1286
|
+
* refuses to seed a state file, refuses to touch a session that already settled, and leaves
|
|
1287
|
+
* `updated_at` alone so the running-heartbeat cadence stays measured from lifecycle writes.
|
|
1288
|
+
*/
|
|
1289
|
+
async function persistCoordinatorRuntimeToolActivity(
|
|
1290
|
+
event: RuntimeStateEvent,
|
|
1291
|
+
context: RuntimeStateContext,
|
|
1292
|
+
stateFile: string,
|
|
1293
|
+
phase: RuntimeToolActivityPhase,
|
|
1294
|
+
label: string | undefined,
|
|
1295
|
+
observedAt: string,
|
|
1296
|
+
): Promise<void> {
|
|
1297
|
+
const identity = normalizedIdentity(context);
|
|
1298
|
+
await serializeStateFileWrite(
|
|
1299
|
+
stateFile,
|
|
1300
|
+
async () =>
|
|
1301
|
+
await withCoordinatorTransactionLock(
|
|
1302
|
+
stateFile,
|
|
1303
|
+
async () =>
|
|
1304
|
+
await withStateFileLock(stateFile, async () => {
|
|
1305
|
+
const previous = await readPreviousPayloadForEvent(stateFile);
|
|
1306
|
+
if (Object.keys(previous).length === 0) return;
|
|
1307
|
+
assertPreviousRuntimeStateIdentity(previous, identity);
|
|
1308
|
+
// A tool event that lands after the session settled must never
|
|
1309
|
+
// resurrect it into a live-looking state.
|
|
1310
|
+
if (previous.state === "completed" || previous.state === "errored") return;
|
|
1311
|
+
const readout = classifyRuntimeToolActivity(previous.activity);
|
|
1312
|
+
// Fail closed rather than replace an unreadable snapshot, or a sequence
|
|
1313
|
+
// that can no longer advance, with a lower one.
|
|
1314
|
+
if (readout.kind === "malformed")
|
|
1315
|
+
throw new RuntimeToolActivityRefusedError("the persisted snapshot is malformed");
|
|
1316
|
+
const priorActivity = readout.kind === "valid" ? readout.activity : null;
|
|
1317
|
+
if (!Number.isSafeInteger((priorActivity?.seq ?? 0) + 1))
|
|
1318
|
+
throw new RuntimeToolActivityRefusedError("its sequence cannot advance safely");
|
|
1319
|
+
await writeStateFile(stateFile, {
|
|
1320
|
+
...previous,
|
|
1321
|
+
activity: nextRuntimeToolActivity(priorActivity, {
|
|
1322
|
+
phase,
|
|
1323
|
+
label,
|
|
1324
|
+
digest: toolCallDigest(identity.sessionId, event.toolCallId),
|
|
1325
|
+
isError: event.isError === true,
|
|
1326
|
+
observedAt,
|
|
1327
|
+
}),
|
|
1328
|
+
});
|
|
1329
|
+
}),
|
|
1330
|
+
),
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
884
1334
|
export async function persistCoordinatorRuntimeStateFromEvent(
|
|
885
1335
|
event: RuntimeStateEvent,
|
|
886
1336
|
context: RuntimeStateContext,
|
|
1337
|
+
/**
|
|
1338
|
+
* What the caller observed at the synchronous agent-event boundary: the canonical
|
|
1339
|
+
* public label it proved against the ACTIVE tool object, and the wall clock it read
|
|
1340
|
+
* there. Without one, a tool event records `custom` at this writer's own clock — the
|
|
1341
|
+
* sidecar never trusts a model-supplied name.
|
|
1342
|
+
*/
|
|
1343
|
+
observation?: CoordinatorToolObservation,
|
|
887
1344
|
): Promise<void> {
|
|
1345
|
+
// The caller's observation time wins so that queueing, subscriber latency, and lock
|
|
1346
|
+
// contention cannot compress a real elapsed interval into near zero. A value this
|
|
1347
|
+
// writer could not have produced is not trusted enough to persist.
|
|
1348
|
+
const observedAt =
|
|
1349
|
+
observation && isCanonicalIsoTimestamp(observation.observedAt)
|
|
1350
|
+
? observation.observedAt
|
|
1351
|
+
: new Date().toISOString();
|
|
888
1352
|
__sessionStateSidecarPerfCounters.persistFromEventCalls += 1;
|
|
889
1353
|
const stateFile = runtimeStateFileForContext(context);
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
if (!
|
|
1354
|
+
const state = stateForEvent(event);
|
|
1355
|
+
if (!stateFile) return;
|
|
1356
|
+
if (!state) {
|
|
1357
|
+
const activityPhase = toolActivityPhaseForEvent(event);
|
|
1358
|
+
if (!activityPhase) return;
|
|
1359
|
+
await persistCoordinatorRuntimeToolActivity(
|
|
1360
|
+
event,
|
|
1361
|
+
context,
|
|
1362
|
+
stateFile,
|
|
1363
|
+
activityPhase,
|
|
1364
|
+
observation?.label,
|
|
1365
|
+
observedAt,
|
|
1366
|
+
);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
893
1369
|
context = contextWithManagedOwnerGeneration(context);
|
|
894
1370
|
const identity = normalizedIdentity(context);
|
|
895
1371
|
await serializeStateFileWrite(
|
|
@@ -903,7 +1379,14 @@ export async function persistCoordinatorRuntimeStateFromEvent(
|
|
|
903
1379
|
const now = new Date(nowMs).toISOString();
|
|
904
1380
|
const previous = await readPreviousPayloadForEvent(stateFile);
|
|
905
1381
|
assertPreviousRuntimeStateIdentity(previous, identity);
|
|
906
|
-
const
|
|
1382
|
+
const finalResponse = finalResponseForEvent(event);
|
|
1383
|
+
const terminalReceipt =
|
|
1384
|
+
state === "completed" || state === "errored"
|
|
1385
|
+
? reduceTerminalReceiptState({
|
|
1386
|
+
execution: state === "errored" ? "failed" : "completed",
|
|
1387
|
+
reportable: Boolean(finalResponse?.text?.trim()),
|
|
1388
|
+
})
|
|
1389
|
+
: null;
|
|
907
1390
|
const payload = {
|
|
908
1391
|
...basePayload({
|
|
909
1392
|
context,
|
|
@@ -915,12 +1398,31 @@ export async function persistCoordinatorRuntimeStateFromEvent(
|
|
|
915
1398
|
reason: null,
|
|
916
1399
|
sessionId: identity.sessionId,
|
|
917
1400
|
}),
|
|
918
|
-
...(
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1401
|
+
...(terminalReceipt
|
|
1402
|
+
? {
|
|
1403
|
+
execution_state: terminalReceipt.execution,
|
|
1404
|
+
receipt_state: terminalReceipt.receipt,
|
|
1405
|
+
ended_at: now,
|
|
1406
|
+
}
|
|
922
1407
|
: {}),
|
|
923
|
-
...(
|
|
1408
|
+
...(finalResponse ? { final_response: finalResponse } : {}),
|
|
1409
|
+
...(terminalReceipt?.receipt === "missing"
|
|
1410
|
+
? {
|
|
1411
|
+
error: {
|
|
1412
|
+
code: "receipt_missing",
|
|
1413
|
+
message: "Agent completed without reportable final response text or artifact path.",
|
|
1414
|
+
recoverable: true,
|
|
1415
|
+
},
|
|
1416
|
+
}
|
|
1417
|
+
: state === "errored"
|
|
1418
|
+
? {
|
|
1419
|
+
error: {
|
|
1420
|
+
code: "agent_error",
|
|
1421
|
+
message: "GJC agent reported an error",
|
|
1422
|
+
recoverable: true,
|
|
1423
|
+
},
|
|
1424
|
+
}
|
|
1425
|
+
: {}),
|
|
924
1426
|
};
|
|
925
1427
|
if (shouldSkipRuntimeStateWrite(previous, payload, nowMs)) return;
|
|
926
1428
|
await writeStateFile(stateFile, payload);
|