@oh-my-pi/pi-tui 18.2.11 → 18.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/THIRD-PARTY-NOTICES.txt +3 -3
- package/dist/types/apps/ps-data.d.ts +1 -1
- package/dist/types/apps/ps-top.d.ts +1 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +1 -1
- package/dist/types/chat/read-tool-group.d.ts +3 -3
- package/dist/types/chat/user-message.d.ts +4 -1
- package/dist/types/components/editor.d.ts +0 -2
- package/dist/types/components/input.d.ts +16 -0
- package/dist/types/overlays/annotation-overlay.d.ts +30 -0
- package/dist/types/overlays/annotation-types.d.ts +84 -0
- package/dist/types/overlays/btw-history-panel.d.ts +4 -1
- package/dist/types/overlays/copy-selector.d.ts +30 -2
- package/dist/types/overlays/copy-targets.d.ts +2 -0
- package/dist/types/overlays/model-hub.d.ts +2 -2
- package/dist/types/overlays/oauth-selector.d.ts +3 -6
- package/dist/types/overlays/settings-defs.d.ts +5 -0
- package/dist/types/overlays/usage-dashboard.d.ts +2 -0
- package/dist/types/prompt/composer.d.ts +12 -1
- package/dist/types/prompt/custom-editor.d.ts +5 -26
- package/dist/types/prompt/image-source.d.ts +9 -8
- package/dist/types/space-hold.d.ts +45 -0
- package/dist/types/status-line/context-usage.d.ts +3 -1
- package/dist/types/status-line/host.d.ts +2 -0
- package/dist/types/theme/mermaid-cache.d.ts +4 -6
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/cfg-render.d.ts +37 -0
- package/dist/types/tools/cfg-url.d.ts +16 -0
- package/dist/types/tools/daemon.d.ts +46 -0
- package/dist/types/tools/find.d.ts +2 -2
- package/dist/types/tools/irc.d.ts +30 -0
- package/dist/types/tools/line-ranges.d.ts +1 -1
- package/dist/types/tools/output-meta.d.ts +2 -0
- package/dist/types/tools/proc-render.d.ts +27 -0
- package/dist/types/tools/read.d.ts +18 -4
- package/dist/types/tools/result-card.d.ts +20 -0
- package/dist/types/tools/url-scheme-host.d.ts +29 -0
- package/dist/types/tools/wait.d.ts +131 -0
- package/dist/types/tools/write.d.ts +6 -0
- package/dist/types/tools/xd-url.d.ts +8 -0
- package/dist/types/tools/xdev.d.ts +9 -1
- package/dist/types/windows-altgr.d.ts +54 -0
- package/package.json +13 -10
- package/src/apps/debug/protocol-probe.ts +1 -1
- package/src/apps/ps-data.ts +1 -1
- package/src/apps/ps-top.ts +1 -1
- package/src/chat/chat-transcript-builder.ts +9 -5
- package/src/chat/read-tool-group.ts +8 -9
- package/src/chat/tool-execution.ts +15 -13
- package/src/chat/transcript-render-helpers.ts +3 -3
- package/src/chat/user-message.ts +16 -6
- package/src/components/editor.ts +2 -4
- package/src/components/input.ts +103 -30
- package/src/components/markdown.ts +19 -7
- package/src/desktop-notify.ts +1 -1
- package/src/overlays/annotation-overlay.ts +1144 -0
- package/src/overlays/annotation-types.ts +78 -0
- package/src/overlays/btw-history-panel.ts +4 -0
- package/src/overlays/copy-selector.ts +45 -19
- package/src/overlays/copy-targets.ts +3 -3
- package/src/overlays/model-hub.ts +5 -4
- package/src/overlays/oauth-selector.ts +8 -12
- package/src/overlays/plan-review-overlay.ts +213 -40
- package/src/overlays/settings-defs.ts +6 -0
- package/src/overlays/settings-selector.ts +4 -3
- package/src/overlays/usage-dashboard.ts +22 -1
- package/src/prompt/composer.ts +83 -56
- package/src/prompt/custom-editor.ts +15 -137
- package/src/prompt/image-source.ts +9 -8
- package/src/prompt/tips.txt +1 -0
- package/src/setup/scenes/sign-in.ts +1 -1
- package/src/space-hold.ts +143 -0
- package/src/status-line/component.ts +6 -0
- package/src/status-line/context-usage.ts +15 -3
- package/src/status-line/host.ts +2 -0
- package/src/status-line/segments.ts +4 -0
- package/src/terminal-capabilities.ts +2 -2
- package/src/terminal.ts +8 -1
- package/src/theme/mermaid-cache.ts +83 -25
- package/src/tools/bash.ts +14 -1
- package/src/tools/cfg-render.ts +117 -0
- package/src/tools/cfg-url.ts +30 -0
- package/src/tools/daemon.ts +51 -0
- package/src/tools/find.ts +7 -6
- package/src/tools/index.ts +2 -2
- package/src/tools/irc.ts +33 -0
- package/src/tools/line-ranges.ts +13 -3
- package/src/tools/output-meta.ts +2 -0
- package/src/tools/proc-render.ts +270 -0
- package/src/tools/read.ts +118 -56
- package/src/tools/result-card.ts +36 -0
- package/src/tools/url-scheme-host.ts +42 -0
- package/src/tools/wait.ts +595 -0
- package/src/tools/write.ts +120 -1
- package/src/tools/xd-url.ts +9 -0
- package/src/tools/xdev.ts +9 -1
- package/src/windows-altgr.ts +227 -0
- package/dist/types/chat/read-target.d.ts +0 -4
- package/dist/types/tools/hub.d.ts +0 -342
- package/src/chat/read-target.ts +0 -7
- package/src/tools/hub.ts +0 -1492
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MermaidRenderOptions } from "@oh-my-pi/pi-natives";
|
|
2
|
-
import
|
|
2
|
+
import * as mermaidAscii from "@oh-my-pi/pi-utils/mermaid-ascii";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Options controlling how fenced Mermaid source is resolved to terminal ASCII.
|
|
@@ -8,12 +8,10 @@ import { renderMermaidAsciiSafe } from "@oh-my-pi/pi-utils/mermaid-ascii";
|
|
|
8
8
|
*/
|
|
9
9
|
export interface MermaidResolveOptions extends MermaidRenderOptions {
|
|
10
10
|
/**
|
|
11
|
-
* Maximum display width (terminal columns) the diagram should occupy.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* tall horizontal column. Omit to keep the source's own layout regardless of
|
|
16
|
-
* width.
|
|
11
|
+
* Maximum display width (terminal columns) the diagram should occupy.
|
|
12
|
+
* Flowcharts are also rendered top-down and left-to-right. A resize picks
|
|
13
|
+
* the shortest variant that fits; if none fit, the narrowest, which the
|
|
14
|
+
* caller may still clip. Omit to keep the source's own layout.
|
|
17
15
|
*/
|
|
18
16
|
maxWidth?: number;
|
|
19
17
|
}
|
|
@@ -23,16 +21,29 @@ export interface MermaidResolveOptions extends MermaidRenderOptions {
|
|
|
23
21
|
// the cached renders, so a terminal resize re-decides without re-rendering.
|
|
24
22
|
const cache = new Map<string, string | null>();
|
|
25
23
|
|
|
26
|
-
/**
|
|
24
|
+
/** Display columns, ignoring ANSI so themed diagrams are measured as drawn. */
|
|
25
|
+
const DISPLAY_WIDTH = { countAnsiEscapeCodes: false } as const;
|
|
26
|
+
|
|
27
27
|
function asciiDisplayWidth(ascii: string): number {
|
|
28
28
|
let max = 0;
|
|
29
29
|
for (const line of ascii.split("\n")) {
|
|
30
|
-
const width = Bun.stringWidth(line);
|
|
30
|
+
const width = Bun.stringWidth(line, DISPLAY_WIDTH);
|
|
31
31
|
if (width > max) max = width;
|
|
32
32
|
}
|
|
33
33
|
return max;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
interface LayoutCandidate {
|
|
37
|
+
ascii: string;
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
authored: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function measureLayout(ascii: string, authored: boolean): LayoutCandidate {
|
|
44
|
+
return { ascii, width: asciiDisplayWidth(ascii), height: ascii.split("\n").length, authored };
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
function renderVariant(
|
|
37
48
|
source: string,
|
|
38
49
|
baseOptions: MermaidRenderOptions,
|
|
@@ -43,11 +54,39 @@ function renderVariant(
|
|
|
43
54
|
const cached = cache.get(key);
|
|
44
55
|
if (cached !== undefined) return cached;
|
|
45
56
|
|
|
46
|
-
const ascii = renderMermaidAsciiSafe(source, direction ? { ...baseOptions, direction } : baseOptions);
|
|
57
|
+
const ascii = mermaidAscii.renderMermaidAsciiSafe(source, direction ? { ...baseOptions, direction } : baseOptions);
|
|
47
58
|
cache.set(key, ascii);
|
|
48
59
|
return ascii;
|
|
49
60
|
}
|
|
50
61
|
|
|
62
|
+
const FORCED_DIRECTIONS = ["TD", "LR"] as const;
|
|
63
|
+
type ForcedDirection = (typeof FORCED_DIRECTIONS)[number];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Forced TD/LR renders that can differ from the authored one. Sequence, class,
|
|
67
|
+
* ER, and xychart ignore direction, so they get none; header match mirrors
|
|
68
|
+
* native `detect_diagram_kind`, including its JavaScript `\w` boundary
|
|
69
|
+
* (`[A-Za-z0-9_]`). A `graph`/`flowchart` header already authored in one of
|
|
70
|
+
* the forced layouts (`TD`/`TB` lay out as TD, `LR`/`RL` as LR) skips that
|
|
71
|
+
* variant: the native override replaces the header direction, so the render
|
|
72
|
+
* would be byte-identical to the authored one.
|
|
73
|
+
*/
|
|
74
|
+
function forcedDirections(source: string): readonly ForcedDirection[] {
|
|
75
|
+
const header = (source.split("\n", 1)[0] ?? "").trim().toLowerCase();
|
|
76
|
+
for (const prefix of ["xychart-beta", "xychart"] as const) {
|
|
77
|
+
if (!header.startsWith(prefix)) continue;
|
|
78
|
+
const next = header.charCodeAt(prefix.length);
|
|
79
|
+
const wordChar =
|
|
80
|
+
(next >= 48 && next <= 57) || (next >= 65 && next <= 90) || (next >= 97 && next <= 122) || next === 95;
|
|
81
|
+
if (!wordChar) return [];
|
|
82
|
+
}
|
|
83
|
+
if (header === "sequencediagram" || header === "classdiagram" || header === "erdiagram") return [];
|
|
84
|
+
const authored = /^(?:graph|flowchart)\s+(td|tb|lr|rl)$/.exec(header)?.[1];
|
|
85
|
+
if (authored === "td" || authored === "tb") return ["LR"];
|
|
86
|
+
if (authored === "lr" || authored === "rl") return ["TD"];
|
|
87
|
+
return FORCED_DIRECTIONS;
|
|
88
|
+
}
|
|
89
|
+
|
|
51
90
|
/**
|
|
52
91
|
* Resolve mermaid ASCII from fenced block source text.
|
|
53
92
|
* Returns null when rendering fails, while memoizing failures to avoid repeated work.
|
|
@@ -65,24 +104,43 @@ export function resolveMermaidAscii(source: string, options?: MermaidResolveOpti
|
|
|
65
104
|
if (base === null) return null;
|
|
66
105
|
if (maxWidth === undefined) return base;
|
|
67
106
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// the narrowest (clipping at the call site handles any residual overflow).
|
|
74
|
-
// Re-rendering the already-authored orientation is a cache hit, so this stays
|
|
75
|
-
// cheap, and one of the two will be the perpendicular fit.
|
|
76
|
-
for (const direction of ["TD", "LR"] as const) {
|
|
107
|
+
// Width selection is against cached renders, so a later resize re-decides
|
|
108
|
+
// without drawing again. Only forced layouts that can differ from the
|
|
109
|
+
// authored one are rendered (see `forcedDirections`).
|
|
110
|
+
const candidates = [measureLayout(base, true)];
|
|
111
|
+
for (const direction of forcedDirections(normalizedSource)) {
|
|
77
112
|
const variant = renderVariant(normalizedSource, baseOptions, baseKey, direction);
|
|
78
|
-
if (variant
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
113
|
+
if (variant !== null) candidates.push(measureLayout(variant, false));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const fitting = candidates.filter(candidate => candidate.width <= maxWidth);
|
|
117
|
+
const pool = fitting.length > 0 ? fitting : candidates;
|
|
118
|
+
let best = pool[0]!;
|
|
119
|
+
for (let i = 1; i < pool.length; i++) {
|
|
120
|
+
const next = pool[i]!;
|
|
121
|
+
if (fitting.length > 0) {
|
|
122
|
+
if (next.height !== best.height) {
|
|
123
|
+
if (next.height < best.height) best = next;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (next.authored !== best.authored) {
|
|
127
|
+
if (next.authored) best = next;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (next.width < best.width) best = next;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (next.width !== best.width) {
|
|
134
|
+
if (next.width < best.width) best = next;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (next.height !== best.height) {
|
|
138
|
+
if (next.height < best.height) best = next;
|
|
139
|
+
continue;
|
|
83
140
|
}
|
|
141
|
+
if (next.authored) best = next;
|
|
84
142
|
}
|
|
85
|
-
return best;
|
|
143
|
+
return best.ascii;
|
|
86
144
|
}
|
|
87
145
|
|
|
88
146
|
/**
|
package/src/tools/bash.ts
CHANGED
|
@@ -41,6 +41,13 @@ export interface BashToolDetails {
|
|
|
41
41
|
timedOut?: boolean;
|
|
42
42
|
/** Live ACP update only; completed results refer to released terminals. */
|
|
43
43
|
terminalId?: string;
|
|
44
|
+
service?: {
|
|
45
|
+
name: string;
|
|
46
|
+
state: string;
|
|
47
|
+
ready: boolean;
|
|
48
|
+
timedOut: boolean;
|
|
49
|
+
pid?: number;
|
|
50
|
+
};
|
|
44
51
|
async?: {
|
|
45
52
|
state: "running" | "completed" | "failed";
|
|
46
53
|
jobId: string;
|
|
@@ -362,6 +369,12 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
362
369
|
if (details?.async?.state === "running") {
|
|
363
370
|
statsParts.push(`Backgrounded: ${details.async.jobId}`);
|
|
364
371
|
}
|
|
372
|
+
if (details?.service) {
|
|
373
|
+
const service = details.service;
|
|
374
|
+
statsParts.push(`Service: ${service.name}`, `State: ${service.state}`);
|
|
375
|
+
statsParts.push(`Ready: ${service.ready ? "yes" : service.timedOut ? "timed out" : "no"}`);
|
|
376
|
+
if (service.pid !== undefined) statsParts.push(`PID: ${service.pid}`);
|
|
377
|
+
}
|
|
365
378
|
if (wallTimeMs !== undefined) {
|
|
366
379
|
statsParts.push(`Wall: ${formatWallTimeSeconds(wallTimeMs)}s`);
|
|
367
380
|
}
|
|
@@ -460,7 +473,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
460
473
|
|
|
461
474
|
/** Renders bash command previews and output. */
|
|
462
475
|
export const bashToolRenderer = createShellRenderer<BashRenderArgs>({
|
|
463
|
-
resolveTitle: ()
|
|
476
|
+
resolveTitle: args => (args?.name ? `Bash · ${String(args.name)}` : "Bash"),
|
|
464
477
|
resolveCommand: args => args?.command,
|
|
465
478
|
resolveCwd: args => args?.cwd,
|
|
466
479
|
resolveEnv: args => args?.env,
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript renderers for `cfg://` settings reads and writes.
|
|
3
|
+
*
|
|
4
|
+
* Reads show the YAML-ish settings listing the model received, highlighted as
|
|
5
|
+
* YAML. Writes show the `previous → value` change, its scope (session or
|
|
6
|
+
* saved), and whether the user approved it.
|
|
7
|
+
*/
|
|
8
|
+
import type { Component } from "../tui";
|
|
9
|
+
import { renderStatusLine } from "../render";
|
|
10
|
+
import {
|
|
11
|
+
cappedHeadLines,
|
|
12
|
+
formatBadge,
|
|
13
|
+
formatErrorDetail,
|
|
14
|
+
formatMoreItems,
|
|
15
|
+
PREVIEW_LIMITS,
|
|
16
|
+
} from "../render/render-utils";
|
|
17
|
+
import { highlightCode, type Theme } from "../theme/theme";
|
|
18
|
+
import { parseCfgUrl } from "./cfg-url";
|
|
19
|
+
import type { RenderResultOptions } from "./renderer";
|
|
20
|
+
import { card, type CardToolResult, firstText, safe } from "./result-card";
|
|
21
|
+
|
|
22
|
+
/** Summary of a `cfg://` read, attached to read tool details. */
|
|
23
|
+
export interface CfgReadDetails {
|
|
24
|
+
/** Dotted setting path or namespace that was read; empty for the whole tree. */
|
|
25
|
+
path: string;
|
|
26
|
+
/** Settings in the listing. */
|
|
27
|
+
count: number;
|
|
28
|
+
/** Listed settings whose effective value differs from the schema default. */
|
|
29
|
+
modified: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** How a `cfg://` write ended. */
|
|
33
|
+
export type CfgWriteOutcome = "applied" | "declined" | "unchanged";
|
|
34
|
+
|
|
35
|
+
/** A `cfg://` write, attached to write tool details. Values are display-formatted (credentials redacted). */
|
|
36
|
+
export interface CfgWriteDetails {
|
|
37
|
+
path: string;
|
|
38
|
+
/** Persisted to config.yml rather than scoped to the session. */
|
|
39
|
+
save: boolean;
|
|
40
|
+
previous: string;
|
|
41
|
+
value: string;
|
|
42
|
+
outcome: CfgWriteOutcome;
|
|
43
|
+
/** Effective value after saving when a higher-precedence layer still shadows the saved one. */
|
|
44
|
+
effective?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Render `read cfg://…`: pending header, or the highlighted listing with counts. */
|
|
48
|
+
export function renderCfgRead(
|
|
49
|
+
url: string,
|
|
50
|
+
result: CardToolResult | undefined,
|
|
51
|
+
details: CfgReadDetails | undefined,
|
|
52
|
+
options: RenderResultOptions,
|
|
53
|
+
theme: Theme,
|
|
54
|
+
): Component {
|
|
55
|
+
return card((_width, expanded) => {
|
|
56
|
+
const meta: string[] = [];
|
|
57
|
+
if (details) {
|
|
58
|
+
meta.push(details.count === 1 ? "1 setting" : `${details.count} settings`);
|
|
59
|
+
if (details.modified) meta.push(`${details.modified} modified`);
|
|
60
|
+
}
|
|
61
|
+
const header = renderStatusLine(
|
|
62
|
+
{
|
|
63
|
+
icon: result === undefined ? "pending" : result.isError ? "error" : "info",
|
|
64
|
+
title: "Config",
|
|
65
|
+
description: (details?.path ?? parseCfgUrl(url)?.segments.join(".")) || "all settings",
|
|
66
|
+
meta,
|
|
67
|
+
},
|
|
68
|
+
theme,
|
|
69
|
+
);
|
|
70
|
+
if (!result) return [header];
|
|
71
|
+
if (result.isError) return [header, formatErrorDetail(firstText(result) || "Settings read failed.", theme)];
|
|
72
|
+
const body = firstText(result).trimEnd().split("\n");
|
|
73
|
+
const shown = cappedHeadLines(body, expanded ? body.length : PREVIEW_LIMITS.COLLAPSED_ITEMS);
|
|
74
|
+
const lines = [header, ...highlightCode(safe(shown.lines.join("\n")), "yaml", theme).map(line => ` ${line}`)];
|
|
75
|
+
if (shown.hidden) lines.push(theme.fg("dim", ` ${formatMoreItems(shown.hidden, "line")}`));
|
|
76
|
+
return lines;
|
|
77
|
+
}, options);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Render `write cfg://…[/save]`: the proposed value while pending, then the approved/declined change. */
|
|
81
|
+
export function renderCfgWrite(
|
|
82
|
+
url: string,
|
|
83
|
+
content: string | undefined,
|
|
84
|
+
result: CardToolResult | undefined,
|
|
85
|
+
details: CfgWriteDetails | undefined,
|
|
86
|
+
options: RenderResultOptions,
|
|
87
|
+
theme: Theme,
|
|
88
|
+
): Component {
|
|
89
|
+
return card(() => {
|
|
90
|
+
const target = parseCfgUrl(url);
|
|
91
|
+
const save = details?.save ?? target?.save ?? false;
|
|
92
|
+
const icon =
|
|
93
|
+
result === undefined
|
|
94
|
+
? "pending"
|
|
95
|
+
: result.isError
|
|
96
|
+
? "error"
|
|
97
|
+
: details?.outcome === "declined"
|
|
98
|
+
? "warning"
|
|
99
|
+
: "success";
|
|
100
|
+
const badges = [formatBadge(save ? "persist" : "session", save ? "accent" : "muted", theme)];
|
|
101
|
+
if (details?.outcome === "declined") badges.push(formatBadge("declined", "warning", theme));
|
|
102
|
+
if (details?.outcome === "unchanged") badges.push(formatBadge("unchanged", "muted", theme));
|
|
103
|
+
const header = `${renderStatusLine({ icon, title: "Config", description: details?.path ?? target?.segments.join(".") }, theme)} ${badges.join(" ")}`;
|
|
104
|
+
if (result?.isError) return [header, formatErrorDetail(firstText(result) || "Settings write failed.", theme)];
|
|
105
|
+
const arrow = theme.fg("dim", "→");
|
|
106
|
+
const next = theme.fg("toolOutput", details?.value ?? content?.trim() ?? "…");
|
|
107
|
+
const lines = [
|
|
108
|
+
header,
|
|
109
|
+
details ? ` ${theme.fg("dim", details.previous)} ${arrow} ${next}` : ` ${arrow} ${next}`,
|
|
110
|
+
];
|
|
111
|
+
if (details?.effective !== undefined)
|
|
112
|
+
lines.push(
|
|
113
|
+
theme.fg("warning", ` still ${details.effective}: a higher-precedence layer overrides the saved value`),
|
|
114
|
+
);
|
|
115
|
+
return lines;
|
|
116
|
+
}, options);
|
|
117
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Canonical prefix for settings URLs. */
|
|
2
|
+
export const CFG_URL_PREFIX = "cfg://";
|
|
3
|
+
|
|
4
|
+
/** Trailing write segment that persists a `cfg://` change instead of scoping it to the session. */
|
|
5
|
+
export const CFG_SAVE_SEGMENT = "save";
|
|
6
|
+
|
|
7
|
+
/** Setting addressed by a `cfg://` URL. */
|
|
8
|
+
export interface CfgTarget {
|
|
9
|
+
/** Setting path segments; empty for the root. `/` and `.` both separate segments. */
|
|
10
|
+
segments: string[];
|
|
11
|
+
/** Whether the URL ends in the `/save` suffix, which is removed from {@link segments}. */
|
|
12
|
+
save: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse a `cfg://` URL (`cfg://advisor/enabled`, `cfg://advisor.enabled/save`).
|
|
17
|
+
* Returns `null` for other schemes; query and fragment are ignored.
|
|
18
|
+
*/
|
|
19
|
+
export function parseCfgUrl(input: string): CfgTarget | null {
|
|
20
|
+
const trimmed = input.trim();
|
|
21
|
+
if (!trimmed.toLowerCase().startsWith(CFG_URL_PREFIX)) return null;
|
|
22
|
+
const segments = trimmed
|
|
23
|
+
.slice(CFG_URL_PREFIX.length)
|
|
24
|
+
.replace(/[?#].*$/s, "")
|
|
25
|
+
.split(/[/.]/)
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
const save = segments.length > 1 && segments.at(-1) === CFG_SAVE_SEGMENT;
|
|
28
|
+
if (save) segments.pop();
|
|
29
|
+
return { segments, save };
|
|
30
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Launch-broker daemon types shared by services, `proc://`, and `omp ps`. */
|
|
2
|
+
|
|
3
|
+
/** Stable lifecycle states exposed by the launch broker. */
|
|
4
|
+
export type DaemonState = "starting" | "running" | "ready" | "restarting" | "stopping" | "exited" | "failed";
|
|
5
|
+
|
|
6
|
+
/** Restart behavior applied after an unexpected daemon exit. */
|
|
7
|
+
export type DaemonRestartPolicy = "no" | "on-failure" | "always";
|
|
8
|
+
|
|
9
|
+
/** Readiness conditions; every configured condition must pass. */
|
|
10
|
+
export interface DaemonReadySpec {
|
|
11
|
+
log?: string;
|
|
12
|
+
port?: number;
|
|
13
|
+
host?: string;
|
|
14
|
+
timeoutMs: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Immutable launch specification retained for restart and inspection. */
|
|
18
|
+
export interface DaemonSpec {
|
|
19
|
+
name: string;
|
|
20
|
+
application: string;
|
|
21
|
+
args: string[];
|
|
22
|
+
env: Record<string, string>;
|
|
23
|
+
cwd: string;
|
|
24
|
+
pty: boolean;
|
|
25
|
+
ready?: DaemonReadySpec;
|
|
26
|
+
restart: DaemonRestartPolicy;
|
|
27
|
+
persist: boolean;
|
|
28
|
+
detached: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Serializable daemon state visible to every client in one broker scope. */
|
|
32
|
+
export interface DaemonSnapshot {
|
|
33
|
+
name: string;
|
|
34
|
+
id: string;
|
|
35
|
+
state: DaemonState;
|
|
36
|
+
pid?: number;
|
|
37
|
+
createdAt: number;
|
|
38
|
+
startedAt: number;
|
|
39
|
+
readyAt?: number;
|
|
40
|
+
exitedAt?: number;
|
|
41
|
+
exitCode?: number;
|
|
42
|
+
exitReason?: string;
|
|
43
|
+
restartCount: number;
|
|
44
|
+
outputBytes: number;
|
|
45
|
+
owner?: string;
|
|
46
|
+
readyMatch?: string;
|
|
47
|
+
/** Readiness conditions still unmet while `state` is `starting`; absent once ready or without a ready spec. */
|
|
48
|
+
readyPending?: ("log" | "port")[];
|
|
49
|
+
persist: boolean;
|
|
50
|
+
detached: boolean;
|
|
51
|
+
}
|
package/src/tools/find.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type { Theme, ThemeColor } from "../theme/theme";
|
|
|
28
28
|
import type { Component } from "../tui";
|
|
29
29
|
import type { OutputMeta } from "./output-meta";
|
|
30
30
|
import type { RenderResultOptions, ToolRenderer } from "./renderer";
|
|
31
|
+
import { splitUrlScheme } from "./url-scheme-host";
|
|
31
32
|
|
|
32
33
|
/** A verified line range with its yes-probability and a one-line preview. */
|
|
33
34
|
export interface FindRange {
|
|
@@ -39,7 +40,7 @@ export interface FindRange {
|
|
|
39
40
|
|
|
40
41
|
/** A file whose verified passages cleared the threshold; `ranges` are merged positive spans, strongest first. */
|
|
41
42
|
export interface FindHit {
|
|
42
|
-
/** Display path relative to {@link FindToolDetails.cwd}, or an
|
|
43
|
+
/** Display path relative to {@link FindToolDetails.cwd}, or an internal URL under URL scopes. */
|
|
43
44
|
rel: string;
|
|
44
45
|
/** Filename judgment, when the name batch answered. */
|
|
45
46
|
nameScore?: number;
|
|
@@ -85,7 +86,7 @@ export interface FindToolDetails {
|
|
|
85
86
|
elapsedMs: number;
|
|
86
87
|
/** Session cwd; hit paths are relative to it. */
|
|
87
88
|
cwd: string;
|
|
88
|
-
/** Display form of the searched directory when narrower than cwd. */
|
|
89
|
+
/** Display form of the searched directory or file when narrower than cwd. */
|
|
89
90
|
scopePath?: string;
|
|
90
91
|
meta?: OutputMeta;
|
|
91
92
|
}
|
|
@@ -124,11 +125,11 @@ function gauge(p: number, theme: Theme): string {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
function renderHit(hit: FindHit, rangeLimit: number, cwd: string | undefined, theme: Theme): string[] {
|
|
127
|
-
// `
|
|
128
|
-
// itself instead of joining it onto a filesystem base.
|
|
129
|
-
const
|
|
128
|
+
// `scheme://` hits (e.g. virtual docs) are not files under `cwd`: link the
|
|
129
|
+
// URL itself instead of joining it onto a filesystem base.
|
|
130
|
+
const isUrlHit = splitUrlScheme(hit.rel) !== undefined;
|
|
130
131
|
const link = (text: string, line?: number): string => {
|
|
131
|
-
if (
|
|
132
|
+
if (isUrlHit) return uriHyperlink(line === undefined ? hit.rel : `${hit.rel}:${line}`, text);
|
|
132
133
|
const absPath = cwd === undefined ? undefined : path.join(cwd, hit.rel);
|
|
133
134
|
return absPath === undefined ? text : fileHyperlink(absPath, text, { line });
|
|
134
135
|
};
|
package/src/tools/index.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { githubToolRenderer } from "./github";
|
|
|
15
15
|
import { globToolRenderer } from "./glob";
|
|
16
16
|
import { goalToolRenderer } from "./goal";
|
|
17
17
|
import { grepToolRenderer } from "./grep";
|
|
18
|
-
import {
|
|
18
|
+
import { waitToolRenderer } from "./wait";
|
|
19
19
|
import { lspToolRenderer } from "./lsp";
|
|
20
20
|
import { recallToolRenderer, reflectToolRenderer, retainToolRenderer } from "./memory";
|
|
21
21
|
import { readToolRenderer } from "./read";
|
|
@@ -45,7 +45,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
45
45
|
glob: globToolRenderer,
|
|
46
46
|
grep: grepToolRenderer,
|
|
47
47
|
lsp: lspToolRenderer,
|
|
48
|
-
|
|
48
|
+
wait: waitToolRenderer,
|
|
49
49
|
read: readToolRenderer,
|
|
50
50
|
// Keyed by xd:// resolution-device names: the write dispatch delegates here
|
|
51
51
|
// by dispatch tool, and historical `resolve` tool transcripts still render
|
package/src/tools/irc.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Peer-message types and roster constants shared by messaging, `wait`, and child prompts. */
|
|
2
|
+
|
|
3
|
+
/** Maximum live peer rows embedded in child prompts. */
|
|
4
|
+
export const DEFAULT_PEER_ROSTER_LIMIT = 32;
|
|
5
|
+
|
|
6
|
+
/** Serializable peer message retained in coordination result snapshots. */
|
|
7
|
+
export interface IrcMessage {
|
|
8
|
+
id: string;
|
|
9
|
+
/** Sender agent id. */
|
|
10
|
+
from: string;
|
|
11
|
+
/** Recipient agent id (resolved; "all" is expanded by the tool, not stored). */
|
|
12
|
+
to: string;
|
|
13
|
+
body: string;
|
|
14
|
+
ts: number;
|
|
15
|
+
/** Message id being answered. */
|
|
16
|
+
replyTo?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Automated wake-turn relay of a woken subagent's stop output (task executor
|
|
19
|
+
* `relayWakeTurnOutput`). Relays are answers, never wake sources: the
|
|
20
|
+
* recipient's own wake-turn relay must skip them or two idle peers
|
|
21
|
+
* ping-pong forever.
|
|
22
|
+
*/
|
|
23
|
+
wakeRelay?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Delivery outcome for one peer recipient. */
|
|
27
|
+
export interface IrcDeliveryReceipt {
|
|
28
|
+
to: string;
|
|
29
|
+
outcome: "injected" | "woken" | "revived" | "failed";
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Status ordering for peer rosters in child prompts. */
|
|
33
|
+
export const LIST_STATUS_ORDER: Record<string, number> = { running: 0, idle: 1, parked: 2 };
|
package/src/tools/line-ranges.ts
CHANGED
|
@@ -15,6 +15,11 @@ const LINE_RANGE_CHUNK_RE = new RegExp(`^${LINE_RANGE_CHUNK_SOURCE}$`, "i");
|
|
|
15
15
|
|
|
16
16
|
/** Parse a single `N`, `N-M`, `N-`, `N+K`, or `..`-aliased (`N..M`, `N..`) chunk. Throws via {@link ToolError} on invalid bounds. */
|
|
17
17
|
export function parseLineRangeChunk(sel: string): LineRange | null {
|
|
18
|
+
return parseChunk(sel, false);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** `pinBare` makes a separator-less `N` the single line `N` instead of "from `N` onward". */
|
|
22
|
+
function parseChunk(sel: string, pinBare: boolean): LineRange | null {
|
|
18
23
|
const lineMatch = LINE_RANGE_CHUNK_RE.exec(sel);
|
|
19
24
|
if (!lineMatch) return null;
|
|
20
25
|
const rawStart = Number.parseInt(lineMatch[1]!, 10);
|
|
@@ -31,27 +36,32 @@ export function parseLineRangeChunk(sel: string): LineRange | null {
|
|
|
31
36
|
}
|
|
32
37
|
rawEnd = rawStart + rhs - 1;
|
|
33
38
|
} else if (sep === "-") {
|
|
34
|
-
// `301-` is shorthand for "from 301 onward" — equivalent to bare `301`.
|
|
39
|
+
// `301-` is shorthand for "from 301 onward" — equivalent to a lone bare `301`.
|
|
35
40
|
if (rhs !== undefined) {
|
|
36
41
|
if (rhs < rawStart) {
|
|
37
42
|
throw new ToolError(`Invalid range ${rawStart}-${rhs}: end must be >= start.`);
|
|
38
43
|
}
|
|
39
44
|
rawEnd = rhs;
|
|
40
45
|
}
|
|
46
|
+
} else if (pinBare) {
|
|
47
|
+
rawEnd = rawStart;
|
|
41
48
|
}
|
|
42
49
|
return { startLine: rawStart, endLine: rawEnd };
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
|
-
* Parse a comma-separated list of line ranges (e.g. `5-16,960-973`). Returns
|
|
53
|
+
* Parse a comma-separated list of line ranges (e.g. `5-16,960-973` or `19,59`). Returns
|
|
47
54
|
* the ranges in ascending order with overlapping/adjacent ranges merged so
|
|
48
55
|
* downstream consumers can stream the file in a single forward pass per range.
|
|
49
56
|
*/
|
|
50
57
|
export function parseLineRanges(sel: string): [LineRange, ...LineRange[]] | null {
|
|
51
58
|
const chunks = sel.split(",");
|
|
59
|
+
// A lone `:50` means "from line 50". Inside a comma list, a bare number is
|
|
60
|
+
// that one line; otherwise `:19,59` collapses to "from 19 through EOF".
|
|
61
|
+
const pinBare = chunks.length > 1;
|
|
52
62
|
const parsed: LineRange[] = [];
|
|
53
63
|
for (const chunk of chunks) {
|
|
54
|
-
const range =
|
|
64
|
+
const range = parseChunk(chunk, pinBare);
|
|
55
65
|
if (!range) return null;
|
|
56
66
|
parsed.push(range);
|
|
57
67
|
}
|
package/src/tools/output-meta.ts
CHANGED
|
@@ -72,6 +72,8 @@ export interface OutputMeta {
|
|
|
72
72
|
/** Capture failure of this output itself; aggregate reports keep source failures on their entries. */
|
|
73
73
|
artifactError?: OutputArtifactError;
|
|
74
74
|
source?: SourceMeta;
|
|
75
|
+
/** The output is a bounded page of session artifact storage its `source` re-reads with line selectors; spill-to-artifact skips it. */
|
|
76
|
+
pagedSource?: true;
|
|
75
77
|
diagnostics?: DiagnosticMeta;
|
|
76
78
|
limits?: LimitsMeta;
|
|
77
79
|
}
|