@monotykamary/pi-ledger 0.2.0 → 0.4.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/README.md +6 -2
- package/extensions/pi-ledger/__tests__/helpers.ts +8 -1
- package/extensions/pi-ledger/__tests__/in-process-session-observer.test.ts +555 -0
- package/extensions/pi-ledger/__tests__/ledger.test.ts +125 -0
- package/extensions/pi-ledger/__tests__/nested-agent-telemetry.test.ts +867 -0
- package/extensions/pi-ledger/__tests__/subprocess-observer.test.ts +364 -0
- package/extensions/pi-ledger/in-process-session-observer.ts +890 -0
- package/extensions/pi-ledger/index.ts +121 -1
- package/extensions/pi-ledger/nested-agent-telemetry.ts +1271 -0
- package/extensions/pi-ledger/subprocess-observer.ts +694 -0
- package/package.json +1 -1
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
type SettingItem,
|
|
64
64
|
type TUI,
|
|
65
65
|
} from '@earendil-works/pi-tui';
|
|
66
|
+
import { installNestedAgentTelemetryHarvester } from './nested-agent-telemetry.js';
|
|
66
67
|
|
|
67
68
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
68
69
|
|
|
@@ -72,6 +73,19 @@ const TPS_TELEMETRY_EVENT = 'tps:telemetry';
|
|
|
72
73
|
/** Custom entry type written by @monotykamary/pi-tps into the session JSONL. */
|
|
73
74
|
const TPS_CUSTOM_TYPE = 'tps';
|
|
74
75
|
|
|
76
|
+
/** Custom events emitted by @monotykamary/pi-retry around its retry loop:
|
|
77
|
+
* 'started' (with a retryId) when the loop spins up, 'completed' when it
|
|
78
|
+
* exits on success, 'cancelled' when it exits on abort/session-change.
|
|
79
|
+
* pi-ledger subscribes so the engagement wizard does not pop mid-retry —
|
|
80
|
+
* agent_settled can fire during a pi-retry backoff sleep (before pi-retry has
|
|
81
|
+
* re-prompted), and popping then would bill the backoff as human time
|
|
82
|
+
* (violating scale-to-zero: a slow/queued provider is a retry, not billable).
|
|
83
|
+
* The prompt is deferred until the retry settles. Mirrors localterm's
|
|
84
|
+
* agent-notify handshake. */
|
|
85
|
+
const PI_RETRY_STARTED_EVENT = 'pi-retry:started';
|
|
86
|
+
const PI_RETRY_COMPLETED_EVENT = 'pi-retry:completed';
|
|
87
|
+
const PI_RETRY_CANCELLED_EVENT = 'pi-retry:cancelled';
|
|
88
|
+
|
|
75
89
|
/** Minimum gap between streaming updates to count as an inference stall (ms). */
|
|
76
90
|
const STALL_THRESHOLD_MS = 500;
|
|
77
91
|
|
|
@@ -847,6 +861,17 @@ function reveal(text: string): string {
|
|
|
847
861
|
return ` data-reveal="${esc(text)}">`;
|
|
848
862
|
}
|
|
849
863
|
|
|
864
|
+
/** Pull the 'retryId' out of a @monotykamary/pi-retry event payload, or
|
|
865
|
+
* 'undefined' if it is absent/malformed. Used to match a 'completed'/
|
|
866
|
+
* 'cancelled' event back to its 'started' so pi-ledger only acts on its own
|
|
867
|
+
* retry. Mirrors localterm's retry-event-id util. Pure.
|
|
868
|
+
* @internal Exported for testing only. */
|
|
869
|
+
export function retryEventId(event: unknown): number | undefined {
|
|
870
|
+
if (typeof event !== 'object' || event === null || !('retryId' in event)) return undefined;
|
|
871
|
+
const id = (event as { retryId?: unknown }).retryId;
|
|
872
|
+
return typeof id === 'number' ? id : undefined;
|
|
873
|
+
}
|
|
874
|
+
|
|
850
875
|
/**
|
|
851
876
|
* Build a self-contained HTML receipt. White-on-white, Geist Mono, with
|
|
852
877
|
* values that stream in autoregressively (char-by-char) on load.
|
|
@@ -1090,6 +1115,8 @@ function fmtNumber(n: number): string {
|
|
|
1090
1115
|
// ─── Extension ──────────────────────────────────────────────────────────────
|
|
1091
1116
|
|
|
1092
1117
|
export default function ledgerExtension(pi: ExtensionAPI) {
|
|
1118
|
+
installNestedAgentTelemetryHarvester(pi);
|
|
1119
|
+
|
|
1093
1120
|
let settings: LedgerSettings = { ...DEFAULT_SETTINGS };
|
|
1094
1121
|
|
|
1095
1122
|
const totals: Totals = {
|
|
@@ -1204,6 +1231,25 @@ export default function ledgerExtension(pi: ExtensionAPI) {
|
|
|
1204
1231
|
|
|
1205
1232
|
let wizardTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1206
1233
|
|
|
1234
|
+
// pi-retry awareness: when @monotykamary/pi-retry is installed it emits
|
|
1235
|
+
// pi-retry:started/completed/cancelled around its (possibly multi-attempt)
|
|
1236
|
+
// retry loop. agent_settled can fire during a retry's backoff sleep —
|
|
1237
|
+
// before pi-retry has decided whether to re-prompt — so without gating the
|
|
1238
|
+
// wizard would pop mid-retry (billing the backoff as human time, violating
|
|
1239
|
+
// scale-to-zero). We capture the active retryId and DEFER the settled
|
|
1240
|
+
// prompt until the retry settles: pop only on 'completed', never on
|
|
1241
|
+
// 'cancelled', and never while a retry is in flight. Mirrors localterm's
|
|
1242
|
+
// agent-notify handshake.
|
|
1243
|
+
let retryActiveId: number | undefined;
|
|
1244
|
+
// The run settled (agent_settled fired, no rolling credit) but a retry was
|
|
1245
|
+
// in flight, so the engagement prompt is pending until the retry completes.
|
|
1246
|
+
// Cleared on pop, on agent_start (a new run supersedes the stale settled
|
|
1247
|
+
// state), on retry cancelled, and at session boundaries.
|
|
1248
|
+
let pendingSettledWizard: { ctx: ExtensionContext } | null = null;
|
|
1249
|
+
// Unsubscribers for the pi-retry event capture (a session-scoped resource);
|
|
1250
|
+
// torn down at session_shutdown. The factory re-binds on the next session.
|
|
1251
|
+
let retryUnsubs: Array<() => void> = [];
|
|
1252
|
+
|
|
1207
1253
|
// Latest ctx (event-bus listeners for tps:telemetry don't receive one).
|
|
1208
1254
|
let lastCtx: ExtensionContext | null = null;
|
|
1209
1255
|
|
|
@@ -1903,6 +1949,11 @@ export default function ledgerExtension(pi: ExtensionAPI) {
|
|
|
1903
1949
|
// rule as an unclosed idle window: uncommitted, so not restored.
|
|
1904
1950
|
pendingSteers = [];
|
|
1905
1951
|
dequeuedBuffer = null;
|
|
1952
|
+
// Reset pi-retry capture state — a fresh session has no in-flight retry and
|
|
1953
|
+
// no deferred prompt (an unclosed prior session's retry could otherwise
|
|
1954
|
+
// leak across the session boundary on a per-process extension instance).
|
|
1955
|
+
retryActiveId = undefined;
|
|
1956
|
+
pendingSettledWizard = null;
|
|
1906
1957
|
// Wrap the input editor so keystrokes stage for billing: during a run they
|
|
1907
1958
|
// feed a steer/followUp burst (committed on submit via `input`); between
|
|
1908
1959
|
// turns the FIRST keystroke engages an idle window at its onset. Extends
|
|
@@ -1944,6 +1995,20 @@ export default function ledgerExtension(pi: ExtensionAPI) {
|
|
|
1944
1995
|
// at shutdown was never committed, so it's abandoned and bills 0 (idle
|
|
1945
1996
|
// with no output is wasted). Persisted as a close for replay cleanliness.
|
|
1946
1997
|
closeHumanWindow(lastCtx, false);
|
|
1998
|
+
// Tear down the pi-retry event capture (a session-scoped resource): the
|
|
1999
|
+
// factory re-binds subscriptions on the next session, so unbind here to
|
|
2000
|
+
// avoid leaking listeners across the session boundary. Reset the capture
|
|
2001
|
+
// state too — no in-flight retry survives a session teardown.
|
|
2002
|
+
for (const unsub of retryUnsubs) {
|
|
2003
|
+
try {
|
|
2004
|
+
unsub();
|
|
2005
|
+
} catch {
|
|
2006
|
+
// best-effort — ignore a stale bus
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
retryUnsubs = [];
|
|
2010
|
+
retryActiveId = undefined;
|
|
2011
|
+
pendingSettledWizard = null;
|
|
1947
2012
|
});
|
|
1948
2013
|
|
|
1949
2014
|
// ── Agent timing (tool execution) ─────────────────────────────────────
|
|
@@ -2044,6 +2109,41 @@ export default function ledgerExtension(pi: ExtensionAPI) {
|
|
|
2044
2109
|
// extension load order — a 'fallback' may be corrected by a later 'tps'
|
|
2045
2110
|
// entry for the same turnIndex (rehydrate keeps the last per turnIndex).
|
|
2046
2111
|
|
|
2112
|
+
// ── pi-retry capture ─────────────────────────────────────────────────
|
|
2113
|
+
// @monotykamary/pi-retry emits started/completed/cancelled around its
|
|
2114
|
+
// (possibly multi-attempt) retry loop. agent_settled can land during a
|
|
2115
|
+
// retry's backoff sleep — before pi-retry has re-prompted — so without this
|
|
2116
|
+
// capture the engagement wizard would pop mid-retry, billing the backoff as
|
|
2117
|
+
// human time (violating scale-to-zero: a slow/queued provider is a retry,
|
|
2118
|
+
// not billable). Defer the settled prompt until the retry settles: pop on
|
|
2119
|
+
// 'completed' (if still no credit), never on 'cancelled', never while one is
|
|
2120
|
+
// in flight. Unsubscribed at session_shutdown (a session-scoped resource);
|
|
2121
|
+
// the factory re-binds on the next session. Mirrors localterm's agent-notify.
|
|
2122
|
+
retryUnsubs.push(
|
|
2123
|
+
pi.events.on(PI_RETRY_STARTED_EVENT, (event: unknown) => {
|
|
2124
|
+
retryActiveId = retryEventId(event);
|
|
2125
|
+
})
|
|
2126
|
+
);
|
|
2127
|
+
retryUnsubs.push(
|
|
2128
|
+
pi.events.on(PI_RETRY_COMPLETED_EVENT, (event: unknown) => {
|
|
2129
|
+
if (retryEventId(event) !== retryActiveId) return;
|
|
2130
|
+
retryActiveId = undefined;
|
|
2131
|
+
const pending = pendingSettledWizard;
|
|
2132
|
+
if (!pending) return;
|
|
2133
|
+
pendingSettledWizard = null;
|
|
2134
|
+
// Credit may have changed since settle (e.g. a mid-backoff /ledger-extend);
|
|
2135
|
+
// with none, pop now that the retry has settled. With credit, stay quiet.
|
|
2136
|
+
if (extensionBudgetMs <= 0) armWizardNow(pending.ctx);
|
|
2137
|
+
})
|
|
2138
|
+
);
|
|
2139
|
+
retryUnsubs.push(
|
|
2140
|
+
pi.events.on(PI_RETRY_CANCELLED_EVENT, (event: unknown) => {
|
|
2141
|
+
if (retryEventId(event) !== retryActiveId) return;
|
|
2142
|
+
retryActiveId = undefined;
|
|
2143
|
+
pendingSettledWizard = null; // abort/session-change → no prompt
|
|
2144
|
+
})
|
|
2145
|
+
);
|
|
2146
|
+
|
|
2047
2147
|
pi.events.on(TPS_TELEMETRY_EVENT, (payload: unknown) => {
|
|
2048
2148
|
const t = payload as TpsTelemetry | null;
|
|
2049
2149
|
if (!t || !t.timing || !t.tokens || !t.model) return;
|
|
@@ -2159,6 +2259,11 @@ export default function ledgerExtension(pi: ExtensionAPI) {
|
|
|
2159
2259
|
// If the human never engaged (no keystroke, no extension), there's no
|
|
2160
2260
|
// window to close and the turn handoff bills nothing.
|
|
2161
2261
|
closeHumanWindow(ctx, true);
|
|
2262
|
+
// A new run supersedes any deferred settled prompt (a retry turn, a queued
|
|
2263
|
+
// follow-up, or a fresh prompt all re-settle later — agent_settled
|
|
2264
|
+
// re-evaluates then). Keeps a pi-retry that completes after a retry
|
|
2265
|
+
// turn's agent_end from popping on a stale 'settled' until that run settles.
|
|
2266
|
+
pendingSettledWizard = null;
|
|
2162
2267
|
});
|
|
2163
2268
|
|
|
2164
2269
|
pi.on('agent_end', (_event, ctx) => {
|
|
@@ -2208,7 +2313,22 @@ export default function ledgerExtension(pi: ExtensionAPI) {
|
|
|
2208
2313
|
// after a retry storm exhausts — the last errored agent_end no longer
|
|
2209
2314
|
// suppresses it, since the run has now settled and the human must take
|
|
2210
2315
|
// over.
|
|
2211
|
-
if (extensionBudgetMs <= 0)
|
|
2316
|
+
if (extensionBudgetMs <= 0) {
|
|
2317
|
+
// @monotykamary/pi-retry (if installed) drives its own retry loop with
|
|
2318
|
+
// backoff sleeps outside processEvents; pi-core's settlement detection
|
|
2319
|
+
// can fire this agent_settled during that backoff — before pi-retry has
|
|
2320
|
+
// decided whether to re-prompt. Popping then would bill the backoff as
|
|
2321
|
+
// human time (violating scale-to-zero: a slow/queued provider is a
|
|
2322
|
+
// retry, not billable). Defer: if a pi-retry is in flight, stage the
|
|
2323
|
+
// prompt and pop when the retry settles (on pi-retry:completed); a
|
|
2324
|
+
// cancelled retry never pops. With no pi-retry (or none active), pop now
|
|
2325
|
+
// as before.
|
|
2326
|
+
if (retryActiveId !== undefined) {
|
|
2327
|
+
pendingSettledWizard = { ctx };
|
|
2328
|
+
} else {
|
|
2329
|
+
armWizardNow(ctx);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2212
2332
|
});
|
|
2213
2333
|
|
|
2214
2334
|
// The `input` event fires for every interactive submit that isn't a slash
|