@linxiraos/pi-utils 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -6
- package/package.json +56 -57
- package/src/dirs.ts +99 -91
- package/src/fetch-retry.ts +11 -2
- package/src/json-parse.ts +18 -7
- package/src/logger.ts +55 -20
- package/src/ptree.ts +2 -2
- package/src/stderr-guard.ts +1 -1
- package/src/stream.ts +27 -3
- package/src/vendor/mermaid-ascii/ascii/canvas.ts +8 -6
- package/src/vendor/mermaid-ascii/ascii/draw.ts +21 -14
- package/src/vendor/mermaid-ascii/text-metrics.ts +3 -0
- package/dist/types/abortable.d.ts +0 -32
- package/dist/types/acp/connection.d.ts +0 -118
- package/dist/types/acp/protocol.d.ts +0 -526
- package/dist/types/acp/schema.d.ts +0 -41
- package/dist/types/acp/stream.d.ts +0 -8
- package/dist/types/acp/transport.d.ts +0 -81
- package/dist/types/acp.d.ts +0 -6
- package/dist/types/async.d.ts +0 -20
- package/dist/types/binary.d.ts +0 -17
- package/dist/types/browsers.d.ts +0 -68
- package/dist/types/chalk.d.ts +0 -125
- package/dist/types/cli.d.ts +0 -134
- package/dist/types/color.d.ts +0 -102
- package/dist/types/dates.d.ts +0 -7
- package/dist/types/dirs.d.ts +0 -282
- package/dist/types/docx/converter.d.ts +0 -46
- package/dist/types/docx/xml.d.ts +0 -26
- package/dist/types/docx/zip.d.ts +0 -6
- package/dist/types/docx.d.ts +0 -11
- package/dist/types/dom/core.d.ts +0 -431
- package/dist/types/dom/parser.d.ts +0 -7
- package/dist/types/dom/selector.d.ts +0 -5
- package/dist/types/dom.d.ts +0 -5
- package/dist/types/env.d.ts +0 -124
- package/dist/types/fetch-retry.d.ts +0 -96
- package/dist/types/file-lock.d.ts +0 -23
- package/dist/types/format.d.ts +0 -37
- package/dist/types/frontmatter.d.ts +0 -46
- package/dist/types/fs-error.d.ts +0 -31
- package/dist/types/glob.d.ts +0 -28
- package/dist/types/headers.d.ts +0 -34
- package/dist/types/index.d.ts +0 -39
- package/dist/types/json-parse.d.ts +0 -74
- package/dist/types/json.d.ts +0 -14
- package/dist/types/logger/rotating-file.d.ts +0 -18
- package/dist/types/logger.d.ts +0 -96
- package/dist/types/loop-phase.d.ts +0 -10
- package/dist/types/lru.d.ts +0 -46
- package/dist/types/marked/core.d.ts +0 -445
- package/dist/types/marked.d.ts +0 -2
- package/dist/types/mermaid-ascii.d.ts +0 -11
- package/dist/types/mime.d.ts +0 -29
- package/dist/types/module-timer.d.ts +0 -1
- package/dist/types/path-tree.d.ts +0 -76
- package/dist/types/path.d.ts +0 -2
- package/dist/types/peek-file.d.ts +0 -29
- package/dist/types/postmortem.d.ts +0 -104
- package/dist/types/process-name.d.ts +0 -7
- package/dist/types/procmgr.d.ts +0 -74
- package/dist/types/prompt.d.ts +0 -18
- package/dist/types/ptree.d.ts +0 -110
- package/dist/types/readability/readability.d.ts +0 -9
- package/dist/types/readability/readerable.d.ts +0 -10
- package/dist/types/readability/types.d.ts +0 -70
- package/dist/types/readability.d.ts +0 -4
- package/dist/types/ring.d.ts +0 -93
- package/dist/types/runtime-install.d.ts +0 -78
- package/dist/types/sanitize-text.d.ts +0 -29
- package/dist/types/snowflake.d.ts +0 -25
- package/dist/types/stderr-guard.d.ts +0 -22
- package/dist/types/stream.d.ts +0 -67
- package/dist/types/tab-spacing.d.ts +0 -24
- package/dist/types/temp.d.ts +0 -17
- package/dist/types/template.d.ts +0 -62
- package/dist/types/timing-buffer.d.ts +0 -22
- package/dist/types/tls-fetch.d.ts +0 -37
- package/dist/types/turndown/gfm.d.ts +0 -11
- package/dist/types/turndown/html.d.ts +0 -5
- package/dist/types/turndown/service.d.ts +0 -21
- package/dist/types/turndown/types.d.ts +0 -70
- package/dist/types/turndown.d.ts +0 -4
- package/dist/types/type-guards.d.ts +0 -5
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +0 -41
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +0 -89
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +0 -12
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +0 -66
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +0 -48
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +0 -43
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +0 -56
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +0 -65
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +0 -27
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +0 -34
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +0 -26
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +0 -31
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +0 -59
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +0 -30
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +0 -55
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +0 -206
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +0 -51
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +0 -2
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +0 -102
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +0 -76
- package/dist/types/vendor/mermaid-ascii/index.d.ts +0 -1
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +0 -9
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +0 -130
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +0 -21
- package/dist/types/vendor/mermaid-ascii/types.d.ts +0 -114
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +0 -25
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +0 -145
- package/dist/types/version.d.ts +0 -18
- package/dist/types/vterm/buffer.d.ts +0 -99
- package/dist/types/vterm/terminal.d.ts +0 -44
- package/dist/types/vterm.d.ts +0 -8
- package/dist/types/which.d.ts +0 -37
- package/dist/types/worker-host.d.ts +0 -51
- package/dist/types/xml.d.ts +0 -31
package/src/logger.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Centralized logger for
|
|
2
|
+
* Centralized logger for zeta.
|
|
3
3
|
*
|
|
4
|
-
* Default: rotating `~/.
|
|
4
|
+
* Default: rotating `~/.zeta/logs/zeta.<DATE>.<PID>.log`, no console output (writing
|
|
5
5
|
* to stdout/stderr would corrupt the TUI). Long-running headless services
|
|
6
6
|
* (the auth broker, etc.) call {@link setTransports} to swap in a console
|
|
7
7
|
* transport so a process supervisor (pm2, journald, k8s) captures the logs.
|
|
@@ -53,9 +53,11 @@ function emitToSinks(level: LogLevel, message: string, context: Record<string, u
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
const PROCESS_LOG_PATTERN = /^
|
|
57
|
-
const PROCESS_AUDIT_PATTERN = /^\.
|
|
58
|
-
const
|
|
56
|
+
const PROCESS_LOG_PATTERN = /^zeta\.(\d{4}-\d{2}-\d{2})\.(\d+)\.log(?:\.(\d+))?$/;
|
|
57
|
+
const PROCESS_AUDIT_PATTERN = /^\.zeta\.(\d+)-audit\.json$/;
|
|
58
|
+
const RETAINED_STALE_LOGS_PER_PROCESS_DAY = 1;
|
|
59
|
+
const RETAINED_STALE_AUDIT_FILES = 0;
|
|
60
|
+
const RETAINED_STALE_LOG_DAYS = 5;
|
|
59
61
|
|
|
60
62
|
function processIsRunning(pid: number): boolean {
|
|
61
63
|
try {
|
|
@@ -67,8 +69,10 @@ function processIsRunning(pid: number): boolean {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
|
-
* Retain
|
|
71
|
-
*
|
|
72
|
+
* Retain one newest completed-process log per process/day within the current
|
|
73
|
+
* and previous four local calendar days, and remove one-use audit files. Live
|
|
74
|
+
* PID namespaces are never touched. The calendar-day boundary preserves daily
|
|
75
|
+
* diagnostic coverage while bounding completed-process storage and scans.
|
|
72
76
|
*/
|
|
73
77
|
function pruneStaleProcessLogs(dir: string): void {
|
|
74
78
|
let entries: fs.Dirent[];
|
|
@@ -77,38 +81,69 @@ function pruneStaleProcessLogs(dir: string): void {
|
|
|
77
81
|
} catch {
|
|
78
82
|
return;
|
|
79
83
|
}
|
|
80
|
-
|
|
81
|
-
const
|
|
84
|
+
const current = new Date();
|
|
85
|
+
const currentDate =
|
|
86
|
+
`${current.getFullYear()}-${String(current.getMonth() + 1).padStart(2, "0")}-` +
|
|
87
|
+
String(current.getDate()).padStart(2, "0");
|
|
88
|
+
const cutoff = new Date(current);
|
|
89
|
+
cutoff.setDate(cutoff.getDate() - (RETAINED_STALE_LOG_DAYS - 1));
|
|
90
|
+
const cutoffDate =
|
|
91
|
+
`${cutoff.getFullYear()}-${String(cutoff.getMonth() + 1).padStart(2, "0")}-` +
|
|
92
|
+
String(cutoff.getDate()).padStart(2, "0");
|
|
93
|
+
|
|
94
|
+
const staleLogsByProcessDay = new Map<string, Array<{ path: string; mtimeMs: number; rollover: number }>>();
|
|
82
95
|
for (const entry of entries) {
|
|
83
96
|
if (!entry.isFile()) continue;
|
|
84
97
|
const logMatch = PROCESS_LOG_PATTERN.exec(entry.name);
|
|
85
98
|
const auditMatch = PROCESS_AUDIT_PATTERN.exec(entry.name);
|
|
86
|
-
const pidText = logMatch?.[
|
|
99
|
+
const pidText = logMatch?.[2] ?? auditMatch?.[1];
|
|
87
100
|
if (!pidText || processIsRunning(Number(pidText))) continue;
|
|
88
101
|
const entryPath = path.join(dir, entry.name);
|
|
89
102
|
|
|
90
103
|
if (auditMatch) {
|
|
104
|
+
if (RETAINED_STALE_AUDIT_FILES === 0) {
|
|
105
|
+
try {
|
|
106
|
+
fs.rmSync(entryPath, { force: true });
|
|
107
|
+
} catch {
|
|
108
|
+
// Retention is best-effort; logging must still initialize.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (!logMatch?.[1]) continue;
|
|
114
|
+
if (logMatch[1] < cutoffDate || logMatch[1] > currentDate) {
|
|
91
115
|
try {
|
|
92
116
|
fs.rmSync(entryPath, { force: true });
|
|
93
117
|
} catch {
|
|
94
|
-
//
|
|
118
|
+
// Another process may have pruned the same stale namespace.
|
|
95
119
|
}
|
|
96
120
|
continue;
|
|
97
121
|
}
|
|
98
122
|
|
|
99
123
|
try {
|
|
100
|
-
|
|
124
|
+
const key = `${pidText}:${logMatch[1]}`;
|
|
125
|
+
const staleLogs = staleLogsByProcessDay.get(key) ?? [];
|
|
126
|
+
staleLogs.push({
|
|
127
|
+
path: entryPath,
|
|
128
|
+
mtimeMs: fs.statSync(entryPath).mtimeMs,
|
|
129
|
+
rollover: Number(logMatch[3] ?? 0),
|
|
130
|
+
});
|
|
131
|
+
staleLogsByProcessDay.set(key, staleLogs);
|
|
101
132
|
} catch {
|
|
102
133
|
// Another process may have pruned the same stale namespace.
|
|
103
134
|
}
|
|
104
135
|
}
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
for (const staleLogs of staleLogsByProcessDay.values()) {
|
|
138
|
+
staleLogs.sort(
|
|
139
|
+
(a, b) => b.mtimeMs - a.mtimeMs || b.rollover - a.rollover || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0),
|
|
140
|
+
);
|
|
141
|
+
for (const stale of staleLogs.slice(RETAINED_STALE_LOGS_PER_PROCESS_DAY)) {
|
|
142
|
+
try {
|
|
143
|
+
fs.rmSync(stale.path, { force: true });
|
|
144
|
+
} catch {
|
|
145
|
+
// Another process may have pruned the same stale namespace.
|
|
146
|
+
}
|
|
112
147
|
}
|
|
113
148
|
}
|
|
114
149
|
}
|
|
@@ -201,11 +236,11 @@ function makeFileTransport(dir?: string): RotatingFileSink {
|
|
|
201
236
|
pruneStaleProcessLogs(logsDir);
|
|
202
237
|
return new RotatingFileSink({
|
|
203
238
|
directory: logsDir,
|
|
204
|
-
filenamePrefix: "
|
|
239
|
+
filenamePrefix: "zeta",
|
|
205
240
|
filenameSuffix: String(process.pid),
|
|
206
241
|
maxBytes: 10 * 1024 * 1024,
|
|
207
242
|
maxFiles: 5,
|
|
208
|
-
auditFile: path.join(logsDir, `.
|
|
243
|
+
auditFile: path.join(logsDir, `.zeta.${process.pid}-audit.json`),
|
|
209
244
|
});
|
|
210
245
|
}
|
|
211
246
|
|
package/src/ptree.ts
CHANGED
|
@@ -217,11 +217,11 @@ export class ChildProcess<In extends InMask = InMask> {
|
|
|
217
217
|
return this;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
kill(reason?: Exception) {
|
|
220
|
+
kill(reason?: Exception, gracefulMs?: number) {
|
|
221
221
|
if (reason && !this.#exitReasonPending) this.#exitReasonPending = reason;
|
|
222
222
|
if (!this.proc.killed)
|
|
223
223
|
void Process.fromPid(this.proc.pid)
|
|
224
|
-
?.terminate()
|
|
224
|
+
?.terminate(gracefulMs === undefined ? undefined : { gracefulMs })
|
|
225
225
|
?.catch(e => void e);
|
|
226
226
|
}
|
|
227
227
|
|
package/src/stderr-guard.ts
CHANGED
|
@@ -104,7 +104,7 @@ export function suppressTerminalStderr(options?: SuppressTerminalStderrOptions):
|
|
|
104
104
|
try {
|
|
105
105
|
const redirectPath = options?.redirectPath ?? getLogPath();
|
|
106
106
|
// getLogsDir() only computes the path; the logger creates it lazily, so
|
|
107
|
-
// on a fresh profile ~/.
|
|
107
|
+
// on a fresh profile ~/.zeta/logs may not exist yet. Create it here so
|
|
108
108
|
// diagnostics land in the log instead of falling through to /dev/null.
|
|
109
109
|
fs.mkdirSync(path.dirname(redirectPath), { recursive: true });
|
|
110
110
|
redirectFd = fs.openSync(redirectPath, "a");
|
package/src/stream.ts
CHANGED
|
@@ -281,11 +281,16 @@ export async function* readSseJson<T>(
|
|
|
281
281
|
* - `raw` is the list of decoded non-empty lines that made up the event,
|
|
282
282
|
* preserved for diagnostic context (error reporting, debugging). The
|
|
283
283
|
* dispatching blank line is not included.
|
|
284
|
+
* - `id` and `retry` are present only when the event carried valid fields with
|
|
285
|
+
* those names. Control-only events are yielded so reconnecting transports can
|
|
286
|
+
* retain the cursor and server-requested retry interval.
|
|
284
287
|
*/
|
|
285
288
|
export interface ServerSentEvent {
|
|
286
289
|
event: string | null;
|
|
287
290
|
data: string;
|
|
288
291
|
raw: string[];
|
|
292
|
+
id?: string;
|
|
293
|
+
retry?: number;
|
|
289
294
|
}
|
|
290
295
|
|
|
291
296
|
interface SseEventState {
|
|
@@ -296,6 +301,8 @@ interface SseEventState {
|
|
|
296
301
|
// seen yet" (distinct from a `data:` field with an empty value).
|
|
297
302
|
data: string | null;
|
|
298
303
|
raw: string[];
|
|
304
|
+
id?: string;
|
|
305
|
+
retry?: number;
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
// Complete lines are decoded in one batch per source chunk. Each batch ends on
|
|
@@ -303,7 +310,7 @@ interface SseEventState {
|
|
|
303
310
|
const SSE_DECODER = new TextDecoder("utf-8");
|
|
304
311
|
|
|
305
312
|
function flushSseEvent(state: SseEventState): ServerSentEvent | null {
|
|
306
|
-
if (state.event === null && state.data === null) {
|
|
313
|
+
if (state.event === null && state.data === null && state.id === undefined && state.retry === undefined) {
|
|
307
314
|
state.raw = [];
|
|
308
315
|
return null;
|
|
309
316
|
}
|
|
@@ -312,9 +319,13 @@ function flushSseEvent(state: SseEventState): ServerSentEvent | null {
|
|
|
312
319
|
data: state.data ?? "",
|
|
313
320
|
raw: state.raw,
|
|
314
321
|
};
|
|
322
|
+
if (state.id !== undefined) event.id = state.id;
|
|
323
|
+
if (state.retry !== undefined) event.retry = state.retry;
|
|
315
324
|
state.event = null;
|
|
316
325
|
state.data = null;
|
|
317
326
|
state.raw = [];
|
|
327
|
+
state.id = undefined;
|
|
328
|
+
state.retry = undefined;
|
|
318
329
|
return event;
|
|
319
330
|
}
|
|
320
331
|
|
|
@@ -348,9 +359,22 @@ function pushSseLine(line: string, state: SseEventState): ServerSentEvent | null
|
|
|
348
359
|
state.data += "\n";
|
|
349
360
|
state.data += value;
|
|
350
361
|
}
|
|
362
|
+
} else if (fieldName === "id") {
|
|
363
|
+
if (!value.includes("\0")) state.id = value;
|
|
364
|
+
} else if (fieldName === "retry" && value.length > 0) {
|
|
365
|
+
let valid = true;
|
|
366
|
+
for (let index = 0; index < value.length; index++) {
|
|
367
|
+
const code = value.charCodeAt(index);
|
|
368
|
+
if (code < 0x30 || code > 0x39) {
|
|
369
|
+
valid = false;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (valid) {
|
|
374
|
+
const retry = Number(value);
|
|
375
|
+
if (Number.isSafeInteger(retry)) state.retry = retry;
|
|
376
|
+
}
|
|
351
377
|
}
|
|
352
|
-
// `id` and `retry` are intentionally ignored — the providers we consume
|
|
353
|
-
// don't use them, and the underlying transport handles reconnects itself.
|
|
354
378
|
return null;
|
|
355
379
|
}
|
|
356
380
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { Canvas, DrawingCoord, RoleCanvas, CharRole, AsciiTheme, ColorMode } from './types'
|
|
10
10
|
import { colorizeLine, DEFAULT_ASCII_THEME } from './ansi'
|
|
11
|
-
import { displayWidth, toCells, WIDE_PAD } from '../text-metrics'
|
|
11
|
+
import { displayWidth, LABEL_SPACE, toCells, WIDE_PAD } from '../text-metrics'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Create a blank canvas filled with spaces.
|
|
@@ -189,7 +189,7 @@ export function isJunctionChar(c: string): boolean {
|
|
|
189
189
|
* letter/digit test misses.
|
|
190
190
|
*/
|
|
191
191
|
function isLabelChar(c: string): boolean {
|
|
192
|
-
return c === WIDE_PAD || displayWidth(c) === 2 || /[\p{L}\p{N}]/u.test(c)
|
|
192
|
+
return c === LABEL_SPACE || c === WIDE_PAD || displayWidth(c) === 2 || /[\p{L}\p{N}]/u.test(c)
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
/**
|
|
@@ -268,7 +268,7 @@ export function mergeCanvases(
|
|
|
268
268
|
for (let x = 0; x < overlay.length; x++) {
|
|
269
269
|
for (let y = 0; y < overlay[0]!.length; y++) {
|
|
270
270
|
const c = overlay[x]![y]!
|
|
271
|
-
// WIDE_PAD cells are written atomically with their lead below
|
|
271
|
+
// Spaces are transparent; WIDE_PAD cells are written atomically with their lead below
|
|
272
272
|
if (c === ' ' || c === WIDE_PAD) continue
|
|
273
273
|
const mx = x + offset.x
|
|
274
274
|
const my = y + offset.y
|
|
@@ -327,8 +327,8 @@ export function canvasToString(canvas: Canvas, options?: CanvasToStringOptions):
|
|
|
327
327
|
let line = ''
|
|
328
328
|
for (let x = 0; x <= maxX; x++) {
|
|
329
329
|
const c = canvas[x]![y]!
|
|
330
|
-
// Skip wide-glyph continuation cells
|
|
331
|
-
if (c !== WIDE_PAD) line += c
|
|
330
|
+
// Skip wide-glyph continuation cells and restore opaque label spaces.
|
|
331
|
+
if (c !== WIDE_PAD) line += c === LABEL_SPACE ? ' ' : c
|
|
332
332
|
}
|
|
333
333
|
lines.push(line)
|
|
334
334
|
} else {
|
|
@@ -338,7 +338,7 @@ export function canvasToString(canvas: Canvas, options?: CanvasToStringOptions):
|
|
|
338
338
|
for (let x = 0; x <= maxX; x++) {
|
|
339
339
|
const c = canvas[x]![y]!
|
|
340
340
|
if (c === WIDE_PAD) continue
|
|
341
|
-
chars.push(c)
|
|
341
|
+
chars.push(c === LABEL_SPACE ? ' ' : c)
|
|
342
342
|
roles.push(roleCanvas[x]?.[y] ?? null)
|
|
343
343
|
}
|
|
344
344
|
lines.push(colorizeLine(chars, roles, theme, colorMode))
|
|
@@ -370,6 +370,8 @@ const VERTICAL_FLIP_MAP: Record<string, string> = {
|
|
|
370
370
|
// Unicode corners
|
|
371
371
|
'┌': '└', '└': '┌',
|
|
372
372
|
'┐': '┘', '┘': '┐',
|
|
373
|
+
'╭': '╰', '╰': '╭',
|
|
374
|
+
'╮': '╯', '╯': '╮',
|
|
373
375
|
// Unicode junctions (T-pieces flip vertically)
|
|
374
376
|
'┬': '┴', '┴': '┬',
|
|
375
377
|
// Box-start junctions (exit points from node boxes)
|
|
@@ -21,7 +21,7 @@ import { gridToDrawingCoord, lineToDrawing } from './grid'
|
|
|
21
21
|
import { splitLines } from './multiline-utils'
|
|
22
22
|
import { getCorners } from './shapes/corners'
|
|
23
23
|
import { getShapeAttachmentPoint } from './shapes/index'
|
|
24
|
-
import { displayWidth, toCells, WIDE_PAD } from '../text-metrics'
|
|
24
|
+
import { displayWidth, LABEL_SPACE, toCells, WIDE_PAD } from '../text-metrics'
|
|
25
25
|
|
|
26
26
|
// ============================================================================
|
|
27
27
|
// Node drawing — renders a node using shape-aware rendering
|
|
@@ -76,16 +76,18 @@ function drawBoxWithGridDimensions(node: AsciiNode, graph: AsciiGraph): Canvas {
|
|
|
76
76
|
// Get corner characters for this shape type
|
|
77
77
|
const corners = getCorners(node.shape, useAscii)
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
const hChar = useAscii ? (
|
|
82
|
-
const vChar = useAscii ? (
|
|
79
|
+
const isStateStart = node.shape === 'state-start'
|
|
80
|
+
const isStateEnd = node.shape === 'state-end'
|
|
81
|
+
const hChar = useAscii ? (isStateEnd ? '=' : '-') : (isStateEnd ? '═' : '─')
|
|
82
|
+
const vChar = useAscii ? (isStateEnd ? '‖' : '|') : (isStateEnd ? '║' : '│')
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
const stateStartCorners = useAscii
|
|
85
|
+
? { tl: '+', tr: '+', bl: '+', br: '+' }
|
|
86
|
+
: { tl: '╭', tr: '╮', bl: '╰', br: '╯' }
|
|
87
|
+
const stateEndCorners = useAscii
|
|
86
88
|
? { tl: '#', tr: '#', bl: '#', br: '#' }
|
|
87
89
|
: { tl: '╔', tr: '╗', bl: '╚', br: '╝' }
|
|
88
|
-
const effectiveCorners =
|
|
90
|
+
const effectiveCorners = isStateEnd ? stateEndCorners : isStateStart ? stateStartCorners : corners
|
|
89
91
|
|
|
90
92
|
// Draw box border with shape-specific corners
|
|
91
93
|
for (let x = from.x + 1; x < to.x; x++) box[x]![from.y] = hChar
|
|
@@ -97,8 +99,8 @@ function drawBoxWithGridDimensions(node: AsciiNode, graph: AsciiGraph): Canvas {
|
|
|
97
99
|
box[from.x]![to.y] = effectiveCorners.bl
|
|
98
100
|
box[to.x]![to.y] = effectiveCorners.br
|
|
99
101
|
|
|
100
|
-
//
|
|
101
|
-
const label = node.displayLabel
|
|
102
|
+
// Pseudostates have no source label; restore their UML marker explicitly.
|
|
103
|
+
const label = node.displayLabel || (isStateStart ? (useAscii ? '*' : '●') : isStateEnd ? (useAscii ? '*' : '◎') : '')
|
|
102
104
|
const lines = splitLines(label)
|
|
103
105
|
const textCenterY = from.y + Math.floor(h / 2)
|
|
104
106
|
const startY = textCenterY - Math.floor((lines.length - 1) / 2)
|
|
@@ -677,7 +679,7 @@ function drawTextOnLine(canvas: Canvas, line: DrawingCoord[], label: string, isU
|
|
|
677
679
|
for (let i = 0; i < lines.length; i++) {
|
|
678
680
|
const lineText = lines[i]!
|
|
679
681
|
const startX = middleX - Math.floor(displayWidth(lineText) / 2)
|
|
680
|
-
drawText(canvas, { x: startX, y: startY + i }, lineText)
|
|
682
|
+
drawText(canvas, { x: startX, y: startY + i }, lineText.replaceAll(' ', LABEL_SPACE))
|
|
681
683
|
}
|
|
682
684
|
}
|
|
683
685
|
|
|
@@ -1223,7 +1225,8 @@ function fillRolesFromCanvases(
|
|
|
1223
1225
|
|
|
1224
1226
|
/**
|
|
1225
1227
|
* Special handling for node boxes: border chars get 'border' role, text gets 'text' role.
|
|
1226
|
-
*
|
|
1228
|
+
* Common final-state border characters (`#` and `=`) count only on the outer edge,
|
|
1229
|
+
* so identical characters inside ordinary node labels retain the text role.
|
|
1227
1230
|
*/
|
|
1228
1231
|
function fillRolesForNodeBox(
|
|
1229
1232
|
roleCanvas: RoleCanvas,
|
|
@@ -1231,7 +1234,9 @@ function fillRolesForNodeBox(
|
|
|
1231
1234
|
offset: DrawingCoord,
|
|
1232
1235
|
): void {
|
|
1233
1236
|
const isBorderChar = (c: string) => /^[┌┐└┘├┤┬┴┼│─╭╮╰╯+\-|.':]$/.test(c)
|
|
1234
|
-
|
|
1237
|
+
const isStateEndBorderChar = (c: string) => /^[╔╗╚╝═║#=‖]$/.test(c)
|
|
1238
|
+
const maxX = canvas.length - 1
|
|
1239
|
+
const maxY = (canvas[0]?.length ?? 1) - 1
|
|
1235
1240
|
for (let x = 0; x < canvas.length; x++) {
|
|
1236
1241
|
for (let y = 0; y < (canvas[0]?.length ?? 0); y++) {
|
|
1237
1242
|
const char = canvas[x]?.[y]
|
|
@@ -1240,7 +1245,9 @@ function fillRolesForNodeBox(
|
|
|
1240
1245
|
const ry = y + offset.y
|
|
1241
1246
|
// Use setRole which auto-expands the role canvas if needed
|
|
1242
1247
|
if (rx >= 0 && ry >= 0) {
|
|
1243
|
-
|
|
1248
|
+
const isOuterEdge = x === 0 || x === maxX || y === 0 || y === maxY
|
|
1249
|
+
const role = isBorderChar(char) || (isOuterEdge && isStateEndBorderChar(char)) ? 'border' : 'text'
|
|
1250
|
+
setRole(roleCanvas, rx, ry, role)
|
|
1244
1251
|
}
|
|
1245
1252
|
}
|
|
1246
1253
|
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export declare class AbortError extends Error {
|
|
2
|
-
constructor(signal: AbortSignal);
|
|
3
|
-
}
|
|
4
|
-
/**
|
|
5
|
-
* Abortable async iteration over a {@link ReadableStream}. Reads the source
|
|
6
|
-
* reader directly and yields each chunk, so the consumer's `for await` drives a
|
|
7
|
-
* single read loop with no intermediate stream or per-chunk enqueue.
|
|
8
|
-
*
|
|
9
|
-
* Unlike `stream.pipeThrough(..., { signal })`, this explicitly cancels the
|
|
10
|
-
* source reader on abort or early `break`, propagating HTTP-client disconnects
|
|
11
|
-
* and watchdog timeouts to the backend request instead of only stopping the
|
|
12
|
-
* local consumer. On abort it throws {@link AbortError}; the lock is released
|
|
13
|
-
* on completion, abort, throw, or early exit. The source is cancelled only on
|
|
14
|
-
* abort or early exit — never on natural EOF.
|
|
15
|
-
*/
|
|
16
|
-
export declare function abortableSource<T>(stream: ReadableStream<T>, signal?: AbortSignal): AsyncGenerator<T>;
|
|
17
|
-
/**
|
|
18
|
-
* Runs a promise-returning function (`pr`). If the given AbortSignal is aborted before or during
|
|
19
|
-
* execution, the promise is rejected with a standard error.
|
|
20
|
-
*
|
|
21
|
-
* @param signal - Optional AbortSignal to cancel the operation
|
|
22
|
-
* @param pr - Function returning a promise to run
|
|
23
|
-
* @returns Promise resolving as `pr` would, or rejecting on abort
|
|
24
|
-
*/
|
|
25
|
-
export declare function untilAborted<T>(signal: AbortSignal | undefined | null, pr: Promise<T> | (() => Promise<T>)): Promise<T>;
|
|
26
|
-
/**
|
|
27
|
-
* Memoizes a function with no arguments, calling it once and caching the result.
|
|
28
|
-
*
|
|
29
|
-
* @param fn - Function to be called once
|
|
30
|
-
* @returns A function that returns the cached result of `fn`
|
|
31
|
-
*/
|
|
32
|
-
export declare function once<T>(fn: () => T): () => T;
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import type { Agent, AuthenticateRequest, AuthenticateResponse, CloseSessionRequest, CloseSessionResponse, CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse, CreateTerminalRequest, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, MaybePromise, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, RequestPermissionRequest, RequestPermissionResponse, ResumeSessionRequest, ResumeSessionResponse, SessionNotification, SetSessionConfigOptionRequest, SetSessionConfigOptionResponse, SetSessionModeRequest, SetSessionModeResponse, TerminalActionResponse, TerminalOutputResponse, WaitForTerminalExitResponse, WriteTextFileRequest, WriteTextFileResponse } from "./protocol.js";
|
|
2
|
-
import type { Stream } from "./stream.js";
|
|
3
|
-
import { RpcConnection } from "./transport.js";
|
|
4
|
-
/** Terminal creation response returned by a client. */
|
|
5
|
-
export interface CreateTerminalResponse {
|
|
6
|
-
terminalId: string;
|
|
7
|
-
}
|
|
8
|
-
/** Client callbacks handled by a client-side connection. */
|
|
9
|
-
export interface Client {
|
|
10
|
-
requestPermission(params: RequestPermissionRequest): MaybePromise<RequestPermissionResponse>;
|
|
11
|
-
sessionUpdate(params: SessionNotification): MaybePromise<void>;
|
|
12
|
-
readTextFile?(params: ReadTextFileRequest): MaybePromise<ReadTextFileResponse>;
|
|
13
|
-
writeTextFile?(params: WriteTextFileRequest): MaybePromise<WriteTextFileResponse | void>;
|
|
14
|
-
createTerminal?(params: CreateTerminalRequest): MaybePromise<CreateTerminalResponse>;
|
|
15
|
-
terminalOutput?(params: {
|
|
16
|
-
sessionId: string;
|
|
17
|
-
terminalId: string;
|
|
18
|
-
}): MaybePromise<TerminalOutputResponse>;
|
|
19
|
-
waitForTerminalExit?(params: {
|
|
20
|
-
sessionId: string;
|
|
21
|
-
terminalId: string;
|
|
22
|
-
}): MaybePromise<WaitForTerminalExitResponse>;
|
|
23
|
-
killTerminal?(params: {
|
|
24
|
-
sessionId: string;
|
|
25
|
-
terminalId: string;
|
|
26
|
-
}): MaybePromise<TerminalActionResponse | void>;
|
|
27
|
-
releaseTerminal?(params: {
|
|
28
|
-
sessionId: string;
|
|
29
|
-
terminalId: string;
|
|
30
|
-
}): MaybePromise<TerminalActionResponse | void>;
|
|
31
|
-
unstable_createElicitation?(params: CreateElicitationRequest): MaybePromise<CreateElicitationResponse>;
|
|
32
|
-
unstable_completeElicitation?(params: CompleteElicitationNotification): MaybePromise<void>;
|
|
33
|
-
extMethod?(method: string, params: Record<string, unknown>): MaybePromise<Record<string, unknown>>;
|
|
34
|
-
extNotification?(method: string, params: Record<string, unknown>): MaybePromise<void>;
|
|
35
|
-
}
|
|
36
|
-
/** Agent-side ACP connection. */
|
|
37
|
-
export declare class AgentSideConnection {
|
|
38
|
-
#private;
|
|
39
|
-
constructor(toAgent: (connection: AgentSideConnection) => Agent, stream: Stream);
|
|
40
|
-
/** Signal aborted when the transport closes. */
|
|
41
|
-
get signal(): AbortSignal;
|
|
42
|
-
/** Promise resolved when the transport closes. */
|
|
43
|
-
get closed(): Promise<void>;
|
|
44
|
-
/** Sends a session update notification. */
|
|
45
|
-
sessionUpdate(params: SessionNotification): Promise<void>;
|
|
46
|
-
/** Requests permission from the client. */
|
|
47
|
-
requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse>;
|
|
48
|
-
/** Reads a client-side text file. */
|
|
49
|
-
readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
|
|
50
|
-
/** Writes a client-side text file. */
|
|
51
|
-
writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
|
|
52
|
-
/** Creates a client terminal and returns its control handle. */
|
|
53
|
-
createTerminal(params: CreateTerminalRequest): Promise<TerminalHandle>;
|
|
54
|
-
/** Creates an interactive elicitation on the client. */
|
|
55
|
-
unstable_createElicitation(params: CreateElicitationRequest): Promise<CreateElicitationResponse>;
|
|
56
|
-
/** Notifies a client that URL elicitation completed. */
|
|
57
|
-
unstable_completeElicitation(params: CompleteElicitationNotification): Promise<void>;
|
|
58
|
-
/** Sends a custom request. */
|
|
59
|
-
request<Response = unknown, Params = unknown>(method: string, params?: Params): Promise<Response>;
|
|
60
|
-
/** Sends a custom notification. */
|
|
61
|
-
notify<Params = unknown>(method: string, params?: Params): Promise<void>;
|
|
62
|
-
/** Sends a legacy extension request. */
|
|
63
|
-
extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
64
|
-
/** Sends a legacy extension notification. */
|
|
65
|
-
extNotification(method: string, params: Record<string, unknown>): Promise<void>;
|
|
66
|
-
}
|
|
67
|
-
/** Handle for controlling a client-owned terminal. */
|
|
68
|
-
export declare class TerminalHandle {
|
|
69
|
-
#private;
|
|
70
|
-
/** Client terminal identifier. */
|
|
71
|
-
readonly id: string;
|
|
72
|
-
constructor(id: string, sessionId: string, connection: RpcConnection);
|
|
73
|
-
/** Gets current terminal output. */
|
|
74
|
-
currentOutput(): Promise<TerminalOutputResponse>;
|
|
75
|
-
/** Waits for terminal exit. */
|
|
76
|
-
waitForExit(): Promise<WaitForTerminalExitResponse>;
|
|
77
|
-
/** Kills the terminal. */
|
|
78
|
-
kill(): Promise<TerminalActionResponse>;
|
|
79
|
-
/** Releases the terminal. */
|
|
80
|
-
release(): Promise<TerminalActionResponse | void>;
|
|
81
|
-
/** Releases the terminal during async disposal. */
|
|
82
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
83
|
-
}
|
|
84
|
-
/** Client-side ACP connection used to call an agent. */
|
|
85
|
-
export declare class ClientSideConnection {
|
|
86
|
-
#private;
|
|
87
|
-
constructor(toClient: (connection: ClientSideConnection) => Client, stream: Stream);
|
|
88
|
-
/** Signal aborted when the transport closes. */
|
|
89
|
-
get signal(): AbortSignal;
|
|
90
|
-
/** Promise resolved when the transport closes. */
|
|
91
|
-
get closed(): Promise<void>;
|
|
92
|
-
/** Initializes protocol capabilities. */
|
|
93
|
-
initialize(params: InitializeRequest): Promise<InitializeResponse>;
|
|
94
|
-
/** Authenticates with an agent. */
|
|
95
|
-
authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse>;
|
|
96
|
-
/** Creates a session. */
|
|
97
|
-
newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
|
|
98
|
-
/** Loads a session. */
|
|
99
|
-
loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse>;
|
|
100
|
-
/** Lists sessions. */
|
|
101
|
-
listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse>;
|
|
102
|
-
/** Resumes a session. */
|
|
103
|
-
resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse>;
|
|
104
|
-
/** Forks a session. */
|
|
105
|
-
unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse>;
|
|
106
|
-
/** Closes a session. */
|
|
107
|
-
closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse>;
|
|
108
|
-
/** Sets a session mode. */
|
|
109
|
-
setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
|
|
110
|
-
/** Sets a session config option. */
|
|
111
|
-
setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>;
|
|
112
|
-
/** Sends a prompt. */
|
|
113
|
-
prompt(params: PromptRequest): Promise<PromptResponse>;
|
|
114
|
-
/** Cancels an active prompt. */
|
|
115
|
-
cancel(params: {
|
|
116
|
-
sessionId: string;
|
|
117
|
-
}): Promise<void>;
|
|
118
|
-
}
|