@bahulam/code 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/package.json +49 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- 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 +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- 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 +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +222 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +306 -0
- package/src/commands/agent.mjs +220 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +200 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +308 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +104 -0
- package/src/core/approval.mjs +476 -0
- package/src/core/attachments.mjs +380 -0
- package/src/core/backend-url.mjs +55 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +311 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +448 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +537 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +337 -0
- package/src/core/safety.mjs +203 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +829 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +1950 -0
- package/src/core/trust.mjs +158 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +426 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +360 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +652 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +269 -0
- package/src/terminal/repl-explore.mjs +35 -0
- package/src/terminal/repl-format.mjs +257 -0
- package/src/terminal/repl-render.mjs +561 -0
- package/src/terminal/repl-resume.mjs +625 -0
- package/src/terminal/repl-state.mjs +103 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +3832 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +240 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +641 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +263 -0
- package/src/ui/banner.mjs +235 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +164 -0
- package/src/ui/input-dock.mjs +444 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/slash-commands.mjs +245 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +152 -0
- package/src/ui/term.mjs +159 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +463 -0
- package/src/ui/tool-details.mjs +312 -0
- package/src/ui/transcript-block.mjs +21 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure display formatters used by repl.mjs.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so the giant repl.mjs shrinks and the pieces become unit-
|
|
5
|
+
* testable. Everything here is a pure function — no runtime state, no
|
|
6
|
+
* mutable side effects beyond the color helpers from ansi.mjs (which
|
|
7
|
+
* themselves just wrap strings in ANSI codes).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { c, stripAnsi, formatElapsed, inPlace } from './ansi.mjs';
|
|
12
|
+
|
|
13
|
+
// ── One-liners ───────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export function messageCountLabel(count) {
|
|
16
|
+
return `${count} ${count === 1 ? 'message' : 'messages'}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function sessionListTimestamp(s) {
|
|
20
|
+
const value = s.updatedAt || s.startedAt;
|
|
21
|
+
return value ? new Date(value).toLocaleString() : '?';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function oneLineInstruction(text, max = 72) {
|
|
25
|
+
const compact = String(text || '(no instruction)').replace(/\s+/g, ' ').trim();
|
|
26
|
+
return compact.length > max ? compact.slice(0, max - 3) + '...' : compact;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Truncate `text` to `maxColumns` visible columns, preserving ANSI
|
|
31
|
+
* escape sequences and appending a dim ellipsis when truncation happens.
|
|
32
|
+
*/
|
|
33
|
+
export function fitAnsiLine(text, maxColumns) {
|
|
34
|
+
const max = Math.max(1, Number(maxColumns) || 80);
|
|
35
|
+
const value = String(text || '');
|
|
36
|
+
if (stripAnsi(value).length <= max) return value;
|
|
37
|
+
|
|
38
|
+
let visible = 0;
|
|
39
|
+
let out = '';
|
|
40
|
+
for (let i = 0; i < value.length; i++) {
|
|
41
|
+
if (value[i] === '\x1b') {
|
|
42
|
+
const match = value.slice(i).match(/^\x1b\[[0-9;]*m/);
|
|
43
|
+
if (match) {
|
|
44
|
+
out += match[0];
|
|
45
|
+
i += match[0].length - 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (visible >= max - 1) break;
|
|
50
|
+
out += value[i];
|
|
51
|
+
visible++;
|
|
52
|
+
}
|
|
53
|
+
return `${out}${c.dim('…')}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── PRD-068 §5.14 helpers ────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export function endStatusMarker(status) {
|
|
59
|
+
switch (status) {
|
|
60
|
+
case 'completed': return c.green('✓');
|
|
61
|
+
case 'interrupted': return c.yellow('⚠');
|
|
62
|
+
case 'errored': return c.red('✗');
|
|
63
|
+
default: return c.dim('·');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function formatSessionCost(usd) {
|
|
68
|
+
const n = Number(usd);
|
|
69
|
+
if (!Number.isFinite(n) || n <= 0) return c.dim(' ');
|
|
70
|
+
if (n < 0.01) return c.dim('<$0.01');
|
|
71
|
+
return c.dim(`$${n.toFixed(2)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function formatRelativeTime(iso) {
|
|
75
|
+
if (!iso) return '';
|
|
76
|
+
const t = Date.parse(iso);
|
|
77
|
+
if (!Number.isFinite(t)) return '';
|
|
78
|
+
const ago = Date.now() - t;
|
|
79
|
+
const m = Math.floor(ago / 60_000);
|
|
80
|
+
if (m < 1) return 'just now';
|
|
81
|
+
if (m < 60) return `${m}m ago`;
|
|
82
|
+
const h = Math.floor(m / 60);
|
|
83
|
+
if (h < 24) return `${h}h ago`;
|
|
84
|
+
const d = Math.floor(h / 24);
|
|
85
|
+
if (d < 30) return `${d}d ago`;
|
|
86
|
+
const mo = Math.floor(d / 30);
|
|
87
|
+
return `${mo}mo ago`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Resume-mode helpers ──────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
export function resumeTailTurnCount(mode = '') {
|
|
93
|
+
const match = String(mode || '').match(/^tail-(\d+)$/);
|
|
94
|
+
if (!match) return null;
|
|
95
|
+
return Math.max(1, Number(match[1]) || 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function resumeModeLabel(mode = 'full') {
|
|
99
|
+
if (mode === 'checkpoint-full') return 'checkpointed transcript';
|
|
100
|
+
if (mode === 'summary') return 'summary only';
|
|
101
|
+
const tailTurns = resumeTailTurnCount(mode);
|
|
102
|
+
if (tailTurns) return `summary + last ${tailTurns} turns`;
|
|
103
|
+
return mode || 'full';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function replayStartOrderForMode(history = [], mode = '') {
|
|
107
|
+
const match = String(mode || '').match(/^tail-(\d+)$/);
|
|
108
|
+
if (!match) return null;
|
|
109
|
+
const wanted = Math.max(1, Number(match[1]) || 1);
|
|
110
|
+
let seen = 0;
|
|
111
|
+
const userTurns = history.filter(m => m.role === 'user' && typeof m.content === 'string');
|
|
112
|
+
for (let i = userTurns.length - 1; i >= 0; i--) {
|
|
113
|
+
seen++;
|
|
114
|
+
if (seen >= wanted) {
|
|
115
|
+
const order = Number(userTurns[i].order);
|
|
116
|
+
return Number.isFinite(order) ? order : null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function filterResumeReplayEvents(events = []) {
|
|
123
|
+
return events.filter(item => {
|
|
124
|
+
const type = item?.event?.type;
|
|
125
|
+
return !['status', 'session_info', 'complete', 'resumed', 'paused'].includes(type);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function mergeResumeReplayItems(userTurns = [], replayEvents = []) {
|
|
130
|
+
const items = [];
|
|
131
|
+
let order = 0;
|
|
132
|
+
for (const message of userTurns) {
|
|
133
|
+
items.push({
|
|
134
|
+
kind: 'user',
|
|
135
|
+
message,
|
|
136
|
+
fileOrder: Number.isFinite(Number(message.order)) ? Number(message.order) : null,
|
|
137
|
+
order: order++,
|
|
138
|
+
time: Date.parse(message.timestamp || '') || 0,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
for (const event of replayEvents) {
|
|
142
|
+
items.push({
|
|
143
|
+
kind: 'event',
|
|
144
|
+
event,
|
|
145
|
+
fileOrder: Number.isFinite(Number(event.order)) ? Number(event.order) : null,
|
|
146
|
+
order: order++,
|
|
147
|
+
time: Date.parse(event.timestamp || '') || 0,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return items.sort((a, b) => {
|
|
151
|
+
if (a.fileOrder !== null && b.fileOrder !== null && a.fileOrder !== b.fileOrder) {
|
|
152
|
+
return a.fileOrder - b.fileOrder;
|
|
153
|
+
}
|
|
154
|
+
if (a.fileOrder !== null && b.fileOrder === null) return -1;
|
|
155
|
+
if (a.fileOrder === null && b.fileOrder !== null) return 1;
|
|
156
|
+
const at = a.time || Number.MAX_SAFE_INTEGER;
|
|
157
|
+
const bt = b.time || Number.MAX_SAFE_INTEGER;
|
|
158
|
+
return at - bt || a.order - b.order;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function resumeProgressBar(percent, width = 12) {
|
|
163
|
+
const p = Math.max(0, Math.min(100, Math.round(percent)));
|
|
164
|
+
const filled = Math.round((p / 100) * width);
|
|
165
|
+
return `${c.brand('█'.repeat(filled))}${c.gray('░'.repeat(width - filled))} ${String(p).padStart(3)}%`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Start an animated "resuming…" progress line. Returns { update, stop }.
|
|
170
|
+
* The caller updates the label + percent as the resume flow advances and
|
|
171
|
+
* calls stop() when done. inPlace-based render, so it lives on one line.
|
|
172
|
+
*/
|
|
173
|
+
export function startResumeProgress(mode = 'full') {
|
|
174
|
+
let percent = 8;
|
|
175
|
+
let label = `resuming as ${resumeModeLabel(mode)}`;
|
|
176
|
+
let active = true;
|
|
177
|
+
const started = Date.now();
|
|
178
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
179
|
+
let frame = 0;
|
|
180
|
+
|
|
181
|
+
const render = () => {
|
|
182
|
+
if (!active) return;
|
|
183
|
+
const glyph = frames[frame % frames.length];
|
|
184
|
+
frame++;
|
|
185
|
+
inPlace(` ${c.brand(glyph)} ${c.dim(label)} ${resumeProgressBar(percent)} ${c.dim(formatElapsed(started))}`);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
render();
|
|
189
|
+
const timer = setInterval(render, 100);
|
|
190
|
+
return {
|
|
191
|
+
update(nextLabel, nextPercent) {
|
|
192
|
+
if (!active) return;
|
|
193
|
+
if (nextLabel) label = nextLabel;
|
|
194
|
+
if (Number.isFinite(nextPercent)) percent = Math.max(percent, Math.min(98, nextPercent));
|
|
195
|
+
render();
|
|
196
|
+
},
|
|
197
|
+
stop() {
|
|
198
|
+
if (!active) return;
|
|
199
|
+
active = false;
|
|
200
|
+
clearInterval(timer);
|
|
201
|
+
inPlace('');
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Normalize a raw session record (from getRecentSessions or persistence)
|
|
208
|
+
* into the shape the resume picker expects. Pure data transform.
|
|
209
|
+
*/
|
|
210
|
+
export function normalizeResumableSession(s) {
|
|
211
|
+
return {
|
|
212
|
+
sessionId: s.sessionId,
|
|
213
|
+
instruction: s.firstPrompt || s.instruction || '(no instruction)',
|
|
214
|
+
startedAt: s.startTime || s.startedAt || '',
|
|
215
|
+
updatedAt: s.endTime || s.updatedAt || (s.mtime ? new Date(s.mtime).toISOString() : ''),
|
|
216
|
+
project: s.project ? path.basename(s.project) : s.projectName || s.project || '',
|
|
217
|
+
projectPath: s.project || s.projectPath || '',
|
|
218
|
+
transcriptPath: s.filePath || s.transcriptPath || '',
|
|
219
|
+
messageCount: (s.userMessages || 0) + (s.assistantMessages || 0),
|
|
220
|
+
// PRD-068 §5.14.11 derived fields for the picker
|
|
221
|
+
endStatus: s.endStatus || 'unknown', // 'completed' | 'interrupted' | 'errored' | 'unknown'
|
|
222
|
+
contextTokens: s.contextTokens || 0, // projected transcript token count
|
|
223
|
+
contextTokenSource: s.contextTokenSource || 'jsonl_bytes',
|
|
224
|
+
resumeSummary: s.resumeSummary || null, // latest resume_summary checkpoint metadata
|
|
225
|
+
models: Array.isArray(s.models) ? s.models : [],
|
|
226
|
+
modelLimits: s.modelLimits && typeof s.modelLimits === 'object' ? s.modelLimits : {},
|
|
227
|
+
costUsd: typeof s.costUsd === 'number' ? s.costUsd : 0,
|
|
228
|
+
partial: !!s.partial, // true if the transcript file was partially malformed
|
|
229
|
+
source: 'transcript',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Speaker prefix label for a transcript entry.
|
|
235
|
+
*/
|
|
236
|
+
export function historyRoleLabel(role) {
|
|
237
|
+
return role === 'user'
|
|
238
|
+
? c.white('You')
|
|
239
|
+
: role === 'tool'
|
|
240
|
+
? c.dim('Tool')
|
|
241
|
+
: c.brand('bahulam');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Render the tail of a conversation transcript to stderr. Used by
|
|
246
|
+
* /history and /resume preview flows.
|
|
247
|
+
*/
|
|
248
|
+
export function renderHistoryEntries(entries, { limit = 20, maxChars = 120, title = 'Conversation' } = {}) {
|
|
249
|
+
const shown = limit === Infinity ? entries : entries.slice(-limit);
|
|
250
|
+
process.stderr.write(`\n ${c.bold(title)} (${shown.length}${shown.length === entries.length ? '' : ` of ${entries.length}`} entries)\n`);
|
|
251
|
+
process.stderr.write(` ${c.gray('─'.repeat(80))}\n`);
|
|
252
|
+
for (const msg of shown) {
|
|
253
|
+
const content = String(msg.content || '').replace(/\s+/g, ' ').trim();
|
|
254
|
+
process.stderr.write(` ${historyRoleLabel(msg.role)}: ${content.slice(0, maxChars)}${content.length > maxChars ? '...' : ''}\n`);
|
|
255
|
+
}
|
|
256
|
+
process.stderr.write('\n');
|
|
257
|
+
}
|