@bahulam/code 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/NOTICE +6 -6
- package/README.md +10 -17
- package/package.json +9 -9
- package/src/agents/loader.mjs +1 -1
- package/src/agents/workflow_loader.mjs +2 -2
- package/src/auth/tarang-auth.mjs +3 -2
- package/src/config/model-catalog-default.json +4 -3
- package/src/config/model-catalog.mjs +2 -0
- package/src/context/prose-chunker.mjs +162 -5
- package/src/core/attachments.mjs +65 -3
- package/src/core/bundled-runtime.mjs +1 -1
- package/src/core/headless.mjs +14 -0
- package/src/core/jsonl-writer.mjs +3 -3
- package/src/core/lint-resolver.mjs +472 -0
- package/src/core/local-agent.mjs +15 -4
- package/src/core/local-store.mjs +4 -0
- package/src/core/output-filter.mjs +21 -25
- package/src/core/pricing.mjs +10 -3
- package/src/core/stream-client.mjs +9 -4
- package/src/core/tool-executor.mjs +105 -29
- package/src/daemon/http-dashboard.mjs +400 -0
- package/src/daemon/session-core.mjs +1 -0
- package/src/local-service/agent-relay.mjs +868 -0
- package/src/local-service/approval-bridge.mjs +222 -0
- package/src/local-service/browser.mjs +24 -0
- package/src/local-service/command.mjs +155 -0
- package/src/local-service/file-access.mjs +393 -0
- package/src/local-service/machine.mjs +86 -0
- package/src/local-service/paths.mjs +29 -0
- package/src/local-service/preview-converters.mjs +260 -0
- package/src/local-service/server.mjs +2644 -0
- package/src/local-service/session-store.mjs +221 -0
- package/src/onboarding/preflight.mjs +1 -1
- package/src/terminal/analytics.mjs +2 -6
- package/src/terminal/ansi.mjs +11 -3
- package/src/terminal/main.mjs +37 -11
- package/src/terminal/model-catalog-display.mjs +111 -0
- package/src/terminal/repl-format.mjs +4 -1
- package/src/terminal/repl-model-form.mjs +81 -14
- package/src/terminal/repl-render.mjs +35 -5
- package/src/terminal/repl-state.mjs +12 -0
- package/src/terminal/repl.mjs +231 -53
- package/src/terminal/tool-display.mjs +32 -0
- package/src/terminal/watch-state.mjs +118 -0
- package/src/tools/analyze-image.mjs +158 -0
- package/src/tools/bash.mjs +11 -9
- package/src/tools/generate-image.mjs +411 -0
- package/src/tools/lint.mjs +24 -43
- package/src/tools/registry.mjs +4 -0
- package/src/ui/icons.mjs +2 -0
- package/src/ui/palette.mjs +1 -1
- package/src/ui/slash-commands.mjs +4 -6
- package/src/ui/tool-card.mjs +7 -3
- package/pulse/app/activity/page.tsx +0 -190
- package/pulse/app/api/activity/route.ts +0 -138
- package/pulse/app/api/benchmark/route.ts +0 -113
- package/pulse/app/api/benchmarks/route.ts +0 -195
- package/pulse/app/api/costs/route.ts +0 -88
- package/pulse/app/api/export/route.ts +0 -77
- package/pulse/app/api/history/route.ts +0 -11
- package/pulse/app/api/import/route.ts +0 -31
- package/pulse/app/api/memory/route.ts +0 -50
- package/pulse/app/api/plans/route.ts +0 -9
- package/pulse/app/api/projects/[slug]/route.ts +0 -96
- package/pulse/app/api/projects/route.ts +0 -121
- package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
- package/pulse/app/api/sessions/[id]/route.ts +0 -31
- package/pulse/app/api/sessions/route.ts +0 -112
- package/pulse/app/api/settings/route.ts +0 -14
- package/pulse/app/api/stats/route.ts +0 -143
- package/pulse/app/api/todos/route.ts +0 -9
- package/pulse/app/api/tools/route.ts +0 -160
- package/pulse/app/benchmarks/page.tsx +0 -224
- package/pulse/app/costs/page.tsx +0 -179
- package/pulse/app/export/page.tsx +0 -465
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +0 -263
- package/pulse/app/help/page.tsx +0 -143
- package/pulse/app/history/page.tsx +0 -157
- package/pulse/app/layout.tsx +0 -46
- package/pulse/app/memory/page.tsx +0 -365
- package/pulse/app/overview-client.tsx +0 -393
- package/pulse/app/page.tsx +0 -14
- package/pulse/app/plans/page.tsx +0 -308
- package/pulse/app/projects/[slug]/page.tsx +0 -390
- package/pulse/app/projects/page.tsx +0 -110
- package/pulse/app/sessions/[id]/page.tsx +0 -243
- package/pulse/app/sessions/page.tsx +0 -39
- package/pulse/app/settings/page.tsx +0 -188
- package/pulse/app/todos/page.tsx +0 -211
- package/pulse/app/tools/page.tsx +0 -249
- package/pulse/cli.js +0 -164
- package/pulse/components/activity/day-of-week-chart.tsx +0 -35
- package/pulse/components/activity/streak-card.tsx +0 -36
- package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
- package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
- package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
- package/pulse/components/costs/model-token-table.tsx +0 -60
- package/pulse/components/global-search.tsx +0 -193
- package/pulse/components/keyboard-nav-provider.tsx +0 -23
- package/pulse/components/layout/bottom-nav.tsx +0 -53
- package/pulse/components/layout/client-layout.tsx +0 -31
- package/pulse/components/layout/sidebar-context.tsx +0 -50
- package/pulse/components/layout/sidebar.tsx +0 -183
- package/pulse/components/layout/top-bar.tsx +0 -121
- package/pulse/components/overview/activity-heatmap.tsx +0 -107
- package/pulse/components/overview/conversation-table.tsx +0 -148
- package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
- package/pulse/components/overview/peak-hours-chart.tsx +0 -87
- package/pulse/components/overview/project-activity-donut.tsx +0 -96
- package/pulse/components/overview/stat-card.tsx +0 -102
- package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
- package/pulse/components/projects/project-card.tsx +0 -175
- package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
- package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
- package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
- package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
- package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
- package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
- package/pulse/components/sessions/session-badges.tsx +0 -49
- package/pulse/components/sessions/session-table.tsx +0 -299
- package/pulse/components/theme-provider.tsx +0 -44
- package/pulse/components/tools/feature-adoption-table.tsx +0 -58
- package/pulse/components/tools/mcp-server-panel.tsx +0 -45
- package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
- package/pulse/components/tools/version-history-table.tsx +0 -32
- package/pulse/components/ui/alert.tsx +0 -66
- package/pulse/components/ui/badge.tsx +0 -48
- package/pulse/components/ui/breadcrumb.tsx +0 -109
- package/pulse/components/ui/button.tsx +0 -64
- package/pulse/components/ui/calendar.tsx +0 -220
- package/pulse/components/ui/card.tsx +0 -92
- package/pulse/components/ui/command.tsx +0 -158
- package/pulse/components/ui/dialog.tsx +0 -158
- package/pulse/components/ui/input.tsx +0 -21
- package/pulse/components/ui/popover.tsx +0 -89
- package/pulse/components/ui/progress.tsx +0 -31
- package/pulse/components/ui/select.tsx +0 -190
- package/pulse/components/ui/separator.tsx +0 -28
- package/pulse/components/ui/sheet.tsx +0 -143
- package/pulse/components/ui/skeleton.tsx +0 -13
- package/pulse/components/ui/table.tsx +0 -116
- package/pulse/components/ui/tabs.tsx +0 -91
- package/pulse/components/ui/tooltip.tsx +0 -57
- package/pulse/components/use-global-keyboard-nav.ts +0 -79
- package/pulse/components.json +0 -23
- package/pulse/eslint.config.mjs +0 -18
- package/pulse/lib/bahulam-paths.ts +0 -23
- package/pulse/lib/claude-reader.ts +0 -592
- package/pulse/lib/decode.ts +0 -129
- package/pulse/lib/pricing.ts +0 -102
- package/pulse/lib/replay-parser.ts +0 -165
- package/pulse/lib/tool-categories.ts +0 -140
- package/pulse/lib/utils.ts +0 -6
- package/pulse/next-env.d.ts +0 -6
- package/pulse/next.config.ts +0 -16
- package/pulse/package.json +0 -45
- package/pulse/postcss.config.mjs +0 -7
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +0 -1
- package/pulse/public/globe.svg +0 -1
- package/pulse/public/next.svg +0 -1
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +0 -1
- package/pulse/public/window.svg +0 -1
- package/pulse/tsconfig.json +0 -34
- package/pulse/types/claude.ts +0 -294
|
@@ -21,6 +21,7 @@ import { paint } from '../ui/palette.mjs';
|
|
|
21
21
|
import { runtime, session } from './repl-state.mjs';
|
|
22
22
|
import { fitAnsiLine } from './repl-format.mjs';
|
|
23
23
|
import { exploreCategory, isExploreTool } from './repl-explore.mjs';
|
|
24
|
+
import { watchState } from './watch-state.mjs';
|
|
24
25
|
import {
|
|
25
26
|
clearPinnedStatus,
|
|
26
27
|
drawPinnedStatus,
|
|
@@ -36,21 +37,50 @@ import * as queue from '../ui/render-queue.mjs';
|
|
|
36
37
|
// Max inner-tool lines shown under the spinner during a sub-agent run.
|
|
37
38
|
const SUB_AGENT_WINDOW_ROWS = 7;
|
|
38
39
|
|
|
40
|
+
function statusWidth() {
|
|
41
|
+
return Math.max(8, (process.stderr.columns || process.stdout.columns || 120) - 1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fitStatusLine(line) {
|
|
45
|
+
return fitAnsiLine(String(line ?? '').replace(/[\r\n]+/g, ' '), statusWidth());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function fitStatusLines(lines) {
|
|
49
|
+
return (Array.isArray(lines) ? lines : [lines]).map(fitStatusLine);
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
function presentStatus(rendered) {
|
|
53
|
+
// Watch panel override: when active, render the watch panel entries as a
|
|
54
|
+
// multi-line status block instead of the normal spinner/status line.
|
|
55
|
+
if (watchState.active) {
|
|
56
|
+
const lines = watchState.visible();
|
|
57
|
+
if (lines.length) {
|
|
58
|
+
const fitted = fitStatusLines(lines);
|
|
59
|
+
if (queue.isActive()) {
|
|
60
|
+
queue.statusBlock(fitted);
|
|
61
|
+
} else if (isInputDockMounted()) {
|
|
62
|
+
drawPinnedStatus(fitted.join('\n'));
|
|
63
|
+
} else {
|
|
64
|
+
inPlace(fitted.join('\n'));
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const fitted = fitStatusLine(rendered);
|
|
40
70
|
if (queue.isActive()) {
|
|
41
71
|
const win = runtime.subAgentWindow;
|
|
42
72
|
if (win?.active && win.lines.length) {
|
|
43
73
|
queue.statusBlock([
|
|
44
|
-
|
|
45
|
-
...win.lines.slice(-SUB_AGENT_WINDOW_ROWS).map(l => ` ${c.dim(l)}`),
|
|
74
|
+
fitted,
|
|
75
|
+
...fitStatusLines(win.lines.slice(-SUB_AGENT_WINDOW_ROWS).map(l => ` ${c.dim(l)}`)),
|
|
46
76
|
]);
|
|
47
77
|
return;
|
|
48
78
|
}
|
|
49
|
-
queue.status(
|
|
79
|
+
queue.status(fitted);
|
|
50
80
|
return;
|
|
51
81
|
}
|
|
52
|
-
if (isInputDockMounted()) { drawPinnedStatus(
|
|
53
|
-
inPlace(
|
|
82
|
+
if (isInputDockMounted()) { drawPinnedStatus(fitted); return; }
|
|
83
|
+
inPlace(fitted);
|
|
54
84
|
}
|
|
55
85
|
|
|
56
86
|
/** Push a line into the live sub-agent tool window (dedup consecutive). */
|
|
@@ -62,6 +62,10 @@ export const runtime = {
|
|
|
62
62
|
// N lines under the spinner) instead of appending to the transcript.
|
|
63
63
|
// Full detail stays on the recorded cards (/expand, /last, `d`).
|
|
64
64
|
subAgentWindow: { active: false, lines: [] },
|
|
65
|
+
|
|
66
|
+
// PRD-092: Watch panel — toggled by /watch. When active, the spinner
|
|
67
|
+
// area renders a compact agent-activity summary instead of the spinner.
|
|
68
|
+
watchPanelActive: false,
|
|
65
69
|
};
|
|
66
70
|
|
|
67
71
|
// Full session state for the current CLI process. Set by the REPL loop
|
|
@@ -73,6 +77,13 @@ export const session = {
|
|
|
73
77
|
startTime: Date.now(),
|
|
74
78
|
inputTokens: 0,
|
|
75
79
|
outputTokens: 0,
|
|
80
|
+
// Per-turn context indicator for the dock strip. Updated at each
|
|
81
|
+
// AGENT_COMPLETE from the backend's usage.total_input_tokens (the
|
|
82
|
+
// cumulative input the primary agent sent this turn across all its
|
|
83
|
+
// internal inferences). Rough proxy for "how big is the prompt right
|
|
84
|
+
// now" — approximates what the NEXT user turn will pay before any
|
|
85
|
+
// growth. Reset by /new.
|
|
86
|
+
lastTurnInputTokens: 0,
|
|
76
87
|
toolCalls: 0, // primary-agent tool calls in the current turn
|
|
77
88
|
subAgentToolCalls: 0,// forwarded internal sub-agent tool calls in the current turn
|
|
78
89
|
totalToolCalls: 0, // across all turns
|
|
@@ -85,6 +96,7 @@ export const session = {
|
|
|
85
96
|
user: null, // { github_username, email, role }
|
|
86
97
|
model: null, // from backend user profile
|
|
87
98
|
modelLimits: {}, // role -> {model, context_length, max_output, source}
|
|
99
|
+
subAgentModels: {}, // role -> model from backend session_info
|
|
88
100
|
blockedOps: 0, // safety guardrail blocks
|
|
89
101
|
delegations: [], // agent delegation events: { from, to, time }
|
|
90
102
|
phases: [], // phase history: { name, time }
|
package/src/terminal/repl.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Bahulam REPL — Full Claude-like terminal UX.
|
|
3
3
|
*
|
|
4
4
|
* Pure ANSI. No React. No Ink. No flickering.
|
|
5
5
|
*
|
|
@@ -67,6 +67,15 @@ import { createAgentFile, isVsCodeTerminal, listLocalAgents, openAgentFile, sync
|
|
|
67
67
|
import { SessionManager } from '../core/session-manager.mjs';
|
|
68
68
|
import { parseArgs } from '../config/cli-args.mjs';
|
|
69
69
|
import { pickModelOverridesForm } from './repl-model-form.mjs';
|
|
70
|
+
import {
|
|
71
|
+
MODEL_CATEGORY_ORDER,
|
|
72
|
+
formatCategoryBadge,
|
|
73
|
+
formatCategoryHeading,
|
|
74
|
+
modelCategory,
|
|
75
|
+
modelDisplayCategory,
|
|
76
|
+
modelMatchesCatalogFilter,
|
|
77
|
+
normalizeCatalogCategory,
|
|
78
|
+
} from './model-catalog-display.mjs';
|
|
70
79
|
import { loadEffectivePolicy, formatPolicySourceRows } from '../core/policy-resolver.mjs';
|
|
71
80
|
import { loadProjectContext } from '../core/project-context-loader.mjs';
|
|
72
81
|
import { buildContextEnvelope } from '../core/context-envelope.mjs';
|
|
@@ -92,6 +101,7 @@ import { exploreCategory, exploreCollapseEnabled, isExploreTool } from './repl-e
|
|
|
92
101
|
import { session, orbitRef, sessionMgrRef, runtime } from './repl-state.mjs';
|
|
93
102
|
import { safeCwd } from './repl-utils.mjs';
|
|
94
103
|
import * as rqueue from '../ui/render-queue.mjs';
|
|
104
|
+
import { watchState } from './watch-state.mjs';
|
|
95
105
|
import {
|
|
96
106
|
appendContent,
|
|
97
107
|
bumpSpinnerProgress,
|
|
@@ -291,14 +301,15 @@ function renderKeyboardHelp() {
|
|
|
291
301
|
|
|
292
302
|
const MODEL_ROLE_ALIASES = new Map([
|
|
293
303
|
['reasoning', 'reasoning'],
|
|
294
|
-
['main', 'reasoning'],
|
|
295
304
|
['coder', 'reasoning'],
|
|
296
305
|
['coding', 'reasoning'],
|
|
297
306
|
['smart', 'reasoning'],
|
|
298
307
|
['fast', 'fast'],
|
|
299
308
|
['explorer', 'fast'],
|
|
300
|
-
['
|
|
301
|
-
['
|
|
309
|
+
['main', 'reasoning'],
|
|
310
|
+
['orchestrator', 'plan'],
|
|
311
|
+
['planner', 'plan'],
|
|
312
|
+
['planning', 'plan'],
|
|
302
313
|
['local', 'local'],
|
|
303
314
|
['worker', 'worker'],
|
|
304
315
|
['explore', 'explore'],
|
|
@@ -306,25 +317,52 @@ const MODEL_ROLE_ALIASES = new Map([
|
|
|
306
317
|
['verify', 'verify'],
|
|
307
318
|
['debug', 'debug'],
|
|
308
319
|
['refactor', 'refactor'],
|
|
320
|
+
['vision', 'image_analysis'],
|
|
321
|
+
['image', 'image_analysis'],
|
|
322
|
+
['image-analysis', 'image_analysis'],
|
|
323
|
+
['image_analysis', 'image_analysis'],
|
|
324
|
+
['analyze-image', 'image_analysis'],
|
|
325
|
+
['analyze_image', 'image_analysis'],
|
|
326
|
+
['image-generation', 'image_generation'],
|
|
327
|
+
['image_generation', 'image_generation'],
|
|
328
|
+
['image-gen', 'image_generation'],
|
|
329
|
+
['generate-image', 'image_generation'],
|
|
330
|
+
['generate_image', 'image_generation'],
|
|
309
331
|
]);
|
|
310
332
|
|
|
311
333
|
const MODEL_ROLE_LABELS = {
|
|
312
334
|
reasoning: 'coding',
|
|
313
335
|
fast: 'fast',
|
|
314
|
-
orchestrator: '
|
|
336
|
+
orchestrator: 'planning',
|
|
315
337
|
local: 'local',
|
|
316
338
|
worker: 'worker',
|
|
317
339
|
explore: 'explore',
|
|
318
|
-
plan: '
|
|
340
|
+
plan: 'planning',
|
|
319
341
|
verify: 'verify',
|
|
320
342
|
debug: 'debug',
|
|
321
343
|
refactor: 'refactor',
|
|
344
|
+
image_analysis: 'image analysis',
|
|
345
|
+
image_generation: 'image generation',
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const MODEL_ROLE_DESCRIPTIONS = {
|
|
349
|
+
reasoning: 'coding model',
|
|
350
|
+
fast: 'fast low-cost work',
|
|
351
|
+
orchestrator: 'planning model (legacy role name)',
|
|
352
|
+
local: 'local fallback role',
|
|
353
|
+
worker: 'background worker role',
|
|
354
|
+
explore: 'read/search sub-agent',
|
|
355
|
+
plan: 'planning sub-agent',
|
|
356
|
+
verify: 'verification sub-agent',
|
|
357
|
+
debug: 'debugging sub-agent',
|
|
358
|
+
refactor: 'refactor sub-agent',
|
|
359
|
+
image_analysis: 'inspect screenshots and uploads',
|
|
360
|
+
image_generation: 'create visual assets',
|
|
322
361
|
};
|
|
323
362
|
|
|
324
363
|
const MODEL_ROLE_ORDER = [
|
|
325
364
|
'reasoning',
|
|
326
365
|
'fast',
|
|
327
|
-
'orchestrator',
|
|
328
366
|
'local',
|
|
329
367
|
'worker',
|
|
330
368
|
'explore',
|
|
@@ -332,6 +370,8 @@ const MODEL_ROLE_ORDER = [
|
|
|
332
370
|
'verify',
|
|
333
371
|
'debug',
|
|
334
372
|
'refactor',
|
|
373
|
+
'image_analysis',
|
|
374
|
+
'image_generation',
|
|
335
375
|
];
|
|
336
376
|
|
|
337
377
|
function normalizeModelRole(value) {
|
|
@@ -349,7 +389,8 @@ function printModelCommandUsage() {
|
|
|
349
389
|
process.stderr.write(` /model <model> set coding model directly\n`);
|
|
350
390
|
process.stderr.write(` /model <role> <model>\n`);
|
|
351
391
|
process.stderr.write(` /model ${NAMED_MODEL_MODES_LIST.join('|')}\n`);
|
|
352
|
-
process.stderr.write(` /model list
|
|
392
|
+
process.stderr.write(` /model list [text|image|image-analysis|image-generation|multimodal]\n`);
|
|
393
|
+
process.stderr.write(` /model status · refresh · clear [role]\n`);
|
|
353
394
|
process.stderr.write(` ${c.gray('Roles:')} ${MODEL_ROLE_ORDER.map(role => MODEL_ROLE_LABELS[role]).join(', ')}\n`);
|
|
354
395
|
}
|
|
355
396
|
|
|
@@ -461,6 +502,15 @@ function modelCreditBadge(model) {
|
|
|
461
502
|
return `~${credits < 10 ? credits.toFixed(1) : String(Math.round(credits))} cr/M in`;
|
|
462
503
|
}
|
|
463
504
|
|
|
505
|
+
function printModelCatalogRows(rows) {
|
|
506
|
+
for (const m of rows) {
|
|
507
|
+
const badge = modelCreditBadge(m);
|
|
508
|
+
const category = formatCategoryBadge(modelDisplayCategory(m));
|
|
509
|
+
const price = badge ? c.dim(badge.padEnd(16)) : ''.padEnd(16);
|
|
510
|
+
process.stderr.write(` ${c.brand(String(m.id || '').padEnd(38))} ${price} ${category}\n`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
464
514
|
async function warnIfNotCurated(model, ctx) {
|
|
465
515
|
if (isByokModelRoute()) return;
|
|
466
516
|
const catalog = await fetchModelCatalog(ctx);
|
|
@@ -470,28 +520,41 @@ async function warnIfNotCurated(model, ctx) {
|
|
|
470
520
|
process.stderr.write(` ${c.yellow('!')} ${c.dim(`${model} is not in the platform catalog — the backend may reject it. See /model list.`)}\n`);
|
|
471
521
|
} else if (row.harness_validated === false) {
|
|
472
522
|
process.stderr.write(` ${c.yellow('!')} ${c.dim(`${model} is not harness-validated for the platform route — cost/quality untuned. See /model list.`)}\n`);
|
|
523
|
+
} else if (modelCategory(row) !== 'text') {
|
|
524
|
+
process.stderr.write(` ${c.yellow('!')} ${c.dim(`${model} is categorized as ${modelCategory(row)}; it is listed for media tooling, not coding-model overrides.`)}\n`);
|
|
473
525
|
}
|
|
474
526
|
}
|
|
475
527
|
|
|
476
|
-
async function printModelCatalog(ctx) {
|
|
528
|
+
async function printModelCatalog(ctx, filterCategory = null) {
|
|
477
529
|
const catalog = await fetchModelCatalog(ctx);
|
|
478
530
|
if (!catalog) {
|
|
479
531
|
process.stderr.write(` ${c.yellow('!')} ${c.dim(`Model catalog unavailable — ${_modelCatalogError || 'unknown error'}.`)}\n`);
|
|
480
532
|
return;
|
|
481
533
|
}
|
|
482
534
|
const curated = catalog.filter(m => m?.harness_validated);
|
|
535
|
+
const filter = filterCategory ? String(filterCategory).trim().toLowerCase() : null;
|
|
536
|
+
const visible = filter
|
|
537
|
+
? curated.filter(m => modelMatchesCatalogFilter(m, filter))
|
|
538
|
+
: curated;
|
|
539
|
+
|
|
483
540
|
process.stderr.write(`\n ${c.bold('Platform catalog')} ${c.dim('(harness-validated, credit-priced)')}\n`);
|
|
484
541
|
process.stderr.write(` ${c.gray('─'.repeat(64))}\n`);
|
|
485
|
-
if (!
|
|
542
|
+
if (!visible.length) {
|
|
486
543
|
process.stderr.write(` ${c.dim('(none published yet)')}\n`);
|
|
487
544
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
545
|
+
|
|
546
|
+
const categories = [...new Set(visible.map(modelDisplayCategory))].sort((a, b) => {
|
|
547
|
+
return (MODEL_CATEGORY_ORDER.indexOf(a) === -1 ? 999 : MODEL_CATEGORY_ORDER.indexOf(a)) -
|
|
548
|
+
(MODEL_CATEGORY_ORDER.indexOf(b) === -1 ? 999 : MODEL_CATEGORY_ORDER.indexOf(b)) ||
|
|
549
|
+
a.localeCompare(b);
|
|
550
|
+
});
|
|
551
|
+
for (const category of categories) {
|
|
552
|
+
const rows = visible.filter(m => modelDisplayCategory(m) === category);
|
|
553
|
+
if (!rows.length) continue;
|
|
554
|
+
process.stderr.write(`\n ${formatCategoryHeading(category, rows.length)}\n`);
|
|
555
|
+
printModelCatalogRows(rows);
|
|
494
556
|
}
|
|
557
|
+
|
|
495
558
|
const rest = catalog.length - curated.length;
|
|
496
559
|
if (rest > 0) {
|
|
497
560
|
process.stderr.write(` ${c.dim(`+${rest} more models available on the BYOK route (--route byok, own API key)`)}\n`);
|
|
@@ -552,10 +615,12 @@ function printModelStatus() {
|
|
|
552
615
|
}
|
|
553
616
|
|
|
554
617
|
const limits = session.modelLimits || {};
|
|
618
|
+
const subAgentModels = session.subAgentModels || {};
|
|
619
|
+
const planningModel = subAgentModels.plan || limits.planning?.model || limits.orchestrator?.model;
|
|
555
620
|
const rows = [
|
|
556
|
-
['
|
|
557
|
-
['
|
|
558
|
-
['
|
|
621
|
+
['coding', limits.coder?.model],
|
|
622
|
+
['explore', subAgentModels.explore || limits.explorer?.model],
|
|
623
|
+
['planning', planningModel],
|
|
559
624
|
].filter(([, model]) => model);
|
|
560
625
|
if (rows.length) {
|
|
561
626
|
process.stderr.write(`\n ${c.bold('Backend roles')}\n`);
|
|
@@ -577,20 +642,28 @@ function printModelStatus() {
|
|
|
577
642
|
printModelCommandUsage();
|
|
578
643
|
}
|
|
579
644
|
|
|
580
|
-
const MODEL_FORM_ROLES = ['reasoning', 'fast', '
|
|
645
|
+
const MODEL_FORM_ROLES = ['reasoning', 'fast', 'explore', 'plan'];
|
|
646
|
+
const MODEL_MEDIA_FORM_ROLES = ['image_analysis', 'image_generation'];
|
|
581
647
|
|
|
582
648
|
async function openModelForm(ctx) {
|
|
583
649
|
const limits = session.modelLimits || {};
|
|
650
|
+
const subAgentModels = session.subAgentModels || {};
|
|
651
|
+
const planningModel = subAgentModels.plan || limits.planning?.model || limits.orchestrator?.model;
|
|
584
652
|
const defaultsByRole = {
|
|
585
653
|
reasoning: limits.coder?.model,
|
|
586
654
|
fast: limits.explorer?.model,
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
655
|
+
explore: subAgentModels.explore || limits.explorer?.model,
|
|
656
|
+
plan: planningModel,
|
|
657
|
+
image_analysis: 'backend tier default',
|
|
658
|
+
image_generation: 'backend tier default',
|
|
590
659
|
};
|
|
591
|
-
const roles = MODEL_FORM_ROLES.map(role => ({
|
|
660
|
+
const roles = [...MODEL_FORM_ROLES, ...MODEL_MEDIA_FORM_ROLES].map(role => ({
|
|
592
661
|
role,
|
|
593
662
|
label: MODEL_ROLE_LABELS[role] || role,
|
|
663
|
+
description: MODEL_ROLE_DESCRIPTIONS[role] || '',
|
|
664
|
+
optionGroup: role === 'image_analysis'
|
|
665
|
+
? 'image_analysis'
|
|
666
|
+
: (role === 'image_generation' ? 'image_generation' : 'text'),
|
|
594
667
|
current: (session.modelOverrides || {})[role] || null,
|
|
595
668
|
defaultLabel: defaultsByRole[role] || null,
|
|
596
669
|
}));
|
|
@@ -616,7 +689,7 @@ async function openModelForm(ctx) {
|
|
|
616
689
|
// Merge: form rows replace their roles; overrides on roles the form
|
|
617
690
|
// doesn't show (verify/debug/…) are left untouched.
|
|
618
691
|
const next = { ...(session.modelOverrides || {}) };
|
|
619
|
-
for (const role of MODEL_FORM_ROLES) delete next[role];
|
|
692
|
+
for (const role of [...MODEL_FORM_ROLES, ...MODEL_MEDIA_FORM_ROLES]) delete next[role];
|
|
620
693
|
Object.assign(next, result.overrides);
|
|
621
694
|
session.modelOverrides = next;
|
|
622
695
|
if (result.overrides.reasoning) session.model = result.overrides.reasoning;
|
|
@@ -653,7 +726,14 @@ async function handleModelCommand(rest = '', ctx) {
|
|
|
653
726
|
}
|
|
654
727
|
|
|
655
728
|
if (parts[0] === 'list' || parts[0] === 'catalog') {
|
|
656
|
-
|
|
729
|
+
const category = parts[1]?.toLowerCase();
|
|
730
|
+
const normalizedCategory = normalizeCatalogCategory(category);
|
|
731
|
+
if (normalizedCategory && !['text', 'image', 'image_analysis', 'image_generation', 'multimodal', 'embedding', 'audio', 'video', 'other'].includes(normalizedCategory)) {
|
|
732
|
+
process.stderr.write(` ${c.yellow('!')} ${c.dim(`Unknown model category: ${category}`)}\n`);
|
|
733
|
+
printModelCommandUsage();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
await printModelCatalog(ctx, normalizedCategory || null);
|
|
657
737
|
return;
|
|
658
738
|
}
|
|
659
739
|
|
|
@@ -1248,12 +1328,17 @@ function buildContextStrip() {
|
|
|
1248
1328
|
// Cache hit % lives under /cache — keep the always-on strip focused on
|
|
1249
1329
|
// volume + elapsed. Historical rate calc was double-counting the cache tokens
|
|
1250
1330
|
// vs OpenRouter's convention (see computeCacheTotals) which was misleading.
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1331
|
+
const parts = [];
|
|
1332
|
+
// ctx: last turn's cumulative input tokens — approximates the CURRENT
|
|
1333
|
+
// prompt size. Only shown once we've completed at least one turn (so
|
|
1334
|
+
// the initial banner doesn't read '0 ctx'). This is the number the
|
|
1335
|
+
// 160k summarize threshold compares against on the next turn.
|
|
1336
|
+
if (session.lastTurnInputTokens > 0) {
|
|
1337
|
+
parts.push(c.dim(`ctx ${formatTokens(session.lastTurnInputTokens)}`));
|
|
1338
|
+
}
|
|
1339
|
+
parts.push(c.dim(`${formatTokens(totalTokens)} tok`));
|
|
1340
|
+
parts.push(c.dim(elapsed));
|
|
1341
|
+
return parts.join(c.dim(' · '));
|
|
1257
1342
|
}
|
|
1258
1343
|
|
|
1259
1344
|
// ── Dock meta line (model · cwd ⎇ branch · turn N) ─────────────────────
|
|
@@ -1784,6 +1869,9 @@ function renderEvent(event) {
|
|
|
1784
1869
|
|
|
1785
1870
|
case 'tool_call':
|
|
1786
1871
|
case 'tool_request': {
|
|
1872
|
+
if (watchState.active) {
|
|
1873
|
+
watchState.addEntry('tool', { label: data?.tool, detail: data?.args?.file_path || data?.args?.path || data?.args?.pattern || data?.args?.query || '' });
|
|
1874
|
+
}
|
|
1787
1875
|
const isInternal = Boolean(data?.internal || data?.sub_agent);
|
|
1788
1876
|
if (isInternal) {
|
|
1789
1877
|
session.subAgentToolCalls++;
|
|
@@ -1841,6 +1929,10 @@ function renderEvent(event) {
|
|
|
1841
1929
|
|
|
1842
1930
|
case 'tool_result':
|
|
1843
1931
|
case 'tool_done': {
|
|
1932
|
+
if (watchState.active) {
|
|
1933
|
+
const success = data?.success !== false;
|
|
1934
|
+
watchState.addEntry('done', { label: data?.tool, detail: success ? '✓' : '✗' });
|
|
1935
|
+
}
|
|
1844
1936
|
if (shouldFoldSubAgentTool(data)) {
|
|
1845
1937
|
foldSubAgentToolResult(data);
|
|
1846
1938
|
break;
|
|
@@ -1936,6 +2028,9 @@ function renderEvent(event) {
|
|
|
1936
2028
|
}
|
|
1937
2029
|
|
|
1938
2030
|
case 'delegation': {
|
|
2031
|
+
if (watchState.active) {
|
|
2032
|
+
watchState.addEntry('handoff', { label: `${data?.from || '?'} → ${data?.to || '?'}` });
|
|
2033
|
+
}
|
|
1939
2034
|
stopSpinner();
|
|
1940
2035
|
clearPendingHead();
|
|
1941
2036
|
const from = data?.from || '';
|
|
@@ -1954,6 +2049,9 @@ function renderEvent(event) {
|
|
|
1954
2049
|
// ── Sub-Agent Activity ──
|
|
1955
2050
|
|
|
1956
2051
|
case 'sub_agent_start': {
|
|
2052
|
+
if (watchState.active) {
|
|
2053
|
+
watchState.addEntry('spawn', { type: data?.type, label: (data?.query || '').slice(0, 60) });
|
|
2054
|
+
}
|
|
1957
2055
|
stopSpinner();
|
|
1958
2056
|
clearPendingHead();
|
|
1959
2057
|
flushFoldedSubAgentTools();
|
|
@@ -1992,7 +2090,7 @@ function renderEvent(event) {
|
|
|
1992
2090
|
// is visible by emitting a small dim transcript line too, dedup'd
|
|
1993
2091
|
// per (agentType, tool). Queue-active mode keeps its clean spinner
|
|
1994
2092
|
// + window; the fallback line is only for the "otherwise blind"
|
|
1995
|
-
// case that surfaced in
|
|
2093
|
+
// case that surfaced in earlier local terminal reports.
|
|
1996
2094
|
if (!rqueue.isActive()) {
|
|
1997
2095
|
const label = data?.label || '';
|
|
1998
2096
|
const hint = label ? ` · ${label}` : '';
|
|
@@ -2014,6 +2112,12 @@ function renderEvent(event) {
|
|
|
2014
2112
|
}
|
|
2015
2113
|
|
|
2016
2114
|
case 'sub_agent_complete': {
|
|
2115
|
+
if (watchState.active) {
|
|
2116
|
+
const agentType = data?.type || 'sub-agent';
|
|
2117
|
+
const toolCalls = data?.tool_calls || 0;
|
|
2118
|
+
const durationS = data?.duration_s || 0;
|
|
2119
|
+
watchState.addEntry('done', { type: agentType, detail: `${toolCalls} tools · ${durationS.toFixed(1)}s`, status: 'done' });
|
|
2120
|
+
}
|
|
2017
2121
|
setSubAgentWindowActive(false);
|
|
2018
2122
|
stopSpinner();
|
|
2019
2123
|
clearPendingHead();
|
|
@@ -2215,6 +2319,9 @@ function renderEvent(event) {
|
|
|
2215
2319
|
if (data?.model_limits && typeof data.model_limits === 'object') {
|
|
2216
2320
|
session.modelLimits = data.model_limits;
|
|
2217
2321
|
}
|
|
2322
|
+
if (data?.sub_agent_models && typeof data.sub_agent_models === 'object') {
|
|
2323
|
+
session.subAgentModels = data.sub_agent_models;
|
|
2324
|
+
}
|
|
2218
2325
|
if (data?.user) session.user = { ...session.user, ...data.user };
|
|
2219
2326
|
// BYOK users pay their model provider directly; the platform does not
|
|
2220
2327
|
// charge them credits. Hide cost + credits when this flag is set.
|
|
@@ -2309,6 +2416,10 @@ function renderEvent(event) {
|
|
|
2309
2416
|
const out = usage.total_output_tokens || usage.output_tokens || 0;
|
|
2310
2417
|
session.inputTokens += inp;
|
|
2311
2418
|
session.outputTokens += out;
|
|
2419
|
+
// Remember THIS turn's cumulative input for the dock strip's ctx
|
|
2420
|
+
// indicator — approximates how big the prompt currently is (vs
|
|
2421
|
+
// session.inputTokens which sums every turn since launch).
|
|
2422
|
+
session.lastTurnInputTokens = inp;
|
|
2312
2423
|
|
|
2313
2424
|
// Model-aware cost calculation
|
|
2314
2425
|
const costResult = calculateCost(usage);
|
|
@@ -2827,6 +2938,19 @@ async function handleCommand(input, ctx) {
|
|
|
2827
2938
|
return;
|
|
2828
2939
|
}
|
|
2829
2940
|
|
|
2941
|
+
case '/watch': {
|
|
2942
|
+
const nowActive = !watchState.active;
|
|
2943
|
+
watchState.active = nowActive;
|
|
2944
|
+
runtime.watchPanelActive = nowActive;
|
|
2945
|
+
watchState.clear();
|
|
2946
|
+
process.stderr.write(` ${c.green(nowActive ? '✓' : '✗')} ${c.dim(`Watch panel ${nowActive ? 'on' : 'off'}`)}\n`);
|
|
2947
|
+
if (!nowActive) {
|
|
2948
|
+
// Clear any watch panel status lines
|
|
2949
|
+
if (rqueue.isActive()) rqueue.clearStatus();
|
|
2950
|
+
else if (isInputDockMounted()) { clearPinnedStatus(); moveToContent(); }
|
|
2951
|
+
}
|
|
2952
|
+
return;
|
|
2953
|
+
}
|
|
2830
2954
|
case '/login':
|
|
2831
2955
|
process.stderr.write(`${c.brand('Starting login flow...')}\n`);
|
|
2832
2956
|
telemetry.track('login_shown', { method: 'repl_command' });
|
|
@@ -3328,40 +3452,48 @@ async function handleCommand(input, ctx) {
|
|
|
3328
3452
|
return;
|
|
3329
3453
|
}
|
|
3330
3454
|
|
|
3331
|
-
case '/revoke': {
|
|
3332
|
-
const wasActive = ctx.approval.revoke();
|
|
3333
|
-
if (wasActive) {
|
|
3334
|
-
process.stderr.write(` ${c.green('✓')} ${c.dim('Auto-approvals revoked. All tool calls will prompt again.')}\n`);
|
|
3335
|
-
} else {
|
|
3336
|
-
process.stderr.write(` ${c.gray('No auto-approvals were active.')}\n`);
|
|
3337
|
-
}
|
|
3338
|
-
return;
|
|
3339
|
-
}
|
|
3340
3455
|
|
|
3341
3456
|
case '/auto': {
|
|
3342
|
-
// Session autopilot for long-running jobs
|
|
3343
|
-
// writes/shell
|
|
3344
|
-
// force-push
|
|
3345
|
-
//
|
|
3346
|
-
//
|
|
3347
|
-
//
|
|
3457
|
+
// Session autopilot for long-running jobs. Three levels:
|
|
3458
|
+
// /auto (or /auto on) routine writes/shell auto-approve;
|
|
3459
|
+
// dangerous tiers (rm/force-push/
|
|
3460
|
+
// command substitution/protected
|
|
3461
|
+
// files) still prompt.
|
|
3462
|
+
// /auto full approves EVERYTHING short of the
|
|
3463
|
+
// unbypassable hard-safety blocks —
|
|
3464
|
+
// mid-session equivalent of the
|
|
3465
|
+
// --dangerously-skip-permissions
|
|
3466
|
+
// launch flag. Long autonomous runs
|
|
3467
|
+
// won't stall on a dangerous tier.
|
|
3468
|
+
// /auto off all approvals prompt again.
|
|
3348
3469
|
const sub = (rest || '').trim().toLowerCase();
|
|
3349
3470
|
if (sub === 'off') {
|
|
3350
3471
|
ctx.approval.approveAll = false;
|
|
3472
|
+
ctx.approval.autoApprove = false;
|
|
3351
3473
|
process.stderr.write(` ${c.green('✓')} ${c.dim('Auto mode off — approvals prompt again.')}\n`);
|
|
3352
3474
|
return;
|
|
3353
3475
|
}
|
|
3476
|
+
if (sub === 'full' || sub === 'dangerous' || sub === 'yolo') {
|
|
3477
|
+
ctx.approval.autoApprove = true;
|
|
3478
|
+
ctx.approval.approveAll = true;
|
|
3479
|
+
process.stderr.write(` ${c.yellow('⚠')} ${c.bold('Auto FULL')} ${c.dim('— EVERY tool call auto-approves this session.')}\n`);
|
|
3480
|
+
process.stderr.write(` ${c.dim('Includes dangerous shell (rm/force-push/substitution) and protected files.')}\n`);
|
|
3481
|
+
process.stderr.write(` ${c.dim('Hard safety blocks are still enforced by the CLI safety layer.')}\n`);
|
|
3482
|
+
process.stderr.write(` ${c.dim('Disable with /auto off · this is the runtime equivalent of --dangerously-skip-permissions.')}\n`);
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3354
3485
|
if (sub === '' || sub === 'on') {
|
|
3486
|
+
ctx.approval.autoApprove = false; // clear any prior /auto full
|
|
3355
3487
|
ctx.approval.approveAll = true;
|
|
3356
3488
|
process.stderr.write(` ${c.green('✓')} ${c.bold('Auto mode on')} ${c.dim('— routine tool calls auto-approve this session.')}\n`);
|
|
3357
3489
|
process.stderr.write(` ${c.dim('Still prompts: dangerous shell (rm/force-push/substitution), protected files.')}\n`);
|
|
3358
3490
|
process.stderr.write(` ${c.dim('Hard safety blocks stay enforced. Disable with /auto off · inspect with /approvals.')}\n`);
|
|
3359
|
-
process.stderr.write(` ${c.dim('
|
|
3491
|
+
process.stderr.write(` ${c.dim('Need full autopilot mid-run? /auto full — same effect as --dangerously-skip-permissions.')}\n`);
|
|
3360
3492
|
return;
|
|
3361
3493
|
}
|
|
3362
3494
|
// status / anything else → show current mode
|
|
3363
3495
|
process.stderr.write(` ${c.dim('Approval mode:')} ${ctx.approval.getModeLabel()}\n`);
|
|
3364
|
-
process.stderr.write(` ${c.dim('Usage: /auto [on|off|status]')}\n`);
|
|
3496
|
+
process.stderr.write(` ${c.dim('Usage: /auto [on|off|full|status]')}\n`);
|
|
3365
3497
|
return;
|
|
3366
3498
|
}
|
|
3367
3499
|
|
|
@@ -3632,7 +3764,7 @@ async function fetchUser(ctx) {
|
|
|
3632
3764
|
});
|
|
3633
3765
|
if (resp.ok) {
|
|
3634
3766
|
session.user = await resp.json();
|
|
3635
|
-
session.model = session.user.default_reasoning_model ||
|
|
3767
|
+
session.model = session.user.default_reasoning_model || null;
|
|
3636
3768
|
}
|
|
3637
3769
|
} catch {}
|
|
3638
3770
|
}
|
|
@@ -3696,7 +3828,7 @@ export async function startTerminalRepl() {
|
|
|
3696
3828
|
});
|
|
3697
3829
|
}
|
|
3698
3830
|
|
|
3699
|
-
let toolExecutor =
|
|
3831
|
+
let toolExecutor = null;
|
|
3700
3832
|
const skipPerms = cliArgs.skipPermissions;
|
|
3701
3833
|
let approval = new ApprovalManager({ autoApprove: skipPerms, cwd: safeCwd(), policy: effectivePolicy.policy });
|
|
3702
3834
|
|
|
@@ -3710,7 +3842,7 @@ export async function startTerminalRepl() {
|
|
|
3710
3842
|
// Persistent stream client — session_id captured from backend on first turn
|
|
3711
3843
|
let streamClient = null;
|
|
3712
3844
|
|
|
3713
|
-
const ctx = { auth, toolExecutor, approval, jsonlWriter, sessionMgr, checkpoints, effectivePolicy, latestProjectContext, latestEnvelope, pendingVisionPaths: [] };
|
|
3845
|
+
const ctx = { auth, toolExecutor: null, approval, jsonlWriter, sessionMgr, checkpoints, effectivePolicy, latestProjectContext, latestEnvelope, pendingVisionPaths: [] };
|
|
3714
3846
|
|
|
3715
3847
|
let startupOutputRow = 1;
|
|
3716
3848
|
let startupOutputCol = 1;
|
|
@@ -3773,6 +3905,7 @@ export async function startTerminalRepl() {
|
|
|
3773
3905
|
user: session.user,
|
|
3774
3906
|
model: session.model,
|
|
3775
3907
|
modelLimits: session.modelLimits,
|
|
3908
|
+
subAgentModels: session.subAgentModels,
|
|
3776
3909
|
modelOverrides: session.modelOverrides,
|
|
3777
3910
|
modelMode: session.modelMode,
|
|
3778
3911
|
routePreference: session.routePreference,
|
|
@@ -3805,6 +3938,7 @@ export async function startTerminalRepl() {
|
|
|
3805
3938
|
startTime: Date.now(),
|
|
3806
3939
|
inputTokens: 0,
|
|
3807
3940
|
outputTokens: 0,
|
|
3941
|
+
lastTurnInputTokens: 0,
|
|
3808
3942
|
toolCalls: 0,
|
|
3809
3943
|
subAgentToolCalls: 0,
|
|
3810
3944
|
totalToolCalls: 0,
|
|
@@ -3817,6 +3951,7 @@ export async function startTerminalRepl() {
|
|
|
3817
3951
|
user: preserved.user,
|
|
3818
3952
|
model: preserved.model,
|
|
3819
3953
|
modelLimits: preserved.modelLimits,
|
|
3954
|
+
subAgentModels: preserved.subAgentModels,
|
|
3820
3955
|
modelOverrides: preserved.modelOverrides,
|
|
3821
3956
|
modelMode: preserved.modelMode,
|
|
3822
3957
|
routePreference: preserved.routePreference,
|
|
@@ -4095,6 +4230,11 @@ export async function startTerminalRepl() {
|
|
|
4095
4230
|
catch { /* preflight is best-effort */ }
|
|
4096
4231
|
}
|
|
4097
4232
|
|
|
4233
|
+
// Create the auto-indexing executor after the startup header is visible so
|
|
4234
|
+
// the indexing status appears in the user's line of sight.
|
|
4235
|
+
toolExecutor = makeToolExecutor({ showIndexStatus: true });
|
|
4236
|
+
ctx.toolExecutor = toolExecutor;
|
|
4237
|
+
|
|
4098
4238
|
// ── Initialization ──
|
|
4099
4239
|
process.stderr.write(` ${c.brand('⠋')} ${c.dim('Initializing...')}\r`);
|
|
4100
4240
|
await fetchUser(ctx);
|
|
@@ -4163,9 +4303,23 @@ export async function startTerminalRepl() {
|
|
|
4163
4303
|
initialContentRow: dockCursor.row,
|
|
4164
4304
|
initialContentCol: dockCursor.col,
|
|
4165
4305
|
});
|
|
4306
|
+
// 1 Hz live-tick for the elapsed clock in the dock's top strip.
|
|
4307
|
+
// Only fires when the user is idle (inputActive === true) — while the
|
|
4308
|
+
// agent is streaming content, skipping the tick avoids ANSI writes
|
|
4309
|
+
// interleaving with the stream. Cheap: one renderIdleDockInput per
|
|
4310
|
+
// second, only if mounted + idle. `unref()` so the timer never blocks
|
|
4311
|
+
// process exit.
|
|
4312
|
+
let _dockTickTimer = null;
|
|
4166
4313
|
if (inputDockActive) {
|
|
4167
4314
|
process.on('beforeExit', unmountInputDock);
|
|
4168
4315
|
process.on('exit', unmountInputDock);
|
|
4316
|
+
_dockTickTimer = setInterval(() => {
|
|
4317
|
+
if (!isInputDockMounted()) return;
|
|
4318
|
+
if (!inputActive) return;
|
|
4319
|
+
try { renderIdleDockInput(); } catch { /* one bad tick is not fatal */ }
|
|
4320
|
+
}, 1000);
|
|
4321
|
+
_dockTickTimer.unref?.();
|
|
4322
|
+
process.on('exit', () => { if (_dockTickTimer) clearInterval(_dockTickTimer); });
|
|
4169
4323
|
}
|
|
4170
4324
|
|
|
4171
4325
|
// ── Bracketed paste (DEC private mode 2004) ──────────────────────────────
|
|
@@ -4922,6 +5076,11 @@ export async function startTerminalRepl() {
|
|
|
4922
5076
|
runtime.lastRenderedBlock = 'user';
|
|
4923
5077
|
}
|
|
4924
5078
|
|
|
5079
|
+
function isExecutionSlashCommand(instruction) {
|
|
5080
|
+
const command = String(instruction || '').trim().split(/\s+/, 1)[0].toLowerCase();
|
|
5081
|
+
return command === '/watch' || command === '/auto';
|
|
5082
|
+
}
|
|
5083
|
+
|
|
4925
5084
|
async function submitExecutionInstruction() {
|
|
4926
5085
|
const instruction = executionInputBuffer.trim();
|
|
4927
5086
|
executionInputBuffer = '';
|
|
@@ -4940,6 +5099,25 @@ export async function startTerminalRepl() {
|
|
|
4940
5099
|
executionInputVisible = false;
|
|
4941
5100
|
return;
|
|
4942
5101
|
}
|
|
5102
|
+
if (isExecutionSlashCommand(instruction)) {
|
|
5103
|
+
if (isInputDockMounted()) {
|
|
5104
|
+
clearInputPrompt();
|
|
5105
|
+
moveToContent();
|
|
5106
|
+
} else if (executionInputVisible) {
|
|
5107
|
+
process.stderr.write('\n');
|
|
5108
|
+
}
|
|
5109
|
+
executionInputVisible = false;
|
|
5110
|
+
await handleCommand(instruction, ctx);
|
|
5111
|
+
if (isInputDockMounted()) {
|
|
5112
|
+
renderDockInput(executionInputPrefix(), '', {
|
|
5113
|
+
context: buildContextStrip(),
|
|
5114
|
+
meta: buildDockMeta(),
|
|
5115
|
+
tips: executionInputTips(),
|
|
5116
|
+
});
|
|
5117
|
+
moveToContent();
|
|
5118
|
+
}
|
|
5119
|
+
return;
|
|
5120
|
+
}
|
|
4943
5121
|
printExecutionInstruction(instruction);
|
|
4944
5122
|
if (isInputDockMounted()) {
|
|
4945
5123
|
renderDockInput(executionInputPrefix(), '', {
|