@oni.bot/core 1.0.0 → 1.0.1
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/package.json +1 -1
- package/dist/oni-code/agent-registry.d.ts +0 -73
- package/dist/oni-code/agent-registry.d.ts.map +0 -1
- package/dist/oni-code/agent-registry.js +0 -151
- package/dist/oni-code/agent-registry.js.map +0 -1
- package/dist/oni-code/bin.d.ts +0 -18
- package/dist/oni-code/bin.d.ts.map +0 -1
- package/dist/oni-code/bin.js +0 -78
- package/dist/oni-code/bin.js.map +0 -1
- package/dist/oni-code/change-tracking.d.ts +0 -28
- package/dist/oni-code/change-tracking.d.ts.map +0 -1
- package/dist/oni-code/change-tracking.js +0 -77
- package/dist/oni-code/change-tracking.js.map +0 -1
- package/dist/oni-code/cli.d.ts +0 -6
- package/dist/oni-code/cli.d.ts.map +0 -1
- package/dist/oni-code/cli.js +0 -30
- package/dist/oni-code/cli.js.map +0 -1
- package/dist/oni-code/conductor.d.ts +0 -203
- package/dist/oni-code/conductor.d.ts.map +0 -1
- package/dist/oni-code/conductor.js +0 -1547
- package/dist/oni-code/conductor.js.map +0 -1
- package/dist/oni-code/config.d.ts +0 -130
- package/dist/oni-code/config.d.ts.map +0 -1
- package/dist/oni-code/config.js +0 -264
- package/dist/oni-code/config.js.map +0 -1
- package/dist/oni-code/context-files.d.ts +0 -22
- package/dist/oni-code/context-files.d.ts.map +0 -1
- package/dist/oni-code/context-files.js +0 -156
- package/dist/oni-code/context-files.js.map +0 -1
- package/dist/oni-code/cron/humanize.d.ts +0 -26
- package/dist/oni-code/cron/humanize.d.ts.map +0 -1
- package/dist/oni-code/cron/humanize.js +0 -83
- package/dist/oni-code/cron/humanize.js.map +0 -1
- package/dist/oni-code/cron/index.d.ts +0 -10
- package/dist/oni-code/cron/index.d.ts.map +0 -1
- package/dist/oni-code/cron/index.js +0 -7
- package/dist/oni-code/cron/index.js.map +0 -1
- package/dist/oni-code/cron/matcher.d.ts +0 -29
- package/dist/oni-code/cron/matcher.d.ts.map +0 -1
- package/dist/oni-code/cron/matcher.js +0 -73
- package/dist/oni-code/cron/matcher.js.map +0 -1
- package/dist/oni-code/cron/parser.d.ts +0 -27
- package/dist/oni-code/cron/parser.d.ts.map +0 -1
- package/dist/oni-code/cron/parser.js +0 -149
- package/dist/oni-code/cron/parser.js.map +0 -1
- package/dist/oni-code/cron/persistence.d.ts +0 -21
- package/dist/oni-code/cron/persistence.d.ts.map +0 -1
- package/dist/oni-code/cron/persistence.js +0 -58
- package/dist/oni-code/cron/persistence.js.map +0 -1
- package/dist/oni-code/cron/scheduler.d.ts +0 -64
- package/dist/oni-code/cron/scheduler.d.ts.map +0 -1
- package/dist/oni-code/cron/scheduler.js +0 -188
- package/dist/oni-code/cron/scheduler.js.map +0 -1
- package/dist/oni-code/cron/store.d.ts +0 -46
- package/dist/oni-code/cron/store.d.ts.map +0 -1
- package/dist/oni-code/cron/store.js +0 -68
- package/dist/oni-code/cron/store.js.map +0 -1
- package/dist/oni-code/env.d.ts +0 -17
- package/dist/oni-code/env.d.ts.map +0 -1
- package/dist/oni-code/env.js +0 -51
- package/dist/oni-code/env.js.map +0 -1
- package/dist/oni-code/file-reader.d.ts +0 -28
- package/dist/oni-code/file-reader.d.ts.map +0 -1
- package/dist/oni-code/file-reader.js +0 -368
- package/dist/oni-code/file-reader.js.map +0 -1
- package/dist/oni-code/file-watcher.d.ts +0 -66
- package/dist/oni-code/file-watcher.d.ts.map +0 -1
- package/dist/oni-code/file-watcher.js +0 -167
- package/dist/oni-code/file-watcher.js.map +0 -1
- package/dist/oni-code/loop-detector.d.ts +0 -64
- package/dist/oni-code/loop-detector.d.ts.map +0 -1
- package/dist/oni-code/loop-detector.js +0 -163
- package/dist/oni-code/loop-detector.js.map +0 -1
- package/dist/oni-code/permissions.d.ts +0 -43
- package/dist/oni-code/permissions.d.ts.map +0 -1
- package/dist/oni-code/permissions.js +0 -98
- package/dist/oni-code/permissions.js.map +0 -1
- package/dist/oni-code/planner.d.ts +0 -26
- package/dist/oni-code/planner.d.ts.map +0 -1
- package/dist/oni-code/planner.js +0 -86
- package/dist/oni-code/planner.js.map +0 -1
- package/dist/oni-code/plugin-loader.d.ts +0 -103
- package/dist/oni-code/plugin-loader.d.ts.map +0 -1
- package/dist/oni-code/plugin-loader.js +0 -171
- package/dist/oni-code/plugin-loader.js.map +0 -1
- package/dist/oni-code/process-tree.d.ts +0 -44
- package/dist/oni-code/process-tree.d.ts.map +0 -1
- package/dist/oni-code/process-tree.js +0 -107
- package/dist/oni-code/process-tree.js.map +0 -1
- package/dist/oni-code/progress-tracker.d.ts +0 -40
- package/dist/oni-code/progress-tracker.d.ts.map +0 -1
- package/dist/oni-code/progress-tracker.js +0 -375
- package/dist/oni-code/progress-tracker.js.map +0 -1
- package/dist/oni-code/scheduler.d.ts +0 -56
- package/dist/oni-code/scheduler.d.ts.map +0 -1
- package/dist/oni-code/scheduler.js +0 -105
- package/dist/oni-code/scheduler.js.map +0 -1
- package/dist/oni-code/session-fork.d.ts +0 -146
- package/dist/oni-code/session-fork.d.ts.map +0 -1
- package/dist/oni-code/session-fork.js +0 -238
- package/dist/oni-code/session-fork.js.map +0 -1
- package/dist/oni-code/session-stats.d.ts +0 -72
- package/dist/oni-code/session-stats.d.ts.map +0 -1
- package/dist/oni-code/session-stats.js +0 -141
- package/dist/oni-code/session-stats.js.map +0 -1
- package/dist/oni-code/session-title.d.ts +0 -25
- package/dist/oni-code/session-title.d.ts.map +0 -1
- package/dist/oni-code/session-title.js +0 -67
- package/dist/oni-code/session-title.js.map +0 -1
- package/dist/oni-code/shell-parser.d.ts +0 -112
- package/dist/oni-code/shell-parser.d.ts.map +0 -1
- package/dist/oni-code/shell-parser.js +0 -657
- package/dist/oni-code/shell-parser.js.map +0 -1
- package/dist/oni-code/summarizer.d.ts +0 -27
- package/dist/oni-code/summarizer.d.ts.map +0 -1
- package/dist/oni-code/summarizer.js +0 -70
- package/dist/oni-code/summarizer.js.map +0 -1
- package/dist/oni-code/swarm-checkpoint.d.ts +0 -52
- package/dist/oni-code/swarm-checkpoint.d.ts.map +0 -1
- package/dist/oni-code/swarm-checkpoint.js +0 -71
- package/dist/oni-code/swarm-checkpoint.js.map +0 -1
- package/dist/oni-code/swarm-runner.d.ts +0 -173
- package/dist/oni-code/swarm-runner.d.ts.map +0 -1
- package/dist/oni-code/swarm-runner.js +0 -873
- package/dist/oni-code/swarm-runner.js.map +0 -1
- package/dist/oni-code/system-prompt.d.ts +0 -13
- package/dist/oni-code/system-prompt.d.ts.map +0 -1
- package/dist/oni-code/system-prompt.js +0 -64
- package/dist/oni-code/system-prompt.js.map +0 -1
- package/dist/oni-code/task-evaluator.d.ts +0 -73
- package/dist/oni-code/task-evaluator.d.ts.map +0 -1
- package/dist/oni-code/task-evaluator.js +0 -172
- package/dist/oni-code/task-evaluator.js.map +0 -1
- package/dist/oni-code/tools/batch.d.ts +0 -12
- package/dist/oni-code/tools/batch.d.ts.map +0 -1
- package/dist/oni-code/tools/batch.js +0 -116
- package/dist/oni-code/tools/batch.js.map +0 -1
- package/dist/oni-code/tools/coding.d.ts +0 -10
- package/dist/oni-code/tools/coding.d.ts.map +0 -1
- package/dist/oni-code/tools/coding.js +0 -557
- package/dist/oni-code/tools/coding.js.map +0 -1
- package/dist/oni-code/tools/cron.d.ts +0 -4
- package/dist/oni-code/tools/cron.d.ts.map +0 -1
- package/dist/oni-code/tools/cron.js +0 -120
- package/dist/oni-code/tools/cron.js.map +0 -1
- package/dist/oni-code/tools/custom.d.ts +0 -43
- package/dist/oni-code/tools/custom.d.ts.map +0 -1
- package/dist/oni-code/tools/custom.js +0 -115
- package/dist/oni-code/tools/custom.js.map +0 -1
- package/dist/oni-code/tools/patch.d.ts +0 -58
- package/dist/oni-code/tools/patch.d.ts.map +0 -1
- package/dist/oni-code/tools/patch.js +0 -247
- package/dist/oni-code/tools/patch.js.map +0 -1
- package/dist/oni-code/tools/plan.d.ts +0 -17
- package/dist/oni-code/tools/plan.d.ts.map +0 -1
- package/dist/oni-code/tools/plan.js +0 -48
- package/dist/oni-code/tools/plan.js.map +0 -1
- package/dist/oni-code/tools/question.d.ts +0 -17
- package/dist/oni-code/tools/question.d.ts.map +0 -1
- package/dist/oni-code/tools/question.js +0 -46
- package/dist/oni-code/tools/question.js.map +0 -1
- package/dist/oni-code/tools/skill.d.ts +0 -36
- package/dist/oni-code/tools/skill.d.ts.map +0 -1
- package/dist/oni-code/tools/skill.js +0 -132
- package/dist/oni-code/tools/skill.js.map +0 -1
- package/dist/oni-code/tools/spawn-agents.d.ts +0 -37
- package/dist/oni-code/tools/spawn-agents.d.ts.map +0 -1
- package/dist/oni-code/tools/spawn-agents.js +0 -91
- package/dist/oni-code/tools/spawn-agents.js.map +0 -1
- package/dist/oni-code/tools/spawn-swarm.d.ts +0 -70
- package/dist/oni-code/tools/spawn-swarm.d.ts.map +0 -1
- package/dist/oni-code/tools/spawn-swarm.js +0 -129
- package/dist/oni-code/tools/spawn-swarm.js.map +0 -1
- package/dist/oni-code/tools/web.d.ts +0 -11
- package/dist/oni-code/tools/web.d.ts.map +0 -1
- package/dist/oni-code/tools/web.js +0 -375
- package/dist/oni-code/tools/web.js.map +0 -1
- package/dist/oni-code/topology-agent-builder.d.ts +0 -22
- package/dist/oni-code/topology-agent-builder.d.ts.map +0 -1
- package/dist/oni-code/topology-agent-builder.js +0 -220
- package/dist/oni-code/topology-agent-builder.js.map +0 -1
- package/dist/oni-code/topology-selector.d.ts +0 -85
- package/dist/oni-code/topology-selector.d.ts.map +0 -1
- package/dist/oni-code/topology-selector.js +0 -338
- package/dist/oni-code/topology-selector.js.map +0 -1
- package/dist/oni-code/ui/App.d.ts +0 -10
- package/dist/oni-code/ui/App.d.ts.map +0 -1
- package/dist/oni-code/ui/App.js +0 -395
- package/dist/oni-code/ui/App.js.map +0 -1
- package/dist/oni-code/ui/FooterPanel.d.ts +0 -16
- package/dist/oni-code/ui/FooterPanel.d.ts.map +0 -1
- package/dist/oni-code/ui/FooterPanel.js +0 -56
- package/dist/oni-code/ui/FooterPanel.js.map +0 -1
- package/dist/oni-code/ui/Header.d.ts +0 -21
- package/dist/oni-code/ui/Header.d.ts.map +0 -1
- package/dist/oni-code/ui/Header.js +0 -105
- package/dist/oni-code/ui/Header.js.map +0 -1
- package/dist/oni-code/ui/InputArea.d.ts +0 -11
- package/dist/oni-code/ui/InputArea.d.ts.map +0 -1
- package/dist/oni-code/ui/InputArea.js +0 -82
- package/dist/oni-code/ui/InputArea.js.map +0 -1
- package/dist/oni-code/ui/MessageBlock.d.ts +0 -11
- package/dist/oni-code/ui/MessageBlock.d.ts.map +0 -1
- package/dist/oni-code/ui/MessageBlock.js +0 -103
- package/dist/oni-code/ui/MessageBlock.js.map +0 -1
- package/dist/oni-code/ui/OutputPane.d.ts +0 -12
- package/dist/oni-code/ui/OutputPane.d.ts.map +0 -1
- package/dist/oni-code/ui/OutputPane.js +0 -8
- package/dist/oni-code/ui/OutputPane.js.map +0 -1
- package/dist/oni-code/ui/PermissionPrompt.d.ts +0 -11
- package/dist/oni-code/ui/PermissionPrompt.d.ts.map +0 -1
- package/dist/oni-code/ui/PermissionPrompt.js +0 -48
- package/dist/oni-code/ui/PermissionPrompt.js.map +0 -1
- package/dist/oni-code/ui/QuestionPrompt.d.ts +0 -8
- package/dist/oni-code/ui/QuestionPrompt.d.ts.map +0 -1
- package/dist/oni-code/ui/QuestionPrompt.js +0 -9
- package/dist/oni-code/ui/QuestionPrompt.js.map +0 -1
- package/dist/oni-code/ui/StatusLine.d.ts +0 -14
- package/dist/oni-code/ui/StatusLine.d.ts.map +0 -1
- package/dist/oni-code/ui/StatusLine.js +0 -23
- package/dist/oni-code/ui/StatusLine.js.map +0 -1
- package/dist/oni-code/ui/SwarmPanel.d.ts +0 -9
- package/dist/oni-code/ui/SwarmPanel.d.ts.map +0 -1
- package/dist/oni-code/ui/SwarmPanel.js +0 -52
- package/dist/oni-code/ui/SwarmPanel.js.map +0 -1
- package/dist/oni-code/ui/ToolCallBlock.d.ts +0 -10
- package/dist/oni-code/ui/ToolCallBlock.d.ts.map +0 -1
- package/dist/oni-code/ui/ToolCallBlock.js +0 -21
- package/dist/oni-code/ui/ToolCallBlock.js.map +0 -1
- package/dist/oni-code/ui/Toolbar.d.ts +0 -8
- package/dist/oni-code/ui/Toolbar.d.ts.map +0 -1
- package/dist/oni-code/ui/Toolbar.js +0 -11
- package/dist/oni-code/ui/Toolbar.js.map +0 -1
- package/dist/oni-code/ui/WelcomeBanner.d.ts +0 -12
- package/dist/oni-code/ui/WelcomeBanner.d.ts.map +0 -1
- package/dist/oni-code/ui/WelcomeBanner.js +0 -24
- package/dist/oni-code/ui/WelcomeBanner.js.map +0 -1
- package/dist/oni-code/ui/activity.d.ts +0 -15
- package/dist/oni-code/ui/activity.d.ts.map +0 -1
- package/dist/oni-code/ui/activity.js +0 -252
- package/dist/oni-code/ui/activity.js.map +0 -1
- package/dist/oni-code/ui/banner.d.ts +0 -16
- package/dist/oni-code/ui/banner.d.ts.map +0 -1
- package/dist/oni-code/ui/banner.js +0 -132
- package/dist/oni-code/ui/banner.js.map +0 -1
- package/dist/oni-code/ui/command-menu.d.ts +0 -7
- package/dist/oni-code/ui/command-menu.d.ts.map +0 -1
- package/dist/oni-code/ui/command-menu.js +0 -20
- package/dist/oni-code/ui/command-menu.js.map +0 -1
- package/dist/oni-code/ui/diff.d.ts +0 -17
- package/dist/oni-code/ui/diff.d.ts.map +0 -1
- package/dist/oni-code/ui/diff.js +0 -37
- package/dist/oni-code/ui/diff.js.map +0 -1
- package/dist/oni-code/ui/format.d.ts +0 -41
- package/dist/oni-code/ui/format.d.ts.map +0 -1
- package/dist/oni-code/ui/format.js +0 -223
- package/dist/oni-code/ui/format.js.map +0 -1
- package/dist/oni-code/ui/input.d.ts +0 -28
- package/dist/oni-code/ui/input.d.ts.map +0 -1
- package/dist/oni-code/ui/input.js +0 -216
- package/dist/oni-code/ui/input.js.map +0 -1
- package/dist/oni-code/ui/insights.d.ts +0 -39
- package/dist/oni-code/ui/insights.d.ts.map +0 -1
- package/dist/oni-code/ui/insights.js +0 -193
- package/dist/oni-code/ui/insights.js.map +0 -1
- package/dist/oni-code/ui/markdown.d.ts +0 -9
- package/dist/oni-code/ui/markdown.d.ts.map +0 -1
- package/dist/oni-code/ui/markdown.js +0 -44
- package/dist/oni-code/ui/markdown.js.map +0 -1
- package/dist/oni-code/ui/panels.d.ts +0 -39
- package/dist/oni-code/ui/panels.d.ts.map +0 -1
- package/dist/oni-code/ui/panels.js +0 -209
- package/dist/oni-code/ui/panels.js.map +0 -1
- package/dist/oni-code/ui/paste.d.ts +0 -17
- package/dist/oni-code/ui/paste.d.ts.map +0 -1
- package/dist/oni-code/ui/paste.js +0 -45
- package/dist/oni-code/ui/paste.js.map +0 -1
- package/dist/oni-code/ui/raw-spinner.d.ts +0 -37
- package/dist/oni-code/ui/raw-spinner.d.ts.map +0 -1
- package/dist/oni-code/ui/raw-spinner.js +0 -121
- package/dist/oni-code/ui/raw-spinner.js.map +0 -1
- package/dist/oni-code/ui/session.d.ts +0 -44
- package/dist/oni-code/ui/session.d.ts.map +0 -1
- package/dist/oni-code/ui/session.js +0 -93
- package/dist/oni-code/ui/session.js.map +0 -1
- package/dist/oni-code/ui/spinner.d.ts +0 -7
- package/dist/oni-code/ui/spinner.d.ts.map +0 -1
- package/dist/oni-code/ui/spinner.js +0 -20
- package/dist/oni-code/ui/spinner.js.map +0 -1
- package/dist/oni-code/ui/swarm-activity.d.ts +0 -50
- package/dist/oni-code/ui/swarm-activity.d.ts.map +0 -1
- package/dist/oni-code/ui/swarm-activity.js +0 -233
- package/dist/oni-code/ui/swarm-activity.js.map +0 -1
- package/dist/oni-code/ui/terminal-size.d.ts +0 -18
- package/dist/oni-code/ui/terminal-size.d.ts.map +0 -1
- package/dist/oni-code/ui/terminal-size.js +0 -45
- package/dist/oni-code/ui/terminal-size.js.map +0 -1
- package/dist/oni-code/ui/theme.d.ts +0 -82
- package/dist/oni-code/ui/theme.d.ts.map +0 -1
- package/dist/oni-code/ui/theme.js +0 -101
- package/dist/oni-code/ui/theme.js.map +0 -1
- package/dist/oni-code/ui/tool-utils.d.ts +0 -10
- package/dist/oni-code/ui/tool-utils.d.ts.map +0 -1
- package/dist/oni-code/ui/tool-utils.js +0 -82
- package/dist/oni-code/ui/tool-utils.js.map +0 -1
- package/dist/oni-code/ui/useTerminalSize.d.ts +0 -3
- package/dist/oni-code/ui/useTerminalSize.d.ts.map +0 -1
- package/dist/oni-code/ui/useTerminalSize.js +0 -16
- package/dist/oni-code/ui/useTerminalSize.js.map +0 -1
- package/dist/oni-code/workspace/change-tracker.d.ts +0 -18
- package/dist/oni-code/workspace/change-tracker.d.ts.map +0 -1
- package/dist/oni-code/workspace/change-tracker.js +0 -67
- package/dist/oni-code/workspace/change-tracker.js.map +0 -1
- package/dist/oni-code/workspace/conflict-detector.d.ts +0 -12
- package/dist/oni-code/workspace/conflict-detector.d.ts.map +0 -1
- package/dist/oni-code/workspace/conflict-detector.js +0 -24
- package/dist/oni-code/workspace/conflict-detector.js.map +0 -1
- package/dist/oni-code/workspace/file-snapshots.d.ts +0 -39
- package/dist/oni-code/workspace/file-snapshots.d.ts.map +0 -1
- package/dist/oni-code/workspace/file-snapshots.js +0 -77
- package/dist/oni-code/workspace/file-snapshots.js.map +0 -1
- package/dist/oni-code/workspace/index.d.ts +0 -5
- package/dist/oni-code/workspace/index.d.ts.map +0 -1
- package/dist/oni-code/workspace/index.js +0 -5
- package/dist/oni-code/workspace/index.js.map +0 -1
- package/dist/oni-code/workspace/project-map.d.ts +0 -14
- package/dist/oni-code/workspace/project-map.d.ts.map +0 -1
- package/dist/oni-code/workspace/project-map.js +0 -91
- package/dist/oni-code/workspace/project-map.js.map +0 -1
- package/dist/sentinel/config/index.d.ts +0 -2
- package/dist/sentinel/config/index.d.ts.map +0 -1
- package/dist/sentinel/config/index.js +0 -2
- package/dist/sentinel/config/index.js.map +0 -1
- package/dist/sentinel/config/schema.d.ts +0 -4
- package/dist/sentinel/config/schema.d.ts.map +0 -1
- package/dist/sentinel/config/schema.js +0 -42
- package/dist/sentinel/config/schema.js.map +0 -1
- package/dist/sentinel/debate/index.d.ts +0 -6
- package/dist/sentinel/debate/index.d.ts.map +0 -1
- package/dist/sentinel/debate/index.js +0 -64
- package/dist/sentinel/debate/index.js.map +0 -1
- package/dist/sentinel/debate/prompts.d.ts +0 -4
- package/dist/sentinel/debate/prompts.d.ts.map +0 -1
- package/dist/sentinel/debate/prompts.js +0 -13
- package/dist/sentinel/debate/prompts.js.map +0 -1
- package/dist/sentinel/fix/index.d.ts +0 -6
- package/dist/sentinel/fix/index.d.ts.map +0 -1
- package/dist/sentinel/fix/index.js +0 -27
- package/dist/sentinel/fix/index.js.map +0 -1
- package/dist/sentinel/fix/strategies.d.ts +0 -9
- package/dist/sentinel/fix/strategies.d.ts.map +0 -1
- package/dist/sentinel/fix/strategies.js +0 -40
- package/dist/sentinel/fix/strategies.js.map +0 -1
- package/dist/sentinel/index.d.ts +0 -14
- package/dist/sentinel/index.d.ts.map +0 -1
- package/dist/sentinel/index.js +0 -22
- package/dist/sentinel/index.js.map +0 -1
- package/dist/sentinel/integrations/cli.d.ts +0 -10
- package/dist/sentinel/integrations/cli.d.ts.map +0 -1
- package/dist/sentinel/integrations/cli.js +0 -24
- package/dist/sentinel/integrations/cli.js.map +0 -1
- package/dist/sentinel/integrations/index.d.ts +0 -2
- package/dist/sentinel/integrations/index.d.ts.map +0 -1
- package/dist/sentinel/integrations/index.js +0 -2
- package/dist/sentinel/integrations/index.js.map +0 -1
- package/dist/sentinel/memory/index.d.ts +0 -16
- package/dist/sentinel/memory/index.d.ts.map +0 -1
- package/dist/sentinel/memory/index.js +0 -60
- package/dist/sentinel/memory/index.js.map +0 -1
- package/dist/sentinel/report/console.d.ts +0 -3
- package/dist/sentinel/report/console.d.ts.map +0 -1
- package/dist/sentinel/report/console.js +0 -27
- package/dist/sentinel/report/console.js.map +0 -1
- package/dist/sentinel/report/github.d.ts +0 -3
- package/dist/sentinel/report/github.d.ts.map +0 -1
- package/dist/sentinel/report/github.js +0 -36
- package/dist/sentinel/report/github.js.map +0 -1
- package/dist/sentinel/report/index.d.ts +0 -6
- package/dist/sentinel/report/index.d.ts.map +0 -1
- package/dist/sentinel/report/index.js +0 -15
- package/dist/sentinel/report/index.js.map +0 -1
- package/dist/sentinel/report/sarif.d.ts +0 -3
- package/dist/sentinel/report/sarif.d.ts.map +0 -1
- package/dist/sentinel/report/sarif.js +0 -29
- package/dist/sentinel/report/sarif.js.map +0 -1
- package/dist/sentinel/sentinel.d.ts +0 -17
- package/dist/sentinel/sentinel.d.ts.map +0 -1
- package/dist/sentinel/sentinel.js +0 -111
- package/dist/sentinel/sentinel.js.map +0 -1
- package/dist/sentinel/swarm/agents.d.ts +0 -6
- package/dist/sentinel/swarm/agents.d.ts.map +0 -1
- package/dist/sentinel/swarm/agents.js +0 -36
- package/dist/sentinel/swarm/agents.js.map +0 -1
- package/dist/sentinel/swarm/index.d.ts +0 -8
- package/dist/sentinel/swarm/index.d.ts.map +0 -1
- package/dist/sentinel/swarm/index.js +0 -74
- package/dist/sentinel/swarm/index.js.map +0 -1
- package/dist/sentinel/swarm/prompts.d.ts +0 -2
- package/dist/sentinel/swarm/prompts.d.ts.map +0 -1
- package/dist/sentinel/swarm/prompts.js +0 -24
- package/dist/sentinel/swarm/prompts.js.map +0 -1
- package/dist/sentinel/swarm/topology.d.ts +0 -10
- package/dist/sentinel/swarm/topology.d.ts.map +0 -1
- package/dist/sentinel/swarm/topology.js +0 -38
- package/dist/sentinel/swarm/topology.js.map +0 -1
- package/dist/sentinel/triage/analyzers/complexity-analyzer.d.ts +0 -7
- package/dist/sentinel/triage/analyzers/complexity-analyzer.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/complexity-analyzer.js +0 -94
- package/dist/sentinel/triage/analyzers/complexity-analyzer.js.map +0 -1
- package/dist/sentinel/triage/analyzers/custom-analyzer.d.ts +0 -19
- package/dist/sentinel/triage/analyzers/custom-analyzer.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/custom-analyzer.js +0 -268
- package/dist/sentinel/triage/analyzers/custom-analyzer.js.map +0 -1
- package/dist/sentinel/triage/analyzers/dependency-analyzer.d.ts +0 -26
- package/dist/sentinel/triage/analyzers/dependency-analyzer.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/dependency-analyzer.js +0 -220
- package/dist/sentinel/triage/analyzers/dependency-analyzer.js.map +0 -1
- package/dist/sentinel/triage/analyzers/diff-analyzer.d.ts +0 -12
- package/dist/sentinel/triage/analyzers/diff-analyzer.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/diff-analyzer.js +0 -19
- package/dist/sentinel/triage/analyzers/diff-analyzer.js.map +0 -1
- package/dist/sentinel/triage/analyzers/index.d.ts +0 -14
- package/dist/sentinel/triage/analyzers/index.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/index.js +0 -25
- package/dist/sentinel/triage/analyzers/index.js.map +0 -1
- package/dist/sentinel/triage/analyzers/pattern-analyzer.d.ts +0 -7
- package/dist/sentinel/triage/analyzers/pattern-analyzer.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/pattern-analyzer.js +0 -180
- package/dist/sentinel/triage/analyzers/pattern-analyzer.js.map +0 -1
- package/dist/sentinel/triage/analyzers/security-analyzer.d.ts +0 -7
- package/dist/sentinel/triage/analyzers/security-analyzer.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/security-analyzer.js +0 -96
- package/dist/sentinel/triage/analyzers/security-analyzer.js.map +0 -1
- package/dist/sentinel/triage/analyzers/ts-parser.d.ts +0 -71
- package/dist/sentinel/triage/analyzers/ts-parser.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/ts-parser.js +0 -323
- package/dist/sentinel/triage/analyzers/ts-parser.js.map +0 -1
- package/dist/sentinel/triage/analyzers/typescript-analyzer.d.ts +0 -7
- package/dist/sentinel/triage/analyzers/typescript-analyzer.d.ts.map +0 -1
- package/dist/sentinel/triage/analyzers/typescript-analyzer.js +0 -68
- package/dist/sentinel/triage/analyzers/typescript-analyzer.js.map +0 -1
- package/dist/sentinel/triage/index.d.ts +0 -10
- package/dist/sentinel/triage/index.d.ts.map +0 -1
- package/dist/sentinel/triage/index.js +0 -39
- package/dist/sentinel/triage/index.js.map +0 -1
- package/dist/sentinel/triage/scorer.d.ts +0 -9
- package/dist/sentinel/triage/scorer.d.ts.map +0 -1
- package/dist/sentinel/triage/scorer.js +0 -28
- package/dist/sentinel/triage/scorer.js.map +0 -1
- package/dist/sentinel/types.d.ts +0 -125
- package/dist/sentinel/types.d.ts.map +0 -1
- package/dist/sentinel/types.js +0 -6
- package/dist/sentinel/types.js.map +0 -1
|
@@ -1,1547 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conductor Agent — the core integration that wires ONIHarness with coding
|
|
3
|
-
* tools, dispatch tools, workspace awareness, and permissions.
|
|
4
|
-
*
|
|
5
|
-
* The Conductor is the main conversational agent for the `oni` CLI. It handles
|
|
6
|
-
* simple tasks directly and escalates to subagents/swarms for complex work.
|
|
7
|
-
*
|
|
8
|
-
* Unlike single-shot harness usage, the Conductor maintains conversation
|
|
9
|
-
* history across turns so the agent remembers prior context.
|
|
10
|
-
*/
|
|
11
|
-
import { agentLoop } from "../harness/agent-loop.js";
|
|
12
|
-
import { ONIHarness } from "../harness/index.js";
|
|
13
|
-
import { TodoModule } from "../harness/todo-module.js";
|
|
14
|
-
import { HooksEngine } from "../harness/hooks-engine.js";
|
|
15
|
-
import { ContextCompactor } from "../harness/context-compactor.js";
|
|
16
|
-
import { defineTool } from "../tools/define.js";
|
|
17
|
-
import { makeCodingTools } from "./tools/coding.js";
|
|
18
|
-
import { LSPManager } from "../lsp/index.js";
|
|
19
|
-
import { makeWebTools } from "./tools/web.js";
|
|
20
|
-
import { makeSpawnAgentsTool } from "./tools/spawn-agents.js";
|
|
21
|
-
import { makeSpawnSwarmTool, } from "./tools/spawn-swarm.js";
|
|
22
|
-
import { makeCreateSkillTool } from "./tools/skill.js";
|
|
23
|
-
import { ProjectMap } from "./workspace/project-map.js";
|
|
24
|
-
import { ChangeTracker } from "./workspace/change-tracker.js";
|
|
25
|
-
import { PermissionManager } from "./permissions.js";
|
|
26
|
-
import { BackgroundScheduler } from "./scheduler.js";
|
|
27
|
-
import { CronScheduler, persistJobs, loadPersistedJobs } from "./cron/index.js";
|
|
28
|
-
import { makeCronTools } from "./tools/cron.js";
|
|
29
|
-
import { LoopDetector } from "./loop-detector.js";
|
|
30
|
-
import { AgentRegistry } from "./agent-registry.js";
|
|
31
|
-
import { makeQuestionTool } from "./tools/question.js";
|
|
32
|
-
import { makePlanTools } from "./tools/plan.js";
|
|
33
|
-
import { makeBatchTool } from "./tools/batch.js";
|
|
34
|
-
import { SessionTree } from "./session-fork.js";
|
|
35
|
-
import { TaskEvaluator } from "./task-evaluator.js";
|
|
36
|
-
import { SessionStats } from "./session-stats.js";
|
|
37
|
-
import { EventBus } from "../events/bus.js";
|
|
38
|
-
import { FileWatcher } from "./file-watcher.js";
|
|
39
|
-
import { SessionTitleGenerator } from "./session-title.js";
|
|
40
|
-
import { FileSnapshots } from "./workspace/file-snapshots.js";
|
|
41
|
-
import { extractFileChange, extractPatchChanges, extractMultiEditChanges } from "./change-tracking.js";
|
|
42
|
-
import { resolve as resolvePath, join as joinPath } from "node:path";
|
|
43
|
-
import { homedir as osHomedir } from "node:os";
|
|
44
|
-
import { SkillLoader } from "../harness/skill-loader.js";
|
|
45
|
-
import { buildSystemPrompt } from "./system-prompt.js";
|
|
46
|
-
import { loadContextFiles } from "./context-files.js";
|
|
47
|
-
import { buildAutoDispatchAgents } from "./topology-agent-builder.js";
|
|
48
|
-
import { summarizeSwarmResult } from "./summarizer.js";
|
|
49
|
-
import { ProgressTracker } from "./progress-tracker.js";
|
|
50
|
-
import { DefaultSwarmRunner } from "./swarm-runner.js";
|
|
51
|
-
// ── Constants ───────────────────────────────────────────────
|
|
52
|
-
/** Tools that mutate files — used to trigger snapshots and agent-change markers. */
|
|
53
|
-
const WRITE_TOOLS = new Set(["write_file", "edit_file", "apply_patch", "multi_edit"]);
|
|
54
|
-
// ── System Prompt ───────────────────────────────────────────
|
|
55
|
-
// Uses buildSystemPrompt() from system-prompt.ts (canonical) +
|
|
56
|
-
// loadContextFiles() from context-files.ts (CLAUDE.md, AGENTS.md, .cursorrules)
|
|
57
|
-
// ── Noop SwarmRunner ────────────────────────────────────────
|
|
58
|
-
const NOOP_SWARM_RUNNER = {
|
|
59
|
-
run: async () => ({
|
|
60
|
-
outcome: "Swarm runner not configured",
|
|
61
|
-
agentSummaries: [],
|
|
62
|
-
filesModified: [],
|
|
63
|
-
duration: 0,
|
|
64
|
-
}),
|
|
65
|
-
};
|
|
66
|
-
// ── Conductor ───────────────────────────────────────────────
|
|
67
|
-
export class Conductor {
|
|
68
|
-
config;
|
|
69
|
-
rootDir;
|
|
70
|
-
permissions;
|
|
71
|
-
changeTracker;
|
|
72
|
-
fileSnapshots;
|
|
73
|
-
swarmRunner;
|
|
74
|
-
swarmRunnerPublishesLifecycle;
|
|
75
|
-
tools;
|
|
76
|
-
todoModule;
|
|
77
|
-
hooksEngine;
|
|
78
|
-
compactor;
|
|
79
|
-
scheduler;
|
|
80
|
-
loopDetector;
|
|
81
|
-
lspManager;
|
|
82
|
-
cronScheduler;
|
|
83
|
-
agentRegistry;
|
|
84
|
-
taskEvaluator;
|
|
85
|
-
sessionStats;
|
|
86
|
-
eventBus;
|
|
87
|
-
fileWatcher;
|
|
88
|
-
titleGenerator;
|
|
89
|
-
skillLoader;
|
|
90
|
-
progressTracker;
|
|
91
|
-
autoCompactionEnabled;
|
|
92
|
-
/** Cached context files content (CLAUDE.md, AGENTS.md, .cursorrules) */
|
|
93
|
-
contextFilesBlock;
|
|
94
|
-
/** Prebuilt tool set for subagents — coding + web + custom, no orchestration tools */
|
|
95
|
-
subagentTools;
|
|
96
|
-
/** Accumulated conversation history across turns */
|
|
97
|
-
conversationHistory = [];
|
|
98
|
-
turnCount = 0;
|
|
99
|
-
/** Session tree for branch-based conversation management */
|
|
100
|
-
sessionTree;
|
|
101
|
-
/** Active agent name — determines system prompt and tool scoping */
|
|
102
|
-
activeAgent = "build";
|
|
103
|
-
/** Cached assembled system prompt (base + context files + skills). Invalidated on agent switch. */
|
|
104
|
-
_cachedSystemPrompt = null;
|
|
105
|
-
_cachedPromptAgent = null;
|
|
106
|
-
_cachedSkillVersion = -1;
|
|
107
|
-
_projectMapDirty = true;
|
|
108
|
-
/** Guard: true while chat() generator is executing. Prevents the background
|
|
109
|
-
* scheduler from compacting conversationHistory while the agentLoop holds
|
|
110
|
-
* a reference to it — otherwise the scheduler's compaction gets silently
|
|
111
|
-
* overwritten when the agentLoop yields finalMessages. */
|
|
112
|
-
_chatActive = false;
|
|
113
|
-
_stopNudgeCount = 0;
|
|
114
|
-
/** Session-level circuit breaker: consecutive chat() calls that ended in inference error.
|
|
115
|
-
* After 3 consecutive failures (9+ total retries), we stop attempting inference and
|
|
116
|
-
* tell the user the provider appears down. Resets on any successful inference. */
|
|
117
|
-
_consecutiveInferenceFailures = 0;
|
|
118
|
-
static INFERENCE_FAILURE_THRESHOLD = 3;
|
|
119
|
-
permissionListener = null;
|
|
120
|
-
questionListener = null;
|
|
121
|
-
toolMetadataListener = null;
|
|
122
|
-
constructor(config) {
|
|
123
|
-
this.config = config;
|
|
124
|
-
this.rootDir = config.rootDir;
|
|
125
|
-
this.permissions = new PermissionManager(config.mode ?? "auto", config.rootDir);
|
|
126
|
-
this.changeTracker = new ChangeTracker();
|
|
127
|
-
this.fileSnapshots = new FileSnapshots();
|
|
128
|
-
this.eventBus = config.eventBus ?? new EventBus();
|
|
129
|
-
// ── LSP manager (shared across all agents) ──────────────
|
|
130
|
-
this.lspManager = new LSPManager(config.rootDir);
|
|
131
|
-
// Apply LSP config if present
|
|
132
|
-
if (config.oniConfig?.lsp === false) {
|
|
133
|
-
this.lspManager.disable();
|
|
134
|
-
}
|
|
135
|
-
else if (config.oniConfig?.lsp && typeof config.oniConfig.lsp === "object") {
|
|
136
|
-
const lspCfg = config.oniConfig.lsp;
|
|
137
|
-
const lspServers = Object.entries(lspCfg).map(([id, cfg]) => ({
|
|
138
|
-
id,
|
|
139
|
-
extensions: cfg.extensions,
|
|
140
|
-
command: cfg.command,
|
|
141
|
-
args: cfg.args,
|
|
142
|
-
languageId: cfg.languageId,
|
|
143
|
-
}));
|
|
144
|
-
this.lspManager.addServers(lspServers);
|
|
145
|
-
}
|
|
146
|
-
// ── Build tools ─────────────────────────────────────────
|
|
147
|
-
const codingTools = makeCodingTools(config.rootDir, {
|
|
148
|
-
lsp: this.lspManager,
|
|
149
|
-
changeTracker: this.changeTracker,
|
|
150
|
-
fileSnapshots: this.fileSnapshots,
|
|
151
|
-
});
|
|
152
|
-
const webTools = makeWebTools({
|
|
153
|
-
searchEndpoint: config.oniConfig?.searchEndpoint,
|
|
154
|
-
searchApiKey: config.oniConfig?.searchApiKey,
|
|
155
|
-
});
|
|
156
|
-
const spawnAgentsTool = makeSpawnAgentsTool({
|
|
157
|
-
runAgent: (task, _index, agentName) => this.runSubagent(task, agentName),
|
|
158
|
-
});
|
|
159
|
-
const shouldCreateDefaultSwarmRunner = !config.swarmRunner && !!config.eventBus;
|
|
160
|
-
const swarmRunner = config.swarmRunner ?? (shouldCreateDefaultSwarmRunner
|
|
161
|
-
? new DefaultSwarmRunner({
|
|
162
|
-
model: config.model,
|
|
163
|
-
fastModel: config.fastModel,
|
|
164
|
-
rootDir: config.rootDir,
|
|
165
|
-
changeTracker: this.changeTracker,
|
|
166
|
-
maxConcurrency: config.oniConfig?.swarm?.maxConcurrency,
|
|
167
|
-
perAgentMaxTurns: config.oniConfig?.swarm?.perAgentMaxTurns,
|
|
168
|
-
agentTimeout: config.oniConfig?.swarm?.agentTimeout,
|
|
169
|
-
eventBus: this.eventBus,
|
|
170
|
-
})
|
|
171
|
-
: NOOP_SWARM_RUNNER);
|
|
172
|
-
this.swarmRunner = swarmRunner;
|
|
173
|
-
this.swarmRunnerPublishesLifecycle = shouldCreateDefaultSwarmRunner;
|
|
174
|
-
const spawnSwarmTool = makeSpawnSwarmTool(swarmRunner);
|
|
175
|
-
const projectMapTool = this.buildProjectMapTool();
|
|
176
|
-
const questionTool = makeQuestionTool();
|
|
177
|
-
const planTools = makePlanTools({
|
|
178
|
-
switchAgent: (name) => this.switchAgent(name),
|
|
179
|
-
});
|
|
180
|
-
// ── Skill loader — scan .oni/skills/, .claude/skills/, and global dirs ──
|
|
181
|
-
{
|
|
182
|
-
const homedir = osHomedir();
|
|
183
|
-
const skillDirs = [
|
|
184
|
-
joinPath(homedir, ".oni", "skills"),
|
|
185
|
-
joinPath(homedir, ".claude", "skills"),
|
|
186
|
-
joinPath(config.rootDir, ".oni", "skills"),
|
|
187
|
-
joinPath(config.rootDir, ".claude", "skills"),
|
|
188
|
-
joinPath(config.rootDir, ".agents", "skills"),
|
|
189
|
-
];
|
|
190
|
-
this.skillLoader = SkillLoader.fromDirectories(skillDirs);
|
|
191
|
-
}
|
|
192
|
-
// ── Context files — load CLAUDE.md, AGENTS.md, .cursorrules ──
|
|
193
|
-
this.contextFilesBlock = loadContextFiles(config.rootDir);
|
|
194
|
-
// Build base tool list (batch tool wraps these)
|
|
195
|
-
const skillTool = this.skillLoader.getSkillTool();
|
|
196
|
-
const createSkillTool = makeCreateSkillTool(this.skillLoader);
|
|
197
|
-
const cronTools = makeCronTools(this.cronScheduler);
|
|
198
|
-
const baseTools = [
|
|
199
|
-
...codingTools,
|
|
200
|
-
...webTools,
|
|
201
|
-
spawnAgentsTool,
|
|
202
|
-
spawnSwarmTool,
|
|
203
|
-
projectMapTool,
|
|
204
|
-
questionTool,
|
|
205
|
-
...planTools,
|
|
206
|
-
skillTool,
|
|
207
|
-
createSkillTool,
|
|
208
|
-
...cronTools,
|
|
209
|
-
...(config.customTools ?? []),
|
|
210
|
-
];
|
|
211
|
-
const batchTool = makeBatchTool(baseTools);
|
|
212
|
-
this.tools = [...baseTools, batchTool];
|
|
213
|
-
// Cache subagent-eligible tools (coding + web + custom, no orchestration)
|
|
214
|
-
this.subagentTools = [...codingTools, ...webTools, ...(config.customTools ?? [])];
|
|
215
|
-
// ── Build harness modules ────────────────────────────────
|
|
216
|
-
this.todoModule = new TodoModule();
|
|
217
|
-
this.loopDetector = new LoopDetector();
|
|
218
|
-
this.agentRegistry = new AgentRegistry();
|
|
219
|
-
this.sessionStats = new SessionStats();
|
|
220
|
-
this.titleGenerator = new SessionTitleGenerator(config.fastModel ?? config.model);
|
|
221
|
-
this.progressTracker = new ProgressTracker(config.rootDir);
|
|
222
|
-
this.progressTracker.restoreInto((todos) => this.todoModule.write(todos), this.changeTracker);
|
|
223
|
-
this.todoModule.onChange((state) => {
|
|
224
|
-
if (this.progressTracker.syncTodoState(state)) {
|
|
225
|
-
this.persistProgressSnapshot();
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
// ── File watcher (reactive file change detection) ──────
|
|
229
|
-
this.fileWatcher = new FileWatcher({
|
|
230
|
-
rootDir: config.rootDir,
|
|
231
|
-
bus: this.eventBus,
|
|
232
|
-
});
|
|
233
|
-
this.fileWatcher.on(() => {
|
|
234
|
-
this._projectMapDirty = true;
|
|
235
|
-
});
|
|
236
|
-
this.fileWatcher.start();
|
|
237
|
-
// ── Cron scheduler (session-scoped recurring prompts) ──
|
|
238
|
-
this.cronScheduler = new CronScheduler({
|
|
239
|
-
onFire: (prompt, jobId) => {
|
|
240
|
-
// Emit event for the CLI to pick up and inject as a new chat turn
|
|
241
|
-
const event = {
|
|
242
|
-
type: "cron.fired",
|
|
243
|
-
prompt,
|
|
244
|
-
jobId,
|
|
245
|
-
timestamp: Date.now(),
|
|
246
|
-
};
|
|
247
|
-
this.eventBus.emit(event);
|
|
248
|
-
},
|
|
249
|
-
isBusy: () => this._chatActive,
|
|
250
|
-
onMutate: (jobs) => persistJobs(this.rootDir, jobs),
|
|
251
|
-
});
|
|
252
|
-
// ── Restore persisted cron jobs ───────────────────────────
|
|
253
|
-
const persistedJobs = loadPersistedJobs(this.rootDir);
|
|
254
|
-
if (persistedJobs.length > 0) {
|
|
255
|
-
this.cronScheduler.restore(persistedJobs);
|
|
256
|
-
}
|
|
257
|
-
// ── Apply ONICodeConfig (agents, permissions) ─────────
|
|
258
|
-
if (config.oniConfig) {
|
|
259
|
-
// Register custom agents from config
|
|
260
|
-
if (config.oniConfig.agents) {
|
|
261
|
-
for (const [name, agentCfg] of Object.entries(config.oniConfig.agents)) {
|
|
262
|
-
this.agentRegistry.register({
|
|
263
|
-
name,
|
|
264
|
-
mode: agentCfg.mode ?? "subagent",
|
|
265
|
-
description: agentCfg.description ?? name,
|
|
266
|
-
tools: agentCfg.tools,
|
|
267
|
-
systemPrompt: agentCfg.systemPrompt,
|
|
268
|
-
maxTurns: agentCfg.maxTurns,
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
// Apply permission rules from config
|
|
273
|
-
if (config.oniConfig.permissions) {
|
|
274
|
-
this.permissions.addRulesFromConfig(config.oniConfig.permissions);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
const userEngine = new HooksEngine();
|
|
278
|
-
const permissionHooks = {
|
|
279
|
-
PreToolUse: [
|
|
280
|
-
{
|
|
281
|
-
handler: async (payload) => {
|
|
282
|
-
const { toolName, input } = payload;
|
|
283
|
-
// Defensive wrapper: if ANY internal check throws (loopDetector,
|
|
284
|
-
// permissionListener, etc.), deny the tool rather than letting
|
|
285
|
-
// the hooks engine's catch treat the exception as "allow".
|
|
286
|
-
try {
|
|
287
|
-
// ── Doom loop detection → permission escalation ──────
|
|
288
|
-
const loopResult = this.loopDetector.check(toolName, input);
|
|
289
|
-
if (loopResult.blocked) {
|
|
290
|
-
// In bypass/auto mode, tool spam (same tool, different args) is
|
|
291
|
-
// normal — e.g., writing 10 different files. Skip escalation.
|
|
292
|
-
const isAutoSpam = this.permissions.getMode() === "auto" &&
|
|
293
|
-
loopResult.type === "spam";
|
|
294
|
-
if (isAutoSpam) {
|
|
295
|
-
// Fall through — allow the call without prompting
|
|
296
|
-
}
|
|
297
|
-
else if (this.permissionListener) {
|
|
298
|
-
const decision = await new Promise((resolve) => {
|
|
299
|
-
this.permissionListener(toolName, input, resolve);
|
|
300
|
-
});
|
|
301
|
-
if (decision === "deny") {
|
|
302
|
-
return {
|
|
303
|
-
decision: "deny",
|
|
304
|
-
reason: loopResult.reason ?? "Doom loop detected",
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
// User approved — reset count for this tool+args so execution continues
|
|
308
|
-
this.loopDetector.allowOnce(toolName, input);
|
|
309
|
-
if (decision === "always_allow") {
|
|
310
|
-
this.permissions.addAlwaysAllow(toolName);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
// No permission listener — block with reason
|
|
315
|
-
return {
|
|
316
|
-
decision: "deny",
|
|
317
|
-
reason: loopResult.reason ?? "Doom loop detected",
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
// ── Plan mode enforcement ────────────────────────────
|
|
322
|
-
if (this.activeAgent === "plan") {
|
|
323
|
-
const planDef = this.agentRegistry.get("plan");
|
|
324
|
-
const allowed = planDef?.tools ? new Set([...planDef.tools, "plan_exit"]) : null;
|
|
325
|
-
if (allowed && !allowed.has(toolName)) {
|
|
326
|
-
return {
|
|
327
|
-
decision: "deny",
|
|
328
|
-
reason: `Tool "${toolName}" is not available in plan mode. Use plan_exit to switch back to build mode.`,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
// Block plan_enter when already in plan mode
|
|
332
|
-
if (toolName === "plan_enter") {
|
|
333
|
-
return {
|
|
334
|
-
decision: "deny",
|
|
335
|
-
reason: "Already in plan mode. Use plan_exit to switch to build mode.",
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
// Block plan_exit when in build mode
|
|
341
|
-
if (toolName === "plan_exit") {
|
|
342
|
-
return {
|
|
343
|
-
decision: "deny",
|
|
344
|
-
reason: "Not in plan mode. Use plan_enter to switch to plan mode first.",
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
// ── Normal permission check ──────────────────────────
|
|
349
|
-
if (this.permissions.needsApproval(toolName, input)) {
|
|
350
|
-
if (this.permissionListener) {
|
|
351
|
-
this.eventBus.emit({
|
|
352
|
-
type: "permission.asked",
|
|
353
|
-
toolName,
|
|
354
|
-
input,
|
|
355
|
-
agentName: this.activeAgent,
|
|
356
|
-
timestamp: Date.now(),
|
|
357
|
-
});
|
|
358
|
-
const decision = await new Promise((resolve) => {
|
|
359
|
-
this.permissionListener(toolName, input, resolve);
|
|
360
|
-
});
|
|
361
|
-
const busDecision = decision === "always_allow" ? "always"
|
|
362
|
-
: decision === "allow" ? "allow"
|
|
363
|
-
: "deny";
|
|
364
|
-
this.eventBus.emit({
|
|
365
|
-
type: "permission.replied",
|
|
366
|
-
toolName,
|
|
367
|
-
decision: busDecision,
|
|
368
|
-
timestamp: Date.now(),
|
|
369
|
-
});
|
|
370
|
-
if (decision === "deny") {
|
|
371
|
-
return { decision: "deny", reason: "User denied" };
|
|
372
|
-
}
|
|
373
|
-
if (decision === "always_allow") {
|
|
374
|
-
this.permissions.addAlwaysAllow(toolName);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
// ── Snapshot files before mutation (for undo_edit) ──────
|
|
379
|
-
// Also mark agent-initiated file changes so FileWatcher
|
|
380
|
-
// tags the resulting change event as agentInitiated: true
|
|
381
|
-
if (WRITE_TOOLS.has(toolName) && input) {
|
|
382
|
-
const filePath = input.path ?? input.file_path;
|
|
383
|
-
if (typeof filePath === "string") {
|
|
384
|
-
const abs = resolvePath(this.rootDir, filePath);
|
|
385
|
-
this.fileSnapshots.capture(abs);
|
|
386
|
-
this.fileWatcher.markAgentChange(abs);
|
|
387
|
-
}
|
|
388
|
-
// multi_edit has an edits array with individual file paths
|
|
389
|
-
const edits = input.edits;
|
|
390
|
-
if (Array.isArray(edits)) {
|
|
391
|
-
for (const edit of edits) {
|
|
392
|
-
const ep = edit?.path ?? edit?.file_path;
|
|
393
|
-
if (typeof ep === "string") {
|
|
394
|
-
const abs = resolvePath(this.rootDir, ep);
|
|
395
|
-
this.fileSnapshots.capture(abs);
|
|
396
|
-
this.fileWatcher.markAgentChange(abs);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return { decision: "allow" };
|
|
402
|
-
}
|
|
403
|
-
catch {
|
|
404
|
-
// Internal error in permission/loop/plan checking — deny to
|
|
405
|
-
// prevent silent allow on handler crash.
|
|
406
|
-
return {
|
|
407
|
-
decision: "deny",
|
|
408
|
-
reason: "Permission check failed (internal error)",
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
],
|
|
414
|
-
};
|
|
415
|
-
// ── Post-compaction state reinsertion ────────────────────
|
|
416
|
-
const postCompactHooks = {
|
|
417
|
-
PostCompact: [
|
|
418
|
-
{
|
|
419
|
-
handler: (_payload) => {
|
|
420
|
-
// Each state-gathering call is wrapped individually so a failure
|
|
421
|
-
// in one (e.g. corrupted todo state) doesn't lose the others.
|
|
422
|
-
const parts = [];
|
|
423
|
-
// Reinject todo list so the agent doesn't lose track
|
|
424
|
-
try {
|
|
425
|
-
const todoState = this.todoModule.getState();
|
|
426
|
-
if (todoState.todos.length > 0) {
|
|
427
|
-
parts.push("Current task list:\n" + this.todoModule.toContextString());
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
catch { /* partial state is better than no state */ }
|
|
431
|
-
// Reinject change tracker summary
|
|
432
|
-
try {
|
|
433
|
-
const changes = this.changeTracker.getSummary();
|
|
434
|
-
if (changes) {
|
|
435
|
-
parts.push("Files modified this session:\n" + changes);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
catch { /* partial state is better than no state */ }
|
|
439
|
-
// Reinject session stats for budget awareness
|
|
440
|
-
try {
|
|
441
|
-
const statsCtx = this.sessionStats.toContextString();
|
|
442
|
-
if (statsCtx) {
|
|
443
|
-
parts.push("Session so far: " + statsCtx);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
catch { /* partial state is better than no state */ }
|
|
447
|
-
try {
|
|
448
|
-
this.syncProgressSnapshot({ persist: false });
|
|
449
|
-
const progressCtx = this.progressTracker.toContextString();
|
|
450
|
-
if (progressCtx) {
|
|
451
|
-
parts.push(progressCtx);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
catch { /* partial state is better than no state */ }
|
|
455
|
-
if (parts.length === 0) {
|
|
456
|
-
return { decision: "allow" };
|
|
457
|
-
}
|
|
458
|
-
return {
|
|
459
|
-
decision: "allow",
|
|
460
|
-
additionalContext: parts.join("\n\n"),
|
|
461
|
-
};
|
|
462
|
-
},
|
|
463
|
-
},
|
|
464
|
-
],
|
|
465
|
-
};
|
|
466
|
-
// ── Session stats tracking hooks ─────────────────────────
|
|
467
|
-
const statsHooks = {
|
|
468
|
-
PostToolUse: [
|
|
469
|
-
{
|
|
470
|
-
handler: (payload) => {
|
|
471
|
-
const p = payload;
|
|
472
|
-
this.sessionStats.recordToolCall(p.toolName, p.durationMs ?? 0);
|
|
473
|
-
let changedWorkspace = false;
|
|
474
|
-
// Record file changes in the ChangeTracker
|
|
475
|
-
if (p.toolName === "apply_patch") {
|
|
476
|
-
const changes = extractPatchChanges(p.output);
|
|
477
|
-
for (const c of changes) {
|
|
478
|
-
this.changeTracker.recordChange(c.file, c.type);
|
|
479
|
-
changedWorkspace = true;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
else if (p.toolName === "multi_edit") {
|
|
483
|
-
const changes = extractMultiEditChanges(p.output);
|
|
484
|
-
for (const c of changes) {
|
|
485
|
-
this.changeTracker.recordChange(c.file, c.type);
|
|
486
|
-
changedWorkspace = true;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
const change = extractFileChange(p.toolName, (p.input ?? {}), p.output);
|
|
491
|
-
if (change) {
|
|
492
|
-
this.changeTracker.recordChange(change.file, change.type);
|
|
493
|
-
changedWorkspace = true;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (changedWorkspace) {
|
|
497
|
-
this._projectMapDirty = true;
|
|
498
|
-
}
|
|
499
|
-
this.progressTracker.recordVerification(p.toolName, (p.input ?? {}), p.output);
|
|
500
|
-
this.syncProgressSnapshot();
|
|
501
|
-
return { decision: "allow" };
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
],
|
|
505
|
-
PostToolUseFailure: [
|
|
506
|
-
{
|
|
507
|
-
handler: (payload) => {
|
|
508
|
-
const p = payload;
|
|
509
|
-
const errMsg = p.error instanceof Error ? p.error.message : String(p.error);
|
|
510
|
-
this.sessionStats.recordToolError(p.toolName, errMsg);
|
|
511
|
-
this.syncProgressSnapshot();
|
|
512
|
-
return { decision: "allow" };
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
],
|
|
516
|
-
PreCompact: [
|
|
517
|
-
{
|
|
518
|
-
handler: () => {
|
|
519
|
-
this.sessionStats.recordCompaction();
|
|
520
|
-
return { decision: "allow" };
|
|
521
|
-
},
|
|
522
|
-
},
|
|
523
|
-
],
|
|
524
|
-
};
|
|
525
|
-
// ── Todo-aware Stop hook (blocks premature exit up to 2 times) ──
|
|
526
|
-
const todoStopHooks = {
|
|
527
|
-
Stop: [
|
|
528
|
-
{
|
|
529
|
-
handler: () => {
|
|
530
|
-
const state = this.todoModule.getState();
|
|
531
|
-
const outstanding = state.todos.filter((t) => t.status === "in_progress" || t.status === "pending");
|
|
532
|
-
if (outstanding.length > 0 && this._stopNudgeCount < 2) {
|
|
533
|
-
this._stopNudgeCount++;
|
|
534
|
-
const names = outstanding.map((t) => t.content).join(", ");
|
|
535
|
-
const reason = this._stopNudgeCount === 1
|
|
536
|
-
? `Outstanding tasks remain: ${names}. Complete them or mark as completed before stopping.`
|
|
537
|
-
: `You still have ${outstanding.length} uncompleted task(s). Continue working — do not stop until tasks are addressed.`;
|
|
538
|
-
return { decision: "block", reason };
|
|
539
|
-
}
|
|
540
|
-
return { decision: "allow" };
|
|
541
|
-
},
|
|
542
|
-
},
|
|
543
|
-
],
|
|
544
|
-
};
|
|
545
|
-
userEngine.configure(permissionHooks);
|
|
546
|
-
userEngine.configure(postCompactHooks);
|
|
547
|
-
userEngine.configure(statsHooks);
|
|
548
|
-
userEngine.configure(todoStopHooks);
|
|
549
|
-
this.hooksEngine = HooksEngine.compose(HooksEngine.withSecurityGuardrails(), userEngine);
|
|
550
|
-
this.autoCompactionEnabled = config.oniConfig?.compaction?.auto !== false;
|
|
551
|
-
const summaryModel = config.fastModel ?? config.model;
|
|
552
|
-
this.compactor = new ContextCompactor({
|
|
553
|
-
summaryModel,
|
|
554
|
-
threshold: config.oniConfig?.compaction?.threshold,
|
|
555
|
-
});
|
|
556
|
-
// ── Background scheduler ─────────────────────────────────
|
|
557
|
-
this.scheduler = new BackgroundScheduler();
|
|
558
|
-
// Register proactive compaction check — runs every 30s.
|
|
559
|
-
// If conversation history exceeds compaction threshold between
|
|
560
|
-
// chat calls, compact proactively so the next chat starts clean.
|
|
561
|
-
if (this.autoCompactionEnabled) {
|
|
562
|
-
this.scheduler.register({
|
|
563
|
-
id: "token-budget-monitor",
|
|
564
|
-
intervalMs: 30_000,
|
|
565
|
-
runImmediately: false,
|
|
566
|
-
run: async () => {
|
|
567
|
-
// Skip if chat() is in progress — the agentLoop handles its own
|
|
568
|
-
// compaction internally, and compacting here would race with its
|
|
569
|
-
// finalMessages capture (overwriting the scheduler's compaction).
|
|
570
|
-
if (this._chatActive)
|
|
571
|
-
return;
|
|
572
|
-
if (this.conversationHistory.length < 4)
|
|
573
|
-
return;
|
|
574
|
-
const check = this.compactor.checkCompaction(this.conversationHistory);
|
|
575
|
-
if (!check.needed)
|
|
576
|
-
return;
|
|
577
|
-
this.eventBus.emit({
|
|
578
|
-
type: "session.compacting",
|
|
579
|
-
sessionId: "",
|
|
580
|
-
messageCount: this.conversationHistory.length,
|
|
581
|
-
estimatedTokens: check.estimatedTokens,
|
|
582
|
-
threshold: check.threshold,
|
|
583
|
-
maxTokens: check.maxTokens,
|
|
584
|
-
percentUsed: check.percentUsed,
|
|
585
|
-
timestamp: Date.now(),
|
|
586
|
-
});
|
|
587
|
-
const before = this.conversationHistory.length;
|
|
588
|
-
await this.hooksEngine.fire("PreCompact", {
|
|
589
|
-
sessionId: "",
|
|
590
|
-
messageCount: before,
|
|
591
|
-
estimatedTokens: check.estimatedTokens,
|
|
592
|
-
});
|
|
593
|
-
try {
|
|
594
|
-
const compacted = await this.compactor.compact(this.conversationHistory, { skipInitialCheck: true });
|
|
595
|
-
const summarized = compacted.length <= 2 && before > 2;
|
|
596
|
-
const estimatedTokensAfter = this.compactor.estimateTokens(compacted);
|
|
597
|
-
const percentUsedAfter = check.maxTokens > 0
|
|
598
|
-
? estimatedTokensAfter / check.maxTokens
|
|
599
|
-
: 0;
|
|
600
|
-
this.conversationHistory = compacted;
|
|
601
|
-
this.eventBus.emit({
|
|
602
|
-
type: "session.compacted",
|
|
603
|
-
sessionId: "",
|
|
604
|
-
beforeCount: before,
|
|
605
|
-
afterCount: compacted.length,
|
|
606
|
-
summarized,
|
|
607
|
-
estimatedTokensBefore: check.estimatedTokens,
|
|
608
|
-
estimatedTokensAfter,
|
|
609
|
-
threshold: check.threshold,
|
|
610
|
-
maxTokens: check.maxTokens,
|
|
611
|
-
percentUsedBefore: check.percentUsed,
|
|
612
|
-
percentUsedAfter,
|
|
613
|
-
timestamp: Date.now(),
|
|
614
|
-
});
|
|
615
|
-
// Fire PostCompact for state reinsertion
|
|
616
|
-
const postResult = await this.hooksEngine.fire("PostCompact", {
|
|
617
|
-
sessionId: "",
|
|
618
|
-
beforeCount: before,
|
|
619
|
-
afterCount: compacted.length,
|
|
620
|
-
estimatedTokensAfter,
|
|
621
|
-
summarized,
|
|
622
|
-
});
|
|
623
|
-
// Merge state reinsertion + continuation into a single user/assistant
|
|
624
|
-
// pair. Two separate pairs would waste ~40-60 tokens per compaction on
|
|
625
|
-
// dead "acknowledgment" turns ("State restored." / "Understood.").
|
|
626
|
-
const focus = this.todoModule.getCurrentFocus();
|
|
627
|
-
const continuationText = focus
|
|
628
|
-
? `Continue working. Current focus: ${focus.content}`
|
|
629
|
-
: "Continue if you have next steps.";
|
|
630
|
-
const userContent = postResult?.additionalContext
|
|
631
|
-
? `<system-reminder>\n${postResult.additionalContext}\n</system-reminder>\n\n${continuationText}`
|
|
632
|
-
: continuationText;
|
|
633
|
-
this.conversationHistory.push({ role: "user", content: userContent }, { role: "assistant", content: "Context loaded." });
|
|
634
|
-
}
|
|
635
|
-
catch (err) {
|
|
636
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
637
|
-
this.eventBus.emit({
|
|
638
|
-
type: "session.compacted",
|
|
639
|
-
sessionId: "",
|
|
640
|
-
beforeCount: before,
|
|
641
|
-
afterCount: before,
|
|
642
|
-
summarized: false,
|
|
643
|
-
estimatedTokensBefore: check.estimatedTokens,
|
|
644
|
-
threshold: check.threshold,
|
|
645
|
-
maxTokens: check.maxTokens,
|
|
646
|
-
percentUsedBefore: check.percentUsed,
|
|
647
|
-
failed: true,
|
|
648
|
-
error: errorMsg,
|
|
649
|
-
timestamp: Date.now(),
|
|
650
|
-
});
|
|
651
|
-
this.eventBus.emit({
|
|
652
|
-
type: "error",
|
|
653
|
-
agent: this.activeAgent,
|
|
654
|
-
error: new Error(`Context compaction failed: ${errorMsg}`),
|
|
655
|
-
timestamp: Date.now(),
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
},
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
// ── Task evaluator (auto-dispatch) ──────────────────────
|
|
662
|
-
this.taskEvaluator = new TaskEvaluator();
|
|
663
|
-
// ── Session tree ──────────────────────────────────────────
|
|
664
|
-
this.sessionTree = new SessionTree();
|
|
665
|
-
}
|
|
666
|
-
// ── Public: chat (multi-turn with history) ─────────────────
|
|
667
|
-
async *chat(message, opts) {
|
|
668
|
-
this._chatActive = true;
|
|
669
|
-
this._stopNudgeCount = 0;
|
|
670
|
-
this.progressTracker.recordUserTask(message);
|
|
671
|
-
this.syncProgressSnapshot();
|
|
672
|
-
try {
|
|
673
|
-
// ── Resolve active agent config ──────────────────────────
|
|
674
|
-
const agentDef = this.agentRegistry.get(this.activeAgent);
|
|
675
|
-
const env = {
|
|
676
|
-
cwd: this.rootDir,
|
|
677
|
-
platform: process.platform,
|
|
678
|
-
date: new Date().toISOString().split("T")[0],
|
|
679
|
-
};
|
|
680
|
-
// Use cached prompt if agent hasn't changed (saves ~10ms/turn of string construction)
|
|
681
|
-
if (this._cachedSystemPrompt === null || this._cachedPromptAgent !== this.activeAgent || this._cachedSkillVersion !== this.skillLoader.version) {
|
|
682
|
-
let prompt = agentDef?.systemPrompt ?? buildSystemPrompt(env);
|
|
683
|
-
// Append project context files (CLAUDE.md, AGENTS.md, .cursorrules)
|
|
684
|
-
if (this.contextFilesBlock) {
|
|
685
|
-
prompt += "\n\n" + this.contextFilesBlock;
|
|
686
|
-
}
|
|
687
|
-
// Append available skill descriptions (progressive disclosure — names only)
|
|
688
|
-
const skillBlock = this.skillLoader.getDescriptionsForContext();
|
|
689
|
-
if (skillBlock) {
|
|
690
|
-
prompt += "\n\n" + skillBlock;
|
|
691
|
-
}
|
|
692
|
-
const progressBlock = this.progressTracker.toContextString();
|
|
693
|
-
if (progressBlock) {
|
|
694
|
-
prompt += `\n\n# Persistent Progress\n\n${progressBlock}`;
|
|
695
|
-
}
|
|
696
|
-
this._cachedSystemPrompt = prompt;
|
|
697
|
-
this._cachedPromptAgent = this.activeAgent;
|
|
698
|
-
this._cachedSkillVersion = this.skillLoader.version;
|
|
699
|
-
}
|
|
700
|
-
const systemPrompt = this._cachedSystemPrompt;
|
|
701
|
-
const maxTurns = agentDef?.maxTurns ?? this.config.maxTurns ?? 50;
|
|
702
|
-
// Scope tools by active agent's allowed list (Set for O(1) lookup)
|
|
703
|
-
const allTools = [...this.todoModule.getTools(), ...this.tools];
|
|
704
|
-
const scopedTools = agentDef?.tools
|
|
705
|
-
? (() => {
|
|
706
|
-
const allowed = new Set([...agentDef.tools, "plan_exit"]);
|
|
707
|
-
return allTools.filter((t) => allowed.has(t.name));
|
|
708
|
-
})()
|
|
709
|
-
: allTools;
|
|
710
|
-
const agentBefore = this.activeAgent;
|
|
711
|
-
// ── Emit session.created ──────────────────────────────────
|
|
712
|
-
this.eventBus.emit({
|
|
713
|
-
type: "session.created",
|
|
714
|
-
sessionId: "",
|
|
715
|
-
agentName: this.activeAgent,
|
|
716
|
-
timestamp: Date.now(),
|
|
717
|
-
});
|
|
718
|
-
// ── Auto-dispatch: evaluate for swarm before entering agent loop ──
|
|
719
|
-
const swarmRunner = this.swarmRunner;
|
|
720
|
-
const hasRealSwarmRunner = swarmRunner !== NOOP_SWARM_RUNNER;
|
|
721
|
-
const runnerPublishesSwarmLifecycle = this.swarmRunnerPublishesLifecycle;
|
|
722
|
-
const dispatch = this.taskEvaluator.evaluate(message, this.activeAgent, this.conversationHistory.length, hasRealSwarmRunner);
|
|
723
|
-
if (dispatch.shouldDispatch && dispatch.recommendation && hasRealSwarmRunner) {
|
|
724
|
-
// Hoist variables above try so the catch block can close lifecycle events
|
|
725
|
-
const rec = dispatch.recommendation;
|
|
726
|
-
const agentCount = rec.suggestedAgentCount;
|
|
727
|
-
const swarmId = `swarm_${Date.now().toString(36)}`;
|
|
728
|
-
const swarmStartMs = Date.now();
|
|
729
|
-
// Run swarm directly
|
|
730
|
-
try {
|
|
731
|
-
// Yield step_start so the UI can show swarm progress in the spinner
|
|
732
|
-
yield {
|
|
733
|
-
type: "step_start",
|
|
734
|
-
sessionId: "",
|
|
735
|
-
turn: this.turnCount,
|
|
736
|
-
timestamp: swarmStartMs,
|
|
737
|
-
metadata: {
|
|
738
|
-
swarmTopology: rec.topology,
|
|
739
|
-
agentCount,
|
|
740
|
-
agentName: "swarm",
|
|
741
|
-
},
|
|
742
|
-
};
|
|
743
|
-
if (!runnerPublishesSwarmLifecycle) {
|
|
744
|
-
this.eventBus.emit({
|
|
745
|
-
type: "swarm.started",
|
|
746
|
-
swarmId,
|
|
747
|
-
topology: rec.topology,
|
|
748
|
-
agentCount,
|
|
749
|
-
timestamp: Date.now(),
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
// Build topology-aware agent definitions with distinct roles and prompts
|
|
753
|
-
const autoAgents = buildAutoDispatchAgents(rec.topology, message, agentCount);
|
|
754
|
-
const agents = autoAgents.map((a) => ({
|
|
755
|
-
id: a.id,
|
|
756
|
-
role: a.role,
|
|
757
|
-
systemPrompt: a.systemPrompt,
|
|
758
|
-
}));
|
|
759
|
-
const result = await swarmRunner.run({
|
|
760
|
-
topology: rec.topology,
|
|
761
|
-
task: message,
|
|
762
|
-
agents,
|
|
763
|
-
tools: ["read_file", "edit_file", "write_file", "bash", "grep", "glob", "git", "list_dir", "apply_patch", "list_changes", "undo_edit", "multi_edit", "webfetch", "websearch"],
|
|
764
|
-
maxTurns: 30,
|
|
765
|
-
}, undefined, { signal: opts?.signal });
|
|
766
|
-
if (!runnerPublishesSwarmLifecycle) {
|
|
767
|
-
this.eventBus.emit({
|
|
768
|
-
type: "swarm.completed",
|
|
769
|
-
swarmId,
|
|
770
|
-
topology: rec.topology,
|
|
771
|
-
agentCount,
|
|
772
|
-
duration: result.duration,
|
|
773
|
-
durationMs: result.duration * 1000,
|
|
774
|
-
agentResults: { outcome: result.outcome, filesModified: result.filesModified },
|
|
775
|
-
timestamp: Date.now(),
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
// Yield step_finish so the UI can clear swarm progress state
|
|
779
|
-
yield {
|
|
780
|
-
type: "step_finish",
|
|
781
|
-
sessionId: "",
|
|
782
|
-
turn: this.turnCount,
|
|
783
|
-
timestamp: Date.now(),
|
|
784
|
-
metadata: {
|
|
785
|
-
stepDurationMs: Date.now() - swarmStartMs,
|
|
786
|
-
swarmTopology: rec.topology,
|
|
787
|
-
agentName: "swarm",
|
|
788
|
-
},
|
|
789
|
-
};
|
|
790
|
-
// Record swarm in session stats so stats reflect auto-dispatched work
|
|
791
|
-
this.sessionStats.recordToolCall("spawn_swarm", result.duration);
|
|
792
|
-
for (const file of result.filesModified) {
|
|
793
|
-
this.changeTracker.recordChange(file, "edit");
|
|
794
|
-
}
|
|
795
|
-
// Record in conversation history using the structured summarizer so
|
|
796
|
-
// the model gets topology, per-agent summaries, files, and duration.
|
|
797
|
-
const assistantContent = summarizeSwarmResult({
|
|
798
|
-
topology: rec.topology,
|
|
799
|
-
task: message,
|
|
800
|
-
outcome: result.outcome,
|
|
801
|
-
agentSummaries: result.agentSummaries,
|
|
802
|
-
filesModified: result.filesModified,
|
|
803
|
-
duration: result.duration,
|
|
804
|
-
});
|
|
805
|
-
this.progressTracker.recordAssistantSummary(assistantContent);
|
|
806
|
-
this.syncProgressSnapshot();
|
|
807
|
-
this.conversationHistory.push({ role: "user", content: message }, { role: "assistant", content: assistantContent });
|
|
808
|
-
this.turnCount++;
|
|
809
|
-
// Emit session.completed for the swarm path too
|
|
810
|
-
this.eventBus.emit({
|
|
811
|
-
type: "session.completed",
|
|
812
|
-
sessionId: "",
|
|
813
|
-
turns: this.turnCount,
|
|
814
|
-
reason: "swarm_completed",
|
|
815
|
-
timestamp: Date.now(),
|
|
816
|
-
});
|
|
817
|
-
// Yield assistant message so the UI renders the swarm outcome.
|
|
818
|
-
// Without this, the outcome only appears in the "result" message
|
|
819
|
-
// whose content is ignored by MessageBlock (returns null).
|
|
820
|
-
yield {
|
|
821
|
-
type: "assistant",
|
|
822
|
-
sessionId: "",
|
|
823
|
-
turn: this.turnCount,
|
|
824
|
-
timestamp: Date.now(),
|
|
825
|
-
content: assistantContent,
|
|
826
|
-
};
|
|
827
|
-
yield {
|
|
828
|
-
type: "result",
|
|
829
|
-
sessionId: "",
|
|
830
|
-
turn: this.turnCount,
|
|
831
|
-
timestamp: Date.now(),
|
|
832
|
-
content: result.outcome,
|
|
833
|
-
metadata: {
|
|
834
|
-
swarmAutoDispatched: true,
|
|
835
|
-
topology: dispatch.recommendation.topology,
|
|
836
|
-
agentCount: dispatch.recommendation.suggestedAgentCount,
|
|
837
|
-
filesModified: result.filesModified,
|
|
838
|
-
duration: result.duration,
|
|
839
|
-
finalMessages: this.conversationHistory,
|
|
840
|
-
},
|
|
841
|
-
};
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
catch (err) {
|
|
845
|
-
// Swarm failed — close lifecycle events, then fall through to agent loop
|
|
846
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
847
|
-
// Close the step_start/step_finish pair so the UI clears swarm spinner
|
|
848
|
-
yield {
|
|
849
|
-
type: "step_finish",
|
|
850
|
-
sessionId: "",
|
|
851
|
-
turn: this.turnCount,
|
|
852
|
-
timestamp: Date.now(),
|
|
853
|
-
metadata: {
|
|
854
|
-
stepDurationMs: Date.now() - swarmStartMs,
|
|
855
|
-
swarmTopology: rec.topology,
|
|
856
|
-
agentName: "swarm",
|
|
857
|
-
error: errMsg,
|
|
858
|
-
},
|
|
859
|
-
};
|
|
860
|
-
// Close the swarm.started EventBus event so subscribers see completion
|
|
861
|
-
if (!runnerPublishesSwarmLifecycle) {
|
|
862
|
-
this.eventBus.emit({
|
|
863
|
-
type: "swarm.completed",
|
|
864
|
-
swarmId,
|
|
865
|
-
topology: rec.topology,
|
|
866
|
-
agentCount,
|
|
867
|
-
durationMs: Date.now() - swarmStartMs,
|
|
868
|
-
agentResults: { error: errMsg },
|
|
869
|
-
timestamp: Date.now(),
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
yield {
|
|
873
|
-
type: "error",
|
|
874
|
-
sessionId: "",
|
|
875
|
-
turn: this.turnCount,
|
|
876
|
-
timestamp: Date.now(),
|
|
877
|
-
content: `Swarm auto-dispatch failed, falling back to agent loop: ${errMsg}`,
|
|
878
|
-
};
|
|
879
|
-
// Continue to normal agentLoop below
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
const loopConfig = {
|
|
883
|
-
model: this.config.model,
|
|
884
|
-
tools: scopedTools,
|
|
885
|
-
agentName: this.activeAgent,
|
|
886
|
-
systemPrompt,
|
|
887
|
-
maxTurns,
|
|
888
|
-
maxTokens: this.config.maxTokens ?? 16384,
|
|
889
|
-
initialMessages: this.conversationHistory,
|
|
890
|
-
todoModule: this.todoModule,
|
|
891
|
-
hooksEngine: this.hooksEngine,
|
|
892
|
-
compactor: this.autoCompactionEnabled ? this.compactor : undefined,
|
|
893
|
-
skillLoader: this.skillLoader,
|
|
894
|
-
signal: opts?.signal,
|
|
895
|
-
inferenceTimeoutMs: this.config.inferenceTimeoutMs,
|
|
896
|
-
env,
|
|
897
|
-
onQuestion: this.questionListener
|
|
898
|
-
? (question, resolve) => this.questionListener(question, resolve)
|
|
899
|
-
: undefined,
|
|
900
|
-
onToolMetadata: this.toolMetadataListener
|
|
901
|
-
? (toolCallId, toolName, update) => this.toolMetadataListener(toolCallId, toolName, update)
|
|
902
|
-
: undefined,
|
|
903
|
-
};
|
|
904
|
-
// ── Session-level circuit breaker ─────────────────────────
|
|
905
|
-
// If inference has failed on the last N consecutive chat() calls,
|
|
906
|
-
// stop attempting and tell the user the provider appears down.
|
|
907
|
-
if (this._consecutiveInferenceFailures >= Conductor.INFERENCE_FAILURE_THRESHOLD) {
|
|
908
|
-
yield {
|
|
909
|
-
type: "error",
|
|
910
|
-
sessionId: "",
|
|
911
|
-
turn: this.turnCount,
|
|
912
|
-
timestamp: Date.now(),
|
|
913
|
-
content: `Model provider appears down — inference has failed ${this._consecutiveInferenceFailures} consecutive times. Please wait and try again, or switch models.`,
|
|
914
|
-
metadata: {
|
|
915
|
-
circuitBreaker: true,
|
|
916
|
-
consecutiveFailures: this._consecutiveInferenceFailures,
|
|
917
|
-
},
|
|
918
|
-
};
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
// Track last terminal message to derive session.completed reason
|
|
922
|
-
let lastTerminalType;
|
|
923
|
-
let lastBudgetExhausted = false;
|
|
924
|
-
for await (const msg of agentLoop(message, loopConfig)) {
|
|
925
|
-
// Track terminal messages (result/error) for session.completed reason
|
|
926
|
-
if (msg.type === "result") {
|
|
927
|
-
lastTerminalType = "result";
|
|
928
|
-
lastBudgetExhausted = !!msg.metadata?.budgetExhausted;
|
|
929
|
-
}
|
|
930
|
-
else if (msg.type === "error") {
|
|
931
|
-
lastTerminalType = "error";
|
|
932
|
-
}
|
|
933
|
-
// Emit agent.start + llm.request for step_start messages
|
|
934
|
-
if (msg.type === "step_start" && msg.metadata) {
|
|
935
|
-
const step = msg.turn ?? 0;
|
|
936
|
-
this.eventBus.emit({
|
|
937
|
-
type: "agent.start",
|
|
938
|
-
agent: this.activeAgent,
|
|
939
|
-
timestamp: Date.now(),
|
|
940
|
-
step,
|
|
941
|
-
});
|
|
942
|
-
this.eventBus.emit({
|
|
943
|
-
type: "llm.request",
|
|
944
|
-
agent: this.activeAgent,
|
|
945
|
-
model: this.config.model.modelId,
|
|
946
|
-
timestamp: Date.now(),
|
|
947
|
-
messageCount: this.conversationHistory.length,
|
|
948
|
-
hasTools: scopedTools.length > 0,
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
// Emit agent.end + llm.response for step_finish messages
|
|
952
|
-
if (msg.type === "step_finish" && msg.metadata) {
|
|
953
|
-
const meta = msg.metadata;
|
|
954
|
-
const step = msg.turn ?? 0;
|
|
955
|
-
this.eventBus.emit({
|
|
956
|
-
type: "agent.end",
|
|
957
|
-
agent: this.activeAgent,
|
|
958
|
-
timestamp: Date.now(),
|
|
959
|
-
step,
|
|
960
|
-
duration: meta.stepDurationMs ?? 0,
|
|
961
|
-
usage: meta.usage,
|
|
962
|
-
});
|
|
963
|
-
this.eventBus.emit({
|
|
964
|
-
type: "llm.response",
|
|
965
|
-
agent: this.activeAgent,
|
|
966
|
-
model: this.config.model.modelId,
|
|
967
|
-
timestamp: Date.now(),
|
|
968
|
-
duration: meta.stepDurationMs ?? 0,
|
|
969
|
-
usage: meta.usage ?? { inputTokens: 0, outputTokens: 0 },
|
|
970
|
-
stopReason: meta.stopReason ?? "end",
|
|
971
|
-
hasToolCalls: (meta.toolCount ?? 0) > 0,
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
// Emit session.compacting for in-chat compaction start
|
|
975
|
-
if (msg.type === "system" && msg.subtype === "compact_start" && msg.metadata) {
|
|
976
|
-
const meta = msg.metadata;
|
|
977
|
-
this.eventBus.emit({
|
|
978
|
-
type: "session.compacting",
|
|
979
|
-
sessionId: "",
|
|
980
|
-
messageCount: meta.beforeCount ?? 0,
|
|
981
|
-
estimatedTokens: meta.estimatedTokens,
|
|
982
|
-
threshold: meta.threshold,
|
|
983
|
-
maxTokens: meta.maxTokens,
|
|
984
|
-
percentUsed: meta.percentUsed,
|
|
985
|
-
timestamp: Date.now(),
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
// Emit session.compacted for in-chat compaction boundary
|
|
989
|
-
if (msg.type === "system" && msg.subtype === "compact_boundary" && msg.metadata) {
|
|
990
|
-
const meta = msg.metadata;
|
|
991
|
-
this.eventBus.emit({
|
|
992
|
-
type: "session.compacted",
|
|
993
|
-
sessionId: "",
|
|
994
|
-
beforeCount: meta.beforeCount ?? 0,
|
|
995
|
-
afterCount: meta.afterCount ?? 0,
|
|
996
|
-
summarized: meta.summarized ?? false,
|
|
997
|
-
estimatedTokensBefore: meta.estimatedTokensBefore,
|
|
998
|
-
estimatedTokensAfter: meta.estimatedTokensAfter,
|
|
999
|
-
threshold: meta.threshold,
|
|
1000
|
-
maxTokens: meta.maxTokens,
|
|
1001
|
-
percentUsedBefore: meta.percentUsedBefore,
|
|
1002
|
-
percentUsedAfter: meta.percentUsedAfter,
|
|
1003
|
-
timestamp: Date.now(),
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
// Emit error event for compaction failures so EventBus subscribers
|
|
1007
|
-
// (analytics, UI) can react. The agent loop handles recovery internally
|
|
1008
|
-
// (continues the loop, retries next turn), but consumers should know.
|
|
1009
|
-
if (msg.type === "system" && msg.subtype === "compact_error") {
|
|
1010
|
-
const meta = msg.metadata;
|
|
1011
|
-
this.eventBus.emit({
|
|
1012
|
-
type: "session.compacted",
|
|
1013
|
-
sessionId: "",
|
|
1014
|
-
beforeCount: meta?.beforeCount ?? 0,
|
|
1015
|
-
afterCount: meta?.afterCount ?? meta?.beforeCount ?? 0,
|
|
1016
|
-
summarized: false,
|
|
1017
|
-
estimatedTokensBefore: meta?.estimatedTokensBefore,
|
|
1018
|
-
threshold: meta?.threshold,
|
|
1019
|
-
maxTokens: meta?.maxTokens,
|
|
1020
|
-
percentUsedBefore: meta?.percentUsedBefore,
|
|
1021
|
-
failed: true,
|
|
1022
|
-
error: meta?.error ?? msg.content ?? "Context compaction failed",
|
|
1023
|
-
timestamp: Date.now(),
|
|
1024
|
-
});
|
|
1025
|
-
this.eventBus.emit({
|
|
1026
|
-
type: "error",
|
|
1027
|
-
error: new Error(msg.content ?? "Context compaction failed"),
|
|
1028
|
-
timestamp: Date.now(),
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
// Emit inference.retry for retry system messages
|
|
1032
|
-
if (msg.type === "system" && msg.subtype === "inference_retry" && msg.metadata) {
|
|
1033
|
-
const meta = msg.metadata;
|
|
1034
|
-
this.eventBus.emit({
|
|
1035
|
-
type: "inference.retry",
|
|
1036
|
-
attempt: meta.attempt ?? 1,
|
|
1037
|
-
maxRetries: meta.maxRetries ?? 3,
|
|
1038
|
-
delayMs: meta.delayMs ?? 0,
|
|
1039
|
-
error: meta.error ?? msg.content ?? "Unknown error",
|
|
1040
|
-
timestamp: Date.now(),
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
// Track token usage from inference calls
|
|
1044
|
-
if (msg.type === "assistant" && msg.metadata?.usage) {
|
|
1045
|
-
const usage = msg.metadata.usage;
|
|
1046
|
-
this.sessionStats.recordInference(usage);
|
|
1047
|
-
this.eventBus.emit({
|
|
1048
|
-
type: "session.updated",
|
|
1049
|
-
sessionId: "",
|
|
1050
|
-
turn: this.turnCount + 1,
|
|
1051
|
-
tokenUsage: usage,
|
|
1052
|
-
timestamp: Date.now(),
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
// Emit tool.call for tool_start messages
|
|
1056
|
-
if (msg.type === "tool_start" && msg.metadata) {
|
|
1057
|
-
const toolName = msg.metadata.toolName;
|
|
1058
|
-
const toolArgs = msg.metadata.toolArgs;
|
|
1059
|
-
if (toolName) {
|
|
1060
|
-
this.eventBus.emit({
|
|
1061
|
-
type: "tool.call",
|
|
1062
|
-
agent: this.activeAgent,
|
|
1063
|
-
tool: toolName,
|
|
1064
|
-
timestamp: Date.now(),
|
|
1065
|
-
input: toolArgs ?? {},
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
// Emit tool.result for tool_result messages
|
|
1070
|
-
if (msg.type === "tool_result" && msg.toolResults) {
|
|
1071
|
-
for (const tr of msg.toolResults) {
|
|
1072
|
-
if (tr.isError && tr.content?.startsWith("Tool use denied:")) {
|
|
1073
|
-
this.sessionStats.recordDenied(tr.toolName, tr.content);
|
|
1074
|
-
}
|
|
1075
|
-
this.eventBus.emit({
|
|
1076
|
-
type: "tool.result",
|
|
1077
|
-
agent: this.activeAgent,
|
|
1078
|
-
tool: tr.toolName,
|
|
1079
|
-
timestamp: Date.now(),
|
|
1080
|
-
duration: tr.durationMs ?? 0,
|
|
1081
|
-
status: tr.isError ? "error" : "success",
|
|
1082
|
-
output: tr.isError ? undefined : tr.content,
|
|
1083
|
-
error: tr.isError ? tr.content : undefined,
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
// Capture final messages from result or error to update history
|
|
1088
|
-
if (msg.type === "result" || msg.type === "error") {
|
|
1089
|
-
const finalMessages = msg.metadata?.finalMessages;
|
|
1090
|
-
if (finalMessages) {
|
|
1091
|
-
this.conversationHistory = finalMessages;
|
|
1092
|
-
this.sessionTree.updateMessages(finalMessages);
|
|
1093
|
-
}
|
|
1094
|
-
if (msg.type === "result" && msg.content) {
|
|
1095
|
-
this.progressTracker.recordAssistantSummary(msg.content);
|
|
1096
|
-
this.syncProgressSnapshot();
|
|
1097
|
-
}
|
|
1098
|
-
if (msg.type === "error") {
|
|
1099
|
-
this.eventBus.emit({
|
|
1100
|
-
type: "error",
|
|
1101
|
-
agent: this.activeAgent,
|
|
1102
|
-
error: new Error(msg.content ?? "Unknown error"),
|
|
1103
|
-
timestamp: Date.now(),
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
yield msg;
|
|
1108
|
-
}
|
|
1109
|
-
this.turnCount++;
|
|
1110
|
-
// ── Update session-level circuit breaker ─────────────────
|
|
1111
|
-
if (lastTerminalType === "error") {
|
|
1112
|
-
this._consecutiveInferenceFailures++;
|
|
1113
|
-
}
|
|
1114
|
-
else {
|
|
1115
|
-
this._consecutiveInferenceFailures = 0;
|
|
1116
|
-
}
|
|
1117
|
-
// ── Fire-and-forget title generation on first turn ──────
|
|
1118
|
-
if (this.turnCount === 1) {
|
|
1119
|
-
void this.titleGenerator.generate(message).catch(() => { });
|
|
1120
|
-
}
|
|
1121
|
-
// Emit session.completed with reason derived from actual termination
|
|
1122
|
-
const completedReason = lastTerminalType === "error"
|
|
1123
|
-
? "error"
|
|
1124
|
-
: lastBudgetExhausted
|
|
1125
|
-
? "budget_exhausted"
|
|
1126
|
-
: "completed";
|
|
1127
|
-
this.eventBus.emit({
|
|
1128
|
-
type: "session.completed",
|
|
1129
|
-
sessionId: "",
|
|
1130
|
-
turns: this.turnCount,
|
|
1131
|
-
reason: completedReason,
|
|
1132
|
-
timestamp: Date.now(),
|
|
1133
|
-
});
|
|
1134
|
-
// ── Yield agent_switch if agent changed during this turn ──
|
|
1135
|
-
if (this.activeAgent !== agentBefore) {
|
|
1136
|
-
yield {
|
|
1137
|
-
type: "agent_switch",
|
|
1138
|
-
sessionId: "",
|
|
1139
|
-
turn: this.turnCount,
|
|
1140
|
-
timestamp: Date.now(),
|
|
1141
|
-
content: `Agent switched from ${agentBefore} to ${this.activeAgent}`,
|
|
1142
|
-
metadata: {
|
|
1143
|
-
previousAgent: agentBefore,
|
|
1144
|
-
newAgent: this.activeAgent,
|
|
1145
|
-
},
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
finally {
|
|
1150
|
-
this._chatActive = false;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
// ── Public: chatToResult ────────────────────────────────────
|
|
1154
|
-
async chatToResult(message) {
|
|
1155
|
-
let finalResult = "";
|
|
1156
|
-
for await (const msg of this.chat(message)) {
|
|
1157
|
-
if (msg.type === "result") {
|
|
1158
|
-
finalResult = msg.content ?? "";
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
return finalResult;
|
|
1162
|
-
}
|
|
1163
|
-
// ── Public: accessors ───────────────────────────────────────
|
|
1164
|
-
getPermissions() {
|
|
1165
|
-
return this.permissions;
|
|
1166
|
-
}
|
|
1167
|
-
setPermissionListener(listener) {
|
|
1168
|
-
this.permissionListener = listener;
|
|
1169
|
-
}
|
|
1170
|
-
setQuestionListener(listener) {
|
|
1171
|
-
this.questionListener = listener;
|
|
1172
|
-
}
|
|
1173
|
-
setToolMetadataListener(listener) {
|
|
1174
|
-
this.toolMetadataListener = listener;
|
|
1175
|
-
}
|
|
1176
|
-
/** Get the active agent name */
|
|
1177
|
-
getActiveAgent() {
|
|
1178
|
-
return this.activeAgent;
|
|
1179
|
-
}
|
|
1180
|
-
/** Switch the active agent. Takes effect on the next chat() call. */
|
|
1181
|
-
switchAgent(name) {
|
|
1182
|
-
const def = this.agentRegistry.get(name);
|
|
1183
|
-
if (!def) {
|
|
1184
|
-
throw new Error(`Unknown agent "${name}". Available: ${this.agentRegistry.names().join(", ")}`);
|
|
1185
|
-
}
|
|
1186
|
-
this.activeAgent = name;
|
|
1187
|
-
this._cachedSystemPrompt = null; // Invalidate prompt cache
|
|
1188
|
-
}
|
|
1189
|
-
/** Get the auto-generated session title (null if not yet generated). */
|
|
1190
|
-
getSessionTitle() {
|
|
1191
|
-
return this.titleGenerator.getTitle();
|
|
1192
|
-
}
|
|
1193
|
-
getChangeTracker() {
|
|
1194
|
-
return this.changeTracker;
|
|
1195
|
-
}
|
|
1196
|
-
getFileSnapshots() {
|
|
1197
|
-
return this.fileSnapshots;
|
|
1198
|
-
}
|
|
1199
|
-
getTurnCount() {
|
|
1200
|
-
return this.turnCount;
|
|
1201
|
-
}
|
|
1202
|
-
/** Get loaded context files content (CLAUDE.md, AGENTS.md, .cursorrules). Empty string if none found. */
|
|
1203
|
-
getContextFiles() {
|
|
1204
|
-
return this.contextFilesBlock;
|
|
1205
|
-
}
|
|
1206
|
-
/** Get the durable session progress snapshot used across restarts/compaction. */
|
|
1207
|
-
getProgressTracker() {
|
|
1208
|
-
return this.progressTracker;
|
|
1209
|
-
}
|
|
1210
|
-
/** Get the skill loader for external skill registration and invocation */
|
|
1211
|
-
getSkillLoader() {
|
|
1212
|
-
return this.skillLoader;
|
|
1213
|
-
}
|
|
1214
|
-
/** Get the currently available tool names for UI display. */
|
|
1215
|
-
getAvailableToolNames() {
|
|
1216
|
-
const names = [...this.todoModule.getTools(), ...this.tools].map((tool) => tool.name);
|
|
1217
|
-
return Array.from(new Set(names)).sort((a, b) => a.localeCompare(b));
|
|
1218
|
-
}
|
|
1219
|
-
/** Get the currently loaded skill names for UI display. */
|
|
1220
|
-
getAvailableSkillNames() {
|
|
1221
|
-
return this.skillLoader.getAll().map((skill) => skill.name).sort((a, b) => a.localeCompare(b));
|
|
1222
|
-
}
|
|
1223
|
-
/** Get configured MCP server names from ONI config. */
|
|
1224
|
-
getConfiguredMCPServerNames() {
|
|
1225
|
-
return Object.keys(this.config.oniConfig?.mcpServers ?? {}).sort((a, b) => a.localeCompare(b));
|
|
1226
|
-
}
|
|
1227
|
-
/** Invoke a skill by name with optional arguments — queues for next turn */
|
|
1228
|
-
invokeSkill(name, args) {
|
|
1229
|
-
return this.skillLoader.invoke(name, args);
|
|
1230
|
-
}
|
|
1231
|
-
getHistoryLength() {
|
|
1232
|
-
return this.conversationHistory.length;
|
|
1233
|
-
}
|
|
1234
|
-
clearHistory() {
|
|
1235
|
-
this.conversationHistory = [];
|
|
1236
|
-
this.turnCount = 0;
|
|
1237
|
-
this.sessionStats.reset();
|
|
1238
|
-
}
|
|
1239
|
-
// ── Session forking ──────────────────────────────────────────
|
|
1240
|
-
/**
|
|
1241
|
-
* Fork the current conversation at a specific message index.
|
|
1242
|
-
* Creates a new branch with messages [0..atIndex-1].
|
|
1243
|
-
* If atIndex is omitted, forks at the current end.
|
|
1244
|
-
* Returns the new branch ID.
|
|
1245
|
-
*/
|
|
1246
|
-
forkSession(atIndex, label) {
|
|
1247
|
-
// Ensure tree is synced with current history
|
|
1248
|
-
this.sessionTree.updateMessages(this.conversationHistory);
|
|
1249
|
-
return this.sessionTree.fork(atIndex, label);
|
|
1250
|
-
}
|
|
1251
|
-
/**
|
|
1252
|
-
* Switch to a different conversation branch.
|
|
1253
|
-
* Updates the internal history to the branch's messages.
|
|
1254
|
-
*/
|
|
1255
|
-
switchBranch(branchId) {
|
|
1256
|
-
const msgs = this.sessionTree.switchBranch(branchId);
|
|
1257
|
-
this.conversationHistory = msgs;
|
|
1258
|
-
}
|
|
1259
|
-
/**
|
|
1260
|
-
* List all conversation branches with summary info.
|
|
1261
|
-
*/
|
|
1262
|
-
listBranches() {
|
|
1263
|
-
return this.sessionTree.listBranches();
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Get the active branch ID.
|
|
1267
|
-
*/
|
|
1268
|
-
getActiveBranchId() {
|
|
1269
|
-
return this.sessionTree.getActiveBranchId();
|
|
1270
|
-
}
|
|
1271
|
-
/**
|
|
1272
|
-
* Delete a branch. Cannot delete the root.
|
|
1273
|
-
* If cascade is true, also deletes all children.
|
|
1274
|
-
*/
|
|
1275
|
-
deleteBranch(branchId, cascade = false) {
|
|
1276
|
-
const deleted = this.sessionTree.deleteBranch(branchId, cascade);
|
|
1277
|
-
// If active branch was deleted, sync history to the new active (root)
|
|
1278
|
-
if (deleted && this.sessionTree.getActiveBranchId() !== branchId) {
|
|
1279
|
-
this.conversationHistory = this.sessionTree.getMessagesForLoop();
|
|
1280
|
-
}
|
|
1281
|
-
return deleted;
|
|
1282
|
-
}
|
|
1283
|
-
/**
|
|
1284
|
-
* Get the session tree for advanced operations (serialization, etc.).
|
|
1285
|
-
*/
|
|
1286
|
-
getSessionTree() {
|
|
1287
|
-
return this.sessionTree;
|
|
1288
|
-
}
|
|
1289
|
-
/** Get session statistics (tool calls, tokens, compactions, errors). */
|
|
1290
|
-
getSessionStats() {
|
|
1291
|
-
return this.sessionStats;
|
|
1292
|
-
}
|
|
1293
|
-
/** Get the background scheduler for registering periodic tasks. */
|
|
1294
|
-
getScheduler() {
|
|
1295
|
-
return this.scheduler;
|
|
1296
|
-
}
|
|
1297
|
-
/** Get the loop detector for inspection or configuration. */
|
|
1298
|
-
getLoopDetector() {
|
|
1299
|
-
return this.loopDetector;
|
|
1300
|
-
}
|
|
1301
|
-
/** Get the event bus for subscribing to lifecycle events. */
|
|
1302
|
-
getEventBus() {
|
|
1303
|
-
return this.eventBus;
|
|
1304
|
-
}
|
|
1305
|
-
/** Get the file watcher for reactive file change detection. */
|
|
1306
|
-
getFileWatcher() {
|
|
1307
|
-
return this.fileWatcher;
|
|
1308
|
-
}
|
|
1309
|
-
/** Get the cron scheduler for managing recurring prompts. */
|
|
1310
|
-
getCronScheduler() {
|
|
1311
|
-
return this.cronScheduler;
|
|
1312
|
-
}
|
|
1313
|
-
/** Stop all background tasks and release resources.
|
|
1314
|
-
* Each cleanup step is isolated — a failure in one (e.g. corrupted
|
|
1315
|
-
* file handle, dead LSP process) must not prevent the others from
|
|
1316
|
-
* running, or resources leak (timers, child processes, listeners). */
|
|
1317
|
-
dispose() {
|
|
1318
|
-
try {
|
|
1319
|
-
this.fileWatcher.stop();
|
|
1320
|
-
}
|
|
1321
|
-
catch { /* best-effort */ }
|
|
1322
|
-
try {
|
|
1323
|
-
this.scheduler.dispose();
|
|
1324
|
-
}
|
|
1325
|
-
catch { /* best-effort */ }
|
|
1326
|
-
try {
|
|
1327
|
-
this.cronScheduler.dispose();
|
|
1328
|
-
}
|
|
1329
|
-
catch { /* best-effort */ }
|
|
1330
|
-
try {
|
|
1331
|
-
this.lspManager.disposeAll();
|
|
1332
|
-
}
|
|
1333
|
-
catch { /* best-effort */ }
|
|
1334
|
-
try {
|
|
1335
|
-
this.eventBus.dispose();
|
|
1336
|
-
}
|
|
1337
|
-
catch { /* best-effort */ }
|
|
1338
|
-
}
|
|
1339
|
-
/** Get the LSP manager for inspection or external use. */
|
|
1340
|
-
getLSPManager() {
|
|
1341
|
-
return this.lspManager;
|
|
1342
|
-
}
|
|
1343
|
-
/** Get the task evaluator for inspection or testing. */
|
|
1344
|
-
getTaskEvaluator() {
|
|
1345
|
-
return this.taskEvaluator;
|
|
1346
|
-
}
|
|
1347
|
-
/** Manually trigger context compaction. Returns before/after message counts. */
|
|
1348
|
-
async compactContext() {
|
|
1349
|
-
if (this.conversationHistory.length < 4)
|
|
1350
|
-
return null;
|
|
1351
|
-
const before = this.conversationHistory.length;
|
|
1352
|
-
const check = this.compactor.checkCompaction(this.conversationHistory);
|
|
1353
|
-
this.eventBus.emit({
|
|
1354
|
-
type: "session.compacting",
|
|
1355
|
-
sessionId: "",
|
|
1356
|
-
messageCount: before,
|
|
1357
|
-
estimatedTokens: check.estimatedTokens,
|
|
1358
|
-
threshold: check.threshold,
|
|
1359
|
-
maxTokens: check.maxTokens,
|
|
1360
|
-
percentUsed: check.percentUsed,
|
|
1361
|
-
timestamp: Date.now(),
|
|
1362
|
-
});
|
|
1363
|
-
await this.hooksEngine.fire("PreCompact", {
|
|
1364
|
-
sessionId: "",
|
|
1365
|
-
messageCount: before,
|
|
1366
|
-
estimatedTokens: check.estimatedTokens,
|
|
1367
|
-
});
|
|
1368
|
-
try {
|
|
1369
|
-
const compacted = await this.compactor.compact(this.conversationHistory, { skipInitialCheck: true });
|
|
1370
|
-
const summarized = compacted.length <= 2 && before > 2;
|
|
1371
|
-
const estimatedTokensAfter = this.compactor.estimateTokens(compacted);
|
|
1372
|
-
const percentUsedAfter = check.maxTokens > 0
|
|
1373
|
-
? estimatedTokensAfter / check.maxTokens
|
|
1374
|
-
: 0;
|
|
1375
|
-
this.conversationHistory = compacted;
|
|
1376
|
-
this.eventBus.emit({
|
|
1377
|
-
type: "session.compacted",
|
|
1378
|
-
sessionId: "",
|
|
1379
|
-
beforeCount: before,
|
|
1380
|
-
afterCount: compacted.length,
|
|
1381
|
-
summarized,
|
|
1382
|
-
estimatedTokensBefore: check.estimatedTokens,
|
|
1383
|
-
estimatedTokensAfter,
|
|
1384
|
-
threshold: check.threshold,
|
|
1385
|
-
maxTokens: check.maxTokens,
|
|
1386
|
-
percentUsedBefore: check.percentUsed,
|
|
1387
|
-
percentUsedAfter,
|
|
1388
|
-
timestamp: Date.now(),
|
|
1389
|
-
});
|
|
1390
|
-
// Fire PostCompact hooks for state reinsertion (todo list, change tracker, stats)
|
|
1391
|
-
const postResult = await this.hooksEngine.fire("PostCompact", {
|
|
1392
|
-
sessionId: "",
|
|
1393
|
-
beforeCount: before,
|
|
1394
|
-
afterCount: compacted.length,
|
|
1395
|
-
estimatedTokensAfter,
|
|
1396
|
-
summarized,
|
|
1397
|
-
});
|
|
1398
|
-
// Inject state reinsertion + continuation prompt
|
|
1399
|
-
const focus = this.todoModule.getCurrentFocus();
|
|
1400
|
-
const continuationText = focus
|
|
1401
|
-
? `Continue working. Current focus: ${focus.content}`
|
|
1402
|
-
: "Continue if you have next steps.";
|
|
1403
|
-
const userContent = postResult?.additionalContext
|
|
1404
|
-
? `<system-reminder>\n${postResult.additionalContext}\n</system-reminder>\n\n${continuationText}`
|
|
1405
|
-
: continuationText;
|
|
1406
|
-
this.conversationHistory.push({ role: "user", content: userContent }, { role: "assistant", content: "Context loaded." });
|
|
1407
|
-
return { before, after: compacted.length };
|
|
1408
|
-
}
|
|
1409
|
-
catch (err) {
|
|
1410
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1411
|
-
// Compaction failed — close the lifecycle event and degrade gracefully.
|
|
1412
|
-
// Conversation history is unchanged (compact() returns a new array).
|
|
1413
|
-
this.eventBus.emit({
|
|
1414
|
-
type: "session.compacted",
|
|
1415
|
-
sessionId: "",
|
|
1416
|
-
beforeCount: before,
|
|
1417
|
-
afterCount: before,
|
|
1418
|
-
summarized: false,
|
|
1419
|
-
estimatedTokensBefore: check.estimatedTokens,
|
|
1420
|
-
threshold: check.threshold,
|
|
1421
|
-
maxTokens: check.maxTokens,
|
|
1422
|
-
percentUsedBefore: check.percentUsed,
|
|
1423
|
-
failed: true,
|
|
1424
|
-
error: errorMsg,
|
|
1425
|
-
timestamp: Date.now(),
|
|
1426
|
-
});
|
|
1427
|
-
this.eventBus.emit({
|
|
1428
|
-
type: "error",
|
|
1429
|
-
agent: this.activeAgent,
|
|
1430
|
-
error: new Error(`Context compaction failed: ${errorMsg}`),
|
|
1431
|
-
timestamp: Date.now(),
|
|
1432
|
-
});
|
|
1433
|
-
return null;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
// ── Private: runSubagent ────────────────────────────────────
|
|
1437
|
-
async runSubagent(task, agentName) {
|
|
1438
|
-
try {
|
|
1439
|
-
// Resolve agent definition from registry (if named)
|
|
1440
|
-
const agentDef = agentName ? this.agentRegistry.get(agentName) : undefined;
|
|
1441
|
-
// Reuse parent's prebuilt subagent tools (coding + web + custom).
|
|
1442
|
-
// These already share LSPManager, ChangeTracker, and FileSnapshots
|
|
1443
|
-
// so subagent edits are visible to list_changes and undo_edit.
|
|
1444
|
-
const scopedTools = agentDef?.tools
|
|
1445
|
-
? (() => {
|
|
1446
|
-
const allowed = new Set(agentDef.tools);
|
|
1447
|
-
return this.subagentTools.filter((t) => allowed.has(t.name));
|
|
1448
|
-
})()
|
|
1449
|
-
: this.subagentTools;
|
|
1450
|
-
// Use fastModel for lightweight agents (explore, compaction) to reduce
|
|
1451
|
-
// latency and cost. Falls back to heavy model if no fastModel configured.
|
|
1452
|
-
const agentModel = agentDef?.useFastModel && this.config.fastModel
|
|
1453
|
-
? this.config.fastModel
|
|
1454
|
-
: this.config.model;
|
|
1455
|
-
const subHarness = ONIHarness.create({
|
|
1456
|
-
model: agentModel,
|
|
1457
|
-
fastModel: this.config.fastModel,
|
|
1458
|
-
sharedTools: scopedTools,
|
|
1459
|
-
...(agentDef?.systemPrompt ? { soul: agentDef.systemPrompt } : {}),
|
|
1460
|
-
});
|
|
1461
|
-
// Iterate run() manually instead of runToResult() so we can capture
|
|
1462
|
-
// step_finish usage and merge it into the parent's sessionStats,
|
|
1463
|
-
// and surface yielded error messages (e.g. inference failures) to
|
|
1464
|
-
// the parent agent instead of silently discarding them.
|
|
1465
|
-
let result = "";
|
|
1466
|
-
const errors = [];
|
|
1467
|
-
const agentConfig = {
|
|
1468
|
-
name: agentName ?? "subagent",
|
|
1469
|
-
...(agentDef?.maxTurns ? { maxTurns: agentDef.maxTurns } : {}),
|
|
1470
|
-
};
|
|
1471
|
-
for await (const msg of subHarness.run(task, agentConfig)) {
|
|
1472
|
-
if (msg.type === "result") {
|
|
1473
|
-
result = msg.content ?? "";
|
|
1474
|
-
}
|
|
1475
|
-
else if (msg.type === "error") {
|
|
1476
|
-
// Capture yielded errors so the parent agent knows the subagent failed
|
|
1477
|
-
if (msg.content)
|
|
1478
|
-
errors.push(msg.content);
|
|
1479
|
-
}
|
|
1480
|
-
else if (msg.type === "step_finish") {
|
|
1481
|
-
// Merge subagent inference usage into parent stats
|
|
1482
|
-
const meta = msg.metadata;
|
|
1483
|
-
if (meta?.usage) {
|
|
1484
|
-
this.sessionStats.recordInference(meta.usage);
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
return {
|
|
1489
|
-
result,
|
|
1490
|
-
filesModified: [],
|
|
1491
|
-
errors,
|
|
1492
|
-
};
|
|
1493
|
-
}
|
|
1494
|
-
catch (err) {
|
|
1495
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1496
|
-
return {
|
|
1497
|
-
result: "",
|
|
1498
|
-
filesModified: [],
|
|
1499
|
-
errors: [message],
|
|
1500
|
-
};
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
// ── Private: project_map tool ───────────────────────────────
|
|
1504
|
-
buildProjectMapTool() {
|
|
1505
|
-
let cachedMap = null;
|
|
1506
|
-
let cachedChangeVersion = -1;
|
|
1507
|
-
return defineTool({
|
|
1508
|
-
name: "project_map",
|
|
1509
|
-
description: "Get a map of the project structure — lists all files with sizes. Useful for understanding the workspace layout.",
|
|
1510
|
-
schema: {
|
|
1511
|
-
type: "object",
|
|
1512
|
-
properties: {
|
|
1513
|
-
refresh: {
|
|
1514
|
-
type: "boolean",
|
|
1515
|
-
description: "Force rebuild the project map (default false)",
|
|
1516
|
-
},
|
|
1517
|
-
},
|
|
1518
|
-
},
|
|
1519
|
-
parallelSafe: true,
|
|
1520
|
-
execute: (input) => {
|
|
1521
|
-
const currentVersion = this.changeTracker.getVersion();
|
|
1522
|
-
if (!cachedMap || input.refresh || this._projectMapDirty || cachedChangeVersion !== currentVersion) {
|
|
1523
|
-
cachedMap = ProjectMap.build(this.rootDir);
|
|
1524
|
-
cachedChangeVersion = currentVersion;
|
|
1525
|
-
this._projectMapDirty = false;
|
|
1526
|
-
}
|
|
1527
|
-
return {
|
|
1528
|
-
projectMap: cachedMap.toContextString(),
|
|
1529
|
-
fileCount: cachedMap.files.length,
|
|
1530
|
-
};
|
|
1531
|
-
},
|
|
1532
|
-
});
|
|
1533
|
-
}
|
|
1534
|
-
syncProgressSnapshot(opts) {
|
|
1535
|
-
this.progressTracker.syncTodoState(this.todoModule.getState());
|
|
1536
|
-
this.progressTracker.syncChangeTracker(this.changeTracker);
|
|
1537
|
-
if (opts?.persist !== false) {
|
|
1538
|
-
this.persistProgressSnapshot();
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
persistProgressSnapshot() {
|
|
1542
|
-
if (this.progressTracker.persist()) {
|
|
1543
|
-
this._cachedSystemPrompt = null;
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
//# sourceMappingURL=conductor.js.map
|