@bahulam/code 0.1.5 → 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 +112 -16
- package/src/core/tool-executor.mjs +123 -31
- 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 +255 -55
- 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/project-overview.mjs +5 -2
- 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
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';
|
|
@@ -74,6 +83,7 @@ import { buildResumeHistory, combineResumeSummaries, getRecentSessions, getSessi
|
|
|
74
83
|
import { decideResumeMode, projectedTokensForChoice, formatTokens as formatCtxTokens } from '../core/resume-mode.mjs';
|
|
75
84
|
import { appendTask, ensureTaskFiles, loadTaskBoard, moveTask, removeTask, taskCounts, TASK_FILES, updateTask } from '../core/tasks.mjs';
|
|
76
85
|
import { applyCompactSummary, localCompactSummary, parseCompactTailCount, prepareCompactHistory } from '../core/compact-history.mjs';
|
|
86
|
+
import { startSpinner as startInlineSpinner } from '../ui/spinner.mjs';
|
|
77
87
|
import {
|
|
78
88
|
appendVisionAnalysisToInstruction,
|
|
79
89
|
appendDocumentsToInstruction,
|
|
@@ -91,6 +101,7 @@ import { exploreCategory, exploreCollapseEnabled, isExploreTool } from './repl-e
|
|
|
91
101
|
import { session, orbitRef, sessionMgrRef, runtime } from './repl-state.mjs';
|
|
92
102
|
import { safeCwd } from './repl-utils.mjs';
|
|
93
103
|
import * as rqueue from '../ui/render-queue.mjs';
|
|
104
|
+
import { watchState } from './watch-state.mjs';
|
|
94
105
|
import {
|
|
95
106
|
appendContent,
|
|
96
107
|
bumpSpinnerProgress,
|
|
@@ -290,14 +301,15 @@ function renderKeyboardHelp() {
|
|
|
290
301
|
|
|
291
302
|
const MODEL_ROLE_ALIASES = new Map([
|
|
292
303
|
['reasoning', 'reasoning'],
|
|
293
|
-
['main', 'reasoning'],
|
|
294
304
|
['coder', 'reasoning'],
|
|
295
305
|
['coding', 'reasoning'],
|
|
296
306
|
['smart', 'reasoning'],
|
|
297
307
|
['fast', 'fast'],
|
|
298
308
|
['explorer', 'fast'],
|
|
299
|
-
['
|
|
300
|
-
['
|
|
309
|
+
['main', 'reasoning'],
|
|
310
|
+
['orchestrator', 'plan'],
|
|
311
|
+
['planner', 'plan'],
|
|
312
|
+
['planning', 'plan'],
|
|
301
313
|
['local', 'local'],
|
|
302
314
|
['worker', 'worker'],
|
|
303
315
|
['explore', 'explore'],
|
|
@@ -305,25 +317,52 @@ const MODEL_ROLE_ALIASES = new Map([
|
|
|
305
317
|
['verify', 'verify'],
|
|
306
318
|
['debug', 'debug'],
|
|
307
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'],
|
|
308
331
|
]);
|
|
309
332
|
|
|
310
333
|
const MODEL_ROLE_LABELS = {
|
|
311
334
|
reasoning: 'coding',
|
|
312
335
|
fast: 'fast',
|
|
313
|
-
orchestrator: '
|
|
336
|
+
orchestrator: 'planning',
|
|
314
337
|
local: 'local',
|
|
315
338
|
worker: 'worker',
|
|
316
339
|
explore: 'explore',
|
|
317
|
-
plan: '
|
|
340
|
+
plan: 'planning',
|
|
318
341
|
verify: 'verify',
|
|
319
342
|
debug: 'debug',
|
|
320
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',
|
|
321
361
|
};
|
|
322
362
|
|
|
323
363
|
const MODEL_ROLE_ORDER = [
|
|
324
364
|
'reasoning',
|
|
325
365
|
'fast',
|
|
326
|
-
'orchestrator',
|
|
327
366
|
'local',
|
|
328
367
|
'worker',
|
|
329
368
|
'explore',
|
|
@@ -331,6 +370,8 @@ const MODEL_ROLE_ORDER = [
|
|
|
331
370
|
'verify',
|
|
332
371
|
'debug',
|
|
333
372
|
'refactor',
|
|
373
|
+
'image_analysis',
|
|
374
|
+
'image_generation',
|
|
334
375
|
];
|
|
335
376
|
|
|
336
377
|
function normalizeModelRole(value) {
|
|
@@ -348,7 +389,8 @@ function printModelCommandUsage() {
|
|
|
348
389
|
process.stderr.write(` /model <model> set coding model directly\n`);
|
|
349
390
|
process.stderr.write(` /model <role> <model>\n`);
|
|
350
391
|
process.stderr.write(` /model ${NAMED_MODEL_MODES_LIST.join('|')}\n`);
|
|
351
|
-
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`);
|
|
352
394
|
process.stderr.write(` ${c.gray('Roles:')} ${MODEL_ROLE_ORDER.map(role => MODEL_ROLE_LABELS[role]).join(', ')}\n`);
|
|
353
395
|
}
|
|
354
396
|
|
|
@@ -460,6 +502,15 @@ function modelCreditBadge(model) {
|
|
|
460
502
|
return `~${credits < 10 ? credits.toFixed(1) : String(Math.round(credits))} cr/M in`;
|
|
461
503
|
}
|
|
462
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
|
+
|
|
463
514
|
async function warnIfNotCurated(model, ctx) {
|
|
464
515
|
if (isByokModelRoute()) return;
|
|
465
516
|
const catalog = await fetchModelCatalog(ctx);
|
|
@@ -469,28 +520,41 @@ async function warnIfNotCurated(model, ctx) {
|
|
|
469
520
|
process.stderr.write(` ${c.yellow('!')} ${c.dim(`${model} is not in the platform catalog — the backend may reject it. See /model list.`)}\n`);
|
|
470
521
|
} else if (row.harness_validated === false) {
|
|
471
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`);
|
|
472
525
|
}
|
|
473
526
|
}
|
|
474
527
|
|
|
475
|
-
async function printModelCatalog(ctx) {
|
|
528
|
+
async function printModelCatalog(ctx, filterCategory = null) {
|
|
476
529
|
const catalog = await fetchModelCatalog(ctx);
|
|
477
530
|
if (!catalog) {
|
|
478
531
|
process.stderr.write(` ${c.yellow('!')} ${c.dim(`Model catalog unavailable — ${_modelCatalogError || 'unknown error'}.`)}\n`);
|
|
479
532
|
return;
|
|
480
533
|
}
|
|
481
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
|
+
|
|
482
540
|
process.stderr.write(`\n ${c.bold('Platform catalog')} ${c.dim('(harness-validated, credit-priced)')}\n`);
|
|
483
541
|
process.stderr.write(` ${c.gray('─'.repeat(64))}\n`);
|
|
484
|
-
if (!
|
|
542
|
+
if (!visible.length) {
|
|
485
543
|
process.stderr.write(` ${c.dim('(none published yet)')}\n`);
|
|
486
544
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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);
|
|
493
556
|
}
|
|
557
|
+
|
|
494
558
|
const rest = catalog.length - curated.length;
|
|
495
559
|
if (rest > 0) {
|
|
496
560
|
process.stderr.write(` ${c.dim(`+${rest} more models available on the BYOK route (--route byok, own API key)`)}\n`);
|
|
@@ -551,10 +615,12 @@ function printModelStatus() {
|
|
|
551
615
|
}
|
|
552
616
|
|
|
553
617
|
const limits = session.modelLimits || {};
|
|
618
|
+
const subAgentModels = session.subAgentModels || {};
|
|
619
|
+
const planningModel = subAgentModels.plan || limits.planning?.model || limits.orchestrator?.model;
|
|
554
620
|
const rows = [
|
|
555
|
-
['
|
|
556
|
-
['
|
|
557
|
-
['
|
|
621
|
+
['coding', limits.coder?.model],
|
|
622
|
+
['explore', subAgentModels.explore || limits.explorer?.model],
|
|
623
|
+
['planning', planningModel],
|
|
558
624
|
].filter(([, model]) => model);
|
|
559
625
|
if (rows.length) {
|
|
560
626
|
process.stderr.write(`\n ${c.bold('Backend roles')}\n`);
|
|
@@ -576,20 +642,28 @@ function printModelStatus() {
|
|
|
576
642
|
printModelCommandUsage();
|
|
577
643
|
}
|
|
578
644
|
|
|
579
|
-
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'];
|
|
580
647
|
|
|
581
648
|
async function openModelForm(ctx) {
|
|
582
649
|
const limits = session.modelLimits || {};
|
|
650
|
+
const subAgentModels = session.subAgentModels || {};
|
|
651
|
+
const planningModel = subAgentModels.plan || limits.planning?.model || limits.orchestrator?.model;
|
|
583
652
|
const defaultsByRole = {
|
|
584
653
|
reasoning: limits.coder?.model,
|
|
585
654
|
fast: limits.explorer?.model,
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
655
|
+
explore: subAgentModels.explore || limits.explorer?.model,
|
|
656
|
+
plan: planningModel,
|
|
657
|
+
image_analysis: 'backend tier default',
|
|
658
|
+
image_generation: 'backend tier default',
|
|
589
659
|
};
|
|
590
|
-
const roles = MODEL_FORM_ROLES.map(role => ({
|
|
660
|
+
const roles = [...MODEL_FORM_ROLES, ...MODEL_MEDIA_FORM_ROLES].map(role => ({
|
|
591
661
|
role,
|
|
592
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'),
|
|
593
667
|
current: (session.modelOverrides || {})[role] || null,
|
|
594
668
|
defaultLabel: defaultsByRole[role] || null,
|
|
595
669
|
}));
|
|
@@ -615,7 +689,7 @@ async function openModelForm(ctx) {
|
|
|
615
689
|
// Merge: form rows replace their roles; overrides on roles the form
|
|
616
690
|
// doesn't show (verify/debug/…) are left untouched.
|
|
617
691
|
const next = { ...(session.modelOverrides || {}) };
|
|
618
|
-
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];
|
|
619
693
|
Object.assign(next, result.overrides);
|
|
620
694
|
session.modelOverrides = next;
|
|
621
695
|
if (result.overrides.reasoning) session.model = result.overrides.reasoning;
|
|
@@ -652,7 +726,14 @@ async function handleModelCommand(rest = '', ctx) {
|
|
|
652
726
|
}
|
|
653
727
|
|
|
654
728
|
if (parts[0] === 'list' || parts[0] === 'catalog') {
|
|
655
|
-
|
|
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);
|
|
656
737
|
return;
|
|
657
738
|
}
|
|
658
739
|
|
|
@@ -1247,12 +1328,17 @@ function buildContextStrip() {
|
|
|
1247
1328
|
// Cache hit % lives under /cache — keep the always-on strip focused on
|
|
1248
1329
|
// volume + elapsed. Historical rate calc was double-counting the cache tokens
|
|
1249
1330
|
// vs OpenRouter's convention (see computeCacheTotals) which was misleading.
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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(' · '));
|
|
1256
1342
|
}
|
|
1257
1343
|
|
|
1258
1344
|
// ── Dock meta line (model · cwd ⎇ branch · turn N) ─────────────────────
|
|
@@ -1783,6 +1869,9 @@ function renderEvent(event) {
|
|
|
1783
1869
|
|
|
1784
1870
|
case 'tool_call':
|
|
1785
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
|
+
}
|
|
1786
1875
|
const isInternal = Boolean(data?.internal || data?.sub_agent);
|
|
1787
1876
|
if (isInternal) {
|
|
1788
1877
|
session.subAgentToolCalls++;
|
|
@@ -1840,6 +1929,10 @@ function renderEvent(event) {
|
|
|
1840
1929
|
|
|
1841
1930
|
case 'tool_result':
|
|
1842
1931
|
case 'tool_done': {
|
|
1932
|
+
if (watchState.active) {
|
|
1933
|
+
const success = data?.success !== false;
|
|
1934
|
+
watchState.addEntry('done', { label: data?.tool, detail: success ? '✓' : '✗' });
|
|
1935
|
+
}
|
|
1843
1936
|
if (shouldFoldSubAgentTool(data)) {
|
|
1844
1937
|
foldSubAgentToolResult(data);
|
|
1845
1938
|
break;
|
|
@@ -1935,6 +2028,9 @@ function renderEvent(event) {
|
|
|
1935
2028
|
}
|
|
1936
2029
|
|
|
1937
2030
|
case 'delegation': {
|
|
2031
|
+
if (watchState.active) {
|
|
2032
|
+
watchState.addEntry('handoff', { label: `${data?.from || '?'} → ${data?.to || '?'}` });
|
|
2033
|
+
}
|
|
1938
2034
|
stopSpinner();
|
|
1939
2035
|
clearPendingHead();
|
|
1940
2036
|
const from = data?.from || '';
|
|
@@ -1953,6 +2049,9 @@ function renderEvent(event) {
|
|
|
1953
2049
|
// ── Sub-Agent Activity ──
|
|
1954
2050
|
|
|
1955
2051
|
case 'sub_agent_start': {
|
|
2052
|
+
if (watchState.active) {
|
|
2053
|
+
watchState.addEntry('spawn', { type: data?.type, label: (data?.query || '').slice(0, 60) });
|
|
2054
|
+
}
|
|
1956
2055
|
stopSpinner();
|
|
1957
2056
|
clearPendingHead();
|
|
1958
2057
|
flushFoldedSubAgentTools();
|
|
@@ -1991,7 +2090,7 @@ function renderEvent(event) {
|
|
|
1991
2090
|
// is visible by emitting a small dim transcript line too, dedup'd
|
|
1992
2091
|
// per (agentType, tool). Queue-active mode keeps its clean spinner
|
|
1993
2092
|
// + window; the fallback line is only for the "otherwise blind"
|
|
1994
|
-
// case that surfaced in
|
|
2093
|
+
// case that surfaced in earlier local terminal reports.
|
|
1995
2094
|
if (!rqueue.isActive()) {
|
|
1996
2095
|
const label = data?.label || '';
|
|
1997
2096
|
const hint = label ? ` · ${label}` : '';
|
|
@@ -2013,6 +2112,12 @@ function renderEvent(event) {
|
|
|
2013
2112
|
}
|
|
2014
2113
|
|
|
2015
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
|
+
}
|
|
2016
2121
|
setSubAgentWindowActive(false);
|
|
2017
2122
|
stopSpinner();
|
|
2018
2123
|
clearPendingHead();
|
|
@@ -2214,6 +2319,9 @@ function renderEvent(event) {
|
|
|
2214
2319
|
if (data?.model_limits && typeof data.model_limits === 'object') {
|
|
2215
2320
|
session.modelLimits = data.model_limits;
|
|
2216
2321
|
}
|
|
2322
|
+
if (data?.sub_agent_models && typeof data.sub_agent_models === 'object') {
|
|
2323
|
+
session.subAgentModels = data.sub_agent_models;
|
|
2324
|
+
}
|
|
2217
2325
|
if (data?.user) session.user = { ...session.user, ...data.user };
|
|
2218
2326
|
// BYOK users pay their model provider directly; the platform does not
|
|
2219
2327
|
// charge them credits. Hide cost + credits when this flag is set.
|
|
@@ -2308,6 +2416,10 @@ function renderEvent(event) {
|
|
|
2308
2416
|
const out = usage.total_output_tokens || usage.output_tokens || 0;
|
|
2309
2417
|
session.inputTokens += inp;
|
|
2310
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;
|
|
2311
2423
|
|
|
2312
2424
|
// Model-aware cost calculation
|
|
2313
2425
|
const costResult = calculateCost(usage);
|
|
@@ -2826,6 +2938,19 @@ async function handleCommand(input, ctx) {
|
|
|
2826
2938
|
return;
|
|
2827
2939
|
}
|
|
2828
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
|
+
}
|
|
2829
2954
|
case '/login':
|
|
2830
2955
|
process.stderr.write(`${c.brand('Starting login flow...')}\n`);
|
|
2831
2956
|
telemetry.track('login_shown', { method: 'repl_command' });
|
|
@@ -3327,40 +3452,48 @@ async function handleCommand(input, ctx) {
|
|
|
3327
3452
|
return;
|
|
3328
3453
|
}
|
|
3329
3454
|
|
|
3330
|
-
case '/revoke': {
|
|
3331
|
-
const wasActive = ctx.approval.revoke();
|
|
3332
|
-
if (wasActive) {
|
|
3333
|
-
process.stderr.write(` ${c.green('✓')} ${c.dim('Auto-approvals revoked. All tool calls will prompt again.')}\n`);
|
|
3334
|
-
} else {
|
|
3335
|
-
process.stderr.write(` ${c.gray('No auto-approvals were active.')}\n`);
|
|
3336
|
-
}
|
|
3337
|
-
return;
|
|
3338
|
-
}
|
|
3339
3455
|
|
|
3340
3456
|
case '/auto': {
|
|
3341
|
-
// Session autopilot for long-running jobs
|
|
3342
|
-
// writes/shell
|
|
3343
|
-
// force-push
|
|
3344
|
-
//
|
|
3345
|
-
//
|
|
3346
|
-
//
|
|
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.
|
|
3347
3469
|
const sub = (rest || '').trim().toLowerCase();
|
|
3348
3470
|
if (sub === 'off') {
|
|
3349
3471
|
ctx.approval.approveAll = false;
|
|
3472
|
+
ctx.approval.autoApprove = false;
|
|
3350
3473
|
process.stderr.write(` ${c.green('✓')} ${c.dim('Auto mode off — approvals prompt again.')}\n`);
|
|
3351
3474
|
return;
|
|
3352
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
|
+
}
|
|
3353
3485
|
if (sub === '' || sub === 'on') {
|
|
3486
|
+
ctx.approval.autoApprove = false; // clear any prior /auto full
|
|
3354
3487
|
ctx.approval.approveAll = true;
|
|
3355
3488
|
process.stderr.write(` ${c.green('✓')} ${c.bold('Auto mode on')} ${c.dim('— routine tool calls auto-approve this session.')}\n`);
|
|
3356
3489
|
process.stderr.write(` ${c.dim('Still prompts: dangerous shell (rm/force-push/substitution), protected files.')}\n`);
|
|
3357
3490
|
process.stderr.write(` ${c.dim('Hard safety blocks stay enforced. Disable with /auto off · inspect with /approvals.')}\n`);
|
|
3358
|
-
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`);
|
|
3359
3492
|
return;
|
|
3360
3493
|
}
|
|
3361
3494
|
// status / anything else → show current mode
|
|
3362
3495
|
process.stderr.write(` ${c.dim('Approval mode:')} ${ctx.approval.getModeLabel()}\n`);
|
|
3363
|
-
process.stderr.write(` ${c.dim('Usage: /auto [on|off|status]')}\n`);
|
|
3496
|
+
process.stderr.write(` ${c.dim('Usage: /auto [on|off|full|status]')}\n`);
|
|
3364
3497
|
return;
|
|
3365
3498
|
}
|
|
3366
3499
|
|
|
@@ -3631,7 +3764,7 @@ async function fetchUser(ctx) {
|
|
|
3631
3764
|
});
|
|
3632
3765
|
if (resp.ok) {
|
|
3633
3766
|
session.user = await resp.json();
|
|
3634
|
-
session.model = session.user.default_reasoning_model ||
|
|
3767
|
+
session.model = session.user.default_reasoning_model || null;
|
|
3635
3768
|
}
|
|
3636
3769
|
} catch {}
|
|
3637
3770
|
}
|
|
@@ -3674,7 +3807,28 @@ export async function startTerminalRepl() {
|
|
|
3674
3807
|
return res;
|
|
3675
3808
|
};
|
|
3676
3809
|
|
|
3677
|
-
|
|
3810
|
+
function makeToolExecutor({ showIndexStatus = false } = {}) {
|
|
3811
|
+
const shouldShowIndexStatus = showIndexStatus && process.stderr.isTTY && !term().plain;
|
|
3812
|
+
let stopIndexSpinner = null;
|
|
3813
|
+
return createToolExecutor({
|
|
3814
|
+
checkpoints,
|
|
3815
|
+
hookRunner,
|
|
3816
|
+
interactionHandler: askUserInteraction,
|
|
3817
|
+
onAutoRegisterStart: shouldShowIndexStatus ? (root) => {
|
|
3818
|
+
const name = path.basename(root || safeCwd()) || root || 'project';
|
|
3819
|
+
stopIndexSpinner?.();
|
|
3820
|
+
stopIndexSpinner = startInlineSpinner(
|
|
3821
|
+
`Indexing ${name} so tools can read and search this project...`
|
|
3822
|
+
);
|
|
3823
|
+
} : null,
|
|
3824
|
+
onAutoRegisterDone: shouldShowIndexStatus ? () => {
|
|
3825
|
+
stopIndexSpinner?.();
|
|
3826
|
+
stopIndexSpinner = null;
|
|
3827
|
+
} : null,
|
|
3828
|
+
});
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
let toolExecutor = null;
|
|
3678
3832
|
const skipPerms = cliArgs.skipPermissions;
|
|
3679
3833
|
let approval = new ApprovalManager({ autoApprove: skipPerms, cwd: safeCwd(), policy: effectivePolicy.policy });
|
|
3680
3834
|
|
|
@@ -3688,7 +3842,7 @@ export async function startTerminalRepl() {
|
|
|
3688
3842
|
// Persistent stream client — session_id captured from backend on first turn
|
|
3689
3843
|
let streamClient = null;
|
|
3690
3844
|
|
|
3691
|
-
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: [] };
|
|
3692
3846
|
|
|
3693
3847
|
let startupOutputRow = 1;
|
|
3694
3848
|
let startupOutputCol = 1;
|
|
@@ -3751,6 +3905,7 @@ export async function startTerminalRepl() {
|
|
|
3751
3905
|
user: session.user,
|
|
3752
3906
|
model: session.model,
|
|
3753
3907
|
modelLimits: session.modelLimits,
|
|
3908
|
+
subAgentModels: session.subAgentModels,
|
|
3754
3909
|
modelOverrides: session.modelOverrides,
|
|
3755
3910
|
modelMode: session.modelMode,
|
|
3756
3911
|
routePreference: session.routePreference,
|
|
@@ -3768,7 +3923,7 @@ export async function startTerminalRepl() {
|
|
|
3768
3923
|
checkpoints = new CheckpointManager(safeCwd());
|
|
3769
3924
|
effectivePolicy = loadEffectivePolicy({ cwd: safeCwd() });
|
|
3770
3925
|
hookRunner = new HookRunner({ cwd: safeCwd() });
|
|
3771
|
-
toolExecutor =
|
|
3926
|
+
toolExecutor = makeToolExecutor();
|
|
3772
3927
|
approval = new ApprovalManager({ autoApprove: skipPerms, cwd: safeCwd(), policy: effectivePolicy.policy });
|
|
3773
3928
|
if (ctx._rl) approval.setReadline(ctx._rl);
|
|
3774
3929
|
sessionMgr = new SessionManager(safeCwd());
|
|
@@ -3783,6 +3938,7 @@ export async function startTerminalRepl() {
|
|
|
3783
3938
|
startTime: Date.now(),
|
|
3784
3939
|
inputTokens: 0,
|
|
3785
3940
|
outputTokens: 0,
|
|
3941
|
+
lastTurnInputTokens: 0,
|
|
3786
3942
|
toolCalls: 0,
|
|
3787
3943
|
subAgentToolCalls: 0,
|
|
3788
3944
|
totalToolCalls: 0,
|
|
@@ -3795,6 +3951,7 @@ export async function startTerminalRepl() {
|
|
|
3795
3951
|
user: preserved.user,
|
|
3796
3952
|
model: preserved.model,
|
|
3797
3953
|
modelLimits: preserved.modelLimits,
|
|
3954
|
+
subAgentModels: preserved.subAgentModels,
|
|
3798
3955
|
modelOverrides: preserved.modelOverrides,
|
|
3799
3956
|
modelMode: preserved.modelMode,
|
|
3800
3957
|
routePreference: preserved.routePreference,
|
|
@@ -3948,7 +4105,7 @@ export async function startTerminalRepl() {
|
|
|
3948
4105
|
checkpoints = new CheckpointManager(safeCwd());
|
|
3949
4106
|
effectivePolicy = loadEffectivePolicy({ cwd: safeCwd() });
|
|
3950
4107
|
hookRunner = new HookRunner({ cwd: safeCwd(), sessionId });
|
|
3951
|
-
toolExecutor =
|
|
4108
|
+
toolExecutor = makeToolExecutor();
|
|
3952
4109
|
approval = new ApprovalManager({ autoApprove: skipPerms, cwd: safeCwd(), policy: effectivePolicy.policy });
|
|
3953
4110
|
if (ctx._rl) approval.setReadline(ctx._rl);
|
|
3954
4111
|
sessionMgr = new SessionManager(safeCwd());
|
|
@@ -4073,6 +4230,11 @@ export async function startTerminalRepl() {
|
|
|
4073
4230
|
catch { /* preflight is best-effort */ }
|
|
4074
4231
|
}
|
|
4075
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
|
+
|
|
4076
4238
|
// ── Initialization ──
|
|
4077
4239
|
process.stderr.write(` ${c.brand('⠋')} ${c.dim('Initializing...')}\r`);
|
|
4078
4240
|
await fetchUser(ctx);
|
|
@@ -4141,9 +4303,23 @@ export async function startTerminalRepl() {
|
|
|
4141
4303
|
initialContentRow: dockCursor.row,
|
|
4142
4304
|
initialContentCol: dockCursor.col,
|
|
4143
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;
|
|
4144
4313
|
if (inputDockActive) {
|
|
4145
4314
|
process.on('beforeExit', unmountInputDock);
|
|
4146
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); });
|
|
4147
4323
|
}
|
|
4148
4324
|
|
|
4149
4325
|
// ── Bracketed paste (DEC private mode 2004) ──────────────────────────────
|
|
@@ -4900,6 +5076,11 @@ export async function startTerminalRepl() {
|
|
|
4900
5076
|
runtime.lastRenderedBlock = 'user';
|
|
4901
5077
|
}
|
|
4902
5078
|
|
|
5079
|
+
function isExecutionSlashCommand(instruction) {
|
|
5080
|
+
const command = String(instruction || '').trim().split(/\s+/, 1)[0].toLowerCase();
|
|
5081
|
+
return command === '/watch' || command === '/auto';
|
|
5082
|
+
}
|
|
5083
|
+
|
|
4903
5084
|
async function submitExecutionInstruction() {
|
|
4904
5085
|
const instruction = executionInputBuffer.trim();
|
|
4905
5086
|
executionInputBuffer = '';
|
|
@@ -4918,6 +5099,25 @@ export async function startTerminalRepl() {
|
|
|
4918
5099
|
executionInputVisible = false;
|
|
4919
5100
|
return;
|
|
4920
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
|
+
}
|
|
4921
5121
|
printExecutionInstruction(instruction);
|
|
4922
5122
|
if (isInputDockMounted()) {
|
|
4923
5123
|
renderDockInput(executionInputPrefix(), '', {
|
|
@@ -24,6 +24,8 @@ const TOOL_LABELS = Object.freeze({
|
|
|
24
24
|
git_diff: 'Reviewing changes',
|
|
25
25
|
git_status: 'Checking git',
|
|
26
26
|
analyze_code: 'Analyzing',
|
|
27
|
+
analyze_image: 'Analyzing image',
|
|
28
|
+
generate_image: 'Generating image',
|
|
27
29
|
get_project_overview: 'Indexing project',
|
|
28
30
|
skills_list: 'Listing skills',
|
|
29
31
|
skill_view: 'Loading skill',
|
|
@@ -76,9 +78,39 @@ function currentWorkingDirectory() {
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
// Shortens a path for tool-card display without dropping the meaningful
|
|
82
|
+
// tail. Strategy, in priority order:
|
|
83
|
+
//
|
|
84
|
+
// 1. INSIDE cwd /repo/foo/bar.py (cwd = /repo) → foo/bar.py
|
|
85
|
+
// 2. SIBLING under cwd's /work/repo-a/x.py (cwd = /work/repo-b)
|
|
86
|
+
// parent → …/repo-a/x.py
|
|
87
|
+
// (typical mono-repo / adjacent-clone case)
|
|
88
|
+
// 3. UNDER $HOME /Users/sree/Sites/…/x.py → ~/Sites/…/x.py
|
|
89
|
+
// 4. Absolute (fallback — printed as-is; card truncator
|
|
90
|
+
// handles the display width from here)
|
|
91
|
+
//
|
|
92
|
+
// The point: keep the basename + parent visible even when the full path
|
|
93
|
+
// is long, so users don't see "Editing /Users/sree/Sites/Tarang-Orca/
|
|
94
|
+
// codekepler-ba…" with the actual filename lost off the right edge.
|
|
79
95
|
function shortPath(filePath, cwd = currentWorkingDirectory()) {
|
|
80
96
|
const value = String(filePath || '');
|
|
97
|
+
if (!value) return value;
|
|
98
|
+
|
|
99
|
+
// 1. inside cwd
|
|
81
100
|
if (cwd && value.startsWith(`${cwd}/`)) return value.slice(cwd.length + 1);
|
|
101
|
+
|
|
102
|
+
// 2. sibling under cwd's parent (e.g. sister repo in the same workspace)
|
|
103
|
+
if (cwd) {
|
|
104
|
+
const cwdParent = cwd.slice(0, cwd.lastIndexOf('/'));
|
|
105
|
+
if (cwdParent && value.startsWith(`${cwdParent}/`)) {
|
|
106
|
+
return `…/${value.slice(cwdParent.length + 1)}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 3. under $HOME
|
|
111
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
112
|
+
if (home && value.startsWith(`${home}/`)) return `~/${value.slice(home.length + 1)}`;
|
|
113
|
+
|
|
82
114
|
return value;
|
|
83
115
|
}
|
|
84
116
|
|