@pi-unipi/unipi 3.0.0-alpha.13 → 3.0.0-alpha.15
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 +20 -0
- package/package.json +21 -21
- 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/package.json +1 -1
- package/packages/footer/package.json +3 -3
- package/packages/fusion/package.json +3 -3
- 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/kanboard/src/guard.ts +66 -10
- package/packages/kanboard/src/reminders.ts +30 -15
- 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/package.json +2 -2
- package/packages/subagents/package.json +2 -2
- package/packages/unipi/bundled.js +398 -85
- package/packages/updater/package.json +2 -2
- package/packages/utility/package.json +2 -2
- package/packages/utility/src/render/reply-bg.ts +303 -0
- package/packages/utility/src/render/simple.ts +79 -1
- 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,26 @@ 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.15] — 2026-09-30
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `utility` (simple render style): a hidden-thinking run inside a thinking+text step no longer leaves blank rows — pi wraps the (empty) label in escapes that survive its own blank check, so a step showed up to three stray blank rows above its text; whitespace-only children and the spacer after each are now dropped before painting.
|
|
13
|
+
- `utility` (simple render style): resumed and re-rendered sessions (resume, boundary-compaction re-render) rebuild tool groups from the rendered transcript order — replayed calls never fired group-break events, so every row landed in one flat group and only the last component painted.
|
|
14
|
+
- `utility` (simple render style): compact spacing — pi's leading spacer row on every content-bearing assistant message is trimmed: no stray blank below custom badges (`▌ Memory recall …`), above mid-turn text, and no painted pad row under the `─ summary ─` rule inside the reply panel. Dropped edge rows keep their OSC-133 prompt-zone markers.
|
|
15
|
+
|
|
16
|
+
## [3.0.0-alpha.14] — 2026-09-30
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `utility` (simple render style): the turn's final reply is painted on its own panel — the darkest possible background (true black) framed by an orange heavy rule reading `summary` above and a matching unlabelled rule below, opening with one blank row of breathing space. Light themes lift the reply text to bright white so it stays readable on black. The panel is cached per component by width + message + colour, so a keystroke re-render is a lookup, not a repaint.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- `utility` (simple render style): a step with no visible text (thinking and/or tool calls only) no longer renders rows — pi still adds a leading spacer for such a step, so each one left a blank row and the gap above a tool group grew with every thinking-only step.
|
|
23
|
+
- `kanboard` guard: read-only commands that merely mention the binary (`which unipi-kanboard`, `find -name "unipi-kanboard"`, `ls …/bin/unipi-kanboard`, `pip show pi-unipi-kanboard`) are no longer blocked as "credits used up" — a token counts as an invocation only in command position (first word of its segment after `&&`/`||`/`;`/`|`/newline splits with quotes masked, past leading `VAR=value` assignments and `exec`/`command`/`env` wrappers).
|
|
24
|
+
- `kanboard` guard: an invalid subcommand no longer poisons a whole compound call — `start KB-2; done KB-1` passes the guard and the binary itself reports the typo (`done` is not a subcommand). Subcommands the guard does not recognize are never charged or blocked.
|
|
25
|
+
- `kanboard` guard: the refusal text now ends with "(board reads, `start` and `finish` are always free)" so a blocked agent does not conclude the CLI is unusable.
|
|
26
|
+
- `kanboard` reminders (R1): now per-task instead of once-per-turn — starting one task no longer silences the reminder for the other, and when the agent moves to a second task's files (in a later turn) it is nudged again; each task is reminded at most once per turn and twice in total.
|
|
27
|
+
- `kanboard` reminders: shell detection masks quoted spans before splitting segments (`grep -c "a;b" f` is no longer "file-changing"), and harmless node-project arms count as reads (`node`/`npm`/`npx`/`python3` version checks, `sort` without `-o`).
|
|
28
|
+
|
|
9
29
|
## [3.0.0-alpha.13] — 2026-09-29
|
|
10
30
|
|
|
11
31
|
### Fixed
|
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.15",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -82,26 +82,26 @@
|
|
|
82
82
|
"typebox": "^1.1.38"
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@pi-unipi/ask-user": "3.0.0-alpha.
|
|
86
|
-
"@pi-unipi/background-tasks": "3.0.0-alpha.
|
|
87
|
-
"@pi-unipi/btw": "3.0.0-alpha.
|
|
88
|
-
"@pi-unipi/command-enchantment": "3.0.0-alpha.
|
|
89
|
-
"@pi-unipi/compactor": "3.0.0-alpha.
|
|
90
|
-
"@pi-unipi/core": "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.
|
|
85
|
+
"@pi-unipi/ask-user": "3.0.0-alpha.15",
|
|
86
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.15",
|
|
87
|
+
"@pi-unipi/btw": "3.0.0-alpha.15",
|
|
88
|
+
"@pi-unipi/command-enchantment": "3.0.0-alpha.15",
|
|
89
|
+
"@pi-unipi/compactor": "3.0.0-alpha.15",
|
|
90
|
+
"@pi-unipi/core": "3.0.0-alpha.15",
|
|
91
|
+
"@pi-unipi/footer": "3.0.0-alpha.15",
|
|
92
|
+
"@pi-unipi/fusion": "3.0.0-alpha.15",
|
|
93
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.15",
|
|
94
|
+
"@pi-unipi/input-shortcuts": "3.0.0-alpha.15",
|
|
95
|
+
"@pi-unipi/kanboard": "3.0.0-alpha.15",
|
|
96
|
+
"@pi-unipi/mcp": "3.0.0-alpha.15",
|
|
97
|
+
"@pi-unipi/memory": "3.0.0-alpha.15",
|
|
98
|
+
"@pi-unipi/notify": "3.0.0-alpha.15",
|
|
99
|
+
"@pi-unipi/skill-registry": "3.0.0-alpha.15",
|
|
100
|
+
"@pi-unipi/subagents": "3.0.0-alpha.15",
|
|
101
|
+
"@pi-unipi/updater": "3.0.0-alpha.15",
|
|
102
|
+
"@pi-unipi/utility": "3.0.0-alpha.15",
|
|
103
|
+
"@pi-unipi/web-api": "3.0.0-alpha.15",
|
|
104
|
+
"@pi-unipi/workflow": "3.0.0-alpha.15"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@earendil-works/pi-agent-core": "^0.87.1",
|
|
@@ -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.15",
|
|
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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
43
|
+
"@pi-unipi/core": "3.0.0-alpha.15"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@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.15",
|
|
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.15",
|
|
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.15",
|
|
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.15"
|
|
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.15",
|
|
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.15",
|
|
37
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.15",
|
|
38
38
|
"@earendil-works/pi-agent-core": "^0.87.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.15",
|
|
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.15",
|
|
36
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.15"
|
|
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/fusion",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.15",
|
|
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,8 +32,8 @@
|
|
|
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.
|
|
35
|
+
"@pi-unipi/core": "3.0.0-alpha.15",
|
|
36
|
+
"@pi-unipi/subagents": "3.0.0-alpha.15"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@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.15",
|
|
4
4
|
"description": "Dashboard and module registry for Unipi — configurable info overlay with tabbed groups",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
35
|
+
"@pi-unipi/core": "3.0.0-alpha.15"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@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.15",
|
|
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.15"
|
|
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.15",
|
|
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.15"
|
|
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.15",
|
|
52
|
+
"@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.15",
|
|
53
|
+
"@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.15",
|
|
54
|
+
"@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.15",
|
|
55
|
+
"@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.15"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import { tokenizeArgs } from "./commands.js";
|
|
11
11
|
|
|
12
12
|
export const DEFAULT_DO_CREDITS = 10;
|
|
13
|
-
export const WRITE_CREDITS_USED_UP =
|
|
13
|
+
export const WRITE_CREDITS_USED_UP =
|
|
14
|
+
"kanboard write credits used up — run /unipi:kanboard-do to reload (board reads, `start` and `finish` are always free)";
|
|
14
15
|
export const addCapReason = (limit: number): string => `at most ${limit} new tasks per turn`;
|
|
15
16
|
/** @deprecated tests should read the limit through the guard's getter instead. */
|
|
16
17
|
export const ADD_CAP = 20;
|
|
@@ -29,6 +30,45 @@ const FREE_WRITES = new Set(["start", "finish"]);
|
|
|
29
30
|
/** Global flags that take a value; `--json` is the only valueless one. */
|
|
30
31
|
const GLOBAL_VALUE_FLAGS = new Set(["--actor", "--project", "--gate", "--session"]);
|
|
31
32
|
|
|
33
|
+
/** Quoted spans (single or double quotes), masked before splitting segments. */
|
|
34
|
+
const QUOTED_SPAN = /(["'])(?:\\.|(?!\1).)*\1/g;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Split a command line into shell segments on `&&`, `||`, `;`, `|` and
|
|
38
|
+
* newlines — ignoring separators inside quoted spans (the mask keeps the
|
|
39
|
+
* original length, so the cut positions map back onto the input exactly).
|
|
40
|
+
*/
|
|
41
|
+
export function shellSegments(command: string): string[] {
|
|
42
|
+
const masked = command.replace(QUOTED_SPAN, (span) => " ".repeat(span.length));
|
|
43
|
+
const out: string[] = [];
|
|
44
|
+
let at = 0;
|
|
45
|
+
for (const match of masked.matchAll(/&&|\|\||[;|\n]/g)) {
|
|
46
|
+
out.push(command.slice(at, match.index));
|
|
47
|
+
at = match.index + match[0].length;
|
|
48
|
+
}
|
|
49
|
+
out.push(command.slice(at));
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** `VAR=value` prefixes that may sit in front of the command word. */
|
|
54
|
+
const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
55
|
+
/** Wrappers that keep the wrapped word in command position. */
|
|
56
|
+
const COMMAND_PREFIXES = new Set(["exec", "command", "env"]);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Every subcommand the CLI understands (crates/kanboard/src/cli.rs `Command`,
|
|
60
|
+
* kebab-case as clap spells it). Anything else — a typo like `done`, a bare
|
|
61
|
+
* binary with no subcommand — is the binary's own usage error; the guard
|
|
62
|
+
* neither charges nor blocks it.
|
|
63
|
+
*/
|
|
64
|
+
export const KNOWN_SUBCOMMANDS = new Set([
|
|
65
|
+
"project", "add", "list", "show", "move", "note", "attach", "attachments",
|
|
66
|
+
"edit", "link", "unlink", "order", "claim-next", "start", "finish", "next",
|
|
67
|
+
"reap", "queue", "unqueue", "chain", "search", "release", "set-run",
|
|
68
|
+
"duplicate", "archive-sweep", "serve", "settings", "rotate-token", "status",
|
|
69
|
+
"stop", "validate",
|
|
70
|
+
]);
|
|
71
|
+
|
|
32
72
|
export interface KanboardInvocation {
|
|
33
73
|
/** First positional after the binary name ("" when absent). */
|
|
34
74
|
sub: string;
|
|
@@ -36,15 +76,25 @@ export interface KanboardInvocation {
|
|
|
36
76
|
args: string[];
|
|
37
77
|
}
|
|
38
78
|
|
|
39
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Every `unipi-kanboard` invocation inside a shell command line. A token
|
|
81
|
+
* counts only when it is positioned like a command: the first word of its
|
|
82
|
+
* segment (segments split on `&&`/`||`/`;`/`|`/newlines, quotes masked),
|
|
83
|
+
* past leading `VAR=value` assignments and the `exec`/`command`/`env`
|
|
84
|
+
* wrappers. `which unipi-kanboard` or `find -name "unipi-kanboard"` merely
|
|
85
|
+
* mention the binary and are not invocations.
|
|
86
|
+
*/
|
|
40
87
|
export function kanboardInvocations(command: string): KanboardInvocation[] {
|
|
41
|
-
const tokens = tokenizeArgs(command);
|
|
42
88
|
const out: KanboardInvocation[] = [];
|
|
43
|
-
for (
|
|
44
|
-
const
|
|
89
|
+
for (const segment of shellSegments(command)) {
|
|
90
|
+
const tokens = tokenizeArgs(segment);
|
|
91
|
+
let head = 0;
|
|
92
|
+
while (head < tokens.length && (ASSIGNMENT.test(tokens[head]!) || COMMAND_PREFIXES.has(tokens[head]!))) {
|
|
93
|
+
head += 1;
|
|
94
|
+
}
|
|
45
95
|
// The binary may be a bare name or an absolute path (and .exe on Windows).
|
|
46
|
-
if (!/unipi-kanboard(\.exe)?$/.test(
|
|
47
|
-
const rest = tokens.slice(
|
|
96
|
+
if (head >= tokens.length || !/unipi-kanboard(\.exe)?$/.test(tokens[head]!)) continue;
|
|
97
|
+
const rest = tokens.slice(head + 1);
|
|
48
98
|
let cursor = 0;
|
|
49
99
|
while (cursor < rest.length) {
|
|
50
100
|
const arg = rest[cursor]!;
|
|
@@ -108,9 +158,12 @@ export interface WriteGuard {
|
|
|
108
158
|
|
|
109
159
|
/**
|
|
110
160
|
* Writes cost session credits (one per write subcommand in the command line);
|
|
111
|
-
* reads and `start`/`finish` are free and never blocked.
|
|
112
|
-
*
|
|
113
|
-
*
|
|
161
|
+
* reads and `start`/`finish` are free and never blocked. Invocations whose
|
|
162
|
+
* subcommand does not exist are skipped: the binary itself rejects them with
|
|
163
|
+
* a usage error, and one typo must not block the rest of a compound call. A
|
|
164
|
+
* runner task keeps unlimited access (still add-capped). Credits persist
|
|
165
|
+
* across turns and follow-up questions until spent; /unipi:kanboard-do tops
|
|
166
|
+
* up to N without stacking past N.
|
|
114
167
|
*/
|
|
115
168
|
export function createWriteGuard(
|
|
116
169
|
runnerTask: () => string | null,
|
|
@@ -161,6 +214,9 @@ export function createWriteGuard(
|
|
|
161
214
|
lastTask = task;
|
|
162
215
|
}
|
|
163
216
|
for (const invocation of invocations) {
|
|
217
|
+
// An unknown subcommand (a typo, a bare binary) is the binary's own
|
|
218
|
+
// usage error — never a reason to block or charge the whole call.
|
|
219
|
+
if (!KNOWN_SUBCOMMANDS.has(invocation.sub)) continue;
|
|
164
220
|
if (isReadonly(invocation) || isFreeWrite(invocation)) continue;
|
|
165
221
|
if (task !== null) {
|
|
166
222
|
const cap = countAdd(invocation);
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* `finish <ID> --comment` hands it to In Review. Two text-only reminders keep
|
|
7
7
|
* that honest:
|
|
8
8
|
*
|
|
9
|
-
* R1
|
|
10
|
-
* task is still Todo and
|
|
11
|
-
*
|
|
9
|
+
* R1 every file-changing tool call of an agent turn, for each mentioned
|
|
10
|
+
* task that is still Todo and has not been reminded this turn (one
|
|
11
|
+
* steer per task per turn, at most twice per task).
|
|
12
12
|
* R2 agent_end with a task this session started still In Progress → one
|
|
13
13
|
* follow-up message (at most twice per task).
|
|
14
14
|
*
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
22
22
|
|
|
23
|
-
import { kanboardInvocations } from "./guard.js";
|
|
23
|
+
import { kanboardInvocations, shellSegments } from "./guard.js";
|
|
24
24
|
import type { KanboardTask } from "./shapes.js";
|
|
25
25
|
|
|
26
26
|
export const REMINDER_CUSTOM_TYPE = "unipi:kanboard-reminder";
|
|
@@ -47,6 +47,9 @@ const READ_COMMANDS = new Set([
|
|
|
47
47
|
"more", "true", "false", "test", "[", "diff", "cmp", "jq", "realpath", "dirname", "basename", "uname",
|
|
48
48
|
"hostname", "id", "ps", "cd",
|
|
49
49
|
]);
|
|
50
|
+
/** Interpreters/package managers whose version-check arms never write. */
|
|
51
|
+
const VERSION_CHECKED = new Set(["node", "npm", "npx", "python", "python3"]);
|
|
52
|
+
const isVersionFlag = (word: string): boolean => word === "--version" || word === "-v" || word === "-V";
|
|
50
53
|
const GIT_READS = new Set(["status", "log", "diff", "show", "branch", "remote", "rev-parse", "blame", "ls-files", "describe", "tag"]);
|
|
51
54
|
|
|
52
55
|
export function taskIdsIn(text: string): string[] {
|
|
@@ -56,14 +59,16 @@ export function taskIdsIn(text: string): string[] {
|
|
|
56
59
|
/**
|
|
57
60
|
* Whether a shell command may change files. Conservative toward "yes": any
|
|
58
61
|
* segment that is not a known read (or a kanboard CLI call) counts, and so
|
|
59
|
-
* does an output redirection.
|
|
62
|
+
* does an output redirection. Quoted spans are masked before both the
|
|
63
|
+
* redirect test and the segment split, so `grep -c "a;b" f` stays one
|
|
64
|
+
* read-only segment.
|
|
60
65
|
*/
|
|
61
66
|
export function shellChangesFiles(command: string): boolean {
|
|
62
67
|
const trimmed = command.trim();
|
|
63
68
|
if (!trimmed) return false;
|
|
64
69
|
// `>`/`>>` into a file (but not `2>&1` / `>/dev/null`).
|
|
65
70
|
if (/(^|[^0-9&>])>{1,2}\s*(?!&|\/dev\/null)[^\s|;&]/.test(trimmed.replace(/(["'])(?:\\.|(?!\1).)*\1/g, '""'))) return true;
|
|
66
|
-
const segments = trimmed
|
|
71
|
+
const segments = shellSegments(trimmed).map((part) => part.trim()).filter(Boolean);
|
|
67
72
|
return segments.some((segment) => {
|
|
68
73
|
const words = segment.split(/\s+/).filter((word) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(word));
|
|
69
74
|
const first = (words[0] ?? "").replace(/^.*\//, "");
|
|
@@ -73,6 +78,11 @@ export function shellChangesFiles(command: string): boolean {
|
|
|
73
78
|
if (READ_COMMANDS.has(first)) return false;
|
|
74
79
|
if (first === "git") return !GIT_READS.has(words[1] ?? "");
|
|
75
80
|
if (first === "sed") return words.includes("-i") || words.some((word) => word.startsWith("-i"));
|
|
81
|
+
// `sort` writes only through -o/--output (a `>` redirect is caught above).
|
|
82
|
+
if (first === "sort") return words.some((word) => /^-[^-]*o/.test(word) || word.startsWith("--output"));
|
|
83
|
+
// node/npm/npx/python are reads only as version checks (`node --version`,
|
|
84
|
+
// `npx tsx --version`); scripts, installs and builds still count.
|
|
85
|
+
if (VERSION_CHECKED.has(first)) return !words.slice(1).some(isVersionFlag);
|
|
76
86
|
return true;
|
|
77
87
|
});
|
|
78
88
|
}
|
|
@@ -160,7 +170,8 @@ export function createProgressTracker(deps: TrackerDeps): ProgressTracker {
|
|
|
160
170
|
const started = new Set<string>();
|
|
161
171
|
const r1Count = new Map<string, number>();
|
|
162
172
|
const r2Count = new Map<string, number>();
|
|
163
|
-
|
|
173
|
+
/** Task ids already reminded this turn (per-task re-arm, not one shot). */
|
|
174
|
+
const remindedThisTurn = new Set<string>();
|
|
164
175
|
const debug = (message: string): void => deps.debug?.(`reminders: ${message}`);
|
|
165
176
|
const silent = (): boolean => !deps.enabled() || deps.runnerOwned() || Boolean(process.env.UNIPI_KANBOARD_CHILD);
|
|
166
177
|
|
|
@@ -183,7 +194,7 @@ export function createProgressTracker(deps: TrackerDeps): ProgressTracker {
|
|
|
183
194
|
},
|
|
184
195
|
|
|
185
196
|
onTurnStart() {
|
|
186
|
-
|
|
197
|
+
remindedThisTurn.clear();
|
|
187
198
|
},
|
|
188
199
|
|
|
189
200
|
async onToolResult(event) {
|
|
@@ -196,20 +207,24 @@ export function createProgressTracker(deps: TrackerDeps): ProgressTracker {
|
|
|
196
207
|
if (invocation.sub === "start" && !event.isError) started.add(id);
|
|
197
208
|
}
|
|
198
209
|
}
|
|
199
|
-
if (
|
|
200
|
-
// Only the first file-changing call of the turn is considered.
|
|
201
|
-
checkedThisTurn = true;
|
|
210
|
+
if (!isFileChangingCall(toolName, event.input)) return undefined;
|
|
202
211
|
if (silent() || mentioned.size === 0) return undefined;
|
|
203
|
-
|
|
212
|
+
// Per task, at most once per turn: candidates are mentioned ids not yet
|
|
213
|
+
// reminded this turn and under the per-task cap (a started task leaves
|
|
214
|
+
// Todo and is never re-reminded).
|
|
215
|
+
const candidates = [...mentioned].filter(
|
|
216
|
+
(id) => !remindedThisTurn.has(id) && (r1Count.get(id) ?? 0) < MAX_REMINDERS_PER_TASK,
|
|
217
|
+
);
|
|
204
218
|
if (candidates.length === 0) return undefined;
|
|
205
219
|
const tasks = await safeList();
|
|
206
220
|
if (!tasks) return undefined;
|
|
207
|
-
const session = deps.session();
|
|
208
|
-
if (tasks.some((task) => ownedStart(task, session))) return undefined;
|
|
209
221
|
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
210
222
|
const todo = candidates.filter((id) => byId.get(id)?.status === "todo");
|
|
211
223
|
if (todo.length === 0) return undefined;
|
|
212
|
-
for (const id of todo)
|
|
224
|
+
for (const id of todo) {
|
|
225
|
+
remindedThisTurn.add(id);
|
|
226
|
+
r1Count.set(id, (r1Count.get(id) ?? 0) + 1);
|
|
227
|
+
}
|
|
213
228
|
debug(`R1 for ${todo.join(", ")}`);
|
|
214
229
|
return { content: [...(event.content ?? []), { type: "text", text: `\n\n${r1Text(todo, deps.cliPrefix())}` }] };
|
|
215
230
|
},
|
|
@@ -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.15",
|
|
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.15"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/mcp",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.15",
|
|
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.15"
|
|
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.15",
|
|
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",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"README.md"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
46
|
-
"@pi-unipi/info-screen": "3.0.0-alpha.
|
|
45
|
+
"@pi-unipi/core": "3.0.0-alpha.15",
|
|
46
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.15",
|
|
47
47
|
"js-yaml": "^4.1.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/notify",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.15",
|
|
4
4
|
"description": "Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
37
|
+
"@pi-unipi/core": "3.0.0-alpha.15",
|
|
38
38
|
"node-notifier": "^10.0.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/skill-registry",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.15",
|
|
4
4
|
"description": "Skill registry for pi — per-project skill on/off, a skill vault, and jev-judged skill exposure",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"main": "index.ts",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
36
|
+
"@pi-unipi/core": "3.0.0-alpha.15"
|
|
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/subagents",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.15",
|
|
4
4
|
"description": "Subagents for UniPi — Devin-model independent child agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"test": "npx tsx --test src/__tests__/*.test.ts"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
12
|
+
"@pi-unipi/core": "3.0.0-alpha.15",
|
|
13
13
|
"@earendil-works/pi-agent-core": "^0.87.1",
|
|
14
14
|
"acorn": "8.18.0"
|
|
15
15
|
},
|