@oh-my-pi/pi-wire 18.16.0 → 18.17.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 +7 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/shell-trace.d.ts +173 -0
- package/dist/types/shell-viz.d.ts +114 -0
- package/dist/types/tsp.d.ts +65 -5
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/shell-trace.ts +448 -0
- package/src/shell-viz.ts +109 -0
- package/src/tsp.ts +65 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.17.0] - 2026-11-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added shell trace visualization: `ShellTrace` model and TSP `shell` kind for shell runs, node state, and layout.
|
|
10
|
+
- Code views gain score-based line shading, labeled collapsible folds with notes, tones, and hover cards, and an overview strip.
|
|
11
|
+
|
|
5
12
|
## [18.16.0] - 2026-11-02
|
|
6
13
|
|
|
7
14
|
### Added
|
package/dist/types/index.d.ts
CHANGED
|
@@ -474,5 +474,7 @@ export type RelayControlToGuest = {
|
|
|
474
474
|
t: "room-closed";
|
|
475
475
|
};
|
|
476
476
|
export type RelayControlMessage = RelayControlToHost | RelayControlToGuest;
|
|
477
|
+
export * from "./shell-trace.js";
|
|
478
|
+
export * from "./shell-viz.js";
|
|
477
479
|
export * from "./stream.js";
|
|
478
480
|
export * from "./tsp.js";
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ShellTrace`: the live model of one embedded-shell run, produced by pi-shell's
|
|
3
|
+
* `RunTrace` (`crates/pi-shell/src/bash/trace.rs`), polled over napi, carried in
|
|
4
|
+
* the bash tool's `details.trace`, and forwarded verbatim to Tern as the `shell`
|
|
5
|
+
* node's props. Every offset is a BYTE offset; UIs convert bytes to characters
|
|
6
|
+
* when the script is not ASCII. Fields at their default are omitted.
|
|
7
|
+
*/
|
|
8
|
+
/** NodeKind of the engine's AST, snake_case. `assign` and `word` are not statements. */
|
|
9
|
+
export type ShellTraceNodeKind = "and_or" | "pipeline" | "simple" | "brace" | "subshell" | "if" | "while" | "until" | "for" | "arith_for" | "select" | "case" | "arith" | "cond" | "func_def" | "coproc" | "assign" | "word";
|
|
10
|
+
/** How the engine resolved a simple command's name. */
|
|
11
|
+
export type ShellTraceResolved = "function" | "intrinsic" | "tool" | "native" | "device" | "self_shell" | "external" | "not_found" | "none";
|
|
12
|
+
/** Derived at snapshot time (contract §3); always present. */
|
|
13
|
+
export type ShellTraceState = "pending" | "running" | "ok" | "fail" | "skipped";
|
|
14
|
+
export interface ShellTraceRedir {
|
|
15
|
+
fd: number;
|
|
16
|
+
op: string;
|
|
17
|
+
target: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ShellTraceDiag {
|
|
20
|
+
code: string;
|
|
21
|
+
message: string;
|
|
22
|
+
}
|
|
23
|
+
/** One AST node of a unit; the unit's `nodes` index is the AstNodeId. */
|
|
24
|
+
export interface ShellTraceNode {
|
|
25
|
+
k: ShellTraceNodeKind;
|
|
26
|
+
/** Byte span relative to the unit source. */
|
|
27
|
+
lo: number;
|
|
28
|
+
hi: number;
|
|
29
|
+
/** Parent node id; absent for the root. */
|
|
30
|
+
p?: number;
|
|
31
|
+
state: ShellTraceState;
|
|
32
|
+
/** CmdStart count. */
|
|
33
|
+
runs?: number;
|
|
34
|
+
/** CmdStart − CmdEnd. */
|
|
35
|
+
live?: number;
|
|
36
|
+
/** Status of the last CmdEnd. */
|
|
37
|
+
st?: number;
|
|
38
|
+
/** CmdEnds with a non-zero status. */
|
|
39
|
+
fails?: number;
|
|
40
|
+
/** Sum of `dur_ns` over the ended runs. */
|
|
41
|
+
ns?: number;
|
|
42
|
+
/** `t_ns` of the last CmdStart, relative to RunStart. */
|
|
43
|
+
at?: number;
|
|
44
|
+
/** Last CmdExpanded argv (each arg ≤ 256 bytes, ≤ 64 args). */
|
|
45
|
+
argv?: string[];
|
|
46
|
+
/** The full argv length when `argv` was cut. */
|
|
47
|
+
argc?: number;
|
|
48
|
+
/** Last CmdExpanded assigns. */
|
|
49
|
+
env?: [string, string][];
|
|
50
|
+
res?: ShellTraceResolved;
|
|
51
|
+
redirs?: ShellTraceRedir[];
|
|
52
|
+
/** Pipestatus of the last CmdEnd. */
|
|
53
|
+
pipe?: number[];
|
|
54
|
+
/** Last IterStart n. */
|
|
55
|
+
iter?: number;
|
|
56
|
+
/** LoopEnd iterations. */
|
|
57
|
+
iters?: number;
|
|
58
|
+
/** Last Branch decision. */
|
|
59
|
+
taken?: boolean;
|
|
60
|
+
/** A Background event named this node. */
|
|
61
|
+
bg?: boolean;
|
|
62
|
+
/** Last Spawn pid. */
|
|
63
|
+
pid?: number;
|
|
64
|
+
/** Actual last command completion timestamp and trace revision. */
|
|
65
|
+
end_ns?: number;
|
|
66
|
+
end_seq?: number;
|
|
67
|
+
}
|
|
68
|
+
/** One compiled unit (a top-level command, or an eval/function body). */
|
|
69
|
+
export interface ShellTraceUnit {
|
|
70
|
+
/** Byte offset of the unit inside `script`; absent when the unit is not part of the script. */
|
|
71
|
+
at?: number;
|
|
72
|
+
/** 1-based script line. */
|
|
73
|
+
line: number;
|
|
74
|
+
/** Exact UTF-8 source byte length, including heredocs; absent in older traces. */
|
|
75
|
+
bytes?: number;
|
|
76
|
+
/** The unit's source; present only for unlocated units. */
|
|
77
|
+
source?: string;
|
|
78
|
+
/** Opaque canonical native syntax archive. Only pi-shell may interpret it. */
|
|
79
|
+
syntax?: unknown;
|
|
80
|
+
/** Compiler-known wrapper bytes removed from this unit's displayed source. */
|
|
81
|
+
projection?: number;
|
|
82
|
+
/** Script a callout (`timeout`, `git`, `xargs`, …) ran for its operands; its calling command stands for it. */
|
|
83
|
+
callout?: boolean;
|
|
84
|
+
nodes: ShellTraceNode[];
|
|
85
|
+
}
|
|
86
|
+
/** Exact output/execution identity; repeated visits to one node have distinct seq values. */
|
|
87
|
+
export interface ShellTraceCommandTag {
|
|
88
|
+
/** Actual engine field: descendant vprocs without a root run queue use 0.
|
|
89
|
+
* Do not reject those tags; capture generation and explicit fork events
|
|
90
|
+
* establish run ownership, while vpid/seq distinguish the producer. */
|
|
91
|
+
run: number;
|
|
92
|
+
vpid: number;
|
|
93
|
+
seq: number;
|
|
94
|
+
}
|
|
95
|
+
/** Proven byte fragment of a decoded scalar spanning different producers.
|
|
96
|
+
* The containing Output has no single primary producer; these at-most-four
|
|
97
|
+
* bytes retain the exact contributing executions without guessing ownership. */
|
|
98
|
+
export interface ShellTraceOutputContributor {
|
|
99
|
+
t_ns: number;
|
|
100
|
+
bytes: number[];
|
|
101
|
+
command?: ShellTraceCommandTag;
|
|
102
|
+
vpid?: number;
|
|
103
|
+
u?: number;
|
|
104
|
+
n?: number;
|
|
105
|
+
}
|
|
106
|
+
/** Bounded replay journal entry. Missing u/n explicitly means unknown attribution. */
|
|
107
|
+
export interface ShellTraceHistoryEvent {
|
|
108
|
+
/** Trace revision, including coalesced output growth. */
|
|
109
|
+
seq: number;
|
|
110
|
+
/** Measured monotonic engine/host nanoseconds since RunStart, like node.at. */
|
|
111
|
+
t_ns: number;
|
|
112
|
+
/** Original engine event sequence; absent for Output callbacks. */
|
|
113
|
+
event_seq?: number;
|
|
114
|
+
/** Zero-based actual minimizer segment submission; lifecycle entries remain real engine events. */
|
|
115
|
+
segment?: number;
|
|
116
|
+
/** The last real root RunEnd finalized the entire logical segmented call. */
|
|
117
|
+
logicalEnd?: boolean;
|
|
118
|
+
/** Actual host outcome after output/close draining, without replacing engine status. */
|
|
119
|
+
logicalStatus?: number;
|
|
120
|
+
/** Actual emitting vproc, present on semantic entries even without a command tag. */
|
|
121
|
+
vpid?: number;
|
|
122
|
+
kind: "CmdStart" | "CmdExpanded" | "CmdEnd" | "Pipeline" | "Branch" | "IterStart" | "LoopEnd" | "Background" | "FuncEnter" | "FuncExit" | "Assign" | "RunStart" | "RunEnd" | "Output" | "Diag" | "Exit" | "ErrExit" | "Spawn";
|
|
123
|
+
u?: number;
|
|
124
|
+
n?: number;
|
|
125
|
+
command?: ShellTraceCommandTag;
|
|
126
|
+
status?: number;
|
|
127
|
+
dur_ns?: number;
|
|
128
|
+
pipestatus?: number[];
|
|
129
|
+
argv?: string[];
|
|
130
|
+
assigns?: [string, string][];
|
|
131
|
+
resolved?: ShellTraceResolved;
|
|
132
|
+
redirs?: ShellTraceRedir[];
|
|
133
|
+
name?: string;
|
|
134
|
+
value?: string;
|
|
135
|
+
full_len?: number;
|
|
136
|
+
scope?: string;
|
|
137
|
+
iteration?: number;
|
|
138
|
+
iterations?: number;
|
|
139
|
+
taken?: boolean;
|
|
140
|
+
construct?: string;
|
|
141
|
+
text?: string;
|
|
142
|
+
contributors?: ShellTraceOutputContributor[];
|
|
143
|
+
/** Final visible capture descriptor; merged stderr uses stdout's descriptor. */
|
|
144
|
+
fd?: number;
|
|
145
|
+
pid?: number;
|
|
146
|
+
}
|
|
147
|
+
export interface ShellTrace {
|
|
148
|
+
v: 1;
|
|
149
|
+
/** Semantic/output revision, including assignments/coalesced bytes; monotonic poll key. */
|
|
150
|
+
seq: number;
|
|
151
|
+
/** Root RunEnd status; absent while running. */
|
|
152
|
+
status?: number;
|
|
153
|
+
/** Actual root vproc, even if history trimming removed its RunStart. */
|
|
154
|
+
rootVpid?: number;
|
|
155
|
+
/** Run clock (ns since RunStart) at the last folded event. */
|
|
156
|
+
t_ns: number;
|
|
157
|
+
/** The command text given at construction. */
|
|
158
|
+
script: string;
|
|
159
|
+
/** Compiled units, in announce order. */
|
|
160
|
+
units: ShellTraceUnit[];
|
|
161
|
+
/** Newest ≤20,000 semantic/output records, ≤2 MiB visible output and ≤8 MiB retained payload.
|
|
162
|
+
* Adjacent output from the same execution may coalesce; its seq changes
|
|
163
|
+
* when text grows. Consumers must replace the full journal on revision. */
|
|
164
|
+
history?: ShellTraceHistoryEvent[];
|
|
165
|
+
/** True when any replay record or output byte was evicted. */
|
|
166
|
+
historyTruncated?: boolean;
|
|
167
|
+
/** The last ≤ 8 diagnostics. */
|
|
168
|
+
diags?: ShellTraceDiag[];
|
|
169
|
+
}
|
|
170
|
+
/** Validate an untrusted persisted/native trace, including nested nodes and replay entries.
|
|
171
|
+
* Callers should memoize by input reference; validating a journal is linear in its retained size.
|
|
172
|
+
* This guard never caches mutable input objects as trusted. */
|
|
173
|
+
export declare function isShellTrace(value: unknown): value is ShellTrace;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ShellTraceState } from "./shell-trace.js";
|
|
2
|
+
/** Supported shell presentation modes. */
|
|
3
|
+
export type ShellVizView = "raw" | "pseudo";
|
|
4
|
+
/** Independently toggleable, non-executing presentation transformations. */
|
|
5
|
+
export type ShellVizLayer = "color" | "inline" | "idioms" | "effects" | "unwrap";
|
|
6
|
+
/** Every layer is explicit so persisted preferences have deterministic meaning. */
|
|
7
|
+
export interface ShellVizLayers {
|
|
8
|
+
color: boolean;
|
|
9
|
+
inline: boolean;
|
|
10
|
+
idioms: boolean;
|
|
11
|
+
effects: boolean;
|
|
12
|
+
unwrap: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Per-tool-call preferences. Node identifiers are opaque native `unit:source:node` keys. */
|
|
15
|
+
export interface ShellVizPreferences {
|
|
16
|
+
view: ShellVizView;
|
|
17
|
+
layers: ShellVizLayers;
|
|
18
|
+
split: boolean;
|
|
19
|
+
/** Number of retained history records applied; absent means live. */
|
|
20
|
+
cursor?: number;
|
|
21
|
+
playing: boolean;
|
|
22
|
+
speed: number;
|
|
23
|
+
selectedNode?: string;
|
|
24
|
+
expandedNodes: string[];
|
|
25
|
+
}
|
|
26
|
+
/** Semantic text. All ranges are UTF-16 [lo, hi) in the original outermost source. */
|
|
27
|
+
export interface ShellVizSegment {
|
|
28
|
+
text: string;
|
|
29
|
+
/** True only on an executing leaf span; dim other spans while presentation.running. */
|
|
30
|
+
active?: boolean;
|
|
31
|
+
/** Actual governing leaf outcome, including failures inside otherwise successful pipelines/chains. */
|
|
32
|
+
state?: ShellTraceState;
|
|
33
|
+
/** Actual last leaf CmdEnd clock/sequence; identity is executionNode (or node) + executionSeq. */
|
|
34
|
+
executedAtNs?: number;
|
|
35
|
+
executionSeq?: number;
|
|
36
|
+
/** Space-separated semantic tokens; structural chip/badge tokens survive Color off. */
|
|
37
|
+
style?: string;
|
|
38
|
+
/** Source-token identity used only for selection and source links. */
|
|
39
|
+
node?: string;
|
|
40
|
+
/** Actual command owning state/completion when this token has a different source identity. */
|
|
41
|
+
executionNode?: string;
|
|
42
|
+
range?: [number, number];
|
|
43
|
+
tip?: string;
|
|
44
|
+
/** Complete original text revealed by inspection or expansion. */
|
|
45
|
+
expanded?: string;
|
|
46
|
+
/** Embedded language, with semantic tokenization already performed. */
|
|
47
|
+
language?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Heuristic effect annotation, never a guarantee of safety or completeness. */
|
|
50
|
+
export interface ShellVizEffect {
|
|
51
|
+
kind: "network" | "write" | "destructive" | "secret" | "privilege" | "background";
|
|
52
|
+
glyph: string;
|
|
53
|
+
label: string;
|
|
54
|
+
why: string;
|
|
55
|
+
}
|
|
56
|
+
/** One physical Raw/Pseudo presentation row; structural line breaks are separate rows, not terminal LF text. */
|
|
57
|
+
export interface ShellVizRow {
|
|
58
|
+
id: string;
|
|
59
|
+
depth: number;
|
|
60
|
+
node?: string;
|
|
61
|
+
range?: [number, number];
|
|
62
|
+
/** This row contains an active executable leaf, not merely an enclosing scope. */
|
|
63
|
+
active?: boolean;
|
|
64
|
+
role?: "stmt" | "head" | "mid" | "tail" | "raw" | "document";
|
|
65
|
+
state?: ShellTraceState;
|
|
66
|
+
segments: ShellVizSegment[];
|
|
67
|
+
effects: ShellVizEffect[];
|
|
68
|
+
/** Actual last leaf completion, never copied onto enclosing scope/scaffold rows. */
|
|
69
|
+
executedAtNs?: number;
|
|
70
|
+
executionSeq?: number;
|
|
71
|
+
/** Human-readable runtime metadata, not a machine-readable protocol. */
|
|
72
|
+
meta?: string;
|
|
73
|
+
detail?: string;
|
|
74
|
+
/** Latest real variable values whose last setter is represented by this row; never a detached watch strip. */
|
|
75
|
+
annotations?: ShellVizWatch[];
|
|
76
|
+
}
|
|
77
|
+
/** Latest observed setter of one scoped variable, never a statically guessed runtime value. */
|
|
78
|
+
export interface ShellVizWatch {
|
|
79
|
+
name: string;
|
|
80
|
+
value: string;
|
|
81
|
+
fullLength?: number;
|
|
82
|
+
scope?: string;
|
|
83
|
+
/** Proven scope/process identity; unknown binding identity is not replaced with a guessed function frame. */
|
|
84
|
+
scopeId?: string;
|
|
85
|
+
/** Owning executable/header AST node, promoted from assignment/word event ids. Absent means unlocated. */
|
|
86
|
+
node?: string;
|
|
87
|
+
/** Actual setter span in original outer-script UTF-16 coordinates; absent for unlocated dynamic source. */
|
|
88
|
+
range?: [number, number];
|
|
89
|
+
/** Retained journal seq of the actual last setter, so replay moves or removes the annotation. */
|
|
90
|
+
setterSeq?: number;
|
|
91
|
+
}
|
|
92
|
+
/** Browser-independent presentation shared by ANSI and native GUI renderers. */
|
|
93
|
+
export interface ShellVizPresentation {
|
|
94
|
+
preferences: ShellVizPreferences;
|
|
95
|
+
rows: ShellVizRow[];
|
|
96
|
+
/** Raw counterpart for Pseudo split; empty when that pane is not requested. */
|
|
97
|
+
original: ShellVizRow[];
|
|
98
|
+
/** Actual captured stdout/stderr through the replay cursor, in event order. */
|
|
99
|
+
output: string;
|
|
100
|
+
/** At least one actual executable leaf is running; dim all inactive content. */
|
|
101
|
+
running?: boolean;
|
|
102
|
+
/** Native run has not ended, even when currently between executable leaves. */
|
|
103
|
+
executing: boolean;
|
|
104
|
+
/** Canonical latest observed values; consumers render row.annotations, not a detached strip. */
|
|
105
|
+
watches: ShellVizWatch[];
|
|
106
|
+
timeline: {
|
|
107
|
+
length: number;
|
|
108
|
+
cursor: number;
|
|
109
|
+
elapsedNs: number;
|
|
110
|
+
durationNs: number;
|
|
111
|
+
trimmed?: boolean;
|
|
112
|
+
};
|
|
113
|
+
message?: string;
|
|
114
|
+
}
|
package/dist/types/tsp.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* Unknown fields and verbs are ignored in both directions, so every addition
|
|
18
18
|
* here is optional.
|
|
19
19
|
*/
|
|
20
|
+
import type { ShellVizPresentation } from "./shell-viz.js";
|
|
20
21
|
/** Protocol version this build speaks. */
|
|
21
22
|
export declare const TSP_VERSION = 1;
|
|
22
23
|
/** APC identifier: every TSP message body starts with `tsp;`. */
|
|
@@ -28,7 +29,7 @@ export declare const TSP_DEFAULT_CREDITS = 2;
|
|
|
28
29
|
/** One-letter message verb. */
|
|
29
30
|
export type TspVerb = "q" | "o" | "f" | "b" | "x" | "r" | "e";
|
|
30
31
|
/** Every component kind in the v1 vocabulary. */
|
|
31
|
-
export declare const TSP_KINDS: readonly ["col", "row", "card", "section", "rule", "spacer", "text", "md", "code", "diff", "ansi", "math", "image", "media", "kv", "table", "tree", "json", "badge", "kbd", "icon", "spinner", "shimmer", "elapsed", "progress", "rate", "list", "item", "tabs", "editor", "input", "button", "status", "seg", "overlay", "toast", "rows", "picker", "prefs", "tool", "checklist", "agent", "chart", "meter", "effort"];
|
|
32
|
+
export declare const TSP_KINDS: readonly ["col", "row", "card", "section", "rule", "spacer", "text", "md", "code", "diff", "ansi", "math", "image", "media", "kv", "table", "tree", "json", "badge", "kbd", "icon", "spinner", "shimmer", "elapsed", "progress", "rate", "list", "item", "tabs", "editor", "input", "button", "status", "seg", "overlay", "toast", "rows", "picker", "prefs", "tool", "checklist", "agent", "chart", "meter", "effort", "shell"];
|
|
32
33
|
export type TspKind = (typeof TSP_KINDS)[number];
|
|
33
34
|
/** Semantic colour of a node's chrome. */
|
|
34
35
|
export type TspTone = "neutral" | "accent" | "info" | "success" | "warning" | "error" | "pending" | "muted" | "user";
|
|
@@ -170,6 +171,33 @@ export interface TspMarkdownProps {
|
|
|
170
171
|
/** Literal runs drawn as these spans wherever they occur in prose (a sent prompt's chip tokens). */
|
|
171
172
|
marks?: readonly TspSpan[];
|
|
172
173
|
}
|
|
174
|
+
/** One marked line of a {@link TspCodeProps.marks}. */
|
|
175
|
+
export interface TspCodeMark {
|
|
176
|
+
line: number;
|
|
177
|
+
tone?: TspTone;
|
|
178
|
+
heat?: number;
|
|
179
|
+
/**
|
|
180
|
+
* Scores in [0, 1] washing the row on the `heat` ramp in place of `heat`'s wash: one flat, several a gradient across
|
|
181
|
+
* the row, an evenly spaced stop per score from left to right. `heat` still sizes the line's overview bar.
|
|
182
|
+
*/
|
|
183
|
+
wash?: readonly number[];
|
|
184
|
+
}
|
|
185
|
+
/** A run of lines a {@link TspCodeProps} draws as one row until it is opened. */
|
|
186
|
+
export interface TspCodeFold {
|
|
187
|
+
/** First and last folded line, in the gutter's numbering. */
|
|
188
|
+
from: number;
|
|
189
|
+
to: number;
|
|
190
|
+
/** The folded row's text, in the code's face (default `… [lines {from}-{to}]`, `… [line {from}]` for one line). */
|
|
191
|
+
label?: string;
|
|
192
|
+
/** What the fold hides, after a sparkline of its lines' mark `heat`. */
|
|
193
|
+
note?: TspText;
|
|
194
|
+
/** `error` tints the row; any tone colors the note. */
|
|
195
|
+
tone?: TspTone;
|
|
196
|
+
/** Drawn open; a click on the folded row (or on an open fold's head) toggles it in place. */
|
|
197
|
+
open?: boolean;
|
|
198
|
+
/** A card shown while the pointer rests on the folded row, as the common {@link TspCommonProps.hovercard}. */
|
|
199
|
+
hovercard?: TspPropNode;
|
|
200
|
+
}
|
|
173
201
|
export interface TspCodeProps {
|
|
174
202
|
text?: string;
|
|
175
203
|
/** The language of text that is no file's (`bash`, `json`); wins over `path`. */
|
|
@@ -186,13 +214,23 @@ export interface TspCodeProps {
|
|
|
186
214
|
*/
|
|
187
215
|
nums?: readonly number[];
|
|
188
216
|
numbers?: boolean;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Marked lines (`line` is the drawn line number). `heat` in [0, 1] washes the row in a heat ramp, `wash` runs that
|
|
219
|
+
* ramp across it; `tone` draws a bar, and any tone but `accent` also tints an unwashed row. A mark without `heat`
|
|
220
|
+
* or `wash` has tone `accent` by default; one with only those draws no bar.
|
|
221
|
+
*/
|
|
222
|
+
marks?: readonly TspCodeMark[];
|
|
193
223
|
wrap?: boolean;
|
|
194
224
|
/** Clamp to the first `lines` drawn lines under a "N more lines" button; gives way inside an open disclosure. */
|
|
195
225
|
preview?: TspPreview;
|
|
226
|
+
/** Runs of lines drawn folded: sorted, not overlapping; marks inside apply once a fold is open. */
|
|
227
|
+
folds?: readonly TspCodeFold[];
|
|
228
|
+
/**
|
|
229
|
+
* An overview strip above the lines: one bar per line (its height the mark's `heat`, its color the mark's tone),
|
|
230
|
+
* a band under it over the lines outside folds, and a bracket over the lines in view. A click or drag on it
|
|
231
|
+
* scrolls the code there.
|
|
232
|
+
*/
|
|
233
|
+
overview?: boolean;
|
|
196
234
|
}
|
|
197
235
|
export interface TspDiffHunk {
|
|
198
236
|
oldStart: number;
|
|
@@ -930,6 +968,27 @@ export interface TspChartProps {
|
|
|
930
968
|
/** Pixel-free size hint: cell size step for heatmaps, height in lines for bars/spark. */
|
|
931
969
|
size?: "sm" | "md" | "lg";
|
|
932
970
|
}
|
|
971
|
+
/** Engine-produced shell presentation; hosts render rows and forward controls, never syntax or history. */
|
|
972
|
+
export interface TspShellProps {
|
|
973
|
+
/** Canonical semantic presentation, shared with the ANSI inspector. */
|
|
974
|
+
presentation: ShellVizPresentation;
|
|
975
|
+
/** Show replay controls only in the explicitly opened inspector. */
|
|
976
|
+
inspector?: boolean;
|
|
977
|
+
/** Changed only by explicit seek/reset/live-mode transitions, clearing native highlight trails. */
|
|
978
|
+
replayEpoch?: number;
|
|
979
|
+
/** This mounted call received a real execution-start event, not a settled historical load. */
|
|
980
|
+
liveOrigin?: boolean;
|
|
981
|
+
/** Working directory shown ahead of the script when it differs from the project's. */
|
|
982
|
+
cwd?: string;
|
|
983
|
+
lang?: "bash" | "zsh";
|
|
984
|
+
/**
|
|
985
|
+
* Clamp the command to its first `lines` drawn lines under a "N more lines" button; gives way inside an open
|
|
986
|
+
* disclosure, and never applies in the inspector. The Raw/Pseudo switch and menu stay on the first line.
|
|
987
|
+
*/
|
|
988
|
+
preview?: {
|
|
989
|
+
lines: number;
|
|
990
|
+
};
|
|
991
|
+
}
|
|
933
992
|
/** Kind-specific props, indexed by kind. */
|
|
934
993
|
export interface TspPropsByKind {
|
|
935
994
|
col: TspColProps;
|
|
@@ -977,6 +1036,7 @@ export interface TspPropsByKind {
|
|
|
977
1036
|
chart: TspChartProps;
|
|
978
1037
|
meter: TspMeterProps;
|
|
979
1038
|
effort: TspEffortProps;
|
|
1039
|
+
shell: TspShellProps;
|
|
980
1040
|
}
|
|
981
1041
|
/**
|
|
982
1042
|
* Props of a node of kind `K`: its kind-specific props plus the common ones.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-wire",
|
|
4
|
-
"version": "18.
|
|
4
|
+
"version": "18.17.0",
|
|
5
5
|
"description": "Shared wire protocol types for omp packages",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"types": "./dist/types/index.d.ts",
|
|
28
28
|
"scripts": {
|
|
29
29
|
"check": "oxlint . && oxfmt --check --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts' && bun run check:types",
|
|
30
|
-
"check:types": "
|
|
30
|
+
"check:types": "bun check --no-script -p tsconfig.json",
|
|
31
31
|
"lint": "oxlint .",
|
|
32
32
|
"fix": "oxlint --fix --fix-suggestions . && bun run fmt",
|
|
33
33
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
package/src/index.ts
CHANGED
|
@@ -471,5 +471,7 @@ export type RelayControlToHost = { t: "peer-joined" | "peer-left"; peer: number
|
|
|
471
471
|
export type RelayControlToGuest = { t: "room-closed" };
|
|
472
472
|
export type RelayControlMessage = RelayControlToHost | RelayControlToGuest;
|
|
473
473
|
|
|
474
|
+
export * from "./shell-trace";
|
|
475
|
+
export * from "./shell-viz";
|
|
474
476
|
export * from "./stream";
|
|
475
477
|
export * from "./tsp";
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ShellTrace`: the live model of one embedded-shell run, produced by pi-shell's
|
|
3
|
+
* `RunTrace` (`crates/pi-shell/src/bash/trace.rs`), polled over napi, carried in
|
|
4
|
+
* the bash tool's `details.trace`, and forwarded verbatim to Tern as the `shell`
|
|
5
|
+
* node's props. Every offset is a BYTE offset; UIs convert bytes to characters
|
|
6
|
+
* when the script is not ASCII. Fields at their default are omitted.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** NodeKind of the engine's AST, snake_case. `assign` and `word` are not statements. */
|
|
10
|
+
export type ShellTraceNodeKind =
|
|
11
|
+
| "and_or"
|
|
12
|
+
| "pipeline"
|
|
13
|
+
| "simple"
|
|
14
|
+
| "brace"
|
|
15
|
+
| "subshell"
|
|
16
|
+
| "if"
|
|
17
|
+
| "while"
|
|
18
|
+
| "until"
|
|
19
|
+
| "for"
|
|
20
|
+
| "arith_for"
|
|
21
|
+
| "select"
|
|
22
|
+
| "case"
|
|
23
|
+
| "arith"
|
|
24
|
+
| "cond"
|
|
25
|
+
| "func_def"
|
|
26
|
+
| "coproc"
|
|
27
|
+
| "assign"
|
|
28
|
+
| "word";
|
|
29
|
+
|
|
30
|
+
/** How the engine resolved a simple command's name. */
|
|
31
|
+
export type ShellTraceResolved =
|
|
32
|
+
| "function"
|
|
33
|
+
| "intrinsic"
|
|
34
|
+
| "tool"
|
|
35
|
+
| "native"
|
|
36
|
+
| "device"
|
|
37
|
+
| "self_shell"
|
|
38
|
+
| "external"
|
|
39
|
+
| "not_found"
|
|
40
|
+
| "none";
|
|
41
|
+
|
|
42
|
+
/** Derived at snapshot time (contract §3); always present. */
|
|
43
|
+
export type ShellTraceState = "pending" | "running" | "ok" | "fail" | "skipped";
|
|
44
|
+
|
|
45
|
+
export interface ShellTraceRedir {
|
|
46
|
+
fd: number;
|
|
47
|
+
op: string;
|
|
48
|
+
target: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ShellTraceDiag {
|
|
52
|
+
code: string;
|
|
53
|
+
message: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One AST node of a unit; the unit's `nodes` index is the AstNodeId. */
|
|
57
|
+
export interface ShellTraceNode {
|
|
58
|
+
k: ShellTraceNodeKind;
|
|
59
|
+
/** Byte span relative to the unit source. */
|
|
60
|
+
lo: number;
|
|
61
|
+
hi: number;
|
|
62
|
+
/** Parent node id; absent for the root. */
|
|
63
|
+
p?: number;
|
|
64
|
+
state: ShellTraceState;
|
|
65
|
+
/** CmdStart count. */
|
|
66
|
+
runs?: number;
|
|
67
|
+
/** CmdStart − CmdEnd. */
|
|
68
|
+
live?: number;
|
|
69
|
+
/** Status of the last CmdEnd. */
|
|
70
|
+
st?: number;
|
|
71
|
+
/** CmdEnds with a non-zero status. */
|
|
72
|
+
fails?: number;
|
|
73
|
+
/** Sum of `dur_ns` over the ended runs. */
|
|
74
|
+
ns?: number;
|
|
75
|
+
/** `t_ns` of the last CmdStart, relative to RunStart. */
|
|
76
|
+
at?: number;
|
|
77
|
+
/** Last CmdExpanded argv (each arg ≤ 256 bytes, ≤ 64 args). */
|
|
78
|
+
argv?: string[];
|
|
79
|
+
/** The full argv length when `argv` was cut. */
|
|
80
|
+
argc?: number;
|
|
81
|
+
/** Last CmdExpanded assigns. */
|
|
82
|
+
env?: [string, string][];
|
|
83
|
+
res?: ShellTraceResolved;
|
|
84
|
+
redirs?: ShellTraceRedir[];
|
|
85
|
+
/** Pipestatus of the last CmdEnd. */
|
|
86
|
+
pipe?: number[];
|
|
87
|
+
/** Last IterStart n. */
|
|
88
|
+
iter?: number;
|
|
89
|
+
/** LoopEnd iterations. */
|
|
90
|
+
iters?: number;
|
|
91
|
+
/** Last Branch decision. */
|
|
92
|
+
taken?: boolean;
|
|
93
|
+
/** A Background event named this node. */
|
|
94
|
+
bg?: boolean;
|
|
95
|
+
/** Last Spawn pid. */
|
|
96
|
+
pid?: number;
|
|
97
|
+
/** Actual last command completion timestamp and trace revision. */
|
|
98
|
+
end_ns?: number;
|
|
99
|
+
end_seq?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** One compiled unit (a top-level command, or an eval/function body). */
|
|
103
|
+
export interface ShellTraceUnit {
|
|
104
|
+
/** Byte offset of the unit inside `script`; absent when the unit is not part of the script. */
|
|
105
|
+
at?: number;
|
|
106
|
+
/** 1-based script line. */
|
|
107
|
+
line: number;
|
|
108
|
+
/** Exact UTF-8 source byte length, including heredocs; absent in older traces. */
|
|
109
|
+
bytes?: number;
|
|
110
|
+
/** The unit's source; present only for unlocated units. */
|
|
111
|
+
source?: string;
|
|
112
|
+
/** Opaque canonical native syntax archive. Only pi-shell may interpret it. */
|
|
113
|
+
syntax?: unknown;
|
|
114
|
+
/** Compiler-known wrapper bytes removed from this unit's displayed source. */
|
|
115
|
+
projection?: number;
|
|
116
|
+
/** Script a callout (`timeout`, `git`, `xargs`, …) ran for its operands; its calling command stands for it. */
|
|
117
|
+
callout?: boolean;
|
|
118
|
+
nodes: ShellTraceNode[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Exact output/execution identity; repeated visits to one node have distinct seq values. */
|
|
122
|
+
export interface ShellTraceCommandTag {
|
|
123
|
+
/** Actual engine field: descendant vprocs without a root run queue use 0.
|
|
124
|
+
* Do not reject those tags; capture generation and explicit fork events
|
|
125
|
+
* establish run ownership, while vpid/seq distinguish the producer. */
|
|
126
|
+
run: number;
|
|
127
|
+
vpid: number;
|
|
128
|
+
seq: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Proven byte fragment of a decoded scalar spanning different producers.
|
|
132
|
+
* The containing Output has no single primary producer; these at-most-four
|
|
133
|
+
* bytes retain the exact contributing executions without guessing ownership. */
|
|
134
|
+
export interface ShellTraceOutputContributor {
|
|
135
|
+
t_ns: number;
|
|
136
|
+
bytes: number[];
|
|
137
|
+
command?: ShellTraceCommandTag;
|
|
138
|
+
vpid?: number;
|
|
139
|
+
u?: number;
|
|
140
|
+
n?: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Bounded replay journal entry. Missing u/n explicitly means unknown attribution. */
|
|
144
|
+
export interface ShellTraceHistoryEvent {
|
|
145
|
+
/** Trace revision, including coalesced output growth. */
|
|
146
|
+
seq: number;
|
|
147
|
+
/** Measured monotonic engine/host nanoseconds since RunStart, like node.at. */
|
|
148
|
+
t_ns: number;
|
|
149
|
+
/** Original engine event sequence; absent for Output callbacks. */
|
|
150
|
+
event_seq?: number;
|
|
151
|
+
/** Zero-based actual minimizer segment submission; lifecycle entries remain real engine events. */
|
|
152
|
+
segment?: number;
|
|
153
|
+
/** The last real root RunEnd finalized the entire logical segmented call. */
|
|
154
|
+
logicalEnd?: boolean;
|
|
155
|
+
/** Actual host outcome after output/close draining, without replacing engine status. */
|
|
156
|
+
logicalStatus?: number;
|
|
157
|
+
/** Actual emitting vproc, present on semantic entries even without a command tag. */
|
|
158
|
+
vpid?: number;
|
|
159
|
+
kind:
|
|
160
|
+
| "CmdStart"
|
|
161
|
+
| "CmdExpanded"
|
|
162
|
+
| "CmdEnd"
|
|
163
|
+
| "Pipeline"
|
|
164
|
+
| "Branch"
|
|
165
|
+
| "IterStart"
|
|
166
|
+
| "LoopEnd"
|
|
167
|
+
| "Background"
|
|
168
|
+
| "FuncEnter"
|
|
169
|
+
| "FuncExit"
|
|
170
|
+
| "Assign"
|
|
171
|
+
| "RunStart"
|
|
172
|
+
| "RunEnd"
|
|
173
|
+
| "Output"
|
|
174
|
+
| "Diag"
|
|
175
|
+
| "Exit"
|
|
176
|
+
| "ErrExit"
|
|
177
|
+
| "Spawn";
|
|
178
|
+
u?: number;
|
|
179
|
+
n?: number;
|
|
180
|
+
command?: ShellTraceCommandTag;
|
|
181
|
+
status?: number;
|
|
182
|
+
dur_ns?: number;
|
|
183
|
+
pipestatus?: number[];
|
|
184
|
+
argv?: string[];
|
|
185
|
+
assigns?: [string, string][];
|
|
186
|
+
resolved?: ShellTraceResolved;
|
|
187
|
+
redirs?: ShellTraceRedir[];
|
|
188
|
+
name?: string;
|
|
189
|
+
value?: string;
|
|
190
|
+
full_len?: number;
|
|
191
|
+
scope?: string;
|
|
192
|
+
iteration?: number;
|
|
193
|
+
iterations?: number;
|
|
194
|
+
taken?: boolean;
|
|
195
|
+
construct?: string;
|
|
196
|
+
text?: string;
|
|
197
|
+
contributors?: ShellTraceOutputContributor[];
|
|
198
|
+
/** Final visible capture descriptor; merged stderr uses stdout's descriptor. */
|
|
199
|
+
fd?: number;
|
|
200
|
+
pid?: number;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface ShellTrace {
|
|
204
|
+
v: 1;
|
|
205
|
+
/** Semantic/output revision, including assignments/coalesced bytes; monotonic poll key. */
|
|
206
|
+
seq: number;
|
|
207
|
+
/** Root RunEnd status; absent while running. */
|
|
208
|
+
status?: number;
|
|
209
|
+
/** Actual root vproc, even if history trimming removed its RunStart. */
|
|
210
|
+
rootVpid?: number;
|
|
211
|
+
/** Run clock (ns since RunStart) at the last folded event. */
|
|
212
|
+
t_ns: number;
|
|
213
|
+
/** The command text given at construction. */
|
|
214
|
+
script: string;
|
|
215
|
+
/** Compiled units, in announce order. */
|
|
216
|
+
units: ShellTraceUnit[];
|
|
217
|
+
/** Newest ≤20,000 semantic/output records, ≤2 MiB visible output and ≤8 MiB retained payload.
|
|
218
|
+
* Adjacent output from the same execution may coalesce; its seq changes
|
|
219
|
+
* when text grows. Consumers must replace the full journal on revision. */
|
|
220
|
+
history?: ShellTraceHistoryEvent[];
|
|
221
|
+
/** True when any replay record or output byte was evicted. */
|
|
222
|
+
historyTruncated?: boolean;
|
|
223
|
+
/** The last ≤ 8 diagnostics. */
|
|
224
|
+
diags?: ShellTraceDiag[];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
228
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function finite(value: unknown): value is number {
|
|
232
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function strings(value: unknown): value is string[] {
|
|
236
|
+
return Array.isArray(value) && value.every((item: unknown) => typeof item === "string");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function numbers(value: unknown): value is number[] {
|
|
240
|
+
return Array.isArray(value) && value.every(finite);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function pairs(value: unknown): value is [string, string][] {
|
|
244
|
+
return (
|
|
245
|
+
Array.isArray(value) &&
|
|
246
|
+
value.every(
|
|
247
|
+
(item: unknown) =>
|
|
248
|
+
Array.isArray(item) && item.length === 2 && typeof item[0] === "string" && typeof item[1] === "string",
|
|
249
|
+
)
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function optional(value: Record<string, unknown>, key: string, check: (item: unknown) => boolean): boolean {
|
|
254
|
+
return value[key] === undefined || check(value[key]);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function string(value: unknown): value is string {
|
|
258
|
+
return typeof value === "string";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function boolean(value: unknown): value is boolean {
|
|
262
|
+
return typeof value === "boolean";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function resolved(value: unknown): value is ShellTraceResolved {
|
|
266
|
+
return (
|
|
267
|
+
value === "function" ||
|
|
268
|
+
value === "intrinsic" ||
|
|
269
|
+
value === "tool" ||
|
|
270
|
+
value === "native" ||
|
|
271
|
+
value === "device" ||
|
|
272
|
+
value === "self_shell" ||
|
|
273
|
+
value === "external" ||
|
|
274
|
+
value === "not_found" ||
|
|
275
|
+
value === "none"
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function redirection(value: unknown): value is ShellTraceRedir {
|
|
280
|
+
return record(value) && finite(value.fd) && string(value.op) && string(value.target);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function redirections(value: unknown): value is ShellTraceRedir[] {
|
|
284
|
+
return Array.isArray(value) && value.every(redirection);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function traceNode(value: unknown): value is ShellTraceNode {
|
|
288
|
+
if (!record(value) || !finite(value.lo) || !finite(value.hi)) return false;
|
|
289
|
+
if (
|
|
290
|
+
!string(value.k) ||
|
|
291
|
+
![
|
|
292
|
+
"and_or",
|
|
293
|
+
"pipeline",
|
|
294
|
+
"simple",
|
|
295
|
+
"brace",
|
|
296
|
+
"subshell",
|
|
297
|
+
"if",
|
|
298
|
+
"while",
|
|
299
|
+
"until",
|
|
300
|
+
"for",
|
|
301
|
+
"arith_for",
|
|
302
|
+
"select",
|
|
303
|
+
"case",
|
|
304
|
+
"arith",
|
|
305
|
+
"cond",
|
|
306
|
+
"func_def",
|
|
307
|
+
"coproc",
|
|
308
|
+
"assign",
|
|
309
|
+
"word",
|
|
310
|
+
].includes(value.k)
|
|
311
|
+
)
|
|
312
|
+
return false;
|
|
313
|
+
if (
|
|
314
|
+
value.state !== "pending" &&
|
|
315
|
+
value.state !== "running" &&
|
|
316
|
+
value.state !== "ok" &&
|
|
317
|
+
value.state !== "fail" &&
|
|
318
|
+
value.state !== "skipped"
|
|
319
|
+
)
|
|
320
|
+
return false;
|
|
321
|
+
for (const key of ["p", "runs", "live", "st", "fails", "ns", "at", "argc", "iter", "iters", "pid"]) {
|
|
322
|
+
if (!optional(value, key, finite)) return false;
|
|
323
|
+
}
|
|
324
|
+
return (
|
|
325
|
+
optional(value, "argv", strings) &&
|
|
326
|
+
optional(value, "env", pairs) &&
|
|
327
|
+
optional(value, "res", resolved) &&
|
|
328
|
+
optional(value, "redirs", redirections) &&
|
|
329
|
+
optional(value, "pipe", numbers) &&
|
|
330
|
+
optional(value, "taken", boolean) &&
|
|
331
|
+
optional(value, "bg", boolean)
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function traceUnit(value: unknown): value is ShellTraceUnit {
|
|
336
|
+
return (
|
|
337
|
+
record(value) &&
|
|
338
|
+
finite(value.line) &&
|
|
339
|
+
optional(value, "at", finite) &&
|
|
340
|
+
optional(value, "bytes", finite) &&
|
|
341
|
+
optional(value, "source", string) &&
|
|
342
|
+
optional(value, "callout", boolean) &&
|
|
343
|
+
Array.isArray(value.nodes) &&
|
|
344
|
+
value.nodes.every(traceNode)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function commandTag(value: unknown): value is ShellTraceCommandTag {
|
|
349
|
+
return record(value) && finite(value.run) && finite(value.vpid) && finite(value.seq);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function outputContributor(value: unknown): value is ShellTraceOutputContributor {
|
|
353
|
+
return (
|
|
354
|
+
record(value) &&
|
|
355
|
+
finite(value.t_ns) &&
|
|
356
|
+
Array.isArray(value.bytes) &&
|
|
357
|
+
value.bytes.every((byte: unknown) => finite(byte) && Number.isInteger(byte) && byte >= 0 && byte <= 255) &&
|
|
358
|
+
optional(value, "u", finite) &&
|
|
359
|
+
optional(value, "n", finite) &&
|
|
360
|
+
optional(value, "vpid", finite) &&
|
|
361
|
+
optional(value, "command", commandTag)
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function historyEntry(value: unknown): value is ShellTraceHistoryEvent {
|
|
366
|
+
if (!record(value) || !finite(value.seq) || !finite(value.t_ns)) return false;
|
|
367
|
+
if (
|
|
368
|
+
!string(value.kind) ||
|
|
369
|
+
![
|
|
370
|
+
"CmdStart",
|
|
371
|
+
"CmdExpanded",
|
|
372
|
+
"CmdEnd",
|
|
373
|
+
"Pipeline",
|
|
374
|
+
"Branch",
|
|
375
|
+
"IterStart",
|
|
376
|
+
"LoopEnd",
|
|
377
|
+
"Background",
|
|
378
|
+
"FuncEnter",
|
|
379
|
+
"FuncExit",
|
|
380
|
+
"Assign",
|
|
381
|
+
"RunStart",
|
|
382
|
+
"RunEnd",
|
|
383
|
+
"Output",
|
|
384
|
+
"Diag",
|
|
385
|
+
"Exit",
|
|
386
|
+
"ErrExit",
|
|
387
|
+
"Spawn",
|
|
388
|
+
].includes(value.kind)
|
|
389
|
+
)
|
|
390
|
+
return false;
|
|
391
|
+
for (const key of [
|
|
392
|
+
"event_seq",
|
|
393
|
+
"segment",
|
|
394
|
+
"logicalStatus",
|
|
395
|
+
"vpid",
|
|
396
|
+
"u",
|
|
397
|
+
"n",
|
|
398
|
+
"status",
|
|
399
|
+
"dur_ns",
|
|
400
|
+
"full_len",
|
|
401
|
+
"iteration",
|
|
402
|
+
"iterations",
|
|
403
|
+
"fd",
|
|
404
|
+
"pid",
|
|
405
|
+
]) {
|
|
406
|
+
if (!optional(value, key, finite)) return false;
|
|
407
|
+
}
|
|
408
|
+
for (const key of ["name", "value", "scope", "construct", "text"]) {
|
|
409
|
+
if (!optional(value, key, string)) return false;
|
|
410
|
+
}
|
|
411
|
+
return (
|
|
412
|
+
optional(value, "argv", strings) &&
|
|
413
|
+
optional(value, "assigns", pairs) &&
|
|
414
|
+
optional(value, "redirs", redirections) &&
|
|
415
|
+
optional(value, "resolved", resolved) &&
|
|
416
|
+
optional(value, "pipestatus", numbers) &&
|
|
417
|
+
optional(value, "taken", boolean) &&
|
|
418
|
+
optional(value, "logicalEnd", boolean) &&
|
|
419
|
+
optional(value, "command", commandTag) &&
|
|
420
|
+
optional(value, "contributors", items => Array.isArray(items) && items.every(outputContributor)) &&
|
|
421
|
+
(value.kind !== "Output" || (string(value.text) && finite(value.fd))) &&
|
|
422
|
+
(value.kind !== "Assign" ||
|
|
423
|
+
(string(value.name) && string(value.value) && finite(value.full_len) && string(value.scope)))
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Validate an untrusted persisted/native trace, including nested nodes and replay entries.
|
|
428
|
+
* Callers should memoize by input reference; validating a journal is linear in its retained size.
|
|
429
|
+
* This guard never caches mutable input objects as trusted. */
|
|
430
|
+
export function isShellTrace(value: unknown): value is ShellTrace {
|
|
431
|
+
if (!record(value) || value.v !== 1 || !finite(value.seq) || !finite(value.t_ns) || !string(value.script))
|
|
432
|
+
return false;
|
|
433
|
+
return (
|
|
434
|
+
optional(value, "status", finite) &&
|
|
435
|
+
optional(value, "rootVpid", finite) &&
|
|
436
|
+
Array.isArray(value.units) &&
|
|
437
|
+
value.units.every(traceUnit) &&
|
|
438
|
+
optional(
|
|
439
|
+
value,
|
|
440
|
+
"diags",
|
|
441
|
+
items =>
|
|
442
|
+
Array.isArray(items) &&
|
|
443
|
+
items.every((item: unknown) => record(item) && string(item.code) && string(item.message)),
|
|
444
|
+
) &&
|
|
445
|
+
optional(value, "history", items => Array.isArray(items) && items.every(historyEntry)) &&
|
|
446
|
+
optional(value, "historyTruncated", boolean)
|
|
447
|
+
);
|
|
448
|
+
}
|
package/src/shell-viz.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ShellTraceState } from "./shell-trace";
|
|
2
|
+
|
|
3
|
+
/** Supported shell presentation modes. */
|
|
4
|
+
export type ShellVizView = "raw" | "pseudo";
|
|
5
|
+
/** Independently toggleable, non-executing presentation transformations. */
|
|
6
|
+
export type ShellVizLayer = "color" | "inline" | "idioms" | "effects" | "unwrap";
|
|
7
|
+
/** Every layer is explicit so persisted preferences have deterministic meaning. */
|
|
8
|
+
export interface ShellVizLayers {
|
|
9
|
+
color: boolean;
|
|
10
|
+
inline: boolean;
|
|
11
|
+
idioms: boolean;
|
|
12
|
+
effects: boolean;
|
|
13
|
+
unwrap: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Per-tool-call preferences. Node identifiers are opaque native `unit:source:node` keys. */
|
|
16
|
+
export interface ShellVizPreferences {
|
|
17
|
+
view: ShellVizView;
|
|
18
|
+
layers: ShellVizLayers;
|
|
19
|
+
split: boolean;
|
|
20
|
+
/** Number of retained history records applied; absent means live. */
|
|
21
|
+
cursor?: number;
|
|
22
|
+
playing: boolean;
|
|
23
|
+
speed: number;
|
|
24
|
+
selectedNode?: string;
|
|
25
|
+
expandedNodes: string[];
|
|
26
|
+
}
|
|
27
|
+
/** Semantic text. All ranges are UTF-16 [lo, hi) in the original outermost source. */
|
|
28
|
+
export interface ShellVizSegment {
|
|
29
|
+
text: string;
|
|
30
|
+
/** True only on an executing leaf span; dim other spans while presentation.running. */
|
|
31
|
+
active?: boolean;
|
|
32
|
+
/** Actual governing leaf outcome, including failures inside otherwise successful pipelines/chains. */
|
|
33
|
+
state?: ShellTraceState;
|
|
34
|
+
/** Actual last leaf CmdEnd clock/sequence; identity is executionNode (or node) + executionSeq. */
|
|
35
|
+
executedAtNs?: number;
|
|
36
|
+
executionSeq?: number;
|
|
37
|
+
/** Space-separated semantic tokens; structural chip/badge tokens survive Color off. */
|
|
38
|
+
style?: string;
|
|
39
|
+
/** Source-token identity used only for selection and source links. */
|
|
40
|
+
node?: string;
|
|
41
|
+
/** Actual command owning state/completion when this token has a different source identity. */
|
|
42
|
+
executionNode?: string;
|
|
43
|
+
range?: [number, number];
|
|
44
|
+
tip?: string;
|
|
45
|
+
/** Complete original text revealed by inspection or expansion. */
|
|
46
|
+
expanded?: string;
|
|
47
|
+
/** Embedded language, with semantic tokenization already performed. */
|
|
48
|
+
language?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Heuristic effect annotation, never a guarantee of safety or completeness. */
|
|
51
|
+
export interface ShellVizEffect {
|
|
52
|
+
kind: "network" | "write" | "destructive" | "secret" | "privilege" | "background";
|
|
53
|
+
glyph: string;
|
|
54
|
+
label: string;
|
|
55
|
+
why: string;
|
|
56
|
+
}
|
|
57
|
+
/** One physical Raw/Pseudo presentation row; structural line breaks are separate rows, not terminal LF text. */
|
|
58
|
+
export interface ShellVizRow {
|
|
59
|
+
id: string;
|
|
60
|
+
depth: number;
|
|
61
|
+
node?: string;
|
|
62
|
+
range?: [number, number];
|
|
63
|
+
/** This row contains an active executable leaf, not merely an enclosing scope. */
|
|
64
|
+
active?: boolean;
|
|
65
|
+
role?: "stmt" | "head" | "mid" | "tail" | "raw" | "document";
|
|
66
|
+
state?: ShellTraceState;
|
|
67
|
+
segments: ShellVizSegment[];
|
|
68
|
+
effects: ShellVizEffect[];
|
|
69
|
+
/** Actual last leaf completion, never copied onto enclosing scope/scaffold rows. */
|
|
70
|
+
executedAtNs?: number;
|
|
71
|
+
executionSeq?: number;
|
|
72
|
+
/** Human-readable runtime metadata, not a machine-readable protocol. */
|
|
73
|
+
meta?: string;
|
|
74
|
+
detail?: string;
|
|
75
|
+
/** Latest real variable values whose last setter is represented by this row; never a detached watch strip. */
|
|
76
|
+
annotations?: ShellVizWatch[];
|
|
77
|
+
}
|
|
78
|
+
/** Latest observed setter of one scoped variable, never a statically guessed runtime value. */
|
|
79
|
+
export interface ShellVizWatch {
|
|
80
|
+
name: string;
|
|
81
|
+
value: string;
|
|
82
|
+
fullLength?: number;
|
|
83
|
+
scope?: string;
|
|
84
|
+
/** Proven scope/process identity; unknown binding identity is not replaced with a guessed function frame. */
|
|
85
|
+
scopeId?: string;
|
|
86
|
+
/** Owning executable/header AST node, promoted from assignment/word event ids. Absent means unlocated. */
|
|
87
|
+
node?: string;
|
|
88
|
+
/** Actual setter span in original outer-script UTF-16 coordinates; absent for unlocated dynamic source. */
|
|
89
|
+
range?: [number, number];
|
|
90
|
+
/** Retained journal seq of the actual last setter, so replay moves or removes the annotation. */
|
|
91
|
+
setterSeq?: number;
|
|
92
|
+
}
|
|
93
|
+
/** Browser-independent presentation shared by ANSI and native GUI renderers. */
|
|
94
|
+
export interface ShellVizPresentation {
|
|
95
|
+
preferences: ShellVizPreferences;
|
|
96
|
+
rows: ShellVizRow[];
|
|
97
|
+
/** Raw counterpart for Pseudo split; empty when that pane is not requested. */
|
|
98
|
+
original: ShellVizRow[];
|
|
99
|
+
/** Actual captured stdout/stderr through the replay cursor, in event order. */
|
|
100
|
+
output: string;
|
|
101
|
+
/** At least one actual executable leaf is running; dim all inactive content. */
|
|
102
|
+
running?: boolean;
|
|
103
|
+
/** Native run has not ended, even when currently between executable leaves. */
|
|
104
|
+
executing: boolean;
|
|
105
|
+
/** Canonical latest observed values; consumers render row.annotations, not a detached strip. */
|
|
106
|
+
watches: ShellVizWatch[];
|
|
107
|
+
timeline: { length: number; cursor: number; elapsedNs: number; durationNs: number; trimmed?: boolean };
|
|
108
|
+
message?: string;
|
|
109
|
+
}
|
package/src/tsp.ts
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* here is optional.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import type { ShellVizPresentation } from "./shell-viz";
|
|
22
|
+
|
|
21
23
|
/** Protocol version this build speaks. */
|
|
22
24
|
export const TSP_VERSION = 1;
|
|
23
25
|
/** APC identifier: every TSP message body starts with `tsp;`. */
|
|
@@ -81,6 +83,7 @@ export const TSP_KINDS = [
|
|
|
81
83
|
"chart",
|
|
82
84
|
"meter",
|
|
83
85
|
"effort",
|
|
86
|
+
"shell",
|
|
84
87
|
] as const;
|
|
85
88
|
|
|
86
89
|
export type TspKind = (typeof TSP_KINDS)[number];
|
|
@@ -223,6 +226,33 @@ export interface TspMarkdownProps {
|
|
|
223
226
|
/** Literal runs drawn as these spans wherever they occur in prose (a sent prompt's chip tokens). */
|
|
224
227
|
marks?: readonly TspSpan[];
|
|
225
228
|
}
|
|
229
|
+
/** One marked line of a {@link TspCodeProps.marks}. */
|
|
230
|
+
export interface TspCodeMark {
|
|
231
|
+
line: number;
|
|
232
|
+
tone?: TspTone;
|
|
233
|
+
heat?: number;
|
|
234
|
+
/**
|
|
235
|
+
* Scores in [0, 1] washing the row on the `heat` ramp in place of `heat`'s wash: one flat, several a gradient across
|
|
236
|
+
* the row, an evenly spaced stop per score from left to right. `heat` still sizes the line's overview bar.
|
|
237
|
+
*/
|
|
238
|
+
wash?: readonly number[];
|
|
239
|
+
}
|
|
240
|
+
/** A run of lines a {@link TspCodeProps} draws as one row until it is opened. */
|
|
241
|
+
export interface TspCodeFold {
|
|
242
|
+
/** First and last folded line, in the gutter's numbering. */
|
|
243
|
+
from: number;
|
|
244
|
+
to: number;
|
|
245
|
+
/** The folded row's text, in the code's face (default `… [lines {from}-{to}]`, `… [line {from}]` for one line). */
|
|
246
|
+
label?: string;
|
|
247
|
+
/** What the fold hides, after a sparkline of its lines' mark `heat`. */
|
|
248
|
+
note?: TspText;
|
|
249
|
+
/** `error` tints the row; any tone colors the note. */
|
|
250
|
+
tone?: TspTone;
|
|
251
|
+
/** Drawn open; a click on the folded row (or on an open fold's head) toggles it in place. */
|
|
252
|
+
open?: boolean;
|
|
253
|
+
/** A card shown while the pointer rests on the folded row, as the common {@link TspCommonProps.hovercard}. */
|
|
254
|
+
hovercard?: TspPropNode;
|
|
255
|
+
}
|
|
226
256
|
export interface TspCodeProps {
|
|
227
257
|
text?: string;
|
|
228
258
|
/** The language of text that is no file's (`bash`, `json`); wins over `path`. */
|
|
@@ -239,10 +269,23 @@ export interface TspCodeProps {
|
|
|
239
269
|
*/
|
|
240
270
|
nums?: readonly number[];
|
|
241
271
|
numbers?: boolean;
|
|
242
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Marked lines (`line` is the drawn line number). `heat` in [0, 1] washes the row in a heat ramp, `wash` runs that
|
|
274
|
+
* ramp across it; `tone` draws a bar, and any tone but `accent` also tints an unwashed row. A mark without `heat`
|
|
275
|
+
* or `wash` has tone `accent` by default; one with only those draws no bar.
|
|
276
|
+
*/
|
|
277
|
+
marks?: readonly TspCodeMark[];
|
|
243
278
|
wrap?: boolean;
|
|
244
279
|
/** Clamp to the first `lines` drawn lines under a "N more lines" button; gives way inside an open disclosure. */
|
|
245
280
|
preview?: TspPreview;
|
|
281
|
+
/** Runs of lines drawn folded: sorted, not overlapping; marks inside apply once a fold is open. */
|
|
282
|
+
folds?: readonly TspCodeFold[];
|
|
283
|
+
/**
|
|
284
|
+
* An overview strip above the lines: one bar per line (its height the mark's `heat`, its color the mark's tone),
|
|
285
|
+
* a band under it over the lines outside folds, and a bracket over the lines in view. A click or drag on it
|
|
286
|
+
* scrolls the code there.
|
|
287
|
+
*/
|
|
288
|
+
overview?: boolean;
|
|
246
289
|
}
|
|
247
290
|
export interface TspDiffHunk {
|
|
248
291
|
oldStart: number;
|
|
@@ -896,6 +939,26 @@ export interface TspChartProps {
|
|
|
896
939
|
size?: "sm" | "md" | "lg";
|
|
897
940
|
}
|
|
898
941
|
|
|
942
|
+
/** Engine-produced shell presentation; hosts render rows and forward controls, never syntax or history. */
|
|
943
|
+
export interface TspShellProps {
|
|
944
|
+
/** Canonical semantic presentation, shared with the ANSI inspector. */
|
|
945
|
+
presentation: ShellVizPresentation;
|
|
946
|
+
/** Show replay controls only in the explicitly opened inspector. */
|
|
947
|
+
inspector?: boolean;
|
|
948
|
+
/** Changed only by explicit seek/reset/live-mode transitions, clearing native highlight trails. */
|
|
949
|
+
replayEpoch?: number;
|
|
950
|
+
/** This mounted call received a real execution-start event, not a settled historical load. */
|
|
951
|
+
liveOrigin?: boolean;
|
|
952
|
+
/** Working directory shown ahead of the script when it differs from the project's. */
|
|
953
|
+
cwd?: string;
|
|
954
|
+
lang?: "bash" | "zsh";
|
|
955
|
+
/**
|
|
956
|
+
* Clamp the command to its first `lines` drawn lines under a "N more lines" button; gives way inside an open
|
|
957
|
+
* disclosure, and never applies in the inspector. The Raw/Pseudo switch and menu stay on the first line.
|
|
958
|
+
*/
|
|
959
|
+
preview?: { lines: number };
|
|
960
|
+
}
|
|
961
|
+
|
|
899
962
|
/** Kind-specific props, indexed by kind. */
|
|
900
963
|
export interface TspPropsByKind {
|
|
901
964
|
col: TspColProps;
|
|
@@ -943,6 +1006,7 @@ export interface TspPropsByKind {
|
|
|
943
1006
|
chart: TspChartProps;
|
|
944
1007
|
meter: TspMeterProps;
|
|
945
1008
|
effort: TspEffortProps;
|
|
1009
|
+
shell: TspShellProps;
|
|
946
1010
|
}
|
|
947
1011
|
|
|
948
1012
|
/**
|