salmon-loop 0.2.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/LICENSE +21 -0
- package/README.md +144 -0
- package/README.zh-CN.md +144 -0
- package/dist/cli/argv/headless-detection.js +60 -0
- package/dist/cli/argv/print-mode.js +60 -0
- package/dist/cli/authorization/allowlist.js +908 -0
- package/dist/cli/authorization/non-interactive.js +166 -0
- package/dist/cli/authorization/provider.js +416 -0
- package/dist/cli/chat-interface.js +83 -0
- package/dist/cli/chat.js +492 -0
- package/dist/cli/cli-runtime-context.js +12 -0
- package/dist/cli/commander-error-adapter.js +35 -0
- package/dist/cli/commander-error-meta.js +13 -0
- package/dist/cli/commands/allowlist.js +270 -0
- package/dist/cli/commands/chat.js +120 -0
- package/dist/cli/commands/config.js +250 -0
- package/dist/cli/commands/context.js +57 -0
- package/dist/cli/commands/dispatcher.js +53 -0
- package/dist/cli/commands/exit.js +9 -0
- package/dist/cli/commands/llm-output.js +135 -0
- package/dist/cli/commands/log-mode.js +143 -0
- package/dist/cli/commands/mode.js +136 -0
- package/dist/cli/commands/new.js +18 -0
- package/dist/cli/commands/parallel.js +256 -0
- package/dist/cli/commands/queue.js +130 -0
- package/dist/cli/commands/registry.js +85 -0
- package/dist/cli/commands/restore.js +26 -0
- package/dist/cli/commands/run/assistant-message.js +14 -0
- package/dist/cli/commands/run/config-resolution.js +37 -0
- package/dist/cli/commands/run/early-errors.js +108 -0
- package/dist/cli/commands/run/execute.js +73 -0
- package/dist/cli/commands/run/extensions-resolution.js +22 -0
- package/dist/cli/commands/run/handler.js +434 -0
- package/dist/cli/commands/run/headless-error-writer.js +182 -0
- package/dist/cli/commands/run/instruction-guard.js +24 -0
- package/dist/cli/commands/run/loop-params.js +46 -0
- package/dist/cli/commands/run/mode.js +8 -0
- package/dist/cli/commands/run/parse-options.js +67 -0
- package/dist/cli/commands/run/persist-session.js +35 -0
- package/dist/cli/commands/run/preflight.js +156 -0
- package/dist/cli/commands/run/reporter-factory.js +52 -0
- package/dist/cli/commands/run/runtime-llm.js +56 -0
- package/dist/cli/commands/run/runtime-options.js +30 -0
- package/dist/cli/commands/run/session.js +19 -0
- package/dist/cli/commands/run/structured-output.js +106 -0
- package/dist/cli/commands/run/types.js +2 -0
- package/dist/cli/commands/run/validate-options.js +28 -0
- package/dist/cli/commands/run/verbose.js +36 -0
- package/dist/cli/commands/run.js +2 -0
- package/dist/cli/commands/serve.js +323 -0
- package/dist/cli/commands/session.js +77 -0
- package/dist/cli/commands/snapshot-interactive.js +165 -0
- package/dist/cli/commands/snapshot.js +159 -0
- package/dist/cli/commands/status.js +17 -0
- package/dist/cli/commands/subagent.js +178 -0
- package/dist/cli/commands/subcommand-suggestions.js +63 -0
- package/dist/cli/commands/tool-names.js +155 -0
- package/dist/cli/commands/types.js +2 -0
- package/dist/cli/commands/utils.js +42 -0
- package/dist/cli/config.js +16 -0
- package/dist/cli/crash-reporter.js +5 -0
- package/dist/cli/headless/anthropic-stream-normalized-encoder.js +164 -0
- package/dist/cli/headless/anthropic-stream-protocol.js +62 -0
- package/dist/cli/headless/json-protocol.js +124 -0
- package/dist/cli/headless/native-stream-normalized-encoder.js +206 -0
- package/dist/cli/headless/openai-responses-canonical-applier.js +94 -0
- package/dist/cli/headless/openai-responses-state.js +294 -0
- package/dist/cli/headless/openai-stream-encoder.js +152 -0
- package/dist/cli/headless/stdout-writer.js +9 -0
- package/dist/cli/headless/stream-json-protocol.js +136 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/locales/en.js +409 -0
- package/dist/cli/locales/index.js +7 -0
- package/dist/cli/program-bootstrap.js +14 -0
- package/dist/cli/program-commands.js +106 -0
- package/dist/cli/program-options.js +15 -0
- package/dist/cli/program-output-mode.js +11 -0
- package/dist/cli/program-parse.js +24 -0
- package/dist/cli/reporters/anthropic-stream.js +77 -0
- package/dist/cli/reporters/base.js +2 -0
- package/dist/cli/reporters/json.js +69 -0
- package/dist/cli/reporters/openai-stream.js +72 -0
- package/dist/cli/reporters/standard.js +226 -0
- package/dist/cli/reporters/stderr-log-reporter.js +71 -0
- package/dist/cli/reporters/stream-json.js +111 -0
- package/dist/cli/run-cli.js +25 -0
- package/dist/cli/slash/runtime.js +240 -0
- package/dist/cli/ui/App.js +273 -0
- package/dist/cli/ui/authorization/bus.js +35 -0
- package/dist/cli/ui/components/CommandInput.js +200 -0
- package/dist/cli/ui/components/CommandSuggestionList.js +20 -0
- package/dist/cli/ui/components/Markdown.js +423 -0
- package/dist/cli/ui/components/MessageList.js +34 -0
- package/dist/cli/ui/components/StatusBannerLine.js +7 -0
- package/dist/cli/ui/components/TodoDrawer.js +60 -0
- package/dist/cli/ui/components/WelcomeMessage.js +14 -0
- package/dist/cli/ui/components/animations/StretchingThinking.js +51 -0
- package/dist/cli/ui/components/animations/ThinkingWave.js +15 -0
- package/dist/cli/ui/components/animations/TypeIndicator.js +30 -0
- package/dist/cli/ui/components/layout/SplitPane.js +11 -0
- package/dist/cli/ui/components/messageList/MessageItem.js +27 -0
- package/dist/cli/ui/components/messageList/QueuePreviewList.js +11 -0
- package/dist/cli/ui/components/messageList/items/EmphasisMessageItem.js +20 -0
- package/dist/cli/ui/components/messageList/items/InterruptMessageItem.js +10 -0
- package/dist/cli/ui/components/messageList/items/LightweightMessageItem.js +12 -0
- package/dist/cli/ui/components/messageList/items/StandardMessageItem.js +23 -0
- package/dist/cli/ui/components/messageList/items/WelcomeMessageItem.js +7 -0
- package/dist/cli/ui/components/messageList/messageListLayout.js +27 -0
- package/dist/cli/ui/components/messageList/streaming.js +51 -0
- package/dist/cli/ui/components/messageList/types.js +2 -0
- package/dist/cli/ui/components/messageList/utils.js +7 -0
- package/dist/cli/ui/components/sidebar/FileContext.js +8 -0
- package/dist/cli/ui/components/sidebar/MissionControl.js +8 -0
- package/dist/cli/ui/config.js +59 -0
- package/dist/cli/ui/hooks/useCommandLifecycle.js +110 -0
- package/dist/cli/ui/hooks/useCommandSuggestions.js +87 -0
- package/dist/cli/ui/hooks/useInputHistory.js +57 -0
- package/dist/cli/ui/hooks/useLoopEvents.js +382 -0
- package/dist/cli/ui/hooks/useLoopState.js +73 -0
- package/dist/cli/ui/hooks/useOnionExit.js +31 -0
- package/dist/cli/ui/hooks/useTerminalDimensions.js +34 -0
- package/dist/cli/ui/index.js +136 -0
- package/dist/cli/ui/selection/bus.js +35 -0
- package/dist/cli/ui/status/formatStatusBanner.js +8 -0
- package/dist/cli/ui/store/context.js +17 -0
- package/dist/cli/ui/store/reducer.js +264 -0
- package/dist/cli/ui/store/types.js +81 -0
- package/dist/cli/ui/styles/theme.js +295 -0
- package/dist/cli/ui/types.js +2 -0
- package/dist/cli/ui/utils/sanitizer.js +122 -0
- package/dist/cli/ui/utils/transcript.js +28 -0
- package/dist/cli/utils/asyncQueue.js +125 -0
- package/dist/cli/utils/audit-scope.js +10 -0
- package/dist/cli/utils/detectors/index.js +38 -0
- package/dist/cli/utils/llm-output.js +34 -0
- package/dist/cli/utils/outcome-reporter.js +17 -0
- package/dist/cli/utils/safe-fs.js +184 -0
- package/dist/cli/utils/verify-resolver.js +34 -0
- package/dist/cli/utils/worktree-prepare-resolver.js +18 -0
- package/dist/core/adapters/fs/atomic-file-writer.js +129 -0
- package/dist/core/adapters/fs/file-adapter.js +95 -0
- package/dist/core/adapters/fs/filesystem.js +31 -0
- package/dist/core/adapters/fs/index.js +5 -0
- package/dist/core/adapters/fs/node-fs.js +7 -0
- package/dist/core/adapters/fs/readonly-filesystem.js +23 -0
- package/dist/core/adapters/git/git-adapter.js +704 -0
- package/dist/core/adapters/git/git-runner.js +119 -0
- package/dist/core/adapters/git/lock-manager.js +314 -0
- package/dist/core/adapters/git/types.js +2 -0
- package/dist/core/adapters/path/index.js +2 -0
- package/dist/core/adapters/path/path-adapter.js +23 -0
- package/dist/core/ast/guard.js +116 -0
- package/dist/core/ast/index.js +4 -0
- package/dist/core/ast/parser.js +284 -0
- package/dist/core/ast/validator.js +46 -0
- package/dist/core/backends/salmon-loop/task-executor.js +68 -0
- package/dist/core/checkpoint-domain/manifest-store.js +379 -0
- package/dist/core/checkpoint-domain/service.js +84 -0
- package/dist/core/checkpoint-domain/types.js +2 -0
- package/dist/core/config/defaults.js +50 -0
- package/dist/core/config/errors.js +11 -0
- package/dist/core/config/file-format.js +108 -0
- package/dist/core/config/index.js +7 -0
- package/dist/core/config/limits.js +77 -0
- package/dist/core/config/load.js +34 -0
- package/dist/core/config/normalize.js +35 -0
- package/dist/core/config/paths.js +20 -0
- package/dist/core/config/redact.js +16 -0
- package/dist/core/config/resolve-env.js +43 -0
- package/dist/core/config/resolve-llm.js +130 -0
- package/dist/core/config/resolve.js +68 -0
- package/dist/core/config/resolvers/ast-validation.js +8 -0
- package/dist/core/config/resolvers/context.js +21 -0
- package/dist/core/config/resolvers/observability.js +45 -0
- package/dist/core/config/resolvers/output.js +8 -0
- package/dist/core/config/resolvers/permission-mode.js +6 -0
- package/dist/core/config/resolvers/security.js +14 -0
- package/dist/core/config/resolvers/server.js +36 -0
- package/dist/core/config/resolvers/tool-authorization.js +39 -0
- package/dist/core/config/resolvers/ui.js +26 -0
- package/dist/core/config/types/config-file.js +2 -0
- package/dist/core/config/types/primitives.js +9 -0
- package/dist/core/config/types/resolved.js +2 -0
- package/dist/core/config/types.js +4 -0
- package/dist/core/config/validate.js +852 -0
- package/dist/core/context/assembly/default-prompt-assembler.js +7 -0
- package/dist/core/context/assembly/prompt-assembler.js +2 -0
- package/dist/core/context/ast/import-extractor.js +28 -0
- package/dist/core/context/ast/module-resolver.js +61 -0
- package/dist/core/context/ast/source-outline.js +25 -0
- package/dist/core/context/audit-constants.js +23 -0
- package/dist/core/context/audit.js +54 -0
- package/dist/core/context/budget/dynamic-adjuster.js +149 -0
- package/dist/core/context/budget/example-integration.js +49 -0
- package/dist/core/context/budget/integration.js +93 -0
- package/dist/core/context/builder.js +289 -0
- package/dist/core/context/cache/errors.js +16 -0
- package/dist/core/context/cache/incremental-updater.js +131 -0
- package/dist/core/context/cache/index.js +25 -0
- package/dist/core/context/cache/path-resolver.js +127 -0
- package/dist/core/context/cache/prompt-caching.js +207 -0
- package/dist/core/context/cache/store-factory.js +63 -0
- package/dist/core/context/cache/store.js +193 -0
- package/dist/core/context/cache/types.js +15 -0
- package/dist/core/context/compression/js-like-comments.js +139 -0
- package/dist/core/context/compression/smart-compress.js +61 -0
- package/dist/core/context/compression/whitespace.js +26 -0
- package/dist/core/context/dependencies.js +102 -0
- package/dist/core/context/effectiveness/index.js +25 -0
- package/dist/core/context/effectiveness/tracker.js +253 -0
- package/dist/core/context/effectiveness/types.js +15 -0
- package/dist/core/context/formatters/index.js +7 -0
- package/dist/core/context/formatters/json-converter.js +662 -0
- package/dist/core/context/formatters/types.js +6 -0
- package/dist/core/context/formatters/xml-context.js +296 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +75 -0
- package/dist/core/context/gatherers/artifact-gatherer.js +53 -0
- package/dist/core/context/gatherers/ast-gatherer.js +370 -0
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +46 -0
- package/dist/core/context/gatherers/git-diff-gatherer.js +91 -0
- package/dist/core/context/gatherers/git-history-gatherer.js +57 -0
- package/dist/core/context/gatherers/knowledge-gatherer.js +101 -0
- package/dist/core/context/gatherers/metadata-gatherer.js +59 -0
- package/dist/core/context/gatherers/primary-text-gatherer.js +36 -0
- package/dist/core/context/gatherers/ripgrep-gatherer.js +104 -0
- package/dist/core/context/hash.js +52 -0
- package/dist/core/context/index.js +3 -0
- package/dist/core/context/keywords.js +179 -0
- package/dist/core/context/policies/budget-policy.js +36 -0
- package/dist/core/context/policies/pack-until-full.js +419 -0
- package/dist/core/context/scoring/relevance.js +191 -0
- package/dist/core/context/service-deps.js +32 -0
- package/dist/core/context/service-helpers.js +32 -0
- package/dist/core/context/service.js +265 -0
- package/dist/core/context/steps/context-budget.js +157 -0
- package/dist/core/context/steps/context-gather.js +71 -0
- package/dist/core/context/steps/context-primary.js +19 -0
- package/dist/core/context/steps/context-promotion.js +78 -0
- package/dist/core/context/steps/context-targets.js +85 -0
- package/dist/core/context/steps/types.js +2 -0
- package/dist/core/context/summarization/index.js +27 -0
- package/dist/core/context/summarization/prompts.js +80 -0
- package/dist/core/context/summarization/summarizer.js +377 -0
- package/dist/core/context/summarization/types.js +29 -0
- package/dist/core/context/targeting/churn-policy.js +27 -0
- package/dist/core/context/targeting/target-resolver.js +491 -0
- package/dist/core/context/token/adaptive-budget.js +364 -0
- package/dist/core/context/token/cache.js +163 -0
- package/dist/core/context/token/counter.js +190 -0
- package/dist/core/context/token/encoding-registry.js +173 -0
- package/dist/core/context/token/index.js +31 -0
- package/dist/core/context/token/token-budget.js +213 -0
- package/dist/core/context/token/types.js +10 -0
- package/dist/core/context/truncation/index.js +23 -0
- package/dist/core/context/truncation/semantic-truncator.js +103 -0
- package/dist/core/context/truncation/strategies/error-stack.js +94 -0
- package/dist/core/context/truncation/strategies/generic.js +48 -0
- package/dist/core/context/truncation/strategies/git-diff.js +99 -0
- package/dist/core/context/truncation/strategies/index.js +10 -0
- package/dist/core/context/truncation/strategies/json.js +142 -0
- package/dist/core/context/truncation/strategies/log.js +131 -0
- package/dist/core/context/truncation/strategies/test-result.js +140 -0
- package/dist/core/context/truncation/type-detector.js +133 -0
- package/dist/core/context/truncation/types.js +16 -0
- package/dist/core/context/types.js +2 -0
- package/dist/core/extensions/index.js +118 -0
- package/dist/core/extensions/load.js +36 -0
- package/dist/core/extensions/merge.js +29 -0
- package/dist/core/extensions/paths.js +40 -0
- package/dist/core/extensions/redact.js +37 -0
- package/dist/core/extensions/schemas.js +70 -0
- package/dist/core/extensions/types.js +2 -0
- package/dist/core/facades/cli-authorization-allowlist.js +3 -0
- package/dist/core/facades/cli-authorization-non-interactive.js +3 -0
- package/dist/core/facades/cli-authorization-provider.js +2 -0
- package/dist/core/facades/cli-chat.js +11 -0
- package/dist/core/facades/cli-command-allowlist.js +3 -0
- package/dist/core/facades/cli-command-chat.js +8 -0
- package/dist/core/facades/cli-command-checkpoint.js +3 -0
- package/dist/core/facades/cli-command-config.js +10 -0
- package/dist/core/facades/cli-command-dispatcher.js +2 -0
- package/dist/core/facades/cli-command-parallel.js +8 -0
- package/dist/core/facades/cli-command-session.js +2 -0
- package/dist/core/facades/cli-command-tool-names.js +6 -0
- package/dist/core/facades/cli-context.js +8 -0
- package/dist/core/facades/cli-headless.js +3 -0
- package/dist/core/facades/cli-observability.js +3 -0
- package/dist/core/facades/cli-program-bootstrap.js +2 -0
- package/dist/core/facades/cli-reporters.js +5 -0
- package/dist/core/facades/cli-run-execute.js +3 -0
- package/dist/core/facades/cli-run-handler.js +7 -0
- package/dist/core/facades/cli-run-headless-error-writer.js +2 -0
- package/dist/core/facades/cli-run-loop-params.js +2 -0
- package/dist/core/facades/cli-run-persist-session.js +2 -0
- package/dist/core/facades/cli-run-runtime-llm.js +5 -0
- package/dist/core/facades/cli-serve.js +21 -0
- package/dist/core/facades/cli-slash-runtime.js +9 -0
- package/dist/core/facades/cli-subagent.js +2 -0
- package/dist/core/facades/cli-ui.js +5 -0
- package/dist/core/facades/cli-utils-llm-output.js +3 -0
- package/dist/core/facades/cli-utils-path.js +2 -0
- package/dist/core/facades/cli-utils-worktree.js +2 -0
- package/dist/core/failure/diagnostics.js +221 -0
- package/dist/core/feedback/index.js +28 -0
- package/dist/core/feedback/parsers.js +59 -0
- package/dist/core/feedback/patterns.js +26 -0
- package/dist/core/feedback/types.js +2 -0
- package/dist/core/grizzco/domain/grizzco-types.js +41 -0
- package/dist/core/grizzco/dsl/DecisionEngine.js +149 -0
- package/dist/core/grizzco/dsl/MicroTaskRunner.js +39 -0
- package/dist/core/grizzco/dsl/llm-strategy.js +80 -0
- package/dist/core/grizzco/dsl/strategies.js +69 -0
- package/dist/core/grizzco/dsl/types.js +2 -0
- package/dist/core/grizzco/engine/observability/event-adapter.js +41 -0
- package/dist/core/grizzco/engine/observability/index.js +3 -0
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +51 -0
- package/dist/core/grizzco/engine/outcome/index.js +2 -0
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +167 -0
- package/dist/core/grizzco/engine/pipeline/pipeline.js +335 -0
- package/dist/core/grizzco/engine/pipeline/types.js +2 -0
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +242 -0
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +44 -0
- package/dist/core/grizzco/engine/transaction/index.js +3 -0
- package/dist/core/grizzco/engine/transaction/report-mapper.js +50 -0
- package/dist/core/grizzco/engine/transaction/retry-policy.js +19 -0
- package/dist/core/grizzco/engine/transaction/runner-builder.js +45 -0
- package/dist/core/grizzco/engine/transaction/session.js +58 -0
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +193 -0
- package/dist/core/grizzco/engine/transaction/types.js +2 -0
- package/dist/core/grizzco/execution/Executor.js +58 -0
- package/dist/core/grizzco/execution/RejectionManager.js +71 -0
- package/dist/core/grizzco/execution/WorkerFactory.js +31 -0
- package/dist/core/grizzco/flows/SalmonLoopFlow.js +102 -0
- package/dist/core/grizzco/runtime/apply-back-runtime.js +136 -0
- package/dist/core/grizzco/runtime/apply-back-utils.js +13 -0
- package/dist/core/grizzco/runtime/host/host-runner.js +99 -0
- package/dist/core/grizzco/runtime/host/index.js +2 -0
- package/dist/core/grizzco/runtime/host/types.js +2 -0
- package/dist/core/grizzco/services/CachedService.js +42 -0
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +38 -0
- package/dist/core/grizzco/services/implementations/mock/MockLockService.js +11 -0
- package/dist/core/grizzco/services/implementations/mock/MockUserQuotaService.js +11 -0
- package/dist/core/grizzco/services/registry.js +30 -0
- package/dist/core/grizzco/services/types.js +2 -0
- package/dist/core/grizzco/steps/answer.js +75 -0
- package/dist/core/grizzco/steps/apply-back.js +46 -0
- package/dist/core/grizzco/steps/apply.js +136 -0
- package/dist/core/grizzco/steps/ast-validate.js +37 -0
- package/dist/core/grizzco/steps/audit.js +311 -0
- package/dist/core/grizzco/steps/context.js +74 -0
- package/dist/core/grizzco/steps/display-answer.js +6 -0
- package/dist/core/grizzco/steps/display-report.js +158 -0
- package/dist/core/grizzco/steps/display-research.js +6 -0
- package/dist/core/grizzco/steps/displayReview.js +6 -0
- package/dist/core/grizzco/steps/explore.js +245 -0
- package/dist/core/grizzco/steps/extractIssues.js +27 -0
- package/dist/core/grizzco/steps/generateFixPlan.js +13 -0
- package/dist/core/grizzco/steps/generateReview.js +71 -0
- package/dist/core/grizzco/steps/patch.js +220 -0
- package/dist/core/grizzco/steps/plan.js +191 -0
- package/dist/core/grizzco/steps/preflight.js +93 -0
- package/dist/core/grizzco/steps/prepare-deps.js +49 -0
- package/dist/core/grizzco/steps/read-only-shrink.js +4 -0
- package/dist/core/grizzco/steps/research.js +188 -0
- package/dist/core/grizzco/steps/rollback.js +138 -0
- package/dist/core/grizzco/steps/shrink.js +64 -0
- package/dist/core/grizzco/steps/validate.js +40 -0
- package/dist/core/grizzco/steps/verify.js +136 -0
- package/dist/core/grizzco/validation/AstValidationService.js +133 -0
- package/dist/core/grizzco/validation/ContextValidator.js +17 -0
- package/dist/core/grizzco/validation/ast-validation-policy.js +11 -0
- package/dist/core/grizzco/workers/direct-write-worker.js +44 -0
- package/dist/core/grizzco/workers/git-apply-worker.js +75 -0
- package/dist/core/grizzco/workers/i-merge-worker.js +2 -0
- package/dist/core/grizzco/workers/mm-three-way-worker.js +117 -0
- package/dist/core/grizzco/workers/no-op-worker.js +18 -0
- package/dist/core/grizzco/workers/overwrite-binary-worker.js +29 -0
- package/dist/core/grizzco/workers/strata-sync-worker.js +69 -0
- package/dist/core/grizzco/workers/three-way-merge-worker.js +84 -0
- package/dist/core/grizzco/workers/three-way-staged-worker.js +93 -0
- package/dist/core/grizzco/workers/union-merge-worker.js +71 -0
- package/dist/core/history/input-history.js +55 -0
- package/dist/core/intent/chat-intent.js +250 -0
- package/dist/core/interaction/events/bus.js +52 -0
- package/dist/core/interaction/model/events.js +2 -0
- package/dist/core/interaction/model/index.js +3 -0
- package/dist/core/interaction/model/task-state.js +9 -0
- package/dist/core/interaction/model/transition-policy.js +50 -0
- package/dist/core/interaction/model/types.js +2 -0
- package/dist/core/interaction/orchestration/facade.js +190 -0
- package/dist/core/interaction/orchestration/index.js +2 -0
- package/dist/core/interaction/orchestration/store.js +32 -0
- package/dist/core/interaction/sync/task-sync-engine.js +57 -0
- package/dist/core/interaction/turn-stop-reason.js +27 -0
- package/dist/core/language-support/index.js +3 -0
- package/dist/core/language-support/orchestrator.js +37 -0
- package/dist/core/language-support/strategies/extension-candidate-strategy.js +27 -0
- package/dist/core/language-support/strategies/index.js +3 -0
- package/dist/core/language-support/strategies/language-query-strategy.js +26 -0
- package/dist/core/llm/ai-sdk/chat-executor.js +88 -0
- package/dist/core/llm/ai-sdk/langfuse-headers.js +28 -0
- package/dist/core/llm/ai-sdk/message-mapper.js +240 -0
- package/dist/core/llm/ai-sdk/observation-context.js +16 -0
- package/dist/core/llm/ai-sdk/provider-factory.js +29 -0
- package/dist/core/llm/ai-sdk/request-params.js +18 -0
- package/dist/core/llm/ai-sdk/request-runtime.js +168 -0
- package/dist/core/llm/ai-sdk/result-mapper.js +31 -0
- package/dist/core/llm/ai-sdk/retry-classifier.js +82 -0
- package/dist/core/llm/ai-sdk/retry-executor.js +38 -0
- package/dist/core/llm/ai-sdk.js +92 -0
- package/dist/core/llm/audit.js +2 -0
- package/dist/core/llm/base-url.js +18 -0
- package/dist/core/llm/contracts/repair.js +68 -0
- package/dist/core/llm/errors.js +172 -0
- package/dist/core/llm/factory.js +21 -0
- package/dist/core/llm/http/index.js +2 -0
- package/dist/core/llm/index.js +6 -0
- package/dist/core/llm/message-composition.js +25 -0
- package/dist/core/llm/openai.js +69 -0
- package/dist/core/llm/output-policy.js +192 -0
- package/dist/core/llm/phase-router.js +55 -0
- package/dist/core/llm/redact.js +37 -0
- package/dist/core/llm/registry.js +81 -0
- package/dist/core/llm/retry-utils.js +114 -0
- package/dist/core/llm/stream-utils.js +87 -0
- package/dist/core/llm/utils.js +82 -0
- package/dist/core/observability/audit-file.js +199 -0
- package/dist/core/observability/audit-trail.js +125 -0
- package/dist/core/observability/authorization-decisions.js +54 -0
- package/dist/core/observability/debug-artifacts.js +61 -0
- package/dist/core/observability/error-envelope.js +63 -0
- package/dist/core/observability/error-mapping.js +271 -0
- package/dist/core/observability/ignored-error.js +6 -0
- package/dist/core/observability/logger.js +457 -0
- package/dist/core/observability/loop-event-reporter.js +46 -0
- package/dist/core/observability/monitor.js +240 -0
- package/dist/core/observability/run-outcome-reporter.js +15 -0
- package/dist/core/observability/token-usage.js +36 -0
- package/dist/core/observability/ui-log-sanitize.js +35 -0
- package/dist/core/patch/aggregator.js +93 -0
- package/dist/core/patch/diff.js +298 -0
- package/dist/core/permission-gate/default-gate.js +115 -0
- package/dist/core/permission-gate/gate.js +2 -0
- package/dist/core/permission-gate/types.js +2 -0
- package/dist/core/plan/index.js +2 -0
- package/dist/core/plan/manager.js +123 -0
- package/dist/core/plan/markdown-editor.js +238 -0
- package/dist/core/plan/storage.js +75 -0
- package/dist/core/plan/types.js +2 -0
- package/dist/core/plugin/interface.js +2 -0
- package/dist/core/plugin/loader.js +130 -0
- package/dist/core/plugin/registry.js +90 -0
- package/dist/core/plugin/validator.js +98 -0
- package/dist/core/prompts/registry.js +189 -0
- package/dist/core/prompts/runtime.js +69 -0
- package/dist/core/prompts/schema.js +2 -0
- package/dist/core/prompts/templates/phases/explore_user.hbs +26 -0
- package/dist/core/prompts/templates/phases/patch_user.hbs +57 -0
- package/dist/core/prompts/templates/phases/plan_user.hbs +33 -0
- package/dist/core/prompts/templates/system/_context_json_legend.hbs +21 -0
- package/dist/core/prompts/templates/system/_tool_defs.hbs +60 -0
- package/dist/core/prompts/templates/system/explore_system.hbs +26 -0
- package/dist/core/prompts/templates/system/main_system.hbs +18 -0
- package/dist/core/prompts/templates/system/patch_system.hbs +10 -0
- package/dist/core/prompts/templates/system/plan_system.hbs +1 -0
- package/dist/core/prompts/templates/system/reflection.hbs +39 -0
- package/dist/core/protocols/a2a/agent-card.js +30 -0
- package/dist/core/protocols/a2a/mapper.js +14 -0
- package/dist/core/protocols/a2a/sdk/auth-middleware.js +31 -0
- package/dist/core/protocols/a2a/sdk/executor.js +301 -0
- package/dist/core/protocols/a2a/sdk/server.js +24 -0
- package/dist/core/protocols/a2a/task-projection.js +45 -0
- package/dist/core/protocols/acp/acp-command-runner.js +204 -0
- package/dist/core/protocols/acp/acp-filesystem.js +43 -0
- package/dist/core/protocols/acp/checkpoint-meta.js +2 -0
- package/dist/core/protocols/acp/formal-agent.js +1201 -0
- package/dist/core/protocols/acp/handlers.js +51 -0
- package/dist/core/protocols/acp/permission-provider.js +122 -0
- package/dist/core/protocols/acp/stdio-server.js +116 -0
- package/dist/core/reflection/engine.js +55 -0
- package/dist/core/reflection/types.js +2 -0
- package/dist/core/runtime/agent-server-runtime.js +88 -0
- package/dist/core/runtime/bun-runtime.js +26 -0
- package/dist/core/runtime/command-runner-context.js +16 -0
- package/dist/core/runtime/exit-codes.js +11 -0
- package/dist/core/runtime/fastify-fetch-bridge.js +51 -0
- package/dist/core/runtime/fastify-server-bundle.js +26 -0
- package/dist/core/runtime/initialize.js +132 -0
- package/dist/core/runtime/loop-finalize.js +71 -0
- package/dist/core/runtime/loop-run-lifecycle.js +73 -0
- package/dist/core/runtime/loop-run-reporter.js +19 -0
- package/dist/core/runtime/loop-runtime-config.js +26 -0
- package/dist/core/runtime/loop-session-runner.js +30 -0
- package/dist/core/runtime/loop.js +84 -0
- package/dist/core/runtime/paths.js +84 -0
- package/dist/core/runtime/process-runner.js +16 -0
- package/dist/core/runtime/process-types.js +2 -0
- package/dist/core/runtime/semaphore.js +41 -0
- package/dist/core/runtime/sidecar-fastify-plugin.js +35 -0
- package/dist/core/runtime/sidecar-paths.js +47 -0
- package/dist/core/runtime/sidecar-route-catalog.js +103 -0
- package/dist/core/runtime/spawn-command.js +392 -0
- package/dist/core/runtime/spawn-interactive.js +71 -0
- package/dist/core/security/redaction.js +160 -0
- package/dist/core/session/compression.js +323 -0
- package/dist/core/session/flow.js +85 -0
- package/dist/core/session/manager.js +313 -0
- package/dist/core/session/pruning-strategy.js +153 -0
- package/dist/core/session/session-context-builder.js +122 -0
- package/dist/core/session/summary-sync.js +82 -0
- package/dist/core/session/token-tracker.js +82 -0
- package/dist/core/session/types.js +2 -0
- package/dist/core/skills/bridge.js +33 -0
- package/dist/core/skills/index.js +8 -0
- package/dist/core/skills/loader.js +80 -0
- package/dist/core/skills/parser.js +66 -0
- package/dist/core/skills/runtime/MicroTaskRunner.js +102 -0
- package/dist/core/skills/runtime/SkillRunner.js +108 -0
- package/dist/core/skills/strategy.js +29 -0
- package/dist/core/skills/types.js +2 -0
- package/dist/core/slash/index.js +6 -0
- package/dist/core/slash/parser.js +33 -0
- package/dist/core/slash/registry.js +78 -0
- package/dist/core/slash/router.js +76 -0
- package/dist/core/slash/steps/slash-decide.js +19 -0
- package/dist/core/slash/steps/slash-execute.js +73 -0
- package/dist/core/slash/steps/types.js +2 -0
- package/dist/core/slash/strategy.js +33 -0
- package/dist/core/slash/types.js +2 -0
- package/dist/core/strata/checkpoint/manager.js +492 -0
- package/dist/core/strata/checkpoint/snapshot-audit.js +88 -0
- package/dist/core/strata/checkpoint/snapshot-create.js +79 -0
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +72 -0
- package/dist/core/strata/engine/shadow-merge-engine.js +394 -0
- package/dist/core/strata/index.js +15 -0
- package/dist/core/strata/interaction/content-guardian.js +59 -0
- package/dist/core/strata/interaction/file-system-provider.js +89 -0
- package/dist/core/strata/layers/file-state-resolver.js +157 -0
- package/dist/core/strata/layers/immutable-git-layer.js +42 -0
- package/dist/core/strata/layers/shadow-driver/copy-backend.js +114 -0
- package/dist/core/strata/layers/shadow-driver/env.js +29 -0
- package/dist/core/strata/layers/shadow-driver/error-classifier.js +41 -0
- package/dist/core/strata/layers/shadow-driver/index.js +17 -0
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +221 -0
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +234 -0
- package/dist/core/strata/layers/shadow-driver/strategy.js +86 -0
- package/dist/core/strata/layers/sidecar-layer.js +96 -0
- package/dist/core/strata/layers/worktree.js +240 -0
- package/dist/core/strata/runtime/environment.js +377 -0
- package/dist/core/strata/runtime/synchronizer.js +819 -0
- package/dist/core/strata/types.js +46 -0
- package/dist/core/streaming/canonical/canonical-responses-event-emitter.js +326 -0
- package/dist/core/streaming/canonical/function-call-item-id.js +13 -0
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +54 -0
- package/dist/core/streaming/canonical/responses-event-emitter.js +127 -0
- package/dist/core/streaming/canonical/responses-events.js +2 -0
- package/dist/core/streaming/normalized-events.js +9 -0
- package/dist/core/streaming/normalized-from-text.js +47 -0
- package/dist/core/streaming/stream-assembler.js +347 -0
- package/dist/core/structured-output/index.js +3 -0
- package/dist/core/structured-output/json-extract.js +70 -0
- package/dist/core/structured-output/json-schema-validator.js +90 -0
- package/dist/core/structured-output/types.js +2 -0
- package/dist/core/sub-agent/artifacts/store.js +141 -0
- package/dist/core/sub-agent/artifacts/types.js +2 -0
- package/dist/core/sub-agent/controller.js +69 -0
- package/dist/core/sub-agent/core/loop.js +79 -0
- package/dist/core/sub-agent/core/manager.js +246 -0
- package/dist/core/sub-agent/registry-defaults.js +52 -0
- package/dist/core/sub-agent/registry.js +35 -0
- package/dist/core/sub-agent/tools/task-spawn.js +29 -0
- package/dist/core/sub-agent/types.js +23 -0
- package/dist/core/target-runtime/command-resolver.js +42 -0
- package/dist/core/target-runtime/index.js +3 -0
- package/dist/core/target-runtime/profile.js +73 -0
- package/dist/core/testgen/detector.js +17 -0
- package/dist/core/testgen/index.js +38 -0
- package/dist/core/testgen/templates.js +46 -0
- package/dist/core/tools/audit.js +140 -0
- package/dist/core/tools/authorization/types.js +2 -0
- package/dist/core/tools/budget.js +118 -0
- package/dist/core/tools/builtin/artifact.js +29 -0
- package/dist/core/tools/builtin/ast-grep.js +107 -0
- package/dist/core/tools/builtin/ast.js +62 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +84 -0
- package/dist/core/tools/builtin/code-search/backends/rg.js +85 -0
- package/dist/core/tools/builtin/code-search/executor.js +87 -0
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +59 -0
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +31 -0
- package/dist/core/tools/builtin/code-search/spec.js +82 -0
- package/dist/core/tools/builtin/fs.js +243 -0
- package/dist/core/tools/builtin/git.js +118 -0
- package/dist/core/tools/builtin/index.js +80 -0
- package/dist/core/tools/builtin/interaction.js +120 -0
- package/dist/core/tools/builtin/knowledge.js +98 -0
- package/dist/core/tools/builtin/plan.js +148 -0
- package/dist/core/tools/builtin/proposal.js +207 -0
- package/dist/core/tools/builtin/shell.js +71 -0
- package/dist/core/tools/builtin/verify.js +41 -0
- package/dist/core/tools/capability/executor.js +84 -0
- package/dist/core/tools/capability/runner.js +50 -0
- package/dist/core/tools/capability/types.js +2 -0
- package/dist/core/tools/dispatcher.js +80 -0
- package/dist/core/tools/headless-payload.js +37 -0
- package/dist/core/tools/loader.js +100 -0
- package/dist/core/tools/mapper.js +142 -0
- package/dist/core/tools/mcp/client.js +308 -0
- package/dist/core/tools/mcp/loader.js +110 -0
- package/dist/core/tools/mcp/schema.js +54 -0
- package/dist/core/tools/mcp/streamable-http.js +101 -0
- package/dist/core/tools/mcp/types.js +26 -0
- package/dist/core/tools/parallel/isolation.js +25 -0
- package/dist/core/tools/parallel/lock-manager.js +124 -0
- package/dist/core/tools/parallel/persistence.js +126 -0
- package/dist/core/tools/parallel/plan-builder.js +66 -0
- package/dist/core/tools/parallel/plan.js +2 -0
- package/dist/core/tools/parallel/refs.js +7 -0
- package/dist/core/tools/parallel/resolve-args.js +50 -0
- package/dist/core/tools/parallel/resource-helpers.js +35 -0
- package/dist/core/tools/parallel/resources.js +2 -0
- package/dist/core/tools/parallel/scheduler.js +372 -0
- package/dist/core/tools/parser.js +89 -0
- package/dist/core/tools/permissions/permission-rules.js +503 -0
- package/dist/core/tools/plugins/loader.js +102 -0
- package/dist/core/tools/policy.js +87 -0
- package/dist/core/tools/registry.js +29 -0
- package/dist/core/tools/router.js +514 -0
- package/dist/core/tools/sanitize.js +78 -0
- package/dist/core/tools/schema-utils.js +71 -0
- package/dist/core/tools/session.js +1105 -0
- package/dist/core/tools/streaming/ToolCallAccumulator.js +64 -0
- package/dist/core/tools/types.js +2 -0
- package/dist/core/types/authorization.js +2 -0
- package/dist/core/types/context.js +2 -0
- package/dist/core/types/errors.js +29 -0
- package/dist/core/types/execution.js +65 -0
- package/dist/core/types/index.js +9 -0
- package/dist/core/types/llm.js +9 -0
- package/dist/core/types/loop.js +2 -0
- package/dist/core/types/planning.js +2 -0
- package/dist/core/types/runtime.js +2 -0
- package/dist/core/types/usage.js +2 -0
- package/dist/core/ui/kaomoji.js +5 -0
- package/dist/core/utils/path.js +116 -0
- package/dist/core/utils/platform-shell.js +10 -0
- package/dist/core/utils/sanitizer.js +107 -0
- package/dist/core/verification/runner.js +265 -0
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +272 -0
- package/dist/integrations/langfuse/outcome-proxy.js +68 -0
- package/dist/interfaces/cli/task-runner.js +11 -0
- package/dist/languages/typescript/index.js +178 -0
- package/dist/locales/en.js +679 -0
- package/dist/locales/index.js +11 -0
- package/dist/utils/eol.js +35 -0
- package/package.json +153 -0
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { PROTOCOL_VERSION, RequestError, } from '@agentclientprotocol/sdk';
|
|
3
|
+
import { text } from '../../../locales/index.js';
|
|
4
|
+
import { mkdir, open, readFile, rename, stat, unlink, writeFile, } from '../../adapters/fs/node-fs.js';
|
|
5
|
+
import { defaultPathAdapter } from '../../adapters/path/path-adapter.js';
|
|
6
|
+
import { inferTurnStopReasonFromFailure } from '../../interaction/turn-stop-reason.js';
|
|
7
|
+
import { recordAuditEvent } from '../../observability/audit-trail.js';
|
|
8
|
+
import { readPlan } from '../../plan/index.js';
|
|
9
|
+
import { parseSlashInput } from '../../slash/parser.js';
|
|
10
|
+
import { createAcpCommandRunner } from './acp-command-runner.js';
|
|
11
|
+
import { createAcpFileSystem } from './acp-filesystem.js';
|
|
12
|
+
import { createAcpSessionStore, isTerminalTaskEvent } from './handlers.js';
|
|
13
|
+
import { createAcpToolAuthorizationProvider } from './permission-provider.js';
|
|
14
|
+
function formatInputRequiredMessage(inputRequired) {
|
|
15
|
+
if (!inputRequired || !Array.isArray(inputRequired.questions))
|
|
16
|
+
return null;
|
|
17
|
+
const questions = inputRequired.questions;
|
|
18
|
+
if (questions.length === 0)
|
|
19
|
+
return null;
|
|
20
|
+
const lines = [text.acp.askUserHeader];
|
|
21
|
+
for (const q of questions) {
|
|
22
|
+
lines.push(text.acp.askUserQuestion(q.question));
|
|
23
|
+
lines.push(text.acp.askUserOptionsHeader);
|
|
24
|
+
for (const option of q.options) {
|
|
25
|
+
lines.push(text.acp.askUserOption(option.label, option.description));
|
|
26
|
+
}
|
|
27
|
+
if (q.multiSelect) {
|
|
28
|
+
lines.push(text.acp.askUserMultiSelectHint);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return lines.join('\n');
|
|
32
|
+
}
|
|
33
|
+
const ACP_PERMISSION_POLICY_CONFIG_ID = '_salmonloop_permission_policy';
|
|
34
|
+
const ACP_MODE_CONFIG_ID = '_salmonloop_mode';
|
|
35
|
+
const ACP_PERMISSION_POLICY_ASK = 'ask';
|
|
36
|
+
const ACP_PERMISSION_POLICY_DENY_ALL = 'deny_all';
|
|
37
|
+
const ACP_DEFAULT_MODE_ID = 'interactive';
|
|
38
|
+
const ACP_SESSION_STORE_MAX_ENTRIES = 200;
|
|
39
|
+
const ACP_SESSION_STORE_MAX_AGE_MS = 1000 * 60 * 60 * 24 * 30;
|
|
40
|
+
const ACP_SESSION_STORE_LOCK_STALE_MS = 1000 * 30;
|
|
41
|
+
const ACP_SESSION_STORE_LOCK_HEARTBEAT_MS = 1000 * 5;
|
|
42
|
+
const ACP_SESSION_STORE_LOCK_ACQUIRE_TIMEOUT_MS = 1000 * 5;
|
|
43
|
+
const ACP_SESSION_HISTORY_MAX_ENTRIES = 40;
|
|
44
|
+
function isAbsolutePath(filePath) {
|
|
45
|
+
if (defaultPathAdapter.isAbsolute(filePath))
|
|
46
|
+
return true;
|
|
47
|
+
// Cross-platform absolute check for Windows paths on non-Windows runtimes.
|
|
48
|
+
// ACP requires absolute paths, but the runtime OS may not match the client OS.
|
|
49
|
+
if (/^[a-zA-Z]:[\\/]/.test(filePath))
|
|
50
|
+
return true; // drive letter
|
|
51
|
+
if (filePath.startsWith('\\\\'))
|
|
52
|
+
return true; // UNC path
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
function ensureMarkdownParagraphBreak(text) {
|
|
56
|
+
if (!text)
|
|
57
|
+
return text;
|
|
58
|
+
const trimmed = text.replace(/\r?\n$/, '');
|
|
59
|
+
return `${trimmed}\n\n`;
|
|
60
|
+
}
|
|
61
|
+
function buildTextContentBlock(text) {
|
|
62
|
+
return { type: 'text', text };
|
|
63
|
+
}
|
|
64
|
+
function buildJsonResourceContentBlock(data) {
|
|
65
|
+
return {
|
|
66
|
+
type: 'resource',
|
|
67
|
+
resource: {
|
|
68
|
+
mimeType: 'application/json',
|
|
69
|
+
uri: 's8p://input-required',
|
|
70
|
+
text: JSON.stringify(data),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const defaultPromptCapabilities = {
|
|
75
|
+
image: false,
|
|
76
|
+
audio: false,
|
|
77
|
+
embeddedContext: false,
|
|
78
|
+
};
|
|
79
|
+
const ACP_AVAILABLE_COMMANDS = [
|
|
80
|
+
{ name: 'help', description: text.acp.slashHelpDescription },
|
|
81
|
+
];
|
|
82
|
+
function formatResourceLink(block) {
|
|
83
|
+
const title = block.title ?? block.name ?? block.uri;
|
|
84
|
+
const description = block.description ? ` - ${block.description}` : '';
|
|
85
|
+
return `Resource: ${title} (${block.uri})${description}`;
|
|
86
|
+
}
|
|
87
|
+
function extractTextFromPrompt(prompt, capabilities) {
|
|
88
|
+
const parts = [];
|
|
89
|
+
for (const block of prompt) {
|
|
90
|
+
switch (block.type) {
|
|
91
|
+
case 'text':
|
|
92
|
+
parts.push(block.text);
|
|
93
|
+
break;
|
|
94
|
+
case 'resource_link':
|
|
95
|
+
parts.push(formatResourceLink(block));
|
|
96
|
+
break;
|
|
97
|
+
case 'image':
|
|
98
|
+
if (!capabilities.image) {
|
|
99
|
+
throw new RequestError(-32000, 'Prompt content type image is not supported');
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
case 'audio':
|
|
103
|
+
if (!capabilities.audio) {
|
|
104
|
+
throw new RequestError(-32000, 'Prompt content type audio is not supported');
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case 'resource':
|
|
108
|
+
if (!capabilities.embeddedContext) {
|
|
109
|
+
throw new RequestError(-32000, 'Prompt content type resource is not supported');
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
throw new RequestError(-32602, 'Invalid params: unsupported content block type');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return parts.join('\n');
|
|
117
|
+
}
|
|
118
|
+
function mapToolKind(toolName, intent) {
|
|
119
|
+
if (intent) {
|
|
120
|
+
switch (intent.toUpperCase()) {
|
|
121
|
+
case 'READ':
|
|
122
|
+
return 'read';
|
|
123
|
+
case 'LIST':
|
|
124
|
+
return 'read';
|
|
125
|
+
case 'SEARCH':
|
|
126
|
+
return 'search';
|
|
127
|
+
case 'WRITE':
|
|
128
|
+
return 'edit';
|
|
129
|
+
case 'INFRA':
|
|
130
|
+
return 'execute';
|
|
131
|
+
case 'AGENT':
|
|
132
|
+
return 'think';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const name = toolName.toLowerCase();
|
|
136
|
+
if (name.includes('read') ||
|
|
137
|
+
name.includes('get') ||
|
|
138
|
+
name.includes('view') ||
|
|
139
|
+
name.includes('ls') ||
|
|
140
|
+
name.includes('list'))
|
|
141
|
+
return 'read';
|
|
142
|
+
if (name.includes('write') || name.includes('edit') || name.includes('patch'))
|
|
143
|
+
return 'edit';
|
|
144
|
+
if (name.includes('delete') || name.includes('remove') || name.includes('rm'))
|
|
145
|
+
return 'delete';
|
|
146
|
+
if (name.includes('move') || name.includes('rename') || name.includes('mv'))
|
|
147
|
+
return 'move';
|
|
148
|
+
if (name.includes('grep') || name.includes('search') || name.includes('find'))
|
|
149
|
+
return 'search';
|
|
150
|
+
if (name.includes('run') || name.includes('exec') || name.includes('spawn'))
|
|
151
|
+
return 'execute';
|
|
152
|
+
if (name.includes('plan') || name.includes('think') || name.includes('reason'))
|
|
153
|
+
return 'think';
|
|
154
|
+
if (name.includes('fetch') || name.includes('curl') || name.includes('http'))
|
|
155
|
+
return 'fetch';
|
|
156
|
+
return 'other';
|
|
157
|
+
}
|
|
158
|
+
function buildToolCallContent(textValue) {
|
|
159
|
+
return [{ type: 'content', content: buildTextContentBlock(textValue) }];
|
|
160
|
+
}
|
|
161
|
+
function extractLocationFromInput(input) {
|
|
162
|
+
if (!input || typeof input !== 'object')
|
|
163
|
+
return undefined;
|
|
164
|
+
const typedInput = input;
|
|
165
|
+
const pathCandidate = typedInput.path ?? typedInput.file ?? typedInput.uri;
|
|
166
|
+
if (typeof pathCandidate === 'string' && pathCandidate.trim()) {
|
|
167
|
+
let path = pathCandidate.replace(/^file:\/\/\//, '/').replace(/^file:\/\//, '');
|
|
168
|
+
if (/^\/[a-zA-Z]:/.test(path)) {
|
|
169
|
+
path = path.slice(1);
|
|
170
|
+
}
|
|
171
|
+
return [
|
|
172
|
+
{
|
|
173
|
+
path,
|
|
174
|
+
line: typeof typedInput.line === 'number' ? typedInput.line : undefined,
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
function formatToolCallEnd(event) {
|
|
181
|
+
if (event.outputSummary)
|
|
182
|
+
return event.outputSummary;
|
|
183
|
+
const status = event.status === 'ok' ? 'completed' : 'failed';
|
|
184
|
+
return `Tool call ${status}: ${event.toolName}`;
|
|
185
|
+
}
|
|
186
|
+
function loopEventToSessionUpdate(event) {
|
|
187
|
+
switch (event.type) {
|
|
188
|
+
case 'llm.stream.delta':
|
|
189
|
+
return {
|
|
190
|
+
sessionUpdate: event.kind === 'assistant_message' ? 'agent_message_chunk' : 'agent_thought_chunk',
|
|
191
|
+
content: buildTextContentBlock(event.content || ''),
|
|
192
|
+
};
|
|
193
|
+
case 'llm.output':
|
|
194
|
+
return {
|
|
195
|
+
sessionUpdate: event.kind === 'assistant_message' ? 'agent_message_chunk' : 'agent_thought_chunk',
|
|
196
|
+
content: buildTextContentBlock(event.content || ''),
|
|
197
|
+
};
|
|
198
|
+
case 'tool.call.start':
|
|
199
|
+
return {
|
|
200
|
+
sessionUpdate: 'tool_call',
|
|
201
|
+
toolCallId: event.callId,
|
|
202
|
+
status: 'pending',
|
|
203
|
+
title: event.toolName,
|
|
204
|
+
kind: mapToolKind(event.toolName, event.toolIntent),
|
|
205
|
+
content: [],
|
|
206
|
+
rawInput: event.input,
|
|
207
|
+
locations: extractLocationFromInput(event.input),
|
|
208
|
+
};
|
|
209
|
+
case 'tool.call.end':
|
|
210
|
+
return {
|
|
211
|
+
sessionUpdate: 'tool_call_update',
|
|
212
|
+
toolCallId: event.callId,
|
|
213
|
+
status: event.status === 'ok' ? 'completed' : 'failed',
|
|
214
|
+
content: event.status !== 'ok' ? buildToolCallContent(formatToolCallEnd(event)) : [],
|
|
215
|
+
rawOutput: event.outputSummary,
|
|
216
|
+
};
|
|
217
|
+
case 'phase.start':
|
|
218
|
+
return null;
|
|
219
|
+
case 'phase.end':
|
|
220
|
+
return null;
|
|
221
|
+
case 'log':
|
|
222
|
+
return null;
|
|
223
|
+
default:
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function createSessionRuntimeState() {
|
|
228
|
+
return createSessionRuntimeStateFromPersisted();
|
|
229
|
+
}
|
|
230
|
+
function isPermissionPolicyValue(value) {
|
|
231
|
+
return value === ACP_PERMISSION_POLICY_ASK || value === ACP_PERMISSION_POLICY_DENY_ALL;
|
|
232
|
+
}
|
|
233
|
+
function buildConfigOptions(state) {
|
|
234
|
+
return [
|
|
235
|
+
{
|
|
236
|
+
type: 'select',
|
|
237
|
+
id: ACP_PERMISSION_POLICY_CONFIG_ID,
|
|
238
|
+
name: text.acp.permissionPolicyName,
|
|
239
|
+
description: text.acp.permissionPolicyDescription,
|
|
240
|
+
currentValue: state.permissionPolicy,
|
|
241
|
+
options: [
|
|
242
|
+
{
|
|
243
|
+
value: ACP_PERMISSION_POLICY_ASK,
|
|
244
|
+
name: text.acp.permissionPolicyAskName,
|
|
245
|
+
description: text.acp.permissionPolicyAskDescription,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
value: ACP_PERMISSION_POLICY_DENY_ALL,
|
|
249
|
+
name: text.acp.permissionPolicyDenyAllName,
|
|
250
|
+
description: text.acp.permissionPolicyDenyAllDescription,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
type: 'select',
|
|
256
|
+
id: ACP_MODE_CONFIG_ID,
|
|
257
|
+
name: 'Session Mode',
|
|
258
|
+
description: text.acp.modeInteractiveDescription,
|
|
259
|
+
currentValue: state.modeId,
|
|
260
|
+
options: [
|
|
261
|
+
{
|
|
262
|
+
value: 'interactive',
|
|
263
|
+
name: 'Interactive',
|
|
264
|
+
description: text.acp.modeInteractiveDescription,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
value: 'yolo',
|
|
268
|
+
name: 'YOLO',
|
|
269
|
+
description: text.acp.modeYoloDescription,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
function buildConfigOptionUpdateIfChanged(state) {
|
|
276
|
+
const configOptions = buildConfigOptions(state);
|
|
277
|
+
const digest = JSON.stringify(configOptions);
|
|
278
|
+
if (digest === state.lastConfigDigest)
|
|
279
|
+
return null;
|
|
280
|
+
state.lastConfigDigest = digest;
|
|
281
|
+
return {
|
|
282
|
+
sessionUpdate: 'config_option_update',
|
|
283
|
+
configOptions,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function buildAvailableCommandsUpdateIfChanged(state) {
|
|
287
|
+
const availableCommands = ACP_AVAILABLE_COMMANDS;
|
|
288
|
+
const digest = JSON.stringify(availableCommands);
|
|
289
|
+
if (digest === state.lastCommandsDigest)
|
|
290
|
+
return null;
|
|
291
|
+
state.lastCommandsDigest = digest;
|
|
292
|
+
return {
|
|
293
|
+
sessionUpdate: 'available_commands_update',
|
|
294
|
+
availableCommands,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function isSessionModeId(value) {
|
|
298
|
+
return value === 'interactive' || value === 'yolo';
|
|
299
|
+
}
|
|
300
|
+
function buildCurrentModeUpdate(modeId) {
|
|
301
|
+
return { sessionUpdate: 'current_mode_update', currentModeId: modeId };
|
|
302
|
+
}
|
|
303
|
+
function buildCurrentModeUpdateIfChanged(state) {
|
|
304
|
+
const digest = state.modeId;
|
|
305
|
+
if (digest === state.lastModeDigest)
|
|
306
|
+
return null;
|
|
307
|
+
state.lastModeDigest = digest;
|
|
308
|
+
return buildCurrentModeUpdate(state.modeId);
|
|
309
|
+
}
|
|
310
|
+
function getPermissionPolicyForAuthorization(state) {
|
|
311
|
+
if (state.modeId === 'yolo')
|
|
312
|
+
return 'allow_all';
|
|
313
|
+
return state.permissionPolicy;
|
|
314
|
+
}
|
|
315
|
+
function createSessionRuntimeStateFromPersisted(input) {
|
|
316
|
+
const permissionPolicy = isPermissionPolicyValue(String(input?.permissionPolicy))
|
|
317
|
+
? input?.permissionPolicy
|
|
318
|
+
: ACP_PERMISSION_POLICY_ASK;
|
|
319
|
+
const modeId = isSessionModeId(String(input?.modeId))
|
|
320
|
+
? input?.modeId
|
|
321
|
+
: (input?.defaultModeId ?? ACP_DEFAULT_MODE_ID);
|
|
322
|
+
const state = {
|
|
323
|
+
runtimePlanSessionId: null,
|
|
324
|
+
runtimePlanPathHint: null,
|
|
325
|
+
lastPlanDigest: null,
|
|
326
|
+
lastCommandsDigest: null,
|
|
327
|
+
lastConfigDigest: null,
|
|
328
|
+
lastModeDigest: null,
|
|
329
|
+
permissionPolicy,
|
|
330
|
+
modeId,
|
|
331
|
+
};
|
|
332
|
+
state.lastConfigDigest = JSON.stringify(buildConfigOptions(state));
|
|
333
|
+
return state;
|
|
334
|
+
}
|
|
335
|
+
function loopEventToSessionUpdates(event, _state) {
|
|
336
|
+
const updates = [];
|
|
337
|
+
const mapped = loopEventToSessionUpdate(event);
|
|
338
|
+
if (mapped)
|
|
339
|
+
updates.push(mapped);
|
|
340
|
+
return updates;
|
|
341
|
+
}
|
|
342
|
+
function shouldRefreshPlanForEvent(event) {
|
|
343
|
+
if (!event)
|
|
344
|
+
return true;
|
|
345
|
+
if (event.type === 'plan.runtime.ready')
|
|
346
|
+
return true;
|
|
347
|
+
if (event.type === 'tool.call.end' &&
|
|
348
|
+
(event.toolName === 'plan.init' || event.toolName === 'plan.update') &&
|
|
349
|
+
event.status === 'ok') {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
if (event.type === 'plan.runtime.journal' &&
|
|
353
|
+
event.phase === 'PLAN' &&
|
|
354
|
+
event.kind === 'end' &&
|
|
355
|
+
event.ok) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
function mapCorePlanToAcpEntries(read) {
|
|
361
|
+
const entries = [];
|
|
362
|
+
const seen = new Set();
|
|
363
|
+
const parsePriority = (text) => {
|
|
364
|
+
const trimmed = text.trim();
|
|
365
|
+
if (trimmed.startsWith('!')) {
|
|
366
|
+
return { priority: 'high', content: trimmed.slice(1).trim() };
|
|
367
|
+
}
|
|
368
|
+
if (trimmed.startsWith('·')) {
|
|
369
|
+
return { priority: 'medium', content: trimmed.slice(1).trim() };
|
|
370
|
+
}
|
|
371
|
+
if (trimmed.startsWith('‐')) {
|
|
372
|
+
return { priority: 'low', content: trimmed.slice(1).trim() };
|
|
373
|
+
}
|
|
374
|
+
return { priority: 'medium', content: trimmed };
|
|
375
|
+
};
|
|
376
|
+
const push = (step, status) => {
|
|
377
|
+
if (!step?.stepId || seen.has(step.stepId))
|
|
378
|
+
return;
|
|
379
|
+
seen.add(step.stepId);
|
|
380
|
+
const { priority, content } = parsePriority(step.text || step.stepId);
|
|
381
|
+
entries.push({
|
|
382
|
+
content,
|
|
383
|
+
status,
|
|
384
|
+
priority,
|
|
385
|
+
});
|
|
386
|
+
};
|
|
387
|
+
for (const step of read.active)
|
|
388
|
+
push(step, 'in_progress');
|
|
389
|
+
for (const step of read.pending)
|
|
390
|
+
push(step, 'pending');
|
|
391
|
+
for (const step of read.recentDone)
|
|
392
|
+
push(step, 'completed');
|
|
393
|
+
return entries;
|
|
394
|
+
}
|
|
395
|
+
function buildPlanUpdateFromCoreIfChanged(read, state) {
|
|
396
|
+
const entries = mapCorePlanToAcpEntries(read);
|
|
397
|
+
const digest = JSON.stringify({
|
|
398
|
+
sessionId: read.sessionId,
|
|
399
|
+
baseHash: read.baseHash,
|
|
400
|
+
entries,
|
|
401
|
+
});
|
|
402
|
+
if (digest === state.lastPlanDigest)
|
|
403
|
+
return null;
|
|
404
|
+
state.lastPlanDigest = digest;
|
|
405
|
+
return {
|
|
406
|
+
sessionUpdate: 'plan',
|
|
407
|
+
entries,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function extractSlashInput(prompt) {
|
|
411
|
+
if (prompt.length !== 1)
|
|
412
|
+
return null;
|
|
413
|
+
const block = prompt[0];
|
|
414
|
+
if (!block || block.type !== 'text')
|
|
415
|
+
return null;
|
|
416
|
+
const raw = block.text ?? '';
|
|
417
|
+
if (!raw.trimStart().startsWith('/'))
|
|
418
|
+
return null;
|
|
419
|
+
return raw;
|
|
420
|
+
}
|
|
421
|
+
function buildSlashHelpMessage() {
|
|
422
|
+
const names = ACP_AVAILABLE_COMMANDS.map((cmd) => `/${cmd.name}`).join(', ');
|
|
423
|
+
return text.acp.slashHelpResponse(names);
|
|
424
|
+
}
|
|
425
|
+
function normalizeSlashName(commandName) {
|
|
426
|
+
return commandName.replace(/^\/+/, '').toLowerCase();
|
|
427
|
+
}
|
|
428
|
+
function isKnownSlashCommand(commandName) {
|
|
429
|
+
const normalized = normalizeSlashName(commandName);
|
|
430
|
+
return ACP_AVAILABLE_COMMANDS.some((cmd) => cmd.name.toLowerCase() === normalized);
|
|
431
|
+
}
|
|
432
|
+
async function awaitTerminalEvent(params) {
|
|
433
|
+
if (!params.eventBus)
|
|
434
|
+
return null;
|
|
435
|
+
const history = params.eventBus.list(params.taskId);
|
|
436
|
+
const terminal = history.find(isTerminalTaskEvent);
|
|
437
|
+
if (terminal)
|
|
438
|
+
return terminal;
|
|
439
|
+
return await new Promise((resolve) => {
|
|
440
|
+
const unsubscribe = params.eventBus.subscribe((event) => {
|
|
441
|
+
if (event.taskId !== params.taskId)
|
|
442
|
+
return;
|
|
443
|
+
if (!isTerminalTaskEvent(event))
|
|
444
|
+
return;
|
|
445
|
+
unsubscribe();
|
|
446
|
+
resolve(event);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
export function createAcpFormalAgent(deps) {
|
|
451
|
+
const sessions = createAcpSessionStore();
|
|
452
|
+
const sessionRuntime = new Map();
|
|
453
|
+
let clientCapabilities;
|
|
454
|
+
const defaultClientCapabilities = {
|
|
455
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
456
|
+
terminal: false,
|
|
457
|
+
};
|
|
458
|
+
const loadSessionCapability = deps.capabilityPolicy?.loadSession ?? true;
|
|
459
|
+
const sessionPersistencePath = deps.sessionPersistencePath;
|
|
460
|
+
const sessionStorePolicy = {
|
|
461
|
+
maxEntries: deps.sessionStorePolicy?.maxEntries ?? ACP_SESSION_STORE_MAX_ENTRIES,
|
|
462
|
+
maxAgeMs: deps.sessionStorePolicy?.maxAgeMs ?? ACP_SESSION_STORE_MAX_AGE_MS,
|
|
463
|
+
historyMaxEntries: deps.sessionStorePolicy?.historyMaxEntries ?? ACP_SESSION_HISTORY_MAX_ENTRIES,
|
|
464
|
+
lockStaleMs: deps.sessionStorePolicy?.lockStaleMs ?? ACP_SESSION_STORE_LOCK_STALE_MS,
|
|
465
|
+
lockHeartbeatMs: deps.sessionStorePolicy?.lockHeartbeatMs ?? ACP_SESSION_STORE_LOCK_HEARTBEAT_MS,
|
|
466
|
+
lockAcquireTimeoutMs: deps.sessionStorePolicy?.lockAcquireTimeoutMs ?? ACP_SESSION_STORE_LOCK_ACQUIRE_TIMEOUT_MS,
|
|
467
|
+
};
|
|
468
|
+
const executionBinding = deps.executionBinding ?? 'local';
|
|
469
|
+
let sessionsHydrated = false;
|
|
470
|
+
let hydratePromise = null;
|
|
471
|
+
function parseTimestamp(value) {
|
|
472
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
473
|
+
return 0;
|
|
474
|
+
const parsed = Date.parse(value);
|
|
475
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
476
|
+
}
|
|
477
|
+
function pruneSessionRecords(records) {
|
|
478
|
+
const cutoff = Date.now() - sessionStorePolicy.maxAgeMs;
|
|
479
|
+
return [...records]
|
|
480
|
+
.filter((record) => parseTimestamp(record.updatedAt) >= cutoff)
|
|
481
|
+
.sort((a, b) => parseTimestamp(b.updatedAt) - parseTimestamp(a.updatedAt))
|
|
482
|
+
.slice(0, sessionStorePolicy.maxEntries);
|
|
483
|
+
}
|
|
484
|
+
function normalizePersistedSessionStore(input) {
|
|
485
|
+
if (!input || typeof input !== 'object') {
|
|
486
|
+
return { schemaVersion: 2, sessions: [] };
|
|
487
|
+
}
|
|
488
|
+
const raw = input;
|
|
489
|
+
if (!Array.isArray(raw.sessions))
|
|
490
|
+
return { schemaVersion: 2, sessions: [] };
|
|
491
|
+
if (raw.schemaVersion === 1) {
|
|
492
|
+
return {
|
|
493
|
+
schemaVersion: 2,
|
|
494
|
+
sessions: raw.sessions.map((entry) => ({
|
|
495
|
+
id: entry.id,
|
|
496
|
+
cwd: entry.cwd,
|
|
497
|
+
mcpServers: entry.mcpServers,
|
|
498
|
+
createdAt: entry.createdAt,
|
|
499
|
+
updatedAt: entry.updatedAt,
|
|
500
|
+
title: entry.title,
|
|
501
|
+
taskId: undefined,
|
|
502
|
+
history: [],
|
|
503
|
+
permissionPolicy: ACP_PERMISSION_POLICY_ASK,
|
|
504
|
+
modeId: deps.defaultModeId ?? ACP_DEFAULT_MODE_ID,
|
|
505
|
+
})),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
if (raw.schemaVersion === 2) {
|
|
509
|
+
return { schemaVersion: 2, sessions: raw.sessions };
|
|
510
|
+
}
|
|
511
|
+
return { schemaVersion: 2, sessions: [] };
|
|
512
|
+
}
|
|
513
|
+
function isPidAlive(pid) {
|
|
514
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
515
|
+
return false;
|
|
516
|
+
try {
|
|
517
|
+
process.kill(pid, 0);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
if (error &&
|
|
522
|
+
typeof error === 'object' &&
|
|
523
|
+
'code' in error &&
|
|
524
|
+
error.code === 'EPERM') {
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function isFileMissing(error) {
|
|
531
|
+
return Boolean(error &&
|
|
532
|
+
typeof error === 'object' &&
|
|
533
|
+
'code' in error &&
|
|
534
|
+
(error.code === 'ENOENT' ||
|
|
535
|
+
error.code === 'ENOTDIR'));
|
|
536
|
+
}
|
|
537
|
+
async function persistSessionsBestEffort() {
|
|
538
|
+
if (!sessionPersistencePath)
|
|
539
|
+
return;
|
|
540
|
+
const dir = defaultPathAdapter.dirname(sessionPersistencePath);
|
|
541
|
+
const lockPath = `${sessionPersistencePath}.lock`;
|
|
542
|
+
const baseRecords = sessions.list().map((session) => {
|
|
543
|
+
const runtimeState = ensureSessionRuntimeState(session.id);
|
|
544
|
+
return {
|
|
545
|
+
id: session.id,
|
|
546
|
+
cwd: session.cwd,
|
|
547
|
+
mcpServers: session.mcpServers,
|
|
548
|
+
createdAt: session.createdAt,
|
|
549
|
+
updatedAt: session.updatedAt,
|
|
550
|
+
title: session.title,
|
|
551
|
+
taskId: session.taskId,
|
|
552
|
+
history: session.history.slice(-sessionStorePolicy.historyMaxEntries),
|
|
553
|
+
permissionPolicy: runtimeState.permissionPolicy,
|
|
554
|
+
modeId: runtimeState.modeId,
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
const prunedRecords = pruneSessionRecords(baseRecords);
|
|
558
|
+
const keepIds = new Set(prunedRecords.map((record) => record.id));
|
|
559
|
+
for (const record of sessions.list()) {
|
|
560
|
+
if (!keepIds.has(record.id)) {
|
|
561
|
+
sessions.delete(record.id);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const payload = { schemaVersion: 2, sessions: prunedRecords };
|
|
565
|
+
const primaryRepoPath = prunedRecords[0]?.cwd;
|
|
566
|
+
const lockAuditDetails = {
|
|
567
|
+
lockPath,
|
|
568
|
+
lockPathHash: createHash('sha256').update(lockPath).digest('hex').slice(0, 16),
|
|
569
|
+
repoPathHash: primaryRepoPath ? hashRepoPath(primaryRepoPath) : undefined,
|
|
570
|
+
};
|
|
571
|
+
const tryClearStaleLock = async () => {
|
|
572
|
+
try {
|
|
573
|
+
const raw = await readFile(lockPath, 'utf8');
|
|
574
|
+
const parsed = JSON.parse(raw);
|
|
575
|
+
const createdAtMs = typeof parsed.createdAtMs === 'number' && Number.isFinite(parsed.createdAtMs)
|
|
576
|
+
? parsed.createdAtMs
|
|
577
|
+
: null;
|
|
578
|
+
if (createdAtMs === null)
|
|
579
|
+
return;
|
|
580
|
+
if (Date.now() - createdAtMs <= sessionStorePolicy.lockStaleMs)
|
|
581
|
+
return;
|
|
582
|
+
if (typeof parsed.pid === 'number' && isPidAlive(parsed.pid))
|
|
583
|
+
return;
|
|
584
|
+
await unlink(lockPath);
|
|
585
|
+
recordAuditEvent('acp.session.lock.stale_reclaimed', lockAuditDetails, {
|
|
586
|
+
source: 'acp',
|
|
587
|
+
severity: 'low',
|
|
588
|
+
scope: 'session',
|
|
589
|
+
phase: 'PREFLIGHT',
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
catch {
|
|
593
|
+
try {
|
|
594
|
+
const lockStat = await stat(lockPath);
|
|
595
|
+
const ageMs = Date.now() - lockStat.mtimeMs;
|
|
596
|
+
if (Number.isFinite(ageMs) && ageMs > sessionStorePolicy.lockStaleMs * 2) {
|
|
597
|
+
await unlink(lockPath);
|
|
598
|
+
recordAuditEvent('acp.session.lock.corrupted_reclaimed', {
|
|
599
|
+
...lockAuditDetails,
|
|
600
|
+
ageMs: Math.max(0, Math.floor(ageMs)),
|
|
601
|
+
}, { source: 'acp', severity: 'medium', scope: 'session', phase: 'PREFLIGHT' });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
// ignore
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
let lockHandle;
|
|
610
|
+
try {
|
|
611
|
+
await mkdir(dir, { recursive: true });
|
|
612
|
+
const acquireDeadlineMs = Date.now() + Math.max(250, sessionStorePolicy.lockAcquireTimeoutMs);
|
|
613
|
+
for (let attempt = 0; Date.now() < acquireDeadlineMs; attempt += 1) {
|
|
614
|
+
try {
|
|
615
|
+
lockHandle = await open(lockPath, 'wx');
|
|
616
|
+
await lockHandle.writeFile(JSON.stringify({ pid: process.pid, createdAtMs: Date.now() }), 'utf8');
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
await tryClearStaleLock();
|
|
621
|
+
const delayMs = Math.min(250, 20 * (attempt + 1));
|
|
622
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (!lockHandle) {
|
|
626
|
+
recordAuditEvent('acp.session.lock.acquire_timeout', lockAuditDetails, {
|
|
627
|
+
source: 'acp',
|
|
628
|
+
severity: 'medium',
|
|
629
|
+
scope: 'session',
|
|
630
|
+
phase: 'PREFLIGHT',
|
|
631
|
+
});
|
|
632
|
+
throw new Error('ACP_SESSION_PERSIST_LOCK_TIMEOUT');
|
|
633
|
+
}
|
|
634
|
+
const heartbeat = setInterval(() => {
|
|
635
|
+
void writeFile(lockPath, JSON.stringify({ pid: process.pid, createdAtMs: Date.now() }), 'utf8');
|
|
636
|
+
}, Math.max(1000, sessionStorePolicy.lockHeartbeatMs));
|
|
637
|
+
const tempPath = defaultPathAdapter.join(dir, `.sessions.v1.json.tmp-${process.pid}-${Date.now()}`);
|
|
638
|
+
try {
|
|
639
|
+
let existing = { schemaVersion: 2, sessions: [] };
|
|
640
|
+
try {
|
|
641
|
+
const existingRaw = await readFile(sessionPersistencePath, 'utf8');
|
|
642
|
+
existing = normalizePersistedSessionStore(JSON.parse(existingRaw));
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
// ignore read failure; writing fresh payload is acceptable
|
|
646
|
+
}
|
|
647
|
+
const merged = new Map();
|
|
648
|
+
for (const entry of existing.sessions)
|
|
649
|
+
merged.set(entry.id, entry);
|
|
650
|
+
for (const entry of payload.sessions)
|
|
651
|
+
merged.set(entry.id, entry);
|
|
652
|
+
const mergedPayload = {
|
|
653
|
+
schemaVersion: 2,
|
|
654
|
+
sessions: pruneSessionRecords(Array.from(merged.values())),
|
|
655
|
+
};
|
|
656
|
+
await writeFile(tempPath, JSON.stringify(mergedPayload, null, 2), 'utf8');
|
|
657
|
+
await rename(tempPath, sessionPersistencePath);
|
|
658
|
+
}
|
|
659
|
+
finally {
|
|
660
|
+
clearInterval(heartbeat);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
recordAuditEvent('acp.session.persist.failed', {
|
|
665
|
+
errorName: error instanceof Error ? error.name : typeof error,
|
|
666
|
+
}, { source: 'acp', severity: 'low', scope: 'session', phase: 'PREFLIGHT' });
|
|
667
|
+
}
|
|
668
|
+
finally {
|
|
669
|
+
if (lockHandle) {
|
|
670
|
+
try {
|
|
671
|
+
await lockHandle.close();
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
// ignore
|
|
675
|
+
}
|
|
676
|
+
try {
|
|
677
|
+
await unlink(lockPath);
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
// ignore
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async function hydrateSessionsOnce() {
|
|
686
|
+
if (sessionsHydrated)
|
|
687
|
+
return;
|
|
688
|
+
if (hydratePromise)
|
|
689
|
+
return hydratePromise;
|
|
690
|
+
hydratePromise = (async () => {
|
|
691
|
+
sessionsHydrated = true;
|
|
692
|
+
if (!sessionPersistencePath)
|
|
693
|
+
return;
|
|
694
|
+
try {
|
|
695
|
+
const raw = await readFile(sessionPersistencePath, 'utf8');
|
|
696
|
+
const parsed = normalizePersistedSessionStore(JSON.parse(raw));
|
|
697
|
+
for (const stored of pruneSessionRecords(parsed.sessions)) {
|
|
698
|
+
sessions.upsert({
|
|
699
|
+
id: stored.id,
|
|
700
|
+
cwd: stored.cwd,
|
|
701
|
+
mcpServers: Array.isArray(stored.mcpServers) ? stored.mcpServers : [],
|
|
702
|
+
createdAt: stored.createdAt,
|
|
703
|
+
updatedAt: stored.updatedAt,
|
|
704
|
+
title: stored.title,
|
|
705
|
+
taskId: stored.taskId,
|
|
706
|
+
history: Array.isArray(stored.history)
|
|
707
|
+
? stored.history.slice(-sessionStorePolicy.historyMaxEntries)
|
|
708
|
+
: [],
|
|
709
|
+
cancelRequested: false,
|
|
710
|
+
});
|
|
711
|
+
if (!sessionRuntime.has(stored.id)) {
|
|
712
|
+
sessionRuntime.set(stored.id, createSessionRuntimeStateFromPersisted({
|
|
713
|
+
permissionPolicy: stored.permissionPolicy,
|
|
714
|
+
modeId: stored.modeId,
|
|
715
|
+
defaultModeId: deps.defaultModeId,
|
|
716
|
+
}));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
if (isFileMissing(error))
|
|
722
|
+
return;
|
|
723
|
+
recordAuditEvent('acp.session.hydrate.failed', {
|
|
724
|
+
errorName: error instanceof Error ? error.name : typeof error,
|
|
725
|
+
}, { source: 'acp', severity: 'low', scope: 'session', phase: 'PREFLIGHT' });
|
|
726
|
+
}
|
|
727
|
+
})();
|
|
728
|
+
return hydratePromise;
|
|
729
|
+
}
|
|
730
|
+
function hashRepoPath(repoPath) {
|
|
731
|
+
return createHash('sha256').update(repoPath).digest('hex').slice(0, 16);
|
|
732
|
+
}
|
|
733
|
+
function toCheckpointMeta(input) {
|
|
734
|
+
if (!input)
|
|
735
|
+
return null;
|
|
736
|
+
return {
|
|
737
|
+
id: input.id,
|
|
738
|
+
createdAt: input.createdAt ?? null,
|
|
739
|
+
strategy: input.strategy ?? null,
|
|
740
|
+
backend: input.backend ?? null,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function toResumeHint(probe) {
|
|
744
|
+
if (!probe || probe.valid)
|
|
745
|
+
return null;
|
|
746
|
+
switch (probe.reason) {
|
|
747
|
+
case 'not_found':
|
|
748
|
+
return {
|
|
749
|
+
code: 'CHECKPOINT_NOT_FOUND',
|
|
750
|
+
message: 'Checkpoint not found. Start a new session.',
|
|
751
|
+
};
|
|
752
|
+
case 'manifest_parse_error':
|
|
753
|
+
return {
|
|
754
|
+
code: 'CHECKPOINT_MANIFEST_PARSE_ERROR',
|
|
755
|
+
message: 'Checkpoint metadata is corrupted. Recreate checkpoint metadata and retry.',
|
|
756
|
+
};
|
|
757
|
+
case 'manifest_io_error':
|
|
758
|
+
return {
|
|
759
|
+
code: 'CHECKPOINT_MANIFEST_IO_ERROR',
|
|
760
|
+
message: 'Checkpoint metadata is unreadable due to filesystem I/O issues.',
|
|
761
|
+
};
|
|
762
|
+
case 'manifest_lock_timeout':
|
|
763
|
+
return {
|
|
764
|
+
code: 'CHECKPOINT_MANIFEST_LOCK_TIMEOUT',
|
|
765
|
+
message: 'Checkpoint metadata is busy (lock timeout). Retry shortly.',
|
|
766
|
+
};
|
|
767
|
+
case 'manifest_unavailable':
|
|
768
|
+
return {
|
|
769
|
+
code: 'CHECKPOINT_MANIFEST_UNAVAILABLE',
|
|
770
|
+
message: 'Checkpoint metadata is unavailable in current runtime.',
|
|
771
|
+
};
|
|
772
|
+
default:
|
|
773
|
+
return {
|
|
774
|
+
code: 'CHECKPOINT_RESUME_UNAVAILABLE',
|
|
775
|
+
message: 'Checkpoint resume is unavailable. Start a new session or retry.',
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async function emitSessionUpdate(sessionId, update) {
|
|
780
|
+
await deps.conn.sessionUpdate({ sessionId, update });
|
|
781
|
+
}
|
|
782
|
+
async function emitRuntimePlanUpdateIfNeeded(params) {
|
|
783
|
+
if (!shouldRefreshPlanForEvent(params.event))
|
|
784
|
+
return;
|
|
785
|
+
const { state, event } = params;
|
|
786
|
+
const planReader = deps.planReader ?? {
|
|
787
|
+
readBySession: async ({ repoPath, sessionId }) => await readPlan({ persistenceRoot: repoPath, sessionId }),
|
|
788
|
+
};
|
|
789
|
+
if (event?.type === 'plan.runtime.ready') {
|
|
790
|
+
state.runtimePlanSessionId = event.sessionId;
|
|
791
|
+
state.runtimePlanPathHint = event.planPathHint;
|
|
792
|
+
state.lastPlanDigest = null;
|
|
793
|
+
}
|
|
794
|
+
if (!state.runtimePlanSessionId)
|
|
795
|
+
return;
|
|
796
|
+
try {
|
|
797
|
+
const read = await planReader.readBySession({
|
|
798
|
+
repoPath: params.repoPath,
|
|
799
|
+
sessionId: state.runtimePlanSessionId,
|
|
800
|
+
});
|
|
801
|
+
const planUpdate = buildPlanUpdateFromCoreIfChanged(read, state);
|
|
802
|
+
if (planUpdate) {
|
|
803
|
+
await emitSessionUpdate(params.sessionId, planUpdate);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
recordAuditEvent('acp.plan.read.failed', {
|
|
808
|
+
sessionId: params.sessionId,
|
|
809
|
+
repoPathHash: hashRepoPath(params.repoPath),
|
|
810
|
+
runtimePlanSessionId: state.runtimePlanSessionId,
|
|
811
|
+
runtimePlanPathHint: state.runtimePlanPathHint,
|
|
812
|
+
errorName: error instanceof Error ? error.name : typeof error,
|
|
813
|
+
}, { source: 'acp', severity: 'low', scope: 'session', phase: 'PLAN' });
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async function loadSessionInternal(params) {
|
|
817
|
+
await hydrateSessionsOnce();
|
|
818
|
+
const session = sessions.get(params.sessionId);
|
|
819
|
+
if (!session) {
|
|
820
|
+
throw new RequestError(-32004, `Session not found: ${params.sessionId}`);
|
|
821
|
+
}
|
|
822
|
+
if (session.cwd !== params.cwd) {
|
|
823
|
+
sessions.update(params.sessionId, (current) => ({
|
|
824
|
+
...current,
|
|
825
|
+
cwd: params.cwd,
|
|
826
|
+
mcpServers: params.mcpServers ?? [],
|
|
827
|
+
}));
|
|
828
|
+
await persistSessionsBestEffort();
|
|
829
|
+
}
|
|
830
|
+
return session;
|
|
831
|
+
}
|
|
832
|
+
function ensureSessionRuntimeState(sessionId) {
|
|
833
|
+
const existing = sessionRuntime.get(sessionId);
|
|
834
|
+
if (existing)
|
|
835
|
+
return existing;
|
|
836
|
+
const created = createSessionRuntimeState();
|
|
837
|
+
if (deps.defaultModeId) {
|
|
838
|
+
created.modeId = deps.defaultModeId;
|
|
839
|
+
}
|
|
840
|
+
sessionRuntime.set(sessionId, created);
|
|
841
|
+
return created;
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
async initialize(params) {
|
|
845
|
+
if (typeof params.protocolVersion !== 'number' || !Number.isFinite(params.protocolVersion)) {
|
|
846
|
+
throw new RequestError(-32602, 'Invalid params: protocolVersion is required');
|
|
847
|
+
}
|
|
848
|
+
clientCapabilities = params.clientCapabilities;
|
|
849
|
+
return {
|
|
850
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
851
|
+
agentInfo: deps.agentInfo,
|
|
852
|
+
authMethods: [],
|
|
853
|
+
agentCapabilities: {
|
|
854
|
+
loadSession: loadSessionCapability,
|
|
855
|
+
promptCapabilities: defaultPromptCapabilities,
|
|
856
|
+
mcpCapabilities: { http: false, sse: false },
|
|
857
|
+
sessionCapabilities: {},
|
|
858
|
+
},
|
|
859
|
+
};
|
|
860
|
+
},
|
|
861
|
+
async authenticate() {
|
|
862
|
+
return;
|
|
863
|
+
},
|
|
864
|
+
async newSession(params) {
|
|
865
|
+
await hydrateSessionsOnce();
|
|
866
|
+
if (!isAbsolutePath(params.cwd)) {
|
|
867
|
+
throw new RequestError(-32602, 'Invalid params: cwd must be an absolute path');
|
|
868
|
+
}
|
|
869
|
+
const session = sessions.create({ cwd: params.cwd, mcpServers: params.mcpServers ?? [] });
|
|
870
|
+
await persistSessionsBestEffort();
|
|
871
|
+
const runtimeState = ensureSessionRuntimeState(session.id);
|
|
872
|
+
// Restore session state on creation
|
|
873
|
+
const commandsUpdate = buildAvailableCommandsUpdateIfChanged(runtimeState);
|
|
874
|
+
if (commandsUpdate)
|
|
875
|
+
await emitSessionUpdate(session.id, commandsUpdate);
|
|
876
|
+
const modeUpdate = buildCurrentModeUpdateIfChanged(runtimeState);
|
|
877
|
+
if (modeUpdate)
|
|
878
|
+
await emitSessionUpdate(session.id, modeUpdate);
|
|
879
|
+
let sessionMeta;
|
|
880
|
+
if (deps.checkpointReader) {
|
|
881
|
+
const checkpoints = await deps.checkpointReader.listBySession({
|
|
882
|
+
repoPath: params.cwd,
|
|
883
|
+
sessionId: session.id,
|
|
884
|
+
limit: 1,
|
|
885
|
+
});
|
|
886
|
+
const latest = checkpoints.at(-1);
|
|
887
|
+
sessionMeta = {
|
|
888
|
+
salmonloop: {
|
|
889
|
+
latestCheckpointId: latest?.id ?? null,
|
|
890
|
+
checkpoint: toCheckpointMeta(latest),
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
sessionId: session.id,
|
|
896
|
+
configOptions: buildConfigOptions(runtimeState),
|
|
897
|
+
...(sessionMeta ? { _meta: sessionMeta } : {}),
|
|
898
|
+
};
|
|
899
|
+
},
|
|
900
|
+
async loadSession(params) {
|
|
901
|
+
if (!loadSessionCapability) {
|
|
902
|
+
throw new RequestError(-32601, '"Method not found": session/load');
|
|
903
|
+
}
|
|
904
|
+
await loadSessionInternal(params);
|
|
905
|
+
const session = sessions.get(params.sessionId);
|
|
906
|
+
const runtimeState = ensureSessionRuntimeState(session.id);
|
|
907
|
+
// Restore plan state if session was running a task
|
|
908
|
+
if (session.taskId && session.cwd) {
|
|
909
|
+
await emitRuntimePlanUpdateIfNeeded({
|
|
910
|
+
sessionId: session.id,
|
|
911
|
+
repoPath: session.cwd,
|
|
912
|
+
state: runtimeState,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
const commandsUpdate = buildAvailableCommandsUpdateIfChanged(runtimeState);
|
|
916
|
+
if (commandsUpdate)
|
|
917
|
+
await emitSessionUpdate(session.id, commandsUpdate);
|
|
918
|
+
const modeUpdate = buildCurrentModeUpdateIfChanged(runtimeState);
|
|
919
|
+
if (modeUpdate)
|
|
920
|
+
await emitSessionUpdate(session.id, modeUpdate);
|
|
921
|
+
for (const entry of session.history) {
|
|
922
|
+
if (entry.role !== 'assistant')
|
|
923
|
+
continue;
|
|
924
|
+
for (const block of entry.content) {
|
|
925
|
+
if (block.type === 'text' && typeof block.text === 'string' && block.text.trim()) {
|
|
926
|
+
await emitSessionUpdate(session.id, {
|
|
927
|
+
sessionUpdate: 'agent_message_chunk',
|
|
928
|
+
content: buildTextContentBlock(block.text),
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const response = {
|
|
934
|
+
configOptions: buildConfigOptions(runtimeState),
|
|
935
|
+
};
|
|
936
|
+
if (deps.checkpointReader) {
|
|
937
|
+
const startedAt = Date.now();
|
|
938
|
+
const checkpoints = await deps.checkpointReader.listBySession({
|
|
939
|
+
repoPath: params.cwd,
|
|
940
|
+
sessionId: params.sessionId,
|
|
941
|
+
limit: 1,
|
|
942
|
+
});
|
|
943
|
+
const latest = checkpoints.at(-1);
|
|
944
|
+
let resumeProbe = null;
|
|
945
|
+
if (latest?.id && deps.checkpointReader.probeById) {
|
|
946
|
+
const probed = await deps.checkpointReader.probeById({
|
|
947
|
+
repoPath: params.cwd,
|
|
948
|
+
checkpointId: latest.id,
|
|
949
|
+
});
|
|
950
|
+
resumeProbe = {
|
|
951
|
+
checkpointId: latest.id,
|
|
952
|
+
valid: probed.valid,
|
|
953
|
+
reason: probed.reason,
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
else if (latest?.id && deps.checkpointReader.getById) {
|
|
957
|
+
const found = await deps.checkpointReader.getById({
|
|
958
|
+
repoPath: params.cwd,
|
|
959
|
+
checkpointId: latest.id,
|
|
960
|
+
});
|
|
961
|
+
resumeProbe = {
|
|
962
|
+
checkpointId: latest.id,
|
|
963
|
+
valid: Boolean(found),
|
|
964
|
+
reason: found ? 'ok' : 'not_found',
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
const resumeReady = resumeProbe?.valid ?? Boolean(latest);
|
|
968
|
+
recordAuditEvent('acp.checkpoint.read', {
|
|
969
|
+
sessionId: params.sessionId,
|
|
970
|
+
repoPathHash: hashRepoPath(params.cwd),
|
|
971
|
+
latestCheckpointId: latest?.id ?? null,
|
|
972
|
+
hit: Boolean(latest),
|
|
973
|
+
latencyMs: Date.now() - startedAt,
|
|
974
|
+
resumeProbe: resumeProbe ?? undefined,
|
|
975
|
+
}, { source: 'acp', severity: 'low', scope: 'session', phase: 'PREFLIGHT' });
|
|
976
|
+
const resumeHint = toResumeHint(resumeProbe);
|
|
977
|
+
response._meta = {
|
|
978
|
+
salmonloop: {
|
|
979
|
+
latestCheckpointId: latest?.id ?? null,
|
|
980
|
+
checkpoint: toCheckpointMeta(latest),
|
|
981
|
+
resumeReady,
|
|
982
|
+
resumeProbe,
|
|
983
|
+
resumeHint: resumeHint?.message ?? null,
|
|
984
|
+
resumeHintCode: resumeHint?.code ?? null,
|
|
985
|
+
},
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
else {
|
|
989
|
+
recordAuditEvent('acp.checkpoint.read', {
|
|
990
|
+
sessionId: params.sessionId,
|
|
991
|
+
repoPathHash: hashRepoPath(params.cwd),
|
|
992
|
+
latestCheckpointId: null,
|
|
993
|
+
hit: false,
|
|
994
|
+
reason: 'checkpoint_reader_missing',
|
|
995
|
+
}, { source: 'acp', severity: 'low', scope: 'session', phase: 'PREFLIGHT' });
|
|
996
|
+
}
|
|
997
|
+
return response;
|
|
998
|
+
},
|
|
999
|
+
async setSessionConfigOption(params) {
|
|
1000
|
+
await hydrateSessionsOnce();
|
|
1001
|
+
if (!sessions.get(params.sessionId)) {
|
|
1002
|
+
throw new RequestError(-32004, `Session not found: ${params.sessionId}`);
|
|
1003
|
+
}
|
|
1004
|
+
const runtimeState = ensureSessionRuntimeState(params.sessionId);
|
|
1005
|
+
if (params.configId === ACP_PERMISSION_POLICY_CONFIG_ID) {
|
|
1006
|
+
if (!isPermissionPolicyValue(params.value)) {
|
|
1007
|
+
throw new RequestError(-32602, `Invalid params: unsupported value "${params.value}" for "${params.configId}"`);
|
|
1008
|
+
}
|
|
1009
|
+
runtimeState.permissionPolicy = params.value;
|
|
1010
|
+
}
|
|
1011
|
+
else if (params.configId === ACP_MODE_CONFIG_ID) {
|
|
1012
|
+
if (!isSessionModeId(params.value)) {
|
|
1013
|
+
throw new RequestError(-32602, `Invalid params: unsupported value "${params.value}" for "${params.configId}"`);
|
|
1014
|
+
}
|
|
1015
|
+
runtimeState.modeId = params.value;
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
throw new RequestError(-32602, `Invalid params: unsupported configId "${params.configId}"`);
|
|
1019
|
+
}
|
|
1020
|
+
sessions.update(params.sessionId, (current) => ({ ...current }));
|
|
1021
|
+
await persistSessionsBestEffort();
|
|
1022
|
+
const update = buildConfigOptionUpdateIfChanged(runtimeState);
|
|
1023
|
+
if (update) {
|
|
1024
|
+
await emitSessionUpdate(params.sessionId, update);
|
|
1025
|
+
}
|
|
1026
|
+
const modeUpdate = buildCurrentModeUpdateIfChanged(runtimeState);
|
|
1027
|
+
if (modeUpdate) {
|
|
1028
|
+
await emitSessionUpdate(params.sessionId, modeUpdate);
|
|
1029
|
+
}
|
|
1030
|
+
return { configOptions: buildConfigOptions(runtimeState) };
|
|
1031
|
+
},
|
|
1032
|
+
async prompt(params) {
|
|
1033
|
+
await hydrateSessionsOnce();
|
|
1034
|
+
const session = sessions.get(params.sessionId);
|
|
1035
|
+
if (!session) {
|
|
1036
|
+
throw new RequestError(-32004, `Session not found: ${params.sessionId}`);
|
|
1037
|
+
}
|
|
1038
|
+
const caps = clientCapabilities ?? defaultClientCapabilities;
|
|
1039
|
+
const fsCaps = caps.fs;
|
|
1040
|
+
const clientExecutionReady = caps.terminal === true && Boolean(fsCaps?.readTextFile) && Boolean(fsCaps?.writeTextFile);
|
|
1041
|
+
const effectiveExecutionBinding = executionBinding === 'client' && !clientExecutionReady ? 'local' : executionBinding;
|
|
1042
|
+
const promptText = extractTextFromPrompt(params.prompt, defaultPromptCapabilities);
|
|
1043
|
+
const runtimeState = ensureSessionRuntimeState(params.sessionId);
|
|
1044
|
+
sessions.update(params.sessionId, (current) => {
|
|
1045
|
+
return {
|
|
1046
|
+
...current,
|
|
1047
|
+
cancelRequested: false,
|
|
1048
|
+
history: [
|
|
1049
|
+
...current.history,
|
|
1050
|
+
{ role: 'user', content: params.prompt },
|
|
1051
|
+
],
|
|
1052
|
+
};
|
|
1053
|
+
});
|
|
1054
|
+
await persistSessionsBestEffort();
|
|
1055
|
+
const configUpdate = buildConfigOptionUpdateIfChanged(runtimeState);
|
|
1056
|
+
if (configUpdate) {
|
|
1057
|
+
await emitSessionUpdate(params.sessionId, configUpdate);
|
|
1058
|
+
}
|
|
1059
|
+
const modeUpdate = buildCurrentModeUpdateIfChanged(runtimeState);
|
|
1060
|
+
if (modeUpdate) {
|
|
1061
|
+
await emitSessionUpdate(params.sessionId, modeUpdate);
|
|
1062
|
+
}
|
|
1063
|
+
const commandsUpdate = buildAvailableCommandsUpdateIfChanged(runtimeState);
|
|
1064
|
+
if (commandsUpdate) {
|
|
1065
|
+
await emitSessionUpdate(params.sessionId, commandsUpdate);
|
|
1066
|
+
}
|
|
1067
|
+
const slashInput = extractSlashInput(params.prompt);
|
|
1068
|
+
if (slashInput) {
|
|
1069
|
+
const parsed = parseSlashInput(slashInput);
|
|
1070
|
+
if (parsed.kind === 'slash' &&
|
|
1071
|
+
parsed.commandName &&
|
|
1072
|
+
isKnownSlashCommand(parsed.commandName)) {
|
|
1073
|
+
const responseText = buildSlashHelpMessage();
|
|
1074
|
+
await emitSessionUpdate(params.sessionId, {
|
|
1075
|
+
sessionUpdate: 'agent_message_chunk',
|
|
1076
|
+
content: buildTextContentBlock(ensureMarkdownParagraphBreak(responseText)),
|
|
1077
|
+
});
|
|
1078
|
+
sessions.update(params.sessionId, (current) => ({
|
|
1079
|
+
...current,
|
|
1080
|
+
history: [
|
|
1081
|
+
...current.history,
|
|
1082
|
+
{ role: 'assistant', content: [buildTextContentBlock(responseText)] },
|
|
1083
|
+
],
|
|
1084
|
+
}));
|
|
1085
|
+
await persistSessionsBestEffort();
|
|
1086
|
+
return { stopReason: 'end_turn' };
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
const { task, signal } = await deps.facade.createTask({
|
|
1090
|
+
capability: 'patch',
|
|
1091
|
+
request: {
|
|
1092
|
+
instruction: promptText,
|
|
1093
|
+
checkpointSessionId: params.sessionId,
|
|
1094
|
+
repoPath: session.cwd,
|
|
1095
|
+
},
|
|
1096
|
+
commandRunner: effectiveExecutionBinding === 'client'
|
|
1097
|
+
? createAcpCommandRunner({ conn: deps.conn, sessionId: params.sessionId })
|
|
1098
|
+
: undefined,
|
|
1099
|
+
fileSystemOverride: effectiveExecutionBinding === 'client'
|
|
1100
|
+
? createAcpFileSystem({ conn: deps.conn, sessionId: params.sessionId })
|
|
1101
|
+
: undefined,
|
|
1102
|
+
authorizationProvider: createAcpToolAuthorizationProvider({
|
|
1103
|
+
conn: deps.conn,
|
|
1104
|
+
sessionId: params.sessionId,
|
|
1105
|
+
clientCapabilities: caps,
|
|
1106
|
+
getPermissionPolicy: () => getPermissionPolicyForAuthorization(runtimeState),
|
|
1107
|
+
enforceClientCapabilities: effectiveExecutionBinding === 'client',
|
|
1108
|
+
}),
|
|
1109
|
+
authorizationMode: 'blocking',
|
|
1110
|
+
onEvent: (event) => {
|
|
1111
|
+
for (const update of loopEventToSessionUpdates(event, runtimeState)) {
|
|
1112
|
+
void emitSessionUpdate(params.sessionId, update);
|
|
1113
|
+
}
|
|
1114
|
+
void emitRuntimePlanUpdateIfNeeded({
|
|
1115
|
+
sessionId: params.sessionId,
|
|
1116
|
+
repoPath: session.cwd,
|
|
1117
|
+
event,
|
|
1118
|
+
state: runtimeState,
|
|
1119
|
+
});
|
|
1120
|
+
},
|
|
1121
|
+
});
|
|
1122
|
+
sessions.update(params.sessionId, (current) => ({ ...current, taskId: task.id }));
|
|
1123
|
+
await persistSessionsBestEffort();
|
|
1124
|
+
if (signal.aborted) {
|
|
1125
|
+
await emitSessionUpdate(params.sessionId, {
|
|
1126
|
+
sessionUpdate: 'agent_message_chunk',
|
|
1127
|
+
content: buildTextContentBlock(ensureMarkdownParagraphBreak('Task cancelled.')),
|
|
1128
|
+
});
|
|
1129
|
+
return { stopReason: 'cancelled' };
|
|
1130
|
+
}
|
|
1131
|
+
const terminalEvent = await awaitTerminalEvent({ taskId: task.id, eventBus: deps.eventBus });
|
|
1132
|
+
let stopReason = 'end_turn';
|
|
1133
|
+
let assistantText = 'Task completed.';
|
|
1134
|
+
let assistantMeta;
|
|
1135
|
+
let latest;
|
|
1136
|
+
const cancelRequested = sessions.get(params.sessionId)?.cancelRequested === true;
|
|
1137
|
+
if (cancelRequested) {
|
|
1138
|
+
assistantText = 'Task cancelled.';
|
|
1139
|
+
stopReason = 'cancelled';
|
|
1140
|
+
}
|
|
1141
|
+
else if (terminalEvent?.type === 'task.failed') {
|
|
1142
|
+
latest = await deps.facade.getTask(task.id);
|
|
1143
|
+
const failureMessage = typeof latest?.failure?.message === 'string' ? latest.failure.message : undefined;
|
|
1144
|
+
assistantText = failureMessage ? `Task failed: ${failureMessage}` : 'Task failed.';
|
|
1145
|
+
const inferred = inferTurnStopReasonFromFailure(latest?.failure);
|
|
1146
|
+
if (inferred)
|
|
1147
|
+
stopReason = inferred;
|
|
1148
|
+
}
|
|
1149
|
+
else if (terminalEvent?.type === 'task.awaiting_input') {
|
|
1150
|
+
assistantText = 'Task awaiting input.';
|
|
1151
|
+
latest = await deps.facade.getTask(task.id);
|
|
1152
|
+
const formatted = latest?.inputRequired
|
|
1153
|
+
? formatInputRequiredMessage(latest.inputRequired)
|
|
1154
|
+
: null;
|
|
1155
|
+
if (formatted)
|
|
1156
|
+
assistantText = formatted;
|
|
1157
|
+
if (latest?.inputRequired) {
|
|
1158
|
+
assistantMeta = { inputRequired: latest.inputRequired };
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
else if (terminalEvent?.type === 'task.cancelled') {
|
|
1162
|
+
assistantText = 'Task cancelled.';
|
|
1163
|
+
stopReason = 'cancelled';
|
|
1164
|
+
}
|
|
1165
|
+
await emitSessionUpdate(params.sessionId, {
|
|
1166
|
+
sessionUpdate: 'agent_message_chunk',
|
|
1167
|
+
content: buildTextContentBlock(ensureMarkdownParagraphBreak(assistantText)),
|
|
1168
|
+
...(assistantMeta ? { _meta: assistantMeta } : {}),
|
|
1169
|
+
});
|
|
1170
|
+
if (latest?.inputRequired) {
|
|
1171
|
+
await emitSessionUpdate(params.sessionId, {
|
|
1172
|
+
sessionUpdate: 'agent_message_chunk',
|
|
1173
|
+
content: buildJsonResourceContentBlock(latest.inputRequired),
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
sessions.update(params.sessionId, (current) => ({
|
|
1177
|
+
...current,
|
|
1178
|
+
history: [
|
|
1179
|
+
...current.history,
|
|
1180
|
+
{ role: 'assistant', content: [buildTextContentBlock(assistantText)] },
|
|
1181
|
+
],
|
|
1182
|
+
}));
|
|
1183
|
+
await persistSessionsBestEffort();
|
|
1184
|
+
return { stopReason };
|
|
1185
|
+
},
|
|
1186
|
+
async cancel(params) {
|
|
1187
|
+
await hydrateSessionsOnce();
|
|
1188
|
+
const session = sessions.get(params.sessionId);
|
|
1189
|
+
if (!session)
|
|
1190
|
+
return;
|
|
1191
|
+
sessions.update(params.sessionId, (current) => ({ ...current, cancelRequested: true }));
|
|
1192
|
+
await persistSessionsBestEffort();
|
|
1193
|
+
if (session.taskId) {
|
|
1194
|
+
await deps.facade.cancelTask(session.taskId);
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
extMethod: async () => ({}),
|
|
1198
|
+
extNotification: async () => { },
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
//# sourceMappingURL=formal-agent.js.map
|