@bahulam/code 0.1.6 → 0.1.8
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 +11 -18
- package/package.json +11 -11
- package/src/agents/loader.mjs +1 -1
- package/src/agents/workflow_loader.mjs +2 -2
- package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +4 -3
- package/src/commands/agent.mjs +3 -3
- package/src/commands/device.mjs +2 -2
- package/src/commands/pair.mjs +2 -2
- package/src/commands/remote.mjs +3 -3
- package/src/commands/workflow.mjs +5 -5
- package/src/config/env.mjs +9 -2
- package/src/config/memory-loader.mjs +1 -1
- package/src/config/model-catalog-default.json +4 -3
- package/src/config/model-catalog.mjs +2 -0
- package/src/config/settings.mjs +2 -2
- package/src/context/prose-chunker.mjs +162 -5
- package/src/core/agent-loop.mjs +17 -0
- package/src/core/attachments.mjs +66 -4
- package/src/core/backend-url.mjs +11 -12
- package/src/core/bundled-runtime.mjs +1 -1
- package/src/core/callback-client.mjs +1 -1
- package/src/core/headless.mjs +18 -4
- package/src/core/jsonl-writer.mjs +18 -18
- package/src/core/lint-resolver.mjs +472 -0
- package/src/core/local-agent.mjs +40 -8
- package/src/core/local-store.mjs +5 -1
- package/src/core/mode-selector.mjs +1 -1
- package/src/core/output-filter.mjs +22 -26
- package/src/core/pricing.mjs +10 -3
- package/src/core/project-artifacts.mjs +3 -3
- package/src/core/project-context-loader.mjs +9 -9
- package/src/core/settings-sync.mjs +1 -1
- package/src/core/stagnation.mjs +61 -1
- package/src/core/stream-client.mjs +13 -8
- package/src/core/system-prompt.mjs +2 -2
- package/src/core/tool-executor.mjs +178 -84
- 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/mcp/client.mjs +4 -4
- package/src/onboarding/preflight.mjs +2 -2
- package/src/terminal/agents.mjs +2 -2
- package/src/terminal/analytics.mjs +2 -6
- package/src/terminal/ansi.mjs +11 -3
- package/src/terminal/main.mjs +43 -17
- 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 +37 -7
- package/src/terminal/repl-resume.mjs +2 -2
- package/src/terminal/repl-state.mjs +12 -0
- package/src/terminal/repl.mjs +248 -70
- package/src/terminal/tool-display.mjs +32 -0
- package/src/terminal/watch-state.mjs +118 -0
- package/src/tools/agent.mjs +2 -2
- 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 +12 -12
- package/src/tools/registry.mjs +4 -0
- package/src/ui/formatter.mjs +2 -2
- package/src/ui/icons.mjs +2 -0
- package/src/ui/input-dock.mjs +7 -7
- package/src/ui/palette.mjs +1 -1
- package/src/ui/slash-commands.mjs +4 -6
- package/src/ui/spinner.mjs +1 -1
- package/src/ui/term.mjs +2 -6
- package/src/ui/tool-card.mjs +81 -6
- 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
|
*
|
|
@@ -22,7 +22,7 @@ import { execSync as _execSync } from 'node:child_process';
|
|
|
22
22
|
import { Writable as _WritableStream } from 'node:stream';
|
|
23
23
|
import { c, progressBar, spinner, inPlace, renderMarkdown, renderDiff, formatElapsed, formatCost, stripAnsi } from './ansi.mjs';
|
|
24
24
|
import { calculateCost, formatCostValue, formatTokens, costToCredits, formatCredits } from '../core/pricing.mjs';
|
|
25
|
-
import {
|
|
25
|
+
import { BahulamStreamClient, EVENT_TYPES } from '../core/stream-client.mjs';
|
|
26
26
|
import { AgentHistoryTurnBuilder } from '../core/agent-history.mjs';
|
|
27
27
|
import { JsonlWriter } from '../core/jsonl-writer.mjs';
|
|
28
28
|
import { tapSseEvent, registerBroadcaster } from '../daemon/event-tap.mjs';
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
MODES as V_MODES,
|
|
54
54
|
} from '../state/verbosity.mjs';
|
|
55
55
|
import { persistProjectArtifacts } from '../core/project-artifacts.mjs';
|
|
56
|
-
import {
|
|
56
|
+
import { BahulamAuth } from '../auth/bahulam-auth.mjs';
|
|
57
57
|
import { ApprovalManager } from '../core/approval.mjs';
|
|
58
58
|
import * as telemetry from '../telemetry/index.mjs';
|
|
59
59
|
import { resolveBackendUrl } from '../core/backend-url.mjs';
|
|
@@ -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) ─────────────────────
|
|
@@ -1501,8 +1586,8 @@ function foldSubAgentToolResult(data = {}) {
|
|
|
1501
1586
|
fold.entries.push(entry);
|
|
1502
1587
|
}
|
|
1503
1588
|
const durationMs = data?.duration_ms ?? (data?.duration_s != null ? data.duration_s * 1000 : null);
|
|
1504
|
-
const summary = summarizeResult(tool, data);
|
|
1505
1589
|
entry.args = entry.args && Object.keys(entry.args).length ? entry.args : args;
|
|
1590
|
+
const summary = summarizeResult(tool, data, entry.args);
|
|
1506
1591
|
entry.result = data;
|
|
1507
1592
|
entry.durationMs = durationMs;
|
|
1508
1593
|
entry.outcome = summary.text || '';
|
|
@@ -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.
|
|
@@ -2251,7 +2358,7 @@ function renderEvent(event) {
|
|
|
2251
2358
|
break;
|
|
2252
2359
|
|
|
2253
2360
|
// Live steering ack events (PRD-081 §5.2). renderEvent is called from
|
|
2254
|
-
// the SSE loop (see `for await ... jsonlWriter.
|
|
2361
|
+
// the SSE loop (see `for await ... jsonlWriter.writeBahulamEvent(event)`
|
|
2255
2362
|
// in the /execute path) so every event already lands in the transcript.
|
|
2256
2363
|
// Here we only render the user-visible surface — no direct jsonlWriter
|
|
2257
2364
|
// calls (it's out of scope in this top-level dispatcher anyway).
|
|
@@ -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
|
}
|
|
@@ -3644,7 +3776,7 @@ export async function startTerminalRepl() {
|
|
|
3644
3776
|
safeCwd(); // prime the cache in repl-utils.mjs for later recovery
|
|
3645
3777
|
|
|
3646
3778
|
const cliArgs = parseArgs(process.argv.slice(2));
|
|
3647
|
-
const auth = new
|
|
3779
|
+
const auth = new BahulamAuth();
|
|
3648
3780
|
|
|
3649
3781
|
// Projects are registered and indexed on demand through get_project_overview.
|
|
3650
3782
|
// CheckpointManager records per-file snapshots before edits so /undo works.
|
|
@@ -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,
|
|
@@ -3988,7 +4123,7 @@ export async function startTerminalRepl() {
|
|
|
3988
4123
|
&& richHistory.summary
|
|
3989
4124
|
&& Number(richHistory.summaryCoveredMessageCount) > Number(richHistory.summaryCheckpointMessageCount || 0)
|
|
3990
4125
|
) {
|
|
3991
|
-
jsonlWriter.
|
|
4126
|
+
jsonlWriter.writeBahulamEvent({
|
|
3992
4127
|
type: 'resume_summary',
|
|
3993
4128
|
data: {
|
|
3994
4129
|
session_id: sessionId,
|
|
@@ -4003,7 +4138,7 @@ export async function startTerminalRepl() {
|
|
|
4003
4138
|
},
|
|
4004
4139
|
});
|
|
4005
4140
|
}
|
|
4006
|
-
jsonlWriter.
|
|
4141
|
+
jsonlWriter.writeBahulamEvent({
|
|
4007
4142
|
type: 'resume_context',
|
|
4008
4143
|
data: {
|
|
4009
4144
|
session_id: sessionId,
|
|
@@ -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) ──────────────────────────────
|
|
@@ -4270,7 +4424,7 @@ export async function startTerminalRepl() {
|
|
|
4270
4424
|
// Proxy stream: swallows writes when the dock owns the input row so
|
|
4271
4425
|
// readline's echoes and _refreshLine cursor moves can't fight the dock's
|
|
4272
4426
|
// absolute cursor placement (PRD-081 §5.1 — cursor race). When the dock
|
|
4273
|
-
// is not mounted (plain-mode fallback,
|
|
4427
|
+
// is not mounted (plain-mode fallback, BAHULAM_FIXED_INPUT=0, non-TTY),
|
|
4274
4428
|
// writes pass through so history navigation / backspace still redraw.
|
|
4275
4429
|
const readlineOutputProxy = new _WritableStream({
|
|
4276
4430
|
write(chunk, _enc, cb) {
|
|
@@ -4317,7 +4471,7 @@ export async function startTerminalRepl() {
|
|
|
4317
4471
|
// 3 rows fits comfortably in the dock's reservation without leaking
|
|
4318
4472
|
// past the safety row.
|
|
4319
4473
|
if (isInputDockMounted()) return 3;
|
|
4320
|
-
const raw = process.env.
|
|
4474
|
+
const raw = process.env.BAHULAM_PROMPT_BOTTOM_PADDING ?? '5';
|
|
4321
4475
|
const n = Number.parseInt(raw, 10);
|
|
4322
4476
|
if (!Number.isFinite(n) || n <= 0) return 0;
|
|
4323
4477
|
return Math.min(8, n);
|
|
@@ -4590,14 +4744,14 @@ export async function startTerminalRepl() {
|
|
|
4590
4744
|
// 1. If the terminal supports bracketed paste, we hold flushing until we
|
|
4591
4745
|
// see the ESC[201~ end marker — reliable regardless of paste latency.
|
|
4592
4746
|
// 2. Otherwise we fall back to a short timer that merges bursts arriving
|
|
4593
|
-
// within
|
|
4747
|
+
// within BAHULAM_PASTE_FLUSH_MS.
|
|
4594
4748
|
let _lineInFlight = false;
|
|
4595
4749
|
const _queuedLines = [];
|
|
4596
4750
|
let _pasteLines = [];
|
|
4597
4751
|
let _pasteFlushTimer = null;
|
|
4598
4752
|
|
|
4599
4753
|
function pasteFlushDelayMs() {
|
|
4600
|
-
const raw = Number.parseInt(process.env.
|
|
4754
|
+
const raw = Number.parseInt(process.env.BAHULAM_PASTE_FLUSH_MS || '35', 10);
|
|
4601
4755
|
return Number.isFinite(raw) && raw >= 0 ? Math.min(250, raw) : 35;
|
|
4602
4756
|
}
|
|
4603
4757
|
|
|
@@ -4743,7 +4897,7 @@ export async function startTerminalRepl() {
|
|
|
4743
4897
|
// Create or reuse stream client — sessionId persists across turns.
|
|
4744
4898
|
// The same client also owns the authenticated vision-analysis preflight.
|
|
4745
4899
|
if (!streamClient || streamClient.baseUrl !== creds.backendUrl || streamClient.token !== creds.token) {
|
|
4746
|
-
streamClient = new
|
|
4900
|
+
streamClient = new BahulamStreamClient({
|
|
4747
4901
|
baseUrl: creds.backendUrl,
|
|
4748
4902
|
token: creds.token,
|
|
4749
4903
|
toolExecutor,
|
|
@@ -4767,7 +4921,7 @@ export async function startTerminalRepl() {
|
|
|
4767
4921
|
` ${c.brand('◇')} ${c.dim(`attached ${docPrep.documents.length} document${docPrep.documents.length === 1 ? '' : 's'}:`)} ` +
|
|
4768
4922
|
`${docPrep.documents.map(documentSummaryLine).join(c.dim(' · '))}\n`
|
|
4769
4923
|
);
|
|
4770
|
-
jsonlWriter.
|
|
4924
|
+
jsonlWriter.writeBahulamEvent({
|
|
4771
4925
|
type: 'attachments',
|
|
4772
4926
|
data: { documents: docPrep.documents.map(publicDocumentMetadata) },
|
|
4773
4927
|
});
|
|
@@ -4783,7 +4937,7 @@ export async function startTerminalRepl() {
|
|
|
4783
4937
|
});
|
|
4784
4938
|
if (prepared.attachments.length) {
|
|
4785
4939
|
process.stderr.write(` ${c.brand('◇')} ${c.dim(`attached ${prepared.attachments.length} image${prepared.attachments.length === 1 ? '' : 's'}:`)} ${prepared.attachments.map(attachmentSummaryLine).join(c.dim(' · '))}\n`);
|
|
4786
|
-
jsonlWriter.
|
|
4940
|
+
jsonlWriter.writeBahulamEvent({
|
|
4787
4941
|
type: 'attachments',
|
|
4788
4942
|
data: { attachments: prepared.attachments.map(publicAttachmentMetadata) },
|
|
4789
4943
|
});
|
|
@@ -4800,7 +4954,7 @@ export async function startTerminalRepl() {
|
|
|
4800
4954
|
});
|
|
4801
4955
|
process.stderr.write(`\r${' '.repeat(80)}\r`);
|
|
4802
4956
|
process.stderr.write(` ${c.green('✓')} ${c.dim(`Vision analysis completed for ${prepared.attachments.length} image${prepared.attachments.length === 1 ? '' : 's'}`)}${analysis.model ? c.dim(` · ${analysis.model}`) : ''}\n`);
|
|
4803
|
-
jsonlWriter.
|
|
4957
|
+
jsonlWriter.writeBahulamEvent({
|
|
4804
4958
|
type: 'vision_analysis',
|
|
4805
4959
|
data: {
|
|
4806
4960
|
model: analysis.model || '',
|
|
@@ -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(), '', {
|
|
@@ -4962,7 +5140,7 @@ export async function startTerminalRepl() {
|
|
|
4962
5140
|
// Persist the local record regardless of outcome so the transcript
|
|
4963
5141
|
// reflects what the user typed. Delivered/queued follow-ups will
|
|
4964
5142
|
// get their SSE ack events written separately by the event handler.
|
|
4965
|
-
jsonlWriter.
|
|
5143
|
+
jsonlWriter.writeBahulamEvent({
|
|
4966
5144
|
type: 'user_intervention',
|
|
4967
5145
|
data: {
|
|
4968
5146
|
instruction,
|
|
@@ -5333,7 +5511,7 @@ export async function startTerminalRepl() {
|
|
|
5333
5511
|
_turnIterable = client.execute(input, execContext, session.agentHistory);
|
|
5334
5512
|
}
|
|
5335
5513
|
for await (const event of _turnIterable) {
|
|
5336
|
-
jsonlWriter.
|
|
5514
|
+
jsonlWriter.writeBahulamEvent(event);
|
|
5337
5515
|
// . daemon event log. Env-var gated (off by default) —
|
|
5338
5516
|
// when BAHULAM_DAEMON_EVENTLOG=1, mirror each SSE frame that maps
|
|
5339
5517
|
// to a first-class type into ~/.bahulam/sessions/<id>/events.jsonl.
|