@oh-my-pi/pi-tui 18.2.10 → 18.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/THIRD-PARTY-NOTICES.txt +1 -1
- 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/overlays/annotation-overlay.d.ts +30 -0
- package/dist/types/overlays/annotation-types.d.ts +84 -0
- 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/usage-dashboard.d.ts +2 -0
- package/dist/types/prompt/image-source.d.ts +9 -8
- package/dist/types/render/render-utils.d.ts +2 -0
- package/dist/types/status-line/context-usage.d.ts +3 -1
- package/dist/types/theme/mermaid-cache.d.ts +4 -6
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/daemon.d.ts +46 -0
- package/dist/types/tools/irc.d.ts +30 -0
- package/dist/types/tools/line-ranges.d.ts +1 -1
- package/dist/types/tools/proc-render.d.ts +34 -0
- package/dist/types/tools/read.d.ts +4 -1
- package/dist/types/tools/task.d.ts +4 -0
- package/dist/types/tools/wait.d.ts +131 -0
- package/dist/types/tools/write.d.ts +4 -0
- package/dist/types/tools/xd-url.d.ts +8 -0
- 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 +4 -4
- package/src/chat/tool-execution.ts +8 -8
- package/src/chat/transcript-render-helpers.ts +3 -3
- package/src/components/markdown.ts +1 -1
- package/src/desktop-notify.ts +1 -1
- package/src/overlays/annotation-overlay.ts +1145 -0
- package/src/overlays/annotation-types.ts +78 -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 +1 -0
- package/src/overlays/usage-dashboard.ts +120 -25
- package/src/prompt/image-source.ts +9 -8
- package/src/prompt/welcome.ts +4 -1
- package/src/render/render-utils.ts +1 -1
- package/src/setup/scenes/sign-in.ts +1 -1
- package/src/status-line/component.ts +15 -4
- package/src/status-line/context-usage.ts +15 -3
- package/src/status-line/segments.ts +3 -3
- package/src/terminal-capabilities.ts +16 -7
- package/src/terminal.ts +8 -1
- package/src/theme/mermaid-cache.ts +83 -25
- package/src/tools/bash.ts +14 -1
- package/src/tools/daemon.ts +51 -0
- 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/proc-render.ts +294 -0
- package/src/tools/read.ts +22 -1
- package/src/tools/task.ts +18 -0
- package/src/tools/wait.ts +595 -0
- package/src/tools/write.ts +76 -0
- package/src/tools/xd-url.ts +9 -0
- package/src/windows-altgr.ts +227 -0
- package/dist/types/tools/hub.d.ts +0 -342
- package/src/tools/hub.ts +0 -1492
|
@@ -230,7 +230,7 @@ export class SignInTab implements SetupTab {
|
|
|
230
230
|
this.#host.restoreFocus();
|
|
231
231
|
this.#host.requestRender();
|
|
232
232
|
try {
|
|
233
|
-
await this.#authStorage.login(providerId as OAuthProvider, {
|
|
233
|
+
await this.#authStorage.oauth.login(providerId as OAuthProvider, {
|
|
234
234
|
signal: this.#loginAbort.signal,
|
|
235
235
|
onBrowserSession: (request, signal) => this.#host.ctx.captureBrowserSession(request, signal),
|
|
236
236
|
onAuth: info => {
|
|
@@ -55,6 +55,18 @@ const BRAND_FADE_MS = 450;
|
|
|
55
55
|
/** Repaint cadence while the brand fade is in flight (rust omp's `FADE_FRAME`). */
|
|
56
56
|
const BRAND_FADE_FRAME_MS = 40;
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Providers whose subscription quota is a single monthly bucket, so their
|
|
60
|
+
* `monthly`/`30d` window is the one the usage segment must show. Providers that
|
|
61
|
+
* merely report a monthly side-counter (GitHub Copilot's premium requests) stay
|
|
62
|
+
* out: their monthly row is not the session quota.
|
|
63
|
+
*/
|
|
64
|
+
const MONTHLY_SUBSCRIPTION_PROVIDERS: Record<string, true> = {
|
|
65
|
+
"alibaba-token-plan": true,
|
|
66
|
+
cursor: true,
|
|
67
|
+
"opencode-go": true,
|
|
68
|
+
};
|
|
69
|
+
|
|
58
70
|
/** A displayable limit after provider, account, model, and window filtering. */
|
|
59
71
|
interface UsageWindowCandidate {
|
|
60
72
|
id?: string;
|
|
@@ -1842,6 +1854,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1842
1854
|
const activeModelId = normalizeUsageScopeValue(context.modelId);
|
|
1843
1855
|
const activeAntigravityCounter =
|
|
1844
1856
|
context.provider === "google-antigravity" ? getAntigravityCounterKeyForModel(context.modelId) : undefined;
|
|
1857
|
+
const monthlySubscriptionProvider =
|
|
1858
|
+
context.provider !== undefined && MONTHLY_SUBSCRIPTION_PROVIDERS[context.provider] === true;
|
|
1845
1859
|
const scopeGroups = new Map<string, UsageScopeGroup>();
|
|
1846
1860
|
for (const report of reports) {
|
|
1847
1861
|
if (!report || typeof report !== "object") continue;
|
|
@@ -1904,10 +1918,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1904
1918
|
: undefined;
|
|
1905
1919
|
const windowClass =
|
|
1906
1920
|
subscriptionWindow ??
|
|
1907
|
-
((
|
|
1908
|
-
(windowId === "monthly" || windowId === "30d")
|
|
1909
|
-
? "monthly"
|
|
1910
|
-
: undefined);
|
|
1921
|
+
(monthlySubscriptionProvider && (windowId === "monthly" || windowId === "30d") ? "monthly" : undefined);
|
|
1911
1922
|
if (!windowClass) continue;
|
|
1912
1923
|
|
|
1913
1924
|
const modelId = normalizeUsageScopeValue("modelId" in scope ? scope.modelId : undefined);
|
|
@@ -2,6 +2,7 @@ import type { AgentMessage, Tokenizer } from "@oh-my-pi/pi-agent-core";
|
|
|
2
2
|
import type { CompactionSettings } from "@oh-my-pi/pi-agent-core/compaction";
|
|
3
3
|
import { effectiveReserveTokens, resolveThresholdTokens } from "@oh-my-pi/pi-agent-core/compaction";
|
|
4
4
|
import type { Tool as AiTool, Model } from "@oh-my-pi/pi-ai";
|
|
5
|
+
import { renderToolExamples } from "@oh-my-pi/pi-ai/dialect";
|
|
5
6
|
import { toolWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
6
7
|
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
7
8
|
import type { Theme } from "../theme";
|
|
@@ -12,7 +13,7 @@ interface ContextSkill {
|
|
|
12
13
|
readonly hide?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
type ContextTool = Pick<AiTool, "name" | "description" | "parameters">;
|
|
16
|
+
type ContextTool = Pick<AiTool, "name" | "description" | "parameters" | "examples">;
|
|
16
17
|
|
|
17
18
|
/** Savings computed by the host's inline-image planner, not by the renderer. */
|
|
18
19
|
export interface ContextSavingsEstimate {
|
|
@@ -140,6 +141,8 @@ export interface NonMessageTokenSource {
|
|
|
140
141
|
};
|
|
141
142
|
};
|
|
142
143
|
readonly skills?: readonly ContextSkill[];
|
|
144
|
+
/** Provider-facing, session-frozen descriptions when available. */
|
|
145
|
+
readonly renderedSkills?: readonly ContextSkill[];
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
/** Shared empty system-prompt part list, avoiding an allocation per render. */
|
|
@@ -244,9 +247,13 @@ export function estimateToolSchemaTokens(tools: ToolSchemaSource, tokenizer: Tok
|
|
|
244
247
|
name,
|
|
245
248
|
description,
|
|
246
249
|
parameters: parameters as AiTool["parameters"],
|
|
250
|
+
examples: tool.examples,
|
|
247
251
|
};
|
|
248
252
|
const wireJson = JSON.stringify(toolWireSchema(wireTool) ?? {});
|
|
249
253
|
if (typeof wireJson === "string") fragments.push(wireJson);
|
|
254
|
+
// The agent loop appends rendered examples to the wire description.
|
|
255
|
+
const examplesBlock = renderToolExamples(wireTool);
|
|
256
|
+
if (examplesBlock) fragments.push(examplesBlock);
|
|
250
257
|
} catch {
|
|
251
258
|
// Schema may contain functions or cycles; ignore.
|
|
252
259
|
}
|
|
@@ -313,7 +320,7 @@ function nonMessageTokenCacheEntry(
|
|
|
313
320
|
const systemPromptRef = session.systemPrompt ?? EMPTY_STRING_PARTS;
|
|
314
321
|
const toolsRef = session.agent?.state?.tools ?? EMPTY_TOOLS;
|
|
315
322
|
const toolsRevision = getToolSchemaMetadataRevision(toolsRef);
|
|
316
|
-
const skillsRef = session.skills ?? EMPTY_SKILLS;
|
|
323
|
+
const skillsRef = session.renderedSkills ?? session.skills ?? EMPTY_SKILLS;
|
|
317
324
|
let entry = cachedSession[NON_MESSAGE_TOKEN_CACHE];
|
|
318
325
|
if (
|
|
319
326
|
entry &&
|
|
@@ -378,7 +385,12 @@ export function computeNonMessageBreakdown(
|
|
|
378
385
|
if (entry.breakdown && entry.skillful === skillful) return entry.breakdown;
|
|
379
386
|
const tools = session.agent?.state?.tools ?? EMPTY_TOOLS;
|
|
380
387
|
const skillsTokens =
|
|
381
|
-
skillful === false
|
|
388
|
+
skillful === false
|
|
389
|
+
? 0
|
|
390
|
+
: estimateSkillsTokens(
|
|
391
|
+
renderedSkills(session.renderedSkills ?? session.skills ?? EMPTY_SKILLS, tools),
|
|
392
|
+
tokenizer,
|
|
393
|
+
);
|
|
382
394
|
const toolsTokens = estimateToolSchemaTokens(tools, tokenizer, sourceRevision);
|
|
383
395
|
const systemPromptParts = session.systemPrompt ?? EMPTY_STRING_PARTS;
|
|
384
396
|
const systemContextTokens = tokenizer.countTokens(Array.from(systemPromptParts.slice(1), part => part ?? ""));
|
|
@@ -881,9 +881,9 @@ const usageSegment: StatusLineSegment = {
|
|
|
881
881
|
parts.push(formatQuotaWindow(ctx, "7d", u.sevenDay.percent, u.sevenDay.resetHours, "h", "round"));
|
|
882
882
|
}
|
|
883
883
|
if (u.monthly) {
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
// "1% used"; OpenCode's endpoint
|
|
884
|
+
// Monthly-subscription providers only (the normalizer gates the class).
|
|
885
|
+
// Cursor and QwenCloud floor used percents upstream (Cursor's dashboard
|
|
886
|
+
// shows 1.88 → "1% used"; OpenCode's endpoint emits floored integers).
|
|
887
887
|
parts.push(formatQuotaWindow(ctx, "mo", u.monthly.percent, u.monthly.resetHours, "h", "floor"));
|
|
888
888
|
}
|
|
889
889
|
if (u.resetCredits) {
|
|
@@ -42,7 +42,7 @@ export type TerminalId =
|
|
|
42
42
|
| "base"
|
|
43
43
|
| "trueColor";
|
|
44
44
|
|
|
45
|
-
const CMUX_NOTIFICATION_TITLE = "
|
|
45
|
+
const CMUX_NOTIFICATION_TITLE = "omp";
|
|
46
46
|
const CMUX_SURFACE_ID_PATTERN = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu;
|
|
47
47
|
|
|
48
48
|
/** Title and body for an out-of-band multiplexer notification (cmux, Herdr). */
|
|
@@ -678,11 +678,20 @@ const KNOWN_TERMINALS = Object.freeze({
|
|
|
678
678
|
// OSC 66 text sizing are unverified, so those stay on conservative defaults;
|
|
679
679
|
// synchronized output is left to the runtime DECRQM probe.
|
|
680
680
|
otty: new TerminalInfo("otty", ImageProtocol.Kitty, true, true, NotifyProtocol.Osc99),
|
|
681
|
-
// rio
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
|
|
681
|
+
// rio publishes its supported escape sequences at
|
|
682
|
+
// rioterm.com/docs/escape-sequence-support: OSC 8 hyperlinks are opened via
|
|
683
|
+
// the hints system (Alt+click on Windows/Linux) — live-verified on
|
|
684
|
+
// rio 0.5.28/Win11 26100 where an OSC 8 span Alt+click opened the target
|
|
685
|
+
// URL in the browser, and an OSC 52 clipboard query round-tripped. Kitty
|
|
686
|
+
// graphics direct placement plus U=1 Unicode placeholders verified by the
|
|
687
|
+
// reporter (#12205). Notifications stay on BEL: rio parses OSC 9/777 into
|
|
688
|
+
// its own notifier, but its Windows path raises an unpackaged WinRT toast
|
|
689
|
+
// with AppId "Rio" that Windows silently drops without a registered AUMID
|
|
690
|
+
// shortcut — observed as a no-op live — so flipping it would only remove
|
|
691
|
+
// the D-Bus fallback for Linux rio users. DECCARA, screen-to-scrollback,
|
|
692
|
+
// OSC 99, and OSC 66 text sizing are outside rio's supported set and keep
|
|
693
|
+
// the conservative defaults.
|
|
694
|
+
rio: new TerminalInfo("rio", ImageProtocol.Kitty, true, true),
|
|
686
695
|
});
|
|
687
696
|
|
|
688
697
|
/** Resolve terminal identity from environment markers used by common emulators. */
|
|
@@ -1424,7 +1433,7 @@ function notificationToLine(n: TerminalNotification): string {
|
|
|
1424
1433
|
// C0/C1 control characters that are unsafe inside an OSC payload (must base64).
|
|
1425
1434
|
const OSC99_UNSAFE = /[\x00-\x1f\x7f\x80-\x9f]/u;
|
|
1426
1435
|
const OSC99_MAX_PAYLOAD_BYTES = 2048;
|
|
1427
|
-
const OSC99_APP_NAME = "
|
|
1436
|
+
const OSC99_APP_NAME = "omp";
|
|
1428
1437
|
let nextOsc99NotificationId = 1;
|
|
1429
1438
|
|
|
1430
1439
|
function base64Utf8(value: string): string {
|
package/src/terminal.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "./terminal-capabilities";
|
|
26
26
|
import { isInsideTmux, wrapTmuxPassthrough } from "./tmux";
|
|
27
27
|
import { setHangulCompatibilityJamoWidth } from "./utils";
|
|
28
|
+
import { translateWindowsAltGrSequence } from "./windows-altgr";
|
|
28
29
|
|
|
29
30
|
const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
|
|
30
31
|
const TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1b]9;4;3\x07";
|
|
@@ -1463,7 +1464,13 @@ export class ProcessTerminal implements Terminal {
|
|
|
1463
1464
|
return;
|
|
1464
1465
|
}
|
|
1465
1466
|
if (this.#inputHandler) {
|
|
1466
|
-
|
|
1467
|
+
// Windows console hosts drop AltGr text under kitty (AltGr+F → `CSI 102;3u`);
|
|
1468
|
+
// recover it from the active layout before any keybinding sees an Alt chord.
|
|
1469
|
+
const altGrText =
|
|
1470
|
+
this.#kittyProtocolActive && this.#conpty && process.platform === "win32"
|
|
1471
|
+
? translateWindowsAltGrSequence(sequence)
|
|
1472
|
+
: undefined;
|
|
1473
|
+
this.#inputHandler(altGrText ?? sequence);
|
|
1467
1474
|
}
|
|
1468
1475
|
});
|
|
1469
1476
|
|
|
@@ -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,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/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
|
}
|