@gajae-code/coding-agent 0.11.1 → 0.11.3
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 +64 -0
- package/dist/types/cli/config-cli.d.ts +11 -1
- package/dist/types/cli/launch-disposition.d.ts +23 -0
- package/dist/types/cli/notify-cli.d.ts +1 -0
- package/dist/types/cli/session-picker.d.ts +2 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/mcp-serve.d.ts +33 -0
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/commands/notify.d.ts +0 -2
- package/dist/types/commands/sdk.d.ts +1 -1
- package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
- package/dist/types/config/file-lock.d.ts +14 -4
- package/dist/types/config/keybindings.d.ts +42 -1
- package/dist/types/config/model-auth.d.ts +3 -0
- package/dist/types/config/model-bindings-applier.d.ts +13 -0
- package/dist/types/config/model-discovery-manager.d.ts +49 -0
- package/dist/types/config/model-equivalence.d.ts +20 -0
- package/dist/types/config/model-registry.d.ts +16 -15
- package/dist/types/config/model-resolver.d.ts +28 -18
- package/dist/types/config/models-config-schema.d.ts +11 -0
- package/dist/types/config/settings-schema.d.ts +141 -11
- package/dist/types/config/settings.d.ts +14 -11
- package/dist/types/coordinator-mcp/policy.d.ts +5 -0
- package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +0 -6
- package/dist/types/edit/index.d.ts +3 -0
- package/dist/types/eval/py/env.d.ts +9 -0
- package/dist/types/extensibility/extensions/loader.d.ts +2 -1
- package/dist/types/extensibility/extensions/runner.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +34 -3
- package/dist/types/extensibility/hooks/runner.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
- package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
- package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
- package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
- package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
- package/dist/types/gjc-runtime/team-store.d.ts +160 -0
- package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
- package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
- package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/goals/state.d.ts +8 -0
- package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
- package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/state.d.ts +11 -4
- package/dist/types/lsp/lspmux.d.ts +5 -3
- package/dist/types/lsp/path-trust.d.ts +1 -0
- package/dist/types/main.d.ts +4 -2
- package/dist/types/memories/storage.d.ts +1 -0
- package/dist/types/modes/action-registry.d.ts +29 -0
- package/dist/types/modes/components/chrome.d.ts +14 -0
- package/dist/types/modes/components/command-palette.d.ts +27 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
- package/dist/types/modes/components/index.d.ts +2 -1
- package/dist/types/modes/components/pet-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
- package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
- package/dist/types/modes/components/queue-pane.d.ts +12 -0
- package/dist/types/modes/components/read-tool-group.d.ts +6 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
- package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
- package/dist/types/modes/components/settings-selector.d.ts +1 -1
- package/dist/types/modes/components/show-images-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +1 -94
- package/dist/types/modes/components/tasks-pane.d.ts +15 -0
- package/dist/types/modes/components/theme-selector.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +15 -0
- package/dist/types/modes/components/tool-status-header.d.ts +111 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
- package/dist/types/modes/controllers/input-controller.d.ts +8 -1
- package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
- package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
- package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
- package/dist/types/modes/interactive-mode.d.ts +19 -17
- package/dist/types/modes/jobs-observer.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
- package/dist/types/modes/tasks-aggregator.d.ts +39 -0
- package/dist/types/modes/transcript-item-registry.d.ts +60 -0
- package/dist/types/modes/types.d.ts +20 -13
- package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +78 -12
- package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
- package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
- package/dist/types/sdk/broker/session-index.d.ts +0 -1
- package/dist/types/sdk/broker/state-version.d.ts +2 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
- package/dist/types/sdk/bus/config-commands.d.ts +1 -1
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/html-format.d.ts +3 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/notification-service.d.ts +20 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
- package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
- package/dist/types/sdk/bus/rich-document.d.ts +7 -0
- package/dist/types/sdk/bus/rich-render.d.ts +18 -41
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
- package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/session-directory.d.ts +56 -0
- package/dist/types/sdk/session.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +82 -47
- package/dist/types/session/agent-storage.d.ts +7 -0
- package/dist/types/session/fallback-chain-controller.d.ts +10 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
- package/dist/types/session/messages.d.ts +16 -0
- package/dist/types/session/session-manager-internal.d.ts +14 -0
- package/dist/types/session/session-manager.d.ts +82 -12
- package/dist/types/session/session-storage.d.ts +43 -3
- package/dist/types/session/volatile-context-pruning.d.ts +15 -0
- package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
- package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
- package/dist/types/system-prompt.d.ts +2 -11
- package/dist/types/task/executor.d.ts +3 -1
- package/dist/types/thinking.d.ts +7 -0
- package/dist/types/tool-discovery/tool-index.d.ts +1 -50
- package/dist/types/tools/ask.d.ts +64 -2
- package/dist/types/tools/bash.d.ts +3 -0
- package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
- package/dist/types/tools/cron.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +1 -0
- package/dist/types/tools/index.d.ts +30 -15
- package/dist/types/tools/output-meta.d.ts +1 -0
- package/dist/types/tools/read.d.ts +5 -0
- package/dist/types/tools/search-tool-bm25.d.ts +3 -4
- package/dist/types/tools/subagent.d.ts +1 -0
- package/dist/types/utils/event-bus.d.ts +1 -1
- package/dist/types/utils/file-mentions.d.ts +3 -3
- package/dist/types/web/search/index.d.ts +3 -1
- package/package.json +21 -9
- package/scripts/build-sdk-package-smoke.ts +34 -1
- package/scripts/generate-hotkeys-docs.ts +74 -0
- package/scripts/generate-sdk-operation-inventory.ts +64 -20
- package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
- package/src/async/job-manager.ts +49 -6
- package/src/cli/config-cli.ts +75 -2
- package/src/cli/launch-disposition.ts +42 -0
- package/src/cli/notify-cli.ts +92 -26
- package/src/cli/session-picker.ts +20 -3
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/harness.ts +34 -106
- package/src/commands/launch.ts +6 -4
- package/src/commands/mcp-serve.ts +85 -8
- package/src/commands/mcp.ts +2 -2
- package/src/commands/notify.ts +8 -4
- package/src/commands/sdk.ts +61 -40
- package/src/commands/ultragoal.ts +4 -1
- package/src/config/atomic-yaml-patch.ts +80 -13
- package/src/config/file-lock.ts +105 -43
- package/src/config/keybindings.ts +64 -20
- package/src/config/model-auth.ts +9 -0
- package/src/config/model-bindings-applier.ts +139 -0
- package/src/config/model-discovery-manager.ts +224 -0
- package/src/config/model-equivalence.ts +66 -0
- package/src/config/model-registry.ts +169 -302
- package/src/config/model-resolver.ts +361 -369
- package/src/config/models-config-schema.ts +5 -4
- package/src/config/settings-schema.ts +214 -12
- package/src/config/settings.ts +458 -482
- package/src/coordinator-mcp/policy.ts +76 -11
- package/src/coordinator-mcp/question-gate-codec.ts +274 -0
- package/src/coordinator-mcp/question-state.ts +778 -0
- package/src/coordinator-mcp/server.ts +1702 -238
- package/src/debug/index.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
- package/src/defaults/gjc/skills/team/SKILL.md +16 -0
- package/src/discovery/helpers.ts +1 -50
- package/src/edit/index.ts +64 -0
- package/src/eval/py/env.ts +26 -0
- package/src/eval/py/kernel.ts +5 -3
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +12 -3
- package/src/extensibility/extensions/types.ts +39 -1
- package/src/extensibility/gjc-plugins/installer.ts +16 -16
- package/src/extensibility/hooks/runner.ts +2 -2
- package/src/extensibility/runtime-skill-discovery.ts +33 -13
- package/src/extensibility/shared-events.ts +2 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
- package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
- package/src/gjc-runtime/deep-interview-state.ts +317 -5
- package/src/gjc-runtime/goal-mode-request.ts +55 -8
- package/src/gjc-runtime/launch-tmux.ts +2 -5
- package/src/gjc-runtime/launch-worktree.ts +5 -5
- package/src/gjc-runtime/linux-proc.ts +71 -0
- package/src/gjc-runtime/ralplan-runtime.ts +74 -90
- package/src/gjc-runtime/session-resolution.ts +18 -3
- package/src/gjc-runtime/session-state-sidecar.ts +6 -107
- package/src/gjc-runtime/state-migrations.ts +1 -1
- package/src/gjc-runtime/state-runtime.ts +327 -249
- package/src/gjc-runtime/state-writer.ts +36 -40
- package/src/gjc-runtime/team-launch.ts +319 -0
- package/src/gjc-runtime/team-notify.ts +326 -0
- package/src/gjc-runtime/team-runtime.ts +1099 -1482
- package/src/gjc-runtime/team-store.ts +828 -0
- package/src/gjc-runtime/team-workers.ts +962 -0
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
- package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
- package/src/gjc-runtime/tmux-sessions.ts +4 -21
- package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
- package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
- package/src/gjc-runtime/ultragoal-guard.ts +29 -5
- package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
- package/src/gjc-runtime/workflow-cli-common.ts +30 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +18 -0
- package/src/goals/continuation-timeout-guard.ts +79 -0
- package/src/goals/runtime.ts +7 -5
- package/src/goals/state.ts +18 -0
- package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
- package/src/harness-control-plane/phase-rollup.ts +4 -7
- package/src/harness-control-plane/receipt-ingest.ts +5 -0
- package/src/harness-control-plane/receipt-spool.ts +51 -7
- package/src/harness-control-plane/session-lease.ts +12 -130
- package/src/hindsight/backend.ts +26 -27
- package/src/hindsight/state.ts +101 -32
- package/src/hooks/skill-state.ts +17 -11
- package/src/internal-urls/docs-index.generated.ts +21 -18
- package/src/lsp/client.ts +1 -1
- package/src/lsp/config.ts +145 -26
- package/src/lsp/index.ts +1 -1
- package/src/lsp/lspmux.ts +39 -10
- package/src/lsp/path-trust.ts +81 -0
- package/src/main.ts +127 -30
- package/src/memories/index.ts +1 -0
- package/src/memories/storage.ts +6 -2
- package/src/modes/action-registry.ts +165 -0
- package/src/modes/components/chrome.ts +40 -0
- package/src/modes/components/command-palette.ts +168 -0
- package/src/modes/components/custom-editor.ts +1 -1
- package/src/modes/components/custom-provider-wizard.ts +51 -14
- package/src/modes/components/index.ts +2 -1
- package/src/modes/components/pet-selector.ts +11 -17
- package/src/modes/components/plan-preview-overlay.ts +295 -0
- package/src/modes/components/queue-mode-selector.ts +10 -27
- package/src/modes/components/queue-pane.ts +79 -0
- package/src/modes/components/read-tool-group.ts +13 -0
- package/src/modes/components/session-observer-overlay.ts +207 -772
- package/src/modes/components/sessions-dashboard.ts +181 -0
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +10 -24
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +1 -969
- package/src/modes/components/tasks-pane.ts +73 -0
- package/src/modes/components/theme-selector.ts +11 -31
- package/src/modes/components/tool-execution.ts +22 -0
- package/src/modes/components/tool-status-header.ts +1056 -0
- package/src/modes/components/transcript-viewer-overlay.ts +384 -0
- package/src/modes/controllers/btw-controller.ts +1 -0
- package/src/modes/controllers/event-controller.ts +3 -2
- package/src/modes/controllers/extension-ui-controller.ts +24 -1
- package/src/modes/controllers/goal-mode-controller.ts +474 -0
- package/src/modes/controllers/input-controller.ts +764 -229
- package/src/modes/controllers/mode-gate.ts +24 -0
- package/src/modes/controllers/plan-mode-controller.ts +517 -0
- package/src/modes/controllers/selector-controller.ts +225 -10
- package/src/modes/controllers/stt-controller.ts +125 -0
- package/src/modes/interactive-mode.ts +289 -1150
- package/src/modes/jobs-observer.ts +3 -0
- package/src/modes/prompt-action-autocomplete.ts +7 -0
- package/src/modes/runtime-init.ts +8 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
- package/src/modes/tasks-aggregator.ts +199 -0
- package/src/modes/transcript-item-registry.ts +229 -0
- package/src/modes/types.ts +45 -18
- package/src/modes/utils/ui-helpers.ts +17 -9
- package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
- package/src/prompts/agent-fragments/restricted-bash.md +1 -0
- package/src/prompts/agents/architect.md +2 -8
- package/src/prompts/agents/critic.md +2 -8
- package/src/prompts/agents/executor.md +3 -0
- package/src/prompts/agents/planner.md +4 -12
- package/src/prompts/system/custom-system-prompt.md +1 -23
- package/src/prompts/system/plan-mode-approved.md +8 -0
- package/src/prompts/system/system-prompt.md +17 -96
- package/src/prompts/tools/bash.md +4 -4
- package/src/prompts/tools/browser.md +15 -52
- package/src/prompts/tools/find.md +2 -2
- package/src/prompts/tools/hashline.md +13 -55
- package/src/prompts/tools/read.md +3 -6
- package/src/prompts/tools/search-tool-bm25.md +0 -2
- package/src/prompts/tools/search.md +2 -4
- package/src/prompts/tools/skill-discovery.md +1 -2
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-mcp/manager.ts +1 -1
- package/src/sdk/broker/broker.ts +206 -78
- package/src/sdk/broker/discovery.ts +1 -1
- package/src/sdk/broker/ensure.ts +3 -1
- package/src/sdk/broker/lifecycle-codec.ts +11 -0
- package/src/sdk/broker/lifecycle-ledger.ts +371 -74
- package/src/sdk/broker/lifecycle.ts +1399 -142
- package/src/sdk/broker/process-incarnation.ts +5 -13
- package/src/sdk/broker/session-index.ts +145 -56
- package/src/sdk/broker/state-version.ts +21 -0
- package/src/sdk/broker/transport.ts +1 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -28
- package/src/sdk/bus/chat-effect-journal.ts +10 -2
- package/src/sdk/bus/config-commands.ts +5 -4
- package/src/sdk/bus/config.ts +98 -0
- package/src/sdk/bus/conversation-store.ts +5 -29
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/html-format.ts +20 -3
- package/src/sdk/bus/index.ts +782 -94
- package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
- package/src/sdk/bus/natives-augment.d.ts +26 -1
- package/src/sdk/bus/notification-service.ts +168 -20
- package/src/sdk/bus/rate-limit-pool.ts +58 -14
- package/src/sdk/bus/recent-activity.ts +188 -74
- package/src/sdk/bus/rich-document.ts +258 -0
- package/src/sdk/bus/rich-render.ts +62 -70
- package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon.ts +1389 -237
- package/src/sdk/bus/threaded-inbound.ts +8 -3
- package/src/sdk/bus/threaded-render.ts +57 -1
- package/src/sdk/bus/topic-registry.ts +120 -13
- package/src/sdk/host/control/dispatch.ts +14 -2
- package/src/sdk/host/control/operations.ts +6 -1
- package/src/sdk/host/host.ts +44 -2
- package/src/sdk/host/query/handlers.ts +20 -22
- package/src/sdk/host/query/revision-store.ts +80 -57
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
- package/src/sdk/session-directory.ts +141 -0
- package/src/sdk/session.ts +462 -480
- package/src/session/agent-session.ts +1700 -583
- package/src/session/agent-storage.ts +18 -0
- package/src/session/cache-economics.ts +25 -8
- package/src/session/fallback-chain-controller.ts +53 -3
- package/src/session/internal/managed-session-scope.ts +2226 -0
- package/src/session/internal/managed-session-storage.ts +453 -0
- package/src/session/messages.ts +91 -2
- package/src/session/session-manager-internal.ts +33 -0
- package/src/session/session-manager.ts +1112 -333
- package/src/session/session-storage.ts +444 -39
- package/src/session/volatile-context-pruning.ts +78 -0
- package/src/setup/credential-auto-import.ts +2 -1
- package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
- package/src/setup/provider-onboarding.ts +36 -61
- package/src/setup/provider-presets.json +5 -0
- package/src/skill-state/active-state.ts +3 -13
- package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
- package/src/skill-state/workflow-state-contract.ts +6 -41
- package/src/slash-commands/builtin-registry.ts +46 -11
- package/src/system-prompt.ts +37 -44
- package/src/task/agents.ts +19 -1
- package/src/task/executor.ts +90 -6
- package/src/task/index.ts +36 -21
- package/src/thinking.ts +21 -6
- package/src/tool-discovery/tool-index.ts +2 -134
- package/src/tools/ask.ts +157 -21
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/bash.ts +38 -0
- package/src/tools/browser/dead-tab-recovery.ts +78 -0
- package/src/tools/browser/tab-supervisor.ts +282 -36
- package/src/tools/computer.ts +10 -3
- package/src/tools/cron.ts +30 -16
- package/src/tools/eval.ts +1 -2
- package/src/tools/fetch.ts +11 -4
- package/src/tools/index.ts +76 -24
- package/src/tools/output-meta.ts +19 -7
- package/src/tools/read.ts +219 -64
- package/src/tools/resource-gc.ts +2 -2
- package/src/tools/search-tool-bm25.ts +11 -74
- package/src/tools/subagent.ts +43 -2
- package/src/utils/event-bus.ts +9 -4
- package/src/utils/file-mentions.ts +4 -4
- package/src/web/search/index.ts +3 -3
- package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
- package/src/discovery/plugin-dir-roots.ts +0 -28
- package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import { createHash, randomBytes } from "node:crypto";
|
|
10
10
|
import * as fs from "node:fs/promises";
|
|
11
11
|
import * as path from "node:path";
|
|
12
|
+
import { withFileLock } from "../config/file-lock";
|
|
13
|
+
|
|
12
14
|
import { controlSocketPath, sessionPaths } from "./storage";
|
|
13
15
|
|
|
14
16
|
export interface SessionLease {
|
|
@@ -51,21 +53,8 @@ async function writeLeaseAtomic(file: string, lease: SessionLease): Promise<void
|
|
|
51
53
|
await fs.writeFile(tmp, `${JSON.stringify(lease, null, 2)}\n`, "utf8");
|
|
52
54
|
await fs.rename(tmp, file);
|
|
53
55
|
}
|
|
54
|
-
const LEASE_LOCK_RETRIES = 100;
|
|
55
|
-
const LEASE_LOCK_RETRY_DELAY_MS = 20;
|
|
56
|
-
const LEASE_LOCK_FILE_SUFFIX = ".json";
|
|
57
|
-
|
|
58
|
-
interface LeaseLockInfo {
|
|
59
|
-
pid: number;
|
|
60
|
-
token: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
56
|
function leaseMutationLockPath(root: string, sessionId: string): string {
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function leaseMutationLockFile(lockPath: string, token: string): string {
|
|
68
|
-
return path.join(lockPath, `${token}${LEASE_LOCK_FILE_SUFFIX}`);
|
|
57
|
+
return sessionPaths(root, sessionId).lease;
|
|
69
58
|
}
|
|
70
59
|
|
|
71
60
|
function isOwnedUnixEndpointPath(root: string, sessionId: string, endpointPath: string, fallbackPath: string): boolean {
|
|
@@ -77,128 +66,21 @@ function isOwnedUnixEndpointPath(root: string, sessionId: string, endpointPath:
|
|
|
77
66
|
return false;
|
|
78
67
|
}
|
|
79
68
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return code === "EEXIST" || code === "ENOTEMPTY" || code === "EISDIR" || code === "ENOTDIR";
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function isIgnorableLockDirRemoveError(error: unknown): boolean {
|
|
87
|
-
const code = (error as NodeJS.ErrnoException).code;
|
|
88
|
-
return code === "ENOENT" || code === "ENOTEMPTY" || code === "EEXIST";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function removeEmptyLockDir(lockPath: string): Promise<void> {
|
|
92
|
-
try {
|
|
93
|
-
await fs.rmdir(lockPath);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (!isIgnorableLockDirRemoveError(error)) throw error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function parseLeaseLockInfo(raw: string): LeaseLockInfo | null {
|
|
100
|
-
try {
|
|
101
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
102
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
103
|
-
const info = parsed as Record<string, unknown>;
|
|
104
|
-
return typeof info.pid === "number" && typeof info.token === "string"
|
|
105
|
-
? { pid: info.pid, token: info.token }
|
|
106
|
-
: null;
|
|
107
|
-
} catch {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function readLeaseLockInfo(lockFile: string): Promise<LeaseLockInfo | null> {
|
|
113
|
-
try {
|
|
114
|
-
return parseLeaseLockInfo(await fs.readFile(lockFile, "utf8"));
|
|
115
|
-
} catch {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function createLeaseMutationLock(lockPath: string, info: LeaseLockInfo): Promise<boolean> {
|
|
121
|
-
const tmpPath = `${lockPath}.tmp-${info.token}`;
|
|
122
|
-
await fs.rm(tmpPath, { recursive: true, force: true });
|
|
123
|
-
await fs.mkdir(tmpPath, { mode: 0o700 });
|
|
69
|
+
async function withLeaseMutationLock<T>(root: string, sessionId: string, fn: () => Promise<T>): Promise<T> {
|
|
70
|
+
const lockPath = leaseMutationLockPath(root, sessionId);
|
|
71
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
124
72
|
try {
|
|
125
|
-
await
|
|
126
|
-
|
|
127
|
-
|
|
73
|
+
return await withFileLock(lockPath, fn, {
|
|
74
|
+
staleMs: 30_000,
|
|
75
|
+
retries: 100,
|
|
76
|
+
retryDelayMs: 20,
|
|
128
77
|
});
|
|
129
|
-
await fs.rename(tmpPath, lockPath);
|
|
130
|
-
return true;
|
|
131
|
-
} catch (error) {
|
|
132
|
-
await fs.rm(tmpPath, { recursive: true, force: true });
|
|
133
|
-
if (isPathExistsError(error)) return false;
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function recoverLegacyStaleLeaseLock(lockPath: string): Promise<void> {
|
|
139
|
-
const info = await readLeaseLockInfo(lockPath);
|
|
140
|
-
if (!info || defaultPidStatusProbe(info.pid) !== "dead") return;
|
|
141
|
-
try {
|
|
142
|
-
await fs.unlink(lockPath);
|
|
143
78
|
} catch (error) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function recoverStaleLeaseMutationLock(lockPath: string): Promise<void> {
|
|
150
|
-
let entries: string[];
|
|
151
|
-
try {
|
|
152
|
-
entries = await fs.readdir(lockPath);
|
|
153
|
-
} catch (error) {
|
|
154
|
-
const code = (error as NodeJS.ErrnoException).code;
|
|
155
|
-
if (code === "ENOENT") return;
|
|
156
|
-
if (code === "ENOTDIR") {
|
|
157
|
-
await recoverLegacyStaleLeaseLock(lockPath);
|
|
158
|
-
return;
|
|
79
|
+
if (error instanceof Error && error.message.startsWith("Failed to acquire lock")) {
|
|
80
|
+
throw new LeaseError(`lease_lock_timeout:${sessionId}`, "lease_lock_timeout");
|
|
159
81
|
}
|
|
160
82
|
throw error;
|
|
161
83
|
}
|
|
162
|
-
const lockFiles = entries.filter(entry => entry.endsWith(LEASE_LOCK_FILE_SUFFIX));
|
|
163
|
-
if (lockFiles.length === 0) {
|
|
164
|
-
await removeEmptyLockDir(lockPath);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
if (lockFiles.length !== 1) return;
|
|
168
|
-
const lockFile = lockFiles[0];
|
|
169
|
-
const info = await readLeaseLockInfo(path.join(lockPath, lockFile));
|
|
170
|
-
if (!info || lockFile !== `${info.token}${LEASE_LOCK_FILE_SUFFIX}`) return;
|
|
171
|
-
if (defaultPidStatusProbe(info.pid) !== "dead") return;
|
|
172
|
-
// Delete only the stale owner's token-named file; a fresh lock uses a different file name.
|
|
173
|
-
await fs.rm(path.join(lockPath, lockFile), { force: true });
|
|
174
|
-
await removeEmptyLockDir(lockPath);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async function releaseLeaseMutationLock(lockPath: string, token: string): Promise<void> {
|
|
178
|
-
await fs.rm(leaseMutationLockFile(lockPath, token), { force: true });
|
|
179
|
-
await removeEmptyLockDir(lockPath);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async function acquireLeaseMutationLock(root: string, sessionId: string): Promise<() => Promise<void>> {
|
|
183
|
-
const lockPath = leaseMutationLockPath(root, sessionId);
|
|
184
|
-
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
185
|
-
for (let attempt = 0; attempt < LEASE_LOCK_RETRIES; attempt++) {
|
|
186
|
-
const token = randomBytes(16).toString("hex");
|
|
187
|
-
const info: LeaseLockInfo = { pid: process.pid, token };
|
|
188
|
-
if (await createLeaseMutationLock(lockPath, info)) return () => releaseLeaseMutationLock(lockPath, token);
|
|
189
|
-
await recoverStaleLeaseMutationLock(lockPath);
|
|
190
|
-
await Bun.sleep(LEASE_LOCK_RETRY_DELAY_MS);
|
|
191
|
-
}
|
|
192
|
-
throw new LeaseError(`lease_lock_timeout:${sessionId}`, "lease_lock_timeout");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function withLeaseMutationLock<T>(root: string, sessionId: string, fn: () => Promise<T>): Promise<T> {
|
|
196
|
-
const release = await acquireLeaseMutationLock(root, sessionId);
|
|
197
|
-
try {
|
|
198
|
-
return await fn();
|
|
199
|
-
} finally {
|
|
200
|
-
await release();
|
|
201
|
-
}
|
|
202
84
|
}
|
|
203
85
|
|
|
204
86
|
export async function readLease(root: string, sessionId: string): Promise<SessionLease | null> {
|
package/src/hindsight/backend.ts
CHANGED
|
@@ -48,23 +48,23 @@ export const hindsightBackend: MemoryBackend = {
|
|
|
48
48
|
if (options.taskDepth > 0) {
|
|
49
49
|
const parent = options.parentHindsightSessionState;
|
|
50
50
|
if (!parent) return;
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
67
|
-
|
|
51
|
+
const replacement = new HindsightSessionState({
|
|
52
|
+
sessionId,
|
|
53
|
+
client: parent.client,
|
|
54
|
+
bankId: parent.bankId,
|
|
55
|
+
retainTags: parent.retainTags,
|
|
56
|
+
recallTags: parent.recallTags,
|
|
57
|
+
recallTagsMatch: parent.recallTagsMatch,
|
|
58
|
+
config: parent.config,
|
|
59
|
+
session,
|
|
60
|
+
missionsSet: parent.missionsSet,
|
|
61
|
+
lastRetainedTurn: 0,
|
|
62
|
+
hasRecalledForFirstTurn: true,
|
|
63
|
+
aliasOf: parent,
|
|
64
|
+
});
|
|
65
|
+
const previous = session.getHindsightSessionState();
|
|
66
|
+
await previous?.dispose();
|
|
67
|
+
session.setHindsightSessionState(replacement);
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -91,10 +91,12 @@ export const hindsightBackend: MemoryBackend = {
|
|
|
91
91
|
hasRecalledForFirstTurn: false,
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
previous
|
|
94
|
+
// Close and drain the old queue while it still owns this session. Closing
|
|
95
|
+
// rejects concurrent enqueues, so no retain can land between the final flush
|
|
96
|
+
// and replacement.
|
|
97
|
+
const previous = session.getHindsightSessionState();
|
|
98
|
+
await previous?.dispose();
|
|
99
|
+
session.setHindsightSessionState(state);
|
|
98
100
|
state.attachSessionListeners();
|
|
99
101
|
|
|
100
102
|
// Kick off mental-model bootstrap. Resolves asynchronously; the first
|
|
@@ -114,15 +116,12 @@ export const hindsightBackend: MemoryBackend = {
|
|
|
114
116
|
|
|
115
117
|
const state = session?.getHindsightSessionState();
|
|
116
118
|
const primary = state?.aliasOf ?? state;
|
|
117
|
-
const recallSnippet = primary?.lastRecallSnippet;
|
|
118
119
|
const mentalModelsSnippet = primary?.mentalModelsSnippet;
|
|
119
120
|
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
// anchored on curated knowledge.
|
|
121
|
+
// Static instructions and curated mental models are prefix-stable. Recall
|
|
122
|
+
// is injected by AgentSession as volatile user-role context instead.
|
|
123
123
|
const parts = [STATIC_INSTRUCTIONS];
|
|
124
124
|
if (mentalModelsSnippet) parts.push(mentalModelsSnippet);
|
|
125
|
-
if (recallSnippet) parts.push(recallSnippet);
|
|
126
125
|
return parts.join("\n\n");
|
|
127
126
|
},
|
|
128
127
|
|
|
@@ -141,7 +140,7 @@ export const hindsightBackend: MemoryBackend = {
|
|
|
141
140
|
const state = session?.getHindsightSessionState();
|
|
142
141
|
if (state) await state.flushRetainQueue();
|
|
143
142
|
const previous = session?.setHindsightSessionState(undefined);
|
|
144
|
-
previous?.dispose();
|
|
143
|
+
await previous?.dispose();
|
|
145
144
|
logger.warn(
|
|
146
145
|
"Hindsight memory is server-side; only the local recall cache was cleared. " +
|
|
147
146
|
"Delete the Hindsight bank from the UI to wipe upstream state.",
|
package/src/hindsight/state.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import { logger } from "@gajae-code/utils";
|
|
2
4
|
import type { AgentSession } from "../session/agent-session";
|
|
3
5
|
import { type BankScope, ensureBankMission } from "./bank";
|
|
@@ -5,7 +7,6 @@ import type { HindsightApi, MemoryItemInput } from "./client";
|
|
|
5
7
|
import type { HindsightConfig } from "./config";
|
|
6
8
|
import {
|
|
7
9
|
composeRecallQuery,
|
|
8
|
-
formatCurrentTime,
|
|
9
10
|
formatMemories,
|
|
10
11
|
type HindsightMessage,
|
|
11
12
|
prepareRetentionTranscript,
|
|
@@ -105,33 +106,31 @@ export class HindsightRetainQueue {
|
|
|
105
106
|
this.#timer = undefined;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
if (this.#flushing)
|
|
109
|
-
// Coalesce: wait for the in-flight flush, then drain anything that
|
|
110
|
-
// landed after it started so we don't strand items.
|
|
111
|
-
await this.#flushing;
|
|
112
|
-
if (this.#items.length > 0) await this.flush();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
109
|
+
if (this.#flushing) return this.#flushing;
|
|
116
110
|
if (this.#items.length === 0) return;
|
|
117
111
|
|
|
118
|
-
|
|
119
|
-
const flushPromise = this.#doFlush(items);
|
|
120
|
-
this.#flushing = flushPromise;
|
|
112
|
+
this.#flushing = this.#flushLoop();
|
|
121
113
|
try {
|
|
122
|
-
await
|
|
114
|
+
await this.#flushing;
|
|
123
115
|
} finally {
|
|
124
116
|
this.#flushing = undefined;
|
|
125
117
|
}
|
|
126
118
|
}
|
|
127
119
|
|
|
128
|
-
|
|
120
|
+
async #flushLoop(): Promise<void> {
|
|
121
|
+
while (this.#items.length > 0) {
|
|
122
|
+
const items = this.#items.splice(0);
|
|
123
|
+
await this.#doFlush(items);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async dispose(): Promise<void> {
|
|
129
128
|
this.#closed = true;
|
|
130
129
|
if (this.#timer) {
|
|
131
130
|
clearTimeout(this.#timer);
|
|
132
131
|
this.#timer = undefined;
|
|
133
132
|
}
|
|
134
|
-
this
|
|
133
|
+
await this.flush();
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
async #doFlush(items: PendingRetainItem[]): Promise<void> {
|
|
@@ -202,6 +201,7 @@ export class HindsightSessionState {
|
|
|
202
201
|
lastRetainedTurn: number;
|
|
203
202
|
hasRecalledForFirstTurn: boolean;
|
|
204
203
|
lastRecallSnippet?: string;
|
|
204
|
+
lastInjectedRecallSnippetHash?: string;
|
|
205
205
|
/** Cached `<mental_models>` block injected into developer instructions. */
|
|
206
206
|
mentalModelsSnippet?: string;
|
|
207
207
|
/** When the cached snippet was last refreshed; gates the agent_end re-list. */
|
|
@@ -215,6 +215,9 @@ export class HindsightSessionState {
|
|
|
215
215
|
unsubscribe?: () => void;
|
|
216
216
|
/** Alias states delegate persistence config to a primary parent state. */
|
|
217
217
|
aliasOf?: HindsightSessionState;
|
|
218
|
+
/** One auto-retain may run at a time; one later agent_end is coalesced. */
|
|
219
|
+
#autoRetainInFlight?: Promise<void>;
|
|
220
|
+
#autoRetainPending = false;
|
|
218
221
|
readonly retainQueue: HindsightRetainQueue;
|
|
219
222
|
|
|
220
223
|
constructor(options: HindsightSessionStateOptions) {
|
|
@@ -241,6 +244,30 @@ export class HindsightSessionState {
|
|
|
241
244
|
this.lastRetainedTurn = 0;
|
|
242
245
|
this.hasRecalledForFirstTurn = false;
|
|
243
246
|
this.lastRecallSnippet = undefined;
|
|
247
|
+
this.lastInjectedRecallSnippetHash = undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Return the current recall payload, resolving subagent aliases to their primary state. */
|
|
251
|
+
getRecallSnippet(): string | undefined {
|
|
252
|
+
return this.aliasOf ? this.aliasOf.getRecallSnippet() : this.lastRecallSnippet;
|
|
253
|
+
}
|
|
254
|
+
/** Return the current recall payload without consuming its one-shot injection eligibility. */
|
|
255
|
+
getRecallSnippetForInjection(): string | undefined {
|
|
256
|
+
if (this.aliasOf) return this.aliasOf.getRecallSnippetForInjection();
|
|
257
|
+
const snippet = this.lastRecallSnippet;
|
|
258
|
+
if (!snippet) return undefined;
|
|
259
|
+
const hash = createHash("sha256").update(snippet).digest("hex");
|
|
260
|
+
return hash === this.lastInjectedRecallSnippetHash ? undefined : snippet;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Mark a recall payload consumed only after it has been accepted for provider injection. */
|
|
264
|
+
markRecallSnippetInjected(snippet: string): boolean {
|
|
265
|
+
if (this.aliasOf) return this.aliasOf.markRecallSnippetInjected(snippet);
|
|
266
|
+
if (snippet !== this.lastRecallSnippet) return false;
|
|
267
|
+
const hash = createHash("sha256").update(snippet).digest("hex");
|
|
268
|
+
if (hash === this.lastInjectedRecallSnippetHash) return false;
|
|
269
|
+
this.lastInjectedRecallSnippetHash = hash;
|
|
270
|
+
return true;
|
|
244
271
|
}
|
|
245
272
|
|
|
246
273
|
enqueueRetain(content: string, context?: string): void {
|
|
@@ -264,7 +291,7 @@ export class HindsightSessionState {
|
|
|
264
291
|
const results = response.results ?? [];
|
|
265
292
|
if (results.length === 0) return { context: null, ok: true };
|
|
266
293
|
const formatted = formatMemories(results);
|
|
267
|
-
const block = `<memories>\n${this.config.recallPromptPreamble}\
|
|
294
|
+
const block = `<memories>\n${this.config.recallPromptPreamble}\n\n${formatted}\n</memories>`;
|
|
268
295
|
return { context: block, ok: true };
|
|
269
296
|
} catch (err) {
|
|
270
297
|
if (this.config.debug) {
|
|
@@ -274,13 +301,14 @@ export class HindsightSessionState {
|
|
|
274
301
|
}
|
|
275
302
|
}
|
|
276
303
|
|
|
277
|
-
async retainSession(messages: HindsightMessage[]): Promise<
|
|
304
|
+
async retainSession(messages: HindsightMessage[]): Promise<boolean> {
|
|
278
305
|
const retainFullWindow = this.config.retainMode === "full-session";
|
|
279
306
|
let target: HindsightMessage[];
|
|
280
307
|
let documentId: string;
|
|
281
308
|
|
|
282
309
|
if (retainFullWindow) {
|
|
283
|
-
target = messages;
|
|
310
|
+
target = sliceMessagesAfterUserTurns(messages, this.lastRetainedTurn);
|
|
311
|
+
if (target.length === 0) return false;
|
|
284
312
|
documentId = this.sessionId;
|
|
285
313
|
} else {
|
|
286
314
|
const windowTurns = this.config.retainEveryNTurns + this.config.retainOverlapTurns;
|
|
@@ -289,19 +317,40 @@ export class HindsightSessionState {
|
|
|
289
317
|
}
|
|
290
318
|
|
|
291
319
|
const { transcript } = prepareRetentionTranscript(target, true);
|
|
292
|
-
if (!transcript) return;
|
|
320
|
+
if (!transcript) return false;
|
|
293
321
|
|
|
294
322
|
await ensureBankMission(this.client, this.bankId, this.config, this.missionsSet);
|
|
295
323
|
await this.client.retain(this.bankId, transcript, {
|
|
296
324
|
documentId,
|
|
325
|
+
updateMode: "append",
|
|
297
326
|
context: this.config.retainContext,
|
|
298
327
|
metadata: { session_id: this.sessionId },
|
|
299
328
|
tags: this.retainTags,
|
|
300
329
|
async: true,
|
|
301
330
|
});
|
|
331
|
+
return true;
|
|
302
332
|
}
|
|
303
333
|
|
|
304
334
|
async maybeRetainOnAgentEnd(): Promise<void> {
|
|
335
|
+
if (this.#autoRetainInFlight) {
|
|
336
|
+
this.#autoRetainPending = true;
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const retain = this.#maybeRetainOnAgentEnd();
|
|
341
|
+
this.#autoRetainInFlight = retain;
|
|
342
|
+
try {
|
|
343
|
+
await retain;
|
|
344
|
+
} finally {
|
|
345
|
+
this.#autoRetainInFlight = undefined;
|
|
346
|
+
if (this.#autoRetainPending) {
|
|
347
|
+
this.#autoRetainPending = false;
|
|
348
|
+
void this.maybeRetainOnAgentEnd();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async #maybeRetainOnAgentEnd(): Promise<void> {
|
|
305
354
|
if (!this.config.autoRetain) return;
|
|
306
355
|
const messages = extractMessages(this.session.sessionManager);
|
|
307
356
|
if (messages.length === 0) return;
|
|
@@ -309,7 +358,8 @@ export class HindsightSessionState {
|
|
|
309
358
|
if (userTurns - this.lastRetainedTurn < this.config.retainEveryNTurns) return;
|
|
310
359
|
|
|
311
360
|
try {
|
|
312
|
-
await this.retainSession(messages);
|
|
361
|
+
const retained = await this.retainSession(messages);
|
|
362
|
+
if (!retained) return;
|
|
313
363
|
this.lastRetainedTurn = userTurns;
|
|
314
364
|
if (this.config.debug) {
|
|
315
365
|
logger.debug("Hindsight: auto-retain succeeded", {
|
|
@@ -332,7 +382,8 @@ export class HindsightSessionState {
|
|
|
332
382
|
const messages = extractMessages(this.session.sessionManager);
|
|
333
383
|
if (messages.length === 0) return;
|
|
334
384
|
try {
|
|
335
|
-
await this.retainSession(messages);
|
|
385
|
+
const retained = await this.retainSession(messages);
|
|
386
|
+
if (!retained) return;
|
|
336
387
|
this.lastRetainedTurn = messages.filter(m => m.role === "user").length;
|
|
337
388
|
} catch (err) {
|
|
338
389
|
logger.warn("Hindsight: forced retain failed", {
|
|
@@ -358,7 +409,6 @@ export class HindsightSessionState {
|
|
|
358
409
|
if (!context) return;
|
|
359
410
|
|
|
360
411
|
this.lastRecallSnippet = context;
|
|
361
|
-
await this.#refreshBaseSystemPromptAfter("recall");
|
|
362
412
|
}
|
|
363
413
|
|
|
364
414
|
async beforeAgentStartPrompt(promptText: string): Promise<string | undefined> {
|
|
@@ -409,21 +459,23 @@ export class HindsightSessionState {
|
|
|
409
459
|
}
|
|
410
460
|
}
|
|
411
461
|
|
|
412
|
-
await this.refreshMentalModelsSnippet();
|
|
413
|
-
await this.#refreshBaseSystemPromptAfter("MM load");
|
|
462
|
+
const changed = await this.refreshMentalModelsSnippet();
|
|
463
|
+
if (changed) await this.#refreshBaseSystemPromptAfter("MM load");
|
|
414
464
|
}
|
|
415
465
|
|
|
416
|
-
async refreshMentalModelsSnippet(): Promise<
|
|
466
|
+
async refreshMentalModelsSnippet(): Promise<boolean> {
|
|
417
467
|
const snippet = await loadMentalModelsBlock(this.client, this.bankId, this.config.mentalModelMaxRenderChars);
|
|
468
|
+
const changed = contentHash(snippet) !== contentHash(this.mentalModelsSnippet);
|
|
418
469
|
this.mentalModelsSnippet = snippet;
|
|
419
470
|
this.mentalModelsLoadedAt = Date.now();
|
|
471
|
+
return changed;
|
|
420
472
|
}
|
|
421
473
|
|
|
422
474
|
async reloadMentalModels(): Promise<boolean> {
|
|
423
475
|
if (this.aliasOf) return false;
|
|
424
476
|
if (!this.config.mentalModelsEnabled) return false;
|
|
425
|
-
await this.refreshMentalModelsSnippet();
|
|
426
|
-
await this.#refreshBaseSystemPromptAfter("MM reload");
|
|
477
|
+
const changed = await this.refreshMentalModelsSnippet();
|
|
478
|
+
if (changed) await this.#refreshBaseSystemPromptAfter("MM reload");
|
|
427
479
|
return true;
|
|
428
480
|
}
|
|
429
481
|
|
|
@@ -445,21 +497,21 @@ export class HindsightSessionState {
|
|
|
445
497
|
this.mentalModelsLoadedAt !== undefined &&
|
|
446
498
|
Date.now() - this.mentalModelsLoadedAt >= this.config.mentalModelRefreshIntervalMs
|
|
447
499
|
) {
|
|
448
|
-
void this.refreshMentalModelsSnippet().then(async
|
|
449
|
-
await this.#refreshBaseSystemPromptAfter("MM TTL reload");
|
|
500
|
+
void this.refreshMentalModelsSnippet().then(async changed => {
|
|
501
|
+
if (changed) await this.#refreshBaseSystemPromptAfter("MM TTL reload");
|
|
450
502
|
});
|
|
451
503
|
}
|
|
452
504
|
}
|
|
453
505
|
});
|
|
454
506
|
}
|
|
455
507
|
|
|
456
|
-
dispose(): void {
|
|
508
|
+
async dispose(): Promise<void> {
|
|
457
509
|
this.unsubscribe?.();
|
|
458
510
|
this.unsubscribe = undefined;
|
|
459
|
-
this.retainQueue.dispose();
|
|
511
|
+
await this.retainQueue.dispose();
|
|
460
512
|
}
|
|
461
513
|
|
|
462
|
-
async #refreshBaseSystemPromptAfter(reason: "
|
|
514
|
+
async #refreshBaseSystemPromptAfter(reason: "MM load" | "MM reload" | "MM TTL reload"): Promise<void> {
|
|
463
515
|
try {
|
|
464
516
|
await this.session.refreshBaseSystemPrompt();
|
|
465
517
|
} catch (err) {
|
|
@@ -467,3 +519,20 @@ export class HindsightSessionState {
|
|
|
467
519
|
}
|
|
468
520
|
}
|
|
469
521
|
}
|
|
522
|
+
|
|
523
|
+
/** Return the tail beginning with the first user turn after `retainedTurns`. */
|
|
524
|
+
function sliceMessagesAfterUserTurns(messages: HindsightMessage[], retainedTurns: number): HindsightMessage[] {
|
|
525
|
+
if (retainedTurns <= 0) return messages;
|
|
526
|
+
|
|
527
|
+
let seenTurns = 0;
|
|
528
|
+
for (let i = 0; i < messages.length; i++) {
|
|
529
|
+
if (messages[i].role !== "user") continue;
|
|
530
|
+
seenTurns += 1;
|
|
531
|
+
if (seenTurns > retainedTurns) return messages.slice(i);
|
|
532
|
+
}
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function contentHash(content: string | undefined): string {
|
|
537
|
+
return Bun.hash(content ?? "").toString(16);
|
|
538
|
+
}
|
package/src/hooks/skill-state.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
writeGuardedWorkflowEnvelopeAtomic,
|
|
13
13
|
} from "../gjc-runtime/state-writer";
|
|
14
14
|
import { isUltragoalBypassPrompt, verifyUltragoalDurableCompletionState } from "../gjc-runtime/ultragoal-guard";
|
|
15
|
+
import { getSkillManifest } from "../gjc-runtime/workflow-manifest";
|
|
15
16
|
import { buildSessionContext, loadEntriesFromFile, type SessionEntry } from "../session/session-manager";
|
|
16
17
|
import {
|
|
17
18
|
readVisibleSkillActiveState as readCanonicalVisibleSkillActiveState,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
syncSkillActiveState,
|
|
21
22
|
} from "../skill-state/active-state";
|
|
22
23
|
import { initialPhaseForSkill } from "../skill-state/initial-phase";
|
|
24
|
+
import { readWorkflowGuardContext } from "../skill-state/workflow-mutation-guard";
|
|
23
25
|
|
|
24
26
|
// Re-export for existing callers and tests that imported it from this module.
|
|
25
27
|
export { initialPhaseForSkill };
|
|
@@ -531,7 +533,6 @@ function isTerminalModeState(state: ModeState | null): boolean {
|
|
|
531
533
|
* declared it is ready to chain but has not yet been demoted/cleared, so it
|
|
532
534
|
* must keep blocking until the chain (or an explicit clear) removes it.
|
|
533
535
|
*/
|
|
534
|
-
const STOP_RELEASING_PHASES = ["complete", "completed", "failed", "cancelled", "canceled", "inactive"] as const;
|
|
535
536
|
|
|
536
537
|
/**
|
|
537
538
|
* Handoff workflows must never stop silently — they always have to offer the
|
|
@@ -553,13 +554,13 @@ function isHandoffRequiredSkill(skill: GjcWorkflowSkill): boolean {
|
|
|
553
554
|
* mode-state preserves the historical fail-open behavior so a broken state file
|
|
554
555
|
* cannot lock a session.
|
|
555
556
|
*/
|
|
556
|
-
function modeStateReleasesStop(state: ModeState | null, handoffRequired: boolean): boolean {
|
|
557
|
+
function modeStateReleasesStop(state: ModeState | null, handoffRequired: boolean, skill: GjcWorkflowSkill): boolean {
|
|
557
558
|
if (!state) return !handoffRequired;
|
|
558
559
|
if (state.active !== true) return true;
|
|
559
560
|
const phase = String(state.current_phase ?? "")
|
|
560
561
|
.trim()
|
|
561
562
|
.toLowerCase();
|
|
562
|
-
if ((
|
|
563
|
+
if (getSkillManifest(skill).stopReleasingPhases.includes(phase)) return true;
|
|
563
564
|
if (!handoffRequired && phase === "handoff") return true;
|
|
564
565
|
return false;
|
|
565
566
|
}
|
|
@@ -743,12 +744,21 @@ function buildHandoffModeStateRecoveryMessage(skill: GjcWorkflowSkill, phase: st
|
|
|
743
744
|
}
|
|
744
745
|
|
|
745
746
|
function buildHandoffForceAskMessage(skill: GjcWorkflowSkill, phase: string, statePath: string): string {
|
|
747
|
+
if (skill === "deep-interview" && phase.trim().toLowerCase() === "interviewing") {
|
|
748
|
+
return `GJC deep-interview is still interviewing and must not stop (${statePath}). Continue the active round immediately: score and persist an answered round, then report progress. Use the ask tool for the next question. Only stop after crystallizing, recording a handoff, or explicitly cancelling the workflow. ${buildHandoffStopReleaseGuidance(skill)}`;
|
|
749
|
+
}
|
|
746
750
|
return `GJC handoff skill "${skill}" must not stop without offering a next step (phase: ${phase}; state: ${statePath}). ${buildHandoffStopReleaseGuidance(skill)}`;
|
|
747
751
|
}
|
|
748
752
|
|
|
749
753
|
export async function buildSkillStopOutput(input: StopHookInput): Promise<Record<string, unknown> | null> {
|
|
750
|
-
const
|
|
751
|
-
|
|
754
|
+
const guardContext = await readWorkflowGuardContext(input.cwd, {
|
|
755
|
+
sessionId: input.sessionId,
|
|
756
|
+
threadId: input.threadId,
|
|
757
|
+
});
|
|
758
|
+
const resolvedSessionId = guardContext.sessionId;
|
|
759
|
+
if (!resolvedSessionId) return null;
|
|
760
|
+
const skillState = guardContext.activeState;
|
|
761
|
+
|
|
752
762
|
const activeEntries = listActiveSkills(skillState)
|
|
753
763
|
.filter(isWorkflowActiveEntry)
|
|
754
764
|
.filter(entry =>
|
|
@@ -757,11 +767,7 @@ export async function buildSkillStopOutput(input: StopHookInput): Promise<Record
|
|
|
757
767
|
if (!skillState || activeEntries.length === 0) return null;
|
|
758
768
|
|
|
759
769
|
for (const entry of activeEntries) {
|
|
760
|
-
const modeState =
|
|
761
|
-
modeStatePath(input.cwd, entry.skill, resolvedSessionId),
|
|
762
|
-
"mode-state",
|
|
763
|
-
ModeStateSchema,
|
|
764
|
-
);
|
|
770
|
+
const modeState = guardContext.modeStates.get(entry.skill) ?? null;
|
|
765
771
|
const handoffRequired = isHandoffRequiredSkill(entry.skill);
|
|
766
772
|
if (!modeState && handoffRequired) {
|
|
767
773
|
const phase = String(entry.phase ?? skillState.phase ?? "active");
|
|
@@ -784,7 +790,7 @@ export async function buildSkillStopOutput(input: StopHookInput): Promise<Record
|
|
|
784
790
|
systemMessage: rescueMessage,
|
|
785
791
|
};
|
|
786
792
|
}
|
|
787
|
-
if (modeStateReleasesStop(modeState, handoffRequired)) {
|
|
793
|
+
if (modeStateReleasesStop(modeState, handoffRequired, entry.skill)) {
|
|
788
794
|
// A mode-state that claims it releases the Stop block must agree with
|
|
789
795
|
// authoritative durable state. If a stale/incoherent mode-state would
|
|
790
796
|
// release while the plan/ledger still shows pending work, block instead
|