@pi-unipi/unipi 3.0.0-alpha.8 → 3.0.0-alpha.9
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 +14 -0
- package/package.json +21 -21
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +3 -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/index.ts +1 -0
- package/packages/kanboard/package.json +7 -7
- package/packages/kanboard/src/commands.ts +53 -19
- package/packages/kanboard/src/guard.ts +41 -18
- package/packages/kanboard/src/runner.ts +86 -11
- package/packages/kanboard/src/settings.ts +5 -0
- package/packages/long-horizon/index.ts +57 -4
- package/packages/long-horizon/package.json +2 -2
- package/packages/long-horizon/src/commands.ts +80 -5
- package/packages/long-horizon/src/engine/continuation.ts +64 -8
- package/packages/long-horizon/src/engine/goal-state.ts +62 -12
- package/packages/long-horizon/src/engine/runaway.ts +67 -10
- package/packages/long-horizon/src/gate.ts +16 -3
- package/packages/long-horizon/src/prompts/goal.ts +1 -1
- package/packages/long-horizon/src/runtime.ts +101 -6
- package/packages/long-horizon/src/tools/goal.ts +6 -0
- 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 +471 -82
- package/packages/updater/package.json +2 -2
- package/packages/utility/package.json +2 -2
- 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,20 @@ 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.9] — 2026-09-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `long-horizon`: `/unipi:goal|swarm|graph stop` — terminally ends the active owner (goal → `complete(user_requested)`; pending proposal discarded; no further continuation follow-up). `/unipi:clear` on an active owner now points at stop instead of silently doing nothing.
|
|
13
|
+
- `long-horizon`: `/unipi:regular` — stops any active owner (incl. ralph loops) and pins the session to plain mode; an explicit mode command unlatches it.
|
|
14
|
+
- `long-horizon`: command runners `unipi:goal-pause` / `unipi:goal-resume` / `unipi:goal-stop` for cross-module callers.
|
|
15
|
+
- `kanboard`: `/unipi:kanboard-do` grants session **write credits** (setting `kanboard.doCredits`, default 10) instead of a turn-scoped window — credits persist across turns, each board write costs 1, reads stay free, runner tasks keep unlimited access, `off` revokes.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `long-horizon`: verifier resilience — evaluator failures retry once, then pause `paused(verifier_unavailable)`; two consecutive inconclusive verdicts pause `paused(verifier_inconclusive)`; five consecutive `not_met` claims pause unconditionally (stall-cap coupling removed). Paused goals park the owner so `/unipi:goal resume` works.
|
|
19
|
+
- `long-horizon`: tokens — provider usage now read in both pi shapes (`{input, output, …}` and `{inputTokens, outputTokens}`); usage-less turns estimate tokens (chars/4) and flag `tokens_estimated` in `get_goal` and the continuation hint.
|
|
20
|
+
- `long-horizon`: no-progress guard keys "identical arguments" on real tool arguments (captured per `toolCallId`), exempts search no-matches, `get_goal`/`update_goal`, and kanboard reads — no more false "ran 3 times with identical arguments" after three different commands.
|
|
21
|
+
- `kanboard`: an interrupted/released task parks its goal (id recorded in the release note) and resumes it on re-claim; a user-stopped goal releases the task to Todo ("goal stopped by user") instead of In Review; a task cancelled outside the runner stops its goal.
|
|
22
|
+
- `kanboard`: `-do` suggestions only for task-id-like tokens (no more task rows under free prose).
|
|
9
23
|
## [3.0.0-alpha.7] — 2026-09-28
|
|
10
24
|
|
|
11
25
|
### 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.9",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,26 +83,26 @@
|
|
|
83
83
|
"typebox": "^1.1.38"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@pi-unipi/ask-user": "3.0.0-alpha.
|
|
87
|
-
"@pi-unipi/background-tasks": "3.0.0-alpha.
|
|
88
|
-
"@pi-unipi/btw": "3.0.0-alpha.
|
|
89
|
-
"@pi-unipi/command-enchantment": "3.0.0-alpha.
|
|
90
|
-
"@pi-unipi/compactor": "3.0.0-alpha.
|
|
91
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
92
|
-
"@pi-unipi/footer": "3.0.0-alpha.
|
|
93
|
-
"@pi-unipi/fusion": "3.0.0-alpha.
|
|
94
|
-
"@pi-unipi/info-screen": "3.0.0-alpha.
|
|
95
|
-
"@pi-unipi/input-shortcuts": "3.0.0-alpha.
|
|
96
|
-
"@pi-unipi/kanboard": "3.0.0-alpha.
|
|
97
|
-
"@pi-unipi/mcp": "3.0.0-alpha.
|
|
98
|
-
"@pi-unipi/memory": "3.0.0-alpha.
|
|
99
|
-
"@pi-unipi/notify": "3.0.0-alpha.
|
|
100
|
-
"@pi-unipi/skill-registry": "3.0.0-alpha.
|
|
101
|
-
"@pi-unipi/subagents": "3.0.0-alpha.
|
|
102
|
-
"@pi-unipi/updater": "3.0.0-alpha.
|
|
103
|
-
"@pi-unipi/utility": "3.0.0-alpha.
|
|
104
|
-
"@pi-unipi/web-api": "3.0.0-alpha.
|
|
105
|
-
"@pi-unipi/workflow": "3.0.0-alpha.
|
|
86
|
+
"@pi-unipi/ask-user": "3.0.0-alpha.9",
|
|
87
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.9",
|
|
88
|
+
"@pi-unipi/btw": "3.0.0-alpha.9",
|
|
89
|
+
"@pi-unipi/command-enchantment": "3.0.0-alpha.9",
|
|
90
|
+
"@pi-unipi/compactor": "3.0.0-alpha.9",
|
|
91
|
+
"@pi-unipi/core": "3.0.0-alpha.9",
|
|
92
|
+
"@pi-unipi/footer": "3.0.0-alpha.9",
|
|
93
|
+
"@pi-unipi/fusion": "3.0.0-alpha.9",
|
|
94
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.9",
|
|
95
|
+
"@pi-unipi/input-shortcuts": "3.0.0-alpha.9",
|
|
96
|
+
"@pi-unipi/kanboard": "3.0.0-alpha.9",
|
|
97
|
+
"@pi-unipi/mcp": "3.0.0-alpha.9",
|
|
98
|
+
"@pi-unipi/memory": "3.0.0-alpha.9",
|
|
99
|
+
"@pi-unipi/notify": "3.0.0-alpha.9",
|
|
100
|
+
"@pi-unipi/skill-registry": "3.0.0-alpha.9",
|
|
101
|
+
"@pi-unipi/subagents": "3.0.0-alpha.9",
|
|
102
|
+
"@pi-unipi/updater": "3.0.0-alpha.9",
|
|
103
|
+
"@pi-unipi/utility": "3.0.0-alpha.9",
|
|
104
|
+
"@pi-unipi/web-api": "3.0.0-alpha.9",
|
|
105
|
+
"@pi-unipi/workflow": "3.0.0-alpha.9"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"@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.9",
|
|
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.9"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -42,11 +42,12 @@ export const COMMAND_REGISTRY: Record<string, string> = {
|
|
|
42
42
|
"unipi:plan": "workflow",
|
|
43
43
|
"unipi:permission": "workflow",
|
|
44
44
|
|
|
45
|
-
// long-horizon (
|
|
45
|
+
// long-horizon (5 commands)
|
|
46
46
|
"unipi:goal": "long-horizon",
|
|
47
47
|
"unipi:ralph": "long-horizon",
|
|
48
48
|
"unipi:swarm": "long-horizon",
|
|
49
49
|
"unipi:graph": "long-horizon",
|
|
50
|
+
"unipi:regular": "long-horizon",
|
|
50
51
|
|
|
51
52
|
// memory (7 commands)
|
|
52
53
|
"unipi:memory": "memory",
|
|
@@ -140,6 +141,7 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
140
141
|
"unipi:ralph": "Checklist grind over iterations · enumerable chores · low cost, solid success",
|
|
141
142
|
"unipi:swarm": "Parallel fan-out + synthesis · complex decomposable · higher cost, high coverage",
|
|
142
143
|
"unipi:graph": "Dependent multi-step work · later steps need earlier results · highest cost",
|
|
144
|
+
"unipi:regular": "Regular mode — stop the active owner and run prompts without long-horizon routing",
|
|
143
145
|
|
|
144
146
|
"unipi:memory": "Memory palace — status, migrate, recall/write toggles",
|
|
145
147
|
"unipi:memory-process": "Analyze text and store extracted memories",
|
|
@@ -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.9",
|
|
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.9",
|
|
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.9",
|
|
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.9"
|
|
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.9",
|
|
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.9",
|
|
37
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.9",
|
|
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.9",
|
|
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.9",
|
|
36
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.9"
|
|
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.9",
|
|
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.9",
|
|
36
|
+
"@pi-unipi/subagents": "3.0.0-alpha.9"
|
|
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.9",
|
|
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.9"
|
|
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.9",
|
|
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.9"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -71,6 +71,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
71
71
|
return status?.phase === "running" ? status.taskId : null;
|
|
72
72
|
},
|
|
73
73
|
() => readKanboardSettings().turnAddLimit,
|
|
74
|
+
() => readKanboardSettings().doCredits,
|
|
74
75
|
);
|
|
75
76
|
const sessionId = (): string => process.env.UNIPI_KANBOARD_SESSION ?? `pi-${process.pid}`;
|
|
76
77
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/kanboard",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.9",
|
|
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.9"
|
|
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.9",
|
|
52
|
+
"@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.9",
|
|
53
|
+
"@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.9",
|
|
54
|
+
"@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.9",
|
|
55
|
+
"@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.9"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -40,9 +40,9 @@ export const HELP = `Kanboard commands
|
|
|
40
40
|
/unipi:kanboard-autowork start|stop work ready tasks chain by chain; stop finishes the current one first
|
|
41
41
|
Priority -p: 1 none · 2 low · 3 medium · 4 high · 5 urgent`;
|
|
42
42
|
|
|
43
|
-
export function doText(slug: string, cli: string, request: string, queueMax = 10): string {
|
|
43
|
+
export function doText(slug: string, cli: string, request: string, queueMax = 10, credits = 0): string {
|
|
44
44
|
const limit = queueMax === 0 ? "Todo tasks" : `Todo tasks, at most ${queueMax}`;
|
|
45
|
-
return `[kanboard] For this request you may use the kanboard skill on project ${slug} (CLI: \`${cli} --actor agent --project ${slug} …\`).
|
|
45
|
+
return `[kanboard] For this request you may use the kanboard skill on project ${slug} (CLI: \`${cli} --actor agent --project ${slug} …\`). You have ${credits} write credit${credits === 1 ? "" : "s"} remaining this session — each board write (add, move, edit, note, link, order, queue) costs 1, reads are free; /unipi:kanboard-do tops the credits back up. You can add tasks, move them between backlog and todo, link, order, note, and edit tasks you created. To have tasks worked, queue them in order with \`queue <IDs>\` (${limit}); the runner starts them one by one after this turn, so do not start the work yourself. Read a task with \`show <ID>\` before editing, linking or queueing it — \`list\` only shows titles and a one-line excerpt. To choose how a task is worked, label it with \`edit <ID> --strategy none|goal|ralph|swarm|graph\` and \`--plan yes|no\`; leave it unset to let the runner decide. If the request is unclear, ask me instead of guessing.
|
|
46
46
|
|
|
47
47
|
Request: ${request}`;
|
|
48
48
|
}
|
|
@@ -1093,7 +1093,21 @@ export function kanboardCompletions(prefix: string): CompletionItem[] | null {
|
|
|
1093
1093
|
/** Live task list for `--after` / `-do` completions, cached ~5s per client. */
|
|
1094
1094
|
const taskCache = new WeakMap<KanboardCli, { at: number; tasks: ShowTask[] }>();
|
|
1095
1095
|
|
|
1096
|
-
export
|
|
1096
|
+
export interface TaskCompletionOptions {
|
|
1097
|
+
/** Also match task TITLES (default true); false = id prefixes only. */
|
|
1098
|
+
titleSearch?: boolean;
|
|
1099
|
+
/** Row style: "status" = label id, description "<status> — <title>";
|
|
1100
|
+
* "id-title" = label "<id> <title>", description "<status>". */
|
|
1101
|
+
rowStyle?: "status" | "id-title";
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
export async function taskCompletions(
|
|
1105
|
+
deps: CommandDeps,
|
|
1106
|
+
prefix: string,
|
|
1107
|
+
before: string,
|
|
1108
|
+
opts: TaskCompletionOptions = {},
|
|
1109
|
+
): Promise<CompletionItem[] | null> {
|
|
1110
|
+
const { titleSearch = true, rowStyle = "status" } = opts;
|
|
1097
1111
|
const client = deps.cli;
|
|
1098
1112
|
if (!client) return null;
|
|
1099
1113
|
let cache = taskCache.get(client);
|
|
@@ -1109,13 +1123,21 @@ export async function taskCompletions(deps: CommandDeps, prefix: string, before:
|
|
|
1109
1123
|
const needle = prefix.toLowerCase();
|
|
1110
1124
|
const items = cache.tasks
|
|
1111
1125
|
.filter((task) => !["cancelled", "archived"].includes(task.status))
|
|
1112
|
-
.filter((task) => task.id.toLowerCase().startsWith(needle) || task.title.toLowerCase().includes(needle))
|
|
1126
|
+
.filter((task) => task.id.toLowerCase().startsWith(needle) || (titleSearch && task.title.toLowerCase().includes(needle)))
|
|
1113
1127
|
.slice(0, 12)
|
|
1114
|
-
.map((task) =>
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1128
|
+
.map((task) =>
|
|
1129
|
+
rowStyle === "id-title"
|
|
1130
|
+
? {
|
|
1131
|
+
value: fullArgs(before, task.id),
|
|
1132
|
+
label: `${task.id} ${task.title.slice(0, 64)}`,
|
|
1133
|
+
description: task.status,
|
|
1134
|
+
}
|
|
1135
|
+
: {
|
|
1136
|
+
value: fullArgs(before, task.id),
|
|
1137
|
+
label: task.id,
|
|
1138
|
+
description: `${task.status} — ${task.title}`,
|
|
1139
|
+
},
|
|
1140
|
+
);
|
|
1119
1141
|
return items.length > 0 ? items : null;
|
|
1120
1142
|
}
|
|
1121
1143
|
|
|
@@ -1191,15 +1213,19 @@ export async function kanboardAddCompletions(deps: CommandDeps, prefix: string):
|
|
|
1191
1213
|
return null;
|
|
1192
1214
|
}
|
|
1193
1215
|
|
|
1194
|
-
/** /unipi:kanboard-do … — complete a trailing task-id
|
|
1216
|
+
/** /unipi:kanboard-do … — complete a trailing task-id token; plain prose never matches. */
|
|
1195
1217
|
export async function kanboardDoCompletions(deps: CommandDeps, prefix: string): Promise<CompletionItem[] | null> {
|
|
1196
1218
|
const raw = prefix ?? "";
|
|
1197
|
-
const
|
|
1198
|
-
const before =
|
|
1199
|
-
const token =
|
|
1200
|
-
// Only
|
|
1201
|
-
|
|
1202
|
-
|
|
1219
|
+
const at = raw.lastIndexOf(" ");
|
|
1220
|
+
const before = at === -1 ? "" : raw.slice(0, at + 1);
|
|
1221
|
+
const token = raw.slice(at + 1);
|
|
1222
|
+
// Only a task-id-like tail ("UNI-1", "uni", "UNI-") completes; words that
|
|
1223
|
+
// match no id shape or prefix ("note", "set") yield nothing.
|
|
1224
|
+
if (!/^[A-Za-z]{2,10}-?\d*$/.test(token)) return null;
|
|
1225
|
+
// Title search only when the token looks like a real id prefix (has a digit
|
|
1226
|
+
// or ends with the dash); otherwise id prefixes only.
|
|
1227
|
+
const idLike = /\d/.test(token) || token.endsWith("-");
|
|
1228
|
+
return taskCompletions(deps, token, before, { titleSearch: idLike, rowStyle: "id-title" });
|
|
1203
1229
|
}
|
|
1204
1230
|
|
|
1205
1231
|
export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): void {
|
|
@@ -1308,10 +1334,15 @@ export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): v
|
|
|
1308
1334
|
});
|
|
1309
1335
|
|
|
1310
1336
|
pi.registerCommand("unipi:kanboard-do", {
|
|
1311
|
-
description: "
|
|
1337
|
+
description: "Grant kanboard write credits to the agent (/unipi:kanboard-do <request>; off revokes)",
|
|
1312
1338
|
getArgumentCompletions: (prefix: string) => kanboardDoCompletions(deps, prefix ?? ""),
|
|
1313
1339
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
1314
1340
|
const request = (args ?? "").trim();
|
|
1341
|
+
if (request.toLowerCase() === "off") {
|
|
1342
|
+
deps.guard.revoke();
|
|
1343
|
+
ctx.ui.notify("kanboard: write credits revoked — reads stay free", "info");
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1315
1346
|
if (!request) {
|
|
1316
1347
|
ctx.ui.notify("kanboard: -do needs a request — /unipi:kanboard-do <what the agent may do on the board>", "warning");
|
|
1317
1348
|
return;
|
|
@@ -1328,9 +1359,12 @@ export function registerKanboardCommands(pi: ExtensionAPI, deps: CommandDeps): v
|
|
|
1328
1359
|
const busy = typeof (ctx as unknown as { isIdle?: () => boolean }).isIdle === "function"
|
|
1329
1360
|
? !(ctx as unknown as { isIdle: () => boolean }).isIdle!()
|
|
1330
1361
|
: false;
|
|
1331
|
-
pi.sendUserMessage(
|
|
1362
|
+
pi.sendUserMessage(
|
|
1363
|
+
doText(slug, client.binary.path, request, deps.settings().queueMax, deps.guard.remaining()),
|
|
1364
|
+
busy ? { deliverAs: "followUp" } : undefined,
|
|
1365
|
+
);
|
|
1332
1366
|
deps.guard.noteSent();
|
|
1333
|
-
deps.debug(`do
|
|
1367
|
+
deps.debug(`do credits open (${slug}): ${deps.guard.remaining()} remaining`);
|
|
1334
1368
|
},
|
|
1335
1369
|
});
|
|
1336
1370
|
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { tokenizeArgs } from "./commands.js";
|
|
11
11
|
|
|
12
|
-
export const
|
|
13
|
-
|
|
12
|
+
export const DEFAULT_DO_CREDITS = 10;
|
|
13
|
+
export const WRITE_CREDITS_USED_UP = "kanboard write credits used up — run /unipi:kanboard-do to reload";
|
|
14
14
|
export const addCapReason = (limit: number): string => `at most ${limit} new tasks per turn`;
|
|
15
15
|
/** @deprecated tests should read the limit through the guard's getter instead. */
|
|
16
16
|
export const ADD_CAP = 20;
|
|
@@ -80,35 +80,55 @@ export function isReadonly(invocation: KanboardInvocation): boolean {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
export interface WriteGuard {
|
|
83
|
-
/**
|
|
83
|
+
/** Grant / top up the session's write credits to N (kanboard.doCredits). */
|
|
84
84
|
open(): void;
|
|
85
|
+
/** Revoke remaining credits (/unipi:kanboard-do off). */
|
|
86
|
+
revoke(): void;
|
|
87
|
+
/** Credits left this session. */
|
|
88
|
+
remaining(): number;
|
|
85
89
|
/** Arm the agent_end closer right after the -do prompt was sent. */
|
|
86
90
|
noteSent(): void;
|
|
87
|
-
/**
|
|
88
|
-
* A window opened by -do closes on the first agent_end that isn't the
|
|
89
|
-
* pre-send echo (>150ms after noteSent). Returns true when it just closed.
|
|
90
|
-
*/
|
|
91
|
+
/** Credits persist across turns; this only closes the -do window label. */
|
|
91
92
|
onAgentEnd(): boolean;
|
|
92
93
|
/** null when the command is allowed; otherwise the block reason. */
|
|
93
94
|
check(command: string): string | null;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* Writes cost session credits (one per write subcommand in the command line);
|
|
99
|
+
* reads are free and never blocked. A runner task keeps unlimited access
|
|
100
|
+
* (still add-capped). Credits persist across turns and follow-up questions
|
|
101
|
+
* until spent; /unipi:kanboard-do tops up to N without stacking past N.
|
|
101
102
|
*/
|
|
102
|
-
export function createWriteGuard(
|
|
103
|
+
export function createWriteGuard(
|
|
104
|
+
runnerTask: () => string | null,
|
|
105
|
+
addLimit: () => number = () => ADD_CAP,
|
|
106
|
+
doCredits: () => number = () => DEFAULT_DO_CREDITS,
|
|
107
|
+
): WriteGuard {
|
|
108
|
+
let credits = 0;
|
|
103
109
|
let doOpen = false;
|
|
104
110
|
let sentAt = 0;
|
|
105
111
|
let adds = 0;
|
|
106
112
|
let lastTask: string | null = null;
|
|
113
|
+
const countAdd = (invocation: KanboardInvocation): string | null => {
|
|
114
|
+
if (invocation.sub !== "add") return null;
|
|
115
|
+
adds += 1;
|
|
116
|
+
const limit = addLimit();
|
|
117
|
+
return limit > 0 && adds > limit ? addCapReason(limit) : null;
|
|
118
|
+
};
|
|
107
119
|
return {
|
|
108
120
|
open() {
|
|
109
|
-
|
|
121
|
+
credits = Math.max(credits, Math.max(0, doCredits()));
|
|
110
122
|
adds = 0;
|
|
111
123
|
lastTask = null;
|
|
124
|
+
doOpen = true;
|
|
125
|
+
},
|
|
126
|
+
revoke() {
|
|
127
|
+
credits = 0;
|
|
128
|
+
doOpen = false;
|
|
129
|
+
},
|
|
130
|
+
remaining() {
|
|
131
|
+
return credits;
|
|
112
132
|
},
|
|
113
133
|
noteSent() {
|
|
114
134
|
sentAt = Date.now();
|
|
@@ -130,12 +150,15 @@ export function createWriteGuard(runnerTask: () => string | null, addLimit: () =
|
|
|
130
150
|
}
|
|
131
151
|
for (const invocation of invocations) {
|
|
132
152
|
if (isReadonly(invocation)) continue;
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (limit > 0 && adds > limit) return addCapReason(limit);
|
|
153
|
+
if (task !== null) {
|
|
154
|
+
const cap = countAdd(invocation);
|
|
155
|
+
if (cap) return cap;
|
|
156
|
+
continue;
|
|
138
157
|
}
|
|
158
|
+
if (credits <= 0) return WRITE_CREDITS_USED_UP;
|
|
159
|
+
credits -= 1;
|
|
160
|
+
const cap = countAdd(invocation);
|
|
161
|
+
if (cap) return cap;
|
|
139
162
|
}
|
|
140
163
|
return null;
|
|
141
164
|
},
|