@cspeach/cli 1.1.17 → 1.1.19
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 +3 -4
- package/dist/agent/loop.js +143 -25
- package/dist/agent/providers/local-provider.js +4 -1
- package/dist/agent/repair-partial.js +76 -19
- package/dist/agent/tool-dispatch.js +80 -22
- package/dist/agent/turn-error-ux.js +77 -26
- package/dist/agent/turn-stream.js +25 -3
- package/dist/approvals/adt-type.js +27 -0
- package/dist/approvals/advisory-prompt.js +48 -0
- package/dist/approvals/advisory-render.js +25 -11
- package/dist/approvals/approval-prompt.js +74 -24
- package/dist/approvals/jwt.js +2 -1
- package/dist/approvals/render.js +64 -36
- package/dist/approvals/risk-floor.js +53 -2
- package/dist/auth/api-key.js +30 -16
- package/dist/auth/keychain.js +0 -0
- package/dist/auth/me.js +25 -7
- package/dist/cli.js +25 -6
- package/dist/commands/compact.js +12 -9
- package/dist/commands/config-set.js +6 -2
- package/dist/commands/config-show.js +7 -5
- package/dist/commands/cost.js +41 -34
- package/dist/commands/help.js +88 -50
- package/dist/commands/login.js +28 -19
- package/dist/commands/logout.js +7 -3
- package/dist/commands/plan-chain.js +71 -77
- package/dist/commands/plan-continue.js +1 -0
- package/dist/commands/plan-resume.js +20 -12
- package/dist/commands/whoami.js +23 -9
- package/dist/config/loader.js +15 -3
- package/dist/cost/pricing.js +6 -5
- package/dist/doctor/checks/forge-rules.js +13 -3
- package/dist/doctor/checks/keychain.js +6 -4
- package/dist/doctor/run.js +101 -20
- package/dist/index.js +5 -2
- package/dist/lib/graceful-exit.js +56 -0
- package/dist/lib/piped-prompt.js +65 -0
- package/dist/lock-contention.js +2 -2
- package/dist/one-shot.js +135 -40
- package/dist/projects/answer-blockers.js +6 -1
- package/dist/projects/handover-md.js +15 -14
- package/dist/projects/plan-run.js +97 -54
- package/dist/projects/promote-command.js +11 -2
- package/dist/projects/save-command.js +3 -2
- package/dist/projects/status.js +2 -1
- package/dist/projects/yes-no.js +12 -0
- package/dist/renderer/abap-inline.js +18 -14
- package/dist/renderer/answer-trim.js +69 -0
- package/dist/renderer/banners.js +8 -8
- package/dist/renderer/brand-settled.js +19 -0
- package/dist/renderer/change-card.js +156 -0
- package/dist/renderer/checklist-format.js +130 -0
- package/dist/renderer/color-mode.js +45 -0
- package/dist/renderer/error-detail.js +104 -0
- package/dist/renderer/fence-state.js +46 -0
- package/dist/renderer/footer-line.js +96 -0
- package/dist/renderer/glyphs.js +25 -0
- package/dist/renderer/goodbye.js +38 -0
- package/dist/renderer/legacy-palette.js +41 -0
- package/dist/renderer/look.js +30 -0
- package/dist/renderer/markdown.js +299 -92
- package/dist/renderer/notice-log.js +72 -0
- package/dist/renderer/notice-shape.js +48 -0
- package/dist/renderer/notices.js +40 -8
- package/dist/renderer/panel-rows.js +16 -0
- package/dist/renderer/pipeline.js +66 -0
- package/dist/renderer/progress-chatter.js +23 -17
- package/dist/renderer/rendering-mode.js +34 -0
- package/dist/renderer/routing-line.js +14 -0
- package/dist/renderer/sanitize.js +12 -0
- package/dist/renderer/severity.js +2 -7
- package/dist/renderer/startup-lines.js +152 -0
- package/dist/renderer/status-footer.js +52 -34
- package/dist/renderer/steering-echo.js +41 -0
- package/dist/renderer/syntax.js +31 -12
- package/dist/renderer/tables.js +5 -1
- package/dist/renderer/theme.js +44 -0
- package/dist/renderer/thinking-heartbeat.js +33 -2
- package/dist/renderer/todo-block.js +10 -49
- package/dist/renderer/tool-labels.js +238 -0
- package/dist/renderer/tool-widget.js +245 -51
- package/dist/renderer/trace.js +42 -0
- package/dist/renderer/transcript-flow.js +132 -0
- package/dist/renderer/tty.js +34 -1
- package/dist/renderer/ui-width.js +42 -0
- package/dist/renderer/verify-chain.js +4 -2
- package/dist/renderer/widget-fallback.js +58 -65
- package/dist/repl/bracketed-paste.js +7 -1
- package/dist/repl/builtin-commands.js +19 -8
- package/dist/repl/credential-handover-gate.js +28 -0
- package/dist/repl/early-line-buffer.js +5 -2
- package/dist/repl/file-picker.js +54 -12
- package/dist/repl/ink-stdin-guard.js +66 -0
- package/dist/repl/inquirer-guard.js +59 -16
- package/dist/repl/inquirer-theme.js +27 -33
- package/dist/repl/paste-marker.js +19 -0
- package/dist/repl/plan-turn-end.js +20 -0
- package/dist/repl/post-turn-status.js +36 -11
- package/dist/repl/reroute-turn.js +21 -0
- package/dist/repl/reset-tty-stdin.js +44 -0
- package/dist/repl/restore-guard.js +22 -0
- package/dist/repl/resume-standalone.js +63 -0
- package/dist/repl/rule8-detector.js +11 -3
- package/dist/repl/safety-confirm.js +161 -93
- package/dist/repl/session-spend-line.js +8 -4
- package/dist/repl/themed-prompts.js +19 -0
- package/dist/repl/ui-look-command.js +75 -0
- package/dist/repl/update-method-preview-hook.js +48 -5
- package/dist/repl.js +795 -332
- package/dist/rewind/cli.js +3 -2
- package/dist/rewind/format.js +14 -9
- package/dist/rewind/restore.js +11 -0
- package/dist/router/classifier.js +47 -4
- package/dist/router/intent-extractor.js +24 -8
- package/dist/router/piped-routing.js +23 -0
- package/dist/router/resume-routing.js +16 -0
- package/dist/router/routing-failure.js +57 -0
- package/dist/sap/first-run-choice.js +1 -1
- package/dist/sap/onboarding.js +3 -2
- package/dist/sap/standalone-onboarding.js +1 -1
- package/dist/sap/system-info.js +4 -2
- package/dist/sap/unreachable.js +28 -0
- package/dist/sap-errors/clean-error-text.js +182 -0
- package/dist/session/interrupt-reason.js +39 -0
- package/dist/session/recap.js +36 -24
- package/dist/session/resume.js +104 -36
- package/dist/session/store.js +69 -27
- package/dist/session/user-prompt.js +21 -0
- package/dist/skill-catalog.js +7 -0
- package/dist/skills/bundled-skills.js +76 -69
- package/dist/skills/signing-public-key.js +1 -1
- package/dist/standards/standards-init.js +1 -1
- package/dist/test-helpers/answer-any.js +46 -0
- package/dist/tools/approval.js +27 -11
- package/dist/tools/ask-question.js +47 -5
- package/dist/tools/filesystem/file-write.js +19 -2
- package/dist/tools/fiori/fe-extend.js +16 -1
- package/dist/tools/fiori/fe-scaffold.js +105 -3
- package/dist/tools/fiori/html-escapes.js +28 -0
- package/dist/tools/fiori/metadata/audit.js +605 -0
- package/dist/tools/fiori/metadata/references.js +104 -0
- package/dist/tools/fiori/metadata/types.js +8 -0
- package/dist/tools/fiori/preview/app-guard.js +215 -0
- package/dist/tools/fiori/preview/env.js +193 -0
- package/dist/tools/fiori/preview/readiness.js +127 -0
- package/dist/tools/fiori/preview/registry.js +412 -0
- package/dist/tools/fiori/preview/start.js +469 -0
- package/dist/tools/fiori/samples/loader.js +20 -5
- package/dist/tools/fiori/scaffold.js +18 -6
- package/dist/tools/fiori/smoke/app-driver-page.js +372 -0
- package/dist/tools/fiori/smoke/app-driver.js +100 -0
- package/dist/tools/fiori/smoke/assertions.js +161 -24
- package/dist/tools/fiori/smoke/audit-columns.js +21 -0
- package/dist/tools/fiori/smoke/batch.js +273 -0
- package/dist/tools/fiori/smoke/draft-safety.js +241 -0
- package/dist/tools/fiori/smoke/draft-smoke.js +473 -0
- package/dist/tools/fiori/smoke/driver.js +171 -4
- package/dist/tools/fiori/smoke/evidence.js +35 -0
- package/dist/tools/fiori/smoke/labels-codes.js +207 -0
- package/dist/tools/fiori/smoke/nav-error.js +26 -0
- package/dist/tools/fiori/smoke/run-smoke.js +155 -12
- package/dist/tools/fiori/tools.js +651 -7
- package/dist/tools/fiori/ui5-version.js +16 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/local-build.js +6 -0
- package/dist/tools/sap-read.js +16 -1
- package/dist/tools/sap-write.js +122 -10
- package/dist/tools/shell/shell_exec.js +30 -12
- package/dist/tools/syntax-state.js +67 -0
- package/dist/tools/todo.js +23 -18
- package/dist/tools/transport-fit.js +462 -0
- package/dist/tools/transport-resolution.js +3 -1
- package/dist/tools/transport.js +33 -7
- package/dist/tools/verify.js +184 -8
- package/dist/ui/app.js +284 -146
- package/dist/ui/approval-modal.js +41 -15
- package/dist/ui/ask-answer-text.js +17 -0
- package/dist/ui/body.js +25 -6
- package/dist/ui/card-slot.js +98 -0
- package/dist/ui/checklist.js +9 -0
- package/dist/ui/coaching-picker-classic.js +8 -5
- package/dist/ui/command-palette.js +6 -4
- package/dist/ui/confirm-request.js +18 -0
- package/dist/ui/context-grid.js +2 -1
- package/dist/ui/file-palette.js +6 -4
- package/dist/ui/files-card-emitter.js +19 -0
- package/dist/ui/footer-line.js +16 -0
- package/dist/ui/footer.js +105 -104
- package/dist/ui/input-wrap.js +122 -0
- package/dist/ui/interrupt.js +103 -0
- package/dist/ui/key-burst.js +117 -0
- package/dist/ui/line-resolution.js +4 -3
- package/dist/ui/live-area.js +71 -0
- package/dist/ui/login-banner.js +54 -43
- package/dist/ui/rewind-panel.js +76 -24
- package/dist/ui/role-props.js +47 -0
- package/dist/ui/sap-state-store.js +1 -0
- package/dist/ui/session-timeline.js +49 -3
- package/dist/ui/skill-picker.js +5 -47
- package/dist/ui/text-input.js +128 -24
- package/dist/ui/todo-emitter.js +19 -0
- package/dist/ui/turn-status-emitter.js +28 -2
- package/dist/ui/turn-status.js +16 -19
- package/dist/ui/use-card-request.js +44 -0
- package/dist/ui/widgets/ask-card.js +521 -0
- package/dist/ui/widgets/bar-chart.js +11 -5
- package/dist/ui/widgets/coaching-picker.js +50 -55
- package/dist/ui/widgets/confirm-card.js +193 -0
- package/dist/ui/widgets/dep-graph.js +3 -2
- package/dist/ui/widgets/diff-viewer.js +6 -5
- package/dist/ui/widgets/files-card.js +97 -0
- package/dist/ui/widgets/question-card.js +2 -1
- package/dist/ui/widgets/reclaim-raw-stdin.js +24 -0
- package/dist/ui/widgets/shortcuts-sheet.js +25 -0
- package/dist/ui/widgets/stack-frames.js +2 -1
- package/dist/upgrade-check.js +39 -8
- package/package.json +11 -2
- package/test-harness/fe-draft/app.ts +188 -0
- package/test-harness/fe-draft/fe-draft.e2e.test.ts +461 -0
- package/test-harness/fe-draft/manifest-enhancer.unit.test.ts +51 -0
- package/test-harness/fe-draft/routes.ts +224 -0
- package/test-harness/fe-draft/routes.unit.test.ts +45 -0
- package/test-harness/fe-draft/server.ts +225 -0
- package/test-harness/fe-draft/variants.ts +196 -0
- package/vitest.smoke-e2e.config.ts +20 -0
package/dist/renderer/syntax.js
CHANGED
|
@@ -26,6 +26,7 @@ import { createRequire } from 'node:module';
|
|
|
26
26
|
import abapGrammar from './highlighters/abap.js';
|
|
27
27
|
import cdsGrammar from './highlighters/cds.js';
|
|
28
28
|
import bdefGrammar from './highlighters/bdef.js';
|
|
29
|
+
import { colorOff } from './color-mode.js';
|
|
29
30
|
// A5 fix (2026-06-06) — registration must hit the EXACT module instance
|
|
30
31
|
// cli-highlight loads, and on this pnpm/Windows layout that is NOT what a
|
|
31
32
|
// plain `import hljs from 'highlight.js'` (or even a local require)
|
|
@@ -41,18 +42,31 @@ import bdefGrammar from './highlighters/bdef.js';
|
|
|
41
42
|
const require = createRequire(import.meta.url);
|
|
42
43
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
43
44
|
const hljs = createRequire(require.resolve('cli-highlight'))('highlight.js');
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
// Registration is idempotent and triggered explicitly (2026-09-26, look
|
|
46
|
+
// piece 1): pipeline.ts calls ensureGrammars() at ITS module load — the same
|
|
47
|
+
// moment this module used to self-register, since pipeline.ts is the only
|
|
48
|
+
// production caller of renderMarkdown — and highlightCodeBlock() calls it
|
|
49
|
+
// lazily. This lets markdown.ts import the fenced-body highlighter without
|
|
50
|
+
// changing the legacy marked-terminal output of a markdown-only module graph
|
|
51
|
+
// (pinned by test/unit/legacy-look-pin.test.ts).
|
|
52
|
+
// The grammar modules export plain objects (shape matches highlight.js
|
|
53
|
+
// `Language` definition). Cast is safe — the grammars match the runtime
|
|
54
|
+
// contract even when strict typings drift.
|
|
55
|
+
let grammarsRegistered = false;
|
|
56
|
+
export function ensureGrammars() {
|
|
57
|
+
if (grammarsRegistered)
|
|
58
|
+
return;
|
|
59
|
+
grammarsRegistered = true;
|
|
60
|
+
hljs.registerLanguage('abap', () => abapGrammar);
|
|
61
|
+
hljs.registerLanguage('cds', () => cdsGrammar);
|
|
62
|
+
hljs.registerLanguage('bdef', () => bdefGrammar);
|
|
63
|
+
// `ddl` is an alias of the CDS grammar: ```ddl fences carry CDS DDL source
|
|
64
|
+
// (the abap-rap templates emit them). Registering it on the same hljs
|
|
65
|
+
// instance also silences marked-terminal's "Could not find the language
|
|
66
|
+
// 'ddl'" warning, since marked-terminal calls cli-highlight directly with
|
|
67
|
+
// the raw fence lang and never goes through highlightCodeBlock().
|
|
68
|
+
hljs.registerLanguage('ddl', () => cdsGrammar);
|
|
69
|
+
}
|
|
56
70
|
const SUPPORTED = new Set(['abap', 'cds', 'bdef', 'ddl']);
|
|
57
71
|
/**
|
|
58
72
|
* Highlight a code block body (without fence markers).
|
|
@@ -66,6 +80,11 @@ export function highlightCodeBlock(code, lang) {
|
|
|
66
80
|
const normalised = lang.trim().toLowerCase();
|
|
67
81
|
if (!SUPPORTED.has(normalised))
|
|
68
82
|
return code;
|
|
83
|
+
// NO_COLOR (piece 2 §1.2): cli-highlight's own chalk 4 ignores NO_COLOR and an
|
|
84
|
+
// empty theme falls back to DEFAULT_THEME, so skip highlighting entirely.
|
|
85
|
+
if (colorOff())
|
|
86
|
+
return code;
|
|
87
|
+
ensureGrammars();
|
|
69
88
|
try {
|
|
70
89
|
return highlight(code, {
|
|
71
90
|
language: normalised,
|
package/dist/renderer/tables.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import Table from 'cli-table3';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { applySeverity } from './severity.js';
|
|
5
|
+
import { colorOff } from './color-mode.js';
|
|
5
6
|
/** Regex that matches a markdown pipe-table row (at least two | delimiters). */
|
|
6
7
|
const PIPE_ROW_RE = /^\|(.+\|)+$/;
|
|
7
8
|
/** Separator row: | --- | or | :---: | etc. */
|
|
@@ -46,7 +47,10 @@ function buildTable(lines) {
|
|
|
46
47
|
return lines.join('\n');
|
|
47
48
|
const [headerRow, ...dataRows] = rows;
|
|
48
49
|
const headers = parseRow(headerRow).map((h) => chalk.cyan.bold(h));
|
|
49
|
-
|
|
50
|
+
// NO_COLOR (piece 2 §1.2): cli-table3's @colors/colors ignores NO_COLOR, so
|
|
51
|
+
// empty its default red head / grey border when colour is off. Coloured
|
|
52
|
+
// output keeps the default style object (byte-identical).
|
|
53
|
+
const table = new Table(colorOff() ? { head: headers, style: { head: [], border: [] } } : { head: headers });
|
|
50
54
|
for (const row of dataRows) {
|
|
51
55
|
const cells = parseRow(row).map((c) => applySeverity(c));
|
|
52
56
|
table.push(cells);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/renderer/theme.ts
|
|
2
|
+
//
|
|
3
|
+
// Panels look colours (2026-09-26). Every panels string builder asks for a ROLE,
|
|
4
|
+
// never a raw colour, so dark/light terminals and colour depth are handled here
|
|
5
|
+
// once. The legacy look does not use this module.
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { getBackground } from './look.js';
|
|
8
|
+
export const PALETTE = {
|
|
9
|
+
dark: {
|
|
10
|
+
sap: '#5AA9FF', ok: '#6CCF7F', warn: '#F5B04D', bad: '#FF8A8A',
|
|
11
|
+
frame: '#33506F', dim: '#8A9BB0', brand: '#FFB38A',
|
|
12
|
+
},
|
|
13
|
+
light: {
|
|
14
|
+
sap: '#0064D9', ok: '#256F3A', warn: '#B44F00', bad: '#AA0808',
|
|
15
|
+
frame: '#A9B4BE', dim: '#556B82', brand: '#C65A24',
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export const BRAND_HEX = PALETTE.dark.brand;
|
|
19
|
+
/** Filled tag chip colours (SAP blue, white text) — readable on dark and light terminals. */
|
|
20
|
+
export const CHIP_BG = '#0070F2';
|
|
21
|
+
export const CHIP_FG = '#FFFFFF';
|
|
22
|
+
const BASIC = {
|
|
23
|
+
sap: (s) => chalk.blue(s),
|
|
24
|
+
ok: (s) => chalk.green(s),
|
|
25
|
+
warn: (s) => chalk.yellow(s),
|
|
26
|
+
bad: (s) => chalk.red(s),
|
|
27
|
+
frame: (s) => chalk.gray(s),
|
|
28
|
+
dim: (s) => chalk.gray(s),
|
|
29
|
+
brand: (s) => chalk.yellow(s),
|
|
30
|
+
};
|
|
31
|
+
export function paint(role, text) {
|
|
32
|
+
if (chalk.level === 0)
|
|
33
|
+
return text;
|
|
34
|
+
if (chalk.level === 1)
|
|
35
|
+
return BASIC[role](text);
|
|
36
|
+
return chalk.hex(PALETTE[getBackground()][role])(text);
|
|
37
|
+
}
|
|
38
|
+
export function chip(text) {
|
|
39
|
+
if (chalk.level === 0)
|
|
40
|
+
return `[${text}]`;
|
|
41
|
+
if (chalk.level === 1)
|
|
42
|
+
return chalk.bgBlue.white.bold(` ${text} `);
|
|
43
|
+
return chalk.bgHex(CHIP_BG).hex(CHIP_FG).bold(` ${text} `);
|
|
44
|
+
}
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
* fallback.
|
|
30
30
|
*/
|
|
31
31
|
import chalk from 'chalk';
|
|
32
|
+
import { getLook } from './look.js';
|
|
33
|
+
import { writeStdout, canAnimateInPlace } from './transcript-flow.js';
|
|
34
|
+
import { isDeclaredOneShot } from './tty.js';
|
|
32
35
|
const LLM_THRESHOLDS = [
|
|
33
36
|
{ at: 3_000, label: 'thinking…' },
|
|
34
37
|
{ at: 10_000, label: 'thinking… (10s)' },
|
|
@@ -71,7 +74,29 @@ export const AUDIT_HEARTBEAT_THRESHOLDS = [
|
|
|
71
74
|
{ at: 240_000, label: '[audit] …running (4m — a prior attempt timed out; retrying · Ctrl+C aborts the turn)' },
|
|
72
75
|
{ at: 300_000, label: '[audit] …running (5m — nearly at the retry ceiling; will stop and re-audit on resume if it can\'t finish)' },
|
|
73
76
|
];
|
|
77
|
+
/**
|
|
78
|
+
* Fix round 3 (item 8) — one-shot's routing wait (the classifier round-trip,
|
|
79
|
+
* up to ~16 s with its retry) showed nothing. These lines go to stderr in
|
|
80
|
+
* one-shot (the default sink there), so stdout stays the answer only.
|
|
81
|
+
*/
|
|
82
|
+
export const ROUTING_HEARTBEAT_THRESHOLDS = [
|
|
83
|
+
{ at: 1_000, label: 'routing…' },
|
|
84
|
+
{ at: 5_000, label: 'routing… (5s)' },
|
|
85
|
+
{ at: 10_000, label: 'routing… (10s)' },
|
|
86
|
+
];
|
|
74
87
|
export function startThinkingHeartbeat(opts) {
|
|
88
|
+
// Polish P2 (panels look): liveness lines are transient. Where a live
|
|
89
|
+
// indicator exists — Ink's TurnStatus row (chunkEmitter present) or the
|
|
90
|
+
// classic in-place spinner (real terminal) — the heartbeat prints nothing,
|
|
91
|
+
// so no "thinking…" / "Searching …" line is left in the transcript. Pipes
|
|
92
|
+
// and dumb terminals keep the permanent lines (nothing can be erased
|
|
93
|
+
// there). Custom sinks/schedules (the plan-chain audit) are unchanged.
|
|
94
|
+
if (getLook() === 'panels' &&
|
|
95
|
+
!opts?.emit &&
|
|
96
|
+
!opts?.thresholds &&
|
|
97
|
+
(opts?.chunkEmitter !== undefined || canAnimateInPlace())) {
|
|
98
|
+
return { stop: () => undefined };
|
|
99
|
+
}
|
|
75
100
|
const startedAt = Date.now();
|
|
76
101
|
let nextIdx = 0;
|
|
77
102
|
let stopped = false;
|
|
@@ -106,9 +131,15 @@ export function startThinkingHeartbeat(opts) {
|
|
|
106
131
|
// render tree and survives subsequent renders.
|
|
107
132
|
opts.chunkEmitter.emit('chunk', line + '\n');
|
|
108
133
|
}
|
|
134
|
+
else if (isDeclaredOneShot()) {
|
|
135
|
+
// Fix round 3 (item 7) — one-shot stdout is the answer only; liveness
|
|
136
|
+
// chatter goes to stderr (piped `cspeach "…" | cat` showed `thinking…`).
|
|
137
|
+
process.stderr.write(line + '\n');
|
|
138
|
+
}
|
|
109
139
|
else {
|
|
110
|
-
// Classic mode — direct stdout, plain newline-terminated line
|
|
111
|
-
|
|
140
|
+
// Classic mode — direct stdout, plain newline-terminated line
|
|
141
|
+
// (panels: through the transcript flow; legacy: verbatim).
|
|
142
|
+
writeStdout(line + '\n');
|
|
112
143
|
}
|
|
113
144
|
};
|
|
114
145
|
const tick = () => {
|
|
@@ -1,51 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* This module is the pure formatter for that block. todo.ts emits the
|
|
12
|
-
* returned string as a `chunk` on ctx.chunkEmitter after a successful set
|
|
13
|
-
* (routes to Ink Static scrollback AND the classic 'chunk'→stdout path);
|
|
14
|
-
* the raw ⏺/⎿ todo_set rows are widget-suppressed (tool-widget.ts) so the
|
|
15
|
-
* block is the sole visible representation — exactly mirroring ask_question.
|
|
16
|
-
*
|
|
17
|
-
* House style follows todo-panel.tsx glyphs precisely so the transcript
|
|
18
|
-
* block and the Ctrl+T panel read as the same list:
|
|
19
|
-
* ▣ completed (dim)
|
|
20
|
-
* ▶ in_progress (peach, bold)
|
|
21
|
-
* □ pending (default)
|
|
22
|
-
* Every row is truncate-end (Wave-1 rule — no soft-wrap), preceded by a
|
|
23
|
-
* slim `Tasks (done/total done)` header.
|
|
24
|
-
*/
|
|
25
|
-
import chalk from 'chalk';
|
|
26
|
-
import { PEACH } from './banners.js';
|
|
27
|
-
/** Max visible width of a row's TEXT (after the 2-char glyph+space prefix).
|
|
28
|
-
* 72-col total matches the Ctrl+T panel width; 70 leaves room for `▶ `. */
|
|
29
|
-
export const TODO_BLOCK_TEXT_MAX = 70;
|
|
30
|
-
function truncateEnd(s, max) {
|
|
31
|
-
return s.length > max ? s.substring(0, max - 1) + '…' : s;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Render the checklist as a scrollback block (leading + trailing newline for
|
|
35
|
-
* separation from surrounding transcript content). Returns ANSI-coloured
|
|
36
|
-
* text ready to emit as a `chunk`.
|
|
37
|
-
*/
|
|
38
|
-
export function formatTodoChecklistBlock(todos) {
|
|
39
|
-
const done = todos.filter((t) => t.status === 'completed').length;
|
|
40
|
-
const total = todos.length;
|
|
41
|
-
const header = chalk.dim(`Tasks (${done}/${total} done)`);
|
|
42
|
-
const rows = todos.map((t) => {
|
|
43
|
-
const text = truncateEnd(t.text, TODO_BLOCK_TEXT_MAX);
|
|
44
|
-
if (t.status === 'completed')
|
|
45
|
-
return chalk.dim(`▣ ${text}`);
|
|
46
|
-
if (t.status === 'in_progress')
|
|
47
|
-
return chalk.hex(PEACH).bold(`▶ ${text}`);
|
|
48
|
-
return `□ ${text}`;
|
|
1
|
+
import { formatChecklist } from './checklist-format.js';
|
|
2
|
+
import { uiWidth } from './ui-width.js';
|
|
3
|
+
/** Leading + trailing newline separate the block from surrounding scrollback. */
|
|
4
|
+
export function formatTodoChecklistBlock(todos, width = uiWidth()) {
|
|
5
|
+
const lines = formatChecklist(todos, {
|
|
6
|
+
width,
|
|
7
|
+
maxRows: Math.max(1, todos.length),
|
|
8
|
+
expanded: true,
|
|
9
|
+
foldedToOne: false,
|
|
49
10
|
});
|
|
50
|
-
return '\n' +
|
|
11
|
+
return '\n' + lines.join('\n') + '\n';
|
|
51
12
|
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const OBJECT_MAX = 60;
|
|
2
|
+
/**
|
|
3
|
+
* Every value shown in a label goes through here: ANSI escape sequences and
|
|
4
|
+
* other C0 control characters (incl. ESC) and DEL are stripped, whitespace
|
|
5
|
+
* (incl. newlines) collapses to one space, ends trimmed — one step, one line.
|
|
6
|
+
*/
|
|
7
|
+
// eslint-disable-next-line no-control-regex
|
|
8
|
+
export const ANSI_RE = /\u001b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
9
|
+
// eslint-disable-next-line no-control-regex
|
|
10
|
+
const CTRL_RE = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
|
|
11
|
+
export const clean = (s) => s.replace(ANSI_RE, '').replace(CTRL_RE, '').replace(/\s+/g, ' ').trim();
|
|
12
|
+
/** Raw string arg (for line counting only — never displayed). */
|
|
13
|
+
const raw = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
|
|
14
|
+
/** Display-safe string arg. */
|
|
15
|
+
const str = (v) => {
|
|
16
|
+
const r = raw(v);
|
|
17
|
+
if (r === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
const c = clean(r);
|
|
20
|
+
return c.length > 0 ? c : undefined;
|
|
21
|
+
};
|
|
22
|
+
const lineCount = (s) => (s === '' ? 0 : s.split(/\r?\n/).length);
|
|
23
|
+
/** Shorten from the LEFT for paths (the file name is what matters). */
|
|
24
|
+
const shortLeft = (s, max = OBJECT_MAX) => (s.length > max ? '…' + s.slice(s.length - (max - 1)) : s);
|
|
25
|
+
const shortRight = (s, max = OBJECT_MAX) => (s.length > max ? s.slice(0, max - 1) + '…' : s);
|
|
26
|
+
const humanise = (name) => clean(name.replace(/_/g, ' '));
|
|
27
|
+
const VERB_MAX = 40;
|
|
28
|
+
const sapObject = (verb) => (a) => ({ verb, object: shortRight(str(a['name']) ?? '') });
|
|
29
|
+
/** User-side names for class local includes; unknown values pass through. */
|
|
30
|
+
const INCLUDE_NAMES = {
|
|
31
|
+
locals_imp: 'local types',
|
|
32
|
+
locals_def: 'local definitions',
|
|
33
|
+
testclasses: 'test classes',
|
|
34
|
+
macros: 'macros',
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* sap_activate's real schema is a batch: `objects: [{ name, type }]`.
|
|
38
|
+
* Up to 3 names are shown; with 4 or more, the first two then "+N more". A bare `name` is still honoured.
|
|
39
|
+
*/
|
|
40
|
+
const activateObject = (a) => {
|
|
41
|
+
const list = Array.isArray(a['objects'])
|
|
42
|
+
? a['objects']
|
|
43
|
+
.map((o) => (o && typeof o === 'object' ? str(o['name']) : undefined))
|
|
44
|
+
.filter((n) => n !== undefined)
|
|
45
|
+
: [];
|
|
46
|
+
if (list.length === 0)
|
|
47
|
+
return str(a['name']) ?? '';
|
|
48
|
+
const shown = list.slice(0, list.length > 3 ? 2 : 3).join(', ');
|
|
49
|
+
return list.length > 3 ? `${shown} +${list.length - 2} more` : shown;
|
|
50
|
+
};
|
|
51
|
+
/** Length of a JSON array result (e.g. sap_search_object); undefined when it is not one — never a guess. */
|
|
52
|
+
const arrayCount = (r) => {
|
|
53
|
+
try {
|
|
54
|
+
const v = JSON.parse(r);
|
|
55
|
+
return Array.isArray(v) ? v.length : undefined;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/** sap_search_object's default maxResults (src/tools/sap-read.ts: `args.maxResults ?? 100`). */
|
|
62
|
+
const SEARCH_DEFAULT_MAX = 100;
|
|
63
|
+
const fileCount = (v) => (Array.isArray(v) ? `${v.length} ${v.length === 1 ? 'file' : 'files'}` : '');
|
|
64
|
+
const commandLine = (a) => [str(a['command']) ?? '', ...(Array.isArray(a['args']) ? a['args'].map(str).filter((x) => x !== undefined) : [])].join(' ').trim();
|
|
65
|
+
const RULES = {
|
|
66
|
+
file_read: (a, r) => ({ verb: 'Read', object: shortLeft(str(a['path']) ?? ''), ...(r !== undefined ? { detail: `${lineCount(r)} lines` } : {}) }),
|
|
67
|
+
file_write: (a) => ({ verb: 'Wrote', object: shortLeft(str(a['path']) ?? ''), detail: `${lineCount(raw(a['content']) ?? '')} lines` }),
|
|
68
|
+
file_edit: (a) => ({
|
|
69
|
+
verb: 'Edited',
|
|
70
|
+
object: shortLeft(str(a['path']) ?? ''),
|
|
71
|
+
detail: `+${lineCount(raw(a['new_string']) ?? '')} −${lineCount(raw(a['old_string']) ?? '')}`,
|
|
72
|
+
}),
|
|
73
|
+
grep: (a) => ({ verb: 'Searched', object: shortRight(`"${str(a['pattern']) ?? ''}"`) }),
|
|
74
|
+
glob: (a) => ({ verb: 'Listed', object: shortRight(str(a['pattern']) ?? '') }),
|
|
75
|
+
shell_exec: (a, r) => {
|
|
76
|
+
const exit = r ? /exit code:\s*(-?\d+|null)/.exec(r)?.[1] : undefined;
|
|
77
|
+
return { verb: 'Ran', object: shortRight(commandLine(a)), ...(exit !== undefined ? { detail: `exit ${exit}` } : {}) };
|
|
78
|
+
},
|
|
79
|
+
background_run: (a) => ({ verb: 'Started', object: shortRight(commandLine(a)) }),
|
|
80
|
+
sap_get_source: sapObject('Read'),
|
|
81
|
+
sap_set_source: sapObject('Wrote'),
|
|
82
|
+
sap_activate: (a) => ({ verb: 'Activated', object: shortRight(activateObject(a)) }),
|
|
83
|
+
sap_syntax_check: sapObject('Checked syntax'),
|
|
84
|
+
sap_atc_run: sapObject('Ran ATC on'),
|
|
85
|
+
sap_delete_object: sapObject('Deleted'),
|
|
86
|
+
sap_snapshot_take: sapObject('Snapshot'),
|
|
87
|
+
sap_usage_references: sapObject('Where-used for'),
|
|
88
|
+
sap_create_object: (a) => ({ verb: 'Created', object: shortRight([str(a['type']), str(a['name'])].filter(Boolean).join(' ')) }),
|
|
89
|
+
sap_update_method: (a) => ({ verb: 'Updated', object: shortRight(`${str(a['class_name']) ?? ''} → ${str(a['method_name']) ?? ''}`) }),
|
|
90
|
+
sap_set_class_include: (a) => {
|
|
91
|
+
const inc = str(a['includeType']);
|
|
92
|
+
const incName = inc !== undefined ? (Object.hasOwn(INCLUDE_NAMES, inc) ? INCLUDE_NAMES[inc] : inc) : '';
|
|
93
|
+
return { verb: 'Wrote', object: shortRight(`${str(a['className']) ?? ''} ${incName}`.trim()) };
|
|
94
|
+
},
|
|
95
|
+
sap_search_object: (a, r) => {
|
|
96
|
+
// "*AGING*" · PROG — the type filter tells otherwise identical searches apart.
|
|
97
|
+
const type = str(a['type']);
|
|
98
|
+
const count = r !== undefined ? arrayCount(r) : undefined;
|
|
99
|
+
// At the cap (maxResults, default 100 as in the tool) there may be more: "100+ hits".
|
|
100
|
+
const max = typeof a['maxResults'] === 'number' && a['maxResults'] > 0 ? a['maxResults'] : SEARCH_DEFAULT_MAX;
|
|
101
|
+
const hits = count === undefined ? undefined : count >= max ? `${max}+ hits` : `${count} ${count === 1 ? 'hit' : 'hits'}`;
|
|
102
|
+
return {
|
|
103
|
+
verb: 'Searched',
|
|
104
|
+
object: shortRight(`"${str(a['query']) ?? ''}"${type ? ` · ${type}` : ''}`),
|
|
105
|
+
...(hits !== undefined ? { detail: hits } : {}),
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
sap_sql_query: (a) => ({ verb: 'Queried', object: shortRight(str(a['query']) ?? '') }),
|
|
109
|
+
sap_inactive_objects: () => ({ verb: 'Checked inactive objects', object: '' }),
|
|
110
|
+
fiori_scaffold_fe: (a) => ({ verb: 'Scaffolded app', object: shortLeft(str(a['basePath']) ?? '') }),
|
|
111
|
+
fiori_render_smoke: (a) => ({ verb: 'Render check', object: shortRight(str(a['previewId']) ?? str(a['appUrl']) ?? '') }),
|
|
112
|
+
fiori_draft_smoke: (a) => ({ verb: 'Draft check', object: shortRight(str(a['servicePath']) ?? '') }),
|
|
113
|
+
request_approval: (a) => ({ verb: 'Approval', object: shortRight(str(a['summary']) ?? '') }),
|
|
114
|
+
// Polish wave (P4): every other registered tool, so none falls back to
|
|
115
|
+
// "tool name key=value". Coverage test: tool-labels-coverage.test.ts.
|
|
116
|
+
sap_object_structure: sapObject('Structure'),
|
|
117
|
+
sap_change_log: sapObject('Change log'),
|
|
118
|
+
sap_short_dump: (a) => ({ verb: 'Read dumps', object: shortRight(str(a['dumpId']) ?? [str(a['user']), str(a['fromDate'])].filter(Boolean).join(' ')) }),
|
|
119
|
+
sap_api_state: (a) => ({ verb: 'API state', object: shortRight(activateObject(a)) }),
|
|
120
|
+
sap_abap_docu: (a) => ({ verb: 'ABAP docs', object: shortRight(str(a['keyword']) ?? '') }),
|
|
121
|
+
sap_class_includes: (a) => ({ verb: 'Read includes', object: shortRight([str(a['className']), str(a['includeType'])].filter(Boolean).join(' ')) }),
|
|
122
|
+
sap_diff: sapObject('Compared'),
|
|
123
|
+
sap_preview_change: sapObject('Previewed change'),
|
|
124
|
+
sap_run_unit_test: sapObject('Ran unit tests'),
|
|
125
|
+
sap_odata_test: (a) => ({ verb: 'OData test', object: shortRight([str(a['servicePath']), str(a['entity'])].filter(Boolean).join(' ')) }),
|
|
126
|
+
sap_badi_list: (a) => ({ verb: 'Searched BAdIs', object: shortRight(`"${str(a['query']) ?? ''}"`) }),
|
|
127
|
+
sap_snapshot_list: (a) => ({ verb: 'Listed snapshots', object: shortRight(str(a['name']) ?? '') }),
|
|
128
|
+
sap_snapshot_read: sapObject('Read snapshot'),
|
|
129
|
+
sap_snapshot_cleanup: () => ({ verb: 'Cleaned snapshots', object: '' }),
|
|
130
|
+
sap_transport_create: (a) => ({ verb: 'Created transport', object: shortRight(str(a['description']) ?? '') }),
|
|
131
|
+
sap_transport_for_object: (a) => ({ verb: 'Transport for', object: shortRight(str(a['object_name']) ?? '') }),
|
|
132
|
+
sap_transport_list: (a) => ({ verb: 'Listed transports', object: shortRight(str(a['owner']) ?? '') }),
|
|
133
|
+
sap_transport_release: (a) => ({ verb: 'Released', object: shortRight(str(a['transport']) ?? '') }),
|
|
134
|
+
sap_create_domain: sapObject('Created domain'),
|
|
135
|
+
sap_create_data_element: sapObject('Created data element'),
|
|
136
|
+
sap_lock: sapObject('Locked'),
|
|
137
|
+
sap_unlock: sapObject('Unlocked'),
|
|
138
|
+
sap_service_binding_publish: sapObject('Published'),
|
|
139
|
+
sap_service_binding_unpublish: sapObject('Unpublished'),
|
|
140
|
+
sap_message_maintain: (a) => ({ verb: 'Message', object: shortRight([str(a['messageClass']), str(a['messageNumber'])].filter(Boolean).join(' ')) }),
|
|
141
|
+
sap_number_range_intervals: (a) => ({ verb: 'Number range', object: shortRight(str(a['nrObject']) ?? '') }),
|
|
142
|
+
ask_question: (a) => ({ verb: 'Question', object: shortRight(str(a['question']) ?? '') }),
|
|
143
|
+
todo_set: () => ({ verb: 'Updated todos', object: '' }),
|
|
144
|
+
dispatch_skill: (a) => ({ verb: 'Ran skill', object: shortRight(str(a['command']) ?? '') }),
|
|
145
|
+
read_document: (a, r) => ({ verb: 'Read document', object: shortLeft(str(a['path']) ?? ''), ...(r !== undefined ? { detail: `${lineCount(r)} lines` } : {}) }),
|
|
146
|
+
web_fetch: (a) => ({ verb: 'Fetched', object: shortRight(str(a['url']) ?? '') }),
|
|
147
|
+
web_search: (a) => ({ verb: 'Searched web', object: shortRight(`"${str(a['query']) ?? ''}"`) }),
|
|
148
|
+
project_context_get: () => ({ verb: 'Read project context', object: '' }),
|
|
149
|
+
playbook_get: (a) => ({ verb: 'Read playbook', object: shortRight(str(a['name']) ?? '') }),
|
|
150
|
+
convention_get: (a) => ({ verb: 'Read convention', object: shortRight(str(a['field']) ?? '') }),
|
|
151
|
+
cca_merge: (a) => ({ verb: 'Merged CCA', object: fileCount(a['inputs']) }),
|
|
152
|
+
upgrade_merge_progress: (a) => ({ verb: 'Merged progress', object: fileCount(a['inputs']) }),
|
|
153
|
+
monitor_emit: (a) => ({ verb: 'Checked background', object: shortRight(str(a['bg_id']) ?? '') }),
|
|
154
|
+
agent_run: (a) => ({ verb: 'Subagent', object: shortRight(str(a['skill']) ?? '') }),
|
|
155
|
+
schedule_draft_create: (a) => ({ verb: 'Drafted schedule', object: shortRight(str(a['when']) ?? '') }),
|
|
156
|
+
fiori_scaffold: (a) => ({ verb: 'Scaffolded app', object: shortLeft(str(a['basePath']) ?? '') }),
|
|
157
|
+
fiori_apply: (a) => ({ verb: 'Applied', object: shortRight([str(a['entry']), str(a['appDir'])].filter(Boolean).join(' · ')) }),
|
|
158
|
+
fiori_list: () => ({ verb: 'Listed patterns', object: '' }),
|
|
159
|
+
fiori_deploy_config: (a) => ({ verb: 'Deploy config', object: shortRight(str(a['name']) ?? str(a['appDir']) ?? '') }),
|
|
160
|
+
fiori_fe_extend: (a) => ({ verb: 'Extended app', object: shortRight([str(a['op']), str(a['basePath'])].filter(Boolean).join(' · ')) }),
|
|
161
|
+
fiori_preview: (a) => ({ verb: 'Preview', object: shortRight([str(a['action']) ?? 'start', str(a['appDir'])].filter(Boolean).join(' ')) }),
|
|
162
|
+
fiori_sample_search: (a) => ({ verb: 'Searched samples', object: shortRight(`"${str(a['text']) ?? ''}"`) }),
|
|
163
|
+
fiori_sample_get: (a) => ({ verb: 'Read sample', object: shortRight(str(a['name']) ?? '') }),
|
|
164
|
+
fiori_metadata_audit: (a) => ({ verb: 'Audited metadata', object: shortRight(str(a['servicePath']) ?? '') }),
|
|
165
|
+
extend_model_insert: (a) => ({ verb: 'Extended model', object: shortRight([str(a['kind']), str(a['field'])].filter(Boolean).join(' · ')) }),
|
|
166
|
+
system_capability: (a) => ({ verb: 'Checked capability', object: shortRight(str(a['capability']) ?? '') }),
|
|
167
|
+
};
|
|
168
|
+
/** True when the tool has its own label (not the generic fallback). */
|
|
169
|
+
export function hasToolLabel(name) {
|
|
170
|
+
return Object.hasOwn(RULES, name);
|
|
171
|
+
}
|
|
172
|
+
function fallback(name, a) {
|
|
173
|
+
// Only simple values: strings (display-cleaned; JSON-looking strings skipped),
|
|
174
|
+
// numbers and booleans. null, arrays and objects are never shown.
|
|
175
|
+
const simple = [];
|
|
176
|
+
for (const [k, v] of Object.entries(a)) {
|
|
177
|
+
if (simple.length >= 2)
|
|
178
|
+
break;
|
|
179
|
+
let shown;
|
|
180
|
+
if (typeof v === 'string') {
|
|
181
|
+
const c = str(v);
|
|
182
|
+
shown = c !== undefined && !/^[{[]/.test(c) ? c : undefined;
|
|
183
|
+
}
|
|
184
|
+
else if (typeof v === 'number' || typeof v === 'boolean') {
|
|
185
|
+
shown = String(v);
|
|
186
|
+
}
|
|
187
|
+
const key = clean(k);
|
|
188
|
+
if (shown !== undefined && key.length > 0)
|
|
189
|
+
simple.push(`${key}=${shown}`);
|
|
190
|
+
}
|
|
191
|
+
return { verb: shortRight(humanise(name), VERB_MAX), object: shortRight(simple.join(' · ')) };
|
|
192
|
+
}
|
|
193
|
+
export function toolLabel(name, args, resultContent) {
|
|
194
|
+
const rule = RULES[name];
|
|
195
|
+
try {
|
|
196
|
+
return rule ? rule(args ?? {}, resultContent) : fallback(name, args ?? {});
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return fallback(name, {});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const PRESENT = {
|
|
203
|
+
Read: 'Reading', Wrote: 'Writing', Edited: 'Editing', Searched: 'Searching', Listed: 'Listing',
|
|
204
|
+
Ran: 'Running', Started: 'Starting', Activated: 'Activating', 'Checked syntax': 'Checking syntax',
|
|
205
|
+
'Ran ATC on': 'Running ATC on', Deleted: 'Deleting', Snapshot: 'Taking snapshot of',
|
|
206
|
+
'Where-used for': 'Finding where-used for', Created: 'Creating', Updated: 'Updating',
|
|
207
|
+
Queried: 'Querying', 'Checked inactive objects': 'Checking inactive objects',
|
|
208
|
+
'Scaffolded app': 'Scaffolding app', 'Render check': 'Running render check',
|
|
209
|
+
'Draft check': 'Running draft check', Approval: 'Waiting for approval',
|
|
210
|
+
Structure: 'Reading structure of', 'Change log': 'Reading change log of', 'Read dumps': 'Reading dumps',
|
|
211
|
+
'API state': 'Checking API state of', 'ABAP docs': 'Reading ABAP docs for', 'Read includes': 'Reading includes of',
|
|
212
|
+
Compared: 'Comparing', 'Previewed change': 'Previewing change to', 'Ran unit tests': 'Running unit tests on',
|
|
213
|
+
'OData test': 'Testing OData', 'Searched BAdIs': 'Searching BAdIs', 'Listed snapshots': 'Listing snapshots',
|
|
214
|
+
'Read snapshot': 'Reading snapshot of', 'Cleaned snapshots': 'Cleaning snapshots',
|
|
215
|
+
'Created transport': 'Creating transport', 'Transport for': 'Finding transport for',
|
|
216
|
+
'Listed transports': 'Listing transports', Released: 'Releasing', 'Created domain': 'Creating domain',
|
|
217
|
+
'Created data element': 'Creating data element', Locked: 'Locking', Unlocked: 'Unlocking',
|
|
218
|
+
Published: 'Publishing', Unpublished: 'Unpublishing', Message: 'Maintaining message',
|
|
219
|
+
'Number range': 'Maintaining number range', Question: 'Asking', 'Updated todos': 'Updating todos',
|
|
220
|
+
'Ran skill': 'Running skill', 'Read document': 'Reading document', Fetched: 'Fetching',
|
|
221
|
+
'Searched web': 'Searching web', 'Read project context': 'Reading project context',
|
|
222
|
+
'Read playbook': 'Reading playbook', 'Read convention': 'Reading convention', 'Merged CCA': 'Merging CCA',
|
|
223
|
+
'Merged progress': 'Merging progress', 'Checked background': 'Checking background',
|
|
224
|
+
Subagent: 'Running subagent', 'Drafted schedule': 'Drafting schedule', Applied: 'Applying',
|
|
225
|
+
'Listed patterns': 'Listing patterns', 'Deploy config': 'Writing deploy config', 'Extended app': 'Extending app',
|
|
226
|
+
Preview: 'Preview', 'Searched samples': 'Searching samples', 'Read sample': 'Reading sample',
|
|
227
|
+
'Audited metadata': 'Auditing metadata', 'Extended model': 'Extending model',
|
|
228
|
+
'Checked capability': 'Checking capability',
|
|
229
|
+
};
|
|
230
|
+
export function toolActivityLabel(name, args) {
|
|
231
|
+
const l = toolLabel(name, args);
|
|
232
|
+
const verb = PRESENT[l.verb] ?? `Running ${l.verb}`;
|
|
233
|
+
return l.object ? `${verb} ${l.object}` : verb;
|
|
234
|
+
}
|
|
235
|
+
export function foldOutput(text, keep) {
|
|
236
|
+
const all = text.split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim().length > 0);
|
|
237
|
+
return { lines: all.slice(0, keep), hidden: Math.max(0, all.length - keep) };
|
|
238
|
+
}
|