@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/ui/footer.js
CHANGED
|
@@ -17,7 +17,10 @@ import { MODE_INFO } from './types.js';
|
|
|
17
17
|
import { getStyles } from '../themes/index.js';
|
|
18
18
|
import * as terminal from './terminal.js';
|
|
19
19
|
import { TodoZone } from './todo-zone.js';
|
|
20
|
-
import { InputPrompt, DEFAULT_COMMANDS } from './input-prompt
|
|
20
|
+
import { InputPrompt, DEFAULT_COMMANDS } from './input-prompt.js';
|
|
21
|
+
import { SubagentRenderer } from './subagent-renderer.js';
|
|
22
|
+
import { debugLog, debugRender } from '../utils/debug-log.js';
|
|
23
|
+
import { getPhysicalLineCount, getVisibleLength } from './line-utils.js';
|
|
21
24
|
// =============================================================================
|
|
22
25
|
// Footer Class
|
|
23
26
|
// =============================================================================
|
|
@@ -25,13 +28,17 @@ export class Footer extends EventEmitter {
|
|
|
25
28
|
// Child components
|
|
26
29
|
todoZone;
|
|
27
30
|
inputPrompt;
|
|
31
|
+
subagentRenderer;
|
|
28
32
|
// State
|
|
29
33
|
agentRunning = false;
|
|
30
34
|
mode;
|
|
31
35
|
lastRenderHeight = 0;
|
|
32
36
|
isRunning = false;
|
|
33
37
|
isPaused = false; // Prevents rendering even if callback is queued
|
|
38
|
+
isRendering = false; // Prevents re-entrant renders
|
|
34
39
|
cursorLineFromBottom = 0; // Track cursor position for clear()
|
|
40
|
+
projectName = null; // Current active project name
|
|
41
|
+
externalRendererEnabled = false; // When true, skip internal render loop
|
|
35
42
|
// Render loop
|
|
36
43
|
renderInterval;
|
|
37
44
|
renderTimer = null;
|
|
@@ -48,10 +55,15 @@ export class Footer extends EventEmitter {
|
|
|
48
55
|
showSeparators: options.showSeparators ?? true,
|
|
49
56
|
commands: DEFAULT_COMMANDS,
|
|
50
57
|
});
|
|
58
|
+
this.subagentRenderer = new SubagentRenderer();
|
|
51
59
|
// Wire up TodoZone animation callback
|
|
52
60
|
this.todoZone.setAnimationCallback(() => {
|
|
53
61
|
this.needsRender = true;
|
|
54
62
|
});
|
|
63
|
+
// Wire up SubagentRenderer update callback
|
|
64
|
+
this.subagentRenderer.setUpdateCallback(() => {
|
|
65
|
+
this.needsRender = true;
|
|
66
|
+
});
|
|
55
67
|
// Wire up InputPrompt events
|
|
56
68
|
this.inputPrompt.on('submit', (input) => {
|
|
57
69
|
this.emit('submit', input);
|
|
@@ -71,6 +83,11 @@ export class Footer extends EventEmitter {
|
|
|
71
83
|
this.inputPrompt.on('change', () => {
|
|
72
84
|
this.needsRender = true;
|
|
73
85
|
});
|
|
86
|
+
// Toggle subagent expand on Ctrl+O
|
|
87
|
+
this.inputPrompt.on('toggleSubagentExpand', () => {
|
|
88
|
+
this.subagentRenderer.toggleExpand();
|
|
89
|
+
this.needsRender = true;
|
|
90
|
+
});
|
|
74
91
|
}
|
|
75
92
|
// ===========================================================================
|
|
76
93
|
// Public API
|
|
@@ -88,7 +105,7 @@ export class Footer extends EventEmitter {
|
|
|
88
105
|
// Start render loop
|
|
89
106
|
this.render();
|
|
90
107
|
this.renderTimer = setInterval(() => {
|
|
91
|
-
if (this.needsRender && !this.isPaused) {
|
|
108
|
+
if (this.isRunning && this.needsRender && !this.isPaused) {
|
|
92
109
|
this.render();
|
|
93
110
|
this.needsRender = false;
|
|
94
111
|
}
|
|
@@ -121,14 +138,31 @@ export class Footer extends EventEmitter {
|
|
|
121
138
|
this.agentRunning = running;
|
|
122
139
|
this.todoZone.setAgentRunning(running);
|
|
123
140
|
this.inputPrompt.setQueueMode(running);
|
|
124
|
-
// When
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.
|
|
141
|
+
// When starting a new run, clear previous subagents
|
|
142
|
+
if (!wasRunning && running) {
|
|
143
|
+
this.subagentRenderer.clear();
|
|
144
|
+
// When external renderer manages rendering, just mark needsRender
|
|
145
|
+
if (this.externalRendererEnabled) {
|
|
146
|
+
this.needsRender = true;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Legacy mode: IMMEDIATELY render so lastRenderHeight includes spinner
|
|
150
|
+
this.clear();
|
|
151
|
+
this.render();
|
|
152
|
+
}
|
|
129
153
|
}
|
|
130
|
-
|
|
131
|
-
|
|
154
|
+
// When stopping, clear subagents
|
|
155
|
+
if (wasRunning && !running) {
|
|
156
|
+
this.subagentRenderer.clear();
|
|
157
|
+
// When external renderer manages rendering, just mark needsRender
|
|
158
|
+
if (this.externalRendererEnabled) {
|
|
159
|
+
this.needsRender = true;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// Legacy mode: immediately re-render to remove spinner
|
|
163
|
+
this.clear();
|
|
164
|
+
this.render();
|
|
165
|
+
}
|
|
132
166
|
}
|
|
133
167
|
}
|
|
134
168
|
/**
|
|
@@ -150,6 +184,19 @@ export class Footer extends EventEmitter {
|
|
|
150
184
|
getMode() {
|
|
151
185
|
return this.mode;
|
|
152
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Set the current project name (displayed in footer)
|
|
189
|
+
*/
|
|
190
|
+
setProjectName(name) {
|
|
191
|
+
this.projectName = name;
|
|
192
|
+
this.needsRender = true;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get the current project name
|
|
196
|
+
*/
|
|
197
|
+
getProjectName() {
|
|
198
|
+
return this.projectName;
|
|
199
|
+
}
|
|
153
200
|
/**
|
|
154
201
|
* Update the todo list
|
|
155
202
|
*/
|
|
@@ -185,6 +232,69 @@ export class Footer extends EventEmitter {
|
|
|
185
232
|
addTokens(count) {
|
|
186
233
|
this.todoZone.addTokens(count);
|
|
187
234
|
}
|
|
235
|
+
// ===========================================================================
|
|
236
|
+
// Subagent API
|
|
237
|
+
// ===========================================================================
|
|
238
|
+
/**
|
|
239
|
+
* Called when a subagent starts
|
|
240
|
+
*/
|
|
241
|
+
onSubagentStart(agentId, agentType, description) {
|
|
242
|
+
this.subagentRenderer.onSubagentStart(agentId, agentType, description);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Called when a subagent uses a tool
|
|
246
|
+
*/
|
|
247
|
+
onSubagentToolUse(agentId, toolName, summary) {
|
|
248
|
+
this.subagentRenderer.onToolUse(agentId, toolName, summary);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Called when a subagent completes
|
|
252
|
+
*
|
|
253
|
+
* CRITICAL (legacy mode only): We must pause rendering immediately to prevent a race condition.
|
|
254
|
+
* The render loop could fire between onSubagentEnd and clearForOutput, which would:
|
|
255
|
+
* 1. See the subagent as 'done'
|
|
256
|
+
* 2. Render with 0 subagent lines
|
|
257
|
+
* 3. Update lastRenderHeight to a smaller value
|
|
258
|
+
* 4. Then clearForOutput would not clear enough lines → ghost lines!
|
|
259
|
+
*
|
|
260
|
+
* In external renderer mode: No pause needed since the internal render loop is disabled.
|
|
261
|
+
*/
|
|
262
|
+
onSubagentEnd(agentId, success, tokenCount, error) {
|
|
263
|
+
// Only pause in legacy mode (internal render loop is running)
|
|
264
|
+
// In external renderer mode, the loop is already stopped
|
|
265
|
+
if (!this.externalRendererEnabled) {
|
|
266
|
+
// Pause rendering BEFORE updating state to prevent race condition
|
|
267
|
+
// The caller (repl.ts) will call clearForOutput() and forceRender() which will unpause
|
|
268
|
+
this.isPaused = true;
|
|
269
|
+
}
|
|
270
|
+
this.subagentRenderer.onSubagentEnd(agentId, success, tokenCount, error);
|
|
271
|
+
this.needsRender = true;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Toggle expanded view for subagents (Ctrl+O)
|
|
275
|
+
*/
|
|
276
|
+
toggleSubagentExpand() {
|
|
277
|
+
this.subagentRenderer.toggleExpand();
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Check if there are active subagents
|
|
281
|
+
*/
|
|
282
|
+
hasActiveSubagents() {
|
|
283
|
+
return this.subagentRenderer.hasActiveSubagents();
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Clear subagent tracking (after completion displayed)
|
|
287
|
+
*/
|
|
288
|
+
clearSubagents() {
|
|
289
|
+
this.subagentRenderer.clear();
|
|
290
|
+
this.needsRender = true;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get the last subagent's stats before clearing
|
|
294
|
+
*/
|
|
295
|
+
getLastSubagentStats() {
|
|
296
|
+
return this.subagentRenderer.getLastAgentStats();
|
|
297
|
+
}
|
|
188
298
|
/**
|
|
189
299
|
* Get queued inputs
|
|
190
300
|
*/
|
|
@@ -217,22 +327,58 @@ export class Footer extends EventEmitter {
|
|
|
217
327
|
/**
|
|
218
328
|
* Clear footer before scrolling output
|
|
219
329
|
* Call this before writing to the scrolling zone
|
|
330
|
+
* Pauses interval rendering to prevent ghost lines during output
|
|
331
|
+
*
|
|
332
|
+
* When external renderer is enabled (scroll region mode):
|
|
333
|
+
* - Footer doesn't need clearing (it's in a separate scroll region)
|
|
334
|
+
* - Just mark that we need a re-render
|
|
220
335
|
*/
|
|
221
336
|
clearForOutput() {
|
|
337
|
+
// When external renderer manages rendering, footer is in a fixed region
|
|
338
|
+
// and doesn't need to be cleared before output
|
|
339
|
+
if (this.externalRendererEnabled) {
|
|
340
|
+
debugRender('Footer:clearForOutput', 'external renderer mode - skipping clear');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// Pause interval rendering to prevent ghost lines during output
|
|
344
|
+
this.isPaused = true;
|
|
345
|
+
debugRender('Footer:clearForOutput', `lastRenderHeight=${String(this.lastRenderHeight)}`);
|
|
346
|
+
// Clear the footer
|
|
222
347
|
this.clear();
|
|
223
348
|
}
|
|
224
349
|
/**
|
|
225
350
|
* Force an immediate render
|
|
351
|
+
* Resumes interval rendering after clearForOutput()
|
|
352
|
+
*
|
|
353
|
+
* When external renderer is enabled (scroll region mode):
|
|
354
|
+
* - Just mark that we need a re-render (TerminalRenderer's loop will pick it up)
|
|
226
355
|
*/
|
|
227
356
|
forceRender() {
|
|
357
|
+
// When external renderer manages rendering, just mark that we need re-render
|
|
358
|
+
if (this.externalRendererEnabled) {
|
|
359
|
+
debugRender('Footer:forceRender', 'external renderer mode - marking needsRender');
|
|
360
|
+
this.needsRender = true;
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
// Resume interval rendering BEFORE calling render (so render() isn't skipped)
|
|
364
|
+
this.isPaused = false;
|
|
228
365
|
this.render();
|
|
229
366
|
this.needsRender = false;
|
|
230
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Restart input prompt after a command completes
|
|
370
|
+
* Use this for non-overlay commands where finishInput() stopped the prompt
|
|
371
|
+
*/
|
|
372
|
+
restartInput() {
|
|
373
|
+
this.inputPrompt.start();
|
|
374
|
+
this.render();
|
|
375
|
+
}
|
|
231
376
|
/**
|
|
232
377
|
* Pause footer completely (for overlays)
|
|
233
378
|
* Stops render loop, input capture, and animation
|
|
234
379
|
*/
|
|
235
380
|
pauseAnimation() {
|
|
381
|
+
debugLog('Footer:pauseAnimation', 'start');
|
|
236
382
|
// Set paused flag FIRST - this prevents any queued render callbacks from executing
|
|
237
383
|
this.isPaused = true;
|
|
238
384
|
// Stop render loop
|
|
@@ -240,12 +386,16 @@ export class Footer extends EventEmitter {
|
|
|
240
386
|
clearInterval(this.renderTimer);
|
|
241
387
|
this.renderTimer = null;
|
|
242
388
|
}
|
|
389
|
+
debugLog('Footer:pauseAnimation', 'render loop stopped');
|
|
243
390
|
// Stop input capture
|
|
244
391
|
this.inputPrompt.stop();
|
|
392
|
+
debugLog('Footer:pauseAnimation', 'input stopped');
|
|
245
393
|
// Stop animation
|
|
246
394
|
this.todoZone.pauseAnimation();
|
|
395
|
+
debugLog('Footer:pauseAnimation', 'animation stopped');
|
|
247
396
|
// Clear footer from screen
|
|
248
397
|
this.clear();
|
|
398
|
+
debugLog('Footer:pauseAnimation', 'footer cleared');
|
|
249
399
|
}
|
|
250
400
|
/**
|
|
251
401
|
* Resume footer after pause
|
|
@@ -261,7 +411,7 @@ export class Footer extends EventEmitter {
|
|
|
261
411
|
// Restart render loop
|
|
262
412
|
this.render();
|
|
263
413
|
this.renderTimer = setInterval(() => {
|
|
264
|
-
if (this.needsRender && !this.isPaused) {
|
|
414
|
+
if (this.isRunning && this.needsRender && !this.isPaused) {
|
|
265
415
|
this.render();
|
|
266
416
|
this.needsRender = false;
|
|
267
417
|
}
|
|
@@ -273,6 +423,99 @@ export class Footer extends EventEmitter {
|
|
|
273
423
|
getLastRenderHeight() {
|
|
274
424
|
return this.lastRenderHeight;
|
|
275
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* Get all footer lines for rendering (for TerminalRenderer integration)
|
|
428
|
+
* Returns the lines WITHOUT writing to terminal
|
|
429
|
+
*/
|
|
430
|
+
getFooterLines() {
|
|
431
|
+
const allLines = [];
|
|
432
|
+
const s = getStyles();
|
|
433
|
+
// 1. Subagent zone (only if agent is running)
|
|
434
|
+
if (this.agentRunning) {
|
|
435
|
+
const subagentLines = this.subagentRenderer.render();
|
|
436
|
+
allLines.push(...subagentLines);
|
|
437
|
+
}
|
|
438
|
+
// 2. Todo zone (spinner + todos)
|
|
439
|
+
const todoLines = this.todoZone.render();
|
|
440
|
+
allLines.push(...todoLines);
|
|
441
|
+
// 3. Queued inputs
|
|
442
|
+
const queuedInputs = this.inputPrompt.getQueuedInputs();
|
|
443
|
+
for (const queued of queuedInputs) {
|
|
444
|
+
allLines.push(s.muted(`queued: "${queued}"`));
|
|
445
|
+
}
|
|
446
|
+
// 4. Input prompt
|
|
447
|
+
const inputLines = this.inputPrompt.render();
|
|
448
|
+
allLines.push(...inputLines);
|
|
449
|
+
// 5. Mode indicator (below input, before autocomplete)
|
|
450
|
+
allLines.push(this.renderModeIndicator());
|
|
451
|
+
// 6. Autocomplete dropdown (if active)
|
|
452
|
+
const autocompleteLines = this.inputPrompt.getAutocompleteLines();
|
|
453
|
+
allLines.push(...autocompleteLines);
|
|
454
|
+
return allLines;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Get cursor position within footer content (for TerminalRenderer)
|
|
458
|
+
* Returns { row, col } where row is 0-indexed from start of footer lines
|
|
459
|
+
*/
|
|
460
|
+
getFooterCursorPosition() {
|
|
461
|
+
const cursorInfo = this.inputPrompt.getCursorInfo();
|
|
462
|
+
// Calculate lines before input prompt
|
|
463
|
+
let linesBeforeInput = 0;
|
|
464
|
+
// Subagent lines
|
|
465
|
+
if (this.agentRunning) {
|
|
466
|
+
linesBeforeInput += this.subagentRenderer.render().length;
|
|
467
|
+
}
|
|
468
|
+
// Todo lines
|
|
469
|
+
linesBeforeInput += this.todoZone.render().length;
|
|
470
|
+
// Queued inputs
|
|
471
|
+
linesBeforeInput += this.inputPrompt.getQueuedInputs().length;
|
|
472
|
+
// cursorInfo.row is 1-indexed (row 1 = first line after separator)
|
|
473
|
+
// Convert to 0-indexed row within footer
|
|
474
|
+
const row = linesBeforeInput + cursorInfo.row - 1;
|
|
475
|
+
return { row, col: cursorInfo.col };
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Enable external renderer mode (TerminalRenderer manages rendering)
|
|
479
|
+
* When enabled:
|
|
480
|
+
* - Footer's internal render loop is disabled
|
|
481
|
+
* - Input capture remains active
|
|
482
|
+
* - Event forwarding remains active
|
|
483
|
+
* - TerminalRenderer calls getFooterLines() and renders
|
|
484
|
+
*/
|
|
485
|
+
setExternalRenderer(enabled) {
|
|
486
|
+
this.externalRendererEnabled = enabled;
|
|
487
|
+
if (enabled && this.renderTimer) {
|
|
488
|
+
// Stop the internal render loop (TerminalRenderer will handle rendering)
|
|
489
|
+
clearInterval(this.renderTimer);
|
|
490
|
+
this.renderTimer = null;
|
|
491
|
+
debugLog('Footer:setExternalRenderer', 'enabled - internal render loop stopped');
|
|
492
|
+
}
|
|
493
|
+
else if (!enabled && this.isRunning && !this.renderTimer) {
|
|
494
|
+
// Restart the internal render loop
|
|
495
|
+
this.renderTimer = setInterval(() => {
|
|
496
|
+
if (this.isRunning && this.needsRender && !this.isPaused) {
|
|
497
|
+
this.render();
|
|
498
|
+
this.needsRender = false;
|
|
499
|
+
}
|
|
500
|
+
}, this.renderInterval);
|
|
501
|
+
debugLog('Footer:setExternalRenderer', 'disabled - internal render loop restarted');
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Check if external renderer is enabled
|
|
506
|
+
*/
|
|
507
|
+
isExternalRendererEnabled() {
|
|
508
|
+
return this.externalRendererEnabled;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Request a render from external renderer
|
|
512
|
+
* Call this to notify TerminalRenderer that footer needs re-rendering
|
|
513
|
+
*/
|
|
514
|
+
requestExternalRender() {
|
|
515
|
+
this.needsRender = true;
|
|
516
|
+
// External renderer will poll this or listen for events
|
|
517
|
+
// For now, just set the flag
|
|
518
|
+
}
|
|
276
519
|
/**
|
|
277
520
|
* Refresh the prompt with current theme colors
|
|
278
521
|
* Call after theme changes to apply new colors immediately
|
|
@@ -287,17 +530,26 @@ export class Footer extends EventEmitter {
|
|
|
287
530
|
// ===========================================================================
|
|
288
531
|
/**
|
|
289
532
|
* Clear the footer area
|
|
533
|
+
*
|
|
534
|
+
* Strategy: Simply move to the top of the CURRENT footer and clear to end.
|
|
535
|
+
* - When footer SHRINKS: clearToEndOfScreen handles the extra lines
|
|
536
|
+
* - When footer GROWS: terminal scrolls naturally when we write more lines
|
|
537
|
+
*
|
|
538
|
+
* We NEVER move up into the scrolling zone - that would eat content!
|
|
290
539
|
*/
|
|
291
540
|
clear() {
|
|
541
|
+
debugRender('Footer:clear', `lastHeight=${String(this.lastRenderHeight)} cursorFromBottom=${String(this.cursorLineFromBottom)}`);
|
|
292
542
|
if (this.lastRenderHeight > 0) {
|
|
293
543
|
// Move to start of current line
|
|
294
544
|
terminal.moveCursorToLineStart();
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
545
|
+
// Calculate DIRECT movement to top of footer
|
|
546
|
+
const cursorRowFromTop = this.lastRenderHeight - this.cursorLineFromBottom;
|
|
547
|
+
const rowsToMoveUp = cursorRowFromTop - 1;
|
|
548
|
+
debugRender('Footer:clear', `cursorRowFromTop=${String(cursorRowFromTop)} rowsToMoveUp=${String(rowsToMoveUp)}`);
|
|
549
|
+
if (rowsToMoveUp > 0) {
|
|
550
|
+
terminal.moveCursorUp(rowsToMoveUp);
|
|
298
551
|
}
|
|
299
|
-
//
|
|
300
|
-
terminal.moveCursorUp(this.lastRenderHeight - 1);
|
|
552
|
+
// Clear from here to end of screen
|
|
301
553
|
terminal.clearToEndOfScreen();
|
|
302
554
|
}
|
|
303
555
|
this.lastRenderHeight = 0;
|
|
@@ -307,47 +559,68 @@ export class Footer extends EventEmitter {
|
|
|
307
559
|
* Render the entire footer
|
|
308
560
|
*/
|
|
309
561
|
render() {
|
|
310
|
-
//
|
|
311
|
-
this.
|
|
312
|
-
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
allLines.push(...inputLines);
|
|
325
|
-
// 4. Mode indicator (below input, before autocomplete)
|
|
326
|
-
allLines.push(this.renderModeIndicator());
|
|
327
|
-
// 5. Autocomplete dropdown (if active)
|
|
328
|
-
const autocompleteLines = this.inputPrompt.getAutocompleteLines();
|
|
329
|
-
allLines.push(...autocompleteLines);
|
|
330
|
-
// Write all lines
|
|
331
|
-
for (let i = 0; i < allLines.length; i++) {
|
|
332
|
-
terminal.write(allLines[i]);
|
|
333
|
-
if (i < allLines.length - 1) {
|
|
334
|
-
terminal.write('\n');
|
|
562
|
+
// Don't render if footer has been stopped, we're paused, or already rendering
|
|
563
|
+
if (!this.isRunning || this.isPaused || this.isRendering)
|
|
564
|
+
return;
|
|
565
|
+
// Prevent re-entrant renders
|
|
566
|
+
this.isRendering = true;
|
|
567
|
+
try {
|
|
568
|
+
// Build all lines FIRST to know new height before clearing
|
|
569
|
+
const allLines = [];
|
|
570
|
+
let subagentLineCount = 0;
|
|
571
|
+
// 1. Subagent zone (only if agent is running - prevents showing after completion)
|
|
572
|
+
if (this.agentRunning) {
|
|
573
|
+
const subagentLines = this.subagentRenderer.render();
|
|
574
|
+
subagentLineCount = subagentLines.length;
|
|
575
|
+
allLines.push(...subagentLines);
|
|
335
576
|
}
|
|
577
|
+
// 2. Todo zone (spinner + todos)
|
|
578
|
+
const todoLines = this.todoZone.render();
|
|
579
|
+
allLines.push(...todoLines);
|
|
580
|
+
// 3. Queued inputs
|
|
581
|
+
const s = getStyles();
|
|
582
|
+
const queuedInputs = this.inputPrompt.getQueuedInputs();
|
|
583
|
+
for (const queued of queuedInputs) {
|
|
584
|
+
allLines.push(s.muted(`queued: "${queued}"`));
|
|
585
|
+
}
|
|
586
|
+
// 4. Input prompt
|
|
587
|
+
const inputLines = this.inputPrompt.render();
|
|
588
|
+
allLines.push(...inputLines);
|
|
589
|
+
// 5. Mode indicator (below input, before autocomplete)
|
|
590
|
+
allLines.push(this.renderModeIndicator());
|
|
591
|
+
// 6. Autocomplete dropdown (if active)
|
|
592
|
+
const autocompleteLines = this.inputPrompt.getAutocompleteLines();
|
|
593
|
+
allLines.push(...autocompleteLines);
|
|
594
|
+
// Calculate new height BEFORE clearing (to clear extra lines if growing)
|
|
595
|
+
const termWidth = process.stdout.columns || 80;
|
|
596
|
+
let newHeight = 0;
|
|
597
|
+
const lineHeights = [];
|
|
598
|
+
for (const line of allLines) {
|
|
599
|
+
// Use unified line-utils for consistent height calculation
|
|
600
|
+
const lineHeight = getPhysicalLineCount(line, termWidth);
|
|
601
|
+
lineHeights.push(lineHeight);
|
|
602
|
+
newHeight += lineHeight;
|
|
603
|
+
}
|
|
604
|
+
debugRender('Footer:render', `subagent=${String(subagentLineCount)} todo=${String(todoLines.length)} queued=${String(queuedInputs.length)} input=${String(inputLines.length)} autocomplete=${String(autocompleteLines.length)}`);
|
|
605
|
+
debugRender('Footer:render', `termWidth=${String(termWidth)} lineHeights=[${lineHeights.join(',')}] newHeight=${String(newHeight)}`);
|
|
606
|
+
// Clear the current footer (terminal scrolls naturally if we write more lines)
|
|
607
|
+
this.clear();
|
|
608
|
+
// Write all lines
|
|
609
|
+
for (let i = 0; i < allLines.length; i++) {
|
|
610
|
+
terminal.write(allLines[i]);
|
|
611
|
+
if (i < allLines.length - 1) {
|
|
612
|
+
terminal.write('\n');
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
// Track height for next clear
|
|
616
|
+
this.lastRenderHeight = newHeight;
|
|
617
|
+
debugRender('Footer:render', `lastRenderHeight set to ${String(newHeight)}`);
|
|
618
|
+
// Position cursor within input prompt
|
|
619
|
+
this.positionCursor(allLines, subagentLineCount + todoLines.length + queuedInputs.length);
|
|
336
620
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
let actualTerminalRows = 0;
|
|
340
|
-
for (const line of allLines) {
|
|
341
|
-
// Strip ANSI codes to get visible length
|
|
342
|
-
// eslint-disable-next-line no-control-regex
|
|
343
|
-
const visibleLength = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
344
|
-
// Each line takes at least 1 row, plus extra rows if it wraps
|
|
345
|
-
actualTerminalRows += Math.max(1, Math.ceil(visibleLength / termWidth));
|
|
621
|
+
finally {
|
|
622
|
+
this.isRendering = false;
|
|
346
623
|
}
|
|
347
|
-
this.lastRenderHeight = actualTerminalRows;
|
|
348
|
-
// Position cursor within input prompt
|
|
349
|
-
// linesBeforeInput = todoLines + queuedInputs (mode indicator is now AFTER input)
|
|
350
|
-
this.positionCursor(allLines, todoLines.length + queuedInputs.length);
|
|
351
624
|
}
|
|
352
625
|
/**
|
|
353
626
|
* Render mode indicator line
|
|
@@ -355,30 +628,43 @@ export class Footer extends EventEmitter {
|
|
|
355
628
|
renderModeIndicator() {
|
|
356
629
|
const s = getStyles();
|
|
357
630
|
const modeInfo = MODE_INFO[this.mode];
|
|
631
|
+
const termWidth = process.stdout.columns || 80;
|
|
632
|
+
// Build left side (mode indicator)
|
|
633
|
+
let leftPart;
|
|
358
634
|
switch (this.mode) {
|
|
359
635
|
case 'normal':
|
|
360
|
-
|
|
361
|
-
|
|
636
|
+
leftPart = s.muted(`mode: ${modeInfo.label} (Shift+Tab to change)`);
|
|
637
|
+
break;
|
|
362
638
|
case 'auto-accept':
|
|
363
|
-
|
|
364
|
-
|
|
639
|
+
leftPart = s.warning(`⚡ mode: ${modeInfo.label}`) + s.muted(' (Shift+Tab to change)');
|
|
640
|
+
break;
|
|
365
641
|
case 'plan':
|
|
366
|
-
|
|
367
|
-
|
|
642
|
+
leftPart = s.info(`📋 mode: ${modeInfo.label}`) + s.muted(' (Shift+Tab to change)');
|
|
643
|
+
break;
|
|
368
644
|
default:
|
|
369
|
-
|
|
645
|
+
leftPart = s.muted(`mode: ${modeInfo.label} (Shift+Tab to change)`);
|
|
646
|
+
}
|
|
647
|
+
// If no project, just return the mode indicator
|
|
648
|
+
if (!this.projectName) {
|
|
649
|
+
return leftPart;
|
|
370
650
|
}
|
|
651
|
+
// Build right side (project name)
|
|
652
|
+
const projectText = `Project: ${this.projectName}`;
|
|
653
|
+
const rightPart = s.muted(projectText);
|
|
654
|
+
// Calculate visible lengths (using unified line-utils)
|
|
655
|
+
const leftVisible = getVisibleLength(leftPart);
|
|
656
|
+
const rightVisible = projectText.length;
|
|
657
|
+
// Calculate padding to right-align project name
|
|
658
|
+
const padding = Math.max(2, termWidth - leftVisible - rightVisible);
|
|
659
|
+
return leftPart + ' '.repeat(padding) + rightPart;
|
|
371
660
|
}
|
|
372
661
|
/**
|
|
373
662
|
* Calculate how many terminal rows a line takes (accounting for wrapping)
|
|
663
|
+
* Uses unified line-utils for consistent calculation across all components.
|
|
374
664
|
*/
|
|
375
665
|
getTerminalRowsForLine(line) {
|
|
376
666
|
const termWidth = process.stdout.columns || 80;
|
|
377
|
-
|
|
378
|
-
// eslint-disable-next-line no-control-regex
|
|
379
|
-
const visibleLength = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
380
|
-
// Each line takes at least 1 row, plus extra rows if it wraps
|
|
381
|
-
return Math.max(1, Math.ceil(visibleLength / termWidth));
|
|
667
|
+
return getPhysicalLineCount(line, termWidth);
|
|
382
668
|
}
|
|
383
669
|
/**
|
|
384
670
|
* Position cursor correctly within the input prompt
|
|
@@ -406,11 +692,17 @@ export class Footer extends EventEmitter {
|
|
|
406
692
|
for (let i = inputEndIndex; i < allLines.length; i++) {
|
|
407
693
|
rowsAfterInput += this.getTerminalRowsForLine(allLines[i]);
|
|
408
694
|
}
|
|
409
|
-
// Cursor target row from start of footer = rows before input + cursor row within input
|
|
695
|
+
// Cursor target row from start of footer (1-indexed) = rows before input + cursor row within input
|
|
410
696
|
const cursorRowFromStart = rowsBeforeInput + cursorInfo.row;
|
|
411
|
-
// Total rows
|
|
697
|
+
// Total rows in footer
|
|
412
698
|
const totalRows = rowsBeforeInput + inputTotalRows + rowsAfterInput;
|
|
699
|
+
// After writing all lines, cursor is at the END of the last row (row totalRows).
|
|
700
|
+
// cursorInfo.row starts at 1 (after separator), so actual footer row = cursorRowFromStart + 1
|
|
701
|
+
// To move to actual row, we move up: totalRows - (cursorRowFromStart + 1) = totalRows - cursorRowFromStart - 1
|
|
702
|
+
// Example: totalRows=7, cursorRowFromStart=4, actual row=5 → move up 2 rows (from row 7 to row 5)
|
|
413
703
|
const rowsToMoveUp = totalRows - cursorRowFromStart - 1;
|
|
704
|
+
debugRender('Footer:positionCursor', `cursorInfo={row:${String(cursorInfo.row)},col:${String(cursorInfo.col)}} rowsBefore=${String(rowsBeforeInput)} inputRows=${String(inputTotalRows)} rowsAfter=${String(rowsAfterInput)}`);
|
|
705
|
+
debugRender('Footer:positionCursor', `totalRows=${String(totalRows)} cursorRowFromStart=${String(cursorRowFromStart)} rowsToMoveUp=${String(rowsToMoveUp)}`);
|
|
414
706
|
if (rowsToMoveUp > 0) {
|
|
415
707
|
terminal.moveCursorUp(rowsToMoveUp);
|
|
416
708
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrail Confirmation Overlay
|
|
3
|
+
*
|
|
4
|
+
* Shows when a guardrail with action='confirm' is triggered.
|
|
5
|
+
* User must approve or deny the risky operation.
|
|
6
|
+
*/
|
|
7
|
+
export interface GuardrailConfirmOptions {
|
|
8
|
+
/** Guardrail name */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Guardrail description/message */
|
|
11
|
+
message: string;
|
|
12
|
+
/** Action type (for display) */
|
|
13
|
+
action: 'warn' | 'confirm' | 'block';
|
|
14
|
+
/** Tool that triggered the guardrail */
|
|
15
|
+
toolName: string;
|
|
16
|
+
/** The input that matched the pattern */
|
|
17
|
+
matchedInput: string;
|
|
18
|
+
/** Category (e.g., 'git', 'filesystem', 'database') */
|
|
19
|
+
category?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface GuardrailConfirmResult {
|
|
22
|
+
/** Whether to proceed with the operation */
|
|
23
|
+
approved: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Show the guardrail confirmation overlay.
|
|
27
|
+
* Returns { approved: true } if user allows, { approved: false } if denied.
|
|
28
|
+
*/
|
|
29
|
+
export declare function showGuardrailOverlay(options: GuardrailConfirmOptions): Promise<GuardrailConfirmResult>;
|