pulseed 0.5.3 → 0.5.4
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/dist/base/state/state-manager-goal-state.d.ts +40 -0
- package/dist/base/state/state-manager-goal-state.d.ts.map +1 -0
- package/dist/base/state/state-manager-goal-state.js +235 -0
- package/dist/base/state/state-manager-goal-state.js.map +1 -0
- package/dist/base/state/state-manager.d.ts +0 -5
- package/dist/base/state/state-manager.d.ts.map +1 -1
- package/dist/base/state/state-manager.js +27 -273
- package/dist/base/state/state-manager.js.map +1 -1
- package/dist/base/types/goal-activation.d.ts +25 -0
- package/dist/base/types/goal-activation.d.ts.map +1 -0
- package/dist/base/types/goal-activation.js +9 -0
- package/dist/base/types/goal-activation.js.map +1 -0
- package/dist/interface/chat/chat-history.d.ts +129 -0
- package/dist/interface/chat/chat-history.d.ts.map +1 -1
- package/dist/interface/chat/chat-history.js +135 -0
- package/dist/interface/chat/chat-history.js.map +1 -1
- package/dist/interface/chat/chat-runner-command-helpers.d.ts +50 -0
- package/dist/interface/chat/chat-runner-command-helpers.d.ts.map +1 -0
- package/dist/interface/chat/chat-runner-command-helpers.js +189 -0
- package/dist/interface/chat/chat-runner-command-helpers.js.map +1 -0
- package/dist/interface/chat/chat-runner-commands.d.ts +76 -0
- package/dist/interface/chat/chat-runner-commands.d.ts.map +1 -0
- package/dist/interface/chat/chat-runner-commands.js +814 -0
- package/dist/interface/chat/chat-runner-commands.js.map +1 -0
- package/dist/interface/chat/chat-runner-event-bridge.d.ts +53 -0
- package/dist/interface/chat/chat-runner-event-bridge.d.ts.map +1 -0
- package/dist/interface/chat/chat-runner-event-bridge.js +311 -0
- package/dist/interface/chat/chat-runner-event-bridge.js.map +1 -0
- package/dist/interface/chat/chat-runner-routes.d.ts +67 -0
- package/dist/interface/chat/chat-runner-routes.d.ts.map +1 -0
- package/dist/interface/chat/chat-runner-routes.js +594 -0
- package/dist/interface/chat/chat-runner-routes.js.map +1 -0
- package/dist/interface/chat/chat-runner-runtime.d.ts +37 -0
- package/dist/interface/chat/chat-runner-runtime.d.ts.map +1 -0
- package/dist/interface/chat/chat-runner-runtime.js +236 -0
- package/dist/interface/chat/chat-runner-runtime.js.map +1 -0
- package/dist/interface/chat/chat-runner-state.d.ts +20 -0
- package/dist/interface/chat/chat-runner-state.d.ts.map +1 -0
- package/dist/interface/chat/chat-runner-state.js +157 -0
- package/dist/interface/chat/chat-runner-state.js.map +1 -0
- package/dist/interface/chat/chat-runner-support.d.ts +15 -0
- package/dist/interface/chat/chat-runner-support.d.ts.map +1 -0
- package/dist/interface/chat/chat-runner-support.js +116 -0
- package/dist/interface/chat/chat-runner-support.js.map +1 -0
- package/dist/interface/chat/chat-runner.d.ts +10 -153
- package/dist/interface/chat/chat-runner.d.ts.map +1 -1
- package/dist/interface/chat/chat-runner.js +299 -2642
- package/dist/interface/chat/chat-runner.js.map +1 -1
- package/dist/interface/chat/chat-session-store.d.ts +32 -0
- package/dist/interface/chat/chat-session-store.d.ts.map +1 -1
- package/dist/interface/chat/chat-session-store.js +100 -10
- package/dist/interface/chat/chat-session-store.js.map +1 -1
- package/dist/interface/chat/cross-platform-session.d.ts.map +1 -1
- package/dist/interface/chat/cross-platform-session.js +2 -5
- package/dist/interface/chat/cross-platform-session.js.map +1 -1
- package/dist/interface/chat/event-subscriber.d.ts.map +1 -1
- package/dist/interface/chat/event-subscriber.js +41 -0
- package/dist/interface/chat/event-subscriber.js.map +1 -1
- package/dist/interface/chat/ingress-router.d.ts +0 -25
- package/dist/interface/chat/ingress-router.d.ts.map +1 -1
- package/dist/interface/chat/ingress-router.js +12 -85
- package/dist/interface/chat/ingress-router.js.map +1 -1
- package/dist/interface/chat/tend-command.d.ts.map +1 -1
- package/dist/interface/chat/tend-command.js +2 -0
- package/dist/interface/chat/tend-command.js.map +1 -1
- package/dist/interface/cli/commands/daemon-shared.d.ts +32 -0
- package/dist/interface/cli/commands/daemon-shared.d.ts.map +1 -0
- package/dist/interface/cli/commands/daemon-shared.js +120 -0
- package/dist/interface/cli/commands/daemon-shared.js.map +1 -0
- package/dist/interface/cli/commands/daemon.d.ts +2 -2
- package/dist/interface/cli/commands/daemon.d.ts.map +1 -1
- package/dist/interface/cli/commands/daemon.js +4 -120
- package/dist/interface/cli/commands/daemon.js.map +1 -1
- package/dist/interface/cli/commands/schedule/history.d.ts.map +1 -1
- package/dist/interface/cli/commands/schedule/history.js +5 -1
- package/dist/interface/cli/commands/schedule/history.js.map +1 -1
- package/dist/interface/cli/commands/schedule.js +34 -4
- package/dist/interface/cli/commands/schedule.js.map +1 -1
- package/dist/interface/tui/app.d.ts.map +1 -1
- package/dist/interface/tui/app.js +14 -1
- package/dist/interface/tui/app.js.map +1 -1
- package/dist/interface/tui/chat/suggestions.d.ts.map +1 -1
- package/dist/interface/tui/chat/suggestions.js +118 -4
- package/dist/interface/tui/chat/suggestions.js.map +1 -1
- package/dist/interface/tui/entry-approval.d.ts +8 -0
- package/dist/interface/tui/entry-approval.d.ts.map +1 -0
- package/dist/interface/tui/entry-approval.js +59 -0
- package/dist/interface/tui/entry-approval.js.map +1 -0
- package/dist/interface/tui/entry-daemon.d.ts +12 -0
- package/dist/interface/tui/entry-daemon.d.ts.map +1 -0
- package/dist/interface/tui/entry-daemon.js +74 -0
- package/dist/interface/tui/entry-daemon.js.map +1 -0
- package/dist/interface/tui/entry-deps.d.ts +22 -0
- package/dist/interface/tui/entry-deps.d.ts.map +1 -0
- package/dist/interface/tui/entry-deps.js +409 -0
- package/dist/interface/tui/entry-deps.js.map +1 -0
- package/dist/interface/tui/entry.d.ts +2 -4
- package/dist/interface/tui/entry.d.ts.map +1 -1
- package/dist/interface/tui/entry.js +10 -557
- package/dist/interface/tui/entry.js.map +1 -1
- package/dist/interface/tui/fullscreen-chat-render.d.ts +127 -0
- package/dist/interface/tui/fullscreen-chat-render.d.ts.map +1 -0
- package/dist/interface/tui/fullscreen-chat-render.js +667 -0
- package/dist/interface/tui/fullscreen-chat-render.js.map +1 -0
- package/dist/interface/tui/fullscreen-chat.d.ts +2 -111
- package/dist/interface/tui/fullscreen-chat.d.ts.map +1 -1
- package/dist/interface/tui/fullscreen-chat.js +4 -663
- package/dist/interface/tui/fullscreen-chat.js.map +1 -1
- package/dist/interface/tui/help-overlay.d.ts.map +1 -1
- package/dist/interface/tui/help-overlay.js +1 -1
- package/dist/interface/tui/help-overlay.js.map +1 -1
- package/dist/interface/tui/intent-recognizer.js +2 -2
- package/dist/interface/tui/intent-recognizer.js.map +1 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-default-profile.d.ts.map +1 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-default-profile.js +8 -0
- package/dist/orchestrator/execution/agent-loop/agent-loop-default-profile.js.map +1 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-tool-router.d.ts.map +1 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-tool-router.js +2 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-tool-router.js.map +1 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-tool-runtime.d.ts +2 -0
- package/dist/orchestrator/execution/agent-loop/agent-loop-tool-runtime.d.ts.map +1 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-tool-runtime.js +31 -0
- package/dist/orchestrator/execution/agent-loop/agent-loop-tool-runtime.js.map +1 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-turn-context.d.ts +1 -0
- package/dist/orchestrator/execution/agent-loop/agent-loop-turn-context.d.ts.map +1 -1
- package/dist/orchestrator/execution/agent-loop/agent-loop-turn-context.js.map +1 -1
- package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.d.ts +1 -0
- package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.d.ts.map +1 -1
- package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.js +36 -2
- package/dist/orchestrator/execution/agent-loop/bounded-agent-loop-runner.js.map +1 -1
- package/dist/orchestrator/execution/agent-loop/chat-agent-loop-runner.d.ts.map +1 -1
- package/dist/orchestrator/execution/agent-loop/chat-agent-loop-runner.js +2 -1
- package/dist/orchestrator/execution/agent-loop/chat-agent-loop-runner.js.map +1 -1
- package/dist/orchestrator/execution/agent-loop/core-loop-control-tools.d.ts +19 -0
- package/dist/orchestrator/execution/agent-loop/core-loop-control-tools.d.ts.map +1 -1
- package/dist/orchestrator/execution/agent-loop/core-loop-control-tools.js +164 -14
- package/dist/orchestrator/execution/agent-loop/core-loop-control-tools.js.map +1 -1
- package/dist/orchestrator/execution/reflection-generator.d.ts +1 -0
- package/dist/orchestrator/execution/reflection-generator.d.ts.map +1 -1
- package/dist/orchestrator/execution/reflection-generator.js +10 -1
- package/dist/orchestrator/execution/reflection-generator.js.map +1 -1
- package/dist/orchestrator/execution/task/task-context-enricher.d.ts +2 -0
- package/dist/orchestrator/execution/task/task-context-enricher.d.ts.map +1 -1
- package/dist/orchestrator/execution/task/task-context-enricher.js +9 -4
- package/dist/orchestrator/execution/task/task-context-enricher.js.map +1 -1
- package/dist/orchestrator/execution/task/task-execution-helpers-internal.d.ts +5 -0
- package/dist/orchestrator/execution/task/task-execution-helpers-internal.d.ts.map +1 -0
- package/dist/orchestrator/execution/task/task-execution-helpers-internal.js +6 -0
- package/dist/orchestrator/execution/task/task-execution-helpers-internal.js.map +1 -0
- package/dist/orchestrator/execution/task/task-generation.d.ts.map +1 -1
- package/dist/orchestrator/execution/task/task-generation.js +8 -3
- package/dist/orchestrator/execution/task/task-generation.js.map +1 -1
- package/dist/orchestrator/execution/task/task-lifecycle-runner.d.ts +73 -0
- package/dist/orchestrator/execution/task/task-lifecycle-runner.d.ts.map +1 -0
- package/dist/orchestrator/execution/task/task-lifecycle-runner.js +184 -0
- package/dist/orchestrator/execution/task/task-lifecycle-runner.js.map +1 -0
- package/dist/orchestrator/execution/task/task-lifecycle.d.ts +7 -7
- package/dist/orchestrator/execution/task/task-lifecycle.d.ts.map +1 -1
- package/dist/orchestrator/execution/task/task-lifecycle.js +37 -181
- package/dist/orchestrator/execution/task/task-lifecycle.js.map +1 -1
- package/dist/orchestrator/execution/task/task-verifier-internal.d.ts +2 -0
- package/dist/orchestrator/execution/task/task-verifier-internal.d.ts.map +1 -0
- package/dist/orchestrator/execution/task/task-verifier-internal.js +2 -0
- package/dist/orchestrator/execution/task/task-verifier-internal.js.map +1 -0
- package/dist/orchestrator/goal/goal-negotiator.d.ts.map +1 -1
- package/dist/orchestrator/goal/goal-negotiator.js +23 -3
- package/dist/orchestrator/goal/goal-negotiator.js.map +1 -1
- package/dist/orchestrator/loop/core-loop/contracts.d.ts +1 -0
- package/dist/orchestrator/loop/core-loop/contracts.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop/contracts.js.map +1 -1
- package/dist/orchestrator/loop/core-loop/iteration-kernel-knowledge.d.ts +7 -0
- package/dist/orchestrator/loop/core-loop/iteration-kernel-knowledge.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/iteration-kernel-knowledge.js +43 -0
- package/dist/orchestrator/loop/core-loop/iteration-kernel-knowledge.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/iteration-kernel-wait.d.ts +14 -0
- package/dist/orchestrator/loop/core-loop/iteration-kernel-wait.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/iteration-kernel-wait.js +41 -0
- package/dist/orchestrator/loop/core-loop/iteration-kernel-wait.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/iteration-kernel.d.ts +3 -2
- package/dist/orchestrator/loop/core-loop/iteration-kernel.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop/iteration-kernel.js +16 -87
- package/dist/orchestrator/loop/core-loop/iteration-kernel.js.map +1 -1
- package/dist/orchestrator/loop/core-loop/phase-policy.js +5 -5
- package/dist/orchestrator/loop/core-loop/task-cycle-stall.d.ts +9 -0
- package/dist/orchestrator/loop/core-loop/task-cycle-stall.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/task-cycle-stall.js +297 -0
- package/dist/orchestrator/loop/core-loop/task-cycle-stall.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/task-cycle-wait.d.ts +11 -0
- package/dist/orchestrator/loop/core-loop/task-cycle-wait.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/task-cycle-wait.js +176 -0
- package/dist/orchestrator/loop/core-loop/task-cycle-wait.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/task-cycle.d.ts +3 -15
- package/dist/orchestrator/loop/core-loop/task-cycle.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop/task-cycle.js +10 -444
- package/dist/orchestrator/loop/core-loop/task-cycle.js.map +1 -1
- package/dist/orchestrator/loop/core-loop.d.ts +3 -0
- package/dist/orchestrator/loop/core-loop.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop.js +4 -0
- package/dist/orchestrator/loop/core-loop.js.map +1 -1
- package/dist/orchestrator/strategy/portfolio-manager.d.ts +3 -2
- package/dist/orchestrator/strategy/portfolio-manager.d.ts.map +1 -1
- package/dist/orchestrator/strategy/portfolio-manager.js +16 -11
- package/dist/orchestrator/strategy/portfolio-manager.js.map +1 -1
- package/dist/orchestrator/strategy/portfolio-rebalance.d.ts +1 -1
- package/dist/orchestrator/strategy/portfolio-rebalance.d.ts.map +1 -1
- package/dist/orchestrator/strategy/portfolio-rebalance.js +31 -9
- package/dist/orchestrator/strategy/portfolio-rebalance.js.map +1 -1
- package/dist/orchestrator/strategy/strategy-manager-base.d.ts +12 -2
- package/dist/orchestrator/strategy/strategy-manager-base.d.ts.map +1 -1
- package/dist/orchestrator/strategy/strategy-manager-base.js +23 -7
- package/dist/orchestrator/strategy/strategy-manager-base.js.map +1 -1
- package/dist/orchestrator/strategy/strategy-manager.d.ts +1 -11
- package/dist/orchestrator/strategy/strategy-manager.d.ts.map +1 -1
- package/dist/orchestrator/strategy/strategy-manager.js +78 -19
- package/dist/orchestrator/strategy/strategy-manager.js.map +1 -1
- package/dist/platform/dream/dream-consolidator/fs-metrics.d.ts +18 -0
- package/dist/platform/dream/dream-consolidator/fs-metrics.d.ts.map +1 -0
- package/dist/platform/dream/dream-consolidator/fs-metrics.js +130 -0
- package/dist/platform/dream/dream-consolidator/fs-metrics.js.map +1 -0
- package/dist/platform/dream/dream-consolidator.d.ts +4 -14
- package/dist/platform/dream/dream-consolidator.d.ts.map +1 -1
- package/dist/platform/dream/dream-consolidator.js +46 -166
- package/dist/platform/dream/dream-consolidator.js.map +1 -1
- package/dist/platform/dream/dream-soil-sync.d.ts +1 -0
- package/dist/platform/dream/dream-soil-sync.d.ts.map +1 -1
- package/dist/platform/dream/dream-soil-sync.js +8 -1
- package/dist/platform/dream/dream-soil-sync.js.map +1 -1
- package/dist/platform/dream/dream-types.d.ts +5 -0
- package/dist/platform/dream/dream-types.d.ts.map +1 -1
- package/dist/platform/dream/dream-types.js +1 -0
- package/dist/platform/dream/dream-types.js.map +1 -1
- package/dist/platform/dream/playbook-memory.d.ts +4 -4
- package/dist/platform/drive/stall-detector/analysis.d.ts +5 -0
- package/dist/platform/drive/stall-detector/analysis.d.ts.map +1 -0
- package/dist/platform/drive/stall-detector/analysis.js +55 -0
- package/dist/platform/drive/stall-detector/analysis.js.map +1 -0
- package/dist/platform/drive/stall-detector/repetitive.d.ts +3 -0
- package/dist/platform/drive/stall-detector/repetitive.d.ts.map +1 -0
- package/dist/platform/drive/stall-detector/repetitive.js +72 -0
- package/dist/platform/drive/stall-detector/repetitive.js.map +1 -0
- package/dist/platform/drive/stall-detector/thresholds.d.ts +10 -0
- package/dist/platform/drive/stall-detector/thresholds.d.ts.map +1 -0
- package/dist/platform/drive/stall-detector/thresholds.js +61 -0
- package/dist/platform/drive/stall-detector/thresholds.js.map +1 -0
- package/dist/platform/drive/stall-detector.d.ts +2 -20
- package/dist/platform/drive/stall-detector.d.ts.map +1 -1
- package/dist/platform/drive/stall-detector.js +9 -202
- package/dist/platform/drive/stall-detector.js.map +1 -1
- package/dist/platform/knowledge/knowledge-manager-agent-memory.d.ts +55 -0
- package/dist/platform/knowledge/knowledge-manager-agent-memory.d.ts.map +1 -0
- package/dist/platform/knowledge/knowledge-manager-agent-memory.js +232 -0
- package/dist/platform/knowledge/knowledge-manager-agent-memory.js.map +1 -0
- package/dist/platform/knowledge/knowledge-manager-internals.d.ts +10 -0
- package/dist/platform/knowledge/knowledge-manager-internals.d.ts.map +1 -0
- package/dist/platform/knowledge/knowledge-manager-internals.js +43 -0
- package/dist/platform/knowledge/knowledge-manager-internals.js.map +1 -0
- package/dist/platform/knowledge/knowledge-manager-store.d.ts +13 -0
- package/dist/platform/knowledge/knowledge-manager-store.d.ts.map +1 -0
- package/dist/platform/knowledge/knowledge-manager-store.js +67 -0
- package/dist/platform/knowledge/knowledge-manager-store.js.map +1 -0
- package/dist/platform/knowledge/knowledge-manager.d.ts +6 -2
- package/dist/platform/knowledge/knowledge-manager.d.ts.map +1 -1
- package/dist/platform/knowledge/knowledge-manager.js +43 -344
- package/dist/platform/knowledge/knowledge-manager.js.map +1 -1
- package/dist/platform/knowledge/memory/memory-lifecycle-storage.d.ts +4 -0
- package/dist/platform/knowledge/memory/memory-lifecycle-storage.d.ts.map +1 -0
- package/dist/platform/knowledge/memory/memory-lifecycle-storage.js +106 -0
- package/dist/platform/knowledge/memory/memory-lifecycle-storage.js.map +1 -0
- package/dist/platform/knowledge/memory/memory-lifecycle.d.ts.map +1 -1
- package/dist/platform/knowledge/memory/memory-lifecycle.js +6 -112
- package/dist/platform/knowledge/memory/memory-lifecycle.js.map +1 -1
- package/dist/platform/observation/capability-detector/prompts.d.ts +18 -0
- package/dist/platform/observation/capability-detector/prompts.d.ts.map +1 -0
- package/dist/platform/observation/capability-detector/prompts.js +80 -0
- package/dist/platform/observation/capability-detector/prompts.js.map +1 -0
- package/dist/platform/observation/capability-detector/recommendations.d.ts +5 -0
- package/dist/platform/observation/capability-detector/recommendations.d.ts.map +1 -0
- package/dist/platform/observation/capability-detector/recommendations.js +76 -0
- package/dist/platform/observation/capability-detector/recommendations.js.map +1 -0
- package/dist/platform/observation/capability-detector/types.d.ts +112 -0
- package/dist/platform/observation/capability-detector/types.d.ts.map +1 -0
- package/dist/platform/observation/capability-detector/types.js +75 -0
- package/dist/platform/observation/capability-detector/types.js.map +1 -0
- package/dist/platform/observation/capability-detector.d.ts +4 -9
- package/dist/platform/observation/capability-detector.d.ts.map +1 -1
- package/dist/platform/observation/capability-detector.js +12 -212
- package/dist/platform/observation/capability-detector.js.map +1 -1
- package/dist/platform/observation/context-provider/collector.d.ts +13 -0
- package/dist/platform/observation/context-provider/collector.d.ts.map +1 -0
- package/dist/platform/observation/context-provider/collector.js +259 -0
- package/dist/platform/observation/context-provider/collector.js.map +1 -0
- package/dist/platform/observation/context-provider/search-terms.d.ts +2 -0
- package/dist/platform/observation/context-provider/search-terms.d.ts.map +1 -0
- package/dist/platform/observation/context-provider/search-terms.js +24 -0
- package/dist/platform/observation/context-provider/search-terms.js.map +1 -0
- package/dist/platform/observation/context-provider/shared.d.ts +17 -0
- package/dist/platform/observation/context-provider/shared.d.ts.map +1 -0
- package/dist/platform/observation/context-provider/shared.js +87 -0
- package/dist/platform/observation/context-provider/shared.js.map +1 -0
- package/dist/platform/observation/context-provider.d.ts +3 -28
- package/dist/platform/observation/context-provider.d.ts.map +1 -1
- package/dist/platform/observation/context-provider.js +7 -395
- package/dist/platform/observation/context-provider.js.map +1 -1
- package/dist/platform/soil/compiled-memory-projections.d.ts +2 -0
- package/dist/platform/soil/compiled-memory-projections.d.ts.map +1 -1
- package/dist/platform/soil/compiled-memory-projections.js +59 -0
- package/dist/platform/soil/compiled-memory-projections.js.map +1 -1
- package/dist/platform/soil/contracts.d.ts +2 -2
- package/dist/platform/soil/retriever.d.ts +25 -0
- package/dist/platform/soil/retriever.d.ts.map +1 -1
- package/dist/platform/soil/retriever.js +94 -5
- package/dist/platform/soil/retriever.js.map +1 -1
- package/dist/platform/soil/sqlite-repository-helpers.d.ts +80 -0
- package/dist/platform/soil/sqlite-repository-helpers.d.ts.map +1 -0
- package/dist/platform/soil/sqlite-repository-helpers.js +143 -0
- package/dist/platform/soil/sqlite-repository-helpers.js.map +1 -0
- package/dist/platform/soil/sqlite-repository-search.d.ts +8 -0
- package/dist/platform/soil/sqlite-repository-search.d.ts.map +1 -0
- package/dist/platform/soil/sqlite-repository-search.js +367 -0
- package/dist/platform/soil/sqlite-repository-search.js.map +1 -0
- package/dist/platform/soil/sqlite-repository-storage.d.ts +8 -0
- package/dist/platform/soil/sqlite-repository-storage.d.ts.map +1 -0
- package/dist/platform/soil/sqlite-repository-storage.js +278 -0
- package/dist/platform/soil/sqlite-repository-storage.js.map +1 -0
- package/dist/platform/soil/sqlite-repository.d.ts +1 -4
- package/dist/platform/soil/sqlite-repository.d.ts.map +1 -1
- package/dist/platform/soil/sqlite-repository.js +26 -820
- package/dist/platform/soil/sqlite-repository.js.map +1 -1
- package/dist/runtime/daemon/index.d.ts +1 -1
- package/dist/runtime/daemon/index.d.ts.map +1 -1
- package/dist/runtime/daemon/index.js +1 -1
- package/dist/runtime/daemon/index.js.map +1 -1
- package/dist/runtime/daemon/maintenance.d.ts +2 -10
- package/dist/runtime/daemon/maintenance.d.ts.map +1 -1
- package/dist/runtime/daemon/maintenance.js +14 -45
- package/dist/runtime/daemon/maintenance.js.map +1 -1
- package/dist/runtime/daemon/runner-bootstrap.d.ts +25 -0
- package/dist/runtime/daemon/runner-bootstrap.d.ts.map +1 -0
- package/dist/runtime/daemon/runner-bootstrap.js +77 -0
- package/dist/runtime/daemon/runner-bootstrap.js.map +1 -0
- package/dist/runtime/daemon/runner-commands.d.ts +7 -7
- package/dist/runtime/daemon/runner-commands.d.ts.map +1 -1
- package/dist/runtime/daemon/runner-commands.js +35 -20
- package/dist/runtime/daemon/runner-commands.js.map +1 -1
- package/dist/runtime/daemon/runner-goal-cycle.d.ts.map +1 -1
- package/dist/runtime/daemon/runner-goal-cycle.js +3 -5
- package/dist/runtime/daemon/runner-goal-cycle.js.map +1 -1
- package/dist/runtime/daemon/runner-resident-curiosity.d.ts +12 -0
- package/dist/runtime/daemon/runner-resident-curiosity.d.ts.map +1 -0
- package/dist/runtime/daemon/runner-resident-curiosity.js +155 -0
- package/dist/runtime/daemon/runner-resident-curiosity.js.map +1 -0
- package/dist/runtime/daemon/runner-resident-dream.d.ts +20 -0
- package/dist/runtime/daemon/runner-resident-dream.d.ts.map +1 -0
- package/dist/runtime/daemon/runner-resident-dream.js +148 -0
- package/dist/runtime/daemon/runner-resident-dream.js.map +1 -0
- package/dist/runtime/daemon/runner-resident-proactive.d.ts +4 -0
- package/dist/runtime/daemon/runner-resident-proactive.d.ts.map +1 -0
- package/dist/runtime/daemon/runner-resident-proactive.js +113 -0
- package/dist/runtime/daemon/runner-resident-proactive.js.map +1 -0
- package/dist/runtime/daemon/runner-resident-shared.d.ts +40 -0
- package/dist/runtime/daemon/runner-resident-shared.d.ts.map +1 -0
- package/dist/runtime/daemon/runner-resident-shared.js +101 -0
- package/dist/runtime/daemon/runner-resident-shared.js.map +1 -0
- package/dist/runtime/daemon/runner-resident.d.ts +4 -68
- package/dist/runtime/daemon/runner-resident.d.ts.map +1 -1
- package/dist/runtime/daemon/runner-resident.js +4 -506
- package/dist/runtime/daemon/runner-resident.js.map +1 -1
- package/dist/runtime/daemon/runner-runtime.d.ts +12 -0
- package/dist/runtime/daemon/runner-runtime.d.ts.map +1 -0
- package/dist/runtime/daemon/runner-runtime.js +43 -0
- package/dist/runtime/daemon/runner-runtime.js.map +1 -0
- package/dist/runtime/daemon/runner-startup.js +3 -4
- package/dist/runtime/daemon/runner-startup.js.map +1 -1
- package/dist/runtime/daemon/runner.d.ts +10 -23
- package/dist/runtime/daemon/runner.d.ts.map +1 -1
- package/dist/runtime/daemon/runner.js +27 -110
- package/dist/runtime/daemon/runner.js.map +1 -1
- package/dist/runtime/daemon/wait-deadline-resolver.d.ts +5 -2
- package/dist/runtime/daemon/wait-deadline-resolver.d.ts.map +1 -1
- package/dist/runtime/daemon/wait-deadline-resolver.js +55 -35
- package/dist/runtime/daemon/wait-deadline-resolver.js.map +1 -1
- package/dist/runtime/event/dispatcher.d.ts +0 -2
- package/dist/runtime/event/dispatcher.d.ts.map +1 -1
- package/dist/runtime/event/dispatcher.js +0 -4
- package/dist/runtime/event/dispatcher.js.map +1 -1
- package/dist/runtime/executor/goal-worker.d.ts +2 -1
- package/dist/runtime/executor/goal-worker.d.ts.map +1 -1
- package/dist/runtime/executor/goal-worker.js +2 -1
- package/dist/runtime/executor/goal-worker.js.map +1 -1
- package/dist/runtime/executor/loop-supervisor.d.ts +3 -0
- package/dist/runtime/executor/loop-supervisor.d.ts.map +1 -1
- package/dist/runtime/executor/loop-supervisor.js +37 -11
- package/dist/runtime/executor/loop-supervisor.js.map +1 -1
- package/dist/runtime/schedule/engine-cron-reflection.d.ts +31 -0
- package/dist/runtime/schedule/engine-cron-reflection.d.ts.map +1 -0
- package/dist/runtime/schedule/engine-cron-reflection.js +229 -0
- package/dist/runtime/schedule/engine-cron-reflection.js.map +1 -0
- package/dist/runtime/schedule/engine-execution.d.ts +47 -0
- package/dist/runtime/schedule/engine-execution.d.ts.map +1 -0
- package/dist/runtime/schedule/engine-execution.js +424 -0
- package/dist/runtime/schedule/engine-execution.js.map +1 -0
- package/dist/runtime/schedule/engine-heartbeat.d.ts +5 -0
- package/dist/runtime/schedule/engine-heartbeat.d.ts.map +1 -0
- package/dist/runtime/schedule/engine-heartbeat.js +104 -0
- package/dist/runtime/schedule/engine-heartbeat.js.map +1 -0
- package/dist/runtime/schedule/engine-layers.d.ts +2 -0
- package/dist/runtime/schedule/engine-layers.d.ts.map +1 -1
- package/dist/runtime/schedule/engine-layers.js +12 -228
- package/dist/runtime/schedule/engine-layers.js.map +1 -1
- package/dist/runtime/schedule/engine-mutations.d.ts +37 -0
- package/dist/runtime/schedule/engine-mutations.d.ts.map +1 -0
- package/dist/runtime/schedule/engine-mutations.js +263 -0
- package/dist/runtime/schedule/engine-mutations.js.map +1 -0
- package/dist/runtime/schedule/engine.d.ts +11 -38
- package/dist/runtime/schedule/engine.d.ts.map +1 -1
- package/dist/runtime/schedule/engine.js +65 -810
- package/dist/runtime/schedule/engine.js.map +1 -1
- package/dist/runtime/schedule/history.d.ts +16 -0
- package/dist/runtime/schedule/history.d.ts.map +1 -1
- package/dist/runtime/schedule/history.js +8 -0
- package/dist/runtime/schedule/history.js.map +1 -1
- package/dist/runtime/schedule/index.d.ts +1 -0
- package/dist/runtime/schedule/index.d.ts.map +1 -1
- package/dist/runtime/schedule/index.js +1 -0
- package/dist/runtime/schedule/index.js.map +1 -1
- package/dist/runtime/schedule/legacy-cron-migration.d.ts +9 -0
- package/dist/runtime/schedule/legacy-cron-migration.d.ts.map +1 -0
- package/dist/runtime/schedule/legacy-cron-migration.js +89 -0
- package/dist/runtime/schedule/legacy-cron-migration.js.map +1 -0
- package/dist/runtime/schedule/wait-projection.d.ts +6 -0
- package/dist/runtime/schedule/wait-projection.d.ts.map +1 -0
- package/dist/runtime/schedule/wait-projection.js +102 -0
- package/dist/runtime/schedule/wait-projection.js.map +1 -0
- package/dist/runtime/session-registry/registry-helpers.d.ts +34 -0
- package/dist/runtime/session-registry/registry-helpers.d.ts.map +1 -0
- package/dist/runtime/session-registry/registry-helpers.js +241 -0
- package/dist/runtime/session-registry/registry-helpers.js.map +1 -0
- package/dist/runtime/session-registry/registry.d.ts.map +1 -1
- package/dist/runtime/session-registry/registry.js +14 -243
- package/dist/runtime/session-registry/registry.js.map +1 -1
- package/dist/runtime/session-registry/types.d.ts +36 -36
- package/dist/runtime/store/runtime-operation-schemas.d.ts +36 -36
- package/dist/runtime/store/runtime-schemas.d.ts +2 -2
- package/dist/runtime/types/daemon.d.ts +10 -0
- package/dist/runtime/types/daemon.d.ts.map +1 -1
- package/dist/runtime/types/daemon.js +2 -0
- package/dist/runtime/types/daemon.js.map +1 -1
- package/dist/runtime/types/schedule.d.ts +65 -0
- package/dist/runtime/types/schedule.d.ts.map +1 -1
- package/dist/runtime/types/schedule.js +5 -0
- package/dist/runtime/types/schedule.js.map +1 -1
- package/dist/tools/builtin/exports.d.ts +1 -0
- package/dist/tools/builtin/exports.d.ts.map +1 -1
- package/dist/tools/builtin/exports.js +1 -0
- package/dist/tools/builtin/exports.js.map +1 -1
- package/dist/tools/builtin/factory.d.ts +2 -0
- package/dist/tools/builtin/factory.d.ts.map +1 -1
- package/dist/tools/builtin/factory.js +8 -1
- package/dist/tools/builtin/factory.js.map +1 -1
- package/dist/tools/fs/FileValidationTool/protected-path-policy.d.ts +1 -0
- package/dist/tools/fs/FileValidationTool/protected-path-policy.d.ts.map +1 -1
- package/dist/tools/fs/FileValidationTool/protected-path-policy.js +17 -4
- package/dist/tools/fs/FileValidationTool/protected-path-policy.js.map +1 -1
- package/dist/tools/fs/ListDirTool/ListDirTool.js +1 -1
- package/dist/tools/fs/ListDirTool/ListDirTool.js.map +1 -1
- package/dist/tools/kaggle/paths.d.ts.map +1 -1
- package/dist/tools/kaggle/paths.js +6 -0
- package/dist/tools/kaggle/paths.js.map +1 -1
- package/dist/tools/query/CodeSearchRepairTool/CodeSearchRepairTool.d.ts.map +1 -1
- package/dist/tools/query/CodeSearchRepairTool/CodeSearchRepairTool.js +23 -2
- package/dist/tools/query/CodeSearchRepairTool/CodeSearchRepairTool.js.map +1 -1
- package/dist/tools/query/CodeSearchTool/CodeSearchTool.d.ts.map +1 -1
- package/dist/tools/query/CodeSearchTool/CodeSearchTool.js +24 -2
- package/dist/tools/query/CodeSearchTool/CodeSearchTool.js.map +1 -1
- package/dist/tools/query/SoilQueryTool/SoilQueryTool.d.ts.map +1 -1
- package/dist/tools/query/SoilQueryTool/SoilQueryTool.js +43 -9
- package/dist/tools/query/SoilQueryTool/SoilQueryTool.js.map +1 -1
- package/dist/tools/query/code-search-root.d.ts +8 -0
- package/dist/tools/query/code-search-root.d.ts.map +1 -0
- package/dist/tools/query/code-search-root.js +41 -0
- package/dist/tools/query/code-search-root.js.map +1 -0
- package/dist/tools/query/runtime-session-tools.d.ts +560 -0
- package/dist/tools/query/runtime-session-tools.d.ts.map +1 -0
- package/dist/tools/query/runtime-session-tools.js +1015 -0
- package/dist/tools/query/runtime-session-tools.js.map +1 -0
- package/dist/tools/runtime/LongRunningRuntimeTools.d.ts +821 -0
- package/dist/tools/runtime/LongRunningRuntimeTools.d.ts.map +1 -0
- package/dist/tools/runtime/LongRunningRuntimeTools.js +845 -0
- package/dist/tools/runtime/LongRunningRuntimeTools.js.map +1 -0
- package/dist/tools/system/ProcessSessionTool/ProcessSessionTool.d.ts +1 -0
- package/dist/tools/system/ProcessSessionTool/ProcessSessionTool.d.ts.map +1 -1
- package/dist/tools/system/ProcessSessionTool/ProcessSessionTool.js +11 -0
- package/dist/tools/system/ProcessSessionTool/ProcessSessionTool.js.map +1 -1
- package/dist/tools/system/ShellTool/ShellTool.d.ts.map +1 -1
- package/dist/tools/system/ShellTool/ShellTool.js +2 -1
- package/dist/tools/system/ShellTool/ShellTool.js.map +1 -1
- package/dist/tools/types.d.ts +2 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/base/types/cron.d.ts +0 -2
- package/dist/base/types/cron.d.ts.map +0 -1
- package/dist/base/types/cron.js +0 -3
- package/dist/base/types/cron.js.map +0 -1
- package/dist/runtime/cron-scheduler.d.ts +0 -13
- package/dist/runtime/cron-scheduler.d.ts.map +0 -1
- package/dist/runtime/cron-scheduler.js +0 -90
- package/dist/runtime/cron-scheduler.js.map +0 -1
- package/dist/runtime/types/cron.d.ts +0 -59
- package/dist/runtime/types/cron.d.ts.map +0 -1
- package/dist/runtime/types/cron.js +0 -13
- package/dist/runtime/types/cron.js.map +0 -1
|
@@ -2,180 +2,36 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Central coordinator for 1-shot chat execution (Tier 1).
|
|
4
4
|
// Bypasses TaskLifecycle — calls adapter.execute() directly.
|
|
5
|
-
import { execFile } from "node:child_process";
|
|
6
|
-
import * as fsp from "node:fs/promises";
|
|
7
|
-
import * as path from "node:path";
|
|
8
5
|
import { getPulseedDirPath } from "../../base/utils/paths.js";
|
|
9
6
|
import { getSelfIdentityResponseForBaseDir } from "../../base/config/identity-loader.js";
|
|
10
|
-
import { loadProviderConfig } from "../../base/llm/provider-config.js";
|
|
11
|
-
import { TaskSchema } from "../../base/types/task.js";
|
|
12
7
|
import { ChatHistory } from "./chat-history.js";
|
|
13
8
|
import { ChatSessionCatalog, ChatSessionSelectorError, } from "./chat-session-store.js";
|
|
14
9
|
import { buildChatContext, resolveGitRoot } from "../../platform/observation/context-provider.js";
|
|
15
10
|
import { buildChatAgentLoopSystemPrompt, buildStaticSystemPrompt, createChatGroundingGateway } from "./grounding.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { resolveExecutionPolicy, summarizeExecutionPolicy, withExecutionPolicyOverrides, } from "../../orchestrator/execution/agent-loop/execution-policy.js";
|
|
23
|
-
import { buildStandaloneIngressMessage, createIngressRouter, } from "./ingress-router.js";
|
|
24
|
-
import { createRuntimeSessionRegistry } from "../../runtime/session-registry/index.js";
|
|
11
|
+
import { createIngressRouter, } from "./ingress-router.js";
|
|
12
|
+
import { classifyInterruptRedirect, collectGitDiffArtifact, previewActivityText } from "./chat-runner-support.js";
|
|
13
|
+
import { COMMAND_HELP, ChatRunnerCommandHandler, } from "./chat-runner-commands.js";
|
|
14
|
+
import { ChatRunnerEventBridge } from "./chat-runner-event-bridge.js";
|
|
15
|
+
import { buildRuntimeControlContextFromIngress, buildStandaloneIngressMessageFromContext, formatRoute, getRouteCapabilities, loadedSessionToChatSession, resolveChatResumeSelector, } from "./chat-runner-runtime.js";
|
|
16
|
+
import { executeAdapterRoute, executeAgentLoopRoute, executeRuntimeControlRoute, executeToolLoopRoute, resolveSessionExecutionPolicy, } from "./chat-runner-routes.js";
|
|
25
17
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
// ─── Command help text ───
|
|
32
|
-
const COMMAND_HELP = `Available commands:
|
|
33
|
-
Session
|
|
34
|
-
/help Show this help message
|
|
35
|
-
/clear Clear conversation history
|
|
36
|
-
/sessions List prior chat sessions
|
|
37
|
-
/history [id|title] Show saved chat history
|
|
38
|
-
/title <title> Rename the current session
|
|
39
|
-
/resume [id|title] Resume native agentloop state for the current or selected session
|
|
40
|
-
/cleanup [--dry-run] Clean up stale chat sessions
|
|
41
|
-
/compact Summarize older chat turns and keep the latest turns
|
|
42
|
-
/context Show active working context and session assumptions
|
|
43
|
-
/exit Exit chat mode
|
|
44
|
-
|
|
45
|
-
Goals and tasks
|
|
46
|
-
/status [goal-id] Show active goal status, or one goal when an id is provided
|
|
47
|
-
/goals List goals
|
|
48
|
-
/tasks [goal-id] List tasks for a goal; uses the only active goal when unambiguous
|
|
49
|
-
/task <task-id> [goal-id]
|
|
50
|
-
Show one task; searches goals when no goal id is provided
|
|
51
|
-
/track Promote session to Tier 2 goal pursuit (not yet implemented)
|
|
52
|
-
/tend Generate a goal from chat history and start autonomous daemon execution
|
|
53
|
-
|
|
54
|
-
Configuration
|
|
55
|
-
/config Show provider configuration with secrets masked
|
|
56
|
-
/model Show the active provider/model/adapter
|
|
57
|
-
/permissions [args] Show or update session execution policy
|
|
58
|
-
/plugins List installed plugins when plugin metadata is available
|
|
59
|
-
/usage [scope] Show usage summary (session, goal <id>, daemon <goal-id>, schedule [7d|24h|2w])
|
|
60
|
-
|
|
61
|
-
Review and branching
|
|
62
|
-
/review Show current diff summary and verification context
|
|
63
|
-
/fork [title] Fork the current chat session into a new session
|
|
64
|
-
/undo Remove the latest chat turn from session history
|
|
65
|
-
|
|
66
|
-
Deferred
|
|
67
|
-
/retry is intentionally not supported yet.`;
|
|
68
|
-
// ─── Helpers ───
|
|
69
|
-
function checkGitChanges(cwd) {
|
|
70
|
-
return new Promise((resolve) => {
|
|
71
|
-
execFile("git", ["diff", "HEAD", "--stat"], { cwd, timeout: 5_000 }, (err, stdout, stderr) => {
|
|
72
|
-
resolve(err ? null : (stdout + stderr).trim());
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
function runGit(cwd, args, timeout = 5_000) {
|
|
77
|
-
return new Promise((resolve) => {
|
|
78
|
-
execFile("git", args, { cwd, timeout }, (err, stdout, stderr) => {
|
|
79
|
-
if (err) {
|
|
80
|
-
resolve(null);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
resolve((stdout + stderr).trim());
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
function parseGitLines(output) {
|
|
88
|
-
return output ? output.split("\n").map((line) => line.trim()).filter(Boolean) : [];
|
|
89
|
-
}
|
|
90
|
-
async function buildUntrackedFilePatch(cwd, relativePath) {
|
|
91
|
-
const absolutePath = path.resolve(cwd, relativePath);
|
|
92
|
-
const relativeFromCwd = path.relative(cwd, absolutePath);
|
|
93
|
-
if (relativeFromCwd.startsWith("..") || path.isAbsolute(relativeFromCwd)) {
|
|
94
|
-
return `diff --git a/${relativePath} b/${relativePath}\nnew file skipped: path outside workspace`;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const stat = await fsp.stat(absolutePath);
|
|
98
|
-
if (!stat.isFile()) {
|
|
99
|
-
return `diff --git a/${relativePath} b/${relativePath}\nnew file skipped: not a regular file`;
|
|
100
|
-
}
|
|
101
|
-
if (stat.size > 100_000) {
|
|
102
|
-
return `diff --git a/${relativePath} b/${relativePath}\nnew file skipped: ${stat.size} bytes`;
|
|
103
|
-
}
|
|
104
|
-
const content = await fsp.readFile(absolutePath, "utf-8");
|
|
105
|
-
const lines = content.split("\n");
|
|
106
|
-
const body = lines.map((line) => `+${line}`).join("\n");
|
|
107
|
-
return [
|
|
108
|
-
`diff --git a/${relativePath} b/${relativePath}`,
|
|
109
|
-
"new file mode 100644",
|
|
110
|
-
"--- /dev/null",
|
|
111
|
-
`+++ b/${relativePath}`,
|
|
112
|
-
`@@ -0,0 +1,${lines.length} @@`,
|
|
113
|
-
body,
|
|
114
|
-
].join("\n");
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return `diff --git a/${relativePath} b/${relativePath}\nnew file skipped: unreadable`;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async function collectGitDiffArtifact(cwd) {
|
|
121
|
-
const trackedStat = await runGit(cwd, ["diff", "HEAD", "--stat"]);
|
|
122
|
-
const untrackedFiles = parseGitLines(await runGit(cwd, ["ls-files", "--others", "--exclude-standard"]));
|
|
123
|
-
if (!trackedStat && untrackedFiles.length === 0)
|
|
18
|
+
function normalizePinnedReplyTarget(replyTarget) {
|
|
19
|
+
if (!replyTarget)
|
|
20
|
+
return null;
|
|
21
|
+
const channel = replyTarget.channel ?? replyTarget.surface;
|
|
22
|
+
if (!channel)
|
|
124
23
|
return null;
|
|
125
|
-
const trackedNameStatus = await runGit(cwd, ["diff", "HEAD", "--name-status"]) ?? "";
|
|
126
|
-
const trackedPatch = await runGit(cwd, ["diff", "HEAD", "--patch", "--unified=3"], 10_000) ?? "";
|
|
127
|
-
const untrackedPatchParts = await Promise.all(untrackedFiles.slice(0, 10).map((file) => buildUntrackedFilePatch(cwd, file)));
|
|
128
|
-
if (untrackedFiles.length > 10) {
|
|
129
|
-
untrackedPatchParts.push(`... ${untrackedFiles.length - 10} additional untracked file(s) omitted`);
|
|
130
|
-
}
|
|
131
|
-
const stat = [
|
|
132
|
-
trackedStat,
|
|
133
|
-
untrackedFiles.length > 0
|
|
134
|
-
? ["Untracked files:", ...untrackedFiles.map((file) => ` ${file}`)].join("\n")
|
|
135
|
-
: "",
|
|
136
|
-
].filter(Boolean).join("\n");
|
|
137
|
-
const nameStatus = [
|
|
138
|
-
trackedNameStatus,
|
|
139
|
-
...untrackedFiles.map((file) => `A\t${file}`),
|
|
140
|
-
].filter(Boolean).join("\n");
|
|
141
|
-
const patch = [trackedPatch, ...untrackedPatchParts].filter(Boolean).join("\n");
|
|
142
|
-
const patchLines = patch.split("\n");
|
|
143
|
-
const truncated = patchLines.length > DIFF_ARTIFACT_MAX_LINES;
|
|
144
24
|
return {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
25
|
+
channel,
|
|
26
|
+
target_id: replyTarget.conversation_id ?? replyTarget.identity_key ?? replyTarget.response_channel ?? null,
|
|
27
|
+
thread_id: replyTarget.message_id ?? null,
|
|
28
|
+
metadata: {
|
|
29
|
+
...replyTarget,
|
|
30
|
+
...(replyTarget.metadata ?? {}),
|
|
31
|
+
},
|
|
149
32
|
};
|
|
150
33
|
}
|
|
151
|
-
|
|
152
|
-
const normalized = value.replace(/\s+/g, " ").trim();
|
|
153
|
-
return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}...` : normalized;
|
|
154
|
-
}
|
|
155
|
-
function classifyInterruptRedirect(input) {
|
|
156
|
-
const normalized = input.trim().toLowerCase();
|
|
157
|
-
if (/\b(background|bg)\b|バックグラウンド|裏で|裏側|continue.*background/.test(normalized)) {
|
|
158
|
-
return "background";
|
|
159
|
-
}
|
|
160
|
-
if (/\b(review|read.?only|readonly)\b|レビュー|確認だけ|読むだけ/.test(normalized)) {
|
|
161
|
-
return "review";
|
|
162
|
-
}
|
|
163
|
-
if (/\b(diff|changes?|patch)\b|差分|変更.*見|変更内容/.test(normalized)) {
|
|
164
|
-
return "diff";
|
|
165
|
-
}
|
|
166
|
-
if (/\b(stop|pause|summary|summarize|interrupt)\b|止め|停止|中断|一旦|要約/.test(normalized)) {
|
|
167
|
-
return "summary";
|
|
168
|
-
}
|
|
169
|
-
return "redirect";
|
|
170
|
-
}
|
|
171
|
-
function formatToolActivity(action, toolName, detail) {
|
|
172
|
-
const preview = detail ? previewActivityText(detail) : "";
|
|
173
|
-
return preview ? `${action} tool: ${toolName} - ${preview}` : `${action} tool: ${toolName}`;
|
|
174
|
-
}
|
|
175
|
-
function formatIntentInput(input, maxChars = 96) {
|
|
176
|
-
const normalized = input.replace(/\s+/g, " ").trim();
|
|
177
|
-
return normalized.length > maxChars ? `${normalized.slice(0, maxChars - 3)}...` : normalized;
|
|
178
|
-
}
|
|
34
|
+
const standaloneIngressRouter = createIngressRouter();
|
|
179
35
|
function resolveSelfIdentityResponse(input, baseDir) {
|
|
180
36
|
const normalized = input.trim().toLowerCase().replace(/\s+/g, "");
|
|
181
37
|
if (!normalized)
|
|
@@ -190,45 +46,52 @@ function resolveSelfIdentityResponse(input, baseDir) {
|
|
|
190
46
|
return null;
|
|
191
47
|
return getSelfIdentityResponseForBaseDir(baseDir, isEnglishIdentityQuestion ? "en" : "ja");
|
|
192
48
|
}
|
|
193
|
-
// ─── ChatRunner ───
|
|
194
49
|
export class ChatRunner {
|
|
195
50
|
deps;
|
|
196
51
|
groundingGateway;
|
|
52
|
+
eventBridge;
|
|
53
|
+
commandHandler;
|
|
197
54
|
history = null;
|
|
198
55
|
sessionCwd = null;
|
|
199
|
-
/** True when startSession() has been called — enables session persistence across execute() calls. */
|
|
200
56
|
sessionActive = false;
|
|
201
|
-
/** Deferred tools activated by ToolSearch results — included in tool definitions for subsequent turns. */
|
|
202
57
|
activatedTools = new Set();
|
|
203
|
-
/** Cached static system prompt — reused across turns; dynamic context is rebuilt each turn. */
|
|
204
58
|
cachedStaticSystemPrompt = null;
|
|
205
|
-
/** Pending /tend state awaiting user confirmation (Y/n). */
|
|
206
59
|
pendingTend = null;
|
|
207
|
-
/** Active EventSubscriber instances keyed by goalId. */
|
|
208
60
|
activeSubscribers = new Map();
|
|
209
|
-
/**
|
|
210
|
-
* Callback invoked when a /tend daemon notification arrives.
|
|
211
|
-
* Can be set after construction (e.g. from a React component via useEffect).
|
|
212
|
-
*/
|
|
213
61
|
onNotification = undefined;
|
|
214
62
|
onEvent = undefined;
|
|
215
63
|
nativeAgentLoopStatePath = null;
|
|
216
64
|
runtimeControlContext = null;
|
|
217
65
|
sessionExecutionPolicy = null;
|
|
218
66
|
lastSelectedRoute = null;
|
|
219
|
-
activeTurn = null;
|
|
220
67
|
constructor(deps) {
|
|
221
68
|
this.deps = deps;
|
|
222
69
|
this.groundingGateway = createChatGroundingGateway({
|
|
223
70
|
stateManager: deps.stateManager,
|
|
224
71
|
pluginLoader: deps.pluginLoader,
|
|
225
72
|
});
|
|
73
|
+
this.eventBridge = new ChatRunnerEventBridge(() => this.onEvent ?? this.deps.onEvent);
|
|
74
|
+
this.commandHandler = new ChatRunnerCommandHandler({
|
|
75
|
+
deps: this.deps,
|
|
76
|
+
onNotification: this.onNotification,
|
|
77
|
+
getHistory: () => this.history,
|
|
78
|
+
setHistory: (history) => { this.history = history; },
|
|
79
|
+
getSessionCwd: () => this.sessionCwd,
|
|
80
|
+
setSessionCwd: (cwd) => { this.sessionCwd = cwd; },
|
|
81
|
+
setSessionActive: (active) => { this.sessionActive = active; },
|
|
82
|
+
getNativeAgentLoopStatePath: () => this.nativeAgentLoopStatePath,
|
|
83
|
+
setNativeAgentLoopStatePath: (path) => { this.nativeAgentLoopStatePath = path; },
|
|
84
|
+
getRuntimeControlContext: () => this.runtimeControlContext,
|
|
85
|
+
getPendingTend: () => this.pendingTend,
|
|
86
|
+
setPendingTend: (value) => { this.pendingTend = value; },
|
|
87
|
+
getLastSelectedRoute: () => this.lastSelectedRoute,
|
|
88
|
+
getSessionExecutionPolicy: () => this.getSessionExecutionPolicy(),
|
|
89
|
+
emitEvent: (event) => this.eventBridge.emitEvent(event),
|
|
90
|
+
getActiveSubscribers: () => this.activeSubscribers,
|
|
91
|
+
setSessionExecutionPolicy: (policy) => { this.sessionExecutionPolicy = policy; },
|
|
92
|
+
resetSessionExecutionPolicy: () => { this.sessionExecutionPolicy = null; },
|
|
93
|
+
});
|
|
226
94
|
}
|
|
227
|
-
/**
|
|
228
|
-
* Initialize a persistent session for interactive (multi-turn) mode.
|
|
229
|
-
* Must be called before the first execute() to share history across turns.
|
|
230
|
-
* If not called, execute() auto-creates a new session per call (Phase 1a behavior).
|
|
231
|
-
*/
|
|
232
95
|
startSession(cwd) {
|
|
233
96
|
const gitRoot = resolveGitRoot(cwd);
|
|
234
97
|
const sessionId = crypto.randomUUID();
|
|
@@ -236,13 +99,13 @@ export class ChatRunner {
|
|
|
236
99
|
this.sessionCwd = gitRoot;
|
|
237
100
|
this.sessionActive = true;
|
|
238
101
|
this.nativeAgentLoopStatePath = `chat/agentloop/${sessionId}.state.json`;
|
|
239
|
-
this.history.
|
|
102
|
+
this.history.resetAgentLoopState(this.nativeAgentLoopStatePath);
|
|
240
103
|
this.sessionExecutionPolicy = null;
|
|
241
104
|
}
|
|
242
105
|
startSessionFromLoadedSession(session) {
|
|
243
|
-
const chatSession =
|
|
106
|
+
const chatSession = loadedSessionToChatSession(session);
|
|
244
107
|
this.history = ChatHistory.fromSession(this.deps.stateManager, chatSession);
|
|
245
|
-
this.sessionCwd = session.cwd;
|
|
108
|
+
this.sessionCwd = resolveGitRoot(session.cwd);
|
|
246
109
|
this.sessionActive = true;
|
|
247
110
|
this.nativeAgentLoopStatePath = session.agentLoopStatePath ?? `chat/agentloop/${session.id}.state.json`;
|
|
248
111
|
this.history.setAgentLoopStatePath(this.nativeAgentLoopStatePath);
|
|
@@ -255,17 +118,17 @@ export class ChatRunner {
|
|
|
255
118
|
return this.history?.getMessages() ?? [];
|
|
256
119
|
}
|
|
257
120
|
hasActiveTurn() {
|
|
258
|
-
return this.
|
|
121
|
+
return this.eventBridge.hasActiveTurn();
|
|
259
122
|
}
|
|
260
123
|
async interruptAndRedirect(input, cwd, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
261
|
-
const activeTurn = this.
|
|
124
|
+
const activeTurn = this.eventBridge.getActiveTurn();
|
|
262
125
|
if (!activeTurn) {
|
|
263
126
|
return this.execute(input, cwd, timeoutMs);
|
|
264
127
|
}
|
|
265
128
|
const start = Date.now();
|
|
266
129
|
const redirect = classifyInterruptRedirect(input);
|
|
267
130
|
if (redirect === "background") {
|
|
268
|
-
return this.emitEphemeralAssistantResult(input, [
|
|
131
|
+
return this.eventBridge.emitEphemeralAssistantResult(input, [
|
|
269
132
|
"Continuing this same turn in the background is not available yet.",
|
|
270
133
|
"",
|
|
271
134
|
"The active turn is still running in the foreground.",
|
|
@@ -276,20 +139,17 @@ export class ChatRunner {
|
|
|
276
139
|
if (!activeTurn.abortController.signal.aborted) {
|
|
277
140
|
activeTurn.abortController.abort();
|
|
278
141
|
}
|
|
279
|
-
this.emitCheckpoint("Interrupt requested", `Redirect: ${previewActivityText(input, 120)}`, activeTurn.context, "interrupt");
|
|
280
|
-
const stopped = await this.waitForActiveTurn(activeTurn, 2_000);
|
|
142
|
+
this.eventBridge.emitCheckpoint("Interrupt requested", `Redirect: ${previewActivityText(input, 120)}`, activeTurn.context, "interrupt");
|
|
143
|
+
const stopped = await this.eventBridge.waitForActiveTurn(activeTurn, 2_000);
|
|
281
144
|
if (!stopped) {
|
|
282
|
-
return this.emitEphemeralAssistantResult(input, "Interrupt requested. The active turn will stop at the next safe point.", false, start);
|
|
283
|
-
}
|
|
284
|
-
if (redirect === "redirect") {
|
|
285
|
-
return this.execute(input, cwd, timeoutMs);
|
|
145
|
+
return this.eventBridge.emitEphemeralAssistantResult(input, "Interrupt requested. The active turn will stop at the next safe point.", false, start);
|
|
286
146
|
}
|
|
287
147
|
let output;
|
|
288
148
|
if (redirect === "diff") {
|
|
289
149
|
const diff = await collectGitDiffArtifact(activeTurn.cwd);
|
|
290
150
|
if (diff) {
|
|
291
|
-
const context = this.createEventContext();
|
|
292
|
-
this.emitDiffArtifact(diff, context);
|
|
151
|
+
const context = this.eventBridge.createEventContext();
|
|
152
|
+
this.eventBridge.emitDiffArtifact(diff, context);
|
|
293
153
|
output = "Interrupted the active turn. Current diff is shown above.";
|
|
294
154
|
}
|
|
295
155
|
else {
|
|
@@ -297,8 +157,8 @@ export class ChatRunner {
|
|
|
297
157
|
}
|
|
298
158
|
}
|
|
299
159
|
else if (redirect === "review") {
|
|
300
|
-
const review = await this.
|
|
301
|
-
output = `Interrupted the active turn and switched to review-only mode.\n\n${review.
|
|
160
|
+
const review = await this.commandHandler.handleCommand("/review", activeTurn.cwd);
|
|
161
|
+
output = `Interrupted the active turn and switched to review-only mode.\n\n${review?.output ?? "Review unavailable."}`;
|
|
302
162
|
}
|
|
303
163
|
else {
|
|
304
164
|
output = [
|
|
@@ -314,7 +174,7 @@ export class ChatRunner {
|
|
|
314
174
|
"- Ask to show diff or switch to review if files may have changed.",
|
|
315
175
|
].join("\n");
|
|
316
176
|
}
|
|
317
|
-
return this.emitEphemeralAssistantResult(input, output, true, start);
|
|
177
|
+
return this.eventBridge.emitEphemeralAssistantResult(input, output, true, start);
|
|
318
178
|
}
|
|
319
179
|
setRuntimeControlContext(context) {
|
|
320
180
|
this.runtimeControlContext = context;
|
|
@@ -323,2499 +183,296 @@ export class ChatRunner {
|
|
|
323
183
|
if (!selectedRoute) {
|
|
324
184
|
throw new Error("executeIngressMessage requires selectedRoute; use CrossPlatformChatSessionManager for ingress route selection.");
|
|
325
185
|
}
|
|
326
|
-
const runtimeControlContext =
|
|
186
|
+
const runtimeControlContext = buildRuntimeControlContextFromIngress(ingress, this.runtimeControlContext, this.deps);
|
|
327
187
|
return this.execute(ingress.text, cwd, timeoutMs, {
|
|
328
188
|
selectedRoute,
|
|
329
189
|
runtimeControlContext,
|
|
330
190
|
goalId: ingress.goal_id,
|
|
331
191
|
});
|
|
332
192
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
hasRuntimeControlService: this.deps.runtimeControlService !== undefined,
|
|
345
|
-
hasDaemonTend: this.deps.llmClient !== undefined
|
|
346
|
-
&& this.deps.goalNegotiator !== undefined
|
|
347
|
-
&& this.deps.daemonClient !== undefined,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
buildStandaloneIngressMessage(input, runtimeControlContext) {
|
|
351
|
-
const channel = runtimeControlContext?.replyTarget?.surface === "tui"
|
|
352
|
-
? "tui"
|
|
353
|
-
: runtimeControlContext?.replyTarget?.surface === "cli"
|
|
354
|
-
? "cli"
|
|
355
|
-
: runtimeControlContext?.replyTarget?.surface === "gateway"
|
|
356
|
-
? "plugin_gateway"
|
|
357
|
-
: "cli";
|
|
358
|
-
const runtimeApprovalFn = runtimeControlContext?.approvalFn
|
|
359
|
-
?? this.deps.runtimeControlApprovalFn
|
|
360
|
-
?? this.deps.approvalFn;
|
|
361
|
-
const replyTarget = runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget;
|
|
362
|
-
const replyTargetInput = replyTarget
|
|
363
|
-
? {
|
|
364
|
-
...(replyTarget.surface ? { surface: replyTarget.surface } : {}),
|
|
365
|
-
channel,
|
|
366
|
-
...(replyTarget.platform ? { platform: replyTarget.platform } : {}),
|
|
367
|
-
...(replyTarget.conversation_id ? { conversation_id: replyTarget.conversation_id } : {}),
|
|
368
|
-
...(replyTarget.message_id ? { message_id: replyTarget.message_id } : {}),
|
|
369
|
-
...(replyTarget.response_channel ? { response_channel: replyTarget.response_channel } : {}),
|
|
370
|
-
...(replyTarget.outbox_topic ? { outbox_topic: replyTarget.outbox_topic } : {}),
|
|
371
|
-
...(replyTarget.identity_key ? { identity_key: replyTarget.identity_key } : {}),
|
|
372
|
-
...(replyTarget.user_id ? { user_id: replyTarget.user_id } : {}),
|
|
373
|
-
...(replyTarget.deliveryMode === "reply" || replyTarget.deliveryMode === "notify" || replyTarget.deliveryMode === "thread_reply"
|
|
374
|
-
? { deliveryMode: replyTarget.deliveryMode }
|
|
375
|
-
: {}),
|
|
376
|
-
...(replyTarget.metadata ? { metadata: replyTarget.metadata } : {}),
|
|
377
|
-
}
|
|
378
|
-
: undefined;
|
|
379
|
-
return buildStandaloneIngressMessage({
|
|
380
|
-
text: input,
|
|
381
|
-
channel,
|
|
382
|
-
platform: runtimeControlContext?.replyTarget?.platform ?? this.deps.runtimeReplyTarget?.platform,
|
|
383
|
-
identity_key: runtimeControlContext?.replyTarget?.identity_key ?? this.deps.runtimeReplyTarget?.identity_key,
|
|
384
|
-
conversation_id: runtimeControlContext?.replyTarget?.conversation_id ?? this.deps.runtimeReplyTarget?.conversation_id,
|
|
385
|
-
user_id: runtimeControlContext?.replyTarget?.user_id ?? this.deps.runtimeReplyTarget?.user_id,
|
|
386
|
-
actor: runtimeControlContext?.actor ?? this.deps.runtimeControlActor,
|
|
387
|
-
replyTarget: replyTargetInput,
|
|
388
|
-
runtimeControl: {
|
|
389
|
-
allowed: true,
|
|
390
|
-
approvalMode: "interactive",
|
|
391
|
-
},
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
buildRuntimeControlContextFromIngress(ingress) {
|
|
395
|
-
if (!ingress.actor && !ingress.replyTarget)
|
|
396
|
-
return null;
|
|
397
|
-
const interactiveApproval = this.runtimeControlContext?.approvalFn
|
|
398
|
-
?? this.deps.runtimeControlApprovalFn
|
|
399
|
-
?? this.deps.approvalFn;
|
|
400
|
-
return {
|
|
401
|
-
actor: ingress.actor,
|
|
402
|
-
replyTarget: ingress.replyTarget,
|
|
403
|
-
approvalFn: ingress.runtimeControl.approvalMode === "preapproved"
|
|
404
|
-
? async () => true
|
|
405
|
-
: ingress.runtimeControl.approvalMode === "interactive"
|
|
406
|
-
? interactiveApproval
|
|
407
|
-
: undefined,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
loadedSessionToChatSession(session) {
|
|
411
|
-
return {
|
|
412
|
-
id: session.id,
|
|
413
|
-
cwd: session.cwd,
|
|
414
|
-
createdAt: session.createdAt,
|
|
415
|
-
updatedAt: session.updatedAt,
|
|
416
|
-
messages: [...session.messages],
|
|
417
|
-
...(session.compactionSummary ? { compactionSummary: session.compactionSummary } : {}),
|
|
418
|
-
...(session.title ? { title: session.title } : {}),
|
|
419
|
-
...(session.agentLoopStatePath ? { agentLoopStatePath: session.agentLoopStatePath } : {}),
|
|
420
|
-
...(session.agentLoopStatus === "running" || session.agentLoopStatus === "completed" || session.agentLoopStatus === "failed"
|
|
421
|
-
? { agentLoopStatus: session.agentLoopStatus }
|
|
422
|
-
: {}),
|
|
423
|
-
...(session.agentLoopResumable ? { agentLoopResumable: true } : {}),
|
|
424
|
-
...(session.agentLoopUpdatedAt ? { agentLoopUpdatedAt: session.agentLoopUpdatedAt } : {}),
|
|
425
|
-
...(session.agentLoop ? { agentLoop: session.agentLoop } : {}),
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
formatRuntimeTimestamp(value) {
|
|
429
|
-
return value ?? "unknown";
|
|
430
|
-
}
|
|
431
|
-
formatRuntimeTitle(value) {
|
|
432
|
-
return value ? ` "${value}"` : "";
|
|
433
|
-
}
|
|
434
|
-
runtimeWarningLine(warnings) {
|
|
435
|
-
return warnings.length > 0 ? `Warnings: ${warnings.length}` : null;
|
|
436
|
-
}
|
|
437
|
-
activeRuntimeSession(session) {
|
|
438
|
-
return session.status === "active";
|
|
439
|
-
}
|
|
440
|
-
statusRuntimeRun(run) {
|
|
441
|
-
return run.status === "queued"
|
|
442
|
-
|| run.status === "running"
|
|
443
|
-
|| run.status === "failed"
|
|
444
|
-
|| run.status === "timed_out"
|
|
445
|
-
|| run.status === "lost";
|
|
446
|
-
}
|
|
447
|
-
compactRunLine(run) {
|
|
448
|
-
const title = this.formatRuntimeTitle(run.title);
|
|
449
|
-
const updated = this.formatRuntimeTimestamp(run.updated_at ?? run.started_at ?? run.created_at);
|
|
450
|
-
const summary = run.summary ? ` - ${run.summary.replace(/\s+/g, " ").trim()}` : "";
|
|
451
|
-
const error = run.error ? ` - error: ${run.error.replace(/\s+/g, " ").trim()}` : "";
|
|
452
|
-
return `- ${run.id}${title} [${run.kind}, ${run.status}], updated ${updated}${summary}${error}`;
|
|
453
|
-
}
|
|
454
|
-
compactSessionLine(session) {
|
|
455
|
-
const displayId = session.kind === "conversation"
|
|
456
|
-
? session.transcript_ref?.id ?? session.id.replace(/^session:conversation:/, "")
|
|
457
|
-
: session.id;
|
|
458
|
-
const title = this.formatRuntimeTitle(session.title);
|
|
459
|
-
const updated = this.formatRuntimeTimestamp(session.updated_at ?? session.last_event_at ?? session.created_at);
|
|
460
|
-
const workspace = session.workspace ? `, cwd ${session.workspace}` : "";
|
|
461
|
-
const resumable = session.resumable ? ", resumable" : "";
|
|
462
|
-
const attachable = session.attachable ? ", attachable" : "";
|
|
463
|
-
const runtimeId = displayId === session.id ? "" : `, runtime ${session.id}`;
|
|
464
|
-
return `- ${displayId}${title} [${session.kind}, ${session.status}], updated ${updated}${workspace}${resumable}${attachable}${runtimeId}`;
|
|
465
|
-
}
|
|
466
|
-
formatRuntimeSessionsList(snapshot) {
|
|
467
|
-
const chatSessions = snapshot.sessions.filter((session) => session.kind === "conversation");
|
|
468
|
-
const nonChatSessions = snapshot.sessions.filter((session) => session.kind !== "conversation");
|
|
469
|
-
const lines = ["Chat sessions:"];
|
|
470
|
-
if (chatSessions.length === 0) {
|
|
471
|
-
lines.push("No chat sessions found.");
|
|
193
|
+
async execute(input, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, options = {}) {
|
|
194
|
+
const eventContext = this.eventBridge.createEventContext();
|
|
195
|
+
const resolvedCwd = resolveGitRoot(cwd);
|
|
196
|
+
const activeTurn = this.eventBridge.beginActiveTurn(eventContext, resolvedCwd);
|
|
197
|
+
const resumeCommand = this.commandHandler.parseResumeCommand(input);
|
|
198
|
+
const resumeOnly = resumeCommand !== null;
|
|
199
|
+
const runtimeControlContext = options.runtimeControlContext ?? this.runtimeControlContext;
|
|
200
|
+
const executionGoalId = options.goalId ?? this.deps.goalId;
|
|
201
|
+
const commandResult = resumeOnly ? null : await this.commandHandler.handleCommand(input, resolvedCwd);
|
|
202
|
+
if (commandResult !== null) {
|
|
203
|
+
return this.finalizeNonPersistentResult(commandResult, eventContext);
|
|
472
204
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
205
|
+
if (this.pendingTend !== null && !resumeOnly) {
|
|
206
|
+
const confirmationResult = await this.commandHandler.handleTendConfirmation(input.trim(), Date.now());
|
|
207
|
+
return this.finalizeNonPersistentResult(confirmationResult, eventContext);
|
|
208
|
+
}
|
|
209
|
+
if (resumeOnly && resumeCommand.selector) {
|
|
210
|
+
try {
|
|
211
|
+
const selectorResolution = await resolveChatResumeSelector(resumeCommand.selector, this.deps);
|
|
212
|
+
if (selectorResolution.nonResumableMessage) {
|
|
213
|
+
return this.finalizeNonPersistentResult({
|
|
214
|
+
success: false,
|
|
215
|
+
output: selectorResolution.nonResumableMessage,
|
|
216
|
+
elapsed_ms: 0,
|
|
217
|
+
}, eventContext);
|
|
218
|
+
}
|
|
219
|
+
const catalog = new ChatSessionCatalog(this.deps.stateManager);
|
|
220
|
+
const session = await catalog.loadSessionBySelector(selectorResolution.chatSelector);
|
|
221
|
+
if (!session) {
|
|
222
|
+
return this.finalizeNonPersistentResult({
|
|
223
|
+
success: false,
|
|
224
|
+
output: `No chat session matched selector "${selectorResolution.chatSelector}".`,
|
|
225
|
+
elapsed_ms: 0,
|
|
226
|
+
}, eventContext);
|
|
479
227
|
}
|
|
228
|
+
this.startSessionFromLoadedSession(session);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
const output = err instanceof ChatSessionSelectorError ? err.message : `Failed to load chat session: ${err instanceof Error ? err.message : String(err)}`;
|
|
232
|
+
return this.finalizeNonPersistentResult({ success: false, output, elapsed_ms: 0 }, eventContext);
|
|
480
233
|
}
|
|
481
234
|
}
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
lines.push("", "Background runs:");
|
|
488
|
-
lines.push(...snapshot.background_runs.map((run) => this.compactRunLine(run)));
|
|
489
|
-
}
|
|
490
|
-
const warningLine = this.runtimeWarningLine(snapshot.warnings);
|
|
491
|
-
if (warningLine)
|
|
492
|
-
lines.push("", warningLine);
|
|
493
|
-
return lines.join("\n");
|
|
494
|
-
}
|
|
495
|
-
formatRuntimeStatus(snapshot) {
|
|
496
|
-
const activeSessions = snapshot.sessions.filter((session) => this.activeRuntimeSession(session));
|
|
497
|
-
const statusRuns = snapshot.background_runs.filter((run) => this.statusRuntimeRun(run));
|
|
498
|
-
const lines = [];
|
|
499
|
-
if (activeSessions.length > 0) {
|
|
500
|
-
lines.push("Active runtime sessions:");
|
|
501
|
-
lines.push(...activeSessions.map((session) => this.compactSessionLine(session)));
|
|
502
|
-
}
|
|
503
|
-
if (statusRuns.length > 0) {
|
|
504
|
-
if (lines.length > 0)
|
|
505
|
-
lines.push("");
|
|
506
|
-
lines.push("Background runs (queued/running/attention-needed):");
|
|
507
|
-
lines.push(...statusRuns.map((run) => this.compactRunLine(run)));
|
|
508
|
-
}
|
|
509
|
-
const warningLine = this.runtimeWarningLine(snapshot.warnings);
|
|
510
|
-
if (warningLine) {
|
|
511
|
-
if (lines.length > 0)
|
|
512
|
-
lines.push("");
|
|
513
|
-
lines.push(warningLine);
|
|
235
|
+
if (!this.sessionActive) {
|
|
236
|
+
const sessionId = crypto.randomUUID();
|
|
237
|
+
this.history = new ChatHistory(this.deps.stateManager, sessionId, resolvedCwd);
|
|
238
|
+
this.nativeAgentLoopStatePath = `chat/agentloop/${sessionId}.state.json`;
|
|
239
|
+
this.history.resetAgentLoopState(this.nativeAgentLoopStatePath);
|
|
514
240
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
241
|
+
const executionCwd = this.sessionCwd ?? resolvedCwd;
|
|
242
|
+
const gitRoot = this.sessionCwd ?? resolvedCwd;
|
|
243
|
+
activeTurn.cwd = gitRoot;
|
|
244
|
+
const history = this.history;
|
|
245
|
+
const pinnedReplyTarget = normalizePinnedReplyTarget(runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget ?? null);
|
|
246
|
+
if (pinnedReplyTarget) {
|
|
247
|
+
history.setNotificationReplyTarget(pinnedReplyTarget);
|
|
521
248
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
249
|
+
this.eventBridge.emitEvent({
|
|
250
|
+
type: "lifecycle_start",
|
|
251
|
+
input,
|
|
252
|
+
...this.eventBridge.eventBase(eventContext),
|
|
525
253
|
});
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
async loadGoals() {
|
|
529
|
-
const goalIds = await this.deps.stateManager.listGoalIds();
|
|
530
|
-
const goals = await Promise.all(goalIds.map((id) => this.deps.stateManager.loadGoal(id)));
|
|
531
|
-
return goals.filter((goal) => goal !== null);
|
|
532
|
-
}
|
|
533
|
-
async listAllGoalIds() {
|
|
534
|
-
const activeIds = await this.deps.stateManager.listGoalIds();
|
|
535
|
-
const archivedIds = await this.deps.stateManager.listArchivedGoals();
|
|
536
|
-
const recoverableArchivedIds = await this.listRecoverableArchivedGoalIds();
|
|
537
|
-
return [...new Set([...activeIds, ...archivedIds, ...recoverableArchivedIds])];
|
|
538
|
-
}
|
|
539
|
-
resolveStatePath(baseDir, ...segments) {
|
|
540
|
-
const base = path.resolve(baseDir);
|
|
541
|
-
const resolved = path.resolve(base, ...segments);
|
|
542
|
-
if (!resolved.startsWith(base + path.sep))
|
|
543
|
-
return null;
|
|
544
|
-
return resolved;
|
|
545
|
-
}
|
|
546
|
-
async listRecoverableArchivedGoalIds() {
|
|
547
|
-
const stateManager = this.deps.stateManager;
|
|
548
|
-
if (typeof stateManager.getBaseDir !== "function")
|
|
549
|
-
return [];
|
|
550
|
-
const archiveDir = this.resolveStatePath(stateManager.getBaseDir(), "archive");
|
|
551
|
-
if (archiveDir === null)
|
|
552
|
-
return [];
|
|
553
|
-
let entries = [];
|
|
554
|
-
try {
|
|
555
|
-
entries = await fsp.readdir(archiveDir, { withFileTypes: true });
|
|
556
|
-
}
|
|
557
|
-
catch {
|
|
558
|
-
return [];
|
|
254
|
+
if (!resumeOnly) {
|
|
255
|
+
await history.appendUserMessage(input);
|
|
559
256
|
}
|
|
560
|
-
|
|
561
|
-
for (const entry of entries) {
|
|
562
|
-
if (!entry.isDirectory() || entry.name === ".staging")
|
|
563
|
-
continue;
|
|
257
|
+
if (this.cachedStaticSystemPrompt === null) {
|
|
564
258
|
try {
|
|
565
|
-
|
|
566
|
-
goalIds.push(entry.name);
|
|
259
|
+
this.cachedStaticSystemPrompt = buildStaticSystemPrompt(this.providerConfigBaseDir());
|
|
567
260
|
}
|
|
568
261
|
catch {
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return goalIds;
|
|
573
|
-
}
|
|
574
|
-
activeGoals(goals) {
|
|
575
|
-
return goals.filter((goal) => goal.status === "active" || goal.status === "waiting" || goal.loop_status === "running");
|
|
576
|
-
}
|
|
577
|
-
formatGoalLine(goal) {
|
|
578
|
-
const dimensions = goal.dimensions.length === 0
|
|
579
|
-
? "no dimensions"
|
|
580
|
-
: goal.dimensions
|
|
581
|
-
.slice(0, 3)
|
|
582
|
-
.map((dimension) => `${dimension.name}: ${String(dimension.current_value)} target ${JSON.stringify(dimension.threshold)}`)
|
|
583
|
-
.join("; ");
|
|
584
|
-
return `${goal.id} - ${goal.title} [${goal.status}, loop ${goal.loop_status}] ${dimensions}`;
|
|
585
|
-
}
|
|
586
|
-
async handleStatus(args, start) {
|
|
587
|
-
if (args) {
|
|
588
|
-
const goal = await this.deps.stateManager.loadGoal(args);
|
|
589
|
-
if (!goal) {
|
|
590
|
-
return { success: false, output: `Goal not found: ${args}`, elapsed_ms: Date.now() - start };
|
|
262
|
+
this.cachedStaticSystemPrompt = "";
|
|
591
263
|
}
|
|
592
|
-
const lines = [
|
|
593
|
-
`Goal status: ${goal.title}`,
|
|
594
|
-
`ID: ${goal.id}`,
|
|
595
|
-
`Status: ${goal.status}`,
|
|
596
|
-
`Loop: ${goal.loop_status}`,
|
|
597
|
-
`Updated: ${goal.updated_at}`,
|
|
598
|
-
`Children: ${goal.children_ids.length}`,
|
|
599
|
-
`Dimensions:`,
|
|
600
|
-
...goal.dimensions.map((dimension) => `- ${dimension.name}: current=${String(dimension.current_value)}, threshold=${JSON.stringify(dimension.threshold)}, confidence=${dimension.confidence}`),
|
|
601
|
-
];
|
|
602
|
-
return { success: true, output: lines.join("\n"), elapsed_ms: Date.now() - start };
|
|
603
264
|
}
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const runtimeStatus = this.formatRuntimeStatus(runtimeSnapshot);
|
|
611
|
-
if (active.length === 0) {
|
|
612
|
-
return { success: true, output: `No active goals found.\n\n${runtimeStatus}`, elapsed_ms: Date.now() - start };
|
|
613
|
-
}
|
|
614
|
-
return {
|
|
615
|
-
success: true,
|
|
616
|
-
output: `Active goals:\n${active.map((goal) => this.formatGoalLine(goal)).join("\n")}\n\n${runtimeStatus}`,
|
|
617
|
-
elapsed_ms: Date.now() - start,
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
async handleGoals(start) {
|
|
621
|
-
const goals = await this.loadGoals();
|
|
622
|
-
if (goals.length === 0) {
|
|
623
|
-
return { success: true, output: "No goals found.", elapsed_ms: Date.now() - start };
|
|
265
|
+
const messages = history.getMessages();
|
|
266
|
+
const compactionSummary = history.getSessionData().compactionSummary;
|
|
267
|
+
const priorTurns = resumeOnly ? messages.slice(-10) : messages.slice(0, -1).slice(-10);
|
|
268
|
+
const historySections = [];
|
|
269
|
+
if (compactionSummary) {
|
|
270
|
+
historySections.push(`Compacted previous conversation summary:\n${compactionSummary}`);
|
|
624
271
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
elapsed_ms: Date.now() - start,
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
async readTasksFromDir(tasksDir) {
|
|
632
|
-
let entries = [];
|
|
633
|
-
try {
|
|
634
|
-
entries = await fsp.readdir(tasksDir);
|
|
272
|
+
if (priorTurns.length > 0) {
|
|
273
|
+
const lines = priorTurns.map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.content}`).join("\n");
|
|
274
|
+
historySections.push(`Previous conversation:\n${lines}`);
|
|
635
275
|
}
|
|
636
|
-
|
|
637
|
-
|
|
276
|
+
const historyBlock = historySections.length > 0 ? `${historySections.join("\n\n")}\n\nCurrent message:\n` : "";
|
|
277
|
+
const selectedRoute = resumeOnly
|
|
278
|
+
? null
|
|
279
|
+
: (options.selectedRoute ?? this.resolveRouteFromInput(input, runtimeControlContext));
|
|
280
|
+
this.lastSelectedRoute = selectedRoute;
|
|
281
|
+
this.eventBridge.emitIntent(input, selectedRoute, eventContext);
|
|
282
|
+
const start = Date.now();
|
|
283
|
+
const assistantBuffer = { text: "" };
|
|
284
|
+
const identityResponse = resumeOnly ? null : resolveSelfIdentityResponse(input, this.providerConfigBaseDir());
|
|
285
|
+
if (identityResponse !== null) {
|
|
286
|
+
const elapsed_ms = Date.now() - start;
|
|
287
|
+
await history.appendAssistantMessage(identityResponse);
|
|
288
|
+
this.eventBridge.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
289
|
+
this.eventBridge.emitEvent({
|
|
290
|
+
type: "assistant_final",
|
|
291
|
+
text: identityResponse,
|
|
292
|
+
persisted: true,
|
|
293
|
+
...this.eventBridge.eventBase(eventContext),
|
|
294
|
+
});
|
|
295
|
+
this.eventBridge.emitLifecycleEndEvent("completed", elapsed_ms, eventContext, true);
|
|
296
|
+
return {
|
|
297
|
+
success: true,
|
|
298
|
+
output: identityResponse,
|
|
299
|
+
elapsed_ms,
|
|
300
|
+
};
|
|
638
301
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
302
|
+
if (selectedRoute?.kind === "runtime_control") {
|
|
303
|
+
this.eventBridge.emitCheckpoint("Runtime control selected", `${selectedRoute.intent.kind} request recognized.`, eventContext, "route");
|
|
304
|
+
const runtimeControlResult = await executeRuntimeControlRoute(this.routeHost(), selectedRoute, runtimeControlContext, executionCwd, start);
|
|
305
|
+
if (runtimeControlResult.success) {
|
|
306
|
+
await history.appendAssistantMessage(runtimeControlResult.output);
|
|
307
|
+
this.eventBridge.emitCheckpoint("Runtime control completed", "The runtime-control operation produced a result.", eventContext, "complete");
|
|
308
|
+
this.eventBridge.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
309
|
+
this.eventBridge.emitEvent({
|
|
310
|
+
type: "assistant_final",
|
|
311
|
+
text: runtimeControlResult.output,
|
|
312
|
+
persisted: true,
|
|
313
|
+
...this.eventBridge.eventBase(eventContext),
|
|
314
|
+
});
|
|
315
|
+
this.eventBridge.emitLifecycleEndEvent("completed", runtimeControlResult.elapsed_ms, eventContext, true);
|
|
646
316
|
}
|
|
647
|
-
|
|
648
|
-
|
|
317
|
+
else {
|
|
318
|
+
runtimeControlResult.output = this.eventBridge.emitLifecycleErrorEvent(runtimeControlResult.output, assistantBuffer.text, eventContext);
|
|
319
|
+
this.eventBridge.emitLifecycleEndEvent("error", runtimeControlResult.elapsed_ms, eventContext, false);
|
|
649
320
|
}
|
|
650
|
-
|
|
651
|
-
if (parsed.success)
|
|
652
|
-
tasks.push(parsed.data);
|
|
653
|
-
}
|
|
654
|
-
return tasks.sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
|
|
655
|
-
}
|
|
656
|
-
async readTasksForGoal(goalId) {
|
|
657
|
-
const stateManager = this.deps.stateManager;
|
|
658
|
-
if (typeof stateManager.getBaseDir !== "function")
|
|
659
|
-
return [];
|
|
660
|
-
const baseDir = stateManager.getBaseDir();
|
|
661
|
-
const activeTasksDir = this.resolveStatePath(baseDir, "tasks", goalId);
|
|
662
|
-
const archiveTasksDir = this.resolveStatePath(baseDir, "archive", goalId, "tasks");
|
|
663
|
-
if (activeTasksDir === null || archiveTasksDir === null)
|
|
664
|
-
return [];
|
|
665
|
-
const activeTasks = await this.readTasksFromDir(activeTasksDir);
|
|
666
|
-
if (activeTasks.length > 0)
|
|
667
|
-
return activeTasks;
|
|
668
|
-
return this.readTasksFromDir(archiveTasksDir);
|
|
669
|
-
}
|
|
670
|
-
async resolveGoalForTasks(selector) {
|
|
671
|
-
if (selector)
|
|
672
|
-
return { goalId: selector };
|
|
673
|
-
const active = this.activeGoals(await this.loadGoals());
|
|
674
|
-
if (active.length === 1)
|
|
675
|
-
return { goalId: active[0].id };
|
|
676
|
-
if (active.length === 0)
|
|
677
|
-
return { error: "No active goals found. Use /tasks <goal-id>." };
|
|
678
|
-
return { error: "Multiple active goals found. Use /tasks <goal-id>." };
|
|
679
|
-
}
|
|
680
|
-
formatTaskLine(task) {
|
|
681
|
-
const verdict = task.verification_verdict ? `, verdict ${task.verification_verdict}` : "";
|
|
682
|
-
return `${task.id} - ${task.status}${verdict}: ${task.work_description}`;
|
|
683
|
-
}
|
|
684
|
-
async handleTasks(args, start) {
|
|
685
|
-
const resolved = await this.resolveGoalForTasks(args);
|
|
686
|
-
if (resolved.error || !resolved.goalId) {
|
|
687
|
-
return { success: false, output: resolved.error ?? "Usage: /tasks <goal-id>", elapsed_ms: Date.now() - start };
|
|
688
|
-
}
|
|
689
|
-
const tasks = await this.readTasksForGoal(resolved.goalId);
|
|
690
|
-
if (tasks.length === 0) {
|
|
691
|
-
return { success: true, output: `No tasks found for goal "${resolved.goalId}".`, elapsed_ms: Date.now() - start };
|
|
692
|
-
}
|
|
693
|
-
return {
|
|
694
|
-
success: true,
|
|
695
|
-
output: `Tasks for goal ${resolved.goalId}:\n${tasks.map((task) => this.formatTaskLine(task)).join("\n")}`,
|
|
696
|
-
elapsed_ms: Date.now() - start,
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
parseTaskArgs(args) {
|
|
700
|
-
const parts = args.split(/\s+/).filter(Boolean);
|
|
701
|
-
const goalFlagIndex = parts.indexOf("--goal");
|
|
702
|
-
if (goalFlagIndex >= 0) {
|
|
703
|
-
const goalId = parts[goalFlagIndex + 1];
|
|
704
|
-
parts.splice(goalFlagIndex, goalId ? 2 : 1);
|
|
705
|
-
return { taskId: parts[0], goalId };
|
|
321
|
+
return runtimeControlResult;
|
|
706
322
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
let raw = null;
|
|
323
|
+
const usesNativeAgentLoop = resumeOnly || selectedRoute?.kind === "agent_loop";
|
|
324
|
+
const groundingWorkspaceContext = !resumeOnly && usesNativeAgentLoop
|
|
325
|
+
? await buildChatContext(input, executionCwd)
|
|
326
|
+
: undefined;
|
|
327
|
+
let systemPrompt = this.cachedStaticSystemPrompt ?? "";
|
|
328
|
+
if (!resumeOnly) {
|
|
714
329
|
try {
|
|
715
|
-
|
|
330
|
+
this.eventBridge.emitActivity("lifecycle", "Preparing context...", eventContext, "lifecycle:context");
|
|
331
|
+
if (usesNativeAgentLoop) {
|
|
332
|
+
systemPrompt = await buildChatAgentLoopSystemPrompt({
|
|
333
|
+
stateManager: this.deps.stateManager,
|
|
334
|
+
pluginLoader: this.deps.pluginLoader,
|
|
335
|
+
workspaceRoot: executionCwd,
|
|
336
|
+
goalId: executionGoalId,
|
|
337
|
+
userMessage: input,
|
|
338
|
+
trustProjectInstructions: this.sessionExecutionPolicy?.trustProjectInstructions ?? true,
|
|
339
|
+
workspaceContext: groundingWorkspaceContext,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
const groundingBundle = await this.groundingGateway.build({
|
|
344
|
+
surface: "chat",
|
|
345
|
+
purpose: "general_turn",
|
|
346
|
+
workspaceRoot: executionCwd,
|
|
347
|
+
goalId: executionGoalId,
|
|
348
|
+
userMessage: input,
|
|
349
|
+
query: input,
|
|
350
|
+
trustProjectInstructions: this.sessionExecutionPolicy?.trustProjectInstructions ?? true,
|
|
351
|
+
});
|
|
352
|
+
systemPrompt = String(groundingBundle.render("prompt"));
|
|
353
|
+
}
|
|
716
354
|
}
|
|
717
355
|
catch {
|
|
718
|
-
|
|
719
|
-
}
|
|
720
|
-
if (!raw) {
|
|
721
|
-
const tasks = await this.readTasksForGoal(candidateGoalId);
|
|
722
|
-
const matched = tasks.find((task) => task.id === taskId || task.id.startsWith(taskId));
|
|
723
|
-
if (matched)
|
|
724
|
-
matches.push({ goalId: candidateGoalId, task: matched });
|
|
725
|
-
continue;
|
|
356
|
+
systemPrompt = this.cachedStaticSystemPrompt ?? "";
|
|
726
357
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
358
|
+
this.eventBridge.emitCheckpoint("Context gathered", usesNativeAgentLoop
|
|
359
|
+
? "Workspace and agent-loop grounding are ready."
|
|
360
|
+
: "Workspace grounding is ready.", eventContext, "context");
|
|
730
361
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
lines.push(`Completed: ${task.completed_at}`);
|
|
747
|
-
if (task.verification_verdict)
|
|
748
|
-
lines.push(`Verification: ${task.verification_verdict}`);
|
|
749
|
-
if (task.verification_evidence?.length)
|
|
750
|
-
lines.push(`Evidence: ${task.verification_evidence.join("; ")}`);
|
|
751
|
-
if (task.success_criteria.length > 0) {
|
|
752
|
-
lines.push("Success criteria:");
|
|
753
|
-
lines.push(...task.success_criteria.map((criterion) => `- ${criterion.description}`));
|
|
362
|
+
const agentLoopSystemPrompt = [
|
|
363
|
+
systemPrompt,
|
|
364
|
+
compactionSummary ? `## Compacted Chat Summary\n${compactionSummary}` : "",
|
|
365
|
+
]
|
|
366
|
+
.filter((section) => section && section.trim().length > 0)
|
|
367
|
+
.join("\n\n")
|
|
368
|
+
.trim();
|
|
369
|
+
const context = resumeOnly || usesNativeAgentLoop ? "" : await buildChatContext(input, gitRoot);
|
|
370
|
+
const basePrompt = resumeOnly ? "" : (context ? `${context}\n\n${input}` : input);
|
|
371
|
+
const prompt = historyBlock ? `${historyBlock}${basePrompt}` : basePrompt;
|
|
372
|
+
if (resumeOnly && !this.deps.chatAgentLoopRunner) {
|
|
373
|
+
const elapsed_ms = Date.now() - start;
|
|
374
|
+
const output = this.eventBridge.emitLifecycleErrorEvent("Resume requires the native chat agentloop runtime.", assistantBuffer.text, eventContext);
|
|
375
|
+
this.eventBridge.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
376
|
+
return { success: false, output, elapsed_ms };
|
|
754
377
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
378
|
+
if (resumeOnly || selectedRoute?.kind === "agent_loop") {
|
|
379
|
+
return executeAgentLoopRoute(this.routeHost(), {
|
|
380
|
+
resumeOnly,
|
|
381
|
+
executionCwd,
|
|
382
|
+
executionGoalId,
|
|
383
|
+
basePrompt,
|
|
384
|
+
priorTurns,
|
|
385
|
+
agentLoopSystemPrompt,
|
|
386
|
+
assistantBuffer,
|
|
387
|
+
eventContext,
|
|
388
|
+
history,
|
|
389
|
+
gitRoot,
|
|
390
|
+
activeAbortSignal: activeTurn.abortController.signal,
|
|
391
|
+
start,
|
|
392
|
+
});
|
|
761
393
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
394
|
+
if (selectedRoute?.kind === "tool_loop") {
|
|
395
|
+
return executeToolLoopRoute(this.routeHost(), {
|
|
396
|
+
prompt,
|
|
397
|
+
eventContext,
|
|
398
|
+
assistantBuffer,
|
|
399
|
+
systemPrompt: systemPrompt || undefined,
|
|
400
|
+
executionGoalId,
|
|
401
|
+
history,
|
|
402
|
+
gitRoot,
|
|
403
|
+
start,
|
|
404
|
+
});
|
|
769
405
|
}
|
|
770
|
-
if (!
|
|
771
|
-
const
|
|
772
|
-
|
|
406
|
+
if (!resumeOnly && selectedRoute && selectedRoute.kind !== "adapter") {
|
|
407
|
+
const elapsed_ms = Date.now() - start;
|
|
408
|
+
const output = this.eventBridge.emitLifecycleErrorEvent(`Unsupported chat route: ${selectedRoute.kind}`, assistantBuffer.text, eventContext);
|
|
409
|
+
this.eventBridge.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
410
|
+
return { success: false, output, elapsed_ms };
|
|
773
411
|
}
|
|
774
|
-
return
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
412
|
+
return executeAdapterRoute(this.routeHost(), {
|
|
413
|
+
prompt,
|
|
414
|
+
cwd,
|
|
415
|
+
timeoutMs,
|
|
416
|
+
systemPrompt: systemPrompt || undefined,
|
|
417
|
+
eventContext,
|
|
418
|
+
assistantBuffer,
|
|
419
|
+
gitRoot,
|
|
420
|
+
start,
|
|
421
|
+
history,
|
|
784
422
|
});
|
|
785
|
-
return {
|
|
786
|
-
provider: config.provider,
|
|
787
|
-
model: config.model,
|
|
788
|
-
adapter: config.adapter,
|
|
789
|
-
light_model: config.light_model,
|
|
790
|
-
base_url: config.base_url,
|
|
791
|
-
codex_cli_path: config.codex_cli_path,
|
|
792
|
-
has_api_key: Boolean(config.api_key),
|
|
793
|
-
};
|
|
794
423
|
}
|
|
795
|
-
|
|
796
|
-
return
|
|
797
|
-
.filter(([, value]) => value !== undefined)
|
|
798
|
-
.map(([key, value]) => `${key}: ${typeof value === "string" && /key|token|secret/i.test(key) ? "[masked]" : String(value)}`)
|
|
799
|
-
.join("\n");
|
|
424
|
+
getSessionCwd() {
|
|
425
|
+
return this.sessionCwd;
|
|
800
426
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
return { success: true, output: `Provider configuration:\n${this.formatConfig(config)}`, elapsed_ms: Date.now() - start };
|
|
427
|
+
getNativeAgentLoopStatePath() {
|
|
428
|
+
return this.nativeAgentLoopStatePath;
|
|
804
429
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
return {
|
|
808
|
-
success: true,
|
|
809
|
-
output: `Model: ${config.model}\nProvider: ${config.provider}\nAdapter: ${config.adapter}`,
|
|
810
|
-
elapsed_ms: Date.now() - start,
|
|
811
|
-
};
|
|
430
|
+
setSessionExecutionPolicy(policy) {
|
|
431
|
+
this.sessionExecutionPolicy = policy;
|
|
812
432
|
}
|
|
813
|
-
async
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
try {
|
|
818
|
-
const plugins = await this.deps.pluginLoader.loadAll();
|
|
819
|
-
if (plugins.length === 0) {
|
|
820
|
-
return { success: true, output: "No plugins found.", elapsed_ms: Date.now() - start };
|
|
821
|
-
}
|
|
822
|
-
return {
|
|
823
|
-
success: true,
|
|
824
|
-
output: `Plugins:\n${plugins.map((plugin) => `${plugin.name} - ${plugin.type ?? "unknown"} - ${plugin.enabled === false ? "disabled" : "enabled"}`).join("\n")}`,
|
|
825
|
-
elapsed_ms: Date.now() - start,
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
catch (err) {
|
|
829
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
830
|
-
return { success: true, output: `Plugin information is unavailable: ${message}`, elapsed_ms: Date.now() - start };
|
|
831
|
-
}
|
|
433
|
+
async getSessionExecutionPolicy() {
|
|
434
|
+
const policy = await resolveSessionExecutionPolicy(this.sessionExecutionPolicy, this.sessionCwd);
|
|
435
|
+
this.sessionExecutionPolicy = policy;
|
|
436
|
+
return policy;
|
|
832
437
|
}
|
|
833
|
-
|
|
834
|
-
return
|
|
438
|
+
resolveRouteFromIngress(ingress) {
|
|
439
|
+
return standaloneIngressRouter.selectRoute(ingress, getRouteCapabilities(this.deps));
|
|
440
|
+
}
|
|
441
|
+
resolveRouteFromInput(input, runtimeControlContext) {
|
|
442
|
+
return this.resolveRouteFromIngress(buildStandaloneIngressMessageFromContext(input, runtimeControlContext, this.deps));
|
|
835
443
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const outputTokens = Number.isFinite(usage.outputTokens) ? Math.max(0, Math.floor(usage.outputTokens)) : 0;
|
|
839
|
-
const totalTokens = Number.isFinite(usage.totalTokens)
|
|
840
|
-
? Math.max(0, Math.floor(usage.totalTokens))
|
|
841
|
-
: inputTokens + outputTokens;
|
|
842
|
-
return { inputTokens, outputTokens, totalTokens };
|
|
444
|
+
loadedSessionToChatSession(session) {
|
|
445
|
+
return loadedSessionToChatSession(session);
|
|
843
446
|
}
|
|
844
|
-
|
|
845
|
-
const inputTokens = response.usage?.input_tokens ?? 0;
|
|
846
|
-
const outputTokens = response.usage?.output_tokens ?? 0;
|
|
447
|
+
routeHost() {
|
|
847
448
|
return {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
449
|
+
deps: this.deps,
|
|
450
|
+
eventBridge: this.eventBridge,
|
|
451
|
+
activatedTools: this.activatedTools,
|
|
452
|
+
getConversationSessionId: () => this.history?.getSessionId() ?? null,
|
|
453
|
+
getSessionCwd: () => this.sessionCwd,
|
|
454
|
+
getNativeAgentLoopStatePath: () => this.nativeAgentLoopStatePath,
|
|
455
|
+
getSessionExecutionPolicy: () => this.getSessionExecutionPolicy(),
|
|
456
|
+
setSessionExecutionPolicy: (policy) => { this.sessionExecutionPolicy = policy; },
|
|
851
457
|
};
|
|
852
458
|
}
|
|
853
|
-
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
target.outputTokens += normalizedDelta.outputTokens;
|
|
857
|
-
target.totalTokens += normalizedDelta.totalTokens;
|
|
858
|
-
}
|
|
859
|
-
hasUsage(usage) {
|
|
860
|
-
return usage.totalTokens > 0 || usage.inputTokens > 0 || usage.outputTokens > 0;
|
|
861
|
-
}
|
|
862
|
-
formatUsageCounter(prefix, usage) {
|
|
863
|
-
return [
|
|
864
|
-
`${prefix} input tokens: ${usage.inputTokens}`,
|
|
865
|
-
`${prefix} output tokens: ${usage.outputTokens}`,
|
|
866
|
-
`${prefix} total tokens: ${usage.totalTokens}`,
|
|
867
|
-
];
|
|
868
|
-
}
|
|
869
|
-
parseUsagePeriodMs(period) {
|
|
870
|
-
const match = /^(\d+)([dhw])$/i.exec(period.trim());
|
|
871
|
-
if (!match) {
|
|
872
|
-
throw new Error("period must be one of 24h, 7d, 2w");
|
|
873
|
-
}
|
|
874
|
-
const value = Number(match[1]);
|
|
875
|
-
const unit = match[2]?.toLowerCase();
|
|
876
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
877
|
-
throw new Error("period value must be positive");
|
|
878
|
-
}
|
|
879
|
-
if (unit === "h")
|
|
880
|
-
return value * 60 * 60 * 1000;
|
|
881
|
-
if (unit === "w")
|
|
882
|
-
return value * 7 * 24 * 60 * 60 * 1000;
|
|
883
|
-
return value * 24 * 60 * 60 * 1000;
|
|
459
|
+
providerConfigBaseDir() {
|
|
460
|
+
const stateManager = this.deps.stateManager;
|
|
461
|
+
return typeof stateManager.getBaseDir === "function" ? stateManager.getBaseDir() : getPulseedDirPath();
|
|
884
462
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
if (err.code !== "ENOENT")
|
|
894
|
-
throw err;
|
|
895
|
-
return { goalId, totalTokens: 0, taskCount: 0, terminalTaskCount: 0 };
|
|
463
|
+
finalizeNonPersistentResult(result, eventContext) {
|
|
464
|
+
if (result.output) {
|
|
465
|
+
this.eventBridge.emitEvent({
|
|
466
|
+
type: "assistant_final",
|
|
467
|
+
text: result.output,
|
|
468
|
+
persisted: false,
|
|
469
|
+
...this.eventBridge.eventBase(eventContext),
|
|
470
|
+
});
|
|
896
471
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
let terminalTaskCount = 0;
|
|
900
|
-
for (const entry of entries) {
|
|
901
|
-
if (!entry.endsWith(".json"))
|
|
902
|
-
continue;
|
|
903
|
-
taskCount += 1;
|
|
904
|
-
try {
|
|
905
|
-
const raw = await fsp.readFile(path.join(ledgerDir, entry), "utf-8");
|
|
906
|
-
const parsed = JSON.parse(raw);
|
|
907
|
-
if (typeof parsed.summary?.tokens_used === "number") {
|
|
908
|
-
totalTokens += parsed.summary.tokens_used;
|
|
909
|
-
}
|
|
910
|
-
if (parsed.summary?.latest_event_type === "succeeded"
|
|
911
|
-
|| parsed.summary?.latest_event_type === "failed"
|
|
912
|
-
|| parsed.summary?.latest_event_type === "abandoned") {
|
|
913
|
-
terminalTaskCount += 1;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
catch {
|
|
917
|
-
// Ignore malformed records.
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
return { goalId, totalTokens, taskCount, terminalTaskCount };
|
|
921
|
-
}
|
|
922
|
-
async collectScheduleUsage(period) {
|
|
923
|
-
const periodMs = this.parseUsagePeriodMs(period);
|
|
924
|
-
const since = Date.now() - periodMs;
|
|
925
|
-
const historyPath = path.join(this.deps.stateManager.getBaseDir(), "schedule-history.json");
|
|
926
|
-
let raw;
|
|
927
|
-
try {
|
|
928
|
-
raw = JSON.parse(await fsp.readFile(historyPath, "utf-8"));
|
|
929
|
-
}
|
|
930
|
-
catch (err) {
|
|
931
|
-
if (err.code === "ENOENT") {
|
|
932
|
-
return { period, runs: 0, totalTokens: 0 };
|
|
933
|
-
}
|
|
934
|
-
throw err;
|
|
935
|
-
}
|
|
936
|
-
if (!Array.isArray(raw)) {
|
|
937
|
-
return { period, runs: 0, totalTokens: 0 };
|
|
938
|
-
}
|
|
939
|
-
let runs = 0;
|
|
940
|
-
let totalTokens = 0;
|
|
941
|
-
for (const record of raw) {
|
|
942
|
-
if (!record || typeof record !== "object")
|
|
943
|
-
continue;
|
|
944
|
-
const finishedAt = record["finished_at"];
|
|
945
|
-
const firedAt = typeof finishedAt === "string" ? Date.parse(finishedAt) : Number.NaN;
|
|
946
|
-
if (!Number.isFinite(firedAt) || firedAt < since)
|
|
947
|
-
continue;
|
|
948
|
-
runs += 1;
|
|
949
|
-
const tokensUsed = record["tokens_used"];
|
|
950
|
-
if (typeof tokensUsed === "number" && Number.isFinite(tokensUsed)) {
|
|
951
|
-
totalTokens += tokensUsed;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
return { period, runs, totalTokens };
|
|
955
|
-
}
|
|
956
|
-
async handleUsage(args, start) {
|
|
957
|
-
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
958
|
-
const scope = tokens[0]?.toLowerCase();
|
|
959
|
-
if (!scope || scope === "session") {
|
|
960
|
-
if (!this.history) {
|
|
961
|
-
return { success: false, output: "No active chat session. Start a session and run work before /usage.", elapsed_ms: Date.now() - start };
|
|
962
|
-
}
|
|
963
|
-
const session = this.history.getSessionData();
|
|
964
|
-
const totals = this.normalizeUsageCounter(session.usage?.totals ?? this.zeroUsageCounter());
|
|
965
|
-
const lines = [
|
|
966
|
-
`Usage summary (session ${session.id})`,
|
|
967
|
-
...this.formatUsageCounter("Session", totals),
|
|
968
|
-
];
|
|
969
|
-
const phaseEntries = Object.entries(session.usage?.byPhase ?? {})
|
|
970
|
-
.map(([phase, usage]) => ({ phase, usage: this.normalizeUsageCounter(usage) }))
|
|
971
|
-
.filter((entry) => this.hasUsage(entry.usage))
|
|
972
|
-
.sort((left, right) => right.usage.totalTokens - left.usage.totalTokens);
|
|
973
|
-
if (phaseEntries.length > 0) {
|
|
974
|
-
lines.push("");
|
|
975
|
-
lines.push("By phase:");
|
|
976
|
-
for (const entry of phaseEntries) {
|
|
977
|
-
lines.push(`- ${entry.phase}: ${entry.usage.totalTokens} (in=${entry.usage.inputTokens}, out=${entry.usage.outputTokens})`);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
return { success: true, output: lines.join("\n"), elapsed_ms: Date.now() - start };
|
|
981
|
-
}
|
|
982
|
-
if (scope === "goal" || scope === "daemon") {
|
|
983
|
-
const goalId = tokens[1] ?? this.deps.goalId;
|
|
984
|
-
if (!goalId) {
|
|
985
|
-
return { success: false, output: "Usage: /usage goal <goal-id>", elapsed_ms: Date.now() - start };
|
|
986
|
-
}
|
|
987
|
-
const summary = await this.collectGoalUsage(goalId);
|
|
988
|
-
const lines = [
|
|
989
|
-
`Usage summary (${scope} scope)`,
|
|
990
|
-
`Goal: ${summary.goalId}`,
|
|
991
|
-
`Tasks observed: ${summary.taskCount}`,
|
|
992
|
-
`Terminal tasks: ${summary.terminalTaskCount}`,
|
|
993
|
-
`Total tokens: ${summary.totalTokens}`,
|
|
994
|
-
];
|
|
995
|
-
return { success: true, output: lines.join("\n"), elapsed_ms: Date.now() - start };
|
|
996
|
-
}
|
|
997
|
-
if (scope === "schedule") {
|
|
998
|
-
const period = tokens[1] ?? "7d";
|
|
999
|
-
try {
|
|
1000
|
-
const summary = await this.collectScheduleUsage(period);
|
|
1001
|
-
const lines = [
|
|
1002
|
-
`Usage summary (schedule, ${summary.period})`,
|
|
1003
|
-
`Runs: ${summary.runs}`,
|
|
1004
|
-
`Total tokens: ${summary.totalTokens}`,
|
|
1005
|
-
];
|
|
1006
|
-
return { success: true, output: lines.join("\n"), elapsed_ms: Date.now() - start };
|
|
1007
|
-
}
|
|
1008
|
-
catch (err) {
|
|
1009
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1010
|
-
return { success: false, output: `Usage: /usage schedule [24h|7d|2w]\nError: ${message}`, elapsed_ms: Date.now() - start };
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
return {
|
|
1014
|
-
success: false,
|
|
1015
|
-
output: "Usage: /usage [session|goal <goal-id>|daemon <goal-id>|schedule [24h|7d|2w]]",
|
|
1016
|
-
elapsed_ms: Date.now() - start,
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
deterministicChatSummary(messages) {
|
|
1020
|
-
const lines = messages.map((message) => `${message.role}: ${message.content.replace(/\s+/g, " ").trim()}`);
|
|
1021
|
-
return lines.join("\n").slice(0, 4_000);
|
|
1022
|
-
}
|
|
1023
|
-
async summarizeChatForCompaction(messages, existingSummary) {
|
|
1024
|
-
const content = [
|
|
1025
|
-
existingSummary ? `Previous summary:\n${existingSummary}` : "",
|
|
1026
|
-
`Messages to summarize:\n${messages.map((message) => `${message.role}: ${message.content}`).join("\n")}`,
|
|
1027
|
-
].filter(Boolean).join("\n\n");
|
|
1028
|
-
if (this.deps.llmClient) {
|
|
1029
|
-
try {
|
|
1030
|
-
const response = await this.deps.llmClient.sendMessage([
|
|
1031
|
-
{ role: "user", content: `Summarize this chat history for later continuation. Preserve decisions, open tasks, constraints, and user preferences. Keep it concise.\n\n${content}` },
|
|
1032
|
-
], { max_tokens: 700, model_tier: "light" });
|
|
1033
|
-
if (response.content.trim())
|
|
1034
|
-
return { summary: response.content.trim(), usedLlm: true };
|
|
1035
|
-
}
|
|
1036
|
-
catch {
|
|
1037
|
-
// Fall back to deterministic summary below.
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
const fallback = [
|
|
1041
|
-
existingSummary ? `Previous summary:\n${existingSummary}` : "",
|
|
1042
|
-
"Extractive summary:",
|
|
1043
|
-
this.deterministicChatSummary(messages),
|
|
1044
|
-
].filter(Boolean).join("\n\n");
|
|
1045
|
-
return { summary: fallback, usedLlm: false };
|
|
1046
|
-
}
|
|
1047
|
-
async handleCompact(start) {
|
|
1048
|
-
if (!this.history) {
|
|
1049
|
-
return { success: false, output: "No active chat session to compact.", elapsed_ms: Date.now() - start };
|
|
1050
|
-
}
|
|
1051
|
-
const session = this.history.getSessionData();
|
|
1052
|
-
if (session.messages.length <= 4) {
|
|
1053
|
-
return { success: true, output: "Chat history is already compact. No messages were removed.", elapsed_ms: Date.now() - start };
|
|
1054
|
-
}
|
|
1055
|
-
const olderMessages = session.messages.slice(0, -4);
|
|
1056
|
-
const { summary, usedLlm } = await this.summarizeChatForCompaction(olderMessages, session.compactionSummary);
|
|
1057
|
-
const { before, after } = await this.history.compact(summary, 4);
|
|
1058
|
-
const method = usedLlm ? "LLM summary" : "deterministic summary";
|
|
1059
|
-
return {
|
|
1060
|
-
success: true,
|
|
1061
|
-
output: `Compacted chat history with ${method}. Persisted ${before} message(s) down to ${after}; the latest user/assistant turns were kept.`,
|
|
1062
|
-
elapsed_ms: Date.now() - start,
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
formatRoute(route) {
|
|
1066
|
-
if (!route)
|
|
1067
|
-
return "none selected yet";
|
|
1068
|
-
const details = [
|
|
1069
|
-
`lane=${route.lane}`,
|
|
1070
|
-
`kind=${route.kind}`,
|
|
1071
|
-
`reason=${route.reason}`,
|
|
1072
|
-
];
|
|
1073
|
-
if (route.kind === "direct_answer") {
|
|
1074
|
-
details.push(`model_tier=${route.modelTier}`, `max_tokens=${route.maxTokens}`);
|
|
1075
|
-
}
|
|
1076
|
-
if (route.kind === "runtime_control") {
|
|
1077
|
-
details.push(`intent=${route.intent.kind}`);
|
|
1078
|
-
}
|
|
1079
|
-
return details.join(", ");
|
|
1080
|
-
}
|
|
1081
|
-
async handleContext(start, cwdOverride) {
|
|
1082
|
-
const cwd = this.sessionCwd ?? (cwdOverride ? resolveGitRoot(cwdOverride) : process.cwd());
|
|
1083
|
-
const session = this.history?.getSessionData() ?? null;
|
|
1084
|
-
const messages = session?.messages ?? [];
|
|
1085
|
-
const policy = await this.getSessionExecutionPolicy();
|
|
1086
|
-
const recentMessages = messages.slice(-6);
|
|
1087
|
-
const userTurns = messages.filter((message) => message.role === "user").length;
|
|
1088
|
-
const assistantTurns = messages.filter((message) => message.role === "assistant").length;
|
|
1089
|
-
const compactionSummary = session?.compactionSummary?.trim() ?? "";
|
|
1090
|
-
const agentLoopPath = this.nativeAgentLoopStatePath ?? session?.agentLoopStatePath ?? null;
|
|
1091
|
-
const replyTarget = this.runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget ?? null;
|
|
1092
|
-
const routeCapabilities = this.getRouteCapabilities();
|
|
1093
|
-
const replyTargetParts = replyTarget
|
|
1094
|
-
? [replyTarget.surface, replyTarget.platform, replyTarget.conversation_id].filter(Boolean)
|
|
1095
|
-
: [];
|
|
1096
|
-
const contextLines = [
|
|
1097
|
-
"Working context",
|
|
1098
|
-
"",
|
|
1099
|
-
"Session",
|
|
1100
|
-
`- session_id: ${this.history?.getSessionId() ?? "none"}`,
|
|
1101
|
-
`- cwd: ${cwd}`,
|
|
1102
|
-
`- messages: ${messages.length} (${userTurns} user, ${assistantTurns} assistant)`,
|
|
1103
|
-
`- recent_turns_retained: ${recentMessages.length}`,
|
|
1104
|
-
`- compaction_summary: ${compactionSummary ? "present" : "none"}`,
|
|
1105
|
-
`- agentloop_state_path: ${agentLoopPath ?? "none"}`,
|
|
1106
|
-
"",
|
|
1107
|
-
"Turn context",
|
|
1108
|
-
`- last_selected_route: ${this.formatRoute(this.lastSelectedRoute)}`,
|
|
1109
|
-
`- reply_target: ${replyTargetParts.length > 0 ? replyTargetParts.join(":") : "none"}`,
|
|
1110
|
-
`- route_capabilities: light_llm=${routeCapabilities.hasLightweightLlm}, agent_loop=${routeCapabilities.hasAgentLoop}, tool_loop=${routeCapabilities.hasToolLoop}, runtime_control=${routeCapabilities.hasRuntimeControlService}, daemon_tend=${routeCapabilities.hasDaemonTend}`,
|
|
1111
|
-
"",
|
|
1112
|
-
"Working assumptions",
|
|
1113
|
-
"- this view exposes operational context, not hidden reasoning",
|
|
1114
|
-
"- last_selected_route describes the most recent non-command turn in this ChatRunner",
|
|
1115
|
-
"- future turns may select a different route based on the next input",
|
|
1116
|
-
"",
|
|
1117
|
-
"Active constraints",
|
|
1118
|
-
...summarizeExecutionPolicy(policy).split("\n").map((line) => `- ${line}`),
|
|
1119
|
-
"",
|
|
1120
|
-
"Included context",
|
|
1121
|
-
"- current session cwd and execution policy because they constrain tool and route behavior",
|
|
1122
|
-
`- ${recentMessages.length} latest persisted message(s)`,
|
|
1123
|
-
`- ${compactionSummary ? "compacted older chat summary because older turns were summarized" : "no compacted older chat summary because none is stored"}`,
|
|
1124
|
-
`- ${agentLoopPath ? "native agent-loop resume path because this session can persist agent-loop state" : "no native agent-loop resume path because none is active"}`,
|
|
1125
|
-
"",
|
|
1126
|
-
"Not included",
|
|
1127
|
-
"- hidden reasoning or private model chain-of-thought",
|
|
1128
|
-
"- raw state files unless a command explicitly reads them",
|
|
1129
|
-
"- older chat turns beyond the retained window unless compacted into the session summary",
|
|
1130
|
-
];
|
|
1131
|
-
return {
|
|
1132
|
-
success: true,
|
|
1133
|
-
output: contextLines.join("\n"),
|
|
1134
|
-
elapsed_ms: Date.now() - start,
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
1137
|
-
async handleCommand(input, cwd) {
|
|
1138
|
-
const trimmed = input.trim();
|
|
1139
|
-
if (!trimmed.startsWith("/"))
|
|
1140
|
-
return null;
|
|
1141
|
-
const cmd = trimmed.toLowerCase().split(/\s+/)[0];
|
|
1142
|
-
const start = Date.now();
|
|
1143
|
-
if (cmd === "/help") {
|
|
1144
|
-
return { success: true, output: COMMAND_HELP, elapsed_ms: Date.now() - start };
|
|
1145
|
-
}
|
|
1146
|
-
if (cmd === "/clear") {
|
|
1147
|
-
await this.history?.clear();
|
|
1148
|
-
return { success: true, output: "Conversation history cleared.", elapsed_ms: Date.now() - start };
|
|
1149
|
-
}
|
|
1150
|
-
if (cmd === "/sessions") {
|
|
1151
|
-
const registry = createRuntimeSessionRegistry({ stateManager: this.deps.stateManager });
|
|
1152
|
-
const snapshot = await registry.snapshot();
|
|
1153
|
-
return { success: true, output: this.formatRuntimeSessionsList(snapshot), elapsed_ms: Date.now() - start };
|
|
1154
|
-
}
|
|
1155
|
-
if (cmd === "/history") {
|
|
1156
|
-
const catalog = new ChatSessionCatalog(this.deps.stateManager);
|
|
1157
|
-
const selector = trimmed.slice("/history".length).trim();
|
|
1158
|
-
const session = selector
|
|
1159
|
-
? await catalog.loadSessionBySelector(selector)
|
|
1160
|
-
: this.history
|
|
1161
|
-
? await catalog.loadSession(this.history.getSessionId())
|
|
1162
|
-
: null;
|
|
1163
|
-
if (!session) {
|
|
1164
|
-
return { success: false, output: "No chat session history found.", elapsed_ms: Date.now() - start };
|
|
1165
|
-
}
|
|
1166
|
-
return { success: true, output: this.formatHistory(session), elapsed_ms: Date.now() - start };
|
|
1167
|
-
}
|
|
1168
|
-
if (cmd === "/title") {
|
|
1169
|
-
const title = trimmed.slice("/title".length).trim();
|
|
1170
|
-
if (!title) {
|
|
1171
|
-
return { success: false, output: "Usage: /title <title>", elapsed_ms: Date.now() - start };
|
|
1172
|
-
}
|
|
1173
|
-
if (!this.history) {
|
|
1174
|
-
return { success: false, output: "No active chat session to rename.", elapsed_ms: Date.now() - start };
|
|
1175
|
-
}
|
|
1176
|
-
const catalog = new ChatSessionCatalog(this.deps.stateManager);
|
|
1177
|
-
this.history.setTitle(title);
|
|
1178
|
-
await this.history.persist();
|
|
1179
|
-
await catalog.renameSession(this.history.getSessionId(), title);
|
|
1180
|
-
return { success: true, output: `Renamed chat session to "${title}".`, elapsed_ms: Date.now() - start };
|
|
1181
|
-
}
|
|
1182
|
-
if (cmd === "/cleanup") {
|
|
1183
|
-
const catalog = new ChatSessionCatalog(this.deps.stateManager);
|
|
1184
|
-
const dryRun = trimmed.includes("--dry-run");
|
|
1185
|
-
const report = await catalog.cleanupSessions({
|
|
1186
|
-
dryRun,
|
|
1187
|
-
activeSessionId: this.history?.getSessionId(),
|
|
1188
|
-
});
|
|
1189
|
-
const verb = dryRun ? "would remove" : "removed";
|
|
1190
|
-
return {
|
|
1191
|
-
success: true,
|
|
1192
|
-
output: `Chat session cleanup ${verb} ${report.removedSessionIds.length} session(s).`,
|
|
1193
|
-
elapsed_ms: Date.now() - start,
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
if (cmd === "/compact") {
|
|
1197
|
-
return this.handleCompact(start);
|
|
1198
|
-
}
|
|
1199
|
-
if (cmd === "/status") {
|
|
1200
|
-
return this.handleStatus(trimmed.slice("/status".length).trim(), start);
|
|
1201
|
-
}
|
|
1202
|
-
if (cmd === "/goals") {
|
|
1203
|
-
return this.handleGoals(start);
|
|
1204
|
-
}
|
|
1205
|
-
if (cmd === "/tasks") {
|
|
1206
|
-
return this.handleTasks(trimmed.slice("/tasks".length).trim(), start);
|
|
1207
|
-
}
|
|
1208
|
-
if (cmd === "/task") {
|
|
1209
|
-
return this.handleTask(trimmed.slice("/task".length).trim(), start);
|
|
1210
|
-
}
|
|
1211
|
-
if (cmd === "/config") {
|
|
1212
|
-
return this.handleConfig(start);
|
|
1213
|
-
}
|
|
1214
|
-
if (cmd === "/model") {
|
|
1215
|
-
return this.handleModel(start);
|
|
1216
|
-
}
|
|
1217
|
-
if (cmd === "/permissions") {
|
|
1218
|
-
return this.handlePermissions(trimmed.slice("/permissions".length).trim(), start);
|
|
1219
|
-
}
|
|
1220
|
-
if (cmd === "/plugins") {
|
|
1221
|
-
return this.handlePlugins(start);
|
|
1222
|
-
}
|
|
1223
|
-
if (cmd === "/usage") {
|
|
1224
|
-
return this.handleUsage(trimmed.slice("/usage".length).trim(), start);
|
|
1225
|
-
}
|
|
1226
|
-
if (cmd === "/context" || cmd === "/working-memory") {
|
|
1227
|
-
return this.handleContext(start, cwd);
|
|
1228
|
-
}
|
|
1229
|
-
if (cmd === "/review") {
|
|
1230
|
-
return this.handleReview(start);
|
|
1231
|
-
}
|
|
1232
|
-
if (cmd === "/fork") {
|
|
1233
|
-
return this.handleFork(trimmed.slice("/fork".length).trim(), start);
|
|
1234
|
-
}
|
|
1235
|
-
if (cmd === "/undo") {
|
|
1236
|
-
return this.handleUndo(start);
|
|
1237
|
-
}
|
|
1238
|
-
if (cmd === "/retry") {
|
|
1239
|
-
return {
|
|
1240
|
-
success: false,
|
|
1241
|
-
output: [
|
|
1242
|
-
"/retry is not supported yet.",
|
|
1243
|
-
"",
|
|
1244
|
-
formatFailureRecovery({
|
|
1245
|
-
kind: "runtime_interruption",
|
|
1246
|
-
label: "Retry unavailable",
|
|
1247
|
-
summary: "PulSeed does not yet have a safe replay contract for the previous turn.",
|
|
1248
|
-
nextActions: [
|
|
1249
|
-
"Use /review to inspect any current diff before continuing.",
|
|
1250
|
-
"Use /resume when PulSeed reports resumable agent-loop state.",
|
|
1251
|
-
"Ask for the exact next step to rerun instead of replaying the full turn.",
|
|
1252
|
-
],
|
|
1253
|
-
}),
|
|
1254
|
-
].join("\n"),
|
|
1255
|
-
elapsed_ms: Date.now() - start,
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
if (cmd === "/exit") {
|
|
1259
|
-
return { success: true, output: "Exiting chat mode.", elapsed_ms: Date.now() - start };
|
|
1260
|
-
}
|
|
1261
|
-
if (cmd === "/track") {
|
|
1262
|
-
return this.handleTrack(start);
|
|
1263
|
-
}
|
|
1264
|
-
if (cmd === "/tend") {
|
|
1265
|
-
const args = trimmed.slice("/tend".length).trim();
|
|
1266
|
-
return this.handleTend(args, start);
|
|
1267
|
-
}
|
|
1268
|
-
// Check if this is a confirmation response for a pending /tend
|
|
1269
|
-
if (this.pendingTend !== null) {
|
|
1270
|
-
return this.handleTendConfirmation(trimmed, start);
|
|
1271
|
-
}
|
|
1272
|
-
return {
|
|
1273
|
-
success: false,
|
|
1274
|
-
output: `Unknown command: ${input.trim()}. Type /help for available commands.`,
|
|
1275
|
-
elapsed_ms: Date.now() - start,
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
async handleTrack(start) {
|
|
1279
|
-
if (!this.deps.escalationHandler) {
|
|
1280
|
-
return {
|
|
1281
|
-
success: false,
|
|
1282
|
-
output: "Escalation not available — missing LLM configuration",
|
|
1283
|
-
elapsed_ms: Date.now() - start,
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
if (!this.history || this.history.getMessages().length === 0) {
|
|
1287
|
-
return {
|
|
1288
|
-
success: false,
|
|
1289
|
-
output: "No conversation to escalate. Chat first, then /track.",
|
|
1290
|
-
elapsed_ms: Date.now() - start,
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
try {
|
|
1294
|
-
const result = await this.deps.escalationHandler.escalateToGoal(this.history);
|
|
1295
|
-
return {
|
|
1296
|
-
success: true,
|
|
1297
|
-
output: `Goal created: ${result.title} (ID: ${result.goalId})\nRun: pulseed run --goal ${result.goalId} --yes`,
|
|
1298
|
-
elapsed_ms: Date.now() - start,
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
catch (err) {
|
|
1302
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1303
|
-
return {
|
|
1304
|
-
success: false,
|
|
1305
|
-
output: `Escalation failed: ${message}`,
|
|
1306
|
-
elapsed_ms: Date.now() - start,
|
|
1307
|
-
};
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
async handlePermissions(args, start) {
|
|
1311
|
-
const policy = await this.getSessionExecutionPolicy();
|
|
1312
|
-
if (!args) {
|
|
1313
|
-
return {
|
|
1314
|
-
success: true,
|
|
1315
|
-
output: summarizeExecutionPolicy(policy),
|
|
1316
|
-
elapsed_ms: Date.now() - start,
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
const tokens = args.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1320
|
-
let nextPolicy = policy;
|
|
1321
|
-
for (let index = 0; index < tokens.length; index++) {
|
|
1322
|
-
const token = tokens[index];
|
|
1323
|
-
if (token === "read-only" || token === "readonly" || token === "read_only") {
|
|
1324
|
-
nextPolicy = withExecutionPolicyOverrides(nextPolicy, { sandboxMode: "read_only" });
|
|
1325
|
-
continue;
|
|
1326
|
-
}
|
|
1327
|
-
if (token === "workspace-write" || token === "workspace_write") {
|
|
1328
|
-
nextPolicy = withExecutionPolicyOverrides(nextPolicy, { sandboxMode: "workspace_write" });
|
|
1329
|
-
continue;
|
|
1330
|
-
}
|
|
1331
|
-
if (token === "full-access" || token === "danger-full-access" || token === "danger_full_access") {
|
|
1332
|
-
nextPolicy = withExecutionPolicyOverrides(nextPolicy, { sandboxMode: "danger_full_access" });
|
|
1333
|
-
continue;
|
|
1334
|
-
}
|
|
1335
|
-
if (token === "network" && tokens[index + 1]) {
|
|
1336
|
-
nextPolicy = withExecutionPolicyOverrides(nextPolicy, { networkAccess: tokens[index + 1] === "on" });
|
|
1337
|
-
index += 1;
|
|
1338
|
-
continue;
|
|
1339
|
-
}
|
|
1340
|
-
if (token === "approval" && tokens[index + 1]) {
|
|
1341
|
-
const approvalPolicy = tokens[index + 1];
|
|
1342
|
-
if (approvalPolicy === "never" || approvalPolicy === "on_request" || approvalPolicy === "untrusted") {
|
|
1343
|
-
nextPolicy = withExecutionPolicyOverrides(nextPolicy, { approvalPolicy });
|
|
1344
|
-
index += 1;
|
|
1345
|
-
continue;
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
return {
|
|
1349
|
-
success: false,
|
|
1350
|
-
output: "Usage: /permissions [read-only|workspace-write|full-access] [network on|off] [approval on_request|never|untrusted]",
|
|
1351
|
-
elapsed_ms: Date.now() - start,
|
|
1352
|
-
};
|
|
1353
|
-
}
|
|
1354
|
-
this.sessionExecutionPolicy = nextPolicy;
|
|
1355
|
-
return {
|
|
1356
|
-
success: true,
|
|
1357
|
-
output: summarizeExecutionPolicy(nextPolicy),
|
|
1358
|
-
elapsed_ms: Date.now() - start,
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
1361
|
-
async handleReview(start) {
|
|
1362
|
-
const cwd = this.sessionCwd ?? process.cwd();
|
|
1363
|
-
const diffStat = await checkGitChanges(cwd);
|
|
1364
|
-
const reviewPolicy = withExecutionPolicyOverrides(await this.getSessionExecutionPolicy(), {
|
|
1365
|
-
sandboxMode: "read_only",
|
|
1366
|
-
approvalPolicy: "never",
|
|
1367
|
-
});
|
|
1368
|
-
if (this.deps.reviewAgentLoopRunner) {
|
|
1369
|
-
const review = await this.deps.reviewAgentLoopRunner.execute({
|
|
1370
|
-
cwd,
|
|
1371
|
-
diffStat,
|
|
1372
|
-
executionPolicy: reviewPolicy,
|
|
1373
|
-
});
|
|
1374
|
-
return { success: review.success, output: review.output, elapsed_ms: Date.now() - start };
|
|
1375
|
-
}
|
|
1376
|
-
const output = [
|
|
1377
|
-
"Review summary",
|
|
1378
|
-
diffStat ? diffStat : "No uncommitted changes detected.",
|
|
1379
|
-
"",
|
|
1380
|
-
"Execution policy",
|
|
1381
|
-
summarizeExecutionPolicy(reviewPolicy),
|
|
1382
|
-
].join("\n");
|
|
1383
|
-
return { success: true, output, elapsed_ms: Date.now() - start };
|
|
1384
|
-
}
|
|
1385
|
-
async handleFork(title, start) {
|
|
1386
|
-
const cwd = this.sessionCwd ?? process.cwd();
|
|
1387
|
-
const sessionId = crypto.randomUUID();
|
|
1388
|
-
const baseSession = this.history?.getSessionData() ?? {
|
|
1389
|
-
id: sessionId,
|
|
1390
|
-
cwd,
|
|
1391
|
-
createdAt: new Date().toISOString(),
|
|
1392
|
-
updatedAt: new Date().toISOString(),
|
|
1393
|
-
messages: [],
|
|
1394
|
-
};
|
|
1395
|
-
const now = new Date().toISOString();
|
|
1396
|
-
const forkedSession = {
|
|
1397
|
-
...baseSession,
|
|
1398
|
-
id: sessionId,
|
|
1399
|
-
createdAt: now,
|
|
1400
|
-
updatedAt: now,
|
|
1401
|
-
title: title || (baseSession.title ? `${baseSession.title} (fork)` : "Forked session"),
|
|
1402
|
-
};
|
|
1403
|
-
this.history = ChatHistory.fromSession(this.deps.stateManager, forkedSession);
|
|
1404
|
-
this.sessionCwd = cwd;
|
|
1405
|
-
this.sessionActive = true;
|
|
1406
|
-
this.nativeAgentLoopStatePath = `chat/agentloop/${sessionId}.state.json`;
|
|
1407
|
-
this.history.setAgentLoopStatePath(this.nativeAgentLoopStatePath);
|
|
1408
|
-
await this.history.persist();
|
|
1409
|
-
return {
|
|
1410
|
-
success: true,
|
|
1411
|
-
output: `Forked chat session as ${sessionId}.`,
|
|
1412
|
-
elapsed_ms: Date.now() - start,
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
async handleUndo(start) {
|
|
1416
|
-
if (!this.history) {
|
|
1417
|
-
return { success: false, output: "No active chat session to undo.", elapsed_ms: Date.now() - start };
|
|
1418
|
-
}
|
|
1419
|
-
const removed = await this.history.removeLastTurn();
|
|
1420
|
-
if (removed === 0) {
|
|
1421
|
-
return { success: false, output: "No chat turn to undo.", elapsed_ms: Date.now() - start };
|
|
1422
|
-
}
|
|
1423
|
-
return {
|
|
1424
|
-
success: true,
|
|
1425
|
-
output: `Removed ${removed} message(s) from chat history. File changes were not reverted.`,
|
|
1426
|
-
elapsed_ms: Date.now() - start,
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
async handleTend(args, start) {
|
|
1430
|
-
if (!this.deps.llmClient) {
|
|
1431
|
-
return {
|
|
1432
|
-
success: false,
|
|
1433
|
-
output: "Tend not available — missing LLM configuration",
|
|
1434
|
-
elapsed_ms: Date.now() - start,
|
|
1435
|
-
};
|
|
1436
|
-
}
|
|
1437
|
-
if (!this.deps.goalNegotiator) {
|
|
1438
|
-
return {
|
|
1439
|
-
success: false,
|
|
1440
|
-
output: "Tend not available — missing goal negotiator",
|
|
1441
|
-
elapsed_ms: Date.now() - start,
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
|
-
if (!this.deps.daemonClient) {
|
|
1445
|
-
return {
|
|
1446
|
-
success: false,
|
|
1447
|
-
output: "Tend not available — daemon client not configured. Start the daemon with 'pulseed daemon start' first.",
|
|
1448
|
-
elapsed_ms: Date.now() - start,
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
const history = this.history?.getMessages() ?? [];
|
|
1452
|
-
const tendDeps = {
|
|
1453
|
-
llmClient: this.deps.llmClient,
|
|
1454
|
-
goalNegotiator: this.deps.goalNegotiator,
|
|
1455
|
-
daemonClient: this.deps.daemonClient,
|
|
1456
|
-
stateManager: this.deps.stateManager,
|
|
1457
|
-
chatHistory: history,
|
|
1458
|
-
sessionId: this.history?.getSessionId() ?? null,
|
|
1459
|
-
workspace: this.sessionCwd ?? process.cwd(),
|
|
1460
|
-
replyTarget: this.runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget ?? null,
|
|
1461
|
-
};
|
|
1462
|
-
const tendCommand = new TendCommand();
|
|
1463
|
-
const result = await tendCommand.execute(args, tendDeps);
|
|
1464
|
-
if (result.needsConfirmation && result.goalId) {
|
|
1465
|
-
this.pendingTend = { goalId: result.goalId, maxIterations: result.maxIterations };
|
|
1466
|
-
return {
|
|
1467
|
-
success: true,
|
|
1468
|
-
output: result.confirmation ?? result.message,
|
|
1469
|
-
elapsed_ms: Date.now() - start,
|
|
1470
|
-
};
|
|
1471
|
-
}
|
|
1472
|
-
return {
|
|
1473
|
-
success: result.success,
|
|
1474
|
-
output: result.message,
|
|
1475
|
-
elapsed_ms: Date.now() - start,
|
|
1476
|
-
};
|
|
1477
|
-
}
|
|
1478
|
-
async handleTendConfirmation(input, start) {
|
|
1479
|
-
const pending = this.pendingTend;
|
|
1480
|
-
this.pendingTend = null;
|
|
1481
|
-
const normalized = input.trim().toLowerCase();
|
|
1482
|
-
const confirmed = normalized === "" || normalized === "y" || normalized === "yes";
|
|
1483
|
-
if (!confirmed) {
|
|
1484
|
-
// Bug 2: treat any non-y/yes/empty/n/no input as cancellation too
|
|
1485
|
-
return {
|
|
1486
|
-
success: true,
|
|
1487
|
-
output: "Tend cancelled. Continue chatting to refine your goal, then try /tend again.",
|
|
1488
|
-
elapsed_ms: Date.now() - start,
|
|
1489
|
-
};
|
|
1490
|
-
}
|
|
1491
|
-
if (!this.deps.daemonClient) {
|
|
1492
|
-
return {
|
|
1493
|
-
success: false,
|
|
1494
|
-
output: "Daemon client not available.",
|
|
1495
|
-
elapsed_ms: Date.now() - start,
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
const { goalId, maxIterations } = pending;
|
|
1499
|
-
let subscriber = null;
|
|
1500
|
-
if (this.deps.daemonBaseUrl && !this.activeSubscribers.has(goalId)) {
|
|
1501
|
-
subscriber = new EventSubscriber(this.deps.daemonBaseUrl, goalId, "normal");
|
|
1502
|
-
this.activeSubscribers.set(goalId, subscriber);
|
|
1503
|
-
subscriber.on("notification", (notification) => {
|
|
1504
|
-
const n = notification;
|
|
1505
|
-
this.deps.onNotification?.(n.message);
|
|
1506
|
-
this.onNotification?.(n.message);
|
|
1507
|
-
});
|
|
1508
|
-
subscriber.on("chat_event", (event) => {
|
|
1509
|
-
this.emitEvent(event);
|
|
1510
|
-
});
|
|
1511
|
-
try {
|
|
1512
|
-
await subscriber.subscribeReady();
|
|
1513
|
-
}
|
|
1514
|
-
catch (err) {
|
|
1515
|
-
subscriber.unsubscribe();
|
|
1516
|
-
this.activeSubscribers.delete(goalId);
|
|
1517
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1518
|
-
return {
|
|
1519
|
-
success: false,
|
|
1520
|
-
output: `Daemon event stream unavailable: ${msg}. Goal was not started.`,
|
|
1521
|
-
elapsed_ms: Date.now() - start,
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
try {
|
|
1526
|
-
const tendDeps = {
|
|
1527
|
-
llmClient: this.deps.llmClient,
|
|
1528
|
-
goalNegotiator: this.deps.goalNegotiator,
|
|
1529
|
-
daemonClient: this.deps.daemonClient,
|
|
1530
|
-
stateManager: this.deps.stateManager,
|
|
1531
|
-
chatHistory: this.history?.getMessages() ?? [],
|
|
1532
|
-
sessionId: this.history?.getSessionId() ?? null,
|
|
1533
|
-
workspace: this.sessionCwd ?? process.cwd(),
|
|
1534
|
-
replyTarget: this.runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget ?? null,
|
|
1535
|
-
};
|
|
1536
|
-
const result = await new TendCommand().startAcceptedGoal(goalId, maxIterations, tendDeps);
|
|
1537
|
-
if (!result.success) {
|
|
1538
|
-
if (subscriber) {
|
|
1539
|
-
subscriber.unsubscribe();
|
|
1540
|
-
this.activeSubscribers.delete(goalId);
|
|
1541
|
-
}
|
|
1542
|
-
return {
|
|
1543
|
-
success: false,
|
|
1544
|
-
output: result.message,
|
|
1545
|
-
elapsed_ms: Date.now() - start,
|
|
1546
|
-
};
|
|
1547
|
-
}
|
|
1548
|
-
const shortId = goalId.length > 12 ? goalId.slice(0, 12) : goalId;
|
|
1549
|
-
return {
|
|
1550
|
-
success: true,
|
|
1551
|
-
output: `[tend] ${shortId}: Started — daemon is now tending your goal${maxIterations !== undefined ? ` (max ${maxIterations} iterations)` : ""}.\nBackground run: ${result.backgroundRunId}\nRun 'pulseed status' to check progress.`,
|
|
1552
|
-
elapsed_ms: Date.now() - start,
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
catch (err) {
|
|
1556
|
-
if (subscriber) {
|
|
1557
|
-
subscriber.unsubscribe();
|
|
1558
|
-
this.activeSubscribers.delete(goalId);
|
|
1559
|
-
}
|
|
1560
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1561
|
-
return {
|
|
1562
|
-
success: false,
|
|
1563
|
-
output: `Daemon unavailable: ${msg}. Start the daemon with 'pulseed daemon start' first.`,
|
|
1564
|
-
elapsed_ms: Date.now() - start,
|
|
1565
|
-
};
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
/**
|
|
1569
|
-
* Execute a single chat turn.
|
|
1570
|
-
*
|
|
1571
|
-
* Flow:
|
|
1572
|
-
* 1. Intercept slash commands before adapter dispatch
|
|
1573
|
-
* 2. Resolve git root → create ChatHistory
|
|
1574
|
-
* 3. Build chat context and assemble prompt
|
|
1575
|
-
* 4. Persist user message BEFORE calling adapter (crash-safe)
|
|
1576
|
-
* 5. Execute via adapter
|
|
1577
|
-
* 6. Verify changes (git diff + tests); retry up to MAX_VERIFY_RETRIES if tests fail
|
|
1578
|
-
* 7. Persist assistant response only after the final assistant text is complete
|
|
1579
|
-
*/
|
|
1580
|
-
async execute(input, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, options = {}) {
|
|
1581
|
-
const eventContext = this.createEventContext();
|
|
1582
|
-
const activeTurn = this.beginActiveTurn(eventContext, resolveGitRoot(cwd));
|
|
1583
|
-
const resumeCommand = this.parseResumeCommand(input);
|
|
1584
|
-
const resumeOnly = resumeCommand !== null;
|
|
1585
|
-
const runtimeControlContext = options.runtimeControlContext ?? this.runtimeControlContext;
|
|
1586
|
-
const executionGoalId = options.goalId ?? this.deps.goalId;
|
|
1587
|
-
// Intercept commands before any adapter call
|
|
1588
|
-
const commandResult = resumeOnly ? null : await this.handleCommand(input, cwd);
|
|
1589
|
-
if (commandResult !== null) {
|
|
1590
|
-
if (commandResult.output) {
|
|
1591
|
-
this.emitEvent({
|
|
1592
|
-
type: "assistant_final",
|
|
1593
|
-
text: commandResult.output,
|
|
1594
|
-
persisted: false,
|
|
1595
|
-
...this.eventBase(eventContext),
|
|
1596
|
-
});
|
|
1597
|
-
}
|
|
1598
|
-
this.emitLifecycleEndEvent(commandResult.success ? "completed" : "error", commandResult.elapsed_ms, eventContext, false);
|
|
1599
|
-
return commandResult;
|
|
1600
|
-
}
|
|
1601
|
-
// Intercept plain Y/n responses (and any other input) when a /tend confirmation is pending
|
|
1602
|
-
if (this.pendingTend !== null && !resumeOnly) {
|
|
1603
|
-
const confirmationResult = await this.handleTendConfirmation(input.trim(), Date.now());
|
|
1604
|
-
if (confirmationResult.output) {
|
|
1605
|
-
this.emitEvent({
|
|
1606
|
-
type: "assistant_final",
|
|
1607
|
-
text: confirmationResult.output,
|
|
1608
|
-
persisted: false,
|
|
1609
|
-
...this.eventBase(eventContext),
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1612
|
-
this.emitLifecycleEndEvent(confirmationResult.success ? "completed" : "error", confirmationResult.elapsed_ms, eventContext, false);
|
|
1613
|
-
return confirmationResult;
|
|
1614
|
-
}
|
|
1615
|
-
if (resumeOnly && resumeCommand.selector) {
|
|
1616
|
-
try {
|
|
1617
|
-
const selectorResolution = await this.resolveChatResumeSelector(resumeCommand.selector);
|
|
1618
|
-
if (selectorResolution.nonResumableMessage) {
|
|
1619
|
-
const elapsed_ms = 0;
|
|
1620
|
-
const output = selectorResolution.nonResumableMessage;
|
|
1621
|
-
this.emitEvent({
|
|
1622
|
-
type: "assistant_final",
|
|
1623
|
-
text: output,
|
|
1624
|
-
persisted: false,
|
|
1625
|
-
...this.eventBase(eventContext),
|
|
1626
|
-
});
|
|
1627
|
-
this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
1628
|
-
return { success: false, output, elapsed_ms };
|
|
1629
|
-
}
|
|
1630
|
-
const catalog = new ChatSessionCatalog(this.deps.stateManager);
|
|
1631
|
-
const session = await catalog.loadSessionBySelector(selectorResolution.chatSelector);
|
|
1632
|
-
if (!session) {
|
|
1633
|
-
const elapsed_ms = 0;
|
|
1634
|
-
const output = `No chat session matched selector "${selectorResolution.chatSelector}".`;
|
|
1635
|
-
this.emitEvent({
|
|
1636
|
-
type: "assistant_final",
|
|
1637
|
-
text: output,
|
|
1638
|
-
persisted: false,
|
|
1639
|
-
...this.eventBase(eventContext),
|
|
1640
|
-
});
|
|
1641
|
-
this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
1642
|
-
return { success: false, output, elapsed_ms };
|
|
1643
|
-
}
|
|
1644
|
-
this.startSessionFromLoadedSession(session);
|
|
1645
|
-
}
|
|
1646
|
-
catch (err) {
|
|
1647
|
-
const elapsed_ms = 0;
|
|
1648
|
-
const output = err instanceof ChatSessionSelectorError ? err.message : `Failed to load chat session: ${err instanceof Error ? err.message : String(err)}`;
|
|
1649
|
-
this.emitEvent({
|
|
1650
|
-
type: "assistant_final",
|
|
1651
|
-
text: output,
|
|
1652
|
-
persisted: false,
|
|
1653
|
-
...this.eventBase(eventContext),
|
|
1654
|
-
});
|
|
1655
|
-
this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
1656
|
-
return { success: false, output, elapsed_ms };
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
// Reuse session (interactive mode) or create a fresh one per call (1-shot mode)
|
|
1660
|
-
if (!this.sessionActive) {
|
|
1661
|
-
const gitRoot = resolveGitRoot(cwd);
|
|
1662
|
-
const sessionId = crypto.randomUUID();
|
|
1663
|
-
this.history = new ChatHistory(this.deps.stateManager, sessionId, gitRoot);
|
|
1664
|
-
this.nativeAgentLoopStatePath = `chat/agentloop/${sessionId}.state.json`;
|
|
1665
|
-
this.history.setAgentLoopStatePath(this.nativeAgentLoopStatePath);
|
|
1666
|
-
}
|
|
1667
|
-
const executionCwd = this.sessionCwd ?? cwd;
|
|
1668
|
-
const gitRoot = this.sessionCwd ?? resolveGitRoot(cwd);
|
|
1669
|
-
activeTurn.cwd = gitRoot;
|
|
1670
|
-
// history is always assigned by this point (either by startSession or the block above)
|
|
1671
|
-
const history = this.history;
|
|
1672
|
-
this.emitEvent({
|
|
1673
|
-
type: "lifecycle_start",
|
|
1674
|
-
input,
|
|
1675
|
-
...this.eventBase(eventContext),
|
|
1676
|
-
});
|
|
1677
|
-
// Persist-before-execute: user message written to disk before model or adapter execution.
|
|
1678
|
-
if (!resumeOnly) {
|
|
1679
|
-
await history.appendUserMessage(input);
|
|
1680
|
-
}
|
|
1681
|
-
// Build static grounding once per session; dynamic context is rebuilt each turn.
|
|
1682
|
-
if (this.cachedStaticSystemPrompt === null) {
|
|
1683
|
-
try {
|
|
1684
|
-
this.cachedStaticSystemPrompt = buildStaticSystemPrompt(this.providerConfigBaseDir());
|
|
1685
|
-
}
|
|
1686
|
-
catch {
|
|
1687
|
-
this.cachedStaticSystemPrompt = "";
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
// Build conversation history from prior turns (last 10), including any manual compaction summary.
|
|
1691
|
-
const messages = history.getMessages();
|
|
1692
|
-
const compactionSummary = history.getSessionData().compactionSummary;
|
|
1693
|
-
const priorTurns = resumeOnly ? messages.slice(-10) : messages.slice(0, -1).slice(-10);
|
|
1694
|
-
let historyBlock = "";
|
|
1695
|
-
const historySections = [];
|
|
1696
|
-
if (compactionSummary) {
|
|
1697
|
-
historySections.push(`Compacted previous conversation summary:\n${compactionSummary}`);
|
|
1698
|
-
}
|
|
1699
|
-
if (priorTurns.length > 0) {
|
|
1700
|
-
const lines = priorTurns.map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.content}`).join("\n");
|
|
1701
|
-
historySections.push(`Previous conversation:\n${lines}`);
|
|
1702
|
-
}
|
|
1703
|
-
if (historySections.length > 0) {
|
|
1704
|
-
historyBlock = `${historySections.join("\n\n")}\n\nCurrent message:\n`;
|
|
1705
|
-
}
|
|
1706
|
-
const selectedRoute = resumeOnly
|
|
1707
|
-
? null
|
|
1708
|
-
: (options.selectedRoute ?? this.resolveRouteFromInput(input, runtimeControlContext));
|
|
1709
|
-
this.lastSelectedRoute = selectedRoute;
|
|
1710
|
-
const directPrompt = historyBlock ? `${historyBlock}${input}` : input;
|
|
1711
|
-
if (!resumeOnly) {
|
|
1712
|
-
this.emitIntent(input, selectedRoute, eventContext);
|
|
1713
|
-
}
|
|
1714
|
-
else if (resumeOnly) {
|
|
1715
|
-
this.emitIntent(input, null, eventContext);
|
|
1716
|
-
}
|
|
1717
|
-
const start = Date.now();
|
|
1718
|
-
const assistantBuffer = { text: "" };
|
|
1719
|
-
const turnUsage = this.zeroUsageCounter();
|
|
1720
|
-
const identityResponse = resumeOnly ? null : resolveSelfIdentityResponse(input, this.providerConfigBaseDir());
|
|
1721
|
-
if (identityResponse !== null) {
|
|
1722
|
-
const elapsed_ms = Date.now() - start;
|
|
1723
|
-
await history.appendAssistantMessage(identityResponse);
|
|
1724
|
-
this.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
1725
|
-
this.emitEvent({
|
|
1726
|
-
type: "assistant_final",
|
|
1727
|
-
text: identityResponse,
|
|
1728
|
-
persisted: true,
|
|
1729
|
-
...this.eventBase(eventContext),
|
|
1730
|
-
});
|
|
1731
|
-
this.emitLifecycleEndEvent("completed", elapsed_ms, eventContext, true);
|
|
1732
|
-
return {
|
|
1733
|
-
success: true,
|
|
1734
|
-
output: identityResponse,
|
|
1735
|
-
elapsed_ms,
|
|
1736
|
-
};
|
|
1737
|
-
}
|
|
1738
|
-
if (selectedRoute?.kind === "runtime_control") {
|
|
1739
|
-
this.emitCheckpoint("Runtime control selected", `${selectedRoute.intent.kind} request recognized.`, eventContext, "route");
|
|
1740
|
-
const runtimeControlResult = await this.executeRuntimeControlRoute(selectedRoute, runtimeControlContext, executionCwd, start);
|
|
1741
|
-
if (runtimeControlResult.success) {
|
|
1742
|
-
await history.appendAssistantMessage(runtimeControlResult.output);
|
|
1743
|
-
this.emitCheckpoint("Runtime control completed", "The runtime-control operation produced a result.", eventContext, "complete");
|
|
1744
|
-
this.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
1745
|
-
this.emitEvent({
|
|
1746
|
-
type: "assistant_final",
|
|
1747
|
-
text: runtimeControlResult.output,
|
|
1748
|
-
persisted: true,
|
|
1749
|
-
...this.eventBase(eventContext),
|
|
1750
|
-
});
|
|
1751
|
-
this.emitLifecycleEndEvent("completed", runtimeControlResult.elapsed_ms, eventContext, true);
|
|
1752
|
-
}
|
|
1753
|
-
else {
|
|
1754
|
-
runtimeControlResult.output = this.emitLifecycleErrorEvent(runtimeControlResult.output, assistantBuffer.text, eventContext);
|
|
1755
|
-
this.emitLifecycleEndEvent("error", runtimeControlResult.elapsed_ms, eventContext, false);
|
|
1756
|
-
}
|
|
1757
|
-
return runtimeControlResult;
|
|
1758
|
-
}
|
|
1759
|
-
if (selectedRoute?.kind === "daemon_tend") {
|
|
1760
|
-
this.emitCheckpoint("Durable goal selected", "This long-running request is being prepared for daemon-backed CoreLoop execution.", eventContext, "route");
|
|
1761
|
-
const tendResult = await this.handleTend("", start);
|
|
1762
|
-
if (tendResult.success) {
|
|
1763
|
-
await history.appendAssistantMessage(tendResult.output);
|
|
1764
|
-
this.emitCheckpoint("Durable goal prepared", "The daemon-backed goal confirmation is ready.", eventContext, "complete");
|
|
1765
|
-
this.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
1766
|
-
this.emitEvent({
|
|
1767
|
-
type: "assistant_final",
|
|
1768
|
-
text: tendResult.output,
|
|
1769
|
-
persisted: true,
|
|
1770
|
-
...this.eventBase(eventContext),
|
|
1771
|
-
});
|
|
1772
|
-
this.emitLifecycleEndEvent("completed", tendResult.elapsed_ms, eventContext, true);
|
|
1773
|
-
}
|
|
1774
|
-
else {
|
|
1775
|
-
tendResult.output = this.emitLifecycleErrorEvent(tendResult.output, assistantBuffer.text, eventContext);
|
|
1776
|
-
this.emitLifecycleEndEvent("error", tendResult.elapsed_ms, eventContext, false);
|
|
1777
|
-
}
|
|
1778
|
-
return tendResult;
|
|
1779
|
-
}
|
|
1780
|
-
if (selectedRoute?.kind === "direct_answer") {
|
|
1781
|
-
try {
|
|
1782
|
-
this.emitActivity("lifecycle", "Calling model...", eventContext, "lifecycle:model");
|
|
1783
|
-
const directResponse = await this.sendLLMMessage(this.deps.llmClient, [{ role: "user", content: directPrompt }], {
|
|
1784
|
-
...(this.cachedStaticSystemPrompt ? { system: this.cachedStaticSystemPrompt } : {}),
|
|
1785
|
-
model_tier: selectedRoute.modelTier,
|
|
1786
|
-
max_tokens: selectedRoute.maxTokens,
|
|
1787
|
-
}, assistantBuffer, eventContext);
|
|
1788
|
-
this.addUsageCounter(turnUsage, this.usageFromLLMResponse(directResponse));
|
|
1789
|
-
const elapsed_ms = Date.now() - start;
|
|
1790
|
-
const output = assistantBuffer.text || directResponse.content || "(no response)";
|
|
1791
|
-
if (this.hasUsage(turnUsage)) {
|
|
1792
|
-
history.recordUsage("execution", turnUsage);
|
|
1793
|
-
}
|
|
1794
|
-
const diffArtifact = await collectGitDiffArtifact(gitRoot);
|
|
1795
|
-
if (diffArtifact) {
|
|
1796
|
-
this.emitDiffArtifact(diffArtifact, eventContext);
|
|
1797
|
-
}
|
|
1798
|
-
await history.appendAssistantMessage(output);
|
|
1799
|
-
this.emitCheckpoint("Response ready", "The direct answer has been persisted for this turn.", eventContext, "complete");
|
|
1800
|
-
this.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
1801
|
-
this.emitEvent({
|
|
1802
|
-
type: "assistant_final",
|
|
1803
|
-
text: output,
|
|
1804
|
-
persisted: true,
|
|
1805
|
-
...this.eventBase(eventContext),
|
|
1806
|
-
});
|
|
1807
|
-
this.emitLifecycleEndEvent("completed", elapsed_ms, eventContext, true);
|
|
1808
|
-
return {
|
|
1809
|
-
success: true,
|
|
1810
|
-
output,
|
|
1811
|
-
elapsed_ms,
|
|
1812
|
-
diagnostics: {
|
|
1813
|
-
route: "direct",
|
|
1814
|
-
reason: selectedRoute.reason,
|
|
1815
|
-
modelTier: selectedRoute.modelTier,
|
|
1816
|
-
maxTokens: selectedRoute.maxTokens,
|
|
1817
|
-
},
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
catch (err) {
|
|
1821
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1822
|
-
const output = this.emitLifecycleErrorEvent(message, assistantBuffer.text, eventContext);
|
|
1823
|
-
this.emitLifecycleEndEvent("error", Date.now() - start, eventContext, false);
|
|
1824
|
-
return {
|
|
1825
|
-
success: false,
|
|
1826
|
-
output,
|
|
1827
|
-
elapsed_ms: Date.now() - start,
|
|
1828
|
-
diagnostics: {
|
|
1829
|
-
route: "direct",
|
|
1830
|
-
reason: selectedRoute.reason,
|
|
1831
|
-
modelTier: selectedRoute.modelTier,
|
|
1832
|
-
maxTokens: selectedRoute.maxTokens,
|
|
1833
|
-
},
|
|
1834
|
-
};
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
const usesNativeAgentLoop = resumeOnly || selectedRoute?.kind === "agent_loop";
|
|
1838
|
-
const groundingWorkspaceContext = !resumeOnly && usesNativeAgentLoop
|
|
1839
|
-
? await buildChatContext(input, executionCwd)
|
|
1840
|
-
: undefined;
|
|
1841
|
-
let systemPrompt = this.cachedStaticSystemPrompt ?? "";
|
|
1842
|
-
if (!resumeOnly) {
|
|
1843
|
-
try {
|
|
1844
|
-
this.emitActivity("lifecycle", "Preparing context...", eventContext, "lifecycle:context");
|
|
1845
|
-
if (usesNativeAgentLoop) {
|
|
1846
|
-
systemPrompt = await buildChatAgentLoopSystemPrompt({
|
|
1847
|
-
stateManager: this.deps.stateManager,
|
|
1848
|
-
pluginLoader: this.deps.pluginLoader,
|
|
1849
|
-
workspaceRoot: executionCwd,
|
|
1850
|
-
goalId: executionGoalId,
|
|
1851
|
-
userMessage: input,
|
|
1852
|
-
trustProjectInstructions: this.sessionExecutionPolicy?.trustProjectInstructions ?? true,
|
|
1853
|
-
workspaceContext: groundingWorkspaceContext,
|
|
1854
|
-
});
|
|
1855
|
-
}
|
|
1856
|
-
else {
|
|
1857
|
-
const groundingBundle = await this.groundingGateway.build({
|
|
1858
|
-
surface: "chat",
|
|
1859
|
-
purpose: "general_turn",
|
|
1860
|
-
workspaceRoot: executionCwd,
|
|
1861
|
-
goalId: executionGoalId,
|
|
1862
|
-
userMessage: input,
|
|
1863
|
-
query: input,
|
|
1864
|
-
trustProjectInstructions: this.sessionExecutionPolicy?.trustProjectInstructions ?? true,
|
|
1865
|
-
});
|
|
1866
|
-
systemPrompt = String(groundingBundle.render("prompt"));
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
catch {
|
|
1870
|
-
systemPrompt = this.cachedStaticSystemPrompt ?? "";
|
|
1871
|
-
}
|
|
1872
|
-
this.emitCheckpoint("Context gathered", usesNativeAgentLoop
|
|
1873
|
-
? "Workspace and agent-loop grounding are ready."
|
|
1874
|
-
: "Workspace grounding is ready.", eventContext, "context");
|
|
1875
|
-
}
|
|
1876
|
-
const agentLoopSystemPrompt = [
|
|
1877
|
-
systemPrompt,
|
|
1878
|
-
compactionSummary ? `## Compacted Chat Summary\n${compactionSummary}` : "",
|
|
1879
|
-
]
|
|
1880
|
-
.filter((section) => section && section.trim().length > 0)
|
|
1881
|
-
.join("\n\n")
|
|
1882
|
-
.trim();
|
|
1883
|
-
const context = resumeOnly || usesNativeAgentLoop ? "" : await buildChatContext(input, gitRoot);
|
|
1884
|
-
const basePrompt = resumeOnly ? "" : (context ? `${context}\n\n${input}` : input);
|
|
1885
|
-
const prompt = historyBlock ? `${historyBlock}${basePrompt}` : basePrompt;
|
|
1886
|
-
if (resumeOnly && !this.deps.chatAgentLoopRunner) {
|
|
1887
|
-
const elapsed_ms = Date.now() - start;
|
|
1888
|
-
const output = this.emitLifecycleErrorEvent("Resume requires the native chat agentloop runtime.", assistantBuffer.text, eventContext);
|
|
1889
|
-
this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
1890
|
-
return {
|
|
1891
|
-
success: false,
|
|
1892
|
-
output,
|
|
1893
|
-
elapsed_ms,
|
|
1894
|
-
};
|
|
1895
|
-
}
|
|
1896
|
-
const chatAgentLoopRunner = this.deps.chatAgentLoopRunner;
|
|
1897
|
-
if (resumeOnly || selectedRoute?.kind === "agent_loop") {
|
|
1898
|
-
try {
|
|
1899
|
-
const resumeState = resumeOnly ? await this.loadResumableAgentLoopState() : null;
|
|
1900
|
-
if (resumeOnly && !resumeState) {
|
|
1901
|
-
const elapsed_ms = Date.now() - start;
|
|
1902
|
-
const output = this.emitLifecycleErrorEvent("No resumable native agentloop state found.", assistantBuffer.text, eventContext);
|
|
1903
|
-
this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
1904
|
-
return {
|
|
1905
|
-
success: false,
|
|
1906
|
-
output,
|
|
1907
|
-
elapsed_ms,
|
|
1908
|
-
};
|
|
1909
|
-
}
|
|
1910
|
-
this.emitCheckpoint(resumeOnly ? "Session resumed" : "Agent loop started", resumeOnly
|
|
1911
|
-
? "Resumable agent-loop state is loaded."
|
|
1912
|
-
: "The agent loop can now inspect, plan, edit, or verify with visible tool activity.", eventContext, "execution");
|
|
1913
|
-
this.emitActivity("lifecycle", "Calling model...", eventContext, "lifecycle:model");
|
|
1914
|
-
const result = await chatAgentLoopRunner.execute({
|
|
1915
|
-
message: basePrompt,
|
|
1916
|
-
cwd: executionCwd,
|
|
1917
|
-
goalId: executionGoalId,
|
|
1918
|
-
history: priorTurns.map((m) => ({
|
|
1919
|
-
role: m.role === "assistant" ? "assistant" : "user",
|
|
1920
|
-
content: m.content,
|
|
1921
|
-
})),
|
|
1922
|
-
eventSink: this.createAgentLoopEventSink(eventContext),
|
|
1923
|
-
approvalFn: async (request) => {
|
|
1924
|
-
if (this.deps.approvalFn) {
|
|
1925
|
-
return this.deps.approvalFn(request.reason);
|
|
1926
|
-
}
|
|
1927
|
-
return false;
|
|
1928
|
-
},
|
|
1929
|
-
toolCallContext: {
|
|
1930
|
-
executionPolicy: await this.getSessionExecutionPolicy(),
|
|
1931
|
-
},
|
|
1932
|
-
...(this.nativeAgentLoopStatePath ? { resumeStatePath: this.nativeAgentLoopStatePath } : {}),
|
|
1933
|
-
...(resumeState ? { resumeState } : {}),
|
|
1934
|
-
...(resumeOnly ? { resumeOnly: true } : {}),
|
|
1935
|
-
...(agentLoopSystemPrompt ? { systemPrompt: agentLoopSystemPrompt } : {}),
|
|
1936
|
-
abortSignal: activeTurn.abortController.signal,
|
|
1937
|
-
});
|
|
1938
|
-
const elapsed_ms = Date.now() - start;
|
|
1939
|
-
const agentLoopUsage = result.agentLoop?.usage
|
|
1940
|
-
? this.normalizeUsageCounter(result.agentLoop.usage)
|
|
1941
|
-
: this.zeroUsageCounter();
|
|
1942
|
-
if (this.hasUsage(agentLoopUsage)) {
|
|
1943
|
-
history.recordUsage("agentloop", agentLoopUsage);
|
|
1944
|
-
}
|
|
1945
|
-
if (result.output) {
|
|
1946
|
-
this.pushAssistantDelta(result.output, assistantBuffer, eventContext);
|
|
1947
|
-
}
|
|
1948
|
-
if (result.success) {
|
|
1949
|
-
const diffArtifact = await collectGitDiffArtifact(gitRoot);
|
|
1950
|
-
if (diffArtifact) {
|
|
1951
|
-
this.emitDiffArtifact(diffArtifact, eventContext);
|
|
1952
|
-
}
|
|
1953
|
-
await history.appendAssistantMessage(result.output);
|
|
1954
|
-
this.emitCheckpoint("Response ready", "The agent-loop response has been persisted for this turn.", eventContext, "complete");
|
|
1955
|
-
this.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
1956
|
-
this.emitEvent({
|
|
1957
|
-
type: "assistant_final",
|
|
1958
|
-
text: result.output,
|
|
1959
|
-
persisted: true,
|
|
1960
|
-
...this.eventBase(eventContext),
|
|
1961
|
-
});
|
|
1962
|
-
this.emitLifecycleEndEvent("completed", elapsed_ms, eventContext, true);
|
|
1963
|
-
}
|
|
1964
|
-
else {
|
|
1965
|
-
result.output = this.emitLifecycleErrorEvent(result.output || result.error || "Unknown error", assistantBuffer.text, eventContext);
|
|
1966
|
-
this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
1967
|
-
}
|
|
1968
|
-
return {
|
|
1969
|
-
success: result.success,
|
|
1970
|
-
output: result.output,
|
|
1971
|
-
elapsed_ms,
|
|
1972
|
-
};
|
|
1973
|
-
}
|
|
1974
|
-
catch (err) {
|
|
1975
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1976
|
-
const output = this.emitLifecycleErrorEvent(message, assistantBuffer.text, eventContext);
|
|
1977
|
-
this.emitLifecycleEndEvent("error", Date.now() - start, eventContext, false);
|
|
1978
|
-
return {
|
|
1979
|
-
success: false,
|
|
1980
|
-
output,
|
|
1981
|
-
elapsed_ms: Date.now() - start,
|
|
1982
|
-
};
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
// Prefer the local LLM/tool loop over the external adapter fallback whenever a client is available.
|
|
1986
|
-
if (selectedRoute?.kind === "tool_loop") {
|
|
1987
|
-
try {
|
|
1988
|
-
this.emitCheckpoint("Tool loop started", "The model will choose tools from the active catalog.", eventContext, "execution");
|
|
1989
|
-
const toolResult = await this.executeWithTools(prompt, eventContext, assistantBuffer, systemPrompt || undefined, executionGoalId);
|
|
1990
|
-
const elapsed_ms = Date.now() - start;
|
|
1991
|
-
if (this.hasUsage(toolResult.usage)) {
|
|
1992
|
-
history.recordUsage("execution", toolResult.usage);
|
|
1993
|
-
}
|
|
1994
|
-
const diffArtifact = await collectGitDiffArtifact(gitRoot);
|
|
1995
|
-
if (diffArtifact) {
|
|
1996
|
-
this.emitDiffArtifact(diffArtifact, eventContext);
|
|
1997
|
-
}
|
|
1998
|
-
await history.appendAssistantMessage(toolResult.output);
|
|
1999
|
-
this.emitCheckpoint("Response ready", "The tool-loop response has been persisted for this turn.", eventContext, "complete");
|
|
2000
|
-
this.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
2001
|
-
this.emitEvent({
|
|
2002
|
-
type: "assistant_final",
|
|
2003
|
-
text: toolResult.output,
|
|
2004
|
-
persisted: true,
|
|
2005
|
-
...this.eventBase(eventContext),
|
|
2006
|
-
});
|
|
2007
|
-
this.emitLifecycleEndEvent("completed", elapsed_ms, eventContext, true);
|
|
2008
|
-
return { success: true, output: toolResult.output, elapsed_ms };
|
|
2009
|
-
}
|
|
2010
|
-
catch (err) {
|
|
2011
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2012
|
-
const output = this.emitLifecycleErrorEvent(message, assistantBuffer.text, eventContext);
|
|
2013
|
-
this.emitLifecycleEndEvent("error", Date.now() - start, eventContext, false);
|
|
2014
|
-
return {
|
|
2015
|
-
success: false,
|
|
2016
|
-
output,
|
|
2017
|
-
elapsed_ms: Date.now() - start,
|
|
2018
|
-
};
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
if (!resumeOnly && selectedRoute && selectedRoute.kind !== "adapter") {
|
|
2022
|
-
const elapsed_ms = Date.now() - start;
|
|
2023
|
-
const output = this.emitLifecycleErrorEvent(`Unsupported chat route: ${selectedRoute.kind}`, assistantBuffer.text, eventContext);
|
|
2024
|
-
this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
2025
|
-
return {
|
|
2026
|
-
success: false,
|
|
2027
|
-
output,
|
|
2028
|
-
elapsed_ms,
|
|
2029
|
-
};
|
|
2030
|
-
}
|
|
2031
|
-
const task = {
|
|
2032
|
-
prompt,
|
|
2033
|
-
timeout_ms: timeoutMs,
|
|
2034
|
-
adapter_type: this.deps.adapter.adapterType,
|
|
2035
|
-
cwd,
|
|
2036
|
-
...(systemPrompt ? { system_prompt: systemPrompt } : {}),
|
|
2037
|
-
};
|
|
2038
|
-
const resolvedTimeoutMs = task.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
2039
|
-
this.emitCheckpoint("Adapter started", "The configured adapter has the current prompt and project context.", eventContext, "execution");
|
|
2040
|
-
this.emitActivity("lifecycle", "Calling adapter...", eventContext, "lifecycle:adapter");
|
|
2041
|
-
const adapterPromise = this.deps.adapter.execute(task);
|
|
2042
|
-
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Chat adapter timed out after ${resolvedTimeoutMs}ms`)), resolvedTimeoutMs));
|
|
2043
|
-
let result;
|
|
2044
|
-
try {
|
|
2045
|
-
result = await Promise.race([adapterPromise, timeoutPromise]);
|
|
2046
|
-
}
|
|
2047
|
-
catch (err) {
|
|
2048
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2049
|
-
const output = this.emitLifecycleErrorEvent(message, assistantBuffer.text, eventContext);
|
|
2050
|
-
const timeoutElapsedMs = Date.now() - start;
|
|
2051
|
-
this.emitLifecycleEndEvent("error", timeoutElapsedMs, eventContext, false);
|
|
2052
|
-
return {
|
|
2053
|
-
success: false,
|
|
2054
|
-
output,
|
|
2055
|
-
elapsed_ms: timeoutElapsedMs,
|
|
2056
|
-
};
|
|
2057
|
-
}
|
|
2058
|
-
// Surface adapter errors into output when output is empty
|
|
2059
|
-
if (!result.output && result.error) {
|
|
2060
|
-
result = { ...result, output: `Error: ${result.error}` };
|
|
2061
|
-
}
|
|
2062
|
-
const elapsed_ms = Date.now() - start;
|
|
2063
|
-
if (result.output) {
|
|
2064
|
-
this.pushAssistantDelta(result.output, assistantBuffer, eventContext);
|
|
2065
|
-
}
|
|
2066
|
-
// Verification loop: check if git has uncommitted changes; if so, run tests
|
|
2067
|
-
const diffArtifact = await collectGitDiffArtifact(gitRoot);
|
|
2068
|
-
if (diffArtifact) {
|
|
2069
|
-
let retries = 0;
|
|
2070
|
-
const VERIFY_TIMEOUT_MS = 30_000;
|
|
2071
|
-
this.emitCheckpoint("Changes detected", "Verification is starting because the turn changed the working tree.", eventContext, "changes");
|
|
2072
|
-
this.emitActivity("lifecycle", "Checking result...", eventContext, "lifecycle:checking");
|
|
2073
|
-
let verification = await Promise.race([
|
|
2074
|
-
verifyChatAction(gitRoot, this.deps.toolExecutor, { force: true }),
|
|
2075
|
-
new Promise((resolve) => setTimeout(() => resolve({ passed: true }), VERIFY_TIMEOUT_MS)),
|
|
2076
|
-
]);
|
|
2077
|
-
while (!verification.passed && retries < MAX_VERIFY_RETRIES) {
|
|
2078
|
-
retries++;
|
|
2079
|
-
this.emitCheckpoint("Verification retry", `Attempt ${retries} of ${MAX_VERIFY_RETRIES} is repairing failed checks.`, eventContext, `verification-retry-${retries}`);
|
|
2080
|
-
const retryPrompt = `The previous changes caused test failures. Please fix them.\n\nTest output:\n${verification.testOutput ?? verification.errors.join("\n")}`;
|
|
2081
|
-
const retryTask = { ...task, prompt: retryPrompt };
|
|
2082
|
-
result = await this.deps.adapter.execute(retryTask);
|
|
2083
|
-
verification = await verifyChatAction(gitRoot, this.deps.toolExecutor, { force: true });
|
|
2084
|
-
}
|
|
2085
|
-
if (!verification.passed) {
|
|
2086
|
-
const finalDiffArtifact = await collectGitDiffArtifact(gitRoot);
|
|
2087
|
-
if (finalDiffArtifact) {
|
|
2088
|
-
this.emitDiffArtifact(finalDiffArtifact, eventContext);
|
|
2089
|
-
}
|
|
2090
|
-
this.emitCheckpoint("Verification failed", `Checks are still failing after ${MAX_VERIFY_RETRIES} retries.`, eventContext, "verification");
|
|
2091
|
-
const failureOutput = this.emitLifecycleErrorEvent(`Changes applied but tests are still failing after ${MAX_VERIFY_RETRIES} retries.`, assistantBuffer.text, eventContext);
|
|
2092
|
-
this.emitLifecycleEndEvent("error", Date.now() - start, eventContext, false);
|
|
2093
|
-
return {
|
|
2094
|
-
success: false,
|
|
2095
|
-
output: `${failureOutput}\n\nTest output:\n${verification.testOutput ?? verification.errors.join("\n")}`.trim(),
|
|
2096
|
-
elapsed_ms: Date.now() - start,
|
|
2097
|
-
};
|
|
2098
|
-
}
|
|
2099
|
-
const finalDiffArtifact = await collectGitDiffArtifact(gitRoot);
|
|
2100
|
-
if (finalDiffArtifact) {
|
|
2101
|
-
this.emitDiffArtifact(finalDiffArtifact, eventContext);
|
|
2102
|
-
}
|
|
2103
|
-
this.emitCheckpoint("Verification passed", "Changed files passed the configured chat verification.", eventContext, "verification");
|
|
2104
|
-
}
|
|
2105
|
-
if (result.success) {
|
|
2106
|
-
await history.appendAssistantMessage(result.output);
|
|
2107
|
-
this.emitCheckpoint("Response ready", "The assistant response has been persisted for this turn.", eventContext, "complete");
|
|
2108
|
-
this.emitActivity("lifecycle", "Finalizing response...", eventContext, "lifecycle:finalizing");
|
|
2109
|
-
this.emitEvent({
|
|
2110
|
-
type: "assistant_final",
|
|
2111
|
-
text: result.output,
|
|
2112
|
-
persisted: true,
|
|
2113
|
-
...this.eventBase(eventContext),
|
|
2114
|
-
});
|
|
2115
|
-
this.emitLifecycleEndEvent("completed", elapsed_ms, eventContext, true);
|
|
2116
|
-
}
|
|
2117
|
-
else {
|
|
2118
|
-
const partialText = assistantBuffer.text !== result.output ? assistantBuffer.text : "";
|
|
2119
|
-
result.output = this.emitLifecycleErrorEvent(result.output || result.error || "Unknown error", partialText, eventContext);
|
|
2120
|
-
this.emitLifecycleEndEvent("error", elapsed_ms, eventContext, false);
|
|
2121
|
-
}
|
|
2122
|
-
return {
|
|
2123
|
-
success: result.success,
|
|
2124
|
-
output: result.output,
|
|
2125
|
-
elapsed_ms,
|
|
2126
|
-
};
|
|
2127
|
-
}
|
|
2128
|
-
async executeRuntimeControlRoute(route, runtimeControlContext, cwd, start) {
|
|
2129
|
-
if (!this.deps.runtimeControlService) {
|
|
2130
|
-
return {
|
|
2131
|
-
success: false,
|
|
2132
|
-
output: "Runtime control is not available in this chat surface yet.",
|
|
2133
|
-
elapsed_ms: Date.now() - start,
|
|
2134
|
-
};
|
|
2135
|
-
}
|
|
2136
|
-
const replyTarget = runtimeControlContext?.replyTarget ?? this.deps.runtimeReplyTarget;
|
|
2137
|
-
const actor = runtimeControlContext?.actor ?? this.deps.runtimeControlActor;
|
|
2138
|
-
const result = await this.deps.runtimeControlService.request({
|
|
2139
|
-
intent: route.intent,
|
|
2140
|
-
cwd,
|
|
2141
|
-
requestedBy: actor ?? {
|
|
2142
|
-
surface: replyTarget?.surface ?? "chat",
|
|
2143
|
-
platform: replyTarget?.platform,
|
|
2144
|
-
conversation_id: replyTarget?.conversation_id,
|
|
2145
|
-
identity_key: replyTarget?.identity_key,
|
|
2146
|
-
user_id: replyTarget?.user_id,
|
|
2147
|
-
},
|
|
2148
|
-
replyTarget: replyTarget ?? { surface: "chat" },
|
|
2149
|
-
approvalFn: runtimeControlContext?.approvalFn
|
|
2150
|
-
?? this.deps.runtimeControlApprovalFn
|
|
2151
|
-
?? this.deps.approvalFn,
|
|
2152
|
-
});
|
|
2153
|
-
return {
|
|
2154
|
-
success: result.success,
|
|
2155
|
-
output: result.message,
|
|
2156
|
-
elapsed_ms: Date.now() - start,
|
|
2157
|
-
};
|
|
2158
|
-
}
|
|
2159
|
-
/**
|
|
2160
|
-
* Execute a chat turn using llmClient with self-knowledge tools (function calling).
|
|
2161
|
-
* Loops up to MAX_TOOL_LOOPS times to resolve tool calls, then returns final text.
|
|
2162
|
-
*/
|
|
2163
|
-
async executeWithTools(prompt, eventContext, assistantBuffer, systemPrompt, goalId) {
|
|
2164
|
-
const llmClient = this.deps.llmClient;
|
|
2165
|
-
const messages = [{ role: "user", content: prompt }];
|
|
2166
|
-
const toolCallContext = await this.buildToolCallContext(goalId);
|
|
2167
|
-
const usage = this.zeroUsageCounter();
|
|
2168
|
-
for (let loop = 0; loop < MAX_TOOL_LOOPS; loop++) {
|
|
2169
|
-
// Recompute tools each iteration so newly activated deferred tools are included
|
|
2170
|
-
const tools = this.deps.registry
|
|
2171
|
-
? toToolDefinitionsFiltered(this.deps.registry.listAll(), { activatedTools: this.activatedTools })
|
|
2172
|
-
: [];
|
|
2173
|
-
const supportsNativeToolCalling = llmClient.supportsToolCalling?.() !== false;
|
|
2174
|
-
let response;
|
|
2175
|
-
try {
|
|
2176
|
-
this.emitActivity("lifecycle", "Calling model...", eventContext, "lifecycle:model");
|
|
2177
|
-
response = await this.sendLLMMessage(llmClient, messages, {
|
|
2178
|
-
...(supportsNativeToolCalling
|
|
2179
|
-
? { tools, ...(systemPrompt ? { system: systemPrompt } : {}) }
|
|
2180
|
-
: { system: buildPromptedToolProtocolSystemPrompt({ systemPrompt, tools }) }),
|
|
2181
|
-
}, assistantBuffer, eventContext);
|
|
2182
|
-
}
|
|
2183
|
-
catch (err) {
|
|
2184
|
-
console.error("[chat-runner] executeWithTools error:", err);
|
|
2185
|
-
const hint = err instanceof Error ? `: ${err.message}` : "";
|
|
2186
|
-
throw new Error(`Sorry, I encountered an error processing your request${hint}.`);
|
|
2187
|
-
}
|
|
2188
|
-
this.addUsageCounter(usage, this.usageFromLLMResponse(response));
|
|
2189
|
-
const toolCalls = response.tool_calls?.length
|
|
2190
|
-
? response.tool_calls
|
|
2191
|
-
: supportsNativeToolCalling
|
|
2192
|
-
? []
|
|
2193
|
-
: extractPromptedToolCalls({
|
|
2194
|
-
content: response.content,
|
|
2195
|
-
tools,
|
|
2196
|
-
createId: () => `prompted-${loop}-${crypto.randomUUID()}`,
|
|
2197
|
-
}).map((call) => ({
|
|
2198
|
-
id: call.id,
|
|
2199
|
-
type: "function",
|
|
2200
|
-
function: {
|
|
2201
|
-
name: call.name,
|
|
2202
|
-
arguments: JSON.stringify(call.input ?? {}),
|
|
2203
|
-
},
|
|
2204
|
-
}));
|
|
2205
|
-
if (!supportsNativeToolCalling && toolCalls.length > 0) {
|
|
2206
|
-
assistantBuffer.text = "";
|
|
2207
|
-
}
|
|
2208
|
-
// No tool calls — return the text content
|
|
2209
|
-
if (toolCalls.length === 0) {
|
|
2210
|
-
return {
|
|
2211
|
-
output: assistantBuffer.text || response.content || "(no response)",
|
|
2212
|
-
usage,
|
|
2213
|
-
};
|
|
2214
|
-
}
|
|
2215
|
-
// Append assistant message, then process tool calls
|
|
2216
|
-
messages.push({ role: "assistant", content: response.content || "" });
|
|
2217
|
-
for (const tc of toolCalls) {
|
|
2218
|
-
let args = {};
|
|
2219
|
-
try {
|
|
2220
|
-
args = JSON.parse(tc.function.arguments || "{}");
|
|
2221
|
-
}
|
|
2222
|
-
catch {
|
|
2223
|
-
// ignore parse errors, use empty args
|
|
2224
|
-
}
|
|
2225
|
-
const toolResult = await this.dispatchToolCall(tc.id, tc.function.name, args, toolCallContext, eventContext);
|
|
2226
|
-
// When ToolSearch returns results, activate deferred tools for subsequent turns
|
|
2227
|
-
if (tc.function.name === "tool_search") {
|
|
2228
|
-
this.activateToolSearchResults(toolResult);
|
|
2229
|
-
}
|
|
2230
|
-
messages.push({ role: "user", content: `Tool result for ${tc.function.name}:\n${toolResult}` });
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
// Max loops reached — return last assistant content or fallback
|
|
2234
|
-
const lastAssistant = [...messages].reverse().find(m => m.role === "assistant");
|
|
2235
|
-
return {
|
|
2236
|
-
output: lastAssistant?.content || "I was unable to complete the request within the allowed tool call limit.",
|
|
2237
|
-
usage,
|
|
2238
|
-
};
|
|
2239
|
-
}
|
|
2240
|
-
/**
|
|
2241
|
-
* Parse ToolSearch result JSON and activate any deferred tools found.
|
|
2242
|
-
* Called after each tool_search execution so the LLM can call found tools on the next turn.
|
|
2243
|
-
*/
|
|
2244
|
-
activateToolSearchResults(toolResult) {
|
|
2245
|
-
try {
|
|
2246
|
-
const parsed = JSON.parse(toolResult);
|
|
2247
|
-
const results = Array.isArray(parsed) ? parsed : null;
|
|
2248
|
-
if (results) {
|
|
2249
|
-
for (const item of results) {
|
|
2250
|
-
if (item && typeof item === "object" && typeof item["name"] === "string") {
|
|
2251
|
-
this.activatedTools.add(item["name"]);
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
catch {
|
|
2257
|
-
// Non-JSON result or unexpected shape — ignore
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
createAgentLoopEventSink(eventContext) {
|
|
2261
|
-
return {
|
|
2262
|
-
emit: async (event) => {
|
|
2263
|
-
if (event.type === "tool_call_started") {
|
|
2264
|
-
const detail = event.inputPreview ? previewActivityText(event.inputPreview) : undefined;
|
|
2265
|
-
this.emitActivity("tool", formatToolActivity("Running", event.toolName, detail), eventContext, event.callId);
|
|
2266
|
-
this.emitEvent({
|
|
2267
|
-
type: "tool_start",
|
|
2268
|
-
toolCallId: event.callId,
|
|
2269
|
-
toolName: event.toolName,
|
|
2270
|
-
args: this.parseAgentLoopPreview(event.inputPreview),
|
|
2271
|
-
...this.eventBase(eventContext),
|
|
2272
|
-
});
|
|
2273
|
-
this.emitEvent({
|
|
2274
|
-
type: "tool_update",
|
|
2275
|
-
toolCallId: event.callId,
|
|
2276
|
-
toolName: event.toolName,
|
|
2277
|
-
status: "running",
|
|
2278
|
-
message: "started",
|
|
2279
|
-
...this.eventBase(eventContext),
|
|
2280
|
-
});
|
|
2281
|
-
return;
|
|
2282
|
-
}
|
|
2283
|
-
if (event.type === "tool_call_finished") {
|
|
2284
|
-
this.emitActivity("tool", formatToolActivity(event.success ? "Finished" : "Failed", event.toolName, event.outputPreview), eventContext, event.callId);
|
|
2285
|
-
this.emitEvent({
|
|
2286
|
-
type: "tool_end",
|
|
2287
|
-
toolCallId: event.callId,
|
|
2288
|
-
toolName: event.toolName,
|
|
2289
|
-
success: event.success,
|
|
2290
|
-
summary: event.outputPreview,
|
|
2291
|
-
durationMs: event.durationMs,
|
|
2292
|
-
...this.eventBase(eventContext),
|
|
2293
|
-
});
|
|
2294
|
-
return;
|
|
2295
|
-
}
|
|
2296
|
-
if (event.type === "assistant_message" && event.phase === "commentary" && event.contentPreview) {
|
|
2297
|
-
this.emitActivity("commentary", previewActivityText(event.contentPreview, 120), eventContext, `commentary:${event.eventId}`);
|
|
2298
|
-
return;
|
|
2299
|
-
}
|
|
2300
|
-
if (event.type === "plan_update") {
|
|
2301
|
-
this.emitActivity("tool", `Updated plan: ${previewActivityText(event.summary)}`, eventContext, `plan:${event.turnId}`);
|
|
2302
|
-
this.emitCheckpoint("Plan updated", previewActivityText(event.summary, 160), eventContext, `plan:${event.eventId}`);
|
|
2303
|
-
this.emitEvent({
|
|
2304
|
-
type: "tool_update",
|
|
2305
|
-
toolCallId: `plan:${event.turnId}:${event.createdAt}`,
|
|
2306
|
-
toolName: "update_plan",
|
|
2307
|
-
status: "result",
|
|
2308
|
-
message: event.summary,
|
|
2309
|
-
...this.eventBase(eventContext),
|
|
2310
|
-
});
|
|
2311
|
-
return;
|
|
2312
|
-
}
|
|
2313
|
-
if (event.type === "approval_request") {
|
|
2314
|
-
this.emitActivity("tool", formatToolActivity("Running", event.toolName, `awaiting approval: ${event.reason}`), eventContext, event.callId);
|
|
2315
|
-
this.emitCheckpoint("Approval requested", `${event.toolName}: ${event.reason}`, eventContext, `approval:${event.callId}`);
|
|
2316
|
-
this.emitEvent({
|
|
2317
|
-
type: "tool_update",
|
|
2318
|
-
toolCallId: event.callId,
|
|
2319
|
-
toolName: event.toolName,
|
|
2320
|
-
status: "awaiting_approval",
|
|
2321
|
-
message: event.reason,
|
|
2322
|
-
...this.eventBase(eventContext),
|
|
2323
|
-
});
|
|
2324
|
-
return;
|
|
2325
|
-
}
|
|
2326
|
-
if (event.type === "approval") {
|
|
2327
|
-
this.emitActivity("tool", formatToolActivity("Finished", event.toolName, `approval ${event.status}: ${event.reason}`), eventContext);
|
|
2328
|
-
this.emitEvent({
|
|
2329
|
-
type: "tool_update",
|
|
2330
|
-
toolCallId: `approval:${event.turnId}:${event.createdAt}`,
|
|
2331
|
-
toolName: event.toolName,
|
|
2332
|
-
status: "result",
|
|
2333
|
-
message: `approval ${event.status}: ${event.reason}`,
|
|
2334
|
-
...this.eventBase(eventContext),
|
|
2335
|
-
});
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
if (event.type === "resumed") {
|
|
2339
|
-
this.emitEvent({
|
|
2340
|
-
type: "tool_update",
|
|
2341
|
-
toolCallId: `resume:${event.turnId}:${event.createdAt}`,
|
|
2342
|
-
toolName: "agentloop_resume",
|
|
2343
|
-
status: "result",
|
|
2344
|
-
message: `resumed ${event.restoredMessages} message(s) from ${event.fromUpdatedAt}`,
|
|
2345
|
-
...this.eventBase(eventContext),
|
|
2346
|
-
});
|
|
2347
|
-
return;
|
|
2348
|
-
}
|
|
2349
|
-
if (event.type === "context_compaction") {
|
|
2350
|
-
this.emitEvent({
|
|
2351
|
-
type: "tool_update",
|
|
2352
|
-
toolCallId: `compaction:${event.turnId}:${event.createdAt}`,
|
|
2353
|
-
toolName: "context_compaction",
|
|
2354
|
-
status: "result",
|
|
2355
|
-
message: `${event.phase} ${event.reason}: ${event.inputMessages} -> ${event.outputMessages}`,
|
|
2356
|
-
...this.eventBase(eventContext),
|
|
2357
|
-
});
|
|
2358
|
-
}
|
|
2359
|
-
},
|
|
2360
|
-
};
|
|
2361
|
-
}
|
|
2362
|
-
parseAgentLoopPreview(preview) {
|
|
2363
|
-
try {
|
|
2364
|
-
const parsed = JSON.parse(preview);
|
|
2365
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2366
|
-
return parsed;
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
catch {
|
|
2370
|
-
// fall through
|
|
2371
|
-
}
|
|
2372
|
-
return preview ? { preview } : {};
|
|
2373
|
-
}
|
|
2374
|
-
async resolveChatResumeSelector(selector) {
|
|
2375
|
-
if (selector.startsWith("session:conversation:")) {
|
|
2376
|
-
return { chatSelector: selector.slice("session:conversation:".length) };
|
|
2377
|
-
}
|
|
2378
|
-
if (selector.startsWith("session:") || selector.startsWith("run:")) {
|
|
2379
|
-
const registry = createRuntimeSessionRegistry({ stateManager: this.deps.stateManager });
|
|
2380
|
-
if (selector.startsWith("session:")) {
|
|
2381
|
-
const session = await registry.getSession(selector);
|
|
2382
|
-
if (session?.kind === "conversation") {
|
|
2383
|
-
return { chatSelector: selector.slice("session:conversation:".length) };
|
|
2384
|
-
}
|
|
2385
|
-
if (session?.kind === "agent"
|
|
2386
|
-
&& session.resumable
|
|
2387
|
-
&& session.parent_session_id?.startsWith("session:conversation:")) {
|
|
2388
|
-
return { chatSelector: session.parent_session_id.slice("session:conversation:".length) };
|
|
2389
|
-
}
|
|
2390
|
-
return {
|
|
2391
|
-
chatSelector: selector,
|
|
2392
|
-
nonResumableMessage: `Runtime session ${selector} is not chat-resumable. Inspect it with 'pulseed runtime session ${selector}'.`,
|
|
2393
|
-
};
|
|
2394
|
-
}
|
|
2395
|
-
return {
|
|
2396
|
-
chatSelector: selector,
|
|
2397
|
-
nonResumableMessage: `Background run ${selector} is not chat-resumable. Inspect it with 'pulseed runtime run ${selector}'.`,
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
return { chatSelector: selector };
|
|
2401
|
-
}
|
|
2402
|
-
parseResumeCommand(input) {
|
|
2403
|
-
const trimmed = input.trim();
|
|
2404
|
-
const match = /^\/resume(?:\s+(.+))?$/i.exec(trimmed);
|
|
2405
|
-
if (!match)
|
|
2406
|
-
return null;
|
|
2407
|
-
const selector = match[1]?.trim();
|
|
2408
|
-
return selector ? { selector } : {};
|
|
2409
|
-
}
|
|
2410
|
-
async loadResumableAgentLoopState() {
|
|
2411
|
-
if (!this.nativeAgentLoopStatePath)
|
|
2412
|
-
return null;
|
|
2413
|
-
const raw = await this.deps.stateManager.readRaw(this.nativeAgentLoopStatePath);
|
|
2414
|
-
if (!this.isAgentLoopSessionState(raw))
|
|
2415
|
-
return null;
|
|
2416
|
-
if (raw.status === "completed")
|
|
2417
|
-
return null;
|
|
2418
|
-
return {
|
|
2419
|
-
...raw,
|
|
2420
|
-
messages: [...raw.messages],
|
|
2421
|
-
calledTools: [...raw.calledTools],
|
|
2422
|
-
};
|
|
2423
|
-
}
|
|
2424
|
-
isAgentLoopSessionState(value) {
|
|
2425
|
-
if (!value || typeof value !== "object")
|
|
2426
|
-
return false;
|
|
2427
|
-
const candidate = value;
|
|
2428
|
-
return typeof candidate["sessionId"] === "string"
|
|
2429
|
-
&& typeof candidate["traceId"] === "string"
|
|
2430
|
-
&& typeof candidate["turnId"] === "string"
|
|
2431
|
-
&& typeof candidate["goalId"] === "string"
|
|
2432
|
-
&& typeof candidate["cwd"] === "string"
|
|
2433
|
-
&& typeof candidate["modelRef"] === "string"
|
|
2434
|
-
&& Array.isArray(candidate["messages"])
|
|
2435
|
-
&& Array.isArray(candidate["calledTools"])
|
|
2436
|
-
&& typeof candidate["status"] === "string";
|
|
2437
|
-
}
|
|
2438
|
-
/** Dispatch a tool call through the registry. */
|
|
2439
|
-
async dispatchToolCall(toolCallId, name, args, context, eventContext) {
|
|
2440
|
-
if (!this.deps.registry) {
|
|
2441
|
-
this.emitActivity("tool", formatToolActivity("Failed", name, "No tool registry configured"), eventContext, toolCallId);
|
|
2442
|
-
return JSON.stringify({ error: `No tool registry configured` });
|
|
2443
|
-
}
|
|
2444
|
-
const tool = this.deps.registry.get(name);
|
|
2445
|
-
if (!tool) {
|
|
2446
|
-
this.emitActivity("tool", formatToolActivity("Failed", name, `Unknown tool: ${name}`), eventContext, toolCallId);
|
|
2447
|
-
return JSON.stringify({ error: `Unknown tool: ${name}` });
|
|
2448
|
-
}
|
|
2449
|
-
const startTime = Date.now();
|
|
2450
|
-
try {
|
|
2451
|
-
const parsed = tool.inputSchema.safeParse(args);
|
|
2452
|
-
if (!parsed.success) {
|
|
2453
|
-
this.emitActivity("tool", formatToolActivity("Failed", name, `Invalid input: ${parsed.error.message}`), eventContext, toolCallId);
|
|
2454
|
-
this.emitEvent({
|
|
2455
|
-
type: "tool_end",
|
|
2456
|
-
toolCallId,
|
|
2457
|
-
toolName: name,
|
|
2458
|
-
success: false,
|
|
2459
|
-
summary: `Invalid input: ${parsed.error.message}`,
|
|
2460
|
-
durationMs: Date.now() - startTime,
|
|
2461
|
-
...this.eventBase(eventContext),
|
|
2462
|
-
});
|
|
2463
|
-
return JSON.stringify({ error: `Invalid input: ${parsed.error.message}` });
|
|
2464
|
-
}
|
|
2465
|
-
this.emitEvent({
|
|
2466
|
-
type: "tool_start",
|
|
2467
|
-
toolCallId,
|
|
2468
|
-
toolName: name,
|
|
2469
|
-
args,
|
|
2470
|
-
...this.eventBase(eventContext),
|
|
2471
|
-
});
|
|
2472
|
-
this.emitActivity("tool", formatToolActivity("Running", name, JSON.stringify(args)), eventContext, toolCallId);
|
|
2473
|
-
let result;
|
|
2474
|
-
if (this.deps.toolExecutor) {
|
|
2475
|
-
this.emitEvent({
|
|
2476
|
-
type: "tool_update",
|
|
2477
|
-
toolCallId,
|
|
2478
|
-
toolName: name,
|
|
2479
|
-
status: "running",
|
|
2480
|
-
message: "running",
|
|
2481
|
-
...this.eventBase(eventContext),
|
|
2482
|
-
});
|
|
2483
|
-
this.deps.onToolStart?.(name, args);
|
|
2484
|
-
result = await this.deps.toolExecutor.execute(name, parsed.data, context);
|
|
2485
|
-
}
|
|
2486
|
-
else {
|
|
2487
|
-
// Gate: check permissions before execution
|
|
2488
|
-
const permResult = await tool.checkPermissions(parsed.data, context);
|
|
2489
|
-
if (permResult.status === "denied") {
|
|
2490
|
-
this.emitEvent({
|
|
2491
|
-
type: "tool_end",
|
|
2492
|
-
toolCallId,
|
|
2493
|
-
toolName: name,
|
|
2494
|
-
success: false,
|
|
2495
|
-
summary: permResult.reason,
|
|
2496
|
-
durationMs: Date.now() - startTime,
|
|
2497
|
-
...this.eventBase(eventContext),
|
|
2498
|
-
});
|
|
2499
|
-
return `Tool ${name} denied: ${permResult.reason}`;
|
|
2500
|
-
}
|
|
2501
|
-
if (permResult.status === "needs_approval") {
|
|
2502
|
-
this.emitActivity("tool", formatToolActivity("Running", name, `awaiting approval: ${permResult.reason}`), eventContext, toolCallId);
|
|
2503
|
-
this.emitEvent({
|
|
2504
|
-
type: "tool_update",
|
|
2505
|
-
toolCallId,
|
|
2506
|
-
toolName: name,
|
|
2507
|
-
status: "awaiting_approval",
|
|
2508
|
-
message: permResult.reason,
|
|
2509
|
-
...this.eventBase(eventContext),
|
|
2510
|
-
});
|
|
2511
|
-
const approved = await context.approvalFn({
|
|
2512
|
-
toolName: name,
|
|
2513
|
-
input: parsed.data,
|
|
2514
|
-
reason: permResult.reason,
|
|
2515
|
-
permissionLevel: tool.metadata.permissionLevel,
|
|
2516
|
-
isDestructive: tool.metadata.isDestructive,
|
|
2517
|
-
reversibility: "unknown",
|
|
2518
|
-
});
|
|
2519
|
-
if (!approved) {
|
|
2520
|
-
this.emitEvent({
|
|
2521
|
-
type: "tool_end",
|
|
2522
|
-
toolCallId,
|
|
2523
|
-
toolName: name,
|
|
2524
|
-
success: false,
|
|
2525
|
-
summary: `Not approved: ${permResult.reason}`,
|
|
2526
|
-
durationMs: Date.now() - startTime,
|
|
2527
|
-
...this.eventBase(eventContext),
|
|
2528
|
-
});
|
|
2529
|
-
return `Tool ${name} not approved: ${permResult.reason}`;
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
this.emitEvent({
|
|
2533
|
-
type: "tool_update",
|
|
2534
|
-
toolCallId,
|
|
2535
|
-
toolName: name,
|
|
2536
|
-
status: "running",
|
|
2537
|
-
message: "running",
|
|
2538
|
-
...this.eventBase(eventContext),
|
|
2539
|
-
});
|
|
2540
|
-
this.deps.onToolStart?.(name, args);
|
|
2541
|
-
result = await tool.call(parsed.data, context);
|
|
2542
|
-
}
|
|
2543
|
-
const durationMs = Date.now() - startTime;
|
|
2544
|
-
this.deps.onToolEnd?.(name, { success: result.success, summary: result.summary || '...', durationMs });
|
|
2545
|
-
this.emitActivity("tool", formatToolActivity(result.success ? "Finished" : "Failed", name, result.summary || "..."), eventContext, toolCallId);
|
|
2546
|
-
this.emitEvent({
|
|
2547
|
-
type: "tool_update",
|
|
2548
|
-
toolCallId,
|
|
2549
|
-
toolName: name,
|
|
2550
|
-
status: "result",
|
|
2551
|
-
message: result.summary || "...",
|
|
2552
|
-
...this.eventBase(eventContext),
|
|
2553
|
-
});
|
|
2554
|
-
this.emitEvent({
|
|
2555
|
-
type: "tool_end",
|
|
2556
|
-
toolCallId,
|
|
2557
|
-
toolName: name,
|
|
2558
|
-
success: result.success,
|
|
2559
|
-
summary: result.summary || "...",
|
|
2560
|
-
durationMs,
|
|
2561
|
-
...this.eventBase(eventContext),
|
|
2562
|
-
});
|
|
2563
|
-
// Prefer structured data (JSON) over plain summary so the LLM gets actionable content
|
|
2564
|
-
return result.data != null ? JSON.stringify(result.data) : (result.summary ?? "(no result)");
|
|
2565
|
-
}
|
|
2566
|
-
catch (err) {
|
|
2567
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2568
|
-
const durationMs = Date.now() - startTime;
|
|
2569
|
-
this.deps.onToolEnd?.(name, { success: false, summary: message, durationMs });
|
|
2570
|
-
this.emitActivity("tool", formatToolActivity("Failed", name, message), eventContext, toolCallId);
|
|
2571
|
-
this.emitEvent({
|
|
2572
|
-
type: "tool_end",
|
|
2573
|
-
toolCallId,
|
|
2574
|
-
toolName: name,
|
|
2575
|
-
success: false,
|
|
2576
|
-
summary: message,
|
|
2577
|
-
durationMs,
|
|
2578
|
-
...this.eventBase(eventContext),
|
|
2579
|
-
});
|
|
2580
|
-
return JSON.stringify({ error: `Tool ${name} failed: ${message}` });
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
async sendLLMMessage(llmClient, messages, options, assistantBuffer, eventContext) {
|
|
2584
|
-
let streamed = false;
|
|
2585
|
-
if (llmClient.sendMessageStream) {
|
|
2586
|
-
const response = await llmClient.sendMessageStream(messages, options, {
|
|
2587
|
-
onTextDelta: (delta) => {
|
|
2588
|
-
streamed = true;
|
|
2589
|
-
this.pushAssistantDelta(delta, assistantBuffer, eventContext);
|
|
2590
|
-
},
|
|
2591
|
-
});
|
|
2592
|
-
if (!streamed && response.content) {
|
|
2593
|
-
this.pushAssistantDelta(response.content, assistantBuffer, eventContext);
|
|
2594
|
-
}
|
|
2595
|
-
return response;
|
|
2596
|
-
}
|
|
2597
|
-
const response = await llmClient.sendMessage(messages, options);
|
|
2598
|
-
if (response.content) {
|
|
2599
|
-
this.pushAssistantDelta(response.content, assistantBuffer, eventContext);
|
|
2600
|
-
}
|
|
2601
|
-
return response;
|
|
2602
|
-
}
|
|
2603
|
-
createEventContext() {
|
|
2604
|
-
return {
|
|
2605
|
-
runId: crypto.randomUUID(),
|
|
2606
|
-
turnId: crypto.randomUUID(),
|
|
2607
|
-
};
|
|
2608
|
-
}
|
|
2609
|
-
eventBase(context) {
|
|
2610
|
-
return { ...context, createdAt: new Date().toISOString() };
|
|
2611
|
-
}
|
|
2612
|
-
beginActiveTurn(context, cwd) {
|
|
2613
|
-
let resolveFinished = () => { };
|
|
2614
|
-
const finished = new Promise((resolve) => {
|
|
2615
|
-
resolveFinished = resolve;
|
|
2616
|
-
});
|
|
2617
|
-
const turn = {
|
|
2618
|
-
context,
|
|
2619
|
-
cwd,
|
|
2620
|
-
startedAt: Date.now(),
|
|
2621
|
-
abortController: new AbortController(),
|
|
2622
|
-
finished,
|
|
2623
|
-
resolveFinished,
|
|
2624
|
-
recentEvents: [],
|
|
2625
|
-
interruptRequested: false,
|
|
2626
|
-
};
|
|
2627
|
-
this.activeTurn = turn;
|
|
2628
|
-
return turn;
|
|
2629
|
-
}
|
|
2630
|
-
finishActiveTurn(context) {
|
|
2631
|
-
const activeTurn = this.activeTurn;
|
|
2632
|
-
if (!activeTurn || activeTurn.context.runId !== context.runId)
|
|
2633
|
-
return;
|
|
2634
|
-
activeTurn.resolveFinished();
|
|
2635
|
-
this.activeTurn = null;
|
|
2636
|
-
}
|
|
2637
|
-
waitForActiveTurn(turn, timeoutMs) {
|
|
2638
|
-
return Promise.race([
|
|
2639
|
-
turn.finished.then(() => true),
|
|
2640
|
-
new Promise((resolve) => setTimeout(() => resolve(false), timeoutMs)),
|
|
2641
|
-
]);
|
|
2642
|
-
}
|
|
2643
|
-
emitEphemeralAssistantResult(input, output, success, start) {
|
|
2644
|
-
const context = this.createEventContext();
|
|
2645
|
-
this.emitEvent({
|
|
2646
|
-
type: "lifecycle_start",
|
|
2647
|
-
input,
|
|
2648
|
-
...this.eventBase(context),
|
|
2649
|
-
});
|
|
2650
|
-
this.emitEvent({
|
|
2651
|
-
type: "assistant_final",
|
|
2652
|
-
text: output,
|
|
2653
|
-
persisted: false,
|
|
2654
|
-
...this.eventBase(context),
|
|
2655
|
-
});
|
|
2656
|
-
const elapsed_ms = Date.now() - start;
|
|
2657
|
-
this.emitLifecycleEndEvent(success ? "completed" : "error", elapsed_ms, context, false);
|
|
2658
|
-
return { success, output, elapsed_ms };
|
|
2659
|
-
}
|
|
2660
|
-
rememberActiveTurnEvent(event) {
|
|
2661
|
-
const activeTurn = this.activeTurn;
|
|
2662
|
-
if (!activeTurn || activeTurn.context.turnId !== event.turnId)
|
|
2663
|
-
return;
|
|
2664
|
-
let summary = null;
|
|
2665
|
-
if (event.type === "activity") {
|
|
2666
|
-
summary = previewActivityText(event.message, 140);
|
|
2667
|
-
}
|
|
2668
|
-
else if (event.type === "tool_start") {
|
|
2669
|
-
summary = `Started ${event.toolName}`;
|
|
2670
|
-
}
|
|
2671
|
-
else if (event.type === "tool_update") {
|
|
2672
|
-
summary = `${event.toolName}: ${previewActivityText(event.message, 100)}`;
|
|
2673
|
-
}
|
|
2674
|
-
else if (event.type === "tool_end") {
|
|
2675
|
-
summary = `${event.success ? "Finished" : "Failed"} ${event.toolName}: ${previewActivityText(event.summary, 100)}`;
|
|
2676
|
-
}
|
|
2677
|
-
if (!summary)
|
|
2678
|
-
return;
|
|
2679
|
-
activeTurn.recentEvents = [...activeTurn.recentEvents, summary].slice(-12);
|
|
2680
|
-
}
|
|
2681
|
-
emitEvent(event) {
|
|
2682
|
-
this.rememberActiveTurnEvent(event);
|
|
2683
|
-
const handler = this.onEvent ?? this.deps.onEvent;
|
|
2684
|
-
handler?.(event);
|
|
2685
|
-
}
|
|
2686
|
-
emitActivity(kind, message, eventContext, sourceId, transient = true) {
|
|
2687
|
-
if (!message.trim())
|
|
2688
|
-
return;
|
|
2689
|
-
this.emitEvent({
|
|
2690
|
-
type: "activity",
|
|
2691
|
-
kind,
|
|
2692
|
-
message,
|
|
2693
|
-
...(sourceId ? { sourceId } : {}),
|
|
2694
|
-
transient,
|
|
2695
|
-
...this.eventBase(eventContext),
|
|
2696
|
-
});
|
|
2697
|
-
}
|
|
2698
|
-
emitIntent(input, selectedRoute, eventContext) {
|
|
2699
|
-
const subject = formatIntentInput(input);
|
|
2700
|
-
let nextStep = "resume the saved agent loop state before continuing.";
|
|
2701
|
-
let reason = "resume needs the prior runtime context before any further action.";
|
|
2702
|
-
if (selectedRoute?.kind === "runtime_control") {
|
|
2703
|
-
nextStep = `prepare the ${selectedRoute.intent.kind} runtime-control request.`;
|
|
2704
|
-
reason = "runtime changes need an explicit operation plan and approval path.";
|
|
2705
|
-
}
|
|
2706
|
-
else if (selectedRoute?.kind === "daemon_tend") {
|
|
2707
|
-
nextStep = "convert the request into a daemon-backed CoreLoop goal confirmation.";
|
|
2708
|
-
reason = "long-running work should survive chat turn timeouts and run through durable runtime state.";
|
|
2709
|
-
}
|
|
2710
|
-
else if (selectedRoute?.kind === "direct_answer") {
|
|
2711
|
-
nextStep = "ask the lightweight model for a concise direct answer.";
|
|
2712
|
-
reason = "the router classified this as a simple question that does not need tools.";
|
|
2713
|
-
}
|
|
2714
|
-
else if (selectedRoute?.kind === "agent_loop") {
|
|
2715
|
-
nextStep = "gather workspace context, then let the agent loop inspect or change files with visible tool activity.";
|
|
2716
|
-
reason = "this request may require multiple tool-backed steps.";
|
|
2717
|
-
}
|
|
2718
|
-
else if (selectedRoute?.kind === "tool_loop") {
|
|
2719
|
-
nextStep = "call the model with the tool catalog, then execute selected tools with visible activity.";
|
|
2720
|
-
reason = "the available tools are needed to answer from current project state.";
|
|
2721
|
-
}
|
|
2722
|
-
else if (selectedRoute?.kind === "adapter") {
|
|
2723
|
-
nextStep = "prepare project context before handing the turn to the configured adapter.";
|
|
2724
|
-
reason = "the adapter needs the current workspace context to act correctly.";
|
|
2725
|
-
}
|
|
2726
|
-
const message = [
|
|
2727
|
-
"Intent",
|
|
2728
|
-
`- Confirm: ${subject || "the current request"}`,
|
|
2729
|
-
`- Next: ${nextStep}`,
|
|
2730
|
-
`- Why: ${reason}`,
|
|
2731
|
-
].join("\n");
|
|
2732
|
-
this.emitActivity("commentary", message, eventContext, "intent:first-step", false);
|
|
2733
|
-
}
|
|
2734
|
-
emitCheckpoint(title, detail, eventContext, sourceKey) {
|
|
2735
|
-
const message = detail
|
|
2736
|
-
? `Checkpoint\n- ${title}: ${detail}`
|
|
2737
|
-
: `Checkpoint\n- ${title}`;
|
|
2738
|
-
this.emitActivity("checkpoint", message, eventContext, `checkpoint:${sourceKey}`, false);
|
|
2739
|
-
}
|
|
2740
|
-
emitDiffArtifact(artifact, eventContext) {
|
|
2741
|
-
const sections = [
|
|
2742
|
-
"Changed files",
|
|
2743
|
-
"",
|
|
2744
|
-
"Modified files",
|
|
2745
|
-
artifact.nameStatus || artifact.stat,
|
|
2746
|
-
"",
|
|
2747
|
-
"Diff summary",
|
|
2748
|
-
artifact.stat,
|
|
2749
|
-
"",
|
|
2750
|
-
"Inline patch",
|
|
2751
|
-
"```diff",
|
|
2752
|
-
artifact.patch || "(patch unavailable)",
|
|
2753
|
-
artifact.truncated ? `... truncated after ${DIFF_ARTIFACT_MAX_LINES} lines; run /review for the full diff.` : "",
|
|
2754
|
-
"```",
|
|
2755
|
-
"",
|
|
2756
|
-
"Files inspected are shown separately in the activity log.",
|
|
2757
|
-
].filter((line) => line !== "").join("\n");
|
|
2758
|
-
this.emitActivity("diff", sections, eventContext, "diff:working-tree", false);
|
|
2759
|
-
}
|
|
2760
|
-
pushAssistantDelta(delta, assistantBuffer, eventContext) {
|
|
2761
|
-
if (!delta)
|
|
2762
|
-
return;
|
|
2763
|
-
assistantBuffer.text += delta;
|
|
2764
|
-
this.emitEvent({
|
|
2765
|
-
type: "assistant_delta",
|
|
2766
|
-
delta,
|
|
2767
|
-
text: assistantBuffer.text,
|
|
2768
|
-
...this.eventBase(eventContext),
|
|
2769
|
-
});
|
|
2770
|
-
}
|
|
2771
|
-
emitLifecycleEndEvent(status, elapsedMs, eventContext, persisted) {
|
|
2772
|
-
this.emitEvent({
|
|
2773
|
-
type: "lifecycle_end",
|
|
2774
|
-
status,
|
|
2775
|
-
elapsedMs,
|
|
2776
|
-
persisted,
|
|
2777
|
-
...this.eventBase(eventContext),
|
|
2778
|
-
});
|
|
2779
|
-
this.finishActiveTurn(eventContext);
|
|
2780
|
-
}
|
|
2781
|
-
emitLifecycleErrorEvent(error, partialText, eventContext) {
|
|
2782
|
-
const recovery = classifyFailureRecovery(error);
|
|
2783
|
-
this.emitEvent({
|
|
2784
|
-
type: "lifecycle_error",
|
|
2785
|
-
error,
|
|
2786
|
-
partialText,
|
|
2787
|
-
persisted: false,
|
|
2788
|
-
recovery,
|
|
2789
|
-
...this.eventBase(eventContext),
|
|
2790
|
-
});
|
|
2791
|
-
return formatLifecycleFailureMessage(error, partialText, recovery);
|
|
2792
|
-
}
|
|
2793
|
-
/** Build a ToolCallContext from ChatRunnerDeps for tool dispatch. */
|
|
2794
|
-
async getSessionExecutionPolicy() {
|
|
2795
|
-
if (this.sessionExecutionPolicy)
|
|
2796
|
-
return this.sessionExecutionPolicy;
|
|
2797
|
-
const config = await loadProviderConfig({ saveMigration: false });
|
|
2798
|
-
this.sessionExecutionPolicy = resolveExecutionPolicy({
|
|
2799
|
-
workspaceRoot: this.sessionCwd ?? process.cwd(),
|
|
2800
|
-
security: config.agent_loop?.security,
|
|
2801
|
-
});
|
|
2802
|
-
return this.sessionExecutionPolicy;
|
|
2803
|
-
}
|
|
2804
|
-
async buildToolCallContext(goalId = this.deps.goalId) {
|
|
2805
|
-
const executionPolicy = await this.getSessionExecutionPolicy();
|
|
2806
|
-
return {
|
|
2807
|
-
cwd: this.sessionCwd ?? process.cwd(),
|
|
2808
|
-
goalId: goalId ?? "",
|
|
2809
|
-
trustBalance: 0,
|
|
2810
|
-
preApproved: false,
|
|
2811
|
-
approvalFn: async (req) => {
|
|
2812
|
-
if (this.deps.approvalFn) {
|
|
2813
|
-
return this.deps.approvalFn(req.reason);
|
|
2814
|
-
}
|
|
2815
|
-
return false;
|
|
2816
|
-
},
|
|
2817
|
-
executionPolicy,
|
|
2818
|
-
};
|
|
472
|
+
this.eventBridge.emitLifecycleEndEvent(result.success ? "completed" : "error", result.elapsed_ms, eventContext, false);
|
|
473
|
+
return result;
|
|
2819
474
|
}
|
|
2820
475
|
}
|
|
476
|
+
void COMMAND_HELP;
|
|
477
|
+
void formatRoute;
|
|
2821
478
|
//# sourceMappingURL=chat-runner.js.map
|