@compilr-dev/cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -12
- package/dist/agent.d.ts +74 -1
- package/dist/agent.js +259 -76
- package/dist/anchors/index.d.ts +9 -0
- package/dist/anchors/index.js +9 -0
- package/dist/anchors/project-anchors.d.ts +79 -0
- package/dist/anchors/project-anchors.js +202 -0
- package/dist/commands/handler-types.d.ts +68 -0
- package/dist/commands/handler-types.js +8 -0
- package/dist/commands/handlers/agent-commands.d.ts +13 -0
- package/dist/commands/handlers/agent-commands.js +305 -0
- package/dist/commands/handlers/design-commands.d.ts +15 -0
- package/dist/commands/handlers/design-commands.js +334 -0
- package/dist/commands/handlers/index.d.ts +20 -0
- package/dist/commands/handlers/index.js +43 -0
- package/dist/commands/handlers/overlay-commands.d.ts +21 -0
- package/dist/commands/handlers/overlay-commands.js +287 -0
- package/dist/commands/handlers/project-commands.d.ts +11 -0
- package/dist/commands/handlers/project-commands.js +167 -0
- package/dist/commands/handlers/simple-commands.d.ts +19 -0
- package/dist/commands/handlers/simple-commands.js +144 -0
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/registry.d.ts +50 -0
- package/dist/commands/registry.js +75 -0
- package/dist/commands-v2/handlers/context.d.ts +13 -0
- package/dist/commands-v2/handlers/context.js +348 -0
- package/dist/commands-v2/handlers/core.d.ts +13 -0
- package/dist/commands-v2/handlers/core.js +165 -0
- package/dist/commands-v2/handlers/debug.d.ts +11 -0
- package/dist/commands-v2/handlers/debug.js +159 -0
- package/dist/commands-v2/handlers/index.d.ts +12 -0
- package/dist/commands-v2/handlers/index.js +24 -0
- package/dist/commands-v2/handlers/project.d.ts +22 -0
- package/dist/commands-v2/handlers/project.js +814 -0
- package/dist/commands-v2/handlers/settings.d.ts +15 -0
- package/dist/commands-v2/handlers/settings.js +235 -0
- package/dist/commands-v2/index.d.ts +13 -0
- package/dist/commands-v2/index.js +15 -0
- package/dist/commands-v2/registry.d.ts +37 -0
- package/dist/commands-v2/registry.js +80 -0
- package/dist/commands-v2/types.d.ts +75 -0
- package/dist/commands-v2/types.js +7 -0
- package/dist/commands.js +110 -7
- package/dist/index.js +288 -29
- package/dist/input-handlers/index.d.ts +7 -0
- package/dist/input-handlers/index.js +7 -0
- package/dist/input-handlers/memory-handler.d.ts +26 -0
- package/dist/input-handlers/memory-handler.js +68 -0
- package/dist/repl-helpers.d.ts +63 -0
- package/dist/repl-helpers.js +318 -0
- package/dist/repl-v2.d.ts +155 -0
- package/dist/repl-v2.js +774 -0
- package/dist/repl.d.ts +32 -4
- package/dist/repl.js +250 -977
- package/dist/settings/index.d.ts +23 -0
- package/dist/settings/index.js +48 -0
- package/dist/settings/paths.d.ts +110 -0
- package/dist/settings/paths.js +264 -0
- package/dist/templates/compilr-md.js +7 -4
- package/dist/templates/index.js +3 -4
- package/dist/themes/colors.js +3 -1
- package/dist/themes/registry.d.ts +5 -36
- package/dist/themes/registry.js +11 -95
- package/dist/themes/types.d.ts +3 -38
- package/dist/themes/types.js +2 -2
- package/dist/tools/anchor-tools.d.ts +31 -0
- package/dist/tools/anchor-tools.js +255 -0
- package/dist/tools/backlog-wrappers.d.ts +54 -0
- package/dist/tools/backlog-wrappers.js +338 -0
- package/dist/tools/backlog.js +1 -1
- package/dist/tools/db-tools.d.ts +65 -0
- package/dist/tools/db-tools.js +19 -0
- package/dist/tools/document-db.d.ts +43 -0
- package/dist/tools/document-db.js +220 -0
- package/dist/tools/project-db.d.ts +102 -0
- package/dist/tools/project-db.js +370 -0
- package/dist/tools/workitem-db.d.ts +103 -0
- package/dist/tools/workitem-db.js +549 -0
- package/dist/tools.js +13 -3
- package/dist/ui/agents-overlay-v2.d.ts +43 -0
- package/dist/ui/agents-overlay-v2.js +809 -0
- package/dist/ui/agents-overlay.d.ts +5 -5
- package/dist/ui/agents-overlay.js +782 -420
- package/dist/ui/anchors-overlay.d.ts +12 -0
- package/dist/ui/anchors-overlay.js +775 -0
- package/dist/ui/arch-type-overlay.d.ts +1 -6
- package/dist/ui/arch-type-overlay.js +175 -203
- package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
- package/dist/ui/ask-user-overlay-v2.js +555 -0
- package/dist/ui/ask-user-overlay.d.ts +2 -2
- package/dist/ui/ask-user-overlay.js +443 -535
- package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
- package/dist/ui/ask-user-simple-overlay.js +182 -209
- package/dist/ui/backlog-overlay.d.ts +16 -1
- package/dist/ui/backlog-overlay.js +525 -659
- package/dist/ui/base/index.d.ts +26 -0
- package/dist/ui/base/index.js +33 -0
- package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
- package/dist/ui/base/inline-overlay-utils.js +320 -0
- package/dist/ui/base/inline-overlay.d.ts +159 -0
- package/dist/ui/base/inline-overlay.js +257 -0
- package/dist/ui/base/key-utils.d.ts +15 -0
- package/dist/ui/base/key-utils.js +30 -0
- package/dist/ui/base/overlay-base-v2.d.ts +193 -0
- package/dist/ui/base/overlay-base-v2.js +246 -0
- package/dist/ui/base/overlay-base.d.ts +156 -0
- package/dist/ui/base/overlay-base.js +238 -0
- package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
- package/dist/ui/base/overlay-lifecycle.js +159 -0
- package/dist/ui/base/overlay-types.d.ts +185 -0
- package/dist/ui/base/overlay-types.js +7 -0
- package/dist/ui/base/render-utils.d.ts +8 -0
- package/dist/ui/base/render-utils.js +11 -0
- package/dist/ui/base/screen-stack.d.ts +148 -0
- package/dist/ui/base/screen-stack.js +184 -0
- package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
- package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
- package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
- package/dist/ui/base/tabbed-list-overlay.js +369 -0
- package/dist/ui/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/commands-overlay-v2.js +441 -0
- package/dist/ui/commands-overlay.d.ts +7 -2
- package/dist/ui/commands-overlay.js +384 -355
- package/dist/ui/config-overlay.d.ts +5 -4
- package/dist/ui/config-overlay.js +243 -513
- package/dist/ui/conversation.d.ts +75 -4
- package/dist/ui/conversation.js +374 -161
- package/dist/ui/docs-overlay.d.ts +17 -0
- package/dist/ui/docs-overlay.js +303 -0
- package/dist/ui/ephemeral.d.ts +1 -1
- package/dist/ui/ephemeral.js +1 -1
- package/dist/ui/features/index.d.ts +34 -0
- package/dist/ui/features/index.js +34 -0
- package/dist/ui/features/input-feature.d.ts +85 -0
- package/dist/ui/features/input-feature.js +238 -0
- package/dist/ui/features/list-feature.d.ts +155 -0
- package/dist/ui/features/list-feature.js +244 -0
- package/dist/ui/features/pagination-feature.d.ts +154 -0
- package/dist/ui/features/pagination-feature.js +238 -0
- package/dist/ui/features/search-feature.d.ts +148 -0
- package/dist/ui/features/search-feature.js +185 -0
- package/dist/ui/features/tab-feature.d.ts +194 -0
- package/dist/ui/features/tab-feature.js +307 -0
- package/dist/ui/footer-v2.d.ts +222 -0
- package/dist/ui/footer-v2.js +1349 -0
- package/dist/ui/footer.d.ts +107 -0
- package/dist/ui/footer.js +359 -67
- package/dist/ui/guardrail-overlay.d.ts +29 -0
- package/dist/ui/guardrail-overlay.js +145 -0
- package/dist/ui/help-overlay-v2.d.ts +34 -0
- package/dist/ui/help-overlay-v2.js +309 -0
- package/dist/ui/help-overlay.d.ts +16 -0
- package/dist/ui/help-overlay.js +316 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -3
- package/dist/ui/init-overlay-v2.d.ts +34 -0
- package/dist/ui/init-overlay-v2.js +600 -0
- package/dist/ui/init-overlay.d.ts +12 -2
- package/dist/ui/init-overlay.js +349 -270
- package/dist/ui/input-prompt-v2.d.ts +1 -0
- package/dist/ui/input-prompt-v2.js +14 -6
- package/dist/ui/input-prompt.d.ts +116 -33
- package/dist/ui/input-prompt.js +536 -337
- package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay-v2.js +114 -0
- package/dist/ui/iteration-limit-overlay.d.ts +2 -2
- package/dist/ui/iteration-limit-overlay.js +92 -128
- package/dist/ui/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/keys-overlay-v2.js +248 -0
- package/dist/ui/keys-overlay.d.ts +1 -0
- package/dist/ui/keys-overlay.js +203 -141
- package/dist/ui/line-utils.d.ts +88 -0
- package/dist/ui/line-utils.js +150 -0
- package/dist/ui/live-region.d.ts +161 -0
- package/dist/ui/live-region.js +387 -0
- package/dist/ui/mascot/expressions.d.ts +32 -0
- package/dist/ui/mascot/expressions.js +213 -0
- package/dist/ui/mascot/index.d.ts +8 -0
- package/dist/ui/mascot/index.js +8 -0
- package/dist/ui/mascot/renderer.d.ts +19 -0
- package/dist/ui/mascot/renderer.js +97 -0
- package/dist/ui/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/mascot-overlay-v2.js +138 -0
- package/dist/ui/mascot-overlay.d.ts +21 -0
- package/dist/ui/mascot-overlay.js +146 -0
- package/dist/ui/model-overlay-v2.d.ts +49 -0
- package/dist/ui/model-overlay-v2.js +118 -0
- package/dist/ui/model-overlay.d.ts +27 -0
- package/dist/ui/model-overlay.js +221 -0
- package/dist/ui/model-warning-overlay.js +3 -5
- package/dist/ui/new-overlay.d.ts +34 -0
- package/dist/ui/new-overlay.js +604 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
- package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
- package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
- package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
- package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
- package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
- package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
- package/dist/ui/overlay/index.d.ts +33 -0
- package/dist/ui/overlay/index.js +35 -0
- package/dist/ui/overlay/key-utils.d.ts +6 -0
- package/dist/ui/overlay/key-utils.js +6 -0
- package/dist/ui/overlay/overlay-types.d.ts +128 -0
- package/dist/ui/overlay/overlay-types.js +22 -0
- package/dist/ui/overlay/types.d.ts +135 -0
- package/dist/ui/overlay/types.js +22 -0
- package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
- package/dist/ui/overlays/help-overlay-v2.js +198 -0
- package/dist/ui/overlays/index.d.ts +11 -0
- package/dist/ui/overlays/index.js +11 -0
- package/dist/ui/overlays.d.ts +0 -4
- package/dist/ui/overlays.js +0 -444
- package/dist/ui/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/permission-overlay-v2.js +380 -0
- package/dist/ui/permission-overlay.d.ts +1 -1
- package/dist/ui/permission-overlay.js +186 -298
- package/dist/ui/projects-overlay.d.ts +19 -0
- package/dist/ui/projects-overlay.js +484 -0
- package/dist/ui/providers/types.d.ts +178 -0
- package/dist/ui/providers/types.js +9 -0
- package/dist/ui/render-modes.d.ts +36 -0
- package/dist/ui/render-modes.js +44 -0
- package/dist/ui/startup-menu.d.ts +36 -0
- package/dist/ui/startup-menu.js +236 -0
- package/dist/ui/subagent-renderer.d.ts +117 -0
- package/dist/ui/subagent-renderer.js +334 -0
- package/dist/ui/terminal-codes.d.ts +94 -0
- package/dist/ui/terminal-codes.js +124 -0
- package/dist/ui/terminal-renderer.d.ts +221 -0
- package/dist/ui/terminal-renderer.js +751 -0
- package/dist/ui/terminal-ui.d.ts +463 -0
- package/dist/ui/terminal-ui.js +2296 -0
- package/dist/ui/terminal.d.ts +20 -0
- package/dist/ui/terminal.js +72 -0
- package/dist/ui/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/theme-overlay-v2.js +135 -0
- package/dist/ui/theme-overlay.d.ts +24 -0
- package/dist/ui/theme-overlay.js +127 -0
- package/dist/ui/todo-zone.js +53 -25
- package/dist/ui/tool-formatters.d.ts +16 -0
- package/dist/ui/tool-formatters.js +516 -0
- package/dist/ui/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/tools-overlay-v2.js +218 -0
- package/dist/ui/tools-overlay.d.ts +10 -2
- package/dist/ui/tools-overlay.js +172 -220
- package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/tutorial-overlay.d.ts +1 -0
- package/dist/ui/tutorial-overlay.js +400 -302
- package/dist/ui/workflow-overlay.d.ts +22 -0
- package/dist/ui/workflow-overlay.js +636 -0
- package/dist/utils/debug-log.d.ts +28 -0
- package/dist/utils/debug-log.js +57 -0
- package/dist/utils/model-tiers.js +1 -1
- package/dist/utils/path-safety.d.ts +56 -0
- package/dist/utils/path-safety.js +239 -0
- package/dist/workflow/guided-mode-injector.d.ts +42 -0
- package/dist/workflow/guided-mode-injector.js +191 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.js +8 -0
- package/dist/workflow/step-criteria.d.ts +62 -0
- package/dist/workflow/step-criteria.js +150 -0
- package/dist/workflow/step-tracker.d.ts +92 -0
- package/dist/workflow/step-tracker.js +141 -0
- package/package.json +12 -5
package/dist/repl-v2.js
ADDED
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL V2 - New Terminal UI based REPL
|
|
3
|
+
*
|
|
4
|
+
* Uses TerminalUI with:
|
|
5
|
+
* - Persistent footer (todo list + input prompt)
|
|
6
|
+
* - Overlay system for commands
|
|
7
|
+
* - Real agent integration (or simulation mode for testing)
|
|
8
|
+
*
|
|
9
|
+
* Can be run standalone: npx tsx src/repl-v2.ts
|
|
10
|
+
* Or imported and used with a real agent from index.ts
|
|
11
|
+
*/
|
|
12
|
+
import { TerminalUI } from './ui/terminal-ui.js';
|
|
13
|
+
import * as terminal from './ui/terminal.js';
|
|
14
|
+
import { getStyles } from './themes/index.js';
|
|
15
|
+
import { getSettings, getStartupMode } from './settings/index.js';
|
|
16
|
+
import { renderMascotWithLogo } from './ui/mascot/renderer.js';
|
|
17
|
+
import { registerCommands, executeCommand, allCommands, } from './commands-v2/index.js';
|
|
18
|
+
import { getCustomCommandRegistry } from './commands/custom-registry.js';
|
|
19
|
+
import { PermissionOverlayV2, } from './ui/overlay/impl/permission-overlay-v2.js';
|
|
20
|
+
import { AskUserSimpleOverlayV2, } from './ui/overlay/impl/ask-user-simple-overlay-v2.js';
|
|
21
|
+
import { GuardrailOverlayV2, } from './ui/overlay/impl/guardrail-overlay-v2.js';
|
|
22
|
+
import { AskUserOverlayV2, } from './ui/overlay/impl/ask-user-overlay-v2.js';
|
|
23
|
+
import { IterationLimitOverlayV2, } from './ui/overlay/impl/iteration-limit-overlay-v2.js';
|
|
24
|
+
import { formatToolResult } from './ui/tool-formatters.js';
|
|
25
|
+
import { isMemoryInput } from './input-handlers/index.js';
|
|
26
|
+
import { addAnchor } from './anchors/index.js';
|
|
27
|
+
import { getActiveProject } from './tools/project-db.js';
|
|
28
|
+
// Version for display (when running standalone)
|
|
29
|
+
const STANDALONE_VERSION = '0.4.0-v2';
|
|
30
|
+
// Register all commands on module load
|
|
31
|
+
registerCommands(allCommands);
|
|
32
|
+
// =============================================================================
|
|
33
|
+
// REPL V2 Class
|
|
34
|
+
// =============================================================================
|
|
35
|
+
export class ReplV2 {
|
|
36
|
+
agent;
|
|
37
|
+
model;
|
|
38
|
+
provider;
|
|
39
|
+
version;
|
|
40
|
+
projectName;
|
|
41
|
+
currentSession = null;
|
|
42
|
+
ui;
|
|
43
|
+
onUIReady;
|
|
44
|
+
onTextBufferReady;
|
|
45
|
+
onAskUserSimpleReady;
|
|
46
|
+
onGuardrailReady;
|
|
47
|
+
onAskUserReady;
|
|
48
|
+
onIterationLimitReady;
|
|
49
|
+
onAgentFinish;
|
|
50
|
+
onSuggestionReady;
|
|
51
|
+
onSubagentReady;
|
|
52
|
+
clearSubagentTracking;
|
|
53
|
+
// Session stats
|
|
54
|
+
startTime = new Date();
|
|
55
|
+
sessionInputTokens = 0;
|
|
56
|
+
sessionOutputTokens = 0;
|
|
57
|
+
sessionRequests = 0;
|
|
58
|
+
// Text accumulator for agent output (class-level so it can be flushed externally)
|
|
59
|
+
textAccumulator = '';
|
|
60
|
+
// Note: Subagent tracking is now simplified - callbacks receive toolUseId directly
|
|
61
|
+
// from agent.ts, so no more FIFO queue correlation needed!
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
this.agent = options.agent;
|
|
64
|
+
this.model = options.model ?? 'simulation';
|
|
65
|
+
this.provider = options.provider ?? 'simulation';
|
|
66
|
+
this.version = options.version ?? STANDALONE_VERSION;
|
|
67
|
+
this.projectName = options.projectName ?? process.cwd().split('/').pop() ?? 'Project';
|
|
68
|
+
this.onUIReady = options.onUIReady;
|
|
69
|
+
this.onTextBufferReady = options.onTextBufferReady;
|
|
70
|
+
this.onAskUserSimpleReady = options.onAskUserSimpleReady;
|
|
71
|
+
this.onGuardrailReady = options.onGuardrailReady;
|
|
72
|
+
this.onAskUserReady = options.onAskUserReady;
|
|
73
|
+
this.onIterationLimitReady = options.onIterationLimitReady;
|
|
74
|
+
this.onAgentFinish = options.onAgentFinish;
|
|
75
|
+
this.onSuggestionReady = options.onSuggestionReady;
|
|
76
|
+
this.onSubagentReady = options.onSubagentReady;
|
|
77
|
+
this.clearSubagentTracking = options.clearSubagentTracking;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Flush accumulated agent text to the UI.
|
|
81
|
+
* Called externally (e.g., before showing permission overlay) to ensure
|
|
82
|
+
* agent's "I'll do X" message appears before the overlay.
|
|
83
|
+
*/
|
|
84
|
+
flushTextBuffer() {
|
|
85
|
+
if (this.textAccumulator.trim()) {
|
|
86
|
+
this.ui.print({ type: 'agent-text', text: this.textAccumulator.trim() });
|
|
87
|
+
this.textAccumulator = '';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Start the REPL.
|
|
92
|
+
*/
|
|
93
|
+
start() {
|
|
94
|
+
// Create TerminalUI
|
|
95
|
+
this.ui = new TerminalUI({
|
|
96
|
+
initialMode: 'normal',
|
|
97
|
+
});
|
|
98
|
+
// Print welcome screen
|
|
99
|
+
this.printWelcome();
|
|
100
|
+
// Set initial state
|
|
101
|
+
this.ui.setProjectName(this.projectName);
|
|
102
|
+
// Notify caller that UI is ready and provide the V2 permission overlay function
|
|
103
|
+
if (this.onUIReady) {
|
|
104
|
+
const ui = this.ui;
|
|
105
|
+
this.onUIReady(async (options) => {
|
|
106
|
+
const overlay = new PermissionOverlayV2(options);
|
|
107
|
+
return ui.showOverlay(overlay);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Provide the text buffer flush function to the caller
|
|
111
|
+
if (this.onTextBufferReady) {
|
|
112
|
+
this.onTextBufferReady(() => {
|
|
113
|
+
this.flushTextBuffer();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Provide the ask_user_simple overlay function to the caller
|
|
117
|
+
if (this.onAskUserSimpleReady) {
|
|
118
|
+
const ui = this.ui;
|
|
119
|
+
this.onAskUserSimpleReady(async (options) => {
|
|
120
|
+
const overlay = new AskUserSimpleOverlayV2(options);
|
|
121
|
+
const result = await ui.showOverlay(overlay);
|
|
122
|
+
return result ?? { answer: '', skipped: true };
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Provide the guardrail overlay function to the caller
|
|
126
|
+
if (this.onGuardrailReady) {
|
|
127
|
+
const ui = this.ui;
|
|
128
|
+
this.onGuardrailReady(async (options) => {
|
|
129
|
+
const overlay = new GuardrailOverlayV2(options);
|
|
130
|
+
const result = await ui.showOverlay(overlay);
|
|
131
|
+
return result ?? { approved: false };
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Provide the ask_user overlay function to the caller
|
|
135
|
+
if (this.onAskUserReady) {
|
|
136
|
+
const ui = this.ui;
|
|
137
|
+
this.onAskUserReady(async (options) => {
|
|
138
|
+
const overlay = new AskUserOverlayV2(options);
|
|
139
|
+
const result = await ui.showOverlay(overlay);
|
|
140
|
+
return result ?? { answers: {}, skipped: options.questions.map((q) => q.id) };
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Provide the iteration_limit overlay function to the caller
|
|
144
|
+
if (this.onIterationLimitReady) {
|
|
145
|
+
const ui = this.ui;
|
|
146
|
+
this.onIterationLimitReady(async (options) => {
|
|
147
|
+
const overlay = new IterationLimitOverlayV2(options);
|
|
148
|
+
const result = await ui.showOverlay(overlay);
|
|
149
|
+
return result ?? { continue: false };
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Provide the suggestion setter function to the caller
|
|
153
|
+
if (this.onSuggestionReady) {
|
|
154
|
+
const ui = this.ui;
|
|
155
|
+
this.onSuggestionReady((action) => {
|
|
156
|
+
ui.setSuggestion(action);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Provide subagent tracking callbacks to the caller for LiveRegion rendering
|
|
160
|
+
// Now using toolUseId directly - no more FIFO queue correlation needed!
|
|
161
|
+
if (this.onSubagentReady) {
|
|
162
|
+
const ui = this.ui;
|
|
163
|
+
this.onSubagentReady({
|
|
164
|
+
onStart: (toolUseId, _agentType, _description) => {
|
|
165
|
+
// The subagent entry was already added in tool_start with toolUseId
|
|
166
|
+
// Just mark it as "started" (no-op for now, already rendering)
|
|
167
|
+
void toolUseId; // Used for correlation, entry already exists
|
|
168
|
+
},
|
|
169
|
+
onToolUse: (toolUseId, toolName, summary) => {
|
|
170
|
+
// Direct update using toolUseId - no mapping needed!
|
|
171
|
+
ui.updateSubagentTool(toolUseId, toolName, summary ?? '');
|
|
172
|
+
},
|
|
173
|
+
onEnd: (toolUseId, _success, tokenCount, _error) => {
|
|
174
|
+
// DON'T mark as complete here! That changes the line count in LiveRegion,
|
|
175
|
+
// and if render loop fires before tool_end, lastRenderHeight becomes wrong.
|
|
176
|
+
// Just store the info for tool_end to use when committing.
|
|
177
|
+
const item = ui.getLiveRegion().getItem(toolUseId);
|
|
178
|
+
if (item && item.type === 'subagent') {
|
|
179
|
+
ui.getLiveRegion().updateItem(toolUseId, {
|
|
180
|
+
tokenCount,
|
|
181
|
+
endTime: Date.now(),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// Event handlers
|
|
188
|
+
this.ui.on('submit', (input) => {
|
|
189
|
+
void this.processInput(input);
|
|
190
|
+
});
|
|
191
|
+
this.ui.on('command', (cmd, args) => {
|
|
192
|
+
void (async () => {
|
|
193
|
+
await this.handleCommand(cmd, args);
|
|
194
|
+
await this.processQueue();
|
|
195
|
+
})();
|
|
196
|
+
});
|
|
197
|
+
this.ui.on('escape', () => {
|
|
198
|
+
// Single Esc when no agent - do nothing
|
|
199
|
+
});
|
|
200
|
+
this.ui.on('interrupt', () => {
|
|
201
|
+
if (this.currentSession) {
|
|
202
|
+
this.cancelSession();
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.ui.stop();
|
|
206
|
+
console.log('\nGoodbye!\n');
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
this.ui.on('cancel', () => {
|
|
211
|
+
if (this.currentSession) {
|
|
212
|
+
this.cancelSession();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
this.ui.on('modeChange', () => {
|
|
216
|
+
const modes = ['normal', 'auto-accept', 'plan'];
|
|
217
|
+
const currentIndex = modes.indexOf(this.ui.getMode());
|
|
218
|
+
const nextMode = modes[(currentIndex + 1) % modes.length];
|
|
219
|
+
this.ui.setMode(nextMode);
|
|
220
|
+
this.ui.print({ type: 'info', message: `Mode: ${nextMode}` });
|
|
221
|
+
});
|
|
222
|
+
// Start UI
|
|
223
|
+
this.ui.start();
|
|
224
|
+
// If startup mode is 'menu', immediately execute /menu command
|
|
225
|
+
const startupMode = getStartupMode();
|
|
226
|
+
if (startupMode === 'menu') {
|
|
227
|
+
void this.handleCommand('menu', '');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Stop the REPL.
|
|
232
|
+
*/
|
|
233
|
+
stop() {
|
|
234
|
+
if (this.currentSession) {
|
|
235
|
+
this.currentSession.abortController.abort();
|
|
236
|
+
this.currentSession = null;
|
|
237
|
+
}
|
|
238
|
+
this.ui.stop();
|
|
239
|
+
}
|
|
240
|
+
// ===========================================================================
|
|
241
|
+
// Welcome Message
|
|
242
|
+
// ===========================================================================
|
|
243
|
+
/**
|
|
244
|
+
* Print just the logo to the scrolling area.
|
|
245
|
+
* Used for dashboard mode where hints are not needed.
|
|
246
|
+
*/
|
|
247
|
+
printLogo() {
|
|
248
|
+
const settings = getSettings();
|
|
249
|
+
const s = getStyles();
|
|
250
|
+
terminal.clearScreen();
|
|
251
|
+
// Render mascot with logo (includes tagline)
|
|
252
|
+
const logoLines = renderMascotWithLogo(settings.mascot, this.version, null);
|
|
253
|
+
console.log('');
|
|
254
|
+
for (const line of logoLines) {
|
|
255
|
+
console.log(line);
|
|
256
|
+
}
|
|
257
|
+
console.log('');
|
|
258
|
+
// Show agent status
|
|
259
|
+
if (this.agent) {
|
|
260
|
+
console.log(s.success(' ✓ Agent connected'));
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.log(s.warning(' ⚠ Simulation mode (no agent)'));
|
|
264
|
+
}
|
|
265
|
+
console.log('');
|
|
266
|
+
// Show model info
|
|
267
|
+
console.log(s.muted('Model: ') + s.secondary(this.model));
|
|
268
|
+
console.log(s.muted('Provider: ') + s.secondary(this.provider));
|
|
269
|
+
console.log('');
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Print full welcome message for REPL mode.
|
|
273
|
+
*/
|
|
274
|
+
printWelcome() {
|
|
275
|
+
const s = getStyles();
|
|
276
|
+
// Print logo with agent/model info
|
|
277
|
+
this.printLogo();
|
|
278
|
+
// Hints
|
|
279
|
+
console.log(s.muted('Type a message to start'));
|
|
280
|
+
console.log(s.muted('Type /help for commands, /exit to quit'));
|
|
281
|
+
console.log('');
|
|
282
|
+
}
|
|
283
|
+
// ===========================================================================
|
|
284
|
+
// Command Handling
|
|
285
|
+
// ===========================================================================
|
|
286
|
+
createCommandContext() {
|
|
287
|
+
return {
|
|
288
|
+
ui: this.ui,
|
|
289
|
+
version: this.version,
|
|
290
|
+
printWelcome: () => { this.printWelcome(); },
|
|
291
|
+
printLogo: () => { this.printLogo(); },
|
|
292
|
+
// Agent integration
|
|
293
|
+
agent: this.agent,
|
|
294
|
+
model: this.model,
|
|
295
|
+
provider: this.provider,
|
|
296
|
+
getContextManager: () => {
|
|
297
|
+
return this.agent?.getContextManager() ?? null;
|
|
298
|
+
},
|
|
299
|
+
getHistory: () => {
|
|
300
|
+
return this.agent?.getHistory() ?? [];
|
|
301
|
+
},
|
|
302
|
+
// Queue agent message (for commands that invoke the agent)
|
|
303
|
+
queueAgentMessage: (options) => {
|
|
304
|
+
this.ui.queueAgentMessage(options);
|
|
305
|
+
},
|
|
306
|
+
// Session stats
|
|
307
|
+
startTime: this.startTime,
|
|
308
|
+
sessionInputTokens: this.sessionInputTokens,
|
|
309
|
+
sessionOutputTokens: this.sessionOutputTokens,
|
|
310
|
+
sessionRequests: this.sessionRequests,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
async handleCommand(cmd, args) {
|
|
314
|
+
const ctx = this.createCommandContext();
|
|
315
|
+
const result = await executeCommand(cmd, args, ctx);
|
|
316
|
+
if (result === null) {
|
|
317
|
+
// Check if it's a custom command
|
|
318
|
+
const customRegistry = getCustomCommandRegistry();
|
|
319
|
+
if (customRegistry.has(cmd)) {
|
|
320
|
+
// Expand custom command and send as message
|
|
321
|
+
const customArgs = args ? args.split(' ').filter(a => a.trim()) : [];
|
|
322
|
+
const expanded = customRegistry.expand(cmd, customArgs);
|
|
323
|
+
if (expanded) {
|
|
324
|
+
// Show user what command is being run
|
|
325
|
+
this.ui.print({ type: 'user-message', text: `/${cmd}${args ? ' ' + args : ''}` });
|
|
326
|
+
// Send expanded prompt to agent
|
|
327
|
+
if (this.agent) {
|
|
328
|
+
await this.runAgentReal(expanded);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
await this.runAgentSimulation(expanded);
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
this.ui.print({ type: 'warning', message: `Unknown command: /${cmd}` });
|
|
337
|
+
this.ui.print({ type: 'info', message: 'Type /help to see available commands' });
|
|
338
|
+
}
|
|
339
|
+
else if (!result) {
|
|
340
|
+
// result === false means exit
|
|
341
|
+
process.exit(0);
|
|
342
|
+
}
|
|
343
|
+
// Process any queued agent messages (from commands like /arch, /design)
|
|
344
|
+
await this.processAgentMessageQueue();
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Process queued agent messages.
|
|
348
|
+
* Commands can queue messages to invoke the agent programmatically.
|
|
349
|
+
*/
|
|
350
|
+
async processAgentMessageQueue() {
|
|
351
|
+
while (this.ui.hasAgentMessage()) {
|
|
352
|
+
const agentMsg = this.ui.popAgentMessage();
|
|
353
|
+
if (agentMsg) {
|
|
354
|
+
// Show display message to user (or full message if no display message)
|
|
355
|
+
this.ui.print({
|
|
356
|
+
type: 'user-message',
|
|
357
|
+
text: agentMsg.displayMessage ?? agentMsg.message,
|
|
358
|
+
});
|
|
359
|
+
// Send full message to agent
|
|
360
|
+
if (this.agent) {
|
|
361
|
+
await this.runAgentReal(agentMsg.message);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
await this.runAgentSimulation(agentMsg.message);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// ===========================================================================
|
|
370
|
+
// Input Processing
|
|
371
|
+
// ===========================================================================
|
|
372
|
+
async processInput(input) {
|
|
373
|
+
if (input.startsWith('/')) {
|
|
374
|
+
const spaceIndex = input.indexOf(' ');
|
|
375
|
+
const cmd = spaceIndex > 0 ? input.slice(1, spaceIndex) : input.slice(1);
|
|
376
|
+
const args = spaceIndex > 0 ? input.slice(spaceIndex + 1) : '';
|
|
377
|
+
await this.handleCommand(cmd, args);
|
|
378
|
+
await this.processQueue();
|
|
379
|
+
}
|
|
380
|
+
else if (isMemoryInput(input)) {
|
|
381
|
+
// Handle memory note ("# note")
|
|
382
|
+
this.handleMemoryInputV2(input);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// Print user message
|
|
386
|
+
this.ui.print({ type: 'user-message', text: input });
|
|
387
|
+
// Run agent (real or simulation)
|
|
388
|
+
if (this.agent) {
|
|
389
|
+
await this.runAgentReal(input);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
await this.runAgentSimulation(input);
|
|
393
|
+
}
|
|
394
|
+
await this.processQueue();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Handle memory note input ("# note") - V2 compatible version
|
|
399
|
+
*/
|
|
400
|
+
handleMemoryInputV2(input) {
|
|
401
|
+
const note = input.slice(1).trim();
|
|
402
|
+
// Get active project to determine scope
|
|
403
|
+
const activeProject = getActiveProject();
|
|
404
|
+
const projectId = activeProject?.id;
|
|
405
|
+
// Determine priority based on keywords
|
|
406
|
+
let priority = 'info';
|
|
407
|
+
const lowerNote = note.toLowerCase();
|
|
408
|
+
if (lowerNote.includes('never') || lowerNote.includes('always') || lowerNote.includes('must')) {
|
|
409
|
+
priority = 'safety';
|
|
410
|
+
}
|
|
411
|
+
if (lowerNote.includes('critical') || lowerNote.includes('important')) {
|
|
412
|
+
priority = 'critical';
|
|
413
|
+
}
|
|
414
|
+
// Create the anchor (persisted to file)
|
|
415
|
+
const anchor = addAnchor({
|
|
416
|
+
content: note,
|
|
417
|
+
priority,
|
|
418
|
+
scope: 'persistent',
|
|
419
|
+
projectId: projectId?.toString(),
|
|
420
|
+
tags: ['user-note'],
|
|
421
|
+
});
|
|
422
|
+
// Show confirmation
|
|
423
|
+
const scope = activeProject ? `project "${activeProject.displayName || activeProject.name}"` : 'global';
|
|
424
|
+
const priorityLabel = priority === 'info' ? '' : ` (${priority})`;
|
|
425
|
+
const truncatedNote = note.length > 50 ? note.slice(0, 47) + '...' : note;
|
|
426
|
+
this.ui.print({ type: 'success', message: `Saved${priorityLabel}: "${truncatedNote}" → ${scope}` });
|
|
427
|
+
// Also add to agent's anchor manager if enabled (for current session)
|
|
428
|
+
if (this.agent?.getAnchorManager()) {
|
|
429
|
+
this.agent.addAnchor({
|
|
430
|
+
id: anchor.id,
|
|
431
|
+
content: note,
|
|
432
|
+
priority,
|
|
433
|
+
scope: 'persistent',
|
|
434
|
+
tags: ['user-note'],
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async processQueue() {
|
|
439
|
+
while (this.ui.hasQueuedInput()) {
|
|
440
|
+
const queued = this.ui.popQueuedInput();
|
|
441
|
+
if (queued) {
|
|
442
|
+
await this.processInput(queued);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// ===========================================================================
|
|
447
|
+
// Real Agent Execution
|
|
448
|
+
// ===========================================================================
|
|
449
|
+
async runAgentReal(userMessage) {
|
|
450
|
+
if (!this.agent)
|
|
451
|
+
return;
|
|
452
|
+
const abortController = new AbortController();
|
|
453
|
+
const signal = abortController.signal;
|
|
454
|
+
const setAction = (action) => {
|
|
455
|
+
if (this.currentSession) {
|
|
456
|
+
this.currentSession.currentAction = action;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
// Clear subagent tracking state from previous runs
|
|
460
|
+
// - Agent.ts tracking (activeSubagents map)
|
|
461
|
+
this.clearSubagentTracking?.();
|
|
462
|
+
// - LiveRegion entries (visual display of running tools)
|
|
463
|
+
this.ui.clearLiveRegion();
|
|
464
|
+
this.currentSession = {
|
|
465
|
+
abortController,
|
|
466
|
+
currentAction: null,
|
|
467
|
+
promise: (async () => {
|
|
468
|
+
try {
|
|
469
|
+
this.ui.setAgentRunning(true);
|
|
470
|
+
setAction('Thinking...');
|
|
471
|
+
this.ui.setSpinnerText('Thinking...');
|
|
472
|
+
// Reset text accumulator for this run (class-level so it can be flushed externally)
|
|
473
|
+
this.textAccumulator = '';
|
|
474
|
+
let lastToolInput = null;
|
|
475
|
+
// Stream agent events (agent is guaranteed to exist in this method)
|
|
476
|
+
const agent = this.agent;
|
|
477
|
+
if (!agent)
|
|
478
|
+
return; // Type guard
|
|
479
|
+
for await (const event of agent.stream(userMessage, {
|
|
480
|
+
signal,
|
|
481
|
+
chatOptions: { model: this.model },
|
|
482
|
+
})) {
|
|
483
|
+
if (signal.aborted)
|
|
484
|
+
break;
|
|
485
|
+
if (event.type === 'llm_chunk') {
|
|
486
|
+
if (event.chunk.type === 'text' && event.chunk.text) {
|
|
487
|
+
this.textAccumulator += event.chunk.text;
|
|
488
|
+
}
|
|
489
|
+
else if (event.chunk.type === 'done' && event.chunk.usage) {
|
|
490
|
+
this.sessionInputTokens += event.chunk.usage.inputTokens;
|
|
491
|
+
this.sessionOutputTokens += event.chunk.usage.outputTokens;
|
|
492
|
+
this.sessionRequests++;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else if (event.type === 'tool_start') {
|
|
496
|
+
// Flush accumulated text before tool
|
|
497
|
+
if (this.textAccumulator.trim()) {
|
|
498
|
+
this.ui.print({ type: 'agent-text', text: this.textAccumulator.trim() });
|
|
499
|
+
this.textAccumulator = '';
|
|
500
|
+
}
|
|
501
|
+
lastToolInput = event.input;
|
|
502
|
+
// For bash tool, add to live region for streaming output
|
|
503
|
+
if (event.name === 'bash' && event.toolUseId) {
|
|
504
|
+
const command = typeof lastToolInput.command === 'string'
|
|
505
|
+
? lastToolInput.command
|
|
506
|
+
: '';
|
|
507
|
+
this.ui.addBashCommand(event.toolUseId, command);
|
|
508
|
+
}
|
|
509
|
+
// Update spinner with tool name
|
|
510
|
+
if (event.name !== 'todo_read' && event.name !== 'todo_write' && event.name !== 'suggest') {
|
|
511
|
+
if (event.name === 'task' && event.toolUseId) {
|
|
512
|
+
const subagentType = typeof lastToolInput.subagent_type === 'string'
|
|
513
|
+
? lastToolInput.subagent_type
|
|
514
|
+
: 'general';
|
|
515
|
+
const description = typeof lastToolInput.description === 'string'
|
|
516
|
+
? lastToolInput.description
|
|
517
|
+
: '';
|
|
518
|
+
setAction(`task(${subagentType}): ${description}`);
|
|
519
|
+
this.ui.setSpinnerText(`task(${subagentType}): ${description}`);
|
|
520
|
+
// Add subagent to LiveRegion with toolUseId
|
|
521
|
+
// No queue needed - callbacks now receive toolUseId directly!
|
|
522
|
+
this.ui.addSubagent(event.toolUseId, subagentType, description);
|
|
523
|
+
}
|
|
524
|
+
else if (event.name !== 'task') {
|
|
525
|
+
setAction(event.name);
|
|
526
|
+
this.ui.setSpinnerText(`Running ${event.name}...`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
else if (event.type === 'tool_output') {
|
|
531
|
+
// Stream bash output to live region
|
|
532
|
+
if (event.toolUseId) {
|
|
533
|
+
this.ui.updateBashOutput(event.toolUseId, event.output);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
else if (event.type === 'tool_end') {
|
|
537
|
+
const toolName = event.name;
|
|
538
|
+
const toolInput = lastToolInput;
|
|
539
|
+
const result = event.result;
|
|
540
|
+
lastToolInput = null;
|
|
541
|
+
// Complete bash command in live region
|
|
542
|
+
if (toolName === 'bash' && event.toolUseId) {
|
|
543
|
+
let exitCode = result.success ? 0 : 1;
|
|
544
|
+
if (result.success && typeof result.result === 'object' && result.result !== null) {
|
|
545
|
+
const res = result.result;
|
|
546
|
+
if (typeof res.exitCode === 'number') {
|
|
547
|
+
exitCode = res.exitCode;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
this.ui.completeBashCommand(event.toolUseId, exitCode);
|
|
551
|
+
this.ui.commitBashCommand(event.toolUseId);
|
|
552
|
+
this.ui.setCurrentTool(null);
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
// Handle todo_write - update UI state
|
|
556
|
+
if (toolName === 'todo_write' && toolInput && Array.isArray(toolInput.todos)) {
|
|
557
|
+
const todos = toolInput.todos.map((t) => {
|
|
558
|
+
const content = typeof t.content === 'string' ? t.content :
|
|
559
|
+
typeof t.title === 'string' ? t.title :
|
|
560
|
+
typeof t.task === 'string' ? t.task :
|
|
561
|
+
typeof t.description === 'string' ? t.description :
|
|
562
|
+
typeof t.text === 'string' ? t.text : 'Untitled task';
|
|
563
|
+
const status = (typeof t.status === 'string' && ['pending', 'in_progress', 'completed'].includes(t.status)
|
|
564
|
+
? t.status
|
|
565
|
+
: 'pending');
|
|
566
|
+
const activeForm = typeof t.activeForm === 'string' ? t.activeForm : undefined;
|
|
567
|
+
return { content, status, activeForm };
|
|
568
|
+
});
|
|
569
|
+
this.ui.setTodos(todos);
|
|
570
|
+
this.ui.setCurrentTool(null);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
// Skip silent tools (no output needed)
|
|
574
|
+
if (toolName === 'todo_read' || toolName === 'suggest' ||
|
|
575
|
+
toolName === 'ask_user' || toolName === 'ask_user_simple') {
|
|
576
|
+
this.ui.setCurrentTool(null);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
// Handle task tool like bash - use atomic commitSubagent
|
|
580
|
+
if (toolName === 'task' && event.toolUseId) {
|
|
581
|
+
// Extract result content
|
|
582
|
+
const { content, success } = this.extractToolResult(toolName, result);
|
|
583
|
+
// Use atomic commitSubagent (same pattern as commitBashCommand)
|
|
584
|
+
// This removes from LiveRegion and prints in one atomic operation
|
|
585
|
+
this.ui.commitSubagent(event.toolUseId, content, success);
|
|
586
|
+
this.ui.setCurrentTool(null);
|
|
587
|
+
continue; // Skip generic handling
|
|
588
|
+
}
|
|
589
|
+
// Extract and format tool result
|
|
590
|
+
const { content, summary, success } = this.extractToolResult(toolName, result);
|
|
591
|
+
this.ui.print({
|
|
592
|
+
type: 'tool-result',
|
|
593
|
+
name: toolName,
|
|
594
|
+
params: this.formatToolParams(toolInput),
|
|
595
|
+
summary,
|
|
596
|
+
content: content.length > 200 ? content : undefined,
|
|
597
|
+
success,
|
|
598
|
+
});
|
|
599
|
+
this.ui.setCurrentTool(null);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// Flush remaining text
|
|
603
|
+
if (this.textAccumulator.trim()) {
|
|
604
|
+
this.ui.print({ type: 'agent-text', text: this.textAccumulator.trim(), expression: 'success' });
|
|
605
|
+
}
|
|
606
|
+
this.ui.setAgentRunning(false);
|
|
607
|
+
// NOTE: Don't clear todos here - they should persist after agent finishes
|
|
608
|
+
// to show the final state. Only clear on explicit user action or new conversation.
|
|
609
|
+
// Notify that agent has finished (for applying deferred suggestions)
|
|
610
|
+
this.onAgentFinish?.();
|
|
611
|
+
}
|
|
612
|
+
catch (err) {
|
|
613
|
+
if (!signal.aborted) {
|
|
614
|
+
this.ui.print({ type: 'error', message: String(err) });
|
|
615
|
+
}
|
|
616
|
+
this.ui.setAgentRunning(false);
|
|
617
|
+
// Still notify on error so suggestions can be applied
|
|
618
|
+
this.onAgentFinish?.();
|
|
619
|
+
}
|
|
620
|
+
})(),
|
|
621
|
+
};
|
|
622
|
+
await this.currentSession.promise;
|
|
623
|
+
this.currentSession = null;
|
|
624
|
+
}
|
|
625
|
+
formatToolParams(input) {
|
|
626
|
+
if (!input)
|
|
627
|
+
return '';
|
|
628
|
+
// Format common params
|
|
629
|
+
if ('path' in input)
|
|
630
|
+
return String(input.path);
|
|
631
|
+
if ('file_path' in input)
|
|
632
|
+
return String(input.file_path);
|
|
633
|
+
if ('pattern' in input)
|
|
634
|
+
return String(input.pattern);
|
|
635
|
+
if ('command' in input)
|
|
636
|
+
return String(input.command).slice(0, 50);
|
|
637
|
+
// Default: show first param
|
|
638
|
+
const keys = Object.keys(input);
|
|
639
|
+
if (keys.length > 0) {
|
|
640
|
+
const val = String(input[keys[0]]);
|
|
641
|
+
return val.length > 50 ? val.slice(0, 47) + '...' : val;
|
|
642
|
+
}
|
|
643
|
+
return '';
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Extract content and summary from tool result.
|
|
647
|
+
* Delegates to tool-formatters module.
|
|
648
|
+
*/
|
|
649
|
+
extractToolResult(toolName, result) {
|
|
650
|
+
return formatToolResult(toolName, result);
|
|
651
|
+
}
|
|
652
|
+
// ===========================================================================
|
|
653
|
+
// Agent Simulation (for standalone testing)
|
|
654
|
+
// ===========================================================================
|
|
655
|
+
async runAgentSimulation(userMessage) {
|
|
656
|
+
const abortController = new AbortController();
|
|
657
|
+
const signal = abortController.signal;
|
|
658
|
+
const setAction = (action) => {
|
|
659
|
+
if (this.currentSession) {
|
|
660
|
+
this.currentSession.currentAction = action;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
this.currentSession = {
|
|
664
|
+
abortController,
|
|
665
|
+
currentAction: null,
|
|
666
|
+
promise: (async () => {
|
|
667
|
+
try {
|
|
668
|
+
this.ui.setAgentRunning(true);
|
|
669
|
+
setAction('Thinking...');
|
|
670
|
+
this.ui.setSpinnerText('Thinking...');
|
|
671
|
+
// 1. Initial response
|
|
672
|
+
await this.sleep(1000, signal);
|
|
673
|
+
if (signal.aborted)
|
|
674
|
+
return;
|
|
675
|
+
const response = `I understand you want me to help with "${userMessage}". Let me analyze this.`;
|
|
676
|
+
this.ui.print({ type: 'agent-text', text: response });
|
|
677
|
+
// 2. Add todos
|
|
678
|
+
this.ui.setTodos([
|
|
679
|
+
{ content: 'Analyze the request', status: 'completed' },
|
|
680
|
+
{ content: 'Search for relevant files', status: 'in_progress' },
|
|
681
|
+
{ content: 'Make changes', status: 'pending' },
|
|
682
|
+
]);
|
|
683
|
+
// 3. Simulate tool
|
|
684
|
+
await this.sleep(800, signal);
|
|
685
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- signal can be aborted externally
|
|
686
|
+
if (signal.aborted)
|
|
687
|
+
return;
|
|
688
|
+
setAction('Glob(**/*.ts)');
|
|
689
|
+
this.ui.setSpinnerText('Running Glob...');
|
|
690
|
+
await this.sleep(600, signal);
|
|
691
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- signal can be aborted externally
|
|
692
|
+
if (signal.aborted)
|
|
693
|
+
return;
|
|
694
|
+
this.ui.print({
|
|
695
|
+
type: 'tool-result',
|
|
696
|
+
name: 'Glob',
|
|
697
|
+
params: '**/*.ts',
|
|
698
|
+
summary: 'Found 42 files',
|
|
699
|
+
content: 'src/index.ts\nsrc/config.ts\nsrc/utils/helpers.ts\n... and 39 more',
|
|
700
|
+
});
|
|
701
|
+
this.ui.setTodos([
|
|
702
|
+
{ content: 'Analyze the request', status: 'completed' },
|
|
703
|
+
{ content: 'Search for relevant files', status: 'completed' },
|
|
704
|
+
{ content: 'Make changes', status: 'in_progress' },
|
|
705
|
+
]);
|
|
706
|
+
// 4. Conclusion
|
|
707
|
+
await this.sleep(500, signal);
|
|
708
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- signal can be aborted externally
|
|
709
|
+
if (signal.aborted)
|
|
710
|
+
return;
|
|
711
|
+
this.ui.setTodos([
|
|
712
|
+
{ content: 'Analyze the request', status: 'completed' },
|
|
713
|
+
{ content: 'Search for relevant files', status: 'completed' },
|
|
714
|
+
{ content: 'Make changes', status: 'completed' },
|
|
715
|
+
]);
|
|
716
|
+
this.ui.print({
|
|
717
|
+
type: 'agent-text',
|
|
718
|
+
text: 'Done! I have completed the analysis.',
|
|
719
|
+
expression: 'success',
|
|
720
|
+
});
|
|
721
|
+
this.ui.setAgentRunning(false);
|
|
722
|
+
// NOTE: Don't clear todos - let them persist to show final state
|
|
723
|
+
// Notify that agent has finished (for applying deferred suggestions)
|
|
724
|
+
this.onAgentFinish?.();
|
|
725
|
+
}
|
|
726
|
+
catch (err) {
|
|
727
|
+
if (!signal.aborted) {
|
|
728
|
+
this.ui.print({ type: 'error', message: String(err) });
|
|
729
|
+
}
|
|
730
|
+
this.ui.setAgentRunning(false);
|
|
731
|
+
this.onAgentFinish?.();
|
|
732
|
+
}
|
|
733
|
+
})(),
|
|
734
|
+
};
|
|
735
|
+
await this.currentSession.promise;
|
|
736
|
+
this.currentSession = null;
|
|
737
|
+
}
|
|
738
|
+
async sleep(ms, signal) {
|
|
739
|
+
return new Promise((resolve) => {
|
|
740
|
+
const timeout = setTimeout(resolve, ms);
|
|
741
|
+
signal?.addEventListener('abort', () => {
|
|
742
|
+
clearTimeout(timeout);
|
|
743
|
+
resolve();
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
// ===========================================================================
|
|
748
|
+
// Session Management
|
|
749
|
+
// ===========================================================================
|
|
750
|
+
cancelSession() {
|
|
751
|
+
const action = this.currentSession?.currentAction ?? undefined;
|
|
752
|
+
if (this.currentSession) {
|
|
753
|
+
this.currentSession.abortController.abort();
|
|
754
|
+
this.currentSession = null;
|
|
755
|
+
}
|
|
756
|
+
this.ui.setAgentRunning(false);
|
|
757
|
+
this.ui.setTodos([]);
|
|
758
|
+
this.ui.clearQueue();
|
|
759
|
+
this.ui.print({ type: 'interrupted', action, suggestion: 'what should I do instead?' });
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// =============================================================================
|
|
763
|
+
// Standalone Entry Point
|
|
764
|
+
// =============================================================================
|
|
765
|
+
// Only run main() when executed directly (not when imported)
|
|
766
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
767
|
+
const repl = new ReplV2();
|
|
768
|
+
repl.start();
|
|
769
|
+
// Handle SIGINT
|
|
770
|
+
process.on('SIGINT', () => {
|
|
771
|
+
console.log('\n\nInterrupted\n');
|
|
772
|
+
process.exit(0);
|
|
773
|
+
});
|
|
774
|
+
}
|