toolcraft 0.0.231 → 0.0.233
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/composition.json +3 -3
- package/dist/applied-default.d.ts +3 -0
- package/dist/applied-default.js +16 -0
- package/dist/cli.js +508 -204
- package/dist/composition.json +3 -3
- package/dist/discriminator.d.ts +9 -0
- package/dist/discriminator.js +32 -0
- package/dist/error-report.d.ts +2 -18
- package/dist/error-report.js +12 -14
- package/dist/http.js +8 -1
- package/dist/human-in-loop/gate.js +6 -0
- package/dist/index.compile-check.js +22 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +50 -0
- package/dist/json-schema-converter.d.ts +14 -6
- package/dist/json-schema-converter.js +150 -20
- package/dist/mcp-proxy-metadata.d.ts +2 -0
- package/dist/mcp-proxy-metadata.js +13 -0
- package/dist/mcp-proxy.d.ts +1 -0
- package/dist/mcp-proxy.js +65 -47
- package/dist/mcp-result.d.ts +3 -0
- package/dist/mcp-result.js +10 -0
- package/dist/mcp.js +211 -153
- package/dist/redaction.js +26 -17
- package/dist/renderer.js +40 -29
- package/dist/schema-member-names.d.ts +2 -0
- package/dist/schema-member-names.js +39 -0
- package/dist/schema-scope.d.ts +1 -0
- package/dist/schema-scope.js +29 -16
- package/dist/schema.d.ts +1 -1
- package/dist/sdk.d.ts +2 -1
- package/dist/sdk.js +164 -50
- package/dist/stream.js +24 -23
- package/dist/testing/diagnostic-value.d.ts +1 -0
- package/dist/testing/diagnostic-value.js +57 -0
- package/dist/testing/fakes.js +2 -1
- package/dist/testing/harness.js +1 -0
- package/dist/testing/memory-fs.js +42 -1
- package/dist/testing/parity.js +50 -6
- package/dist/union-validation.d.ts +5 -0
- package/dist/union-validation.js +36 -0
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +2 -0
- package/node_modules/mcp-oauth-server/dist/index.js +47 -6
- package/node_modules/tiny-http-mcp-server/dist/cli.js +14 -2
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/dist/http-server.d.ts +7 -5
- package/node_modules/tiny-http-mcp-server/dist/http-server.js +3 -11
- package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +6 -1
- package/node_modules/tiny-http-mcp-server/dist/http-transport.js +196 -15
- package/node_modules/tiny-http-mcp-server/dist/index.d.ts +2 -2
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.d.ts +3 -0
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +35 -0
- package/node_modules/tiny-http-mcp-server/dist/parse-body.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/dist/parse-body.js +9 -5
- package/node_modules/tiny-http-mcp-server/dist/test-support.js +17 -0
- package/node_modules/tiny-http-mcp-server/dist/testing.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/dist/testing.js +3 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +21 -4
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +14 -6
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +8 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.js +50 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +21 -3
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/README.md +1 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +178 -101
- package/node_modules/tiny-mcp-client/dist/index.js +4696 -291
- package/node_modules/tiny-mcp-client/package.json +2 -1
- package/node_modules/tiny-stdio-mcp-server/dist/base64.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/base64.js +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +5 -1
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +41 -6
- package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/headers.d.ts +10 -0
- package/node_modules/tiny-stdio-mcp-server/dist/headers.js +180 -0
- package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +5 -5
- package/node_modules/tiny-stdio-mcp-server/dist/index.js +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.d.ts +220 -0
- package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.js +1995 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +38 -25
- package/node_modules/tiny-stdio-mcp-server/dist/metadata.d.ts +2 -0
- package/node_modules/tiny-stdio-mcp-server/dist/metadata.js +37 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.d.ts +4 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.js +53 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol.d.ts +17 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol.js +228 -0
- package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.js +18 -0
- package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.d.ts +18 -0
- package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.js +34 -0
- package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +4 -1
- package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +9 -4
- package/node_modules/tiny-stdio-mcp-server/dist/server.js +334 -135
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.js +50 -28
- package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.d.ts +11 -0
- package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.js +128 -0
- package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +58 -18
- package/node_modules/tiny-stdio-mcp-server/dist/types.js +2 -1
- package/node_modules/tiny-stdio-mcp-server/dist/uri.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/uri.js +41 -0
- package/node_modules/tiny-stdio-mcp-server/package.json +8 -0
- package/node_modules/toolcraft-design/dist/command-registry.d.ts +11 -0
- package/node_modules/toolcraft-design/dist/command-registry.js +24 -0
- package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/ansi.js +81 -27
- package/node_modules/toolcraft-design/dist/dashboard/buffer.js +39 -25
- package/node_modules/toolcraft-design/dist/dashboard/components/border.js +6 -5
- package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +26 -21
- package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +12 -6
- package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +2 -1
- package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +51 -10
- package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +77 -4
- package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/index.js +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/keymap.js +26 -6
- package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/layout.js +19 -3
- package/node_modules/toolcraft-design/dist/dashboard/line-buffer.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/dashboard/line-buffer.js +63 -0
- package/node_modules/toolcraft-design/dist/dashboard/output-preview.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/dashboard/output-preview.js +70 -0
- package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +3 -1
- package/node_modules/toolcraft-design/dist/dashboard/store.js +9 -3
- package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.js +108 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +3 -1
- package/node_modules/toolcraft-design/dist/dashboard/terminal.js +11 -1
- package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.js +126 -0
- package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +3 -1
- package/node_modules/toolcraft-design/dist/event-groups.d.ts +19 -0
- package/node_modules/toolcraft-design/dist/event-groups.js +49 -0
- package/node_modules/toolcraft-design/dist/index.d.ts +6 -1
- package/node_modules/toolcraft-design/dist/index.js +5 -1
- package/node_modules/toolcraft-design/dist/inline-notice.d.ts +14 -0
- package/node_modules/toolcraft-design/dist/inline-notice.js +28 -0
- package/node_modules/toolcraft-design/dist/metric.d.ts +8 -0
- package/node_modules/toolcraft-design/dist/metric.js +21 -0
- package/node_modules/toolcraft-design/dist/overlay-manager.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/overlay-manager.js +10 -0
- package/node_modules/toolcraft-design/dist/primitives.d.ts +12 -0
- package/node_modules/toolcraft-design/dist/primitives.js +7 -0
- package/node_modules/toolcraft-design/dist/progress-group.d.ts +7 -0
- package/node_modules/toolcraft-design/dist/progress-group.js +10 -0
- package/node_modules/toolcraft-design/dist/render-performance.d.ts +33 -0
- package/node_modules/toolcraft-design/dist/render-performance.js +71 -0
- package/node_modules/toolcraft-design/dist/task-tree.d.ts +22 -0
- package/node_modules/toolcraft-design/dist/task-tree.js +79 -0
- package/node_modules/toolcraft-design/dist/viewport.d.ts +18 -0
- package/node_modules/toolcraft-design/dist/viewport.js +56 -0
- package/node_modules/toolcraft-schema/dist/clone-default.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/clone-default.js +26 -0
- package/node_modules/toolcraft-schema/dist/index.compile-check.js +1 -0
- package/node_modules/toolcraft-schema/dist/index.d.ts +18 -9
- package/node_modules/toolcraft-schema/dist/index.js +46 -8
- package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +6 -0
- package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +6 -3
- package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.js +69 -0
- package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json.d.ts +4 -1
- package/node_modules/toolcraft-schema/dist/json.js +57 -1
- package/node_modules/toolcraft-schema/dist/native-json-schema.d.ts +10 -0
- package/node_modules/toolcraft-schema/dist/native-json-schema.js +11 -0
- package/node_modules/toolcraft-schema/dist/validate.d.ts +5 -1
- package/node_modules/toolcraft-schema/dist/validate.js +61 -47
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createDashboard } from "../dashboard.js";
|
|
2
|
+
import { createDashboardLineBuffer } from "../line-buffer.js";
|
|
3
|
+
// Fake output only: no agent, credentials, repository writes, or network requests.
|
|
4
|
+
const scenario = process.argv[2] ?? "streaming";
|
|
5
|
+
const labelControl = scenario === "label-controls" ? "\u001b]52;c;" + "HIDDEN_".repeat(100) + "\u0007" : "";
|
|
6
|
+
const dashboard = createDashboard({
|
|
7
|
+
title: scenario === "unicode-title" ? "Pipeline · 界界 · 👩💻 · é · ".repeat(3) : `Pipeline · ${scenario}${labelControl}`,
|
|
8
|
+
statsTitle: scenario === "unicode-title" ? "Run · 界界 · 👩💻 · é" : `Run${labelControl}`,
|
|
9
|
+
rightPaneWidth: 32,
|
|
10
|
+
hints: [
|
|
11
|
+
{ key: "q", label: `Quit${labelControl}` },
|
|
12
|
+
{ key: "↑↓", label: "Scroll" },
|
|
13
|
+
{ key: "F", label: "Follow" }
|
|
14
|
+
]
|
|
15
|
+
});
|
|
16
|
+
let count = 0;
|
|
17
|
+
let timer;
|
|
18
|
+
const append = (kind, text) => {
|
|
19
|
+
dashboard.appendOutput({ kind, text, ts: count++ });
|
|
20
|
+
};
|
|
21
|
+
const shutdown = () => {
|
|
22
|
+
clearInterval(timer);
|
|
23
|
+
dashboard.destroy();
|
|
24
|
+
};
|
|
25
|
+
dashboard.onCommand((command) => {
|
|
26
|
+
if (command === "quit" || command === "forceQuit")
|
|
27
|
+
shutdown();
|
|
28
|
+
});
|
|
29
|
+
process.once("SIGINT", shutdown);
|
|
30
|
+
process.once("SIGTERM", shutdown);
|
|
31
|
+
dashboard.start();
|
|
32
|
+
if (scenario !== "empty")
|
|
33
|
+
dashboard.updateStats({
|
|
34
|
+
status: "running",
|
|
35
|
+
iterationsLabel: `Tasks${labelControl}`,
|
|
36
|
+
iterations: 1,
|
|
37
|
+
tokensIn: 24000,
|
|
38
|
+
tokensOut: 7000,
|
|
39
|
+
elapsedMs: 65000,
|
|
40
|
+
currentAction: "Improve streaming output (implement)"
|
|
41
|
+
});
|
|
42
|
+
append("info", "Config · codex · model-example · docs/plans/fake-pipeline.md");
|
|
43
|
+
if (scenario !== "empty")
|
|
44
|
+
append("status", "Task 2/8 · Improve streaming output (implement)");
|
|
45
|
+
if (scenario === "empty") {
|
|
46
|
+
dashboard.updateStats({
|
|
47
|
+
status: "done",
|
|
48
|
+
iterationsLabel: "Tasks",
|
|
49
|
+
iterations: 0,
|
|
50
|
+
tokensIn: 0,
|
|
51
|
+
tokensOut: 0,
|
|
52
|
+
elapsedMs: 0,
|
|
53
|
+
currentAction: "Nothing to run"
|
|
54
|
+
});
|
|
55
|
+
append("info", "All tasks are already complete.");
|
|
56
|
+
}
|
|
57
|
+
else if (scenario === "oversized") {
|
|
58
|
+
append("tool", "output word ".repeat(50000) + "LATEST RESULT");
|
|
59
|
+
}
|
|
60
|
+
else if (scenario === "newline-free") {
|
|
61
|
+
const output = createDashboardLineBuffer((line) => append("tool", line));
|
|
62
|
+
for (let index = 0; index < 1000; index += 1)
|
|
63
|
+
output.push("output word ".repeat(100));
|
|
64
|
+
output.push("LATEST RESULT");
|
|
65
|
+
output.flush();
|
|
66
|
+
}
|
|
67
|
+
else if (scenario === "execution-error") {
|
|
68
|
+
append("error", "Fake execution threw before producing a task result");
|
|
69
|
+
dashboard.updateStats({ status: "error", currentAction: undefined });
|
|
70
|
+
}
|
|
71
|
+
else if (scenario === "failure") {
|
|
72
|
+
append("tool", "npm test · checking streaming output");
|
|
73
|
+
append("error", "Tool exited with code 1\nExpected task result, received empty output\nRetry after correcting the task.");
|
|
74
|
+
dashboard.updateStats({ status: "error", currentAction: "Task 2/8 failed (implement)" });
|
|
75
|
+
}
|
|
76
|
+
else if (scenario === "unicode") {
|
|
77
|
+
append("info", "解析中 · 界界 · café · 👩💻 · é");
|
|
78
|
+
append("tool", "\u001b[31merror\u001b[0m normal \u001b[1;32msuccess\u001b[0m\nindented\tcolumn\nprogress 10%\rprogress 100%");
|
|
79
|
+
append("success", "Unicode and ANSI fixture ready");
|
|
80
|
+
}
|
|
81
|
+
else if (scenario === "label-controls") {
|
|
82
|
+
append("success", "Label control fixture ready");
|
|
83
|
+
}
|
|
84
|
+
else if (scenario === "unicode-title") {
|
|
85
|
+
append("success", "Unicode heading fixture ready");
|
|
86
|
+
}
|
|
87
|
+
else if (scenario === "cursor-controls") {
|
|
88
|
+
append("tool", "\u001b[32m界界\rA\u001b[0m\n😀\bX\n👩💻\bX\né\bX\na\tB\rX");
|
|
89
|
+
append("tool", "\u001b]52;c;HIDDEN_OSC_PAYLOAD\u0007Visible OSC result");
|
|
90
|
+
append("tool", "\u001bP HIDDEN_DCS_PAYLOAD\u001b\\Visible DCS result");
|
|
91
|
+
append("tool", "\u001b[2J\u001b[1;1HFrame preserved");
|
|
92
|
+
append("tool", "left\u007fright · \u009b2JC1 frame preserved");
|
|
93
|
+
append("tool", "\u009dHIDDEN_C1_OSC\u009cVisible C1 OSC result");
|
|
94
|
+
append("tool", "\u0090HIDDEN_FIRST\u0007HIDDEN_SECOND\u009cVisible C1 DCS result");
|
|
95
|
+
append("tool", "\u001bP" + "HIDDEN_LONG_".repeat(4_000) + "\u001b\\Visible long DCS result");
|
|
96
|
+
const output = createDashboardLineBuffer((line) => append("tool", line));
|
|
97
|
+
output.push("\u001b]HIDDEN_FIRST\n");
|
|
98
|
+
output.push("HIDDEN_SECOND\nHIDDEN_THIRD\u001b");
|
|
99
|
+
output.push("\\Visible multiline OSC result\n");
|
|
100
|
+
append("tool", "\u001b[" + "0;".repeat(20_000) + "mVisible long CSI result");
|
|
101
|
+
append("tool", "\u001bPHIDDEN_CANCELLED_DCS\u0018Visible cancelled DCS result");
|
|
102
|
+
append("tool", "\u001b]HIDDEN_CANCELLED_OSC\u001aVisible cancelled OSC result");
|
|
103
|
+
append("tool", "\u001b[0;\u001b[32mVisible restarted CSI result\u001b[0m");
|
|
104
|
+
append("tool", "Tab preservation fixture\nABCDEF\r\tX");
|
|
105
|
+
append("tool", "\u001b[31mABCDEF\u001b[0m\r\tX\n界界AB\r\tX");
|
|
106
|
+
append("tool", "Erase-line fixture\nprogress 100%\rprogress 50%\u001b[K\nABCDE\rXX\u001b[1K");
|
|
107
|
+
append("tool", "界界\b\u001b[KX\n界界AB\rXX\u001b[1K");
|
|
108
|
+
append("success", "Cursor control fixture ready");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
for (let index = 0; index < 200; index += 1) {
|
|
112
|
+
append("tool", `[implement] Inspecting source file ${index} and running focused checks`);
|
|
113
|
+
}
|
|
114
|
+
if (scenario === "burst") {
|
|
115
|
+
timer = setInterval(() => {
|
|
116
|
+
for (let index = 0; index < 100; index += 1)
|
|
117
|
+
append("tool", `burst output ${count}`);
|
|
118
|
+
}, 100);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
timer = setInterval(() => append("info", `Streaming response ${count} · working on task 2/8`), 500);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Keep terminal state visible until QA sends q, including completed/error scenarios.
|
|
125
|
+
if (timer === undefined)
|
|
126
|
+
timer = setInterval(() => { }, 1000);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type OutputItemKind = "info" | "success" | "error" | "tool" | "status";
|
|
2
2
|
export type OutputItem = {
|
|
3
|
+
/** Reusing an id replaces its retained preview in place, without changing held history. */
|
|
4
|
+
id?: string;
|
|
3
5
|
kind: OutputItemKind;
|
|
4
6
|
text: string;
|
|
5
7
|
ts: number;
|
|
@@ -13,7 +15,7 @@ export type DashboardStats = {
|
|
|
13
15
|
elapsedMs: number;
|
|
14
16
|
currentAction?: string;
|
|
15
17
|
};
|
|
16
|
-
export type Command = "quit" | "forceQuit" | "edit" | "pause" | "retry" | "view-log";
|
|
18
|
+
export type Command = "quit" | "forceQuit" | "edit" | "pause" | "retry" | "view-log" | "scroll-up" | "scroll-down" | "page-up" | "page-down" | "follow" | "render-stats";
|
|
17
19
|
export type DashboardState = {
|
|
18
20
|
output: OutputItem[];
|
|
19
21
|
stats: DashboardStats;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface GroupEvent {
|
|
2
|
+
id: string;
|
|
3
|
+
text: string;
|
|
4
|
+
error?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface EventGroupRow extends GroupEvent {
|
|
7
|
+
groupId: string;
|
|
8
|
+
header?: boolean;
|
|
9
|
+
expanded?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function createEventGroups({ capacity, children }: {
|
|
12
|
+
capacity: number;
|
|
13
|
+
children: number;
|
|
14
|
+
}): {
|
|
15
|
+
append(groupId: string, event: GroupEvent): void;
|
|
16
|
+
toggle(groupId: string): void;
|
|
17
|
+
rows(offset: number, height: number): EventGroupRow[];
|
|
18
|
+
};
|
|
19
|
+
export declare function renderEventGroupRows(rows: readonly EventGroupRow[], width: number): string[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { plainTerminalText } from "./dashboard/ansi.js";
|
|
2
|
+
import { fitToWidth } from "./explorer/render/text.js";
|
|
3
|
+
import { limitOutputPreview } from "./dashboard/output-preview.js";
|
|
4
|
+
export function createEventGroups({ capacity, children }) {
|
|
5
|
+
if (!Number.isInteger(capacity) || capacity < 1 || !Number.isInteger(children) || children < 1)
|
|
6
|
+
throw new RangeError("positive capacities required");
|
|
7
|
+
const groups = new Map();
|
|
8
|
+
return {
|
|
9
|
+
append(groupId, event) {
|
|
10
|
+
let group = groups.get(groupId);
|
|
11
|
+
if (!group) {
|
|
12
|
+
group = { events: new Map(), expanded: false };
|
|
13
|
+
groups.set(groupId, group);
|
|
14
|
+
}
|
|
15
|
+
group.events.set(event.id, { ...event, text: limitOutputPreview(event.text) });
|
|
16
|
+
if (group.events.size > children)
|
|
17
|
+
group.events.delete(group.events.keys().next().value);
|
|
18
|
+
if (event.error)
|
|
19
|
+
group.expanded = true;
|
|
20
|
+
if (groups.size > capacity)
|
|
21
|
+
groups.delete(groups.keys().next().value);
|
|
22
|
+
},
|
|
23
|
+
toggle(groupId) { const group = groups.get(groupId); if (group)
|
|
24
|
+
group.expanded = !group.expanded; },
|
|
25
|
+
rows(offset, height) {
|
|
26
|
+
const result = [];
|
|
27
|
+
let index = 0;
|
|
28
|
+
for (const [groupId, group] of groups) {
|
|
29
|
+
if (index++ >= offset)
|
|
30
|
+
result.push({ id: groupId, text: groupId, groupId, header: true, expanded: group.expanded });
|
|
31
|
+
if (result.length >= height)
|
|
32
|
+
break;
|
|
33
|
+
if (group.expanded)
|
|
34
|
+
for (const event of group.events.values()) {
|
|
35
|
+
if (index++ >= offset)
|
|
36
|
+
result.push({ ...event, groupId });
|
|
37
|
+
if (result.length >= height)
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
if (result.length >= height)
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
return height > 0 ? result : [];
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function renderEventGroupRows(rows, width) {
|
|
48
|
+
return rows.map(row => fitToWidth(`${row.header ? row.expanded ? "▾" : "▸" : row.error ? " ■" : " │"} ${plainTerminalText(row.text)}`, width));
|
|
49
|
+
}
|
|
@@ -34,7 +34,7 @@ export type { RenderTemplateOptions, TemplateEscape } from "./components/templat
|
|
|
34
34
|
export { openExternal } from "./components/browser.js";
|
|
35
35
|
export * as acp from "./acp/index.js";
|
|
36
36
|
export * as dashboard from "./dashboard/index.js";
|
|
37
|
-
export { createDashboard, shouldUseInteractiveDashboard } from "./dashboard/index.js";
|
|
37
|
+
export { createDashboard, createDashboardLineBuffer, createStreamingDashboardLineBuffer, shouldUseInteractiveDashboard } from "./dashboard/index.js";
|
|
38
38
|
export type { Dashboard, DashboardOptions } from "./dashboard/index.js";
|
|
39
39
|
export * as explorer from "./explorer/index.js";
|
|
40
40
|
export { runExplorer, singleDetail, normalizeExplorerConfig } from "./explorer/index.js";
|
|
@@ -60,3 +60,8 @@ export { Screen } from "./screen/screen.js";
|
|
|
60
60
|
export type { Cell as ScreenCell, ScreenSize, ScreenSurface } from "./screen/screen.js";
|
|
61
61
|
export { packStyle, styleToSgrDelta } from "./screen/style.js";
|
|
62
62
|
export type { PackedStyle } from "./screen/style.js";
|
|
63
|
+
export { createRenderPerformanceMonitor, formatRenderPerformance } from "./render-performance.js";
|
|
64
|
+
export type { RenderPerformanceSnapshot, RenderPercentiles } from "./render-performance.js";
|
|
65
|
+
export { createViewport, selectViewportTail } from "./viewport.js";
|
|
66
|
+
export { createNotices, createCommandRegistry, createOverlayManager, createEventGroups, createTaskTree, renderTaskRows, renderEventGroupRows, renderProgressGroup, createMetric, renderNotice } from "./primitives.js";
|
|
67
|
+
export type { DesignCommand, GroupEvent, EventGroupRow, TaskNode, ProgressItem, InlineNotice } from "./primitives.js";
|
|
@@ -26,7 +26,7 @@ export { openExternal } from "./components/browser.js";
|
|
|
26
26
|
export * as acp from "./acp/index.js";
|
|
27
27
|
// Dashboard
|
|
28
28
|
export * as dashboard from "./dashboard/index.js";
|
|
29
|
-
export { createDashboard, shouldUseInteractiveDashboard } from "./dashboard/index.js";
|
|
29
|
+
export { createDashboard, createDashboardLineBuffer, createStreamingDashboardLineBuffer, shouldUseInteractiveDashboard } from "./dashboard/index.js";
|
|
30
30
|
// Explorer
|
|
31
31
|
export * as explorer from "./explorer/index.js";
|
|
32
32
|
export { runExplorer, singleDetail, normalizeExplorerConfig } from "./explorer/index.js";
|
|
@@ -48,3 +48,7 @@ export { resolveOutputFormat, resetOutputFormatCache, withOutputFormat } from ".
|
|
|
48
48
|
export { createTerminalDriver } from "./terminal/driver.js";
|
|
49
49
|
export { Screen } from "./screen/screen.js";
|
|
50
50
|
export { packStyle, styleToSgrDelta } from "./screen/style.js";
|
|
51
|
+
// Bounded render diagnostics
|
|
52
|
+
export { createRenderPerformanceMonitor, formatRenderPerformance } from "./render-performance.js";
|
|
53
|
+
export { createViewport, selectViewportTail } from "./viewport.js";
|
|
54
|
+
export { createNotices, createCommandRegistry, createOverlayManager, createEventGroups, createTaskTree, renderTaskRows, renderEventGroupRows, renderProgressGroup, createMetric, renderNotice } from "./primitives.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface InlineNotice {
|
|
2
|
+
level: "info" | "success" | "warning" | "error";
|
|
3
|
+
text: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function renderNotice(notice: InlineNotice, width: number): string;
|
|
6
|
+
/** Notices expire during reads, so an idle terminal needs no repaint timer. */
|
|
7
|
+
export declare function createNotices({ capacity, now }: {
|
|
8
|
+
capacity: number;
|
|
9
|
+
now?: () => number;
|
|
10
|
+
}): {
|
|
11
|
+
put(id: string, notice: InlineNotice, durationMs?: number): void;
|
|
12
|
+
dismiss(id: string): void;
|
|
13
|
+
list(): InlineNotice[];
|
|
14
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { plainTerminalText } from "./dashboard/ansi.js";
|
|
2
|
+
import { limitOutputPreview } from "./dashboard/output-preview.js";
|
|
3
|
+
import { fitToWidth } from "./explorer/render/text.js";
|
|
4
|
+
export function renderNotice(notice, width) {
|
|
5
|
+
const marker = { info: "●", success: "✓", warning: "▲", error: "■" }[notice.level];
|
|
6
|
+
return fitToWidth(`${marker} ${plainTerminalText(notice.text)}`, width);
|
|
7
|
+
}
|
|
8
|
+
/** Notices expire during reads, so an idle terminal needs no repaint timer. */
|
|
9
|
+
export function createNotices({ capacity, now = () => performance.now() }) {
|
|
10
|
+
if (!Number.isInteger(capacity) || capacity < 1)
|
|
11
|
+
throw new RangeError("positive capacity required");
|
|
12
|
+
const notices = new Map();
|
|
13
|
+
return {
|
|
14
|
+
put(id, notice, durationMs = Infinity) {
|
|
15
|
+
notices.set(id, { notice: { ...notice, text: limitOutputPreview(notice.text) }, expires: now() + Math.max(0, durationMs) });
|
|
16
|
+
if (notices.size > capacity)
|
|
17
|
+
notices.delete(notices.keys().next().value);
|
|
18
|
+
},
|
|
19
|
+
dismiss(id) { notices.delete(id); },
|
|
20
|
+
list() {
|
|
21
|
+
const time = now();
|
|
22
|
+
for (const [id, entry] of notices)
|
|
23
|
+
if (entry.expires <= time)
|
|
24
|
+
notices.delete(id);
|
|
25
|
+
return Array.from(notices.values(), entry => ({ ...entry.notice }));
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { fitToWidth } from "./explorer/render/text.js";
|
|
2
|
+
export function createMetric({ capacity, unit }) {
|
|
3
|
+
if (!Number.isInteger(capacity) || capacity < 1)
|
|
4
|
+
throw new RangeError("positive capacity required");
|
|
5
|
+
const values = Array(capacity).fill(null);
|
|
6
|
+
let count = 0;
|
|
7
|
+
let cursor = 0;
|
|
8
|
+
return {
|
|
9
|
+
push(value) { values[cursor] = value !== null && Number.isFinite(value) ? value : null; cursor = (cursor + 1) % capacity; count = Math.min(count + 1, capacity); },
|
|
10
|
+
samples() { return Array.from({ length: count }, (_, i) => values[(cursor - count + i + capacity) % capacity]); },
|
|
11
|
+
render(width) {
|
|
12
|
+
const samples = Array.from({ length: Math.min(count, Math.max(0, width)) }, (_, i) => values[(cursor - Math.min(count, Math.max(0, width)) + i + capacity) % capacity]);
|
|
13
|
+
const known = samples.filter((value) => value !== null);
|
|
14
|
+
const min = Math.min(...known);
|
|
15
|
+
const max = Math.max(...known);
|
|
16
|
+
const spark = samples.map(value => value === null ? "·" : "▁▂▃▄▅▆▇█"[max === min ? 3 : Math.round((value - min) / (max - min) * 7)]).join("");
|
|
17
|
+
const latest = count ? values[(cursor - 1 + capacity) % capacity] : null;
|
|
18
|
+
return fitToWidth(`${latest ?? "—"} ${unit} ${spark}`, width);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function createOverlayManager(initialFocus) {
|
|
2
|
+
const stack = [];
|
|
3
|
+
return {
|
|
4
|
+
open(focus) { const controller = new AbortController(); stack.push({ focus, controller }); return controller.signal; },
|
|
5
|
+
close() { const overlay = stack.pop(); overlay?.controller.abort(); return overlay !== undefined; },
|
|
6
|
+
focus() { return stack.at(-1)?.focus ?? initialFocus; },
|
|
7
|
+
dispose() { while (stack.length)
|
|
8
|
+
stack.pop().controller.abort(); }
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { createCommandRegistry } from "./command-registry.js";
|
|
2
|
+
export type { DesignCommand } from "./command-registry.js";
|
|
3
|
+
export { createOverlayManager } from "./overlay-manager.js";
|
|
4
|
+
export { createEventGroups, renderEventGroupRows } from "./event-groups.js";
|
|
5
|
+
export type { GroupEvent, EventGroupRow } from "./event-groups.js";
|
|
6
|
+
export { createTaskTree, renderTaskRows } from "./task-tree.js";
|
|
7
|
+
export type { TaskNode } from "./task-tree.js";
|
|
8
|
+
export { renderProgressGroup } from "./progress-group.js";
|
|
9
|
+
export type { ProgressItem } from "./progress-group.js";
|
|
10
|
+
export { createMetric } from "./metric.js";
|
|
11
|
+
export { renderNotice, createNotices } from "./inline-notice.js";
|
|
12
|
+
export type { InlineNotice } from "./inline-notice.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createCommandRegistry } from "./command-registry.js";
|
|
2
|
+
export { createOverlayManager } from "./overlay-manager.js";
|
|
3
|
+
export { createEventGroups, renderEventGroupRows } from "./event-groups.js";
|
|
4
|
+
export { createTaskTree, renderTaskRows } from "./task-tree.js";
|
|
5
|
+
export { renderProgressGroup } from "./progress-group.js";
|
|
6
|
+
export { createMetric } from "./metric.js";
|
|
7
|
+
export { renderNotice, createNotices } from "./inline-notice.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { plainTerminalText } from "./dashboard/ansi.js";
|
|
2
|
+
import { fitToWidth } from "./explorer/render/text.js";
|
|
3
|
+
export function renderProgressGroup(items, width) {
|
|
4
|
+
return items.map(item => {
|
|
5
|
+
const known = item.total !== undefined && item.total > 0 && Number.isFinite(item.total) && item.completed !== undefined && Number.isFinite(item.completed);
|
|
6
|
+
const progress = known ? `${Math.round(Math.max(0, Math.min(1, item.completed / item.total)) * 100)}%` : "…";
|
|
7
|
+
const marker = item.status === "success" ? "✓" : item.status === "error" ? "■" : "●";
|
|
8
|
+
return fitToWidth(`${marker} ${plainTerminalText(item.label)} ${progress}`, width);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface RenderPercentiles {
|
|
2
|
+
p50: number;
|
|
3
|
+
p95: number;
|
|
4
|
+
max: number;
|
|
5
|
+
}
|
|
6
|
+
export interface RenderPerformanceSnapshot {
|
|
7
|
+
/** Rolling one-second repaint dispatch rate, with 10ms bucket resolution. */
|
|
8
|
+
fps: number;
|
|
9
|
+
frames: number;
|
|
10
|
+
renders: number;
|
|
11
|
+
requests: number;
|
|
12
|
+
coalesced: number;
|
|
13
|
+
changedCells: number;
|
|
14
|
+
sampleCount: number;
|
|
15
|
+
/** Paints exceeding the 60Hz frame budget (16.67ms), cumulative. */
|
|
16
|
+
slowFrames: number;
|
|
17
|
+
longestRenderMs: number;
|
|
18
|
+
renderMs: RenderPercentiles;
|
|
19
|
+
/** Time from oldest pending input to flush enqueue; terminal presentation is not observable. */
|
|
20
|
+
inputMs: RenderPercentiles;
|
|
21
|
+
}
|
|
22
|
+
export declare function createRenderPerformanceMonitor(options?: {
|
|
23
|
+
now?: () => number;
|
|
24
|
+
sampleSize?: number;
|
|
25
|
+
}): {
|
|
26
|
+
request(kind: "input" | "update" | "resize"): void;
|
|
27
|
+
begin(): number;
|
|
28
|
+
end(startedAt: number, frame: {
|
|
29
|
+
changedCells: number;
|
|
30
|
+
}): void;
|
|
31
|
+
snapshot(): RenderPerformanceSnapshot;
|
|
32
|
+
};
|
|
33
|
+
export declare function formatRenderPerformance(stats: RenderPerformanceSnapshot, width: number): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { fitToWidth } from "./explorer/render/text.js";
|
|
2
|
+
export function createRenderPerformanceMonitor(options = {}) {
|
|
3
|
+
const now = options.now ?? (() => performance.now());
|
|
4
|
+
const capacity = options.sampleSize ?? 256;
|
|
5
|
+
if (!Number.isInteger(capacity) || capacity < 1 || capacity > 4096)
|
|
6
|
+
throw new Error("sampleSize must be an integer between 1 and 4096");
|
|
7
|
+
const durations = new Float64Array(capacity);
|
|
8
|
+
const inputs = new Float64Array(capacity);
|
|
9
|
+
const buckets = new Float64Array(101).fill(-Infinity);
|
|
10
|
+
const counts = new Uint32Array(101);
|
|
11
|
+
let renders = 0, frames = 0, requests = 0, pending = 0, coalesced = 0, changedCells = 0;
|
|
12
|
+
let inputCount = 0;
|
|
13
|
+
let slowFrames = 0, longestRenderMs = 0;
|
|
14
|
+
let firstInput;
|
|
15
|
+
return {
|
|
16
|
+
request(kind) {
|
|
17
|
+
requests++;
|
|
18
|
+
pending++;
|
|
19
|
+
if (kind === "input")
|
|
20
|
+
firstInput ??= now();
|
|
21
|
+
},
|
|
22
|
+
begin() { return now(); },
|
|
23
|
+
end(startedAt, frame) {
|
|
24
|
+
const finished = now();
|
|
25
|
+
const duration = Math.max(0, finished - startedAt);
|
|
26
|
+
durations[renders % capacity] = duration;
|
|
27
|
+
longestRenderMs = Math.max(longestRenderMs, duration);
|
|
28
|
+
if (frame.changedCells > 0 && duration > 1000 / 60)
|
|
29
|
+
slowFrames++;
|
|
30
|
+
renders++;
|
|
31
|
+
coalesced += Math.max(0, pending - 1);
|
|
32
|
+
pending = 0;
|
|
33
|
+
if (firstInput !== undefined) {
|
|
34
|
+
inputs[inputCount++ % capacity] = Math.max(0, finished - firstInput);
|
|
35
|
+
firstInput = undefined;
|
|
36
|
+
}
|
|
37
|
+
if (frame.changedCells > 0) {
|
|
38
|
+
frames++;
|
|
39
|
+
changedCells += frame.changedCells;
|
|
40
|
+
const bucket = Math.floor(finished / 10);
|
|
41
|
+
const index = bucket % buckets.length;
|
|
42
|
+
if (buckets[index] !== bucket) {
|
|
43
|
+
buckets[index] = bucket;
|
|
44
|
+
counts[index] = 0;
|
|
45
|
+
}
|
|
46
|
+
counts[index] = counts[index] + 1;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
snapshot() {
|
|
50
|
+
const time = now();
|
|
51
|
+
let fps = 0;
|
|
52
|
+
for (let index = 0; index < buckets.length; index++) {
|
|
53
|
+
if (buckets[index] * 10 >= time - 1000 && buckets[index] * 10 <= time)
|
|
54
|
+
fps += counts[index];
|
|
55
|
+
}
|
|
56
|
+
return { fps, frames, renders, requests, coalesced, changedCells, slowFrames, longestRenderMs,
|
|
57
|
+
sampleCount: Math.min(renders, capacity), renderMs: percentiles(durations, Math.min(renders, capacity)),
|
|
58
|
+
inputMs: percentiles(inputs, Math.min(inputCount, capacity)) };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function percentiles(samples, count) {
|
|
63
|
+
if (count === 0)
|
|
64
|
+
return { p50: 0, p95: 0, max: 0 };
|
|
65
|
+
const sorted = samples.slice(0, count).sort();
|
|
66
|
+
return { p50: sorted[Math.ceil(count * .5) - 1], p95: sorted[Math.ceil(count * .95) - 1], max: sorted[count - 1] };
|
|
67
|
+
}
|
|
68
|
+
export function formatRenderPerformance(stats, width) {
|
|
69
|
+
const value = `FPS ${stats.fps} · paint p95 ${stats.renderMs.p95.toFixed(1)}ms · input p95 ${stats.inputMs.p95.toFixed(1)}ms · slow ${stats.slowFrames} · merged ${stats.coalesced}`;
|
|
70
|
+
return fitToWidth(value, width);
|
|
71
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface TaskNode {
|
|
2
|
+
id: string;
|
|
3
|
+
parentId?: string;
|
|
4
|
+
label: string;
|
|
5
|
+
status: "pending" | "running" | "success" | "error";
|
|
6
|
+
durationMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function createTaskTree({ capacity }?: {
|
|
9
|
+
capacity?: number;
|
|
10
|
+
}): {
|
|
11
|
+
upsert(node: TaskNode): void;
|
|
12
|
+
remove(id: string): void;
|
|
13
|
+
toggle(id: string): void;
|
|
14
|
+
rows(offset: number, height: number): (TaskNode & {
|
|
15
|
+
depth: number;
|
|
16
|
+
collapsed: boolean;
|
|
17
|
+
})[];
|
|
18
|
+
};
|
|
19
|
+
export declare function renderTaskRows(rows: readonly (TaskNode & {
|
|
20
|
+
depth: number;
|
|
21
|
+
collapsed: boolean;
|
|
22
|
+
})[], width: number): string[];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { plainTerminalText } from "./dashboard/ansi.js";
|
|
2
|
+
import { fitToWidth } from "./explorer/render/text.js";
|
|
3
|
+
export function createTaskTree({ capacity = 10000 } = {}) {
|
|
4
|
+
if (!Number.isInteger(capacity) || capacity < 1)
|
|
5
|
+
throw new RangeError("positive capacity required");
|
|
6
|
+
const nodes = new Map();
|
|
7
|
+
const children = new Map();
|
|
8
|
+
const collapsed = new Set();
|
|
9
|
+
return {
|
|
10
|
+
upsert(node) {
|
|
11
|
+
if (!nodes.has(node.id) && nodes.size >= capacity)
|
|
12
|
+
throw new RangeError("Task capacity exceeded");
|
|
13
|
+
let parent = node.parentId;
|
|
14
|
+
const seen = new Set([node.id]);
|
|
15
|
+
while (parent !== undefined) {
|
|
16
|
+
if (seen.has(parent))
|
|
17
|
+
throw new Error("Task cycle");
|
|
18
|
+
seen.add(parent);
|
|
19
|
+
parent = nodes.get(parent)?.parentId;
|
|
20
|
+
}
|
|
21
|
+
const previous = nodes.get(node.id);
|
|
22
|
+
if (previous)
|
|
23
|
+
children.get(previous.parentId)?.delete(node.id);
|
|
24
|
+
nodes.set(node.id, { ...node });
|
|
25
|
+
let siblings = children.get(node.parentId);
|
|
26
|
+
if (!siblings) {
|
|
27
|
+
siblings = new Set();
|
|
28
|
+
children.set(node.parentId, siblings);
|
|
29
|
+
}
|
|
30
|
+
siblings.add(node.id);
|
|
31
|
+
},
|
|
32
|
+
remove(id) {
|
|
33
|
+
const node = nodes.get(id);
|
|
34
|
+
if (!node)
|
|
35
|
+
return;
|
|
36
|
+
children.get(node.parentId)?.delete(id);
|
|
37
|
+
const pending = [id];
|
|
38
|
+
while (pending.length) {
|
|
39
|
+
const current = pending.pop();
|
|
40
|
+
for (const child of children.get(current) ?? [])
|
|
41
|
+
pending.push(child);
|
|
42
|
+
children.delete(current);
|
|
43
|
+
nodes.delete(current);
|
|
44
|
+
collapsed.delete(current);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
toggle(id) { if (collapsed.has(id))
|
|
48
|
+
collapsed.delete(id);
|
|
49
|
+
else
|
|
50
|
+
collapsed.add(id); },
|
|
51
|
+
rows(offset, height) {
|
|
52
|
+
const result = [];
|
|
53
|
+
const stack = [{ iterator: (children.get(undefined) ?? new Set()).values(), depth: 0 }];
|
|
54
|
+
let index = 0;
|
|
55
|
+
while (stack.length && result.length < height) {
|
|
56
|
+
const frame = stack[stack.length - 1];
|
|
57
|
+
const next = frame.iterator.next();
|
|
58
|
+
if (next.done) {
|
|
59
|
+
stack.pop();
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const node = nodes.get(next.value);
|
|
63
|
+
if (index++ >= offset)
|
|
64
|
+
result.push({ ...node, depth: frame.depth, collapsed: collapsed.has(node.id) });
|
|
65
|
+
if (!collapsed.has(node.id) && children.has(node.id))
|
|
66
|
+
stack.push({ iterator: children.get(node.id).values(), depth: frame.depth + 1 });
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function renderTaskRows(rows, width) {
|
|
73
|
+
const markers = { pending: "○", running: "●", success: "✓", error: "■" };
|
|
74
|
+
return rows.map(row => {
|
|
75
|
+
const indent = " ".repeat(Math.min(Math.max(0, width), Math.max(0, row.depth) * 2));
|
|
76
|
+
const duration = row.durationMs !== undefined && Number.isFinite(row.durationMs) ? ` · ${Math.max(0, Math.round(row.durationMs))}ms` : "";
|
|
77
|
+
return fitToWidth(`${indent}${row.collapsed ? "▸" : "▾"} ${markers[row.status]} ${plainTerminalText(row.label)}${duration}`, width);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Select wrapped rows from the tail, retaining only one viewport of rows. */
|
|
2
|
+
export declare function selectViewportTail<T, R>(items: readonly T[], height: number, offset: number, renderRows: (item: T) => readonly R[]): {
|
|
3
|
+
rows: R[];
|
|
4
|
+
offset: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function createViewport<T extends {
|
|
7
|
+
id: string;
|
|
8
|
+
}>({ capacity }: {
|
|
9
|
+
capacity: number;
|
|
10
|
+
}): {
|
|
11
|
+
append(item: T): void;
|
|
12
|
+
scroll(delta: number): void;
|
|
13
|
+
items(): readonly T[];
|
|
14
|
+
offset(): number;
|
|
15
|
+
unseen(): number;
|
|
16
|
+
find(predicate: (item: T) => boolean): number;
|
|
17
|
+
follow(): void;
|
|
18
|
+
};
|