@corenel/term 0.3.0 → 0.5.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 +1 -1
- package/dist/approvalText.d.ts +6 -0
- package/dist/approvalText.d.ts.map +1 -0
- package/dist/{chunk-HUVRQX53.js → chunk-IF6DCCK2.js} +118 -12
- package/dist/{chunk-2MGDL3I4.js → chunk-MAZLORMD.js} +2 -0
- package/dist/{chunk-5RPHYXND.js → chunk-TWFWLEIP.js} +4 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/ink/App.d.ts.map +1 -1
- package/dist/ink/ContextOverlay.d.ts +2 -1
- package/dist/ink/ContextOverlay.d.ts.map +1 -1
- package/dist/ink/HelpOverlay.d.ts +9 -2
- package/dist/ink/HelpOverlay.d.ts.map +1 -1
- package/dist/ink/McpOverlay.d.ts +2 -7
- package/dist/ink/McpOverlay.d.ts.map +1 -1
- package/dist/ink/Modal.d.ts +11 -2
- package/dist/ink/Modal.d.ts.map +1 -1
- package/dist/ink/index.d.ts +18 -0
- package/dist/ink/index.d.ts.map +1 -1
- package/dist/ink/index.js +293 -98
- package/dist/ink/overlayBudget.d.ts +27 -0
- package/dist/ink/overlayBudget.d.ts.map +1 -0
- package/dist/session.d.ts +51 -8
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +13 -5
- package/dist/state.d.ts +26 -2
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +3 -1
- package/package.json +5 -5
package/dist/ink/index.js
CHANGED
|
@@ -3,14 +3,19 @@ import {
|
|
|
3
3
|
ageWord,
|
|
4
4
|
formatContext,
|
|
5
5
|
mcpStatusWord,
|
|
6
|
-
parseMarkdown
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
parseMarkdown,
|
|
7
|
+
parseSuggestionChoice,
|
|
8
|
+
suggestionKeys,
|
|
9
|
+
suggestionLine
|
|
10
|
+
} from "../chunk-IF6DCCK2.js";
|
|
11
|
+
import {
|
|
12
|
+
SUGGESTION_ARM_DELAY_MS
|
|
13
|
+
} from "../chunk-MAZLORMD.js";
|
|
9
14
|
import {
|
|
10
15
|
TERM_COMMANDS,
|
|
11
16
|
filterCommands,
|
|
12
17
|
parseCommand
|
|
13
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-TWFWLEIP.js";
|
|
14
19
|
import "../chunk-M44CET5L.js";
|
|
15
20
|
import {
|
|
16
21
|
formatEndingChange
|
|
@@ -28,6 +33,7 @@ import { useCallback, useState as useState2, useSyncExternalStore } from "react"
|
|
|
28
33
|
import { render, useApp, useInput } from "ink";
|
|
29
34
|
import { PermissionService as PermissionService2 } from "@corenel/harness/guardrail/permission-service";
|
|
30
35
|
import { STANDARD_POLICY } from "@corenel/harness/guardrail/builtins";
|
|
36
|
+
import { approvalsHooks } from "@corenel/harness/approvals";
|
|
31
37
|
|
|
32
38
|
// src/ink/App.tsx
|
|
33
39
|
import { useEffect, useState } from "react";
|
|
@@ -175,47 +181,106 @@ function LiveToolRow({ tool }) {
|
|
|
175
181
|
|
|
176
182
|
// src/ink/McpOverlay.tsx
|
|
177
183
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
184
|
+
|
|
185
|
+
// src/ink/overlayBudget.ts
|
|
186
|
+
var RESERVED_CHROME_ROWS = 6;
|
|
187
|
+
function overlayBoxBudget(rows) {
|
|
188
|
+
if (rows === void 0) return Number.MAX_SAFE_INTEGER;
|
|
189
|
+
return Math.max(rows - RESERVED_CHROME_ROWS, 3);
|
|
190
|
+
}
|
|
191
|
+
function capList(items, cap, overflowLabel) {
|
|
192
|
+
const rowCap = Math.max(cap, 0);
|
|
193
|
+
if (items.length <= rowCap) return { shown: items, overflow: null };
|
|
194
|
+
if (rowCap <= 0) return { shown: [], overflow: null };
|
|
195
|
+
const shown = Math.max(rowCap - 1, 0);
|
|
196
|
+
const hidden = items.length - shown;
|
|
197
|
+
return { shown: items.slice(0, shown), overflow: overflowLabel(hidden) };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/ink/McpOverlay.tsx
|
|
178
201
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
179
202
|
function statusColor(word) {
|
|
180
203
|
if (word === "connected") return palette.good;
|
|
181
204
|
if (word === "unreachable") return palette.bad;
|
|
182
205
|
return palette.warn;
|
|
183
206
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
+
var BORDER_ROWS = 2;
|
|
208
|
+
function McpOverlay({ modal, rows }) {
|
|
209
|
+
let content = Math.max(overlayBoxBudget(rows) - BORDER_ROWS, 0);
|
|
210
|
+
const take = (n) => {
|
|
211
|
+
if (content < n) return false;
|
|
212
|
+
content -= n;
|
|
213
|
+
return true;
|
|
214
|
+
};
|
|
215
|
+
if (modal.login) {
|
|
216
|
+
const haveCore = take(1);
|
|
217
|
+
const showEsc = haveCore && modal.login.url !== null && take(1);
|
|
218
|
+
const showMessage = haveCore && modal.login.url !== null && take(1);
|
|
219
|
+
const showHeader2 = take(1);
|
|
220
|
+
const showFooter2 = take(1);
|
|
221
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: palette.tool, paddingX: 1, children: [
|
|
222
|
+
showHeader2 ? /* @__PURE__ */ jsx2(Text2, { color: palette.tool, bold: true, children: "MCP servers" }) : null,
|
|
223
|
+
haveCore && modal.login.url ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
224
|
+
showMessage ? /* @__PURE__ */ jsxs2(Text2, { color: palette.amber, children: [
|
|
225
|
+
"signing in to ",
|
|
226
|
+
modal.login.server,
|
|
227
|
+
" -- open this URL:"
|
|
228
|
+
] }) : null,
|
|
229
|
+
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: modal.login.url }),
|
|
230
|
+
showEsc ? /* @__PURE__ */ jsx2(Text2, { color: palette.dim, children: "esc cancels" }) : null
|
|
231
|
+
] }) : haveCore ? /* @__PURE__ */ jsxs2(Text2, { color: palette.amber, wrap: "truncate-end", children: [
|
|
207
232
|
"signing in to ",
|
|
208
233
|
modal.login.server,
|
|
209
|
-
" --
|
|
210
|
-
] }),
|
|
211
|
-
/* @__PURE__ */
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
234
|
+
" -- waiting for a login URL..."
|
|
235
|
+
] }) : null,
|
|
236
|
+
showFooter2 ? /* @__PURE__ */ jsxs2(Text2, { color: palette.dim, children: [
|
|
237
|
+
glyph.active,
|
|
238
|
+
" select ",
|
|
239
|
+
glyph.sep,
|
|
240
|
+
" a add ",
|
|
241
|
+
glyph.sep,
|
|
242
|
+
" x remove ",
|
|
243
|
+
glyph.sep,
|
|
244
|
+
" l login ",
|
|
245
|
+
glyph.sep,
|
|
246
|
+
" r refresh ",
|
|
247
|
+
glyph.sep,
|
|
248
|
+
" esc close"
|
|
249
|
+
] }) : null
|
|
250
|
+
] });
|
|
251
|
+
}
|
|
252
|
+
const showHeader = take(1);
|
|
253
|
+
const emptyRows = modal.servers.length === 0 && take(1);
|
|
254
|
+
const showFooter = take(1);
|
|
255
|
+
const { shown, overflow } = capList(modal.servers, content, (n) => `\u2026 ${n} more server${n === 1 ? "" : "s"}`);
|
|
256
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: palette.tool, paddingX: 1, children: [
|
|
257
|
+
showHeader ? /* @__PURE__ */ jsx2(Text2, { color: palette.tool, bold: true, children: "MCP servers" }) : null,
|
|
258
|
+
emptyRows ? /* @__PURE__ */ jsx2(Text2, { color: palette.dim, children: "none configured -- press a to add one" }) : /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
259
|
+
shown.map((s) => {
|
|
260
|
+
const i = modal.servers.indexOf(s);
|
|
261
|
+
const word = mcpStatusWord(s);
|
|
262
|
+
const isSelected = i === modal.selected;
|
|
263
|
+
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
264
|
+
/* @__PURE__ */ jsx2(Text2, { color: palette.ember, children: isSelected ? `${glyph.active} ` : " " }),
|
|
265
|
+
/* @__PURE__ */ jsx2(Text2, { bold: isSelected, wrap: "truncate-end", children: s.name }),
|
|
266
|
+
/* @__PURE__ */ jsx2(Text2, { color: palette.dim, children: " " }),
|
|
267
|
+
/* @__PURE__ */ jsx2(Text2, { color: statusColor(word), children: word }),
|
|
268
|
+
/* @__PURE__ */ jsxs2(Text2, { color: palette.dim, wrap: "truncate-end", children: [
|
|
269
|
+
" ",
|
|
270
|
+
s.toolCount,
|
|
271
|
+
" tool",
|
|
272
|
+
s.toolCount === 1 ? "" : "s",
|
|
273
|
+
", ",
|
|
274
|
+
ageWord(s.fetchedAt, Date.now())
|
|
275
|
+
] })
|
|
276
|
+
] }, s.name);
|
|
277
|
+
}),
|
|
278
|
+
overflow ? /* @__PURE__ */ jsxs2(Text2, { color: palette.dim, children: [
|
|
279
|
+
" ",
|
|
280
|
+
overflow
|
|
281
|
+
] }) : null
|
|
282
|
+
] }),
|
|
283
|
+
showFooter ? /* @__PURE__ */ jsxs2(Text2, { color: palette.dim, children: [
|
|
219
284
|
glyph.active,
|
|
220
285
|
" select ",
|
|
221
286
|
glyph.sep,
|
|
@@ -228,43 +293,58 @@ function McpOverlay({ modal }) {
|
|
|
228
293
|
" r refresh ",
|
|
229
294
|
glyph.sep,
|
|
230
295
|
" esc close"
|
|
231
|
-
] })
|
|
296
|
+
] }) : null
|
|
232
297
|
] });
|
|
233
298
|
}
|
|
234
299
|
|
|
235
300
|
// src/ink/HelpOverlay.tsx
|
|
236
301
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
237
302
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
238
|
-
|
|
303
|
+
var FIXED_CHROME_ROWS = 4;
|
|
304
|
+
function HelpOverlay({ rows }) {
|
|
239
305
|
const width = Math.max(...TERM_COMMANDS.map((c) => `${c.name} ${c.args ?? ""}`.trim().length));
|
|
306
|
+
const bodyBudget = Math.max(overlayBoxBudget(rows) - FIXED_CHROME_ROWS, 0);
|
|
307
|
+
const { shown, overflow } = capList(TERM_COMMANDS, bodyBudget, (n) => `\u2026 ${n} more command${n === 1 ? "" : "s"}`);
|
|
240
308
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", borderColor: palette.tool, paddingX: 1, children: [
|
|
241
309
|
/* @__PURE__ */ jsx3(Text3, { color: palette.tool, bold: true, children: "Commands" }),
|
|
242
|
-
/* @__PURE__ */
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
/* @__PURE__ */ jsxs3(
|
|
246
|
-
"
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
"
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
310
|
+
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
311
|
+
shown.map((c) => {
|
|
312
|
+
const usage = `${c.name} ${c.args ?? ""}`.trim();
|
|
313
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
314
|
+
/* @__PURE__ */ jsxs3(Text3, { color: palette.ember, wrap: "truncate-end", children: [
|
|
315
|
+
"/",
|
|
316
|
+
usage.padEnd(width)
|
|
317
|
+
] }),
|
|
318
|
+
/* @__PURE__ */ jsxs3(Text3, { color: palette.dim, wrap: "truncate-end", children: [
|
|
319
|
+
" ",
|
|
320
|
+
c.summary
|
|
321
|
+
] })
|
|
322
|
+
] }, c.name);
|
|
323
|
+
}),
|
|
324
|
+
overflow ? /* @__PURE__ */ jsxs3(Text3, { color: palette.dim, children: [
|
|
325
|
+
" ",
|
|
326
|
+
overflow
|
|
327
|
+
] }) : null
|
|
328
|
+
] }),
|
|
329
|
+
/* @__PURE__ */ jsx3(Text3, { color: palette.dim, children: "esc close" })
|
|
256
330
|
] });
|
|
257
331
|
}
|
|
258
332
|
|
|
259
333
|
// src/ink/ContextOverlay.tsx
|
|
260
334
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
261
335
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
262
|
-
|
|
263
|
-
|
|
336
|
+
var FIXED_CHROME_ROWS2 = 4;
|
|
337
|
+
function ContextOverlay({ snapshot, rows }) {
|
|
338
|
+
const lines = formatContext(snapshot).split("\n");
|
|
339
|
+
const bodyBudget = Math.max(overlayBoxBudget(rows) - FIXED_CHROME_ROWS2, 0);
|
|
340
|
+
const { shown, overflow } = capList(lines, bodyBudget, (n) => `\u2026 ${n} more line${n === 1 ? "" : "s"}`);
|
|
264
341
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: palette.tool, paddingX: 1, children: [
|
|
265
342
|
/* @__PURE__ */ jsx4(Text4, { color: palette.tool, bold: true, children: "Context window" }),
|
|
266
|
-
/* @__PURE__ */
|
|
267
|
-
|
|
343
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
344
|
+
shown.map((line, i) => /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: line === "" ? " " : line }, i)),
|
|
345
|
+
overflow ? /* @__PURE__ */ jsx4(Text4, { color: palette.dim, children: overflow }) : null
|
|
346
|
+
] }),
|
|
347
|
+
/* @__PURE__ */ jsx4(Text4, { color: palette.dim, children: "esc close" })
|
|
268
348
|
] });
|
|
269
349
|
}
|
|
270
350
|
function contextSnapshotFromState(state, contextWindow, toolCount) {
|
|
@@ -281,18 +361,44 @@ function contextSnapshotFromState(state, contextWindow, toolCount) {
|
|
|
281
361
|
// src/ink/Modal.tsx
|
|
282
362
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
283
363
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
284
|
-
function
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
364
|
+
function SuggestionView({ suggestion }) {
|
|
365
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: palette.ember, paddingX: 1, children: [
|
|
366
|
+
/* @__PURE__ */ jsx5(Text5, { wrap: "truncate-end", children: suggestionLine(suggestion) }),
|
|
367
|
+
/* @__PURE__ */ jsxs5(Text5, { color: palette.dim, wrap: "truncate-end", children: [
|
|
368
|
+
suggestionKeys(suggestion),
|
|
369
|
+
" esc ignores"
|
|
370
|
+
] })
|
|
371
|
+
] });
|
|
372
|
+
}
|
|
373
|
+
function capDiffRows(rows, max) {
|
|
374
|
+
if (rows.length <= max || max <= 0) return max <= 0 ? [] : rows;
|
|
375
|
+
const kept = rows.slice(0, Math.max(max - 1, 0));
|
|
376
|
+
const dropped = rows.slice(kept.length);
|
|
377
|
+
const hidden = dropped.reduce((n, r) => n + (r.kind === "skip" ? r.count : 1), 0);
|
|
378
|
+
return [...kept, { kind: "skip", count: hidden, reason: "context" }];
|
|
379
|
+
}
|
|
380
|
+
function DiffPreview({ diff, budget }) {
|
|
381
|
+
if (budget !== void 0 && budget <= 0) return null;
|
|
288
382
|
if (diff.noop) {
|
|
289
383
|
return /* @__PURE__ */ jsx5(Text5, { color: palette.dim, children: "(no change -- new content is identical to what's on disk)" });
|
|
290
384
|
}
|
|
385
|
+
const contentUnchanged = diff.added === 0 && diff.removed === 0;
|
|
386
|
+
let remaining = budget !== void 0 ? budget - 1 : void 0;
|
|
387
|
+
const fits = () => {
|
|
388
|
+
if (remaining === void 0) return true;
|
|
389
|
+
if (remaining < 1) return false;
|
|
390
|
+
remaining -= 1;
|
|
391
|
+
return true;
|
|
392
|
+
};
|
|
393
|
+
const endingRow = diff.endingChange && fits() && /* @__PURE__ */ jsx5(Text5, { color: palette.amber, bold: true, children: formatEndingChange(diff.endingChange, contentUnchanged) });
|
|
394
|
+
const unexplainedRow = diff.unexplainedChange && fits() && /* @__PURE__ */ jsx5(Text5, { color: palette.amber, bold: true, children: "the file changes in a way not shown above (e.g. trailing whitespace or encoding)" });
|
|
395
|
+
const newFileRow = diff.isNewFile && fits() && /* @__PURE__ */ jsx5(Text5, { color: palette.dim, children: "(new file)" });
|
|
396
|
+
const rows = remaining !== void 0 ? capDiffRows(diff.rows, remaining) : diff.rows;
|
|
291
397
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
292
398
|
endingRow,
|
|
293
399
|
unexplainedRow,
|
|
294
|
-
|
|
295
|
-
|
|
400
|
+
newFileRow,
|
|
401
|
+
rows.map((r, i) => {
|
|
296
402
|
if (r.kind === "skip") {
|
|
297
403
|
const label = r.reason === "changes" ? `\u2026 ${r.count} more changed line${r.count === 1 ? "" : "s"}` : `\u2026 ${r.count} unchanged line${r.count === 1 ? "" : "s"}`;
|
|
298
404
|
return /* @__PURE__ */ jsx5(Text5, { color: palette.dim, children: label }, i);
|
|
@@ -301,6 +407,7 @@ function DiffPreview({ diff }) {
|
|
|
301
407
|
Text5,
|
|
302
408
|
{
|
|
303
409
|
color: r.kind === "add" ? palette.good : r.kind === "del" ? palette.bad : palette.dim,
|
|
410
|
+
wrap: "truncate-end",
|
|
304
411
|
children: [
|
|
305
412
|
r.kind === "add" ? "+ " : r.kind === "del" ? "- " : " ",
|
|
306
413
|
r.text
|
|
@@ -317,43 +424,64 @@ function DiffPreview({ diff }) {
|
|
|
317
424
|
] })
|
|
318
425
|
] });
|
|
319
426
|
}
|
|
320
|
-
|
|
427
|
+
var PERMISSION_BORDER_ROWS = 2;
|
|
428
|
+
var QUESTION_FIXED_ROWS = 5;
|
|
429
|
+
function ModalView({ modal, rows }) {
|
|
321
430
|
if (modal.kind === "permission") {
|
|
431
|
+
let content = Math.max(overlayBoxBudget(rows) - PERMISSION_BORDER_ROWS, 0);
|
|
432
|
+
const take = (n) => {
|
|
433
|
+
if (content < n) return false;
|
|
434
|
+
content -= n;
|
|
435
|
+
return true;
|
|
436
|
+
};
|
|
437
|
+
const showButtons = take(1);
|
|
438
|
+
const showTool = take(1);
|
|
439
|
+
const showDetail = take(1);
|
|
440
|
+
const diffBudget = modal.diff ? content : void 0;
|
|
322
441
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: palette.warn, paddingX: 1, children: [
|
|
323
|
-
/* @__PURE__ */ jsxs5(Text5, { color: palette.warn, children: [
|
|
442
|
+
showTool ? /* @__PURE__ */ jsxs5(Text5, { color: palette.warn, children: [
|
|
324
443
|
modal.tool,
|
|
325
444
|
" wants to run"
|
|
326
|
-
] }),
|
|
327
|
-
|
|
328
|
-
modal.diff && /* @__PURE__ */ jsx5(
|
|
329
|
-
/* @__PURE__ */ jsxs5(Box5, {
|
|
445
|
+
] }) : null,
|
|
446
|
+
showDetail ? /* @__PURE__ */ jsx5(Text5, { bold: true, wrap: "truncate-end", children: modal.detail }) : null,
|
|
447
|
+
modal.diff && /* @__PURE__ */ jsx5(DiffPreview, { diff: modal.diff, budget: diffBudget }),
|
|
448
|
+
showButtons ? /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
330
449
|
/* @__PURE__ */ jsx5(Text5, { color: palette.good, children: "[y]" }),
|
|
331
450
|
/* @__PURE__ */ jsx5(Text5, { color: palette.dim, children: " run it " }),
|
|
332
451
|
/* @__PURE__ */ jsx5(Text5, { color: palette.bad, children: "[n]" }),
|
|
333
452
|
/* @__PURE__ */ jsx5(Text5, { color: palette.dim, children: " deny esc denies" })
|
|
334
|
-
] })
|
|
453
|
+
] }) : null
|
|
335
454
|
] });
|
|
336
455
|
}
|
|
337
456
|
const q = modal.questions[0];
|
|
457
|
+
const options = q?.options ?? [];
|
|
458
|
+
const optionsBudget = Math.max(overlayBoxBudget(rows) - QUESTION_FIXED_ROWS, 0);
|
|
459
|
+
const { shown, overflow } = capList(options, optionsBudget, (n) => `\u2026 ${n} more option${n === 1 ? "" : "s"}`);
|
|
338
460
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: palette.ember, paddingX: 1, children: [
|
|
339
461
|
/* @__PURE__ */ jsx5(Text5, { color: palette.dim, children: q?.header }),
|
|
340
|
-
/* @__PURE__ */ jsx5(Text5, { bold: true, children: q?.question }),
|
|
341
|
-
/* @__PURE__ */
|
|
342
|
-
|
|
343
|
-
i
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
462
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, wrap: "truncate-end", children: q?.question }),
|
|
463
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
464
|
+
shown.map((o) => {
|
|
465
|
+
const i = options.indexOf(o);
|
|
466
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
467
|
+
/* @__PURE__ */ jsxs5(Text5, { color: palette.amber, children: [
|
|
468
|
+
i + 1,
|
|
469
|
+
" "
|
|
470
|
+
] }),
|
|
471
|
+
/* @__PURE__ */ jsx5(Text5, { wrap: "truncate-end", children: o.label }),
|
|
472
|
+
o.description ? /* @__PURE__ */ jsxs5(Text5, { color: palette.dim, wrap: "truncate-end", children: [
|
|
473
|
+
" ",
|
|
474
|
+
o.description
|
|
475
|
+
] }) : null
|
|
476
|
+
] }, o.label);
|
|
477
|
+
}),
|
|
478
|
+
overflow ? /* @__PURE__ */ jsx5(Text5, { color: palette.dim, children: overflow }) : null
|
|
479
|
+
] }),
|
|
480
|
+
/* @__PURE__ */ jsxs5(Text5, { color: palette.dim, children: [
|
|
353
481
|
"1-",
|
|
354
|
-
|
|
482
|
+
options.length,
|
|
355
483
|
" choose \xB7 \u21B5 confirm \xB7 or type your own answer \xB7 esc dismisses"
|
|
356
|
-
] })
|
|
484
|
+
] })
|
|
357
485
|
] });
|
|
358
486
|
}
|
|
359
487
|
|
|
@@ -504,6 +632,23 @@ function useColumns() {
|
|
|
504
632
|
}, [stdout]);
|
|
505
633
|
return columns;
|
|
506
634
|
}
|
|
635
|
+
function usableRows(rows) {
|
|
636
|
+
return rows !== void 0 && Number.isFinite(rows) && rows > 0 ? rows : void 0;
|
|
637
|
+
}
|
|
638
|
+
function useRows() {
|
|
639
|
+
const { stdout } = useStdout();
|
|
640
|
+
const [rows, setRows] = useState(usableRows(stdout?.rows));
|
|
641
|
+
useEffect(() => {
|
|
642
|
+
if (!stdout) return;
|
|
643
|
+
const onResize = () => setRows(usableRows(stdout.rows));
|
|
644
|
+
onResize();
|
|
645
|
+
stdout.on("resize", onResize);
|
|
646
|
+
return () => {
|
|
647
|
+
stdout.off("resize", onResize);
|
|
648
|
+
};
|
|
649
|
+
}, [stdout]);
|
|
650
|
+
return rows;
|
|
651
|
+
}
|
|
507
652
|
function usd(v) {
|
|
508
653
|
return v < 0.01 ? `$${v.toFixed(4)}` : `$${v.toFixed(2)}`;
|
|
509
654
|
}
|
|
@@ -513,11 +658,14 @@ function App(props) {
|
|
|
513
658
|
const paletteSelected = Math.min(props.paletteSelected, Math.max(paletteItems.length - 1, 0));
|
|
514
659
|
const busy = state.status !== "idle";
|
|
515
660
|
const columns = useColumns();
|
|
661
|
+
const rows = useRows();
|
|
662
|
+
const modalOpen = state.modal != null;
|
|
516
663
|
const statusWord = busy ? "running" : "idle";
|
|
517
664
|
const roundSeg = state.live ? ` ${glyph.sep} turn ${state.live.turn}` : ` ${glyph.sep} ${props.toolCount} tools`;
|
|
518
665
|
const turnTokens = tokens(state.turnUsage.promptTokens + state.turnUsage.completionTokens);
|
|
519
666
|
const totalTokens = tokens(state.usage.promptTokens + state.usage.completionTokens);
|
|
520
|
-
const
|
|
667
|
+
const ctxSeg = state.sentTokens ? `${tokens(state.sentTokens)} ctx ${glyph.sep} ` : "";
|
|
668
|
+
const tokenSeg = ` ${glyph.sep} ${ctxSeg}${turnTokens} turn ${glyph.sep} ${totalTokens} total`;
|
|
521
669
|
const costSeg = state.costUsd > 0 ? ` ${glyph.sep} ${usd(state.costUsd)}` : "";
|
|
522
670
|
const escSeg = busy ? ` ${glyph.sep} esc stops` : "";
|
|
523
671
|
const queuedSeg = state.queued.length ? ` ${glyph.sep} ${state.queued.length} queued` : "";
|
|
@@ -526,12 +674,12 @@ function App(props) {
|
|
|
526
674
|
const { right: statusRight } = fitStatus(statusLeft, { model: state.model, cwd: props.cwd, home: homedir() }, columns);
|
|
527
675
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
528
676
|
/* @__PURE__ */ jsx8(Static, { items: state.committed, children: (item) => /* @__PURE__ */ jsx8(CommittedRow, { item }, item.id) }, `transcript-${state.transcriptGen}`),
|
|
529
|
-
state.live?.tools.map((t) => /* @__PURE__ */ jsx8(LiveToolRow, { tool: t }, t.callId)),
|
|
530
|
-
state.live?.streaming ? /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: palette.soft, children: state.live.streaming }) }) : null,
|
|
531
|
-
/* @__PURE__ */ jsx8(TodoBlock, { todos: state.todos }),
|
|
532
|
-
state.modal ? /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: state.modal.kind === "mcp" ? /* @__PURE__ */ jsx8(McpOverlay, { modal: state.modal }) : state.modal.kind === "help" ? /* @__PURE__ */ jsx8(HelpOverlay, {}) : state.modal.kind === "context" ? /* @__PURE__ */ jsx8(ContextOverlay, { snapshot: contextSnapshotFromState(state, props.contextWindow ?? null, props.toolCount) }) : state.modal.kind === "permission" || state.modal.kind === "user" ? /* @__PURE__ */ jsx8(ModalView, { modal: state.modal }) : null }) : null,
|
|
533
|
-
paletteItems.length && !
|
|
534
|
-
state.pendingSkill ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
677
|
+
!modalOpen && state.live?.tools.map((t) => /* @__PURE__ */ jsx8(LiveToolRow, { tool: t }, t.callId)),
|
|
678
|
+
!modalOpen && state.live?.streaming ? /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: palette.soft, children: state.live.streaming }) }) : null,
|
|
679
|
+
!modalOpen ? /* @__PURE__ */ jsx8(TodoBlock, { todos: state.todos }) : null,
|
|
680
|
+
state.modal ? /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: state.modal.kind === "mcp" ? /* @__PURE__ */ jsx8(McpOverlay, { modal: state.modal, rows }) : state.modal.kind === "help" ? /* @__PURE__ */ jsx8(HelpOverlay, { rows }) : state.modal.kind === "context" ? /* @__PURE__ */ jsx8(ContextOverlay, { snapshot: contextSnapshotFromState(state, props.contextWindow ?? null, props.toolCount), rows }) : state.modal.kind === "suggestion" ? /* @__PURE__ */ jsx8(SuggestionView, { suggestion: state.modal.suggestion }) : state.modal.kind === "permission" || state.modal.kind === "user" ? /* @__PURE__ */ jsx8(ModalView, { modal: state.modal, rows }) : null }) : null,
|
|
681
|
+
paletteItems.length && !modalOpen ? /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(PaletteView, { items: paletteItems, selected: paletteSelected }) }) : null,
|
|
682
|
+
state.pendingSkill && !modalOpen ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
535
683
|
/* @__PURE__ */ jsxs8(Text8, { color: palette.ember, children: [
|
|
536
684
|
glyph.sep,
|
|
537
685
|
" skill armed: ",
|
|
@@ -557,7 +705,7 @@ function App(props) {
|
|
|
557
705
|
props.inputKey
|
|
558
706
|
)
|
|
559
707
|
] }),
|
|
560
|
-
state.committed.length === 0 ? /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: palette.soft, children: [
|
|
708
|
+
state.committed.length === 0 && !modalOpen ? /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: palette.soft, children: [
|
|
561
709
|
"Type a request. ",
|
|
562
710
|
/* @__PURE__ */ jsxs8(Text8, { color: palette.dim, children: [
|
|
563
711
|
"/help for commands ",
|
|
@@ -748,6 +896,34 @@ function Root({ session, deps }) {
|
|
|
748
896
|
}
|
|
749
897
|
return;
|
|
750
898
|
}
|
|
899
|
+
if (state.modal.kind === "suggestion") {
|
|
900
|
+
if (key.ctrl && input === "c") {
|
|
901
|
+
if (state.status !== "idle" && !sawSigint) {
|
|
902
|
+
session.abort();
|
|
903
|
+
setSawSigint(true);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
exit();
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (key.escape) {
|
|
910
|
+
session.dismissApprovalSuggestion();
|
|
911
|
+
if (state.status !== "idle") session.abort();
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (Date.now() - state.modal.openedAt < SUGGESTION_ARM_DELAY_MS) return;
|
|
915
|
+
if (key.return) {
|
|
916
|
+
if (state.modal.armedChoice) void session.respondApprovalSuggestion(state.modal.armedChoice);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const choice = parseSuggestionChoice(state.modal.suggestion, input);
|
|
920
|
+
if (choice === "workspace" || choice === "global") {
|
|
921
|
+
session.armApprovalSuggestion(choice);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (choice) void session.respondApprovalSuggestion(choice);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
751
927
|
if (key.escape) {
|
|
752
928
|
session.answer(state.modal.kind === "permission" ? "deny" : null);
|
|
753
929
|
return;
|
|
@@ -871,13 +1047,29 @@ function Root({ session, deps }) {
|
|
|
871
1047
|
}
|
|
872
1048
|
);
|
|
873
1049
|
}
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
1050
|
+
function tuiPermission(session, deps) {
|
|
1051
|
+
const approvals = deps.approvals;
|
|
1052
|
+
const hooks = approvals ? approvalsHooks(approvals, {
|
|
1053
|
+
standing: true,
|
|
1054
|
+
context: () => {
|
|
1055
|
+
const id = session.getSnapshot().session?.id;
|
|
1056
|
+
return { host: "tui", surface: "chat", ...deps.approvalsContext ?? {}, ...id ? { sessionId: id } : {} };
|
|
1057
|
+
},
|
|
1058
|
+
onSuggestion: (s) => session.offerApprovalSuggestion(s)
|
|
1059
|
+
}) : null;
|
|
877
1060
|
const permission = new PermissionService2({
|
|
878
1061
|
policy: deps.policy ?? STANDARD_POLICY,
|
|
879
|
-
prompt: { ask: (req) => session.askPermission(req) }
|
|
1062
|
+
prompt: { ask: (req) => session.askPermission(req) },
|
|
1063
|
+
...hooks?.standing ? { standing: hooks.standing } : {},
|
|
1064
|
+
...hooks ? { onDecision: hooks.onDecision } : {}
|
|
880
1065
|
});
|
|
1066
|
+
return { permission, settled: hooks ? hooks.settled : async () => void 0 };
|
|
1067
|
+
}
|
|
1068
|
+
async function runTui(deps) {
|
|
1069
|
+
const approvals = deps.approvals;
|
|
1070
|
+
const session = new TermSession(approvals ? { ...deps, saveApprovalChoice: (s, c) => approvals.respond(s, c) } : deps);
|
|
1071
|
+
if (deps.resume) await session.resume(deps.resume.id);
|
|
1072
|
+
const { permission, settled } = tuiPermission(session, deps);
|
|
881
1073
|
session.attachPermission(permission);
|
|
882
1074
|
const bannerOpts = {
|
|
883
1075
|
policy: deps.policy ?? STANDARD_POLICY,
|
|
@@ -906,9 +1098,12 @@ async function runTui(deps) {
|
|
|
906
1098
|
exitOnCtrlC: false
|
|
907
1099
|
});
|
|
908
1100
|
await app.waitUntilExit();
|
|
1101
|
+
await settled();
|
|
1102
|
+
await approvals?.flush().catch(() => void 0);
|
|
909
1103
|
return 0;
|
|
910
1104
|
}
|
|
911
1105
|
export {
|
|
912
1106
|
Root,
|
|
913
|
-
runTui
|
|
1107
|
+
runTui,
|
|
1108
|
+
tuiPermission
|
|
914
1109
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Rows the live region spends OUTSIDE the overlay box itself, every time an
|
|
2
|
+
* overlay is open: the modal Box's own `marginTop` (1), the input box's
|
|
3
|
+
* `marginTop` (1) plus its three border/content rows (3), and the one-line
|
|
4
|
+
* status row (1) -- 6 total. Live tool rows, streaming text, TodoBlock, the
|
|
5
|
+
* palette, the pending-skill indicator, and the empty-conversation hint are
|
|
6
|
+
* all suppressed while a modal is open (App.tsx), so none of them are part
|
|
7
|
+
* of this reservation; if a future change stops suppressing one of them,
|
|
8
|
+
* this constant must grow to match or the invariant this exists to keep
|
|
9
|
+
* (packages/term/src/ink/liveRegionHeight.test.tsx) goes red. */
|
|
10
|
+
export declare const RESERVED_CHROME_ROWS = 6;
|
|
11
|
+
/** No usable row count -- see App.tsx's `usableRows`: a stream that cannot
|
|
12
|
+
* report its height (not a TTY, piped output, a render test double) gets
|
|
13
|
+
* this, meaning "don't cap" rather than a guessed height. Callers pass it
|
|
14
|
+
* straight through to `capList`, whose `Number.MAX_SAFE_INTEGER` cap is a
|
|
15
|
+
* no-op (every real list is shorter than that). */
|
|
16
|
+
export declare function overlayBoxBudget(rows: number | undefined): number;
|
|
17
|
+
export interface CappedList<T> {
|
|
18
|
+
shown: T[];
|
|
19
|
+
/** Set only when `items` didn't fit -- the caller renders this as its own
|
|
20
|
+
* line, the same way TodoBlock renders `view.overflow`. */
|
|
21
|
+
overflow: string | null;
|
|
22
|
+
}
|
|
23
|
+
/** Cap `items` to `cap` rows TOTAL, overflow line included -- the exact rule
|
|
24
|
+
* `visibleTodos` (todos.ts) already applies to the todo list, generalized to
|
|
25
|
+
* any list an overlay renders one item per row. */
|
|
26
|
+
export declare function capList<T>(items: T[], cap: number, overflowLabel: (hiddenCount: number) => string): CappedList<T>;
|
|
27
|
+
//# sourceMappingURL=overlayBudget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlayBudget.d.ts","sourceRoot":"","sources":["../../src/ink/overlayBudget.ts"],"names":[],"mappings":"AAgBA;;;;;;;;iEAQiE;AACjE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;;mDAImD;AACnD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGjE;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX;+DAC2D;IAC3D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;mDAEmD;AACnD,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAYjH"}
|