atom-agent 1.2.0 → 1.4.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/CHANGELOG.md +97 -0
- package/README.md +13 -4
- package/atom.example.json +11 -0
- package/dist/App.js +923 -200
- package/dist/adapters.js +82 -13
- package/dist/agent/goal-evaluator.js +69 -0
- package/dist/agent/loop.js +517 -76
- package/dist/cli.js +11 -3
- package/dist/compact.js +41 -15
- package/dist/config.js +43 -7
- package/dist/context-manager.js +16 -198
- package/dist/context-windows.js +4 -2
- package/dist/env-block.js +5 -5
- package/dist/extension-commands.js +196 -0
- package/dist/extension-ui.js +153 -0
- package/dist/extensions.js +1571 -0
- package/dist/goal.js +583 -0
- package/dist/project-trust.js +96 -0
- package/dist/providers.js +6 -6
- package/dist/scheduler.js +74 -36
- package/dist/session.js +23 -5
- package/dist/sessions.js +25 -6
- package/dist/telemetry-dashboard.js +28 -0
- package/dist/telemetry.js +39 -0
- package/dist/tools/compaction-hooks.js +165 -0
- package/dist/tools/custom.js +189 -0
- package/dist/tools/intercept.js +145 -0
- package/dist/tools/overrides.js +105 -0
- package/dist/tools/provider-hooks.js +224 -0
- package/dist/tools/registry.js +246 -17
- package/dist/tools.js +44 -0
- package/dist/ui/diff-panel.js +5 -5
- package/dist/ui/diff-view.js +3 -2
- package/dist/ui/diff.js +7 -52
- package/dist/ui/modals.js +5 -5
- package/dist/ui/palette.js +1 -1
- package/dist/ui/side-by-side.js +7 -5
- package/dist/ui/status-bar.js +80 -5
- package/dist/ui/transcript.js +3 -3
- package/dist/zen.js +305 -75
- package/documentation/architecture.md +114 -0
- package/documentation/cli.md +82 -0
- package/documentation/compaction.md +50 -0
- package/documentation/configuration.md +111 -0
- package/documentation/development.md +62 -0
- package/documentation/extensions.md +160 -0
- package/documentation/getting-started.md +63 -0
- package/documentation/goals.md +41 -0
- package/documentation/index.md +41 -0
- package/documentation/observability.md +70 -0
- package/documentation/permissions.md +66 -0
- package/documentation/providers.md +78 -0
- package/documentation/sessions.md +92 -0
- package/documentation/skills.md +57 -0
- package/documentation/tools.md +94 -0
- package/documentation/troubleshooting.md +54 -0
- package/examples/extensions/01-audit-gate.js +24 -0
- package/examples/extensions/02-notes-tool.js +32 -0
- package/examples/extensions/03-custom-command.js +32 -0
- package/package.json +6 -2
package/dist/ui/diff.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
export const CHANGE_THRESHOLD = 0.4;
|
|
5
5
|
// Mirrors Claude's DiffDetailView guards.
|
|
6
6
|
export const MAX_FILE_BYTES = 1_000_000;
|
|
7
|
+
// Retained for compatibility (no longer applied — the engine returns the
|
|
8
|
+
// full hunk list; views render it whole). Safety caps that remain enforced:
|
|
9
|
+
// MAX_FILE_BYTES, binary detect, MAX_MYERS_LINES fallback, MAX_WORD_TOKENS
|
|
10
|
+
// fallback.
|
|
7
11
|
export const MAX_CHANGED_LINES = 400;
|
|
8
12
|
export const DIFF_CONTEXT = 3;
|
|
9
13
|
// Worst-case guards for the O(ND) Myers pass + O(w1*w2) word pass.
|
|
@@ -183,7 +187,7 @@ export function computeDiff(oldText, newText) {
|
|
|
183
187
|
const hunks = [];
|
|
184
188
|
let adds = 0;
|
|
185
189
|
let dels = 0;
|
|
186
|
-
|
|
190
|
+
const truncated = false;
|
|
187
191
|
let hunkStart = Math.max(0, changeIdx[0] - DIFF_CONTEXT);
|
|
188
192
|
let hunkEnd = Math.min(raw.length, changeIdx[0] + DIFF_CONTEXT + 1);
|
|
189
193
|
const flush = (s, e) => {
|
|
@@ -242,7 +246,6 @@ export function computeDiff(oldText, newText) {
|
|
|
242
246
|
}
|
|
243
247
|
hunks.push({ oldStart, oldLines: oCount, newStart, newLines: nCount, lines });
|
|
244
248
|
};
|
|
245
|
-
let changed = 0;
|
|
246
249
|
for (let c = 1; c < changeIdx.length; c++) {
|
|
247
250
|
const prev = changeIdx[c - 1];
|
|
248
251
|
const cur = changeIdx[c];
|
|
@@ -251,32 +254,11 @@ export function computeDiff(oldText, newText) {
|
|
|
251
254
|
}
|
|
252
255
|
else {
|
|
253
256
|
flush(hunkStart, hunkEnd);
|
|
254
|
-
changed = hunks.reduce((t, h) => t + h.lines.filter((l) => l.kind !== "context").length, 0);
|
|
255
|
-
if (changed >= MAX_CHANGED_LINES) {
|
|
256
|
-
truncated = true;
|
|
257
|
-
return { hunks, adds, dels, truncated, skipped: null, isNewFile: oldText === null };
|
|
258
|
-
}
|
|
259
257
|
hunkStart = Math.max(0, cur - DIFF_CONTEXT);
|
|
260
258
|
hunkEnd = Math.min(raw.length, cur + DIFF_CONTEXT + 1);
|
|
261
259
|
}
|
|
262
260
|
}
|
|
263
261
|
flush(hunkStart, hunkEnd);
|
|
264
|
-
changed = hunks.reduce((t, h) => t + h.lines.filter((l) => l.kind !== "context").length, 0);
|
|
265
|
-
if (changed > MAX_CHANGED_LINES) {
|
|
266
|
-
// Trim trailing hunks past the budget (keep the head — the user
|
|
267
|
-
// reviews top-down; the notice names the remainder).
|
|
268
|
-
let kept = 0;
|
|
269
|
-
const out = [];
|
|
270
|
-
for (const h of hunks) {
|
|
271
|
-
const n = h.lines.filter((l) => l.kind !== "context").length;
|
|
272
|
-
if (kept + n > MAX_CHANGED_LINES)
|
|
273
|
-
break;
|
|
274
|
-
out.push(h);
|
|
275
|
-
kept += n;
|
|
276
|
-
}
|
|
277
|
-
truncated = true;
|
|
278
|
-
return { hunks: out, adds, dels, truncated, skipped: null, isNewFile: oldText === null };
|
|
279
|
-
}
|
|
280
262
|
return { hunks, adds, dels, truncated, skipped: null, isNewFile: oldText === null };
|
|
281
263
|
}
|
|
282
264
|
export function computeSideBySide(oldText, newText) {
|
|
@@ -316,9 +298,7 @@ export function computeSideBySide(oldText, newText) {
|
|
|
316
298
|
const rows = [];
|
|
317
299
|
let adds = 0;
|
|
318
300
|
let dels = 0;
|
|
319
|
-
|
|
320
|
-
const changedSoFar = () => dels + adds;
|
|
321
|
-
// Returns false when the change budget is exhausted (stop windowing).
|
|
301
|
+
const truncated = false;
|
|
322
302
|
const flushSlice = (s, e) => {
|
|
323
303
|
const slice = raw.slice(s, e);
|
|
324
304
|
let k = 0;
|
|
@@ -376,12 +356,7 @@ export function computeSideBySide(oldText, newText) {
|
|
|
376
356
|
}
|
|
377
357
|
dels += delRun.length;
|
|
378
358
|
adds += addRun.length;
|
|
379
|
-
if (changedSoFar() >= MAX_CHANGED_LINES) {
|
|
380
|
-
truncated = true;
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
359
|
}
|
|
384
|
-
return true;
|
|
385
360
|
};
|
|
386
361
|
let hunkStart = Math.max(0, changeIdx[0] - DIFF_CONTEXT);
|
|
387
362
|
let hunkEnd = Math.min(raw.length, changeIdx[0] + DIFF_CONTEXT + 1);
|
|
@@ -392,31 +367,11 @@ export function computeSideBySide(oldText, newText) {
|
|
|
392
367
|
hunkEnd = Math.min(raw.length, cur + DIFF_CONTEXT + 1);
|
|
393
368
|
}
|
|
394
369
|
else {
|
|
395
|
-
|
|
396
|
-
return { kind: "diff", rows, adds, dels, truncated, isNewFile: oldText === null };
|
|
397
|
-
}
|
|
370
|
+
flushSlice(hunkStart, hunkEnd);
|
|
398
371
|
hunkStart = Math.max(0, cur - DIFF_CONTEXT);
|
|
399
372
|
hunkEnd = Math.min(raw.length, cur + DIFF_CONTEXT + 1);
|
|
400
373
|
}
|
|
401
374
|
}
|
|
402
375
|
flushSlice(hunkStart, hunkEnd);
|
|
403
|
-
if (dels + adds > MAX_CHANGED_LINES) {
|
|
404
|
-
// Single-hunk overflow: the slice already pushed past the budget —
|
|
405
|
-
// trim trailing rows past 400 changes (keep the head; the notice
|
|
406
|
-
// names the remainder). Mirrors computeDiff's trailing-hunk trim.
|
|
407
|
-
let kept = 0;
|
|
408
|
-
let cut = rows.length;
|
|
409
|
-
for (let i = 0; i < rows.length; i++) {
|
|
410
|
-
if (rows[i].kind === "change") {
|
|
411
|
-
kept += 1;
|
|
412
|
-
if (kept > MAX_CHANGED_LINES) {
|
|
413
|
-
cut = i;
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
rows.length = cut;
|
|
419
|
-
truncated = true;
|
|
420
|
-
}
|
|
421
376
|
return { kind: "diff", rows, adds, dels, truncated, isNewFile: oldText === null };
|
|
422
377
|
}
|
package/dist/ui/modals.js
CHANGED
|
@@ -7,10 +7,10 @@ import React from "react";
|
|
|
7
7
|
import { Box, Text } from "ink";
|
|
8
8
|
import { SideBySideDiffView } from "./side-by-side.js";
|
|
9
9
|
import { theme } from "./theme.js";
|
|
10
|
-
//
|
|
11
|
-
// the
|
|
12
|
-
//
|
|
13
|
-
export const APPROVAL_DIFF_MAX_LINES =
|
|
10
|
+
// Retained for compatibility (no longer applied — the approval preview
|
|
11
|
+
// renders the full diff; smoothness comes from the per-mount memo + word
|
|
12
|
+
// fallbacks, not from a row cap).
|
|
13
|
+
export const APPROVAL_DIFF_MAX_LINES = Infinity;
|
|
14
14
|
export const APPROVAL_OPTIONS = ["once", "always", "trustAll", "no"];
|
|
15
15
|
// Command/file preview: the audit description minus its `⚙ name` prefix
|
|
16
16
|
// (the tool name already headlines above). Falls back to the full text
|
|
@@ -39,7 +39,7 @@ export const ApprovalBox = React.memo(function ApprovalBox({ toolName, descripti
|
|
|
39
39
|
{ label: "[t]rust all write/edit/bash this session", option: "trustAll" },
|
|
40
40
|
{ label: "[n]o — deny this call", option: "no" },
|
|
41
41
|
];
|
|
42
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.border.style, borderColor: theme.border.approval, paddingX: theme.spacing.pickerPadX, children: [_jsxs(Text, { bold: true, color: theme.color.warning, children: [theme.symbol.warningMark, " Atom permission \u2014 allow this tool?"] }), _jsx(Text, { bold: true, children: approvalTitle(toolName) }), _jsx(Text, { color: theme.color.code, children: approvalPreview(toolName, description) }), diff ? _jsx(SideBySideDiffView, { oldText: diff.oldText, newText: diff.newText, lang: diff.lang
|
|
42
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.border.style, borderColor: theme.border.approval, paddingX: theme.spacing.pickerPadX, children: [_jsxs(Text, { bold: true, color: theme.color.warning, children: [theme.symbol.warningMark, " Atom permission \u2014 allow this tool?"] }), _jsx(Text, { bold: true, children: approvalTitle(toolName) }), _jsx(Text, { color: theme.color.code, children: approvalPreview(toolName, description) }), diff ? _jsx(SideBySideDiffView, { oldText: diff.oldText, newText: diff.newText, lang: diff.lang }) : null, rows.map((r, i) => (_jsxs(Text, { color: i === selected ? theme.color.selection : undefined, children: [i === selected ? `${theme.symbol.select} ` : theme.spacing.rowIndent, r.label] }, r.option))), _jsx(Text, { dimColor: true, children: "\u2191/\u2193 + Enter selects \u00B7 y/a/t/n shortcuts \u00B7 Esc denies" })] }));
|
|
43
43
|
});
|
|
44
44
|
export const QuestionBox = React.memo(function QuestionBox({ question, options, allowCustom, askCustom, askSelIndex }) {
|
|
45
45
|
questionRenderProbe.count += 1;
|
package/dist/ui/palette.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Box, Text } from "ink";
|
|
|
9
9
|
import { PickerMoreAbove, PickerMoreBelow, pickerWindow } from "./pickers.js";
|
|
10
10
|
import { theme } from "./theme.js";
|
|
11
11
|
export const PALETTE_WINDOW = 12;
|
|
12
|
-
export const PALETTE_CATEGORY_ORDER = ["Model", "Session", "Tools", "Skills", "Flow", "Help"];
|
|
12
|
+
export const PALETTE_CATEGORY_ORDER = ["Model", "Session", "Tools", "Skills", "Flow", "Extensions", "Help"];
|
|
13
13
|
const PALETTE_CATEGORIES = {
|
|
14
14
|
"/model": "Model",
|
|
15
15
|
"/provider": "Model",
|
package/dist/ui/side-by-side.js
CHANGED
|
@@ -14,7 +14,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
// (code-point safe); below NARROW_COLUMNS the view degrades to the
|
|
15
15
|
// stacked unified DiffView instead of destroying the layout.
|
|
16
16
|
// - computed once per mount (useMemo, keyed on inputs + pane width) and
|
|
17
|
-
//
|
|
17
|
+
// rendered whole (an explicit maxRows windows it when a caller passes one)
|
|
18
|
+
// — never recomputed per tick, never floods via re-computation.
|
|
18
19
|
// All paint comes from ui/theme tokens.
|
|
19
20
|
import React from "react";
|
|
20
21
|
import { Box, Text } from "ink";
|
|
@@ -24,10 +25,11 @@ import { DiffView, LineBody } from "./diff-view.js";
|
|
|
24
25
|
import { theme } from "./theme.js";
|
|
25
26
|
// Below this width two panes cannot breathe — stack unified instead.
|
|
26
27
|
export const SBS_NARROW_COLUMNS = 70;
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
28
|
+
// Uncapped: views render the full row list (smooth via per-mount useMemo +
|
|
29
|
+
// append-once Static + word-token/Myers fallbacks in the engine). maxRows
|
|
30
|
+
// remains as an opt-in window for callers/tests that want a collapsed tail.
|
|
31
|
+
// Retained for compatibility.
|
|
32
|
+
export const TRANSCRIPT_DIFF_MAX_LINES = Infinity;
|
|
31
33
|
function truncateTo(s, width) {
|
|
32
34
|
const chars = [...s];
|
|
33
35
|
if (chars.length <= width)
|
package/dist/ui/status-bar.js
CHANGED
|
@@ -12,6 +12,47 @@ import React from "react";
|
|
|
12
12
|
import { Box, Text } from "ink";
|
|
13
13
|
import { formatTokenSegment } from "../context-windows.js";
|
|
14
14
|
import { theme } from "./theme.js";
|
|
15
|
+
// Default objective budget for the goal segment: compact enough to share the
|
|
16
|
+
// line with the pinned model/token/mode segments at 100 columns.
|
|
17
|
+
export const GOAL_STATUS_OBJECTIVE_CHARS = 32;
|
|
18
|
+
// Truncate an objective to n chars max (`…` tail keeps the start, which
|
|
19
|
+
// carries the verb). n < 4 yields "" (the caller drops the segment instead).
|
|
20
|
+
export function truncateGoalObjective(objective, max = GOAL_STATUS_OBJECTIVE_CHARS) {
|
|
21
|
+
const text = typeof objective === "string" ? objective : "";
|
|
22
|
+
if (text.length <= max)
|
|
23
|
+
return text;
|
|
24
|
+
if (max < 4)
|
|
25
|
+
return "";
|
|
26
|
+
return `${text.slice(0, max - 1)}…`;
|
|
27
|
+
}
|
|
28
|
+
// Full goal segment at the default budget, or null when no goal is live.
|
|
29
|
+
// Paused reads distinct from active (`[paused]` vs `[active]`).
|
|
30
|
+
export function formatGoalSegment(goal, max = GOAL_STATUS_OBJECTIVE_CHARS) {
|
|
31
|
+
if (!goal || typeof goal.objective !== "string" || goal.objective.length === 0)
|
|
32
|
+
return null;
|
|
33
|
+
const state = goal.active === true ? "active" : "paused";
|
|
34
|
+
return `goal: ${truncateGoalObjective(goal.objective, max)} [${state}]`;
|
|
35
|
+
}
|
|
36
|
+
// Fit the goal segment into `room` chars (the width left after every other
|
|
37
|
+
// segment): full text when it fits, a shorter truncation when it almost
|
|
38
|
+
// fits, null (drop the segment) when even a stub would displace the line.
|
|
39
|
+
// Never throws; never returns "".
|
|
40
|
+
export function fitGoalSegment(goal, room) {
|
|
41
|
+
if (!goal || typeof goal.objective !== "string" || goal.objective.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
if (typeof room !== "number" || !Number.isFinite(room) || room <= 0)
|
|
44
|
+
return null;
|
|
45
|
+
const state = goal.active === true ? "active" : "paused";
|
|
46
|
+
const full = `goal: ${goal.objective} [${state}]`;
|
|
47
|
+
if (full.length <= room)
|
|
48
|
+
return full;
|
|
49
|
+
// Room for at least 4 objective chars plus the fixed framing, else drop.
|
|
50
|
+
const overhead = `goal: [${state}]`.length + 1;
|
|
51
|
+
const allow = Math.floor(room - overhead);
|
|
52
|
+
if (allow < 4)
|
|
53
|
+
return null;
|
|
54
|
+
return `goal: ${truncateGoalObjective(goal.objective, allow)} [${state}]`;
|
|
55
|
+
}
|
|
15
56
|
// ~/… collapse + tail-cut: informative, never a full scroll of nesting.
|
|
16
57
|
// Further shrinking for tight widths goes through shrinkTo below (the bar
|
|
17
58
|
// measures first and only renders what fits).
|
|
@@ -34,9 +75,13 @@ export function shrinkTo(s, n) {
|
|
|
34
75
|
// render (same-props parent churn — token paints, keystrokes, unrelated
|
|
35
76
|
// ticks — must skip it; only changed props repaint).
|
|
36
77
|
export const statusBarRenderProbe = { count: 0 };
|
|
37
|
-
export const StatusBar = React.memo(function StatusBar({ provider, model, usageTotals, contextLoad, reasoningDisplay, mode, trustAll, busy, activity, phaseLabel, elapsedSecs, stalled, approvalPending, cwd, branch, columns = 100, }) {
|
|
78
|
+
export const StatusBar = React.memo(function StatusBar({ provider, model, usageTotals, contextLoad, reasoningDisplay, mode, trustAll, busy, activity, phaseLabel, elapsedSecs, stalled, approvalPending, cwd, branch, columns = 100, extensionStatus, goal, }) {
|
|
38
79
|
statusBarRenderProbe.count += 1;
|
|
39
80
|
const bar = theme.symbol.bar;
|
|
81
|
+
// Extension guest slot (ticket 10): pre-budgeted text renders only when
|
|
82
|
+
// the full line still fits — the fixed-width contract above. The `+ 3`
|
|
83
|
+
// is the ` ${bar} ` separator the segment carries with it.
|
|
84
|
+
const hasExt = typeof extensionStatus === "string" && extensionStatus.length > 0;
|
|
40
85
|
if (!busy) {
|
|
41
86
|
const token = formatTokenSegment(usageTotals, model, contextLoad);
|
|
42
87
|
const trust = trustAll && mode !== "plan" ? "+trust" : "";
|
|
@@ -46,7 +91,8 @@ export const StatusBar = React.memo(function StatusBar({ provider, model, usageT
|
|
|
46
91
|
// order: branch → cwd tail → the whole segment.
|
|
47
92
|
const tail = `reasoning: ${reasoningDisplay} ${bar} mode: ${mode}${trust}`;
|
|
48
93
|
const baseLen = `${provider}/${model} ${bar} ${token} ${bar} ${bar} ${tail}`.length;
|
|
49
|
-
const
|
|
94
|
+
const showExt = hasExt && baseLen + extensionStatus.length + 3 + 2 <= columns;
|
|
95
|
+
const avail = columns - baseLen - (showExt ? extensionStatus.length + 3 : 0);
|
|
50
96
|
let loc = null;
|
|
51
97
|
if (cwd) {
|
|
52
98
|
const branchPart = branch ? ` : ${branch}` : "";
|
|
@@ -61,15 +107,44 @@ export const StatusBar = React.memo(function StatusBar({ provider, model, usageT
|
|
|
61
107
|
loc = shrunk ? shrunk : null;
|
|
62
108
|
}
|
|
63
109
|
}
|
|
64
|
-
|
|
110
|
+
// Goal segment (ticket 09): lowest-priority builtin — it takes only the
|
|
111
|
+
// width left after every other segment and drops whole rather than push
|
|
112
|
+
// the line past `columns`. Hidden entirely with no goal.
|
|
113
|
+
const lineSoFar = baseLen + (showExt ? extensionStatus.length + 3 : 0) + (loc ? loc.length + 3 : 0);
|
|
114
|
+
const goalSeg = fitGoalSegment(goal ?? null, columns - lineSoFar - 2);
|
|
115
|
+
return (_jsx(Box, { marginTop: theme.spacing.statusMarginTop, children: _jsxs(Text, { dimColor: true, children: [provider, "/", model, " ", bar, " ", token, showExt ? (_jsxs(_Fragment, { children: [" ", bar, " ", extensionStatus] })) : null, loc ? (_jsxs(_Fragment, { children: [" ", bar, " ", loc] })) : null, " ", bar, " reasoning: ", reasoningDisplay, " ", bar, " mode: ", mode, trust ? "+trust" : null, goalSeg ? (_jsxs(_Fragment, { children: [" ", bar, " ", goalSeg] })) : null] }) }));
|
|
65
116
|
}
|
|
66
117
|
// Busy layout prioritizes activity + clock + interrupt hint; the mode
|
|
67
118
|
// stays pinned (it used to vanish while working), and the reasoning
|
|
68
119
|
// effort stays visible (it used to vanish while working). The activity text
|
|
69
120
|
// shrinks to fit so `esc stops` never wraps away.
|
|
70
121
|
const busyTrust = trustAll && mode !== "plan" ? "+trust" : "";
|
|
71
|
-
const
|
|
122
|
+
const busyToken = formatTokenSegment(usageTotals, model, contextLoad);
|
|
123
|
+
// Goal segment (ticket 09): a guest in the fixed part — capped at 48
|
|
124
|
+
// chars and rendered only when the FULL activity text still fits beside
|
|
125
|
+
// it. Otherwise the goal drops whole and every existing segment renders
|
|
126
|
+
// exactly as with no goal (the goal never displaces, same precedent as
|
|
127
|
+
// the extension guest above). The clock, token, mode, and esc-hint
|
|
128
|
+
// segments never move for it either way.
|
|
129
|
+
const busyGoalSeg = fitGoalSegment(goal ?? null, 48);
|
|
130
|
+
const busyGoalCandidate = busyGoalSeg ? ` ${bar} ${busyGoalSeg}` : "";
|
|
131
|
+
const activityFull = activity ?? phaseLabel;
|
|
132
|
+
const busyCore = ` ${bar} ${elapsedSecs}s ${bar} ${busyToken} ${bar} reasoning: ${reasoningDisplay} ${bar} mode: ${mode}${busyTrust}`;
|
|
133
|
+
const busyTail = ` ${bar} esc stops`;
|
|
134
|
+
const busyExtCandidate = hasExt && `${busyCore}${busyGoalCandidate}${busyTail}`.length + extensionStatus.length + 3 + 2 <= columns
|
|
135
|
+
? ` ${bar} ${extensionStatus}`
|
|
136
|
+
: "";
|
|
137
|
+
const withGoalFixed = `${busyCore}${busyExtCandidate}${busyGoalCandidate}${busyTail}`;
|
|
138
|
+
// Room check against the unfitted activity text: when it no longer fits
|
|
139
|
+
// whole with the goal aboard, the goal yields (drop whole, recompute).
|
|
140
|
+
const busyGoalPart = busyGoalCandidate !== "" &&
|
|
141
|
+
withGoalFixed.length + activityFull.length + 2 <= columns
|
|
142
|
+
? busyGoalCandidate
|
|
143
|
+
: "";
|
|
144
|
+
const busyNoExt = `${busyCore}${busyGoalPart}${busyTail}`;
|
|
145
|
+
const showBusyExt = hasExt && busyNoExt.length + extensionStatus.length + 3 + 2 <= columns;
|
|
146
|
+
const busyFixed = ` ${bar} ${elapsedSecs}s${showBusyExt ? ` ${bar} ${extensionStatus}` : ""} ${bar} ${busyToken} ${bar} reasoning: ${reasoningDisplay} ${bar} mode: ${mode}${busyTrust}${busyGoalPart} ${bar} esc stops`;
|
|
72
147
|
const busyAvail = columns - busyFixed.length - 2;
|
|
73
|
-
const activityText = shrinkTo(
|
|
148
|
+
const activityText = shrinkTo(activityFull, Math.max(0, busyAvail));
|
|
74
149
|
return (_jsx(Box, { marginTop: theme.spacing.statusMarginTop, children: _jsxs(Text, { dimColor: true, children: [_jsxs(Text, { color: theme.color.activity, children: [theme.symbol.workTool, " ", activityText] }), busyFixed, stalled && !approvalPending ? ` ${bar} waiting${theme.symbol.ellipsis}` : null, approvalPending ? (_jsxs(Text, { color: theme.color.warning, children: [" ", bar, " waiting approval"] })) : null] }) }));
|
|
75
150
|
});
|
package/dist/ui/transcript.js
CHANGED
|
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
// All paint comes from ui/theme tokens — no literal colors or glyphs here.
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { Box, Static, Text } from "ink";
|
|
8
|
-
import { SideBySideDiffView
|
|
8
|
+
import { SideBySideDiffView } from "./side-by-side.js";
|
|
9
9
|
import { ErrorCard, classifyToolError } from "./errors.js";
|
|
10
10
|
import { MarkdownText, ToolLine } from "./markdown.js";
|
|
11
11
|
import { theme } from "./theme.js";
|
|
@@ -73,9 +73,9 @@ export function renderTranscriptItem(item) {
|
|
|
73
73
|
// write/edit label swallowed by pairing (success line immediately
|
|
74
74
|
// followed by an error line) keeps its committed diff above the card.
|
|
75
75
|
const labelDiff = item.label?.diff;
|
|
76
|
-
return (_jsxs(React.Fragment, { children: [item.label ? _jsx(ToolLine, { content: item.label.content, ms: item.label.ms }) : null, labelDiff && !item.label?.error ? (_jsx(SideBySideDiffView, { oldText: labelDiff.oldText, newText: labelDiff.newText, lang: labelDiff.lang
|
|
76
|
+
return (_jsxs(React.Fragment, { children: [item.label ? _jsx(ToolLine, { content: item.label.content, ms: item.label.ms }) : null, labelDiff && !item.label?.error ? (_jsx(SideBySideDiffView, { oldText: labelDiff.oldText, newText: labelDiff.newText, lang: labelDiff.lang })) : null, _jsx(ErrorCard, { classified: classified })] }, i));
|
|
77
77
|
}
|
|
78
|
-
return (_jsxs(React.Fragment, { children: [_jsx(ToolLine, { content: t.content, error: t.error, ms: t.ms }), t.diff && !t.error ? (_jsx(SideBySideDiffView, { oldText: t.diff.oldText, newText: t.diff.newText, lang: t.diff.lang
|
|
78
|
+
return (_jsxs(React.Fragment, { children: [_jsx(ToolLine, { content: t.content, error: t.error, ms: t.ms }), t.diff && !t.error ? (_jsx(SideBySideDiffView, { oldText: t.diff.oldText, newText: t.diff.newText, lang: t.diff.lang })) : null] }, i));
|
|
79
79
|
}
|
|
80
80
|
return (_jsxs(Box, { flexDirection: "column", marginBottom: theme.spacing.turnGap, children: [_jsx(Text, { children: _jsxs(Text, { color: theme.color.assistant, bold: true, children: [theme.symbol.speakerAssistant, " "] }) }), _jsx(MarkdownText, { text: t.content })] }, i));
|
|
81
81
|
}
|