@pi-unipi/unipi 2.1.0 → 2.1.2
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/package.json +21 -21
- package/packages/ask-user/package.json +3 -3
- package/packages/ask-user/tools.ts +20 -11
- package/packages/autocomplete/package.json +2 -2
- package/packages/btw/package.json +3 -3
- package/packages/cocoindex/package.json +3 -3
- package/packages/compactor/package.json +10 -6
- package/packages/core/package.json +1 -1
- package/packages/core/utils.ts +26 -0
- package/packages/footer/package.json +3 -3
- package/packages/footer/src/index.ts +71 -4
- package/packages/footer/src/tps-tracker.ts +109 -33
- package/packages/info-screen/package.json +3 -3
- package/packages/info-screen/tui/info-overlay.ts +16 -7
- package/packages/input-shortcuts/package.json +3 -3
- package/packages/kanboard/package.json +3 -3
- package/packages/mcp/package.json +3 -3
- package/packages/memory/mempalace.ts +28 -0
- package/packages/memory/package.json +3 -3
- package/packages/memory/storage.ts +37 -21
- package/packages/milestone/package.json +3 -3
- package/packages/notify/package.json +1 -1
- package/packages/ralph/package.json +3 -3
- package/packages/subagents/package.json +5 -5
- package/packages/subagents/src/index.ts +27 -23
- package/packages/updater/package.json +3 -3
- package/packages/utility/package.json +3 -3
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/package.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -65,26 +65,26 @@
|
|
|
65
65
|
"typebox": "^1.1.38"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@pi-unipi/ask-user": "2.1.
|
|
69
|
-
"@pi-unipi/btw": "2.1.
|
|
70
|
-
"@pi-unipi/compactor": "2.1.
|
|
71
|
-
"@pi-unipi/notify": "2.1.
|
|
72
|
-
"@pi-unipi/command-enchantment": "2.1.
|
|
73
|
-
"@pi-unipi/core": "2.1.
|
|
74
|
-
"@pi-unipi/info-screen": "2.1.
|
|
75
|
-
"@pi-unipi/mcp": "2.1.
|
|
76
|
-
"@pi-unipi/memory": "2.1.
|
|
77
|
-
"@pi-unipi/ralph": "2.1.
|
|
78
|
-
"@pi-unipi/subagents": "2.1.
|
|
79
|
-
"@pi-unipi/utility": "2.1.
|
|
80
|
-
"@pi-unipi/milestone": "2.1.
|
|
81
|
-
"@pi-unipi/kanboard": "2.1.
|
|
82
|
-
"@pi-unipi/web-api": "2.1.
|
|
83
|
-
"@pi-unipi/workflow": "2.1.
|
|
84
|
-
"@pi-unipi/footer": "2.1.
|
|
85
|
-
"@pi-unipi/updater": "2.1.
|
|
86
|
-
"@pi-unipi/input-shortcuts": "2.1.
|
|
87
|
-
"@pi-unipi/cocoindex": "2.1.
|
|
68
|
+
"@pi-unipi/ask-user": "2.1.2",
|
|
69
|
+
"@pi-unipi/btw": "2.1.2",
|
|
70
|
+
"@pi-unipi/compactor": "2.1.2",
|
|
71
|
+
"@pi-unipi/notify": "2.1.2",
|
|
72
|
+
"@pi-unipi/command-enchantment": "2.1.2",
|
|
73
|
+
"@pi-unipi/core": "2.1.2",
|
|
74
|
+
"@pi-unipi/info-screen": "2.1.2",
|
|
75
|
+
"@pi-unipi/mcp": "2.1.2",
|
|
76
|
+
"@pi-unipi/memory": "2.1.2",
|
|
77
|
+
"@pi-unipi/ralph": "2.1.2",
|
|
78
|
+
"@pi-unipi/subagents": "2.1.2",
|
|
79
|
+
"@pi-unipi/utility": "2.1.2",
|
|
80
|
+
"@pi-unipi/milestone": "2.1.2",
|
|
81
|
+
"@pi-unipi/kanboard": "2.1.2",
|
|
82
|
+
"@pi-unipi/web-api": "2.1.2",
|
|
83
|
+
"@pi-unipi/workflow": "2.1.2",
|
|
84
|
+
"@pi-unipi/footer": "2.1.2",
|
|
85
|
+
"@pi-unipi/updater": "2.1.2",
|
|
86
|
+
"@pi-unipi/input-shortcuts": "2.1.2",
|
|
87
|
+
"@pi-unipi/cocoindex": "2.1.2"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@earendil-works/pi-agent-core": "^0.80.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/ask-user",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Structured user input tool for Pi coding agent
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Structured user input tool for Pi coding agent — single-select, multi-select, freeform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@pi-unipi/core": "2.1.
|
|
43
|
+
"@pi-unipi/core": "2.1.2"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
COMPACTOR_INSTRUCTION,
|
|
12
12
|
UNIPI_EVENTS,
|
|
13
13
|
emitEvent,
|
|
14
|
+
withHerdrBlocked,
|
|
14
15
|
} from "@pi-unipi/core";
|
|
15
16
|
import type { NormalizedOption, AskUserResponse, SessionLauncherResult } from "./types.js";
|
|
16
17
|
import { renderAskUI, createRenderCall, createRenderResult } from "./ask-ui.js";
|
|
@@ -272,15 +273,19 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
// Render interactive UI
|
|
275
|
-
const result = await
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
276
|
+
const result = await withHerdrBlocked(
|
|
277
|
+
pi,
|
|
278
|
+
"ask_user",
|
|
279
|
+
() => ctx.ui.custom<{ response: AskUserResponse } | null>(
|
|
280
|
+
renderAskUI({
|
|
281
|
+
question,
|
|
282
|
+
context,
|
|
283
|
+
options: normalizedOptions,
|
|
284
|
+
allowMultiple,
|
|
285
|
+
allowFreeform,
|
|
286
|
+
timeout,
|
|
287
|
+
}),
|
|
288
|
+
),
|
|
284
289
|
);
|
|
285
290
|
|
|
286
291
|
// Handle cancel
|
|
@@ -346,8 +351,12 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
346
351
|
// Session launcher intercept: when user selects new_session, offer compact/direct/cancel
|
|
347
352
|
if (response.kind === "new_session") {
|
|
348
353
|
const prefill = response.prefill || "";
|
|
349
|
-
const launcherResult = await
|
|
350
|
-
|
|
354
|
+
const launcherResult = await withHerdrBlocked(
|
|
355
|
+
pi,
|
|
356
|
+
"ask_user: launch",
|
|
357
|
+
() => ctx.ui.custom<SessionLauncherResult | null>(
|
|
358
|
+
renderLauncherUI({ prefill }),
|
|
359
|
+
),
|
|
351
360
|
);
|
|
352
361
|
|
|
353
362
|
if (!launcherResult || launcherResult.action === "cancel") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/command-enchantment",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Enhanced TUI autocomplete for /unipi:* commands
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Enhanced TUI autocomplete for /unipi:* commands — colored, sorted, and grouped by package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/btw",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "A pi extension for parallel side conversations with /unipi:btw
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "A pi extension for parallel side conversations with /unipi:btw — part of the Unipi suite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "extensions/btw.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@earendil-works/pi-tui": "^0.80.0"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@pi-unipi/core": "2.1.
|
|
40
|
+
"@pi-unipi/core": "2.1.2"
|
|
41
41
|
},
|
|
42
42
|
"pi": {
|
|
43
43
|
"extensions": [],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/cocoindex",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "CocoIndex integration for Pi
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "CocoIndex integration for Pi — AST-aware content indexing, semantic vector search, and incremental pipeline management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"types": "index.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"typebox": "^1.1.38"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@pi-unipi/core": "2.1.
|
|
28
|
+
"@pi-unipi/core": "2.1.2"
|
|
29
29
|
},
|
|
30
30
|
"optionalDependencies": {
|
|
31
31
|
"@lancedb/lancedb": "^0.21.0"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/compactor",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Context engine for Pi
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Context engine for Pi — zero-LLM compaction, session continuity, sandbox execution, and tool display optimization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@pi-unipi/core": "2.1.
|
|
38
|
-
"@pi-unipi/info-screen": "2.1.
|
|
37
|
+
"@pi-unipi/core": "2.1.2",
|
|
38
|
+
"@pi-unipi/info-screen": "2.1.2",
|
|
39
39
|
"@earendil-works/pi-agent-core": "^0.80.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
@@ -51,8 +51,12 @@
|
|
|
51
51
|
"better-sqlite3": ">=11.0.0"
|
|
52
52
|
},
|
|
53
53
|
"pi": {
|
|
54
|
-
"extensions": [
|
|
55
|
-
|
|
54
|
+
"extensions": [
|
|
55
|
+
"./index.ts"
|
|
56
|
+
],
|
|
57
|
+
"skills": [
|
|
58
|
+
"./skills"
|
|
59
|
+
],
|
|
56
60
|
"prompts": [],
|
|
57
61
|
"themes": []
|
|
58
62
|
}
|
package/packages/core/utils.ts
CHANGED
|
@@ -240,3 +240,29 @@ export function emitEvent(
|
|
|
240
240
|
return false;
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Wrap an awaitable blocking-UI operation with herdr `blocked` state reporting.
|
|
246
|
+
*
|
|
247
|
+
* Emits `herdr:blocked` active (with a label) before awaiting `fn`, and
|
|
248
|
+
* inactive (with matching label) afterwards — including on throw. This lets
|
|
249
|
+
* the herdr integration surface `blocked` agent status for ask_user and other
|
|
250
|
+
* stop-and-continue UIs instead of `working`.
|
|
251
|
+
*
|
|
252
|
+
* The label should match what's shown to the user so herdr's sidebar reads
|
|
253
|
+
* meaningfully (e.g. "ask_user", "helper viewer").
|
|
254
|
+
*/
|
|
255
|
+
export async function withHerdrBlocked<T>(
|
|
256
|
+
pi: { events: { emit: (name: string, payload: unknown) => void } },
|
|
257
|
+
label: string,
|
|
258
|
+
fn: () => Promise<T>,
|
|
259
|
+
): Promise<T> {
|
|
260
|
+
const event = (active: boolean) =>
|
|
261
|
+
emitEvent(pi, "herdr:blocked", { active, label });
|
|
262
|
+
event(true);
|
|
263
|
+
try {
|
|
264
|
+
return await fn();
|
|
265
|
+
} finally {
|
|
266
|
+
event(false);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Persistent status bar for Unipi
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Persistent status bar for Unipi — subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "2.1.
|
|
35
|
+
"@pi-unipi/core": "2.1.2"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -96,6 +96,14 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
96
96
|
state.registry.registerGroup(group);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// ─── TPS streaming-event hooks (registered once) ────────────────────────
|
|
100
|
+
// pi.on() has no unsubscribe, so we register these exactly once at factory
|
|
101
|
+
// time (not per session_start) to avoid duplicate handlers accumulating
|
|
102
|
+
// across session restarts. The streamingIndex counter is reset on each
|
|
103
|
+
// session_shutdown. These hooks feed the TPS tracker in real time; the
|
|
104
|
+
// 1s branch-scan in the refresh timer only reconciles persisted messages.
|
|
105
|
+
wireTpsStreamingEvents(pi);
|
|
106
|
+
|
|
99
107
|
// ─── Session lifecycle ──────────────────────────────────────────────────
|
|
100
108
|
|
|
101
109
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -127,6 +135,7 @@ export default function footerExtension(pi: ExtensionAPI): void {
|
|
|
127
135
|
}
|
|
128
136
|
state.tuiRef = null;
|
|
129
137
|
tpsTracker.reset();
|
|
138
|
+
resetTpsStreamingIndex();
|
|
130
139
|
});
|
|
131
140
|
|
|
132
141
|
// ─── Register commands ──────────────────────────────────────────────────
|
|
@@ -152,10 +161,14 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
152
161
|
ctx.ui.setFooter((tui, _theme, footerData) => {
|
|
153
162
|
state.tuiRef = tui;
|
|
154
163
|
|
|
155
|
-
// Start periodic refresh for time-sensitive segments (e.g. clock)
|
|
164
|
+
// Start periodic refresh for time-sensitive segments (e.g. clock, TPS)
|
|
156
165
|
if (!state.refreshTimer) {
|
|
157
166
|
state.refreshTimer = setInterval(() => {
|
|
158
|
-
//
|
|
167
|
+
// Re-seed TPS tracker from the session branch on each tick.
|
|
168
|
+
// Streaming events (message_start/update/end) handle live updates
|
|
169
|
+
// in real time; this scan reconciles the tracker with persisted
|
|
170
|
+
// messages after compactions, branch switches, or session reloads
|
|
171
|
+
// where in-flight streaming state may have been lost.
|
|
159
172
|
try {
|
|
160
173
|
const piCtx = state.piContext as Record<string, unknown> | undefined;
|
|
161
174
|
if (piCtx?.sessionManager) {
|
|
@@ -168,9 +181,10 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
168
181
|
const m = e.message;
|
|
169
182
|
if (!m || m.role !== "assistant") continue;
|
|
170
183
|
if (m.stopReason === "error" || m.stopReason === "aborted") continue;
|
|
171
|
-
const output = m.usage?.output ?? 0;
|
|
172
184
|
const hasStop = !!m.stopReason;
|
|
173
|
-
|
|
185
|
+
// Pass the whole message: TPS tracker counts tokens from content,
|
|
186
|
+
// not from usage.output (which is 0 during streaming).
|
|
187
|
+
tpsTracker.onMessageUpdate(msgIndex, m, hasStop);
|
|
174
188
|
msgIndex++;
|
|
175
189
|
}
|
|
176
190
|
}
|
|
@@ -251,3 +265,56 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
251
265
|
};
|
|
252
266
|
}, { placement: "belowEditor" });
|
|
253
267
|
}
|
|
268
|
+
|
|
269
|
+
// ─── TPS streaming-event hooks ──────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Sequential index of the currently-streaming assistant message. Tracked
|
|
273
|
+
* locally because pi does not expose a stable message index on streaming
|
|
274
|
+
* events, and the TPS tracker keys records off this index.
|
|
275
|
+
*/
|
|
276
|
+
let tpsStreamingIndex = -1;
|
|
277
|
+
|
|
278
|
+
/** Reset the streaming index (called on session_shutdown). */
|
|
279
|
+
function resetTpsStreamingIndex(): void {
|
|
280
|
+
tpsStreamingIndex = -1;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Subscribe to pi's message streaming events and feed the TPS tracker in real
|
|
285
|
+
* time. This complements the 1s branch-scan in the refresh timer, which only
|
|
286
|
+
* sees persisted (completed) messages. Without these hooks the tracker would
|
|
287
|
+
* never observe an in-flight assistant message, so live TPS would stay frozen
|
|
288
|
+
* at the last completed message's value.
|
|
289
|
+
*
|
|
290
|
+
* Registered once at extension-factory time (pi.on has no unsubscribe, so we
|
|
291
|
+
* must not re-register per session_start or handlers would accumulate).
|
|
292
|
+
*/
|
|
293
|
+
function wireTpsStreamingEvents(pi: ExtensionAPI): void {
|
|
294
|
+
const safe = (fn: () => void) => {
|
|
295
|
+
try { fn(); } catch { /* TPS is best-effort */ }
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
pi.on("message_start", ((event: { message: unknown }) => safe(() => {
|
|
299
|
+
const m = event.message as Record<string, unknown> | undefined;
|
|
300
|
+
if (!m || m.role !== "assistant") return;
|
|
301
|
+
if (m.stopReason === "error" || m.stopReason === "aborted") return;
|
|
302
|
+
tpsStreamingIndex++;
|
|
303
|
+
tpsTracker.onMessageUpdate(tpsStreamingIndex, m, false);
|
|
304
|
+
})) as (event: unknown) => void);
|
|
305
|
+
|
|
306
|
+
pi.on("message_update", ((event: { message: unknown }) => safe(() => {
|
|
307
|
+
if (tpsStreamingIndex < 0) return;
|
|
308
|
+
const m = event.message as Record<string, unknown> | undefined;
|
|
309
|
+
if (!m || m.role !== "assistant") return;
|
|
310
|
+
tpsTracker.onMessageUpdate(tpsStreamingIndex, m, false);
|
|
311
|
+
})) as (event: unknown) => void);
|
|
312
|
+
|
|
313
|
+
pi.on("message_end", ((event: { message: unknown }) => safe(() => {
|
|
314
|
+
if (tpsStreamingIndex < 0) return;
|
|
315
|
+
const m = event.message as Record<string, unknown> | undefined;
|
|
316
|
+
if (!m || m.role !== "assistant") return;
|
|
317
|
+
if (m.stopReason === "error" || m.stopReason === "aborted") return;
|
|
318
|
+
tpsTracker.onMessageUpdate(tpsStreamingIndex, m, true);
|
|
319
|
+
})) as (event: unknown) => void);
|
|
320
|
+
}
|
|
@@ -4,37 +4,112 @@
|
|
|
4
4
|
* Per-message TPS calculation for live generation rate display.
|
|
5
5
|
* Tracks individual assistant messages with start/stop timestamps
|
|
6
6
|
* to measure generation rate excluding idle/tool-execution time.
|
|
7
|
+
*
|
|
8
|
+
* ## Token counting
|
|
9
|
+
*
|
|
10
|
+
* Output tokens are counted from the assistant message *content* (text +
|
|
11
|
+
* thinking blocks) via a lightweight word/char heuristic, NOT from
|
|
12
|
+
* `usage.output`. Rationale:
|
|
13
|
+
* - `usage.output` is only populated by the provider at stream end (or in
|
|
14
|
+
* a final `message_delta`), so it is 0 throughout generation and useless
|
|
15
|
+
* for a live rate.
|
|
16
|
+
* - Counting the streamed text ourselves gives a real, monotonically
|
|
17
|
+
* increasing numerator that reflects how much the model has actually
|
|
18
|
+
* produced so far.
|
|
19
|
+
* - This matches what users perceive as "tokens per second" (visible
|
|
20
|
+
* output rate) and avoids the previous bug where the displayed TPS was
|
|
21
|
+
* always inflated to 90+ t/s (caused by dividing a post-hoc `usage.output`
|
|
22
|
+
* total by a too-short, incorrectly measured elapsed window).
|
|
23
|
+
*
|
|
24
|
+
* The heuristic intentionally over-counts slightly (whitespace + punctuation
|
|
25
|
+
* are treated as tokens). That is acceptable for a live speed indicator and
|
|
26
|
+
* keeps the estimate in the right order of magnitude (within ~10-20% of the
|
|
27
|
+
* provider's reported output tokens for typical English/code output).
|
|
7
28
|
*/
|
|
8
29
|
|
|
9
30
|
/** Per-message TPS record */
|
|
10
31
|
interface MessageTpsRecord {
|
|
11
|
-
/** Message index in the session */
|
|
32
|
+
/** Message index in the session (sequential, assistant-only) */
|
|
12
33
|
messageIndex: number;
|
|
13
|
-
/**
|
|
34
|
+
/** Estimated output tokens produced so far for this message */
|
|
14
35
|
outputTokens: number;
|
|
15
|
-
/** When generation started (Date.now()) */
|
|
36
|
+
/** When generation started (Date.now(), ms) */
|
|
16
37
|
startedAt: number;
|
|
17
|
-
/** When generation completed (Date.now()), 0 if still generating */
|
|
38
|
+
/** When generation completed (Date.now(), ms), 0 if still generating */
|
|
18
39
|
completedAt: number;
|
|
19
|
-
/** Computed TPS for this message */
|
|
40
|
+
/** Computed TPS for this message (final, once completed) */
|
|
20
41
|
tps: number;
|
|
21
42
|
}
|
|
22
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Estimate the number of tokens in a string.
|
|
46
|
+
*
|
|
47
|
+
* Uses a cheap word + punctuation split. Good enough for a live TPS display;
|
|
48
|
+
* we deliberately avoid pulling in a full BPE tokenizer for performance and
|
|
49
|
+
* footprint reasons.
|
|
50
|
+
*/
|
|
51
|
+
function estimateTokens(text: string): number {
|
|
52
|
+
if (!text) return 0;
|
|
53
|
+
// Count word-ish tokens and standalone punctuation.
|
|
54
|
+
// \p{L} / \p{N} keep this Unicode-aware for non-ASCII content.
|
|
55
|
+
const wordish = text.match(/[\p{L}\p{N}]+/gu);
|
|
56
|
+
const punct = text.match(/[^\s\p{L}\p{N}]+/gu);
|
|
57
|
+
const wordCount = wordish ? wordish.length : 0;
|
|
58
|
+
const punctCount = punct ? punct.length : 0;
|
|
59
|
+
return wordCount + punctCount;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Estimate output tokens from an assistant message by summing text and
|
|
64
|
+
* thinking content lengths. Tool-call argument JSON is also counted since
|
|
65
|
+
* it represents model output.
|
|
66
|
+
*
|
|
67
|
+
* Accepts the raw message shape from pi's session entries (loosely typed).
|
|
68
|
+
*/
|
|
69
|
+
function estimateOutputTokens(message: unknown): number {
|
|
70
|
+
if (!message || typeof message !== "object") return 0;
|
|
71
|
+
const m = message as Record<string, unknown>;
|
|
72
|
+
if (m.role !== "assistant") return 0;
|
|
73
|
+
const content = m.content;
|
|
74
|
+
if (!Array.isArray(content)) return 0;
|
|
75
|
+
|
|
76
|
+
let total = 0;
|
|
77
|
+
for (const block of content) {
|
|
78
|
+
if (!block || typeof block !== "object") continue;
|
|
79
|
+
const b = block as Record<string, unknown>;
|
|
80
|
+
const type = b.type;
|
|
81
|
+
if (type === "text" && typeof b.text === "string") {
|
|
82
|
+
total += estimateTokens(b.text);
|
|
83
|
+
} else if (type === "thinking" && typeof b.thinking === "string") {
|
|
84
|
+
total += estimateTokens(b.thinking);
|
|
85
|
+
} else if (type === "tool_use" || type === "toolcall") {
|
|
86
|
+
// Count tool name + serialized arguments as model output.
|
|
87
|
+
const name = typeof b.name === "string" ? b.name : "";
|
|
88
|
+
const input = b.input ?? b.arguments;
|
|
89
|
+
let inputStr: string;
|
|
90
|
+
try {
|
|
91
|
+
inputStr = typeof input === "string" ? input : JSON.stringify(input ?? "");
|
|
92
|
+
} catch {
|
|
93
|
+
inputStr = "";
|
|
94
|
+
}
|
|
95
|
+
total += estimateTokens(name) + estimateTokens(inputStr);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return total;
|
|
99
|
+
}
|
|
100
|
+
|
|
23
101
|
/**
|
|
24
102
|
* Tracks per-message TPS and computes live/session metrics.
|
|
25
103
|
*
|
|
26
|
-
* Usage: Call `onMessageUpdate()`
|
|
27
|
-
* The tracker records generation start/stop
|
|
28
|
-
*
|
|
104
|
+
* Usage: Call `onMessageUpdate()` on every tick (e.g. 1s interval) with the
|
|
105
|
+
* current assistant message state. The tracker records generation start/stop
|
|
106
|
+
* per message and computes live TPS from elapsed wall-clock time.
|
|
29
107
|
*/
|
|
30
108
|
export class TpsTracker {
|
|
31
|
-
/** Per-message records */
|
|
109
|
+
/** Per-message records (one per assistant message, in order) */
|
|
32
110
|
private records: MessageTpsRecord[] = [];
|
|
33
111
|
|
|
34
|
-
/**
|
|
35
|
-
private lastSeenMessageCount = 0;
|
|
36
|
-
|
|
37
|
-
/** Total output tokens across all completed messages */
|
|
112
|
+
/** Total estimated output tokens across all messages */
|
|
38
113
|
private totalOutput = 0;
|
|
39
114
|
|
|
40
115
|
/**
|
|
@@ -42,15 +117,16 @@ export class TpsTracker {
|
|
|
42
117
|
* Call this on every tick (e.g. 1s interval) with the current state.
|
|
43
118
|
*
|
|
44
119
|
* @param messageIndex - Index of the assistant message (0-based, sequential)
|
|
45
|
-
* @param
|
|
120
|
+
* @param message - The assistant message object (content used to count tokens)
|
|
46
121
|
* @param hasStopReason - Whether this message has completed (has stopReason)
|
|
47
122
|
*/
|
|
48
|
-
onMessageUpdate(messageIndex: number,
|
|
123
|
+
onMessageUpdate(messageIndex: number, message: unknown, hasStopReason: boolean): void {
|
|
49
124
|
const now = Date.now();
|
|
125
|
+
const outputTokens = estimateOutputTokens(message);
|
|
50
126
|
|
|
51
127
|
// New message — create a record
|
|
52
128
|
if (messageIndex >= this.records.length) {
|
|
53
|
-
// Fill gaps if indices jump
|
|
129
|
+
// Fill gaps if indices jump (shouldn't normally happen)
|
|
54
130
|
while (this.records.length < messageIndex) {
|
|
55
131
|
this.records.push({
|
|
56
132
|
messageIndex: this.records.length,
|
|
@@ -62,11 +138,12 @@ export class TpsTracker {
|
|
|
62
138
|
}
|
|
63
139
|
|
|
64
140
|
if (hasStopReason && outputTokens > 0) {
|
|
65
|
-
// Fast message: already completed on first sighting
|
|
66
|
-
//
|
|
67
|
-
|
|
141
|
+
// Fast message: already completed on first sighting.
|
|
142
|
+
// We never observed it streaming, so we can't measure real duration.
|
|
143
|
+
// Fall back to a conservative estimate instead of inventing a 1s
|
|
144
|
+
// window (which previously produced inflated TPS values).
|
|
145
|
+
const estimatedDuration = Math.max(0.5, outputTokens / 60);
|
|
68
146
|
const tps = outputTokens / estimatedDuration;
|
|
69
|
-
|
|
70
147
|
this.records.push({
|
|
71
148
|
messageIndex,
|
|
72
149
|
outputTokens,
|
|
@@ -85,7 +162,6 @@ export class TpsTracker {
|
|
|
85
162
|
tps: 0,
|
|
86
163
|
});
|
|
87
164
|
}
|
|
88
|
-
this.lastSeenMessageCount = messageIndex + 1;
|
|
89
165
|
return;
|
|
90
166
|
}
|
|
91
167
|
|
|
@@ -93,23 +169,24 @@ export class TpsTracker {
|
|
|
93
169
|
const record = this.records[messageIndex];
|
|
94
170
|
if (!record) return;
|
|
95
171
|
|
|
96
|
-
record.
|
|
172
|
+
if (record.completedAt === 0) {
|
|
173
|
+
// Still generating — update token count (live TPS computed on demand)
|
|
174
|
+
record.outputTokens = outputTokens;
|
|
97
175
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Still generating — update output tokens (live TPS computed on demand)
|
|
176
|
+
if (hasStopReason) {
|
|
177
|
+
// Message just completed
|
|
178
|
+
record.completedAt = now;
|
|
179
|
+
const durationSec = (record.completedAt - record.startedAt) / 1000;
|
|
180
|
+
record.tps = durationSec > 0 ? record.outputTokens / durationSec : 0;
|
|
181
|
+
this.totalOutput += record.outputTokens;
|
|
182
|
+
}
|
|
106
183
|
}
|
|
107
184
|
}
|
|
108
185
|
|
|
109
186
|
/**
|
|
110
187
|
* Get the live TPS from the currently generating message.
|
|
111
188
|
* Returns the instantaneous rate based on tokens generated so far
|
|
112
|
-
* in the current message divided by elapsed time.
|
|
189
|
+
* in the current message divided by elapsed wall-clock time.
|
|
113
190
|
*/
|
|
114
191
|
getLiveTps(): number {
|
|
115
192
|
// Find the last record that's still generating
|
|
@@ -177,7 +254,7 @@ export class TpsTracker {
|
|
|
177
254
|
}
|
|
178
255
|
|
|
179
256
|
/**
|
|
180
|
-
* Get total output tokens for the session.
|
|
257
|
+
* Get total output tokens for the session (estimated).
|
|
181
258
|
*/
|
|
182
259
|
getTotalOutput(): number {
|
|
183
260
|
// Include tokens from incomplete messages too
|
|
@@ -195,7 +272,6 @@ export class TpsTracker {
|
|
|
195
272
|
*/
|
|
196
273
|
reset(): void {
|
|
197
274
|
this.records = [];
|
|
198
|
-
this.lastSeenMessageCount = 0;
|
|
199
275
|
this.totalOutput = 0;
|
|
200
276
|
}
|
|
201
277
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/info-screen",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Dashboard and module registry for Unipi
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Dashboard and module registry for Unipi — configurable info overlay with tabbed groups",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "2.1.
|
|
36
|
+
"@pi-unipi/core": "2.1.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -94,15 +94,24 @@ export class InfoOverlay implements Component {
|
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Fetch all groups in background. Each resolves independently.
|
|
97
|
+
*
|
|
98
|
+
* Each fetch is deferred to a macrotask (setTimeout 0) so the constructor
|
|
99
|
+
* returns immediately. Without this, getGroupData() runs each group's
|
|
100
|
+
* dataProvider synchronously up to its first `await` before yielding —
|
|
101
|
+
* heavy providers (usage stats parse 1GB+ of session files, memory scans)
|
|
102
|
+
* blocked the session_start handler for seconds.
|
|
97
103
|
*/
|
|
98
|
-
private
|
|
104
|
+
private fetchAllBackground(): void {
|
|
99
105
|
for (const group of this.groups) {
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
this.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
// Defer each fetch to a macrotask so the overlay constructs instantly.
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
if (this._destroyed) return;
|
|
109
|
+
infoRegistry.getGroupData(group.id).then(() => {
|
|
110
|
+
this.groupLoading.set(group.id, false);
|
|
111
|
+
}).catch(() => {
|
|
112
|
+
this.groupLoading.set(group.id, false);
|
|
113
|
+
});
|
|
114
|
+
}, 0);
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/input-shortcuts",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Keyboard shortcuts for stash/restore, undo/redo, clipboard, and thinking toggle
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Keyboard shortcuts for stash/restore, undo/redo, clipboard, and thinking toggle — chord-based overlay system",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "2.1.
|
|
36
|
+
"@pi-unipi/core": "2.1.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/kanboard",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Visualization layer for unipi workflow
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Visualization layer for unipi workflow — HTTP server with htmx/Alpine.js UI, modular parsers, TUI overlay, and kanban board",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@pi-unipi/core": "2.1.
|
|
42
|
+
"@pi-unipi/core": "2.1.2"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/mcp",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "MCP server management extension for Pi coding agent
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "MCP server management extension for Pi coding agent — browse, add, configure, and use MCP servers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"README.md"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pi-unipi/core": "2.1.
|
|
30
|
+
"@pi-unipi/core": "2.1.2"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -22,6 +22,10 @@ export const DEFAULT_PALACE = path.join(os.homedir(), ".mempalace", "palace");
|
|
|
22
22
|
|
|
23
23
|
const INSTALL_FLAG = path.join(os.homedir(), ".unipi", "memory", ".mempalace-install");
|
|
24
24
|
const MIGRATED_FLAG = path.join(os.homedir(), ".unipi", "memory", ".mempalace-migrated");
|
|
25
|
+
/** Flag written after a successful ping, so subsequent sessions can skip
|
|
26
|
+
* the ~0.5s Python cold-start sanity check. Stale after PING_VERIFIED_TTL_MS. */
|
|
27
|
+
const PING_VERIFIED_FLAG = path.join(os.homedir(), ".unipi", "memory", ".mempalace-ping-verified");
|
|
28
|
+
const PING_VERIFIED_TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
25
29
|
|
|
26
30
|
/** Path to the bundled bridge script. */
|
|
27
31
|
const BRIDGE_PATH = path.join(dirname(fileURLToPath(import.meta.url)), "bridge", "mempalace_bridge.py");
|
|
@@ -166,6 +170,30 @@ export function ensureMempalace(): MempalaceInstall | null {
|
|
|
166
170
|
/** Drop the cached install record (forces re-detection next session). */
|
|
167
171
|
export function invalidateInstallCache(): void {
|
|
168
172
|
try { if (fs.existsSync(INSTALL_FLAG)) fs.unlinkSync(INSTALL_FLAG); } catch { /* ignore */ }
|
|
173
|
+
invalidatePingVerified();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Was the palace ping-verified recently enough to trust without re-pinging? */
|
|
177
|
+
export function isPingVerified(): boolean {
|
|
178
|
+
try {
|
|
179
|
+
if (!fs.existsSync(PING_VERIFIED_FLAG)) return false;
|
|
180
|
+
const ts = Number.parseInt(fs.readFileSync(PING_VERIFIED_FLAG, "utf-8").trim(), 10);
|
|
181
|
+
if (!Number.isFinite(ts)) return false;
|
|
182
|
+
return Date.now() - ts < PING_VERIFIED_TTL_MS;
|
|
183
|
+
} catch { return false; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Mark the palace as ping-verified (written after a successful ping). */
|
|
187
|
+
export function markPingVerified(): void {
|
|
188
|
+
try {
|
|
189
|
+
fs.mkdirSync(path.dirname(PING_VERIFIED_FLAG), { recursive: true });
|
|
190
|
+
fs.writeFileSync(PING_VERIFIED_FLAG, String(Date.now()), "utf-8");
|
|
191
|
+
} catch { /* ignore */ }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Drop the ping-verified flag (forces a real ping next session). */
|
|
195
|
+
export function invalidatePingVerified(): void {
|
|
196
|
+
try { if (fs.existsSync(PING_VERIFIED_FLAG)) fs.unlinkSync(PING_VERIFIED_FLAG); } catch { /* ignore */ }
|
|
169
197
|
}
|
|
170
198
|
|
|
171
199
|
/** Has the one-way legacy migration been completed? */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/memory",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Persistent cross-session memory with MemPalace backend (auto-installed) and SQLite fallback for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"better-sqlite3": "^12.9.0",
|
|
44
44
|
"sqlite-vec": "^0.1.9",
|
|
45
45
|
"js-yaml": "^4.1.0",
|
|
46
|
-
"@pi-unipi/core": "2.1.
|
|
47
|
-
"@pi-unipi/info-screen": "2.1.
|
|
46
|
+
"@pi-unipi/core": "2.1.2",
|
|
47
|
+
"@pi-unipi/info-screen": "2.1.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -19,6 +19,9 @@ import {
|
|
|
19
19
|
runBridge,
|
|
20
20
|
isMigrated,
|
|
21
21
|
markMigrated,
|
|
22
|
+
isPingVerified,
|
|
23
|
+
markPingVerified,
|
|
24
|
+
invalidatePingVerified,
|
|
22
25
|
DEFAULT_PALACE,
|
|
23
26
|
type MempalaceInstall,
|
|
24
27
|
type MempalaceRecord,
|
|
@@ -262,6 +265,22 @@ export class MemoryStorage {
|
|
|
262
265
|
return this.backend === "mempalace" && this.mempalaceInstall !== null;
|
|
263
266
|
}
|
|
264
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Run a MemPalace bridge command, invalidating the ping-verified flag
|
|
270
|
+
* when the call fails. This ensures a palace that breaks after the
|
|
271
|
+
* startup ping-skip gets re-verified on the next session.
|
|
272
|
+
*/
|
|
273
|
+
private memPalaceCall<T>(cmd: string, args: Record<string, unknown> = {}): T | null {
|
|
274
|
+
const install = this.mempalaceInstall;
|
|
275
|
+
if (!install) return null;
|
|
276
|
+
const result = runBridge<T>(install, this.palacePath, cmd, args);
|
|
277
|
+
if (result === null) {
|
|
278
|
+
// Backend didn't respond — force a real ping next session.
|
|
279
|
+
invalidatePingVerified();
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
265
284
|
/**
|
|
266
285
|
* Initialize storage. Tries MemPalace first (auto-install + one-way
|
|
267
286
|
* auto-migration of legacy memories); falls back to SQLite if MemPalace
|
|
@@ -325,8 +344,14 @@ export class MemoryStorage {
|
|
|
325
344
|
if (!install) return false;
|
|
326
345
|
|
|
327
346
|
// Sanity ping — if the palace/bridge is broken, fall back.
|
|
328
|
-
|
|
329
|
-
|
|
347
|
+
// Skip the ~0.5s Python cold-start when we ping-verified recently;
|
|
348
|
+
// the flag is invalidated on any backend failure so a broken palace
|
|
349
|
+
// is re-checked on the next session.
|
|
350
|
+
if (!isPingVerified()) {
|
|
351
|
+
const ok = runBridge<string>(install, this.palacePath, "ping");
|
|
352
|
+
if (ok !== "pong") return false;
|
|
353
|
+
markPingVerified();
|
|
354
|
+
}
|
|
330
355
|
|
|
331
356
|
this.mempalaceInstall = install;
|
|
332
357
|
this.backend = "mempalace";
|
|
@@ -539,8 +564,7 @@ export class MemoryStorage {
|
|
|
539
564
|
* consistent and durable as a fallback source.
|
|
540
565
|
*/
|
|
541
566
|
private storeMempalace(record: MemoryRecord): void {
|
|
542
|
-
|
|
543
|
-
runBridge(install, this.palacePath, "store", {
|
|
567
|
+
this.memPalaceCall("store", {
|
|
544
568
|
record: {
|
|
545
569
|
id: record.id,
|
|
546
570
|
title: record.title,
|
|
@@ -572,8 +596,7 @@ export class MemoryStorage {
|
|
|
572
596
|
*/
|
|
573
597
|
syncOrphanedFiles(): number {
|
|
574
598
|
if (this.isMempalace()) {
|
|
575
|
-
const
|
|
576
|
-
const synced = runBridge<number>(install, this.palacePath, "sync_orphaned", {
|
|
599
|
+
const synced = this.memPalaceCall<number>("sync_orphaned", {
|
|
577
600
|
project_dir: this.scopeDir,
|
|
578
601
|
wing: this.projectName,
|
|
579
602
|
});
|
|
@@ -636,8 +659,7 @@ export class MemoryStorage {
|
|
|
636
659
|
*/
|
|
637
660
|
hasByTitle(title: string): boolean {
|
|
638
661
|
if (this.isMempalace()) {
|
|
639
|
-
|
|
640
|
-
return runBridge<boolean>(install, this.palacePath, "has_title", {
|
|
662
|
+
return this.memPalaceCall<boolean>("has_title", {
|
|
641
663
|
wing: this.projectName,
|
|
642
664
|
title,
|
|
643
665
|
}) ?? false;
|
|
@@ -654,9 +676,8 @@ export class MemoryStorage {
|
|
|
654
676
|
*/
|
|
655
677
|
findSimilarByTitle(title: string, threshold = 0.6): Array<{ record: MemoryRecord; similarity: number }> {
|
|
656
678
|
if (this.isMempalace()) {
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
install, this.palacePath, "find_similar",
|
|
679
|
+
const rows = this.memPalaceCall<Array<{ record: MempalaceRecord; similarity: number }>>(
|
|
680
|
+
"find_similar",
|
|
660
681
|
{ wing: this.projectName, title, threshold },
|
|
661
682
|
) ?? [];
|
|
662
683
|
return rows.map((r) => ({ record: toMemoryRecord(r.record), similarity: r.similarity }));
|
|
@@ -694,8 +715,7 @@ export class MemoryStorage {
|
|
|
694
715
|
*/
|
|
695
716
|
getById(id: string): MemoryRecord | null {
|
|
696
717
|
if (this.isMempalace()) {
|
|
697
|
-
const
|
|
698
|
-
const rec = runBridge<MempalaceRecord | null>(install, this.palacePath, "get", { id });
|
|
718
|
+
const rec = this.memPalaceCall<MempalaceRecord | null>("get", { id });
|
|
699
719
|
return rec ? toMemoryRecord(rec) : null;
|
|
700
720
|
}
|
|
701
721
|
if (!this.db) throw new Error("Storage not initialized");
|
|
@@ -721,8 +741,7 @@ export class MemoryStorage {
|
|
|
721
741
|
*/
|
|
722
742
|
getByTitle(title: string): MemoryRecord | null {
|
|
723
743
|
if (this.isMempalace()) {
|
|
724
|
-
const
|
|
725
|
-
const rec = runBridge<MempalaceRecord | null>(install, this.palacePath, "get_by_title", {
|
|
744
|
+
const rec = this.memPalaceCall<MempalaceRecord | null>("get_by_title", {
|
|
726
745
|
wing: this.projectName,
|
|
727
746
|
title,
|
|
728
747
|
});
|
|
@@ -768,8 +787,7 @@ export class MemoryStorage {
|
|
|
768
787
|
*/
|
|
769
788
|
listAll(): Array<{ id: string; title: string; type: string }> {
|
|
770
789
|
if (this.isMempalace()) {
|
|
771
|
-
const
|
|
772
|
-
const items = runBridge<MempalaceListItem[]>(install, this.palacePath, "list", {
|
|
790
|
+
const items = this.memPalaceCall<MempalaceListItem[]>("list", {
|
|
773
791
|
wing: this.projectName,
|
|
774
792
|
}) ?? [];
|
|
775
793
|
return items;
|
|
@@ -785,8 +803,7 @@ export class MemoryStorage {
|
|
|
785
803
|
*/
|
|
786
804
|
delete(id: string): boolean {
|
|
787
805
|
if (this.isMempalace()) {
|
|
788
|
-
const
|
|
789
|
-
const ok = runBridge<boolean>(install, this.palacePath, "delete", { id }) ?? false;
|
|
806
|
+
const ok = this.memPalaceCall<boolean>("delete", { id }) ?? false;
|
|
790
807
|
// Also remove the markdown tier if present.
|
|
791
808
|
try {
|
|
792
809
|
const mdPath = path.join(this.scopeDir, `${id}.md`);
|
|
@@ -833,8 +850,7 @@ export class MemoryStorage {
|
|
|
833
850
|
*/
|
|
834
851
|
search(query: string, limit = 10, embedding?: Float32Array | null): SearchResult[] {
|
|
835
852
|
if (this.isMempalace()) {
|
|
836
|
-
const
|
|
837
|
-
const rows = runBridge<MempalaceSearchResult[]>(install, this.palacePath, "search", {
|
|
853
|
+
const rows = this.memPalaceCall<MempalaceSearchResult[]>("search", {
|
|
838
854
|
query,
|
|
839
855
|
wing: this.projectName,
|
|
840
856
|
limit,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/milestone",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Lifecycle layer for project-level goals
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Lifecycle layer for project-level goals — MILESTONES.md tracking, session hooks, auto-sync",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@pi-unipi/core": "2.1.
|
|
32
|
+
"@pi-unipi/core": "2.1.2"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/ralph",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Long-running iterative development loops for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pi-unipi/core": "2.1.
|
|
31
|
-
"@pi-unipi/info-screen": "2.1.
|
|
30
|
+
"@pi-unipi/core": "2.1.2",
|
|
31
|
+
"@pi-unipi/info-screen": "2.1.2"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@earendil-works/pi-ai": "^0.80.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/subagents",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Subagents for UniPi
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Subagents for UniPi — parallel execution, file locking, workflow integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"test": "node --test src/__tests__/*.test.ts"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@pi-unipi/core": "2.1.
|
|
21
|
-
"@pi-unipi/workflow": "2.1.
|
|
22
|
-
"@pi-unipi/info-screen": "2.1.
|
|
20
|
+
"@pi-unipi/core": "2.1.2",
|
|
21
|
+
"@pi-unipi/workflow": "2.1.2",
|
|
22
|
+
"@pi-unipi/info-screen": "2.1.2",
|
|
23
23
|
"@earendil-works/pi-agent-core": "^0.80.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
@@ -12,7 +12,7 @@ import { Type } from "typebox";
|
|
|
12
12
|
import { existsSync, readdirSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
|
-
import { emitEvent, MODULES, UNIPI_EVENTS, type UnipiBadgeGenerateRequestEvent } from "@pi-unipi/core";
|
|
15
|
+
import { emitEvent, MODULES, UNIPI_EVENTS, withHerdrBlocked, type UnipiBadgeGenerateRequestEvent } from "@pi-unipi/core";
|
|
16
16
|
import { AgentManager } from "./agent-manager.js";
|
|
17
17
|
import { initConfig } from "./config.js";
|
|
18
18
|
import { type AgentActivity, type NotificationDetails, BUILTIN_TYPES } from "./types.js";
|
|
@@ -758,28 +758,32 @@ Guidelines:
|
|
|
758
758
|
// Open conversation viewer overlay if requested
|
|
759
759
|
if (params.view && record.session) {
|
|
760
760
|
const activity = agentActivity.get(record.id);
|
|
761
|
-
await
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
761
|
+
await withHerdrBlocked(
|
|
762
|
+
pi,
|
|
763
|
+
"helper viewer",
|
|
764
|
+
() => ctx.ui.custom<undefined>(
|
|
765
|
+
(tui, theme, _keybindings, done) => {
|
|
766
|
+
return new ConversationViewer(
|
|
767
|
+
tui,
|
|
768
|
+
record.session!,
|
|
769
|
+
{
|
|
770
|
+
type: record.type,
|
|
771
|
+
description: record.description,
|
|
772
|
+
status: record.status,
|
|
773
|
+
toolUses: record.toolUses,
|
|
774
|
+
startedAt: record.startedAt,
|
|
775
|
+
completedAt: record.completedAt,
|
|
776
|
+
},
|
|
777
|
+
activity,
|
|
778
|
+
theme,
|
|
779
|
+
done,
|
|
780
|
+
);
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
overlay: true,
|
|
784
|
+
overlayOptions: { anchor: "center", width: "90%" },
|
|
785
|
+
},
|
|
786
|
+
),
|
|
783
787
|
);
|
|
784
788
|
}
|
|
785
789
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/updater",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Auto-updater, changelog browser, and readme browser for Unipi
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Auto-updater, changelog browser, and readme browser for Unipi — checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "2.1.
|
|
36
|
+
"@pi-unipi/core": "2.1.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/utility",
|
|
3
|
-
"version": "2.1.
|
|
4
|
-
"description": "Utility commands and tools for Pi coding agent
|
|
3
|
+
"version": "2.1.2",
|
|
4
|
+
"description": "Utility commands and tools for Pi coding agent — lifecycle, diagnostics, cache, analytics, display, batch execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@pi-unipi/core": "2.1.
|
|
37
|
+
"@pi-unipi/core": "2.1.2",
|
|
38
38
|
"diff": "^7.0.0",
|
|
39
39
|
"@shikijs/cli": "^4.0.2"
|
|
40
40
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/web-api",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Web search, read, and summarize tools with provider-based backend selection for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@pi-unipi/core": "2.1.
|
|
34
|
+
"@pi-unipi/core": "2.1.2",
|
|
35
35
|
"defuddle": "^0.18.1",
|
|
36
36
|
"linkedom": "^0.18.12",
|
|
37
37
|
"lodash": "^4.17.21",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/workflow",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Structured development workflow commands for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@pi-unipi/core": "2.1.
|
|
29
|
+
"@pi-unipi/core": "2.1.2"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@earendil-works/pi-ai": "^0.80.0",
|