@nredd/pi-session-kit 0.7.0 → 0.9.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 +18 -0
- package/README.md +41 -8
- package/dist/pi-stats-export.mjs +22 -4
- package/docs/mon-dashboard.md +140 -0
- package/docs/stats-snapshots.md +15 -2
- package/package.json +10 -5
- package/src/config.ts +1 -1
- package/src/index.ts +4 -1
- package/src/mon/component.ts +604 -0
- package/src/mon/index.ts +105 -0
- package/src/mon/model.ts +197 -0
- package/src/mon/poll.ts +80 -0
- package/src/mon/source.ts +181 -0
- package/src/monitor/process.ts +2 -1
- package/src/monitor/validate.ts +1 -1
- package/src/stats/chart.ts +3 -1
- package/src/stats/cli.ts +24 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
- Make the portable stats CLI import-safe: importing `dist/pi-stats-export.mjs` no longer runs an operation; direct Node execution still runs it
|
|
6
|
+
|
|
7
|
+
- Replace `/mon`'s ambiguous non-free label with `total-free` (`T-F` compact); accept additive native App+Wired+Compressed and swap metrics without silently substituting free-page subtraction
|
|
8
|
+
- Accept native macOS interval CPU and footprint; add `f` to toggle the labelled byte column between `RSS` and `FOOT`, with failed measurements shown as `--`
|
|
9
|
+
- Explain RSS versus footprint, compressed pages, executable names (`pi` as `node`), strong native identity and unavailable-CPU fallback in `?`; native metrics require the subsequent published sampler dependency bump
|
|
10
|
+
|
|
11
|
+
- Raise the `monitor_start` `timeout_s` cap from 24h to 7 days (`604800`) so monitors can outlive long jobs
|
|
12
|
+
|
|
13
|
+
## 0.8.0
|
|
14
|
+
|
|
15
|
+
- Add `/mon`: a read-only TUI dashboard of host CPU history, non-free memory, load and sampled processes, on by default (`mon.enabled`)
|
|
16
|
+
- Depend on the exact published `@nredd/pi-resource-sampler@0.1.1` (executable names, never argv or process titles; Bun host CPU measured), locked as a nested registry install so tests never borrow the workspace copy; one sampler per open view, disposed (children reaped, no timers) on close, session shutdown or replacement
|
|
17
|
+
- Map warming/unavailable metrics to `--`, never zero; split warnings (budget-limited lists, unavailable host CPU) from muted platform hints (Linux kernel-thread skips, unknown names, macOS process CPU)
|
|
18
|
+
- Render unreadable names as `(unknown)` and mark macOS names that may be cut at 16 bytes with `…`
|
|
19
|
+
- Header shows collector state with the last successful collection's local time; every resolved sample counts as live, separate from host CPU `warming`/`unavailable`
|
|
20
|
+
- Side-by-side cards from 72 columns with a two-row CPU history at the observed monotonic interval; process table `PID CPU% RSS MEM% NAME` with a selection marker and overflow scrollbar
|
|
21
|
+
- Pack `docs/mon-dashboard.md`, which the README links
|
|
22
|
+
|
|
5
23
|
## 0.7.0
|
|
6
24
|
|
|
7
25
|
- Add a `/stats` **Machines** tab (Tab/Shift-Tab): each known machine's label and opaque UUID, local scan status, and remote producer `exportedAt` UTC instant plus age at opening
|
package/README.md
CHANGED
|
@@ -3,20 +3,27 @@
|
|
|
3
3
|
Published to npm as `@nredd/pi-session-kit` (public). From `0.4.0`, each version is cut by the monorepo `Publish` workflow from a signed `@nredd/pi-session-kit@<version>` tag, with npm provenance and a GitHub release carrying the tarball, checksums and manifest (`docs/releases.md`); earlier versions were published manually without provenance. `0.4.0` adds `createSessionKit(options)` (explicit `config`, `agentDir`, `monitorRoot`, injectable `clock` and `spawnMonitor`; the default export is unchanged) and fixes leaderless orphan-group sweeps at `ps` start-time resolution. `0.5.0` redesigns `/stats` around the full pane (pinned header, aligned stat cells, a Models table on wide panes) and gives every chart a clock- or calendar-aligned time axis, including the 5h and 24h hourly views.
|
|
4
4
|
Use the [monorepo development commands](https://github.com/nredd/pi-harness/blob/main/README.md). The Git install and standalone harness
|
|
5
5
|
references below describe the legacy deployment, not installation or candidate verification of
|
|
6
|
-
this package. `0.6.0` keeps passive monitor output only in the monitor ring buffer rather than adding it to the transcript or model context; `monitor_read` is the explicit output surface, and match/exit/timeout wakes are compact summaries with a cursor. `0.7.0` adds the `/stats` Machines tab with per-machine producer export age, separate from the viewer's last import, and the collapsible monitors widget.
|
|
6
|
+
this package. `0.6.0` keeps passive monitor output only in the monitor ring buffer rather than adding it to the transcript or model context; `monitor_read` is the explicit output surface, and match/exit/timeout wakes are compact summaries with a cursor. `0.7.0` adds the `/stats` Machines tab with per-machine producer export age, separate from the viewer's last import, and the collapsible monitors widget. `0.8.0` adds `/mon`.
|
|
7
7
|
|
|
8
8
|
## Standalone use
|
|
9
9
|
|
|
10
|
-
The packed tarball is self-contained: Pi host modules (`@earendil-works/pi-*`, `typebox`) are `*` peers supplied by Pi,
|
|
10
|
+
The packed tarball is self-contained: Pi host modules (`@earendil-works/pi-*`, `typebox`) are `*` peers supplied by Pi, and no sibling plugin is required. Its only runtime dependency is the exact published `@nredd/pi-resource-sampler@0.1.1` library (no Pi imports), which npm installs from the registry for `/mon`. Verified by installing the tarball alone and running it in exact stock Pi 1.3.0 (see the repository's `docs/package-contracts.md`). Optional fork integrations (subagents, Plan mode) are absent in that setup; the exercised behavior does not need them.
|
|
11
11
|
|
|
12
12
|
Session-scoped pi extensions for tmux users. No daemon, supervisor, core patch or cross-pane
|
|
13
13
|
registry. Each runtime owns its goal, loops and monitors; persisted goal state follows the active
|
|
14
14
|
session branch. Closing pi stops its work.
|
|
15
15
|
|
|
16
|
+
`/mon` opens a read-only, full-pane view of local CPU, memory, load and processes, sampled
|
|
17
|
+
every ~2s by the shared `@nredd/pi-resource-sampler` library only while it is open. Details and
|
|
18
|
+
limits (macOS native CPU/footprint require the upcoming sampler pin, executable names keep 16 bytes,
|
|
19
|
+
RSS excludes compressed pages, `f` toggles a labelled footprint column,
|
|
20
|
+
and memory labels state their measurement basis) are in
|
|
21
|
+
[docs/mon-dashboard.md](docs/mon-dashboard.md). It is not a second `/monitor` process runner.
|
|
22
|
+
|
|
16
23
|
## Install and config
|
|
17
24
|
|
|
18
25
|
```sh
|
|
19
|
-
pi install npm:@nredd/pi-session-kit@0.
|
|
26
|
+
pi install npm:@nredd/pi-session-kit@0.8.0
|
|
20
27
|
```
|
|
21
28
|
|
|
22
29
|
Optional `~/.pi/agent/pi-session-kit.json` (respects `PI_CODING_AGENT_DIR`):
|
|
@@ -29,7 +36,8 @@ Optional `~/.pi/agent/pi-session-kit.json` (respects `PI_CODING_AGENT_DIR`):
|
|
|
29
36
|
"loop": { "enabled": true },
|
|
30
37
|
"monitor": { "enabled": true, "collapseKey": "ctrl+shift+m", "maxWidgetLines": 12 },
|
|
31
38
|
"diff": { "enabled": true },
|
|
32
|
-
"stats": { "enabled": true }
|
|
39
|
+
"stats": { "enabled": true },
|
|
40
|
+
"mon": { "enabled": true }
|
|
33
41
|
}
|
|
34
42
|
```
|
|
35
43
|
|
|
@@ -63,13 +71,17 @@ persistent cron is installed. Deferred-tool configurations need `goal_update` in
|
|
|
63
71
|
## Monitor
|
|
64
72
|
|
|
65
73
|
- `monitor_start({argv, name, match?, wake?, timeout_s?})`: run an argv command with stdin closed,
|
|
66
|
-
no implicit shell. Five active monitors maximum, one-hour default timeout. Explicitly passing a
|
|
74
|
+
no implicit shell. Five active monitors maximum, one-hour default timeout, 7 day (`604800` s) maximum. Explicitly passing a
|
|
67
75
|
shell remains arbitrary command execution; this is not a sandbox.
|
|
68
76
|
- `monitor_read({id, since?})`: numbered output since a cursor. Ring buffers retain 2000 lines.
|
|
69
77
|
- `monitor_stop({id})`, `/monitor list`, `/monitor stop <id|all>`: stop and inspect owned monitors.
|
|
70
78
|
- Output batches every two seconds into the bounded ring buffer (with dropped-line reporting). Passive batches neither start turns nor create transcript/model-context entries; call `monitor_read` to fetch them. A regex match, natural exit, timeout or `wake: true` emits one compact visible wake with a cursor after the output the model last saw through a wake or read, not a replay of buffered output. A bounded batch preserves its first and latest dropped matching lines as wake evidence even if later output evicts them from the ring.
|
|
71
79
|
- Children run in process groups: SIGTERM, then SIGKILL. Exit cleanup and pidfiles cover orderly quit
|
|
72
|
-
and later recovery after SIGKILL.
|
|
80
|
+
and later recovery after SIGKILL. Sweeps reauthenticate the leader after enumerating members;
|
|
81
|
+
uncertain identities or guards never authorize signaling. Reap checks are bounded to 20 polls
|
|
82
|
+
after escalation; a timeout keeps authenticated recovery on disk rather than claiming cleanup.
|
|
83
|
+
Spawn failures after child creation kill the child; matcher initialization failures also remove
|
|
84
|
+
its pidfile. A spawn completing after session disposal is killed without registration. Files live at
|
|
73
85
|
`$TMPDIR/pi-monitors-<uid>/<sessionId>.<ownerPid>/<monitorId>.json`. Startup checks dead-owner
|
|
74
86
|
records, including a resumed session, and validates process identity before signalling. It never
|
|
75
87
|
sweeps a live owner's monitors. Two panes resuming the same session have separate directories.
|
|
@@ -213,8 +225,17 @@ the kit does not append a second reminder or request another continuation.
|
|
|
213
225
|
`test/scenarios/` drives the real kit through core's in-process session harness under Vitest and Bun,
|
|
214
226
|
covering goals, file-backed restore, child rename/reload, read-rule injection, titles,
|
|
215
227
|
monitor output/wakes/cursors/caps/timeouts and loop scheduling with same-cwd session isolation.
|
|
228
|
+
Command completion scenarios resolve public runner suggestions and execute the selected `/goal`,
|
|
229
|
+
`/loop` and `/monitor` commands, checking persisted state, timer removal and the selected child.
|
|
230
|
+
Loop resume starts from a provider-error pause: ticks stay quiet until the selected resume command
|
|
231
|
+
restores dispatch. Git setup uses child-only scratch HOME/config and empty hooks/templates, with
|
|
232
|
+
hostile config/template/hook controls; it never changes the parent process HOME.
|
|
233
|
+
Pidfile age-boundary fixtures pin file/directory mtimes to the injected sweep clock and check the
|
|
234
|
+
24-hour threshold under both past and future wall clocks.
|
|
216
235
|
The interactive host/VirtualTerminal covers `/diff`, `/stats` and real `/name`, including width/resize
|
|
217
|
-
matrices, chart glyph ports/colours and file-backed titles.
|
|
236
|
+
matrices, chart glyph ports/colours and file-backed titles. Mounted command callbacks also verify
|
|
237
|
+
live-theme invalidation, empty Models ranges, help page boundaries, source-failure reopening,
|
|
238
|
+
stale deferred factories and stats load-error cleanup without replacing the interactive host. Stats histories use a fixed clock;
|
|
218
239
|
`createSessionKit({ agentDir, clock })` scopes both offline loading and displayed time ranges.
|
|
219
240
|
Terminal fixtures route input chunks through pi-tui's real `StdinBuffer`, matching ProcessTerminal's
|
|
220
241
|
keypress splitting (complete keys only; lone Escape is flushed immediately). Stats scroll-to-clamp
|
|
@@ -223,6 +244,18 @@ tabs and exact provider totals remain covered at every width. Each stats-view bo
|
|
|
223
244
|
and disposes it in `finally`, so runner timeout teardown cannot race a still-running callback.
|
|
224
245
|
Monitor scenarios use a shared fake clock and controlled children; native `test/monitor/` keeps real
|
|
225
246
|
workers/processes, and process-group kill/orphan recovery stays in the assembled integration tier.
|
|
226
|
-
|
|
247
|
+
Native stop/dispose checks assert direct children are reaped when the operation resolves, then
|
|
248
|
+
observe orphaned descendants disappearing with the existing bounded liveness check. Leader exit
|
|
249
|
+
and pipe closure do not synchronize PID 1's reaping; on Linux, `kill(pid, 0)` can still succeed
|
|
250
|
+
for a terminated zombie. No extra signal, fixed delay or larger timeout is needed.
|
|
251
|
+
The native diff descendant timeout fixture gates its fake deadline on real stdout readiness, so
|
|
252
|
+
slow process startup cannot race a fixed wall-clock deadline. Adapter fault tests cover monitor
|
|
253
|
+
startup/sweep/widget failures, post-spawn errors, worker startup timeout, rejected sampler cleanup,
|
|
254
|
+
first/latest dropped-match evidence, title-stream failure and real rule reinjection after tree
|
|
255
|
+
navigation. Synthetic PIDs are never passed to real signal capabilities. Native Promise construction
|
|
256
|
+
initializes the monitor exit resolver before child handlers register; synchronous close/error
|
|
257
|
+
registration fixtures lock that ordering. The overwritten no-op resolver initializer is removed,
|
|
258
|
+
not a reachable validation or lifecycle path.
|
|
259
|
+
`npm run check` runs Biome, TypeScript, the full Vitest suite and `bun test test/scenarios` (Bun 1.4.4).
|
|
227
260
|
Cross-extension Plan/subagent/todo interactions and OS terminal boundaries remain in the integration tier;
|
|
228
261
|
`docs/verification-rebuild.md` at the monorepo root tracks their destinations.
|
package/dist/pi-stats-export.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// src/stats/cli.ts
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
2
3
|
import { resolve as resolve2 } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
3
5
|
|
|
4
6
|
// src/stats/load.ts
|
|
5
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -651,7 +653,23 @@ async function main() {
|
|
|
651
653
|
"usage: export --agent-dir DIR --output OUT [--sessions-dir DIR] | import --agent-dir DIR --input REPO"
|
|
652
654
|
);
|
|
653
655
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
})
|
|
656
|
+
async function run() {
|
|
657
|
+
try {
|
|
658
|
+
await main();
|
|
659
|
+
} catch (error) {
|
|
660
|
+
console.error(error instanceof Error ? error.message : "Stats operation failed");
|
|
661
|
+
process.exitCode = 1;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function isEntrypoint() {
|
|
665
|
+
try {
|
|
666
|
+
return !!process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
|
|
667
|
+
} catch {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (isEntrypoint()) void run();
|
|
672
|
+
export {
|
|
673
|
+
main,
|
|
674
|
+
run
|
|
675
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# /mon resource dashboard
|
|
2
|
+
|
|
3
|
+
`/mon` is a read-only, interactive-TUI view of local host resources. It is on by default and
|
|
4
|
+
disabled with `{ "mon": { "enabled": false } }` in `pi-session-kit.json`. In RPC/print/JSON modes
|
|
5
|
+
it only warns; it never samples there.
|
|
6
|
+
|
|
7
|
+
## Source
|
|
8
|
+
|
|
9
|
+
`src/mon/source.ts` is a thin mapping over the published `@nredd/pi-resource-sampler`, declared
|
|
10
|
+
as the exact runtime dependency `0.1.1` (`0.1.0` leaked argv-style process titles as macOS names
|
|
11
|
+
and could fake Bun host CPU). No collector lives in session-kit. Each open view creates
|
|
12
|
+
one sampler (its own CPU baseline) and disposes it on close; disposal aborts and awaits any active
|
|
13
|
+
sample, including its `ps` child on macOS. Mapping rules:
|
|
14
|
+
|
|
15
|
+
- Collector success and host CPU state are separate. Every resolved sample is `LIVE` and refreshes
|
|
16
|
+
the last-success time, even while host CPU is `warming` or `unavailable`, because its memory,
|
|
17
|
+
load and process data are fresh. The CPU readout then says `-- warming` or `-- unavailable`.
|
|
18
|
+
The header pairs the state (`● LIVE`, `○ WAITING`, `▲ STALE`) with the local wall time of the
|
|
19
|
+
last successful collection (`updated HH:MM:SS`, `last ok` when stale), so freshness is never
|
|
20
|
+
inferred from the CPU state.
|
|
21
|
+
- `warming` and `unavailable` metrics become `--`, never zero. The first observation is a warming
|
|
22
|
+
baseline; persistent warming shows the sampler's reason (e.g. `Host CPU warming: CPU counters
|
|
23
|
+
have not advanced`).
|
|
24
|
+
- Two diagnostic tones. A warning (`▲`, warning color) means a limit hides data the user would
|
|
25
|
+
expect: `Partial list: S of E seen (reasons)` for budget caps or collector failures (at most three
|
|
26
|
+
reasons, in words: `time budget`, `process cap`, `read budget`, `ps failed`, ...), unavailable host
|
|
27
|
+
CPU, persistent warming. A muted hint states an expected platform limit: Linux always skips kernel
|
|
28
|
+
threads (no RSS) and processes that exit mid-scan, so a scan whose only reason is
|
|
29
|
+
`process-unreadable-or-changed` is `N skipped: kernel threads, exited or unreadable`, not a
|
|
30
|
+
warning; `N names unknown (no access, e.g. other users)`; macOS process CPU. The process header
|
|
31
|
+
counts *sampled* rows, not host processes.
|
|
32
|
+
- Names are the sampler's executable basenames, never argv or process titles. `""` renders as a dim
|
|
33
|
+
`(unknown)` (Linux hides other users' `/proc/PID/exe` without ptrace access). macOS `p_comm` keeps
|
|
34
|
+
16 bytes, so a macOS name of 16+ UTF-8 bytes ends in a dim `…` (`Google Chrome He…`): possibly cut,
|
|
35
|
+
never claimed complete. Names stay untrusted text: sanitized, never interpreted.
|
|
36
|
+
- Upcoming sampler source supplies macOS native interval CPU and footprint via builtin FFI and
|
|
37
|
+
libproc, with process-start absolute ticks plus microsecond identity. Inaccessible PIDs and
|
|
38
|
+
unsupported runtimes have unknown metrics. The existing published `0.1.1` remains entirely
|
|
39
|
+
unavailable for process CPU; `ps` whole-second start times alone never authenticate CPU. The
|
|
40
|
+
view orders by RSS only when every process CPU is unavailable, until the user picks a sort.
|
|
41
|
+
Linux first samples are warming, so CPU order stays stable.
|
|
42
|
+
- History spacing uses the sampler's monotonic `monotonicTimeMs`, so wall-clock steps (NTP, manual
|
|
43
|
+
changes) cannot invent or hide gaps; the wall `wallTimeMs` is only displayed (header clock and
|
|
44
|
+
last-success UTC).
|
|
45
|
+
Node's monotonic clock pauses during system sleep on macOS and Linux, so a suspend shows no gap.
|
|
46
|
+
`scan Nms` (80+ columns) is the sampler's collection duration. Missing/zero memory totals and non-finite values become unknown.
|
|
47
|
+
|
|
48
|
+
## Presentation
|
|
49
|
+
|
|
50
|
+
- From 72 columns and 17 rows (one more per diagnostic line), CPU and memory/load cards sit side
|
|
51
|
+
by side; CPU history is two rows (16 levels) there. Narrower or shorter terminals get a
|
|
52
|
+
three-line summary (`CPU`, `RAM`, 1-minute load, one-row history); at six rows summary/chrome
|
|
53
|
+
yields to the selected process, but a compact partial-list or stopped/retry warning remains visible.
|
|
54
|
+
At eight rows, core CPU/RAM/load metrics take priority over optional history and platform hints.
|
|
55
|
+
Compact metrics tighten spacing when needed to keep CPU, RAM and the 1-minute load visible.
|
|
56
|
+
The last row reserves close/help. Below 60 columns the header
|
|
57
|
+
keeps the update clock over the tagline.
|
|
58
|
+
- Columns: `PID`, `CPU%` (100 = one core), `RSS`, `MEM%` (RSS over total RAM, 60+ columns),
|
|
59
|
+
`NAME` last so long names never push numbers off screen. `f` toggles the byte column between
|
|
60
|
+
`RSS` and `FOOT` (native physical footprint, including compressed memory); unavailable/failed
|
|
61
|
+
PIDs show `--`, never zero. `MEM%` and `m` sorting remain RSS-based in either view. `--` is dimmed. A `▸` marks the
|
|
62
|
+
selection; a theme scrollbar appears only when the list overflows. The memory card adds 1-minute
|
|
63
|
+
load per logical CPU.
|
|
64
|
+
- Host CPU severity colors the value (success under 50%, warning under 85%, error above) next to
|
|
65
|
+
the number itself. Stale CPU retains the last measured value, muted rather than live severity-colored.
|
|
66
|
+
Cards add a `last value` label; compact summaries rely on the `STALE` header instead, reserving
|
|
67
|
+
the metric row for CPU, RAM and load.
|
|
68
|
+
- Host CPU history keeps each capture timestamp, uses a fixed 0..100% scale and at most 120
|
|
69
|
+
samples, newest on the right. A column is the lower-median interval between captures, widened
|
|
70
|
+
only when the history is longer than the width, so completion-paced drift never invents gaps.
|
|
71
|
+
Columns with no CPU value after the first one are gaps (`·`), never zero or interpolation;
|
|
72
|
+
columns before it are blank, including the warming baseline. Stale snapshots add no point. The caption states the actual span. The latest sample in a bucket wins.
|
|
73
|
+
Stale views show the last successful capture's UTC timestamp (or `none`). Waiting/live/stale and
|
|
74
|
+
CPU warming/unavailable use semantic theme colors and text, so meaning does not depend on color.
|
|
75
|
+
- Memory labels its actual basis. Older samplers report **total-free** (`T-F` in the compact
|
|
76
|
+
summary), which includes cached files and is NOT Activity Monitor's Memory Used. Additive v1
|
|
77
|
+
`memory.usage` metrics instead render **used (A+W+C)** (`A+W+C` compact), an estimate of resident
|
|
78
|
+
App + Wired + Compressed physical RAM. A failed native metric stays `--`, never total-free under
|
|
79
|
+
a used label. Cards also show free pages and swap used/allocated; missing swap stays `--/--`.
|
|
80
|
+
`?` explains each basis, RSS excluding compressed pages versus Activity Monitor's footprint,
|
|
81
|
+
executable names (`pi` runs as `node`), and decaying `ps %cpu` versus interval CPU.
|
|
82
|
+
Load averages are not percentages. Process CPU is interval CPU, where 100% is one logical CPU and
|
|
83
|
+
multithreaded processes can exceed 100%.
|
|
84
|
+
- Release sequencing: the registry dependency remains `0.1.1`, which lacks native host-memory
|
|
85
|
+
metrics. This adapter accepts the new optional memory/footprint fields and tests them, but production
|
|
86
|
+
remains honestly labelled total-free with unavailable process CPU/footprint until a new sampler
|
|
87
|
+
is published, its common dependency pin
|
|
88
|
+
and nested registry lock entries are updated, and both consumer registry tests pass. Do NOT claim
|
|
89
|
+
the new native used/swap line is active with the old registry pin.
|
|
90
|
+
- The process viewport preserves selection by sampler identity, not PID or sorted row.
|
|
91
|
+
Exited identities choose a bounded neighboring row; PID reuse cannot inherit selection.
|
|
92
|
+
Unknown/non-finite measurements sort last with deterministic PID/identity tie breaks.
|
|
93
|
+
- `j/k` and arrows select; PageUp/PageDown page; `g/G` jump first/last; `f` toggles RSS/FOOT; `c/m/p` sort
|
|
94
|
+
CPU/RSS/PID; `s` cycles sort; `?` toggles help; `q`/Escape close. CPU/RSS descend,
|
|
95
|
+
PID ascends. Help has a bounded, wrapped viewport with a visible range and scroll hint;
|
|
96
|
+
`j/k`, arrows, PageUp/PageDown and `g/G` scroll help without moving the process selection.
|
|
97
|
+
No kill, locks, workload control, process arguments or environment display. Process names
|
|
98
|
+
are the sampler's sanitized display names and are never interpreted.
|
|
99
|
+
|
|
100
|
+
## Ownership
|
|
101
|
+
|
|
102
|
+
`MonView` accepts presentation snapshots and never collects data. It caches rendered lines
|
|
103
|
+
by dimensions until update/input/theme invalidation. `MonPoll` owns one completion-paced probe
|
|
104
|
+
(~2s after the previous one completed), with no catch-up queue. Closing aborts collection,
|
|
105
|
+
cancels the timeout and awaits the sampler's disposal. Errors stop collection and retain
|
|
106
|
+
explicitly stale values; reopening is the retry mechanism. Late results are discarded.
|
|
107
|
+
|
|
108
|
+
`registerMon` opens a source only inside a TUI interaction and serializes replacement through
|
|
109
|
+
completed asynchronous disposal, including concurrent opens. Session shutdown/start invalidates
|
|
110
|
+
pending openers before they can create a source, even when Pi defers the UI factory. It registers
|
|
111
|
+
no model tool, persists no history, sends no model messages and does no network work.
|
|
112
|
+
|
|
113
|
+
## Verification
|
|
114
|
+
|
|
115
|
+
- `npm run check` in `packages/session-kit`. The root lock pins a nested registry install
|
|
116
|
+
(`packages/session-kit/node_modules/@nredd/pi-resource-sampler`, exact `0.1.1` tarball and
|
|
117
|
+
integrity), so tests run the published bytes, not the same-version workspace that npm would
|
|
118
|
+
otherwise link. `source.test.ts` fails if resolution ever reaches the workspace.
|
|
119
|
+
- `test/mon/source.test.ts`: two actual host observations (warming, then measured), a real poll's
|
|
120
|
+
two updates, and mapping edge cases. In-flight close wraps the real sampler to hold its pending
|
|
121
|
+
sample, then requires that sample to reject with `AbortError`, no update, no child and no timer.
|
|
122
|
+
On macOS it first requires the sampler's live `ps` child; Linux reads `/proc` in process, so
|
|
123
|
+
there the `AbortError` is the only provider evidence and no child is claimed.
|
|
124
|
+
- `test/mon/theme.test.ts`: actual built-in dark and light themes over an actual observation at
|
|
125
|
+
40x12, 60x20, 80x24 and 120x40.
|
|
126
|
+
- `test/scenarios/mon.test.ts` (Vitest and Bun): the real interactive Pi host, `createSessionKit`
|
|
127
|
+
and the real sampler; two observations, resize matrix, close/Escape/reopen, close before the
|
|
128
|
+
first sample and session shutdown all leave no kit timer or child process.
|
|
129
|
+
- `test/mon/{component,poll,index}.test.ts`: layout, identity, history, lifecycle interleavings
|
|
130
|
+
with fakes; `resource-fixture.ts` is type-checked against the package's `ResourceSnapshot`.
|
|
131
|
+
- `make check-package-contracts` packs session-kit, installs it alone (sampler from npm, exact
|
|
132
|
+
lockfile closure) and drives it through stock Pi.
|
|
133
|
+
- Real macOS terminal recordings exercise the packed, registry-backed view, resize, navigation,
|
|
134
|
+
close and reopen. Empty direct-child lists before and after close show only that no child was
|
|
135
|
+
observed at those instants; they do NOT prove an in-flight helper was cancelled or reaped.
|
|
136
|
+
In-flight cancellation/reaping evidence comes from the deterministic real-sampler tests above,
|
|
137
|
+
not those terminal snapshots. No Linux terminal recording is claimed.
|
|
138
|
+
- Manual fixture preview (no sampling): `bun test/mon/terminal.ts dark|light`. It is labelled
|
|
139
|
+
`FIXTURE DATA` and uses `fixture-*` names, so a capture can never pass for host data; it is not
|
|
140
|
+
evidence of real measurements.
|
package/docs/stats-snapshots.md
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
Build the committed `dist/pi-stats-export.mjs` with `npm ci && npm run build:stats-export`.
|
|
4
4
|
The exact esbuild version and dependencies are locked; tests assert byte-identical rebuilds.
|
|
5
5
|
Copy only that file. Runtime is Node 22+ with builtins only, no Pi install, extension, private
|
|
6
|
-
repository, package install, auth or network access.
|
|
6
|
+
repository, package install, auth or network access. Importing the artifact is silent, even when
|
|
7
|
+
an importing process supplies a nonexistent entrypoint argument; direct and symlinked Node execution
|
|
8
|
+
still run the CLI. Packed-CLI tests explicitly launch `node`, including when their Vitest
|
|
9
|
+
host is Bun; this tests the artifact's declared runtime, not Bun's entry-module resolver.
|
|
10
|
+
|
|
11
|
+
Chart tick steps parse decimal powers directly rather than using implementation-approximated
|
|
12
|
+
exponentiation, keeping large finite ticks consistent between Node and Bun. Steps above the
|
|
13
|
+
numeric range retain infinite headroom.
|
|
7
14
|
|
|
8
15
|
```
|
|
9
16
|
node ARTIFACT export --agent-dir DIR --output OUT [--sessions-dir ROOT]
|
|
@@ -36,10 +43,16 @@ Installation state, never synced:
|
|
|
36
43
|
- `DIR/pi-session-kit/stats-cache.json`: local parser cache v2, including file paths and compact
|
|
37
44
|
usage tuples. Version 1 entries for deleted files stay intact; existing files reparse even if
|
|
38
45
|
their mtime/size match. Strong identities are not invented for inaccessible legacy files.
|
|
46
|
+
Failed stat/parse operations count as incomplete history; a failed reparse retains the last-good
|
|
47
|
+
cached record. Invalid cached identities and raw provider/model pairs are discarded. Persisted
|
|
48
|
+
child markers apply only to the matching session ID, surviving user-visible renames.
|
|
39
49
|
- `DIR/pi-session-kit/stats-global.json`: one atomically replaced document containing validated
|
|
40
50
|
last-good machine snapshots, last successful import time and coverage problems. Imports do not
|
|
41
51
|
mutate local usage or installation identity. A wholly failed import does not advance the sync
|
|
42
|
-
time; a partial import records valid updates and marks coverage incomplete.
|
|
52
|
+
time; a partial import records valid updates and marks coverage incomplete. The assembled cache
|
|
53
|
+
accepts up to 128 MiB; exceeding that limit fails before replacement and retains its last-good
|
|
54
|
+
bytes. Tests inject final-document byte accounting at the exact limit and one byte over, while
|
|
55
|
+
retaining real import validation and atomic publication.
|
|
43
56
|
|
|
44
57
|
Snapshot version 1:
|
|
45
58
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nredd/pi-session-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"src",
|
|
13
13
|
"dist/pi-stats-export.mjs",
|
|
14
14
|
"docs/stats-snapshots.md",
|
|
15
|
+
"docs/mon-dashboard.md",
|
|
15
16
|
"CHANGELOG.md",
|
|
16
17
|
"LICENSE",
|
|
17
18
|
"README.md"
|
|
@@ -25,6 +26,9 @@
|
|
|
25
26
|
"pi-package",
|
|
26
27
|
"pi-extension"
|
|
27
28
|
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@nredd/pi-resource-sampler": "0.2.0"
|
|
31
|
+
},
|
|
28
32
|
"peerDependencies": {
|
|
29
33
|
"@earendil-works/pi-coding-agent": "*",
|
|
30
34
|
"@earendil-works/pi-tui": "*",
|
|
@@ -35,15 +39,16 @@
|
|
|
35
39
|
"format": "biome check --write src/ test/",
|
|
36
40
|
"typecheck": "tsc --noEmit",
|
|
37
41
|
"build:stats-export": "esbuild src/stats/cli.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/pi-stats-export.mjs",
|
|
38
|
-
"test": "npm run build:stats-export && vitest run",
|
|
39
|
-
"test:bun": "bun test test/scenarios",
|
|
42
|
+
"test": "npm run build:stats-export && vitest run --coverage",
|
|
43
|
+
"test:bun": "bun test test/scenarios --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir=coverage/bun",
|
|
40
44
|
"check": "npm run lint && npm run typecheck && npm run test && npm run test:bun"
|
|
41
45
|
},
|
|
42
46
|
"devDependencies": {
|
|
43
47
|
"@biomejs/biome": "2.5.15",
|
|
44
|
-
"@earendil-works/pi-coding-agent": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.
|
|
45
|
-
"@earendil-works/pi-tui": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.
|
|
48
|
+
"@earendil-works/pi-coding-agent": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.8/earendil-works-pi-coding-agent-1.3.0.tgz",
|
|
49
|
+
"@earendil-works/pi-tui": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.8/earendil-works-pi-tui-1.3.0.tgz",
|
|
46
50
|
"@types/node": "26.6.5",
|
|
51
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
47
52
|
"@xterm/headless": "5.5.0",
|
|
48
53
|
"esbuild": "0.28.2",
|
|
49
54
|
"typebox": "1.3.36",
|
package/src/config.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { readFileSync } from "node:fs";
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
|
|
12
|
-
export const FEATURES = ["titles", "rules", "goal", "loop", "monitor", "diff", "stats"] as const;
|
|
12
|
+
export const FEATURES = ["titles", "rules", "goal", "loop", "monitor", "diff", "stats", "mon"] as const;
|
|
13
13
|
export type Feature = (typeof FEATURES)[number];
|
|
14
14
|
|
|
15
15
|
export interface KitConfig {
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { configPath, type Feature, type KitConfig, loadConfig } from "./config.t
|
|
|
9
9
|
import { register as registerDiff } from "./diff/index.ts";
|
|
10
10
|
import { register as registerGoal } from "./goal/index.ts";
|
|
11
11
|
import { register as registerLoop } from "./loop/index.ts";
|
|
12
|
+
import { registerMon } from "./mon/index.ts";
|
|
13
|
+
import { createSamplerSource } from "./mon/source.ts";
|
|
12
14
|
import { register as registerMonitor } from "./monitor/index.ts";
|
|
13
15
|
import type { MonitorProcess, SpawnOptions } from "./monitor/process.ts";
|
|
14
16
|
import { register as registerRules } from "./rules/index.ts";
|
|
@@ -20,7 +22,7 @@ export interface SessionKitOptions {
|
|
|
20
22
|
config?: KitConfig;
|
|
21
23
|
agentDir?: string;
|
|
22
24
|
monitorRoot?: string;
|
|
23
|
-
/** Time for stats ranges, loops, goal wakes
|
|
25
|
+
/** Time for stats ranges, loops, goal wakes, monitor batching/timeouts and /mon cadence. Defaults to real time. */
|
|
24
26
|
clock?: Clock;
|
|
25
27
|
/** Child boundary only; validation, matching, ring buffering and message delivery stay in the manager. */
|
|
26
28
|
spawnMonitor?: (argv: readonly string[], options: SpawnOptions) => Promise<MonitorProcess>;
|
|
@@ -37,6 +39,7 @@ const MODULES: Record<Feature, Register> = {
|
|
|
37
39
|
registerMonitor(pi, config, options.monitorRoot, { clock: options.clock, spawnMonitor: options.spawnMonitor }),
|
|
38
40
|
diff: registerDiff,
|
|
39
41
|
stats: (pi, config, options) => registerStats(pi, config, options),
|
|
42
|
+
mon: (pi, _config, options) => registerMon(pi, { createSource: () => createSamplerSource(), clock: options.clock }),
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
/** Create the real kit without requiring a machine-local agent configuration. */
|