@pi-unipi/unipi 3.0.0-alpha.41 → 3.0.0-alpha.42
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 +8 -0
- package/package.json +23 -23
- package/packages/app-bridge/package.json +2 -2
- package/packages/app-bridge/src/bridge.ts +9 -2
- package/packages/app-bridge/src/snapshot.ts +26 -0
- package/packages/app-bridge/src/wire.ts +5 -2
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/background-tasks/package.json +2 -2
- package/packages/btw/package.json +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/core/index.ts +1 -0
- package/packages/core/package.json +1 -1
- package/packages/core/src/skill-conditions.ts +68 -0
- package/packages/core/src/work/tray.ts +70 -9
- package/packages/dream/package.json +2 -2
- package/packages/footer/package.json +3 -3
- package/packages/footer/src/index.ts +1 -1
- package/packages/fusion/package.json +4 -4
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/package.json +7 -7
- package/packages/long-horizon/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +3 -3
- package/packages/notify/package.json +2 -2
- package/packages/skill-registry/index.ts +16 -4
- package/packages/skill-registry/package.json +2 -2
- package/packages/subagents/package.json +3 -3
- package/packages/subagents/src/index.ts +1 -1
- package/packages/unipi/bundled.js +743 -509
- package/packages/updater/package.json +2 -2
- package/packages/utility/package.json +2 -2
- package/packages/utility/skills/browser/SKILL.md +73 -0
- package/packages/utility/src/browser/state.ts +218 -0
- package/packages/utility/src/index.ts +24 -0
- package/packages/watchdog/package.json +3 -3
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [3.0.0-alpha.42] — 2026-10-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `skill:browser`: the agent uses the PC's browser, the one you see in the UniPi app, with `agent-browser`. While the browser is on, UniPi points agent-browser at it (`AGENT_BROWSER_CDP`), so `agent-browser open <url>` just works, with no start or connect step. The browser is CloakBrowser, a stealthy Chromium shown only in the app; the agent's clicks and typing look human, and you can take over. Needs UniPi host 0.5.0. (UNI-295, UNI-303)
|
|
13
|
+
- The agent sees `skill:browser` and a one-line note only while the PC's browser is on. Turning it off hides both from the next prompt, and `/skill:browser` explains how to turn it on. Any skill can now be shown only when a condition holds (`registerSkillCondition`). (UNI-300)
|
|
14
|
+
- The work tray has a hint again: while it has items, one dim line under the input reads, for example, "Background tasks 1 running · Subagents 2 · ↓ open". (UNI-291)
|
|
15
|
+
- App: "Start from here" on one of your messages puts that message, with its images, back in the app's input, like `/tree` in the terminal. (UNI-290)
|
|
16
|
+
|
|
9
17
|
## [3.0.0-alpha.41] — 2026-10-13
|
|
10
18
|
|
|
11
19
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -80,28 +80,28 @@
|
|
|
80
80
|
"typebox": "^1.1.38"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"@pi-unipi/app-bridge": "3.0.0-alpha.
|
|
84
|
-
"@pi-unipi/ask-user": "3.0.0-alpha.
|
|
85
|
-
"@pi-unipi/background-tasks": "3.0.0-alpha.
|
|
86
|
-
"@pi-unipi/btw": "3.0.0-alpha.
|
|
87
|
-
"@pi-unipi/command-enchantment": "3.0.0-alpha.
|
|
88
|
-
"@pi-unipi/compactor": "3.0.0-alpha.
|
|
89
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
90
|
-
"@pi-unipi/dream": "3.0.0-alpha.
|
|
91
|
-
"@pi-unipi/footer": "3.0.0-alpha.
|
|
92
|
-
"@pi-unipi/fusion": "3.0.0-alpha.
|
|
93
|
-
"@pi-unipi/info-screen": "3.0.0-alpha.
|
|
94
|
-
"@pi-unipi/input-shortcuts": "3.0.0-alpha.
|
|
95
|
-
"@pi-unipi/kanboard": "3.0.0-alpha.
|
|
96
|
-
"@pi-unipi/mcp": "3.0.0-alpha.
|
|
97
|
-
"@pi-unipi/memory": "3.0.0-alpha.
|
|
98
|
-
"@pi-unipi/notify": "3.0.0-alpha.
|
|
99
|
-
"@pi-unipi/skill-registry": "3.0.0-alpha.
|
|
100
|
-
"@pi-unipi/subagents": "3.0.0-alpha.
|
|
101
|
-
"@pi-unipi/updater": "3.0.0-alpha.
|
|
102
|
-
"@pi-unipi/utility": "3.0.0-alpha.
|
|
103
|
-
"@pi-unipi/web-api": "3.0.0-alpha.
|
|
104
|
-
"@pi-unipi/workflow": "3.0.0-alpha.
|
|
83
|
+
"@pi-unipi/app-bridge": "3.0.0-alpha.42",
|
|
84
|
+
"@pi-unipi/ask-user": "3.0.0-alpha.42",
|
|
85
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.42",
|
|
86
|
+
"@pi-unipi/btw": "3.0.0-alpha.42",
|
|
87
|
+
"@pi-unipi/command-enchantment": "3.0.0-alpha.42",
|
|
88
|
+
"@pi-unipi/compactor": "3.0.0-alpha.42",
|
|
89
|
+
"@pi-unipi/core": "3.0.0-alpha.42",
|
|
90
|
+
"@pi-unipi/dream": "3.0.0-alpha.42",
|
|
91
|
+
"@pi-unipi/footer": "3.0.0-alpha.42",
|
|
92
|
+
"@pi-unipi/fusion": "3.0.0-alpha.42",
|
|
93
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.42",
|
|
94
|
+
"@pi-unipi/input-shortcuts": "3.0.0-alpha.42",
|
|
95
|
+
"@pi-unipi/kanboard": "3.0.0-alpha.42",
|
|
96
|
+
"@pi-unipi/mcp": "3.0.0-alpha.42",
|
|
97
|
+
"@pi-unipi/memory": "3.0.0-alpha.42",
|
|
98
|
+
"@pi-unipi/notify": "3.0.0-alpha.42",
|
|
99
|
+
"@pi-unipi/skill-registry": "3.0.0-alpha.42",
|
|
100
|
+
"@pi-unipi/subagents": "3.0.0-alpha.42",
|
|
101
|
+
"@pi-unipi/updater": "3.0.0-alpha.42",
|
|
102
|
+
"@pi-unipi/utility": "3.0.0-alpha.42",
|
|
103
|
+
"@pi-unipi/web-api": "3.0.0-alpha.42",
|
|
104
|
+
"@pi-unipi/workflow": "3.0.0-alpha.42",
|
|
105
105
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
106
106
|
"beautiful-mermaid": "^1.1.3"
|
|
107
107
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/app-bridge",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Live bridge between a pi TUI session and the UniPi phone app — streaming chat, prompts, steering and dialogs from the phone",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
34
|
+
"@pi-unipi/core": "3.0.0-alpha.42"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -14,7 +14,7 @@ import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync
|
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { join, relative, resolve as resolvePath } from "node:path";
|
|
16
16
|
import { DialogHub, wrapUi } from "./dialogs.js";
|
|
17
|
-
import { ENTRIES_BUDGET, HELLO_ENTRIES_BUDGET, LINE_BUDGET, fitLine, historyPage, jsonBytes, phoneSafe, snapshotEntries, clipText, editedAway, wantedEntry } from "./snapshot.js";
|
|
17
|
+
import { ENTRIES_BUDGET, HELLO_ENTRIES_BUDGET, LINE_BUDGET, fitLine, historyPage, jsonBytes, phoneSafe, snapshotEntries, clipText, editedAway, wantedEntry, treeGoRestore, type TreeGoRestore } from "./snapshot.js";
|
|
18
18
|
import { searchSession } from "./search.js";
|
|
19
19
|
import {
|
|
20
20
|
BRIDGE_PROTOCOL,
|
|
@@ -1510,11 +1510,18 @@ export function createBridge(pi: ExtensionAPI, deps: BridgeDeps = defaultDeps) {
|
|
|
1510
1510
|
case "tree_go": {
|
|
1511
1511
|
if (!(await ensureIdle(msg.force))) return write(sock, { t: "error", message: "pi is busy. Stop the current run first.", code: "busy", ref: msg.ref });
|
|
1512
1512
|
try {
|
|
1513
|
+
let restore: TreeGoRestore | undefined;
|
|
1513
1514
|
await runCommandOp(async (cctx) => {
|
|
1515
|
+
// Read before navigating: the target is a user message → pi moves
|
|
1516
|
+
// the leaf to just before it, and (like /tree) its text goes back
|
|
1517
|
+
// to the composer (UNI-290).
|
|
1518
|
+
const target = cctx.sessionManager.getEntry?.(msg.id);
|
|
1514
1519
|
const result = await cctx.navigateTree(msg.id, { summarize: msg.summarize });
|
|
1515
1520
|
if (result.cancelled) throw new Error("Cancelled");
|
|
1521
|
+
const leaf = cctx.sessionManager.getLeafId?.() ?? null;
|
|
1522
|
+
restore = treeGoRestore(target, (result as { editorText?: unknown }).editorText, leaf);
|
|
1516
1523
|
});
|
|
1517
|
-
ack();
|
|
1524
|
+
write(sock, { t: "ack", ref: msg.ref, ...(restore ?? {}) });
|
|
1518
1525
|
} catch (error) {
|
|
1519
1526
|
fail(error instanceof Error ? error.message : String(error));
|
|
1520
1527
|
}
|
|
@@ -273,3 +273,29 @@ export function historyPage(branch: readonly unknown[], before: string, budget =
|
|
|
273
273
|
}
|
|
274
274
|
return { entries: out.reverse(), more };
|
|
275
275
|
}
|
|
276
|
+
|
|
277
|
+
/** UNI-290: what goes back into the app's composer after `tree_go` to a user
|
|
278
|
+
* message — its text (pi's own `editorText` when the runtime passes it on,
|
|
279
|
+
* else read off the entry, joined the same way pi does) and its images as
|
|
280
|
+
* the usual phone-safe refs (inline thumbnail or a `mediaRef`). */
|
|
281
|
+
export type TreeGoRestore = { restore: { text: string; images?: Array<{ mime: string; data?: string; mediaRef?: string; bytes?: number }> } };
|
|
282
|
+
|
|
283
|
+
/** The ack extras for a `tree_go` that landed: only when `target` is a user
|
|
284
|
+
* message and pi really moved the leaf off it (to its parent) — a no-op
|
|
285
|
+
* navigation (already there) restores nothing. */
|
|
286
|
+
export function treeGoRestore(target: unknown, editorText: unknown, leafAfter: string | null): TreeGoRestore | undefined {
|
|
287
|
+
const e = target as { id?: string; type?: string; message?: { role?: string; content?: unknown } } | null | undefined;
|
|
288
|
+
if (!e || e.type !== "message" || e.message?.role !== "user") return undefined;
|
|
289
|
+
if (leafAfter !== null && leafAfter === e.id) return undefined;
|
|
290
|
+
const content = e.message.content;
|
|
291
|
+
const parts = Array.isArray(content) ? (content as Array<{ type?: string; text?: unknown }>) : [];
|
|
292
|
+
const fromEntry = typeof content === "string" ? content : parts.filter((p) => p?.type === "text" && typeof p.text === "string").map((p) => p.text as string).join("");
|
|
293
|
+
const text = typeof editorText === "string" ? editorText : fromEntry;
|
|
294
|
+
const images = parts
|
|
295
|
+
.filter((p) => p?.type === "image")
|
|
296
|
+
.map((p) => phoneSafe(p) as { mime?: string; data?: string; mediaRef?: string; bytes?: number } | null)
|
|
297
|
+
.filter((p): p is { mime?: string; data?: string; mediaRef?: string; bytes?: number } => !!p && (typeof p.data === "string" || typeof p.mediaRef === "string"))
|
|
298
|
+
.map((p) => ({ mime: p.mime ?? "image/*", ...(p.data ? { data: p.data } : {}), ...(p.mediaRef ? { mediaRef: p.mediaRef } : {}), ...(typeof p.bytes === "number" ? { bytes: p.bytes } : {}) }));
|
|
299
|
+
if (!text && !images.length) return undefined;
|
|
300
|
+
return { restore: { text: clipText(text, TEXT_CLIP), ...(images.length ? { images } : {}) } };
|
|
301
|
+
}
|
|
@@ -275,8 +275,11 @@ export type OutMsg =
|
|
|
275
275
|
| { t: "btw_list"; pages: BtwListPage[]; complete?: true; ref?: string }
|
|
276
276
|
/** `code: "busy"`: new/resume/fork/tree_go while pi is running — the phone may retry with `force:true`. */
|
|
277
277
|
| { t: "error"; message: string; ref?: string; code?: "busy" }
|
|
278
|
-
/** `as: "command"`: the prompt ran an extension command (no chat message follows).
|
|
279
|
-
|
|
278
|
+
/** `as: "command"`: the prompt ran an extension command (no chat message follows).
|
|
279
|
+
* `restore` (UNI-290, `tree_go` only): the target was a user message, so pi
|
|
280
|
+
* moved the leaf to just before it — its text (and images, phone-safe refs)
|
|
281
|
+
* go back into the phone's composer, like /tree puts it in the TUI editor. */
|
|
282
|
+
| { t: "ack"; ref?: string; as?: "command"; restore?: { text: string; images?: Array<{ mime: string; data?: string; mediaRef?: string; bytes?: number }> } }
|
|
280
283
|
/** The session is being replaced (new/resume/fork/reload): reconnect to get the new session's hello. */
|
|
281
284
|
| { t: "reconnect"; reason: string }
|
|
282
285
|
/** A page of older entries (oldest first) ending right before `before`. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/ask-user",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Structured user input tool for Pi coding agent — single-select, multi-select, freeform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
45
|
+
"@pi-unipi/core": "3.0.0-alpha.42"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/background-tasks",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Background tasks for UniPi — durable shell jobs and delegated background agents with a footer dock",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@earendil-works/pi-ai": "^0.87.1",
|
|
13
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
13
|
+
"@pi-unipi/core": "3.0.0-alpha.42",
|
|
14
14
|
"turndown": "^7.2.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/btw",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
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",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@earendil-works/pi-tui": "^0.87.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
40
|
+
"@pi-unipi/core": "3.0.0-alpha.42"
|
|
41
41
|
},
|
|
42
42
|
"pi": {
|
|
43
43
|
"extensions": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/compactor",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Compaction for Pi — lossless zero-LLM or model summaries, rebuilt from full history, with session recall",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
37
|
-
"@pi-unipi/info-screen": "3.0.0-alpha.
|
|
36
|
+
"@pi-unipi/core": "3.0.0-alpha.42",
|
|
37
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.42",
|
|
38
38
|
"@earendil-works/pi-agent-core": "^0.87.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
package/packages/core/index.ts
CHANGED
|
@@ -54,3 +54,4 @@ export * from "./src/hints/store.js";
|
|
|
54
54
|
export { CRAB_14_LINES_256, CRAB_14_LINES_TRUECOLOR, CRAB_22_LINES_256, CRAB_22_LINES_TRUECOLOR } from "./src/hints/crab-data.js";
|
|
55
55
|
export * from "./harness-messages.js";
|
|
56
56
|
export * from "./remote-dialogs.js";
|
|
57
|
+
export * from "./src/skill-conditions.js";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/core — skill visibility conditions (UNI-300)
|
|
3
|
+
*
|
|
4
|
+
* A generic, tiny gate any module can use to hide one of its bundled
|
|
5
|
+
* skills from the model when some runtime fact says it can't be used
|
|
6
|
+
* right now (e.g. the PC's browser is off). Skill-registry (and anything
|
|
7
|
+
* else shaping `options.skills`) calls `skillConditionsPass` per skill
|
|
8
|
+
* name; a module that owns the fact calls `registerSkillCondition` once
|
|
9
|
+
* at load time with a cheap, synchronous predicate.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately process-global like the rest of core's small registries
|
|
12
|
+
* (command-runner, settings): modules load once per process, and this
|
|
13
|
+
* needs no session/ctx threading. A condition must never throw or block;
|
|
14
|
+
* callers treat a throwing condition as "false" (hidden) defensively but
|
|
15
|
+
* conditions should fail open themselves when unsure.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type SkillCondition = () => boolean;
|
|
19
|
+
|
|
20
|
+
const conditions = new Map<string, SkillCondition>();
|
|
21
|
+
|
|
22
|
+
/** Registers (or replaces) the visibility condition for a skill name. */
|
|
23
|
+
export function registerSkillCondition(skillName: string, condition: SkillCondition): void {
|
|
24
|
+
conditions.set(skillName, condition);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function unregisterSkillCondition(skillName: string): void {
|
|
28
|
+
conditions.delete(skillName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** True when the skill has no registered condition, or its condition returns true. A throwing
|
|
32
|
+
* condition counts as false (hidden) — safer than crashing the turn to show a broken skill. */
|
|
33
|
+
export function skillConditionPasses(skillName: string): boolean {
|
|
34
|
+
const condition = conditions.get(skillName);
|
|
35
|
+
if (!condition) return true;
|
|
36
|
+
try {
|
|
37
|
+
return condition();
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Reason a skill is currently hidden (for a clear `/skill:name` refusal), or undefined when
|
|
44
|
+
* it isn't gated by a condition, or its condition is registered but no reason was given. */
|
|
45
|
+
export type SkillConditionReason = () => string | undefined;
|
|
46
|
+
|
|
47
|
+
const reasons = new Map<string, SkillConditionReason>();
|
|
48
|
+
|
|
49
|
+
/** Optional: a human reason shown when `/skill:name` is blocked because the condition is false. */
|
|
50
|
+
export function registerSkillConditionReason(skillName: string, reason: SkillConditionReason): void {
|
|
51
|
+
reasons.set(skillName, reason);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function skillConditionReason(skillName: string): string | undefined {
|
|
55
|
+
const fn = reasons.get(skillName);
|
|
56
|
+
if (!fn) return undefined;
|
|
57
|
+
try {
|
|
58
|
+
return fn();
|
|
59
|
+
} catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Test hook. */
|
|
65
|
+
export function resetSkillConditionsForTests(): void {
|
|
66
|
+
conditions.clear();
|
|
67
|
+
reasons.clear();
|
|
68
|
+
}
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
* ── ▌Background tasks (2 · 1 running)▐ Subagents (3) ───── ←→ tabs · esc close
|
|
7
7
|
* <the active tab's pane: the bg task list/logs, or the subagent list/transcript>
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* While the tray is closed and has items, ONE dim hint line sits under the
|
|
10
|
+
* editor (UNI-291) — `Background tasks 1 running · Subagents 2 · ↓ open` —
|
|
11
|
+
* so ↓ is discoverable; nothing at all when the tray is empty or open (no
|
|
12
|
+
* strip chrome, no per-item spinners).
|
|
10
13
|
*
|
|
11
14
|
* Owners register a tab (`registerWorkTrayTab`) and supply the pane; this
|
|
12
15
|
* module owns the tab strip, ←/→ switching, the ↓ key and the open/close
|
|
@@ -63,11 +66,9 @@ export interface WorkTrayTab {
|
|
|
63
66
|
|
|
64
67
|
export type TrayTheme = Pick<Theme, "fg" | "bold">;
|
|
65
68
|
|
|
66
|
-
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* lines messy); the footer's single "Working… · ↓ open" line is the only
|
|
70
|
-
* hint that wake-capable work is pending. */
|
|
69
|
+
/** The below-editor widget: learns the TUI (for the ↓ focus check) and draws
|
|
70
|
+
* the one-line hint while the tray is closed and has items (UNI-291). The
|
|
71
|
+
* old "◆ …" strip + per-agent spinner lines stay gone (5b7f2e1: messy). */
|
|
71
72
|
const STRIP_KEY = "work-tray-strip";
|
|
72
73
|
export const TRAY_HINT = "←→ tabs · esc close";
|
|
73
74
|
|
|
@@ -261,6 +262,47 @@ export class WorkTray implements Component {
|
|
|
261
262
|
}
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
// ── hint line (below the editor while the tray is closed) ─────────────────────
|
|
266
|
+
|
|
267
|
+
export const TRAY_OPEN_HINT = "↓ open";
|
|
268
|
+
|
|
269
|
+
/** One tab's part of the hint: `Label N`, `Label N running` (all running) or
|
|
270
|
+
* `Label N (k running)`; level 1 uses the short label, level 2 also the
|
|
271
|
+
* compact `Short N/k` (k running of N). */
|
|
272
|
+
function hintPart(tab: WorkTrayTab, c: WorkTrayCounts, level: number): string {
|
|
273
|
+
const label = level > 0 ? (tab.shortLabel ?? tab.label) : tab.label;
|
|
274
|
+
const n = String(c.total);
|
|
275
|
+
if (c.running <= 0) return `${label} ${n}`;
|
|
276
|
+
if (level >= 2) return `${label} ${n}/${String(c.running)}`;
|
|
277
|
+
return c.running >= c.total ? `${label} ${n} running` : `${label} ${n} (${String(c.running)} running)`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** The plain hint text (no colour) for `width` columns, or undefined when no
|
|
281
|
+
* visible tab has items. Full labels, then short labels, then compact
|
|
282
|
+
* counts; past that the counts are cut so "↓ open" always stays visible. */
|
|
283
|
+
export function trayHintText(list: readonly WorkTrayTab[], width: number): string | undefined {
|
|
284
|
+
const items = list.map((tab) => ({ tab, c: safeCounts(tab) })).filter(({ c }) => c.total > 0);
|
|
285
|
+
if (items.length === 0 || width <= 0) return undefined;
|
|
286
|
+
const suffix = ` · ${TRAY_OPEN_HINT}`;
|
|
287
|
+
let line = "";
|
|
288
|
+
for (const level of [0, 1, 2]) {
|
|
289
|
+
line = items.map(({ tab, c }) => hintPart(tab, c, level)).join(" · ");
|
|
290
|
+
if (visibleWidth(line) + visibleWidth(suffix) <= width) return `${line}${suffix}`;
|
|
291
|
+
}
|
|
292
|
+
const room = width - visibleWidth(suffix);
|
|
293
|
+
if (room < 4) return truncateToWidth(TRAY_OPEN_HINT, width, "");
|
|
294
|
+
return `${truncateToWidth(line, room, "…")}${suffix}`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** The below-editor hint: [] when the tray is open or empty, else one dim
|
|
298
|
+
* line one column short of the terminal (exactly-full-width lines desync
|
|
299
|
+
* wrapping terminals — footer issue #31), indented like the editor text. */
|
|
300
|
+
export function renderTrayHint(theme: TrayTheme, list: readonly WorkTrayTab[], width: number, open = false): string[] {
|
|
301
|
+
if (open || width <= 2) return [];
|
|
302
|
+
const text = trayHintText(list, width - 2);
|
|
303
|
+
return text === undefined ? [] : [` ${theme.fg("dim", text)}`];
|
|
304
|
+
}
|
|
305
|
+
|
|
264
306
|
// ── registration + lifecycle ────────────────────────────────────────────────
|
|
265
307
|
|
|
266
308
|
/** True while the tray is open. */
|
|
@@ -301,10 +343,29 @@ function ensureWorkTray(pi: ExtensionAPI): void {
|
|
|
301
343
|
unsubInput?.();
|
|
302
344
|
unsubInput = undefined;
|
|
303
345
|
if (!ctx.hasUI) return;
|
|
304
|
-
//
|
|
305
|
-
|
|
346
|
+
// The hint line under the editor (UNI-291), also the TUI ref for the
|
|
347
|
+
// ↓ focus check. Renders nothing with no items, so idle sessions show
|
|
348
|
+
// no line; work changes re-render it (the TUI diffs, so an unchanged
|
|
349
|
+
// line costs nothing and never moves focus).
|
|
350
|
+
ctx.ui.setWidget(STRIP_KEY, (tui, theme) => {
|
|
306
351
|
tuiRef = tui;
|
|
307
|
-
|
|
352
|
+
let unsub: () => void = () => {};
|
|
353
|
+
try {
|
|
354
|
+
unsub = subscribeWorkChanges(() => tui.requestRender());
|
|
355
|
+
} catch {
|
|
356
|
+
/* the line is still right on the next render */
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
render: (width: number) => {
|
|
360
|
+
try {
|
|
361
|
+
return renderTrayHint(theme, sortedTabs(), width, openTray !== undefined);
|
|
362
|
+
} catch {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
invalidate() {},
|
|
367
|
+
dispose: () => unsub(),
|
|
368
|
+
};
|
|
308
369
|
}, { placement: "belowEditor" });
|
|
309
370
|
unsubInput = ctx.ui.onTerminalInput(onTerminalInput);
|
|
310
371
|
} catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/dream",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "A pi extension that dreams: reviews past session struggles in the background, turns repeated false paths into memory lessons, and crafts skills from errors — part of the Unipi suite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "extensions/dream.ts",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@earendil-works/pi-tui": "^0.87.1"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
42
|
+
"@pi-unipi/core": "3.0.0-alpha.42"
|
|
43
43
|
},
|
|
44
44
|
"pi": {
|
|
45
45
|
"extensions": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
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": "index.ts",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
36
|
-
"@pi-unipi/background-tasks": "3.0.0-alpha.
|
|
35
|
+
"@pi-unipi/core": "3.0.0-alpha.42",
|
|
36
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.42"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -269,7 +269,7 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
269
269
|
// bg: npm test · 12s") — the pane must not read as finished. It is
|
|
270
270
|
// a status, not part of the bg dots, so it ignores processLine/
|
|
271
271
|
// strip-hiding settings. It is the only bg/subagent line outside
|
|
272
|
-
// the ↓ work tray (the tray draws
|
|
272
|
+
// the ↓ work tray (closed, the tray draws only its one "↓ open" hint).
|
|
273
273
|
const ctx = state.piContext as { isIdle?: () => boolean } | undefined;
|
|
274
274
|
const now = Date.now();
|
|
275
275
|
const frame = Math.floor(now / 80);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/fusion",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Devin-style model picker, fusion presets (lead + sidekick), and Local Fusion runtime for UniPi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
36
|
-
"@pi-unipi/subagents": "3.0.0-alpha.
|
|
37
|
-
"@pi-unipi/utility": "3.0.0-alpha.
|
|
35
|
+
"@pi-unipi/core": "3.0.0-alpha.42",
|
|
36
|
+
"@pi-unipi/subagents": "3.0.0-alpha.42",
|
|
37
|
+
"@pi-unipi/utility": "3.0.0-alpha.42"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@earendil-works/pi-ai": "^0.87.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/info-screen",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Unicrab startup splash and the /unipi:info dashboard — this session, context, usage, compaction savings",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
36
|
+
"@pi-unipi/core": "3.0.0-alpha.42"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/input-shortcuts",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
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",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
36
|
+
"@pi-unipi/core": "3.0.0-alpha.42"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/kanboard",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Kanboard v3 — deferred-work board: CLI/daemon bridge, task runner and skill",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
34
|
+
"@pi-unipi/core": "3.0.0-alpha.42"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"themes": []
|
|
49
49
|
},
|
|
50
50
|
"optionalDependencies": {
|
|
51
|
-
"@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.
|
|
52
|
-
"@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.
|
|
53
|
-
"@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.
|
|
54
|
-
"@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.
|
|
55
|
-
"@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.
|
|
51
|
+
"@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.42",
|
|
52
|
+
"@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.42",
|
|
53
|
+
"@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.42",
|
|
54
|
+
"@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.42",
|
|
55
|
+
"@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.42"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/long-horizon",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Long-horizon execution for Pi — mode-gated /goal /ralph /swarm /graph orchestration with a TypeSafe prompt judge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
40
|
+
"@pi-unipi/core": "3.0.0-alpha.42",
|
|
41
41
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
42
42
|
"beautiful-mermaid": "^1.1.3"
|
|
43
43
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/mcp",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
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",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"test": "npx tsx --test tests/**/*.test.ts"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
33
|
+
"@pi-unipi/core": "3.0.0-alpha.42"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/memory",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.42",
|
|
4
4
|
"description": "Persistent cross-session memory on MemPalace (auto-installed): daemon-routed writes, shared palace with other agents, for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"README.md"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
48
|
-
"@pi-unipi/info-screen": "3.0.0-alpha.
|
|
47
|
+
"@pi-unipi/core": "3.0.0-alpha.42",
|
|
48
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.42",
|
|
49
49
|
"js-yaml": "^4.1.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|