@matterailab/orbcode 0.3.4 → 0.4.1

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 CHANGED
@@ -185,10 +185,9 @@ sessions.
185
185
  | ------------------------- | ------- | ---------- | ---------------------- |
186
186
  | `axon-eido-3-code-pro` | 400k | 64k | $3/M in · $9/M out |
187
187
  | `axon-eido-3-code-mini` | 400k | 64k | $1.5/M in · $4.5/M out |
188
- | `axon-code-2-5-pro` | 400k | 64k | $2/M in · $6/M out |
189
- | `axon-code-2-5-mini` | 400k | 64k | free |
188
+ | `axon-eido-3-flash` | 200k | 64k | free |
190
189
 
191
- `axon-eido-3-code-mini` is the default. All four support native JSON tool calls
190
+ `axon-eido-3-code-mini` is the default. All three support native JSON tool calls
192
191
  and image input. Cost comes from the API's usage chunks (`is_byok`-aware) and is
193
192
  shown in the status bar.
194
193
 
@@ -298,6 +297,7 @@ MatterAI gateway untouched.
298
297
  | `/status` | version, model, account, gateway, context usage, cost, approval modes |
299
298
  | `/usage` | fetch plan usage |
300
299
  | `/init` | analyze the codebase and create/improve `AGENTS.md` in the repo's `.orb/` directory |
300
+ | `/task` | reference a previous session as context for the current task — opens a picker if no ID is given |
301
301
  | `/link` | link other repos on your machine so changes here are checked against them (enter a folder path) |
302
302
  | `/mcp` | manage MCP servers — enable, disable, reconnect, view status & tool counts |
303
303
  | `/login` | start the browser sign-in flow |
@@ -95,28 +95,17 @@ export const ANTHROPIC_MODELS = {
95
95
  * interactive picker for now.
96
96
  */
97
97
  export const BUILTIN_AXON_MODELS = {
98
- "axon-code-2-5-mini": {
99
- id: "axon-code-2-5-mini",
100
- name: "Axon Code 2.5 Mini (free)",
101
- description: "Axon Mini is an general purpose super intelligent LLM coding model for low-effort day-to-day tasks",
102
- contextWindow: 400000,
98
+ "axon-eido-3-flash": {
99
+ id: "axon-eido-3-flash",
100
+ name: "Axon Eido 3 Flash (free)",
101
+ description: "Axon Eido is a fast and low cost general purpose model for low-effort day-to-day tasks",
102
+ contextWindow: 200000,
103
103
  maxOutputTokens: 64000,
104
104
  supportsImages: true,
105
- inputPrice: 0.0000005,
106
- outputPrice: 0.0000015,
105
+ inputPrice: 0.0,
106
+ outputPrice: 0.0,
107
107
  free: true,
108
108
  },
109
- "axon-code-2-5-pro": {
110
- id: "axon-code-2-5-pro",
111
- name: "Axon Code 2.5 Pro",
112
- description: "Axon Code 2.5 Pro is the next-generation of Axon Code for coding tasks, currently in experimental stage.",
113
- contextWindow: 400000,
114
- maxOutputTokens: 64000,
115
- supportsImages: true,
116
- inputPrice: 0.000002,
117
- outputPrice: 0.000006,
118
- free: false,
119
- },
120
109
  "axon-eido-3-code-pro": {
121
110
  id: "axon-eido-3-code-pro",
122
111
  name: "Axon Eido 3 Pro",
package/dist/ui/App.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
3
- import { Box, Static, Text, useApp, useInput } from "ink";
3
+ import { Box, Text, useApp, useInput, useStdout } from "ink";
4
4
  import open from "open";
5
5
  import * as path from "node:path";
6
6
  import { COLORS, VERSION } from "../branding.js";
@@ -226,6 +226,9 @@ function rowId() {
226
226
  }
227
227
  export function App({ initialView, initialPrompt, initialSession, systemPromptOverride, updateCheck, }) {
228
228
  const { exit } = useApp();
229
+ const { stdout } = useStdout();
230
+ const termRows = stdout?.rows ?? 24;
231
+ const termCols = stdout?.columns ?? 80;
229
232
  const [settings, setSettings] = useState(() => loadSettings());
230
233
  const [view, setView] = useState(initialView ?? (getAuthToken(settings) ? "chat" : "login"));
231
234
  const [updateInfo, setUpdateInfo] = useState(null);
@@ -266,7 +269,6 @@ export function App({ initialView, initialPrompt, initialSession, systemPromptOv
266
269
  const [mcpMigrationEntries, setMcpMigrationEntries] = useState(null);
267
270
  // Project-scope MCP servers awaiting the user's approval at startup.
268
271
  const [pendingMcpApproval, setPendingMcpApproval] = useState(null);
269
- const [staticKey, setStaticKey] = useState(0);
270
272
  const [tasks, setTasks] = useState("");
271
273
  const [contextTokens, setContextTokens] = useState(0);
272
274
  const [totalCost, setTotalCost] = useState(0);
@@ -356,7 +358,6 @@ export function App({ initialView, initialPrompt, initialSession, systemPromptOv
356
358
  modelName: getModel(loadSettings().model).name,
357
359
  },
358
360
  ]);
359
- setStaticKey((k) => k + 1);
360
361
  }, []);
361
362
  const handleEvent = useCallback((event) => {
362
363
  switch (event.type) {
@@ -1068,7 +1069,6 @@ export function App({ initialView, initialPrompt, initialSession, systemPromptOv
1068
1069
  if (process.stdout.isTTY) {
1069
1070
  process.stdout.write("\x1b[2J\x1b[H");
1070
1071
  }
1071
- setStaticKey((k) => k + 1);
1072
1072
  }
1073
1073
  });
1074
1074
  const handleLogin = useCallback((token, profile) => {
@@ -1103,48 +1103,170 @@ export function App({ initialView, initialPrompt, initialSession, systemPromptOv
1103
1103
  !resumableSessions &&
1104
1104
  !taskPickerSessions &&
1105
1105
  !linkManagerOpen;
1106
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: rows, children: (row) => _jsx(RowView, { row: row }, row.id) }, staticKey), view === "login" ? (_jsx(LoginSection, { onLogin: handleLogin })) : (_jsxs(Box, { flexDirection: "column", children: [updateInfo?.updateAvailable && updateInfo.latest && (_jsxs(Box, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: COLORS.warning, paddingX: 2, alignSelf: "flex-start", children: [_jsxs(Text, { color: COLORS.warning, bold: true, children: ["\u2191 Update available: v", updateInfo.current, " \u2192 v", updateInfo.latest] }), _jsxs(Text, { children: ["Run ", _jsx(Text, { color: COLORS.accent, children: "orbcode update" }), " to install the latest version, then relaunch."] })] })), streamingReasoning && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: COLORS.thinking, italic: true, children: "\u2726 Thinking\u2026" }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, italic: true, children: tail(streamingReasoning, expandReasoningRef.current ? 30 : 3) }) })] })), streamingText && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: COLORS.primary, children: "\u25CF " }), streamingText] }) })), taskLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: "Tasks" }), taskLines.slice(0, 10).map((line, i) => (_jsx(Text, { dimColor: /^[-*]\s*\[x\]/i.test(line), children: line
1107
- .replace(/^[-*]\s*\[x\]/i, " ■")
1108
- .replace(/^[-*]\s*\[-\]/, " ◧")
1109
- .replace(/^[-*]\s*\[ \]/, " □") }, i))), taskLines.length > 10 && (_jsxs(Text, { dimColor: true, children: [" \u2026 ", taskLines.length - 10, " more"] }))] })), modelPickerOpen && (_jsx(Box, { marginTop: 1, children: _jsx(ModelPicker, { currentId: settings.model, onSelect: (modelId) => {
1110
- setModelPickerOpen(false);
1111
- switchModel(modelId);
1112
- }, onCancel: () => setModelPickerOpen(false) }) })), resumableSessions && (_jsx(Box, { marginTop: 1, children: _jsx(SessionPicker, { sessions: resumableSessions, onSelect: handleResume, onCancel: () => setResumableSessions(null) }) })), taskPickerSessions && (_jsx(Box, { marginTop: 1, children: _jsx(SessionPicker, { sessions: taskPickerSessions, title: "Reference a previous task", onSelect: handleTaskSelect, onCancel: () => setTaskPickerSessions(null) }) })), linkManagerOpen && (_jsx(Box, { marginTop: 1, children: _jsx(LinkManager, { links: links, status: linkStatus, onAdd: (input) => {
1113
- const result = addLink(process.cwd(), input);
1114
- setLinkStatus(result.message);
1115
- if (result.ok)
1116
- setLinks(loadLinks(process.cwd()));
1117
- }, onRemove: (link) => {
1118
- removeLink(process.cwd(), link.path);
1106
+ // ── Viewport management ─────────────────────────────────────────────
1107
+ // Instead of <Static> (which permanently writes to stdout and causes
1108
+ // uncontrolled scrolling), we render everything in the dynamic region and
1109
+ // only show the rows that fit above the pinned input box + status bar.
1110
+ // This keeps the input box always at the bottom of the terminal.
1111
+ // Height of the fixed bottom chrome (input box border + text + status bar +
1112
+ // margins). Intentionally slightly overestimated so we never push the
1113
+ // input box off-screen.
1114
+ let bottomHeight = 8;
1115
+ if (queuedMessages.length > 0)
1116
+ bottomHeight += 2 + Math.min(5, queuedMessages.length);
1117
+ if (pendingApproval ||
1118
+ pendingFollowup ||
1119
+ pendingHookTrust ||
1120
+ pendingMcpApproval ||
1121
+ modelPickerOpen ||
1122
+ mcpPickerOpen ||
1123
+ mcpMigrationEntries ||
1124
+ resumableSessions ||
1125
+ taskPickerSessions ||
1126
+ linkManagerOpen)
1127
+ bottomHeight += 10;
1128
+ // Height of the middle dynamic elements (between rows and input box).
1129
+ let middleHeight = 0;
1130
+ if (streamingReasoning)
1131
+ middleHeight += 4;
1132
+ if (taskLines.length > 0)
1133
+ middleHeight += 1 + Math.min(10, taskLines.length);
1134
+ if (busy && !streamingText && !streamingReasoning)
1135
+ middleHeight += 1;
1136
+ if (updateInfo?.updateAvailable && updateInfo.latest)
1137
+ middleHeight += 4;
1138
+ // Available height for committed rows + streaming text.
1139
+ const availableHeight = Math.max(3, termRows - bottomHeight - middleHeight);
1140
+ // Estimate how many terminal lines a row will occupy.
1141
+ const wrapWidth = Math.max(20, termCols - 4);
1142
+ const visibleRows = useMemo(() => {
1143
+ let used = 0;
1144
+ let start = rows.length;
1145
+ for (let i = rows.length - 1; i >= 0; i--) {
1146
+ const h = estimateRowLines(rows[i], wrapWidth);
1147
+ if (used + h > availableHeight)
1148
+ break;
1149
+ used += h;
1150
+ start = i;
1151
+ }
1152
+ return rows.slice(start);
1153
+ }, [rows, availableHeight, wrapWidth]);
1154
+ // Cap streaming text to the space left after committed rows.
1155
+ const rowsHeight = useMemo(() => visibleRows.reduce((s, r) => s + estimateRowLines(r, wrapWidth), 0), [visibleRows, wrapWidth]);
1156
+ const streamingMaxLines = Math.max(3, availableHeight - rowsHeight);
1157
+ const streamingDisplay = streamingText
1158
+ ? tailForHeight(streamingText, streamingMaxLines, wrapWidth)
1159
+ : "";
1160
+ const streamingReasoningDisplay = streamingReasoning
1161
+ ? tailForHeight(streamingReasoning, 3, wrapWidth)
1162
+ : "";
1163
+ // Spacer to push the input box to the bottom when content is short.
1164
+ const streamingHeight = streamingDisplay
1165
+ ? Math.min(streamingMaxLines, wrapHeight(streamingDisplay, wrapWidth))
1166
+ : 0;
1167
+ const spacerHeight = Math.max(0, availableHeight - rowsHeight - streamingHeight);
1168
+ return (_jsx(Box, { flexDirection: "column", children: view === "login" ? (_jsx(LoginSection, { onLogin: handleLogin })) : (_jsxs(Box, { flexDirection: "column", children: [visibleRows.map((row) => (_jsx(RowView, { row: row }, row.id))), updateInfo?.updateAvailable && updateInfo.latest && (_jsxs(Box, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: COLORS.warning, paddingX: 2, alignSelf: "flex-start", children: [_jsxs(Text, { color: COLORS.warning, bold: true, children: ["\u2191 Update available: v", updateInfo.current, " \u2192 v", updateInfo.latest] }), _jsxs(Text, { children: ["Run ", _jsx(Text, { color: COLORS.accent, children: "orbcode update" }), " to install the latest version, then relaunch."] })] })), streamingReasoning && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: COLORS.thinking, italic: true, children: "\u2726 Thinking\u2026" }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, italic: true, children: streamingReasoningDisplay }) })] })), streamingDisplay && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: COLORS.primary, children: "\u25CF " }), streamingDisplay] }) })), spacerHeight > 0 && _jsx(Box, { height: spacerHeight }), taskLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 1, children: [_jsx(Text, { dimColor: true, bold: true, children: "Tasks" }), taskLines.slice(0, 10).map((line, i) => (_jsx(Text, { dimColor: /^[-*]\s*\[x\]/i.test(line), children: line
1169
+ .replace(/^[-*]\s*\[x\]/i, " ■")
1170
+ .replace(/^[-*]\s*\[-\]/, " ◧")
1171
+ .replace(/^[-*]\s*\[ \]/, " □") }, i))), taskLines.length > 10 && (_jsxs(Text, { dimColor: true, children: [" \u2026 ", taskLines.length - 10, " more"] }))] })), modelPickerOpen && (_jsx(Box, { marginTop: 1, children: _jsx(ModelPicker, { currentId: settings.model, onSelect: (modelId) => {
1172
+ setModelPickerOpen(false);
1173
+ switchModel(modelId);
1174
+ }, onCancel: () => setModelPickerOpen(false) }) })), resumableSessions && (_jsx(Box, { marginTop: 1, children: _jsx(SessionPicker, { sessions: resumableSessions, onSelect: handleResume, onCancel: () => setResumableSessions(null) }) })), taskPickerSessions && (_jsx(Box, { marginTop: 1, children: _jsx(SessionPicker, { sessions: taskPickerSessions, title: "Reference a previous task", onSelect: handleTaskSelect, onCancel: () => setTaskPickerSessions(null) }) })), linkManagerOpen && (_jsx(Box, { marginTop: 1, children: _jsx(LinkManager, { links: links, status: linkStatus, onAdd: (input) => {
1175
+ const result = addLink(process.cwd(), input);
1176
+ setLinkStatus(result.message);
1177
+ if (result.ok)
1119
1178
  setLinks(loadLinks(process.cwd()));
1120
- setLinkStatus(`Unlinked ${path.basename(link.path)}`);
1121
- }, onClose: () => setLinkManagerOpen(false) }) })), pendingHookTrust && (_jsx(Box, { marginTop: 1, children: _jsx(HookTrustPrompt, { cwd: process.cwd(), commands: pendingHookTrust.commands, onDecision: resolveHookTrust }) })), pendingMcpApproval && (_jsx(Box, { marginTop: 1, children: _jsx(McpApprovalPrompt, { serverNames: pendingMcpApproval, onApprove: resolveMcpApproval }) })), mcpPickerOpen && mcpManagerRef.current && (_jsx(Box, { marginTop: 1, children: _jsx(McpPicker, { manager: mcpManagerRef.current, onChanged: () => {
1122
- const m = mcpManagerRef.current;
1123
- if (m)
1124
- saveMcpApproval(process.cwd(), m.getEnabled(), m.getDisabled());
1125
- }, onCancel: () => setMcpPickerOpen(false), onDeleted: handleMcpServerDeleted }) })), mcpMigrationEntries && (_jsx(Box, { marginTop: 1, children: _jsx(McpMigrationPicker, { entries: mcpMigrationEntries, onCancel: () => setMcpMigrationEntries(null), onConfirm: (selected) => {
1126
- setMcpMigrationEntries(null);
1127
- resolveMcpMigration(selected);
1128
- } }) })), pendingApproval && (_jsx(Box, { marginTop: 1, children: _jsx(ApprovalPrompt, { request: pendingApproval.request, onDecision: (decision) => {
1129
- pendingApproval.resolve(decision);
1130
- setPendingApproval(null);
1131
- } }) })), pendingFollowup && (_jsx(Box, { marginTop: 1, children: _jsx(FollowupPrompt, { question: pendingFollowup.question, suggestions: pendingFollowup.suggestions, onAnswer: (answer) => {
1132
- pushRow({ kind: "user", text: answer });
1133
- pendingFollowup.resolve(answer);
1134
- setPendingFollowup(null);
1135
- } }) })), busy &&
1136
- !pendingApproval &&
1137
- !pendingFollowup &&
1138
- !pendingHookTrust &&
1139
- !pendingMcpApproval &&
1140
- !mcpPickerOpen &&
1141
- !mcpMigrationEntries &&
1142
- !streamingText &&
1143
- !streamingReasoning && (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: busyLabel }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [queuedMessages.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, children: [_jsxs(Text, { dimColor: true, bold: true, children: ["Queue (", queuedMessages.length, ")"] }), queuedMessages.slice(0, 5).map((msg, i) => (_jsxs(Text, { dimColor: true, children: [i + 1, ". ", truncateForQueue(msg).replace(/\n/g, "↵")] }, i))), queuedMessages.length > 5 && (_jsxs(Text, { dimColor: true, children: [" \u2026 ", queuedMessages.length - 5, " more"] }))] })), _jsx(InputBox, { active: inputActive, slashCommands: SLASH_COMMANDS, onSubmit: handleSubmit }), _jsx(StatusBar, { modelId: settings.model, contextTokens: contextTokens, totalCost: totalCost, state: busy ? busyLabel : "", approvalMode: approvalMode, busy: busy, title: sessionTitle, plan: usage?.plan, usagePercentage: usage?.usagePercentage, tieredUsage: usage?.tieredUsage })] })] }))] }));
1179
+ }, onRemove: (link) => {
1180
+ removeLink(process.cwd(), link.path);
1181
+ setLinks(loadLinks(process.cwd()));
1182
+ setLinkStatus(`Unlinked ${path.basename(link.path)}`);
1183
+ }, onClose: () => setLinkManagerOpen(false) }) })), pendingHookTrust && (_jsx(Box, { marginTop: 1, children: _jsx(HookTrustPrompt, { cwd: process.cwd(), commands: pendingHookTrust.commands, onDecision: resolveHookTrust }) })), pendingMcpApproval && (_jsx(Box, { marginTop: 1, children: _jsx(McpApprovalPrompt, { serverNames: pendingMcpApproval, onApprove: resolveMcpApproval }) })), mcpPickerOpen && mcpManagerRef.current && (_jsx(Box, { marginTop: 1, children: _jsx(McpPicker, { manager: mcpManagerRef.current, onChanged: () => {
1184
+ const m = mcpManagerRef.current;
1185
+ if (m)
1186
+ saveMcpApproval(process.cwd(), m.getEnabled(), m.getDisabled());
1187
+ }, onCancel: () => setMcpPickerOpen(false), onDeleted: handleMcpServerDeleted }) })), mcpMigrationEntries && (_jsx(Box, { marginTop: 1, children: _jsx(McpMigrationPicker, { entries: mcpMigrationEntries, onCancel: () => setMcpMigrationEntries(null), onConfirm: (selected) => {
1188
+ setMcpMigrationEntries(null);
1189
+ resolveMcpMigration(selected);
1190
+ } }) })), pendingApproval && (_jsx(Box, { marginTop: 1, children: _jsx(ApprovalPrompt, { request: pendingApproval.request, onDecision: (decision) => {
1191
+ pendingApproval.resolve(decision);
1192
+ setPendingApproval(null);
1193
+ } }) })), pendingFollowup && (_jsx(Box, { marginTop: 1, children: _jsx(FollowupPrompt, { question: pendingFollowup.question, suggestions: pendingFollowup.suggestions, onAnswer: (answer) => {
1194
+ pushRow({ kind: "user", text: answer });
1195
+ pendingFollowup.resolve(answer);
1196
+ setPendingFollowup(null);
1197
+ } }) })), busy &&
1198
+ !pendingApproval &&
1199
+ !pendingFollowup &&
1200
+ !pendingHookTrust &&
1201
+ !pendingMcpApproval &&
1202
+ !mcpPickerOpen &&
1203
+ !mcpMigrationEntries &&
1204
+ !streamingText &&
1205
+ !streamingReasoning && (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: busyLabel }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [queuedMessages.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginBottom: 1, children: [_jsxs(Text, { dimColor: true, bold: true, children: ["Queue (", queuedMessages.length, ")"] }), queuedMessages.slice(0, 5).map((msg, i) => (_jsxs(Text, { dimColor: true, children: [i + 1, ". ", truncateForQueue(msg).replace(/\n/g, "↵")] }, i))), queuedMessages.length > 5 && (_jsxs(Text, { dimColor: true, children: [" \u2026 ", queuedMessages.length - 5, " more"] }))] })), _jsx(InputBox, { active: inputActive, slashCommands: SLASH_COMMANDS, onSubmit: handleSubmit }), _jsx(StatusBar, { modelId: settings.model, contextTokens: contextTokens, totalCost: totalCost, state: busy ? busyLabel : "", approvalMode: approvalMode, busy: busy, title: sessionTitle, plan: usage?.plan, usagePercentage: usage?.usagePercentage, tieredUsage: usage?.tieredUsage })] })] })) }));
1206
+ }
1207
+ /** Count the number of terminal rows a block of text will occupy (accounting
1208
+ * for wrapping at `width` columns). */
1209
+ function wrapHeight(text, width) {
1210
+ return text.split("\n").reduce((sum, line) => sum + Math.max(1, Math.ceil(Math.max(1, line.length) / width)), 0);
1211
+ }
1212
+ /** Return the tail of `text` that fits within `maxLines` terminal rows,
1213
+ * accounting for line wrapping at `width` columns. */
1214
+ function tailForHeight(text, maxLines, width) {
1215
+ const lines = text.split("\n");
1216
+ const result = [];
1217
+ let height = 0;
1218
+ for (let i = lines.length - 1; i >= 0 && height < maxLines; i--) {
1219
+ const line = lines[i];
1220
+ const wrapped = Math.max(1, Math.ceil(Math.max(1, line.length) / width));
1221
+ if (height + wrapped > maxLines) {
1222
+ const remaining = maxLines - height;
1223
+ if (remaining > 0) {
1224
+ const chars = remaining * width;
1225
+ result.unshift(line.slice(-chars));
1226
+ }
1227
+ break;
1228
+ }
1229
+ result.unshift(line);
1230
+ height += wrapped;
1231
+ }
1232
+ return result.join("\n");
1144
1233
  }
1145
- function tail(text, lines) {
1146
- const all = text.split("\n").filter((l) => l.trim());
1147
- return all.slice(-lines).join("\n");
1234
+ /** Estimate how many terminal lines a committed row will occupy. Intentionally
1235
+ * slightly conservative (overestimates) so we never show too many rows and
1236
+ * push the input box off-screen. */
1237
+ function estimateRowLines(row, width) {
1238
+ const w = Math.max(20, width);
1239
+ function wrapped(text) {
1240
+ return text.split("\n").reduce((sum, line) => sum + Math.max(1, Math.ceil(Math.max(1, line.length) / w)), 0);
1241
+ }
1242
+ switch (row.kind) {
1243
+ case "header":
1244
+ return 9;
1245
+ case "user":
1246
+ return 1 + wrapped(row.text);
1247
+ case "assistant":
1248
+ return 1 + wrapped(row.text);
1249
+ case "reasoning":
1250
+ return row.expanded ? 1 + wrapped(row.text) : 1;
1251
+ case "tool": {
1252
+ let h = 1;
1253
+ if (row.diff) {
1254
+ h += Math.min(62, row.diff.split("\n").length) + 1;
1255
+ }
1256
+ else if (row.resultPreview) {
1257
+ h += wrapped(row.resultPreview);
1258
+ }
1259
+ return h;
1260
+ }
1261
+ case "info":
1262
+ return Math.max(1, wrapped(row.text));
1263
+ case "error":
1264
+ return Math.max(1, wrapped(row.text));
1265
+ case "completion":
1266
+ return 2 + wrapped(row.text);
1267
+ default:
1268
+ return 1;
1269
+ }
1148
1270
  }
1149
1271
  const QUEUE_PREVIEW_LIMIT = 80;
1150
1272
  function truncateForQueue(text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterailab/orbcode",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "OrbCode CLI — agentic coding in your terminal, powered by Axon models by MatterAI",
5
5
  "type": "module",
6
6
  "bin": {