@gajae-code/coding-agent 0.18.11 → 0.19.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 +760 -0
- package/dist/types/async/job-manager.d.ts +7 -3
- package/dist/types/async/result-formatting.d.ts +2 -0
- package/dist/types/autoresearch/git.d.ts +1 -1
- package/dist/types/capability/fs.d.ts +2 -2
- package/dist/types/capability/rule.d.ts +7 -2
- package/dist/types/cli/accounts-cli.d.ts +2 -2
- package/dist/types/cli/append-system-prompt.d.ts +6 -0
- package/dist/types/cli/args.d.ts +7 -2
- package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
- package/dist/types/cli/customize-doctor.d.ts +4 -3
- package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
- package/dist/types/cli/install-activation.d.ts +10 -4
- package/dist/types/cli/public-command-errors.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +0 -12
- package/dist/types/cli/update-cli.d.ts +21 -3
- package/dist/types/cli-main.d.ts +0 -6
- package/dist/types/cli-ordinary.d.ts +2 -2
- package/dist/types/commands/accounts.d.ts +2 -1
- package/dist/types/commands/launch.d.ts +0 -6
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +13 -0
- package/dist/types/commit/changelog/index.d.ts +1 -0
- package/dist/types/commit/map-reduce/index.d.ts +4 -4
- package/dist/types/config/autorouting-tier-map.d.ts +53 -1
- package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
- package/dist/types/config/config-hot-reload.d.ts +44 -0
- package/dist/types/config/file-lock.d.ts +52 -3
- package/dist/types/config/model-discovery-manager.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +79 -9
- package/dist/types/config/model-registry.d.ts +72 -8
- package/dist/types/config/model-resolver.d.ts +7 -4
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +57 -4
- package/dist/types/config/settings.d.ts +33 -1
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/customization/import.d.ts +2 -0
- package/dist/types/customization/types.d.ts +1 -1
- package/dist/types/dap/config.d.ts +8 -4
- package/dist/types/dap/session.d.ts +11 -2
- package/dist/types/debug/system-info.d.ts +6 -1
- package/dist/types/discovery/helpers.d.ts +7 -2
- package/dist/types/discovery/mcp-compat.d.ts +3 -1
- package/dist/types/edit/index.d.ts +5 -0
- package/dist/types/edit/modes/patch.d.ts +14 -0
- package/dist/types/edit/modes/replace.d.ts +3 -0
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +12 -0
- package/dist/types/export/ttsr.d.ts +19 -0
- package/dist/types/extensibility/extensions/types.d.ts +5 -1
- package/dist/types/extensibility/slash-commands.d.ts +1 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/content.d.ts +5 -6
- package/dist/types/internal-urls/types.d.ts +2 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
- package/dist/types/main.d.ts +23 -18
- package/dist/types/migrate/action-planner.d.ts +10 -1
- package/dist/types/migrate/adapters/codex.d.ts +2 -1
- package/dist/types/migrate/adapters/index.d.ts +42 -1
- package/dist/types/migrate/executor.d.ts +3 -1
- package/dist/types/migrate/hook-module.d.ts +23 -0
- package/dist/types/migrate/report.d.ts +2 -1
- package/dist/types/migrate/types.d.ts +82 -3
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +43 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
- package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/utils/host-status.d.ts +15 -11
- package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
- package/dist/types/runtime-mcp/manager.d.ts +7 -0
- package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
- package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
- package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
- package/dist/types/sdk/broker/broker-exit.d.ts +3 -3
- package/dist/types/sdk/broker/broker.d.ts +29 -2
- package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
- package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
- package/dist/types/sdk/broker/ensure.d.ts +141 -9
- package/dist/types/sdk/broker/hop.d.ts +45 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
- package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
- package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
- package/dist/types/sdk/broker/session-index.d.ts +6 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +44 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
- package/dist/types/sdk/cli/session-cli.d.ts +0 -1
- package/dist/types/sdk/host/control/operations.d.ts +5 -1
- package/dist/types/sdk/host/events.d.ts +4 -0
- package/dist/types/sdk/host/host.d.ts +2 -1
- package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
- package/dist/types/sdk/host/session-runtime.d.ts +40 -8
- package/dist/types/sdk/host/surface-policy.d.ts +2 -1
- package/dist/types/sdk/lifecycle/service.d.ts +12 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +11 -4
- package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
- package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +107 -9
- package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
- package/dist/types/session/messages.d.ts +5 -1
- package/dist/types/session/session-manager.d.ts +29 -3
- package/dist/types/session/startup-auth-config.d.ts +24 -2
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/session/terminal-abort.d.ts +3 -1
- package/dist/types/session-import/service.d.ts +1 -1
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +20 -4
- package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
- package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
- package/dist/types/ssh/ssh-executor.d.ts +8 -1
- package/dist/types/system-prompt.d.ts +3 -2
- package/dist/types/task/executor.d.ts +19 -2
- package/dist/types/task/role-body.d.ts +11 -0
- package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
- package/dist/types/tools/atomic-file-write.d.ts +9 -7
- package/dist/types/tools/awaiting-input.d.ts +28 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +17 -0
- package/dist/types/tools/bash-skill-urls.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/descriptors.d.ts +15 -2
- package/dist/types/tools/fetch.d.ts +7 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/irc.d.ts +5 -3
- package/dist/types/tools/python.d.ts +1 -1
- package/dist/types/tools/report-issue.d.ts +41 -0
- package/dist/types/tools/subagent.d.ts +23 -0
- package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/file-mentions.d.ts +5 -0
- package/dist/types/utils/image-limits.d.ts +7 -0
- package/dist/types/utils/image-loading.d.ts +0 -1
- package/dist/types/utils/open.d.ts +0 -1
- package/dist/types/utils/pasted-image-loading.d.ts +1 -5
- package/dist/types/utils/prompt-suggestion.d.ts +2 -2
- package/dist/types/utils/shell-snapshot.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +13 -3
- package/dist/types/web/scrapers/types.d.ts +8 -1
- package/dist/types/web/search/providers/codex.d.ts +6 -7
- package/dist/types/web/search/providers/insane.d.ts +7 -1
- package/dist/types/web/search/providers/searxng.d.ts +5 -0
- package/package.json +10 -9
- package/scripts/acp-conformance-agent.ts +12 -2
- package/scripts/build-binary.ts +2 -1
- package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
- package/scripts/generate-docs-index.ts +8 -1
- package/scripts/generate-sdk-operation-inventory.ts +38 -4
- package/src/async/job-manager.ts +356 -56
- package/src/async/result-formatting.ts +40 -0
- package/src/autoresearch/git.ts +9 -2
- package/src/autoresearch/prompts/prompt-setup.md +1 -1
- package/src/capability/fs.ts +58 -22
- package/src/capability/index.ts +2 -2
- package/src/capability/rule.ts +11 -2
- package/src/cli/accounts-cli.ts +176 -8
- package/src/cli/append-system-prompt.ts +12 -0
- package/src/cli/args.ts +11 -7
- package/src/cli/auth-broker-cli.ts +21 -11
- package/src/cli/customize-doctor-precedence.ts +31 -0
- package/src/cli/customize-doctor.ts +49 -39
- package/src/cli/doctor/install-repairs.ts +35 -5
- package/src/cli/fast-help.ts +1 -3
- package/src/cli/install-activation.ts +47 -4
- package/src/cli/migrate-cli.ts +19 -6
- package/src/cli/public-command-errors.ts +6 -0
- package/src/cli/root-flags.ts +2 -4
- package/src/cli/stats-cli.ts +9 -5
- package/src/cli/update-cli.ts +471 -13
- package/src/cli-ordinary.ts +7 -2
- package/src/commands/accounts.ts +10 -5
- package/src/commands/harness.ts +25 -15
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +277 -50
- package/src/commands/ultragoal.ts +16 -8
- package/src/commit/agentic/agent.ts +6 -6
- package/src/commit/analysis/conventional.ts +14 -5
- package/src/commit/changelog/index.ts +39 -1
- package/src/commit/map-reduce/index.ts +7 -7
- package/src/commit/message.ts +10 -3
- package/src/commit/pipeline.ts +12 -12
- package/src/config/autorouting-tier-map.ts +50 -1
- package/src/config/config-file.ts +106 -3
- package/src/config/config-hot-reload-trust.ts +148 -0
- package/src/config/config-hot-reload.ts +742 -0
- package/src/config/file-lock.ts +1615 -328
- package/src/config/keybindings.ts +154 -7
- package/src/config/model-bindings-applier.ts +85 -47
- package/src/config/model-discovery-manager.ts +10 -0
- package/src/config/model-profile-activation.ts +889 -148
- package/src/config/model-registry.ts +1706 -205
- package/src/config/model-resolver.ts +52 -22
- package/src/config/models-config-schema.ts +6 -0
- package/src/config/settings-schema.ts +73 -4
- package/src/config/settings.ts +460 -24
- package/src/coordinator-mcp/policy.ts +26 -1
- package/src/coordinator-mcp/server.ts +37 -2
- package/src/crash/sanitize.ts +7 -1
- package/src/customization/import.ts +279 -8
- package/src/customization/inventory.ts +24 -8
- package/src/customization/types.ts +1 -0
- package/src/dap/client.ts +3 -2
- package/src/dap/config.ts +31 -11
- package/src/dap/defaults.json +1 -1
- package/src/dap/session.ts +161 -36
- package/src/debug/crash-diagnostics.ts +89 -6
- package/src/debug/report-bundle.ts +90 -2
- package/src/debug/system-info.ts +8 -3
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/builtin.ts +4 -3
- package/src/discovery/helpers.ts +25 -16
- package/src/discovery/mcp-compat.ts +120 -17
- package/src/edit/apply-patch/index.ts +12 -1
- package/src/edit/index.ts +204 -22
- package/src/edit/modes/patch.ts +214 -8
- package/src/edit/modes/replace.ts +136 -0
- package/src/eval/js/context-manager.ts +29 -3
- package/src/eval/js/worker-entry.ts +51 -18
- package/src/eval/py/executor.ts +14 -4
- package/src/eval/py/runner.py +144 -5
- package/src/eval/py/runtime.ts +44 -4
- package/src/exec/bash-executor.ts +81 -4
- package/src/exec/bash-shell-guardian.ts +4 -1
- package/src/exec/bash-shell-supervisor.ts +8 -4
- package/src/exec/isolated-shell.ts +5 -1
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/html/index.ts +86 -6
- package/src/export/ttsr.ts +176 -1
- package/src/extensibility/custom-tools/wrapper.ts +3 -1
- package/src/extensibility/extensions/loader.ts +10 -1
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
- package/src/extensibility/extensions/types.ts +5 -1
- package/src/extensibility/hooks/loader.ts +10 -2
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
- package/src/extensibility/plugins/marketplace/manager.ts +36 -10
- package/src/extensibility/slash-commands.ts +5 -0
- package/src/gjc-runtime/gc-runtime.ts +37 -7
- package/src/gjc-runtime/launch-tmux.ts +30 -15
- package/src/gjc-runtime/launch-worktree.test.ts +148 -0
- package/src/gjc-runtime/launch-worktree.ts +101 -14
- package/src/gjc-runtime/managed-owner-admission.ts +46 -2
- package/src/gjc-runtime/ralplan-current-final.ts +294 -0
- package/src/gjc-runtime/ralplan-runtime.ts +95 -1
- package/src/gjc-runtime/session-resolution.ts +38 -6
- package/src/gjc-runtime/session-state-lock.ts +59 -14
- package/src/gjc-runtime/state-runtime.ts +23 -19
- package/src/gjc-runtime/state-writer.ts +109 -5
- package/src/gjc-runtime/tmux-sessions.ts +93 -13
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
- package/src/gjc-runtime/ultragoal-guard.ts +9 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
- package/src/harness-control-plane/finalize.ts +11 -0
- package/src/harness-control-plane/receipt-spool.ts +105 -28
- package/src/harness-control-plane/storage.ts +124 -6
- package/src/hindsight/content.ts +116 -16
- package/src/hindsight/mental-models.ts +22 -10
- package/src/hindsight/state.ts +7 -4
- package/src/hooks/native-skill-hook.ts +17 -12
- package/src/internal-urls/artifact-protocol.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +23 -22
- package/src/internal-urls/local-protocol.ts +167 -8
- package/src/internal-urls/types.ts +2 -0
- package/src/lsp/client.ts +2 -1
- package/src/lsp/clients/biome-client.ts +2 -0
- package/src/lsp/clients/swiftlint-client.ts +2 -0
- package/src/lsp/index.ts +29 -0
- package/src/lsp/workspace-path.ts +23 -0
- package/src/main.ts +330 -92
- package/src/memories/index.ts +25 -2
- package/src/migrate/action-planner.ts +574 -2
- package/src/migrate/adapters/claude-code.ts +162 -5
- package/src/migrate/adapters/codex.ts +77 -7
- package/src/migrate/adapters/index.ts +258 -5
- package/src/migrate/adapters/opencode.ts +10 -1
- package/src/migrate/executor.ts +222 -7
- package/src/migrate/hook-module.ts +193 -0
- package/src/migrate/report.ts +63 -2
- package/src/migrate/types.ts +84 -4
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/acp-agent.ts +10 -1
- package/src/modes/acp/permission-mode.ts +18 -1
- package/src/modes/components/agent-dashboard.ts +20 -4
- package/src/modes/components/assistant-message.ts +55 -1
- package/src/modes/components/customization/customization-dashboard.ts +7 -0
- package/src/modes/components/customization/import-wizard.ts +29 -3
- package/src/modes/components/model-selector.ts +157 -103
- package/src/modes/components/settings-selector.ts +103 -37
- package/src/modes/components/welcome.ts +3 -1
- package/src/modes/controllers/command-controller.ts +300 -9
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +31 -1
- package/src/modes/controllers/goal-mode-controller.ts +79 -10
- package/src/modes/controllers/input-controller.ts +2 -0
- package/src/modes/controllers/plan-mode-controller.ts +66 -11
- package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
- package/src/modes/controllers/selector-controller.ts +76 -7
- package/src/modes/interactive-mode.ts +123 -4
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +5 -1
- package/src/modes/theme/theme.ts +15 -0
- package/src/modes/utils/host-status.ts +21 -10
- package/src/prompts/agent-fragments/browser-aside.md +2 -2
- package/src/prompts/system/irc-incoming.md +3 -1
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/report-issue.md +13 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-credential-selector.ts +2 -2
- package/src/runtime-mcp/content-limits.ts +7 -0
- package/src/runtime-mcp/manager-pool.test.ts +5 -4
- package/src/runtime-mcp/manager.ts +40 -3
- package/src/runtime-mcp/oauth-flow.ts +32 -6
- package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
- package/src/runtime-mcp/project-host-values.ts +12 -8
- package/src/runtime-mcp/render.ts +16 -1
- package/src/runtime-mcp/tool-bridge.ts +194 -21
- package/src/runtime-mcp/transports/stdio.ts +2 -1
- package/src/sdk/broker/broker-exit.ts +10 -5
- package/src/sdk/broker/broker.ts +484 -85
- package/src/sdk/broker/daemon-entry.ts +338 -116
- package/src/sdk/broker/doctor-restart.ts +2 -1
- package/src/sdk/broker/ensure.ts +1214 -268
- package/src/sdk/broker/hop.ts +286 -0
- package/src/sdk/broker/lifecycle-ledger.ts +38 -11
- package/src/sdk/broker/lifecycle.ts +102 -6
- package/src/sdk/broker/lock-host-identity.ts +29 -0
- package/src/sdk/broker/session-index.ts +91 -16
- package/src/sdk/broker/transport.ts +17 -1
- package/src/sdk/bus/chat-command-policy.ts +16 -3
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +460 -35
- package/src/sdk/bus/reconciliation-store.ts +201 -8
- package/src/sdk/bus/session-control.ts +2 -0
- package/src/sdk/bus/slack-daemon.ts +105 -9
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -2
- package/src/sdk/bus/threaded-render.ts +20 -1
- package/src/sdk/cli/session-cli.ts +23 -6
- package/src/sdk/host/control/dispatch.ts +13 -1
- package/src/sdk/host/control/operations.ts +10 -1
- package/src/sdk/host/events.ts +67 -4
- package/src/sdk/host/host.ts +8 -2
- package/src/sdk/host/prompt-image-upload.ts +438 -0
- package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
- package/src/sdk/host/session-runtime.test.ts +1737 -129
- package/src/sdk/host/session-runtime.ts +684 -83
- package/src/sdk/host/surface-policy.ts +4 -0
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
- package/src/sdk/host/websocket-transport.ts +19 -7
- package/src/sdk/lifecycle/service.ts +15 -1
- package/src/sdk/prompt-deadline-flush.ts +78 -5
- package/src/sdk/prompt-deadline-manager.ts +68 -14
- package/src/sdk/prompt-status.ts +6 -4
- package/src/sdk/protocol/adapter-validation.ts +81 -12
- package/src/sdk/protocol/operation-inventory.generated.json +261 -2
- package/src/sdk/protocol/operation-registry.ts +33 -3
- package/src/sdk/reconciliation-extensions.ts +1 -0
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +237 -97
- package/src/session/agent-session.ts +2726 -479
- package/src/session/history-storage.ts +182 -12
- package/src/session/internal/managed-session-scope.ts +173 -57
- package/src/session/internal/managed-session-storage.ts +85 -12
- package/src/session/internal/native-publish-outcome.ts +5 -1
- package/src/session/messages.ts +47 -6
- package/src/session/session-manager.ts +326 -19
- package/src/session/startup-auth-config.ts +116 -8
- package/src/session/streaming-output.ts +262 -4
- package/src/session/terminal-abort.ts +14 -2
- package/src/session-import/redact.ts +53 -2
- package/src/session-import/service.ts +1 -0
- package/src/skill-state/workflow-mutation-guard.ts +2145 -206
- package/src/slash-commands/builtin-registry.ts +21 -6
- package/src/slash-commands/helpers/credential-switch.ts +10 -5
- package/src/slash-commands/helpers/mcp.ts +11 -3
- package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
- package/src/ssh/ssh-executor.ts +9 -2
- package/src/stt/recorder.ts +26 -9
- package/src/system-prompt.ts +61 -16
- package/src/task/executor.ts +115 -20
- package/src/task/index.ts +389 -175
- package/src/task/role-body.ts +84 -0
- package/src/task/token-log-mode.test.ts +162 -0
- package/src/task/token-log.ts +91 -2
- package/src/tools/archive-reader.ts +650 -37
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-edit-write-path.ts +64 -0
- package/src/tools/ast-edit.ts +18 -1
- package/src/tools/ast-grep.ts +7 -2
- package/src/tools/atomic-file-write.ts +46 -15
- package/src/tools/auto-generated-guard.ts +15 -3
- package/src/tools/awaiting-input.ts +114 -0
- package/src/tools/bash-allowed-prefixes.ts +214 -6
- package/src/tools/bash-interactive.ts +14 -2
- package/src/tools/bash-skill-urls.ts +58 -5
- package/src/tools/bash.ts +113 -26
- package/src/tools/browser/tab-worker.ts +39 -13
- package/src/tools/browser.ts +17 -7
- package/src/tools/cron.ts +10 -1
- package/src/tools/debug.ts +16 -8
- package/src/tools/descriptors.test.ts +44 -0
- package/src/tools/descriptors.ts +50 -6
- package/src/tools/eval.ts +15 -0
- package/src/tools/fetch.ts +93 -4
- package/src/tools/gh.ts +27 -12
- package/src/tools/image-gen.ts +20 -6
- package/src/tools/index.ts +2 -0
- package/src/tools/irc.ts +5 -3
- package/src/tools/monitor.ts +9 -1
- package/src/tools/output-meta.ts +74 -5
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +26 -37
- package/src/tools/recipe/runner.ts +23 -12
- package/src/tools/render-utils.ts +2 -2
- package/src/tools/report-issue.ts +184 -0
- package/src/tools/search.ts +3 -2
- package/src/tools/ssh.ts +274 -8
- package/src/tools/subagent-render.ts +114 -12
- package/src/tools/subagent.ts +118 -5
- package/src/tools/tmux-self-injection-guard.ts +3301 -122
- package/src/tools/todo-write.ts +41 -3
- package/src/tools/tool-catalog.generated.ts +32 -4
- package/src/tools/ultragoal-ask-guard.ts +13 -1
- package/src/tools/write.ts +42 -3
- package/src/tui/tree-list.ts +15 -1
- package/src/utils/commit-message-generator.ts +12 -4
- package/src/utils/external-editor.ts +21 -7
- package/src/utils/file-mentions.ts +75 -7
- package/src/utils/git.ts +38 -7
- package/src/utils/image-limits.ts +8 -0
- package/src/utils/image-loading.ts +1 -1
- package/src/utils/open.ts +73 -4
- package/src/utils/pasted-image-loading.ts +9 -8
- package/src/utils/prompt-suggestion.ts +5 -4
- package/src/utils/shell-snapshot.ts +9 -1
- package/src/utils/title-generator.ts +72 -22
- package/src/web/scrapers/index.ts +3 -1
- package/src/web/scrapers/types.ts +8 -0
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/index.ts +2 -1
- package/src/web/search/providers/codex.ts +29 -32
- package/src/web/search/providers/insane.ts +91 -28
- package/src/web/search/providers/searxng.ts +62 -10
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
3
|
import * as fs from "node:fs/promises";
|
|
3
4
|
import * as path from "node:path";
|
|
5
|
+
import { canonicalEnvKey, loadedProjectEnvSnapshot } from "@gajae-code/utils/dirs";
|
|
4
6
|
import { withFileLock } from "../config/file-lock";
|
|
5
7
|
import type { ReceiptEnvelope } from "./receipts";
|
|
6
8
|
|
|
@@ -21,6 +23,42 @@ export interface ReceiptSpoolAppendResult {
|
|
|
21
23
|
const receiptSpoolDirStorage = new AsyncLocalStorage<string | undefined>();
|
|
22
24
|
const spoolQueues = new Map<string, Promise<void>>();
|
|
23
25
|
const noop = (): void => undefined;
|
|
26
|
+
const NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0;
|
|
27
|
+
|
|
28
|
+
/** True only when an exception's `code` is `ENOENT`. Other codes propagate. */
|
|
29
|
+
export function isEnoent(error: unknown): boolean {
|
|
30
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Bun copies the cwd dotenv into `process.env` before module init. The startup
|
|
35
|
+
* project snapshot is the declaration set trusted directory overrides use: a
|
|
36
|
+
* path that resolves like that declaration, or a dynamic declaration, is not a
|
|
37
|
+
* spool directory. A shell or `--receipt-spool-dir` value that resolves
|
|
38
|
+
* somewhere else is kept.
|
|
39
|
+
*/
|
|
40
|
+
function projectDeclaredReceiptSpoolDir(raw: string): boolean {
|
|
41
|
+
const snapshot = loadedProjectEnvSnapshot();
|
|
42
|
+
const key = canonicalEnvKey(RECEIPT_SPOOL_DIR_ENV);
|
|
43
|
+
const projectValue = snapshot.values[key];
|
|
44
|
+
if (projectValue === undefined) return false;
|
|
45
|
+
if (snapshot.dynamic.has(key)) return true;
|
|
46
|
+
const declared = projectValue.trim();
|
|
47
|
+
const candidate = raw.trim();
|
|
48
|
+
if (!declared || !candidate) return declared === candidate;
|
|
49
|
+
return path.resolve(declared) === path.resolve(candidate);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function adoptReceiptSpoolDir(raw: string | undefined): string | undefined {
|
|
53
|
+
if (raw === undefined || projectDeclaredReceiptSpoolDir(raw)) return undefined;
|
|
54
|
+
const trimmed = raw.trim();
|
|
55
|
+
return trimmed ? path.resolve(trimmed) : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A spool directory the startup project snapshot does not declare. */
|
|
59
|
+
export function trustedReceiptSpoolDir(raw: string | undefined): string | undefined {
|
|
60
|
+
return adoptReceiptSpoolDir(raw);
|
|
61
|
+
}
|
|
24
62
|
|
|
25
63
|
interface ReceiptSpoolCursorCache {
|
|
26
64
|
mtimeMs: number;
|
|
@@ -44,10 +82,15 @@ export function resetReceiptSpoolCursorCacheForTesting(): void {
|
|
|
44
82
|
|
|
45
83
|
async function spoolStat(spoolFile: string): Promise<{ mtimeMs: number; size: number } | undefined> {
|
|
46
84
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
85
|
+
const handle = await fs.open(spoolFile, fsConstants.O_RDONLY | NOFOLLOW);
|
|
86
|
+
try {
|
|
87
|
+
const stats = await handle.stat();
|
|
88
|
+
return { mtimeMs: stats.mtimeMs, size: stats.size };
|
|
89
|
+
} finally {
|
|
90
|
+
await handle.close();
|
|
91
|
+
}
|
|
49
92
|
} catch (error) {
|
|
50
|
-
if ((error
|
|
93
|
+
if (isEnoent(error)) return undefined;
|
|
51
94
|
throw error;
|
|
52
95
|
}
|
|
53
96
|
}
|
|
@@ -64,15 +107,17 @@ async function cacheHighestCursor(spoolFile: string, highest: bigint): Promise<v
|
|
|
64
107
|
export async function withReceiptSpoolDir<T>(spoolDir: string, fn: () => Promise<T>): Promise<T> {
|
|
65
108
|
const trimmed = spoolDir.trim();
|
|
66
109
|
if (!trimmed) throw new Error("receipt_spool_dir_empty");
|
|
67
|
-
const resolved =
|
|
110
|
+
const resolved = adoptReceiptSpoolDir(trimmed);
|
|
111
|
+
// Owner input repeats process.env. A project-declared directory must not
|
|
112
|
+
// become the active spool via that copy.
|
|
113
|
+
if (!resolved) return fn();
|
|
68
114
|
return receiptSpoolDirStorage.run(resolved, fn);
|
|
69
115
|
}
|
|
70
116
|
|
|
71
117
|
export function resolveReceiptSpoolDir(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
72
118
|
const active = receiptSpoolDirStorage.getStore();
|
|
73
119
|
if (active !== undefined) return active;
|
|
74
|
-
|
|
75
|
-
return raw ? path.resolve(raw) : undefined;
|
|
120
|
+
return adoptReceiptSpoolDir(env[RECEIPT_SPOOL_DIR_ENV]);
|
|
76
121
|
}
|
|
77
122
|
|
|
78
123
|
export function receiptSpoolPath(spoolDir: string): string {
|
|
@@ -95,30 +140,56 @@ export function formatReceiptSpoolCursor(cursor: bigint): string {
|
|
|
95
140
|
|
|
96
141
|
export async function readHighestReceiptSpoolCursor(spoolDir: string): Promise<bigint> {
|
|
97
142
|
const spoolFile = receiptSpoolPath(spoolDir);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
143
|
+
let handle: fs.FileHandle;
|
|
144
|
+
try {
|
|
145
|
+
handle = await fs.open(spoolFile, fsConstants.O_RDONLY | NOFOLLOW);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (isEnoent(error)) {
|
|
148
|
+
cursorCache.delete(spoolFile);
|
|
149
|
+
return 0n;
|
|
150
|
+
}
|
|
151
|
+
throw error;
|
|
102
152
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
153
|
+
try {
|
|
154
|
+
const stats = await handle.stat();
|
|
155
|
+
const revision = { mtimeMs: stats.mtimeMs, size: stats.size };
|
|
156
|
+
const cached = cursorCache.get(spoolFile);
|
|
157
|
+
if (cached && sameSpoolRevision(cached, revision)) return cached.highest;
|
|
158
|
+
|
|
159
|
+
fullScanCount++;
|
|
160
|
+
const raw = await handle.readFile({ encoding: "utf8" });
|
|
161
|
+
let highest = 0n;
|
|
162
|
+
for (const line of raw.split("\n")) {
|
|
163
|
+
const trimmed = line.trim();
|
|
164
|
+
if (!trimmed) continue;
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(trimmed) as { cursor?: unknown };
|
|
167
|
+
const cursor = parseCursor(parsed.cursor);
|
|
168
|
+
if (cursor !== undefined && cursor > highest) highest = cursor;
|
|
169
|
+
} catch {
|
|
170
|
+
// A crash may leave a torn tail; a full scan ignores it and re-establishes the cache.
|
|
171
|
+
}
|
|
118
172
|
}
|
|
173
|
+
cursorCache.set(spoolFile, { ...revision, highest });
|
|
174
|
+
return highest;
|
|
175
|
+
} finally {
|
|
176
|
+
await handle.close();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function tightenSpoolDir(dir: string): Promise<void> {
|
|
181
|
+
if (process.platform === "win32") return;
|
|
182
|
+
let handle: fs.FileHandle;
|
|
183
|
+
try {
|
|
184
|
+
handle = await fs.open(dir, fsConstants.O_RDONLY | (fsConstants.O_DIRECTORY ?? 0) | NOFOLLOW);
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
await handle.chmod(0o700);
|
|
190
|
+
} finally {
|
|
191
|
+
await handle.close();
|
|
119
192
|
}
|
|
120
|
-
cursorCache.set(spoolFile, { ...revision, highest });
|
|
121
|
-
return highest;
|
|
122
193
|
}
|
|
123
194
|
|
|
124
195
|
async function enqueueSpoolAppend<T>(spoolFile: string, task: () => Promise<T>): Promise<T> {
|
|
@@ -142,13 +213,19 @@ export async function appendReceiptToSpool(
|
|
|
142
213
|
const spoolFile = receiptSpoolPath(resolvedDir);
|
|
143
214
|
return enqueueSpoolAppend(spoolFile, async () => {
|
|
144
215
|
await fs.mkdir(resolvedDir, { recursive: true, mode: 0o700 });
|
|
216
|
+
await tightenSpoolDir(resolvedDir);
|
|
145
217
|
return withFileLock(
|
|
146
218
|
spoolFile,
|
|
147
219
|
async () => {
|
|
148
220
|
const cursor = formatReceiptSpoolCursor((await readHighestReceiptSpoolCursor(resolvedDir)) + 1n);
|
|
149
221
|
const record: ReceiptSpoolRecord = { cursor, envelope };
|
|
150
|
-
const handle = await fs.open(
|
|
222
|
+
const handle = await fs.open(
|
|
223
|
+
spoolFile,
|
|
224
|
+
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_APPEND | NOFOLLOW,
|
|
225
|
+
0o600,
|
|
226
|
+
);
|
|
151
227
|
try {
|
|
228
|
+
if (process.platform !== "win32") await handle.chmod(0o600);
|
|
152
229
|
await handle.writeFile(`${JSON.stringify(record)}\n`, "utf8");
|
|
153
230
|
await handle.sync();
|
|
154
231
|
} finally {
|
|
@@ -293,11 +293,131 @@ export function sessionPaths(root: string, sessionId: string): SessionPaths {
|
|
|
293
293
|
};
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
const NO_FOLLOW = process.platform === "win32" ? 0 : (fsSync.constants.O_NOFOLLOW ?? 0);
|
|
297
|
+
|
|
298
|
+
function errnoCode(error: unknown): string | undefined {
|
|
299
|
+
return error instanceof Error && "code" in error ? (error as NodeJS.ErrnoException).code : undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function sameInode(left: fsSync.BigIntStats, right: fsSync.BigIntStats): boolean {
|
|
303
|
+
return left.dev === right.dev && left.ino === right.ino && left.nlink === right.nlink;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Leaf directory mode 0700. `lstat` plus `chmod` does not need read permission, so a
|
|
308
|
+
* mode 0300 directory stays writable. A symlink leaf is refused before chmod.
|
|
309
|
+
* This does not pin the directory against a later rename in a writable parent.
|
|
310
|
+
*/
|
|
311
|
+
async function ensureOwnerDirectory(dir: string): Promise<void> {
|
|
312
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
313
|
+
let current: fsSync.Stats;
|
|
314
|
+
try {
|
|
315
|
+
current = await fs.lstat(dir);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
const code = errnoCode(error);
|
|
318
|
+
if (code === "ELOOP" || code === "ENOTDIR") throw new Error("harness state directory is not a directory");
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
if (current.isSymbolicLink() || !current.isDirectory())
|
|
322
|
+
throw new Error("harness state directory is not a directory");
|
|
323
|
+
if (process.platform === "win32" || (current.mode & 0o077) === 0) return;
|
|
324
|
+
await fs.chmod(dir, 0o700);
|
|
325
|
+
const tightened = await fs.lstat(dir);
|
|
326
|
+
if (tightened.isSymbolicLink() || !tightened.isDirectory() || (tightened.mode & 0o777) !== 0o700) {
|
|
327
|
+
throw new Error("harness state directory could not be tightened to mode 0700");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function assertRegularOrMissing(file: string): Promise<void> {
|
|
332
|
+
try {
|
|
333
|
+
const existing = await fs.lstat(file);
|
|
334
|
+
if (existing.isSymbolicLink() || !existing.isFile()) throw new Error("harness state path is not a regular file");
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (errnoCode(error) !== "ENOENT") throw error;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Append like the workflow owner-only writer: mode 0600, `O_NOFOLLOW` when the file
|
|
342
|
+
* exists, `O_EXCL` when it does not, and chmod to 0600 when group or other bits are set.
|
|
343
|
+
*/
|
|
344
|
+
async function appendOwnerOnly(filePath: string, text: string): Promise<void> {
|
|
345
|
+
await ensureOwnerDirectory(path.dirname(filePath));
|
|
346
|
+
let handle: fs.FileHandle | undefined;
|
|
347
|
+
let initial: fsSync.BigIntStats | undefined;
|
|
348
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
349
|
+
initial = undefined;
|
|
350
|
+
try {
|
|
351
|
+
initial = await fs.lstat(filePath, { bigint: true });
|
|
352
|
+
if (initial.isSymbolicLink() || !initial.isFile()) throw new Error("harness state path is not a regular file");
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (errnoCode(error) !== "ENOENT") throw error;
|
|
355
|
+
}
|
|
356
|
+
const flags = initial
|
|
357
|
+
? fsSync.constants.O_WRONLY | fsSync.constants.O_APPEND | NO_FOLLOW
|
|
358
|
+
: fsSync.constants.O_WRONLY | fsSync.constants.O_APPEND | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL;
|
|
359
|
+
try {
|
|
360
|
+
handle = await fs.open(filePath, flags, 0o600);
|
|
361
|
+
break;
|
|
362
|
+
} catch (error) {
|
|
363
|
+
const code = errnoCode(error);
|
|
364
|
+
if (code === "ELOOP") throw new Error("harness state path is not a regular file");
|
|
365
|
+
if (initial === undefined && code === "EEXIST" && attempt === 0) continue;
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (!handle) throw new Error("harness state path identity changed before append");
|
|
370
|
+
try {
|
|
371
|
+
const opened = await handle.stat({ bigint: true });
|
|
372
|
+
const named = await fs.lstat(filePath, { bigint: true });
|
|
373
|
+
if (
|
|
374
|
+
opened.isSymbolicLink() ||
|
|
375
|
+
!opened.isFile() ||
|
|
376
|
+
named.isSymbolicLink() ||
|
|
377
|
+
!named.isFile() ||
|
|
378
|
+
(initial !== undefined && !sameInode(initial, opened)) ||
|
|
379
|
+
!sameInode(opened, named)
|
|
380
|
+
) {
|
|
381
|
+
throw new Error("harness state path identity changed before append");
|
|
382
|
+
}
|
|
383
|
+
if (process.platform !== "win32" && (Number(opened.mode) & 0o077) !== 0) {
|
|
384
|
+
await handle.chmod(0o600);
|
|
385
|
+
const tightened = await handle.stat({ bigint: true });
|
|
386
|
+
if ((Number(tightened.mode) & 0o777) !== 0o600) {
|
|
387
|
+
throw new Error("harness state path could not be tightened to mode 0600");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
await handle.writeFile(text, "utf8");
|
|
391
|
+
} finally {
|
|
392
|
+
await handle.close();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
296
396
|
async function writeJsonAtomic(file: string, value: unknown): Promise<void> {
|
|
297
|
-
await
|
|
397
|
+
await ensureOwnerDirectory(path.dirname(file));
|
|
398
|
+
await assertRegularOrMissing(file);
|
|
298
399
|
const tmp = `${file}.tmp-${randomBytes(4).toString("hex")}`;
|
|
299
|
-
|
|
300
|
-
await fs.
|
|
400
|
+
const flags = fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL | NO_FOLLOW;
|
|
401
|
+
const handle = await fs.open(tmp, flags, 0o600);
|
|
402
|
+
try {
|
|
403
|
+
if (process.platform !== "win32") {
|
|
404
|
+
await handle.chmod(0o600);
|
|
405
|
+
const created = await handle.stat();
|
|
406
|
+
if (!created.isFile() || (created.mode & 0o777) !== 0o600) {
|
|
407
|
+
throw new Error("harness state path could not be tightened to mode 0600");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
await handle.writeFile(`${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
411
|
+
} finally {
|
|
412
|
+
await handle.close();
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
await assertRegularOrMissing(file);
|
|
416
|
+
await fs.rename(tmp, file);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
await fs.rm(tmp, { force: true }).catch(() => undefined);
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
301
421
|
}
|
|
302
422
|
|
|
303
423
|
async function readJson<T>(file: string): Promise<T | null> {
|
|
@@ -396,7 +516,6 @@ export async function resolveHarnessSessionRoot(
|
|
|
396
516
|
|
|
397
517
|
export async function writeSessionState(root: string, state: SessionState): Promise<void> {
|
|
398
518
|
const paths = sessionPaths(root, state.sessionId);
|
|
399
|
-
await fs.mkdir(paths.dir, { recursive: true });
|
|
400
519
|
await writeJsonAtomic(paths.state, state);
|
|
401
520
|
}
|
|
402
521
|
|
|
@@ -407,8 +526,7 @@ export async function sessionExists(root: string, sessionId: string): Promise<bo
|
|
|
407
526
|
/** Append a single severity envelope to events.jsonl. Single-writer discipline is the owner's job (M3). */
|
|
408
527
|
export async function appendEvent(root: string, sessionId: string, envelope: EventEnvelope): Promise<void> {
|
|
409
528
|
const paths = sessionPaths(root, sessionId);
|
|
410
|
-
await
|
|
411
|
-
await fs.appendFile(paths.events, `${JSON.stringify(envelope)}\n`, "utf8");
|
|
529
|
+
await appendOwnerOnly(paths.events, `${JSON.stringify(envelope)}\n`);
|
|
412
530
|
}
|
|
413
531
|
|
|
414
532
|
/** Read events from cursor (exclusive). Tail-only: never mutates the log. */
|
package/src/hindsight/content.ts
CHANGED
|
@@ -20,27 +20,126 @@ export interface RecallResultLike {
|
|
|
20
20
|
mentioned_at?: string | null;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const MEMORY_TAG_NAMES = ["memories", "hindsight_memories", "relevant_memories", "mental_models"] as const;
|
|
24
|
+
|
|
25
|
+
function memoryTagExpression(name: string): RegExp {
|
|
26
|
+
return new RegExp(`<\\s*\\/?\\s*${name}\\s*>`, "gi");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface MemoryTagSpan {
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
close: boolean;
|
|
33
|
+
paired: boolean;
|
|
34
|
+
absorbed: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findMemoryTagSpans(content: string, name: string): MemoryTagSpan[] {
|
|
38
|
+
const spans: MemoryTagSpan[] = [];
|
|
39
|
+
for (const match of content.matchAll(memoryTagExpression(name))) {
|
|
40
|
+
const raw = match[0];
|
|
41
|
+
const start = match.index ?? 0;
|
|
42
|
+
spans.push({
|
|
43
|
+
start,
|
|
44
|
+
end: start + raw.length,
|
|
45
|
+
close: raw.includes("/"),
|
|
46
|
+
paired: false,
|
|
47
|
+
absorbed: false,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return spans;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function mergeSpans(ranges: Array<[number, number]>): Array<[number, number]> {
|
|
54
|
+
const sorted = ranges.filter(([start, end]) => end > start).sort((a, b) => a[0] - b[0] || b[1] - a[1]);
|
|
55
|
+
const merged: Array<[number, number]> = [];
|
|
56
|
+
for (const [start, end] of sorted) {
|
|
57
|
+
const last = merged[merged.length - 1];
|
|
58
|
+
if (!last || start > last[1]) merged.push([start, end]);
|
|
59
|
+
else last[1] = Math.max(last[1], end);
|
|
60
|
+
}
|
|
61
|
+
return merged;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Remove one memory-tag family.
|
|
66
|
+
*
|
|
67
|
+
* A matched block is removed whole. An extra closer after that block is the
|
|
68
|
+
* recall frame ending early: the escaped text through that closer is removed
|
|
69
|
+
* so it is not retained as instructions. A closer or opener with no block
|
|
70
|
+
* before it is removed as a token only, and the surrounding prose stays.
|
|
71
|
+
*/
|
|
72
|
+
function stripMemoryTagFamily(content: string, name: string): string {
|
|
73
|
+
const tags = findMemoryTagSpans(content, name);
|
|
74
|
+
if (tags.length === 0) return content;
|
|
75
|
+
|
|
76
|
+
const ranges: Array<[number, number]> = [];
|
|
77
|
+
const stack: MemoryTagSpan[] = [];
|
|
78
|
+
for (const tag of tags) {
|
|
79
|
+
if (!tag.close) {
|
|
80
|
+
stack.push(tag);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const open = stack.pop();
|
|
84
|
+
if (!open) continue;
|
|
85
|
+
open.paired = true;
|
|
86
|
+
tag.paired = true;
|
|
87
|
+
ranges.push([open.start, tag.end]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const tag of tags) {
|
|
91
|
+
if (!tag.close || tag.paired) continue;
|
|
92
|
+
let previous: MemoryTagSpan | undefined;
|
|
93
|
+
for (const candidate of tags) {
|
|
94
|
+
if (candidate.end <= tag.start) previous = candidate;
|
|
95
|
+
else break;
|
|
96
|
+
}
|
|
97
|
+
const followsClosedBlock = Boolean(previous?.close && (previous.paired || previous.absorbed));
|
|
98
|
+
if (followsClosedBlock && previous) {
|
|
99
|
+
ranges.push([previous.end, tag.end]);
|
|
100
|
+
tag.absorbed = true;
|
|
101
|
+
} else {
|
|
102
|
+
ranges.push([tag.start, tag.end]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const tag of tags) {
|
|
107
|
+
if (!tag.close && !tag.paired) ranges.push([tag.start, tag.end]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let out = content;
|
|
111
|
+
for (const [start, end] of mergeSpans(ranges).reverse()) {
|
|
112
|
+
out = out.slice(0, start) + out.slice(end);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Escape recognized tag delimiters so recalled text cannot close the outer frame. */
|
|
118
|
+
function stripMemoryTagTokens(text: string): string {
|
|
119
|
+
let out = text;
|
|
120
|
+
for (const name of MEMORY_TAG_NAMES) {
|
|
121
|
+
out = out.replace(memoryTagExpression(name), tag => tag.replace("<", "<"));
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
27
125
|
|
|
28
126
|
/**
|
|
29
127
|
* Strip `<memories>`, `<mental_models>`, and legacy memory blocks.
|
|
30
128
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* retaining.
|
|
129
|
+
* Matching is case-insensitive and allows whitespace inside the tag.
|
|
130
|
+
* Both per-turn recall and curated mental models are injected around the
|
|
131
|
+
* prompt. If a closer, a case variant, or an early-ended frame leaks into
|
|
132
|
+
* the retention transcript, the next retain stores the breakout as a new
|
|
133
|
+
* memory. Always strip before retaining.
|
|
37
134
|
*/
|
|
38
135
|
export function stripMemoryTags(content: string): string {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
136
|
+
let out = content;
|
|
137
|
+
while (true) {
|
|
138
|
+
let stripped = out;
|
|
139
|
+
for (const name of MEMORY_TAG_NAMES) stripped = stripMemoryTagFamily(stripped, name);
|
|
140
|
+
if (stripped === out) return out;
|
|
141
|
+
out = stripped;
|
|
142
|
+
}
|
|
44
143
|
}
|
|
45
144
|
|
|
46
145
|
/** Format recall results into a bullet list for context injection. */
|
|
@@ -50,7 +149,8 @@ export function formatMemories(results: RecallResultLike[]): string {
|
|
|
50
149
|
.map(r => {
|
|
51
150
|
const typeStr = r.type ? ` [${r.type}]` : "";
|
|
52
151
|
const dateStr = r.mentioned_at ? ` (${r.mentioned_at})` : "";
|
|
53
|
-
|
|
152
|
+
const text = stripMemoryTagTokens(r.text);
|
|
153
|
+
return `- ${text}${typeStr}${dateStr}`;
|
|
54
154
|
})
|
|
55
155
|
.join("\n\n");
|
|
56
156
|
}
|
|
@@ -219,6 +219,17 @@ const TRUNCATION_MARKER = "\n\n…[mental-model snapshot truncated at render bud
|
|
|
219
219
|
*/
|
|
220
220
|
const MIN_CONTENT_ROOM_CHARS = 64;
|
|
221
221
|
|
|
222
|
+
const MEMORY_FRAME_NAMES = ["memories", "hindsight_memories", "relevant_memories", "mental_models"] as const;
|
|
223
|
+
|
|
224
|
+
/** Escape the same frame tokens as recalled memory text so a stored closer cannot end this block early. */
|
|
225
|
+
function escapeMemoryFrameTokens(text: string): string {
|
|
226
|
+
let out = text;
|
|
227
|
+
for (const name of MEMORY_FRAME_NAMES) {
|
|
228
|
+
out = out.replace(new RegExp(`<\\s*\\/?\\s*${name}\\s*>`, "gi"), tag => tag.replace("<", "<"));
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
|
|
222
233
|
/** Smallest budget that can yield a usable block (wrapper + preamble + marker + a few chars of content). */
|
|
223
234
|
function minRenderBudgetChars(): number {
|
|
224
235
|
const cleanOverhead = `<mental_models>\n${PREAMBLE}\n\n\n</mental_models>`.length;
|
|
@@ -235,7 +246,6 @@ export function renderMentalModelsBlock(models: MentalModelSummary[], budgetChar
|
|
|
235
246
|
if (budgetChars < minRenderBudgetChars()) return "";
|
|
236
247
|
|
|
237
248
|
const truncatedOverhead = `<mental_models>\n${PREAMBLE}\n\n${TRUNCATION_MARKER}\n</mental_models>`.length;
|
|
238
|
-
const cleanOverhead = `<mental_models>\n${PREAMBLE}\n\n\n</mental_models>`.length;
|
|
239
249
|
const innerBudget = Math.max(0, budgetChars - truncatedOverhead);
|
|
240
250
|
const perModelBudget = Math.max(120, Math.floor(innerBudget / Math.max(1, models.length)));
|
|
241
251
|
|
|
@@ -260,18 +270,20 @@ export function renderMentalModelsBlock(models: MentalModelSummary[], budgetChar
|
|
|
260
270
|
consumed += sectionCost;
|
|
261
271
|
}
|
|
262
272
|
|
|
273
|
+
// Escape after the heading/body join. A closer split across that newline
|
|
274
|
+
// (`</mental_models` + `>`) is not visible in either field alone.
|
|
275
|
+
// The wrapper is added after this step and is not escaped.
|
|
276
|
+
let inner = escapeMemoryFrameTokens(sections.join("\n\n"));
|
|
263
277
|
const tail = truncated ? TRUNCATION_MARKER : "";
|
|
264
|
-
let assembled = `<mental_models>\n${PREAMBLE}\n\n${
|
|
278
|
+
let assembled = `<mental_models>\n${PREAMBLE}\n\n${inner}${tail}\n</mental_models>`;
|
|
265
279
|
|
|
266
|
-
// Final hard-cap:
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
// still find the closing tag.
|
|
280
|
+
// Final hard-cap: slice the already escaped body so a cut cannot restore a raw closer.
|
|
281
|
+
// The marker is always appended on this path, so reserve its overhead.
|
|
282
|
+
// Escaped tags are longer than the raw text the per-model budget measured.
|
|
270
283
|
if (assembled.length > budgetChars) {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
assembled = `<mental_models>\n${PREAMBLE}\n\n${body}${TRUNCATION_MARKER}\n</mental_models>`;
|
|
284
|
+
const room = Math.max(0, budgetChars - truncatedOverhead);
|
|
285
|
+
inner = inner.slice(0, room).trimEnd();
|
|
286
|
+
assembled = `<mental_models>\n${PREAMBLE}\n\n${inner}${TRUNCATION_MARKER}\n</mental_models>`;
|
|
275
287
|
}
|
|
276
288
|
return assembled;
|
|
277
289
|
}
|
package/src/hindsight/state.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import { logger } from "@gajae-code/utils";
|
|
4
|
+
import { redactMemorySecretsForTesting as redactMemorySecrets } from "../memories";
|
|
4
5
|
import type { AgentSession } from "../session/agent-session";
|
|
5
6
|
import { type BankScope, ensureBankMission } from "./bank";
|
|
6
7
|
import type { HindsightApi, MemoryItemInput } from "./client";
|
|
@@ -149,8 +150,8 @@ export class HindsightRetainQueue {
|
|
|
149
150
|
try {
|
|
150
151
|
await ensureBankMission(state.client, state.bankId, state.config, state.missionsSet);
|
|
151
152
|
const batch: MemoryItemInput[] = items.map(item => ({
|
|
152
|
-
content: item.content,
|
|
153
|
-
context: item.context
|
|
153
|
+
content: redactMemorySecrets(item.content),
|
|
154
|
+
context: item.context === undefined ? state.config.retainContext : redactMemorySecrets(item.context),
|
|
154
155
|
metadata: { session_id: sessionId },
|
|
155
156
|
tags: state.retainTags,
|
|
156
157
|
}));
|
|
@@ -316,8 +317,10 @@ export class HindsightSessionState {
|
|
|
316
317
|
documentId = `${this.sessionId}-${Date.now()}`;
|
|
317
318
|
}
|
|
318
319
|
|
|
319
|
-
const
|
|
320
|
-
if (!transcript) return false;
|
|
320
|
+
const prepared = prepareRetentionTranscript(target, true);
|
|
321
|
+
if (!prepared.transcript) return false;
|
|
322
|
+
// Same scrub the local memory backend applies before it stores consolidation text.
|
|
323
|
+
const transcript = redactMemorySecrets(prepared.transcript);
|
|
321
324
|
|
|
322
325
|
await ensureBankMission(this.client, this.bankId, this.config, this.missionsSet);
|
|
323
326
|
await this.client.retain(this.bankId, transcript, {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { appendFile, mkdir, stat } from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { getAgentDir, getConfigDirName } from "@gajae-code/utils";
|
|
4
|
+
import { projectEnvSnapshot } from "@gajae-code/utils/env-file";
|
|
4
5
|
import { YAML } from "bun";
|
|
5
6
|
import type { SkillDiscoverySettings } from "../config/skill-settings-defaults";
|
|
6
7
|
import { DEFAULT_DISABLED_EXTENSIONS, DEFAULT_SKILL_DISCOVERY_SETTINGS } from "../config/skill-settings-defaults";
|
|
7
8
|
import { sessionLogsDir } from "../gjc-runtime/session-layout";
|
|
9
|
+
import { trustedCoordinatorEnv } from "../gjc-runtime/sidecar-bootstrap-env";
|
|
8
10
|
import {
|
|
9
11
|
detectMcpDelegateFlowActivation,
|
|
10
12
|
type McpDelegateHostContextV1,
|
|
@@ -281,14 +283,14 @@ function readTurnId(payload: HookPayload): string | undefined {
|
|
|
281
283
|
return safeString(payload.turn_id ?? payload.turnId).trim() || undefined;
|
|
282
284
|
}
|
|
283
285
|
|
|
284
|
-
function readSessionFile(payload: HookPayload): string | undefined {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
);
|
|
286
|
+
function readSessionFile(payload: HookPayload, cwd: string): string | undefined {
|
|
287
|
+
const explicit = safeString(
|
|
288
|
+
payload.session_file ?? payload.sessionFile ?? payload.transcript_path ?? payload.transcriptPath,
|
|
289
|
+
).trim();
|
|
290
|
+
if (explicit) return explicit;
|
|
291
|
+
// Bun has already copied the project dotenv into the environment. A value
|
|
292
|
+
// that matches that snapshot must not select another transcript.
|
|
293
|
+
return trustedCoordinatorEnv("GJC_SESSION_FILE", process.env, projectEnvSnapshot(cwd));
|
|
292
294
|
}
|
|
293
295
|
|
|
294
296
|
export async function dispatchGjcNativeSkillHook(
|
|
@@ -297,6 +299,7 @@ export async function dispatchGjcNativeSkillHook(
|
|
|
297
299
|
): Promise<GjcNativeHookDispatchResult> {
|
|
298
300
|
const hookEventName = readHookEventName(payload);
|
|
299
301
|
const cwd = (options.cwd ?? safeString(payload.cwd).trim()) || process.cwd();
|
|
302
|
+
const sessionFile = readSessionFile(payload, cwd);
|
|
300
303
|
if (hookEventName === "UserPromptSubmit") {
|
|
301
304
|
const recoveryDiagnostics = await collectUserPromptStateRecoveryDiagnostics({
|
|
302
305
|
cwd,
|
|
@@ -304,7 +307,7 @@ export async function dispatchGjcNativeSkillHook(
|
|
|
304
307
|
threadId: readThreadId(payload),
|
|
305
308
|
stateDir: options.stateDir,
|
|
306
309
|
prompt: readPromptText(payload),
|
|
307
|
-
sessionFile
|
|
310
|
+
sessionFile,
|
|
308
311
|
});
|
|
309
312
|
const recoveryContext = buildStateRecoveryDiagnosticsContext(recoveryDiagnostics);
|
|
310
313
|
const prompt = readPromptText(payload);
|
|
@@ -346,7 +349,7 @@ export async function dispatchGjcNativeSkillHook(
|
|
|
346
349
|
threadId: readThreadId(payload),
|
|
347
350
|
stateDir: options.stateDir,
|
|
348
351
|
prompt,
|
|
349
|
-
sessionFile
|
|
352
|
+
sessionFile,
|
|
350
353
|
});
|
|
351
354
|
if (activeUltragoalContext?.startsWith("BLOCK_ULTRAGOAL_COMPLETION:")) {
|
|
352
355
|
return {
|
|
@@ -390,7 +393,7 @@ export async function dispatchGjcNativeSkillHook(
|
|
|
390
393
|
sessionId: readSessionId(payload),
|
|
391
394
|
threadId: readThreadId(payload),
|
|
392
395
|
stateDir: options.stateDir,
|
|
393
|
-
sessionFile
|
|
396
|
+
sessionFile,
|
|
394
397
|
}),
|
|
395
398
|
};
|
|
396
399
|
}
|
|
@@ -424,7 +427,9 @@ async function readStdinJson(): Promise<{ payload: HookPayload; parseError: Erro
|
|
|
424
427
|
}
|
|
425
428
|
|
|
426
429
|
async function logHookError(cwd: string, type: string, error: unknown): Promise<void> {
|
|
427
|
-
|
|
430
|
+
// Bun has already copied the project dotenv into the environment. A value
|
|
431
|
+
// that matches that snapshot must not select another session's log directory.
|
|
432
|
+
const gjcSessionId = trustedCoordinatorEnv("GJC_SESSION_ID", process.env, projectEnvSnapshot(cwd));
|
|
428
433
|
if (!gjcSessionId) {
|
|
429
434
|
console.error(
|
|
430
435
|
JSON.stringify({
|