@pi-unipi/unipi 3.0.0-alpha.23 → 3.0.0-alpha.24

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +21 -21
  3. package/packages/ask-user/package.json +2 -2
  4. package/packages/autocomplete/package.json +1 -1
  5. package/packages/autocomplete/src/constants.ts +2 -0
  6. package/packages/background-tasks/package.json +2 -2
  7. package/packages/btw/package.json +2 -2
  8. package/packages/compactor/package.json +3 -3
  9. package/packages/core/constants.ts +1 -0
  10. package/packages/core/package.json +1 -1
  11. package/packages/footer/package.json +3 -3
  12. package/packages/fusion/package.json +4 -4
  13. package/packages/info-screen/package.json +2 -2
  14. package/packages/input-shortcuts/package.json +2 -2
  15. package/packages/kanboard/package.json +7 -7
  16. package/packages/long-horizon/package.json +2 -2
  17. package/packages/mcp/package.json +2 -2
  18. package/packages/memory/package.json +3 -3
  19. package/packages/notify/package.json +2 -2
  20. package/packages/skill-registry/README.md +1 -1
  21. package/packages/skill-registry/index.ts +3 -1
  22. package/packages/skill-registry/package.json +2 -2
  23. package/packages/skill-registry/src/editor.ts +5 -5
  24. package/packages/skill-registry/src/registry.ts +15 -3
  25. package/packages/subagents/package.json +3 -3
  26. package/packages/unipi/bundled.js +51 -8
  27. package/packages/updater/package.json +2 -2
  28. package/packages/utility/README.md +19 -0
  29. package/packages/utility/package.json +2 -2
  30. package/packages/utility/skills/summarize/SKILL.md +304 -0
  31. package/packages/utility/skills/utility/SKILL.md +2 -1
  32. package/packages/utility/src/index.ts +2 -0
  33. package/packages/utility/src/summarize/index.ts +51 -0
  34. package/packages/watchdog/package.json +3 -3
  35. package/packages/web-api/package.json +2 -2
  36. package/packages/workflow/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,13 @@ 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.24] — 2026-10-04
10
+
11
+ ### Added
12
+ - `utility`: bundled **`summarize` skill** — the default shape for the agent's final reply after work: a one-sentence answer, numbered sections that open with a bold takeaway, optional mermaid diagram for 3+ step flows, "Problems and fixes", "Not verified", "Still open", and typed questions (`Q1 (pick one)` / `(pick any)` / `(your answer)`, a recommendation only when the work gives a reason). A level gate matches the user's technical depth. Skipped when the user asks for full detail or raw output. Tuned against `ds/deepseek-flash` in a blind A/B arena over 12 real long-session turns.
13
+ - `utility`: **`/unipi:summarize [focus]`** — asks the agent for that summary of the session now (expands `/skill:summarize`, so the skill is always applied; waits for the turn in print mode).
14
+ - `skill-registry`: bundled skills can be **must show by default** (`DEFAULT_MUST_SHOW`, currently `summarize`), with the proxy on or off. A scope can still clear **M** or **E** in `/unipi:skills`.
15
+
9
16
  ## [3.0.0-alpha.23] — 2026-10-04
10
17
 
11
18
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
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.23",
86
- "@pi-unipi/background-tasks": "3.0.0-alpha.23",
87
- "@pi-unipi/btw": "3.0.0-alpha.23",
88
- "@pi-unipi/command-enchantment": "3.0.0-alpha.23",
89
- "@pi-unipi/compactor": "3.0.0-alpha.23",
90
- "@pi-unipi/core": "3.0.0-alpha.23",
91
- "@pi-unipi/footer": "3.0.0-alpha.23",
92
- "@pi-unipi/fusion": "3.0.0-alpha.23",
93
- "@pi-unipi/info-screen": "3.0.0-alpha.23",
94
- "@pi-unipi/input-shortcuts": "3.0.0-alpha.23",
95
- "@pi-unipi/kanboard": "3.0.0-alpha.23",
96
- "@pi-unipi/mcp": "3.0.0-alpha.23",
97
- "@pi-unipi/memory": "3.0.0-alpha.23",
98
- "@pi-unipi/notify": "3.0.0-alpha.23",
99
- "@pi-unipi/skill-registry": "3.0.0-alpha.23",
100
- "@pi-unipi/subagents": "3.0.0-alpha.23",
101
- "@pi-unipi/updater": "3.0.0-alpha.23",
102
- "@pi-unipi/utility": "3.0.0-alpha.23",
103
- "@pi-unipi/web-api": "3.0.0-alpha.23",
104
- "@pi-unipi/workflow": "3.0.0-alpha.23"
85
+ "@pi-unipi/ask-user": "3.0.0-alpha.24",
86
+ "@pi-unipi/background-tasks": "3.0.0-alpha.24",
87
+ "@pi-unipi/btw": "3.0.0-alpha.24",
88
+ "@pi-unipi/command-enchantment": "3.0.0-alpha.24",
89
+ "@pi-unipi/compactor": "3.0.0-alpha.24",
90
+ "@pi-unipi/core": "3.0.0-alpha.24",
91
+ "@pi-unipi/footer": "3.0.0-alpha.24",
92
+ "@pi-unipi/fusion": "3.0.0-alpha.24",
93
+ "@pi-unipi/info-screen": "3.0.0-alpha.24",
94
+ "@pi-unipi/input-shortcuts": "3.0.0-alpha.24",
95
+ "@pi-unipi/kanboard": "3.0.0-alpha.24",
96
+ "@pi-unipi/mcp": "3.0.0-alpha.24",
97
+ "@pi-unipi/memory": "3.0.0-alpha.24",
98
+ "@pi-unipi/notify": "3.0.0-alpha.24",
99
+ "@pi-unipi/skill-registry": "3.0.0-alpha.24",
100
+ "@pi-unipi/subagents": "3.0.0-alpha.24",
101
+ "@pi-unipi/updater": "3.0.0-alpha.24",
102
+ "@pi-unipi/utility": "3.0.0-alpha.24",
103
+ "@pi-unipi/web-api": "3.0.0-alpha.24",
104
+ "@pi-unipi/workflow": "3.0.0-alpha.24"
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23"
43
+ "@pi-unipi/core": "3.0.0-alpha.24"
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/command-enchantment",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
4
4
  "description": "Enhanced TUI autocomplete for /unipi:* commands — colored, sorted, and grouped by package",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -74,6 +74,7 @@ export const COMMAND_REGISTRY: Record<string, string> = {
74
74
  "unipi:cleanup": "utility",
75
75
  "unipi:doctor": "utility",
76
76
  "unipi:answer": "utility",
77
+ "unipi:summarize": "utility",
77
78
 
78
79
  // skill-registry
79
80
  "unipi:skills": "skill-registry",
@@ -160,6 +161,7 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
160
161
  "unipi:cleanup": "Remove stale UniPi temp files and leftovers (preview first)",
161
162
  "unipi:doctor": "Check UniPi's runtime: config, model cache, Decision Model, skills",
162
163
  "unipi:answer": "Answer the last reply — fixed input under the scrollable reply, per question, or web form",
164
+ "unipi:summarize": "Summarize this session — answer first, then findings, open items, questions; add text to set a focus",
163
165
  "unipi:skills": "Manage skills — on/off per project, listed or not, the skill vault",
164
166
 
165
167
  "unipi:kanboard": "Kanboard — capture tasks, run them, open the board",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
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.23",
13
+ "@pi-unipi/core": "3.0.0-alpha.24",
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23"
40
+ "@pi-unipi/core": "3.0.0-alpha.24"
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23",
37
- "@pi-unipi/info-screen": "3.0.0-alpha.23",
36
+ "@pi-unipi/core": "3.0.0-alpha.24",
37
+ "@pi-unipi/info-screen": "3.0.0-alpha.24",
38
38
  "@earendil-works/pi-agent-core": "^0.87.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -108,6 +108,7 @@ export const UTILITY_COMMANDS = {
108
108
  CLEANUP: "cleanup",
109
109
  DOCTOR: "doctor",
110
110
  ANSWER: "answer",
111
+ SUMMARIZE: "summarize",
111
112
  } as const;
112
113
 
113
114
  /** Ask-user tool names */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
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.23",
36
- "@pi-unipi/background-tasks": "3.0.0-alpha.23"
35
+ "@pi-unipi/core": "3.0.0-alpha.24",
36
+ "@pi-unipi/background-tasks": "3.0.0-alpha.24"
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23",
36
- "@pi-unipi/subagents": "3.0.0-alpha.23",
37
- "@pi-unipi/utility": "3.0.0-alpha.23"
35
+ "@pi-unipi/core": "3.0.0-alpha.24",
36
+ "@pi-unipi/subagents": "3.0.0-alpha.24",
37
+ "@pi-unipi/utility": "3.0.0-alpha.24"
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23"
35
+ "@pi-unipi/core": "3.0.0-alpha.24"
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23"
36
+ "@pi-unipi/core": "3.0.0-alpha.24"
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23"
34
+ "@pi-unipi/core": "3.0.0-alpha.24"
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.23",
52
- "@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.23",
53
- "@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.23",
54
- "@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.23",
55
- "@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.23"
51
+ "@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.24",
52
+ "@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.24",
53
+ "@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.24",
54
+ "@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.24",
55
+ "@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.24"
56
56
  }
57
57
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/long-horizon",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
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.23"
40
+ "@pi-unipi/core": "3.0.0-alpha.24"
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23"
33
+ "@pi-unipi/core": "3.0.0-alpha.24"
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23",
46
- "@pi-unipi/info-screen": "3.0.0-alpha.23",
45
+ "@pi-unipi/core": "3.0.0-alpha.24",
46
+ "@pi-unipi/info-screen": "3.0.0-alpha.24",
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.23",
3
+ "version": "3.0.0-alpha.24",
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.23",
37
+ "@pi-unipi/core": "3.0.0-alpha.24",
38
38
  "node-notifier": "^10.0.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -39,7 +39,7 @@ Changes apply from the next prompt. You do not need `/reload`.
39
39
  **Skill settings…** shows each skill with its source (`vault`, `project`, `user`, `unipi` or `package`) and two columns:
40
40
 
41
41
  - **E** (enabled): the agent can see the skill. Off removes it from the session, and blocks `/skill:name`.
42
- - **M** (must show): the agent always sees the skill, also when exposure judging would hide it.
42
+ - **M** (must show): the agent always sees the skill, also when exposure judging would hide it. The bundled `summarize` skill has **M** on by default.
43
43
 
44
44
  | Key | What it does |
45
45
  |---|---|
@@ -16,7 +16,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
16
16
  import { emitEvent, getPackageVersion, harnessMetadata, HUB_OVERLAY_OPTIONS, MODULES, openSettingsHub, registerCommandRunner, setSettings, UNIPI_EVENTS } from "@pi-unipi/core";
17
17
  import { dirname } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
- import { applyRegistry, isBundledSkillLocation, isUnderDir, skillCommandName, skillDir, skillSource, type CatalogSkill } from "./src/registry.js";
19
+ import { applyRegistry, defaultMustShow, isBundledSkillLocation, isUnderDir, skillCommandName, skillDir, skillSource, type CatalogSkill } from "./src/registry.js";
20
20
  import { readSkillsSettings, readStateLayers, writeStateLayers } from "./src/settings.js";
21
21
  import { SkillEditor, type EditorResult } from "./src/editor.js";
22
22
  import { listVaultSkills, vaultDir } from "./src/vault.js";
@@ -187,6 +187,8 @@ export default function skillRegistry(pi: ExtensionAPI) {
187
187
  if (inVault) disabled.add(s.name);
188
188
  return !inVault;
189
189
  });
190
+ // Proxy off: per-skill states are ignored, defaults still apply.
191
+ for (const s of pool) if (defaultMustShow(s.name, skillSource(s, cwd, vault))) mustShow.add(s.name);
190
192
  }
191
193
 
192
194
  const setSkills = (list: CatalogSkill[]) => {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/skill-registry",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
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.23"
36
+ "@pi-unipi/core": "3.0.0-alpha.24"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -18,7 +18,7 @@
18
18
 
19
19
  import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
20
20
  import { frameOverlay } from "@pi-unipi/core";
21
- import type { SkillSource } from "./registry.js";
21
+ import { defaultMustShow, type SkillSource } from "./registry.js";
22
22
  import type { SkillState } from "./settings.js";
23
23
 
24
24
  export type Column = "enabled" | "mustShow";
@@ -52,9 +52,9 @@ export interface EditorOptions {
52
52
  visibleRows?: number;
53
53
  }
54
54
 
55
- function defaultFor(column: Column, source: SkillSource): boolean {
56
- if (column === "enabled") return source !== "vault";
57
- return false;
55
+ function defaultFor(column: Column, skill: EditorSkill): boolean {
56
+ if (column === "enabled") return skill.source !== "vault";
57
+ return defaultMustShow(skill.name, skill.source);
58
58
  }
59
59
 
60
60
  /** Effective value of one cell: project over global over the source default. */
@@ -63,7 +63,7 @@ export function cellValue(layers: Layers, skill: EditorSkill, column: Column): {
63
63
  if (typeof p === "boolean") return { value: p, from: "project" };
64
64
  const g = layers.global[skill.name]?.[column];
65
65
  if (typeof g === "boolean") return { value: g, from: "global" };
66
- return { value: defaultFor(column, skill.source), from: "default" };
66
+ return { value: defaultFor(column, skill), from: "default" };
67
67
  }
68
68
 
69
69
  function printable(data: string): string | undefined {
@@ -43,13 +43,25 @@ export function skillSource(s: CatalogSkill, cwd: string, vault: string): SkillS
43
43
  return "package";
44
44
  }
45
45
 
46
+ /**
47
+ * Bundled UniPi skills that are listed by default ("Must show" on unless a
48
+ * scope turns it off). summarize sets the shape of the agent's final reply,
49
+ * so judging must not hide it on prompts that never mention a summary.
50
+ */
51
+ export const DEFAULT_MUST_SHOW: ReadonlySet<string> = new Set(["summarize"]);
52
+
53
+ /** The default "Must show" value of a skill from its name and source. */
54
+ export function defaultMustShow(name: string, source: SkillSource): boolean {
55
+ return source === "unipi" && DEFAULT_MUST_SHOW.has(name);
56
+ }
57
+
46
58
  export interface EffectiveState {
47
59
  enabled: boolean;
48
60
  mustShow: boolean;
49
61
  }
50
62
 
51
- export function effectiveState(state: SkillState | undefined, source: SkillSource): EffectiveState {
52
- const mustShow = state?.mustShow === true;
63
+ export function effectiveState(state: SkillState | undefined, source: SkillSource, name = ""): EffectiveState {
64
+ const mustShow = state?.mustShow ?? defaultMustShow(name, source);
53
65
  // Must show implies enabled and is always listed.
54
66
  return { enabled: mustShow || (state?.enabled ?? source !== "vault"), mustShow };
55
67
  }
@@ -73,7 +85,7 @@ export function applyRegistry<T extends CatalogSkill>(
73
85
  const disabled = new Set<string>();
74
86
  const mustShow = new Set<string>();
75
87
  for (const skill of catalog) {
76
- const eff = effectiveState(states[skill.name], skillSource(skill, cwd, vault));
88
+ const eff = effectiveState(states[skill.name], skillSource(skill, cwd, vault), skill.name);
77
89
  if (eff.mustShow) mustShow.add(skill.name);
78
90
  if (!eff.enabled) disabled.add(skill.name);
79
91
  else listed.push(skill);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
4
4
  "description": "Subagents for UniPi — Devin-model independent child agents",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -9,8 +9,8 @@
9
9
  "test": "npx tsx --test src/__tests__/*.test.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@pi-unipi/core": "3.0.0-alpha.23",
13
- "@pi-unipi/utility": "3.0.0-alpha.23",
12
+ "@pi-unipi/core": "3.0.0-alpha.24",
13
+ "@pi-unipi/utility": "3.0.0-alpha.24",
14
14
  "@earendil-works/pi-agent-core": "^0.87.1",
15
15
  "acorn": "8.18.0"
16
16
  },
@@ -96,7 +96,8 @@ var init_constants = __esm({
96
96
  RETRY: "retry",
97
97
  CLEANUP: "cleanup",
98
98
  DOCTOR: "doctor",
99
- ANSWER: "answer"
99
+ ANSWER: "answer",
100
+ SUMMARIZE: "summarize"
100
101
  };
101
102
  ASK_USER_TOOLS = {
102
103
  ASK: "ask_user"
@@ -9529,6 +9530,40 @@ function registerAnswerCommand(pi) {
9529
9530
  });
9530
9531
  }
9531
9532
 
9533
+ // packages/utility/src/summarize/index.ts
9534
+ init_core();
9535
+ var SUMMARIZE_SKILL = "summarize";
9536
+ function summarizePrompt(focus) {
9537
+ const f = focus.trim();
9538
+ const scope = f ? `Summarize this session for me, with the focus on: ${f}` : "Summarize this session for me: what we did, why, and where it stands now.";
9539
+ return `/skill:${SUMMARIZE_SKILL} ${scope} Use only what happened in this session. Do not call tools.`;
9540
+ }
9541
+ function hasSummarizeSkill(pi) {
9542
+ try {
9543
+ return pi.getCommands().some((c2) => c2.name === `skill:${SUMMARIZE_SKILL}`);
9544
+ } catch {
9545
+ return false;
9546
+ }
9547
+ }
9548
+ function registerSummarizeCommand(pi) {
9549
+ pi.registerCommand(`${UNIPI_PREFIX}${UTILITY_COMMANDS.SUMMARIZE}`, {
9550
+ description: "Summarize this session for you (answer first, then findings, open items, questions); add text to set a focus",
9551
+ handler: async (args, ctx) => {
9552
+ if (!ctx.isIdle()) {
9553
+ if (ctx.hasUI) ctx.ui.notify("Agent is busy. Press ESC to interrupt, then try again.", "warning");
9554
+ return;
9555
+ }
9556
+ if (!hasSummarizeSkill(pi)) {
9557
+ if (ctx.hasUI) ctx.ui.notify("The summarize skill is off or missing. Turn it on in /unipi:skills.", "warning");
9558
+ return;
9559
+ }
9560
+ pi.sendUserMessage(summarizePrompt(args ?? ""), { expandPromptTemplates: true });
9561
+ await new Promise((r) => setTimeout(r, 0));
9562
+ await ctx.waitForIdle?.();
9563
+ }
9564
+ });
9565
+ }
9566
+
9532
9567
  // packages/utility/src/attach/index.ts
9533
9568
  import { readFileSync as readFileSync9 } from "node:fs";
9534
9569
  import { getCapabilities as getCapabilities2, Image, Key as Key7, matchesKey as matchesKey6, truncateToWidth as truncateToWidth8 } from "@earendil-works/pi-tui";
@@ -12562,6 +12597,7 @@ function src_default(pi) {
12562
12597
  registerUtilityCommands(pi);
12563
12598
  registerAutoRename(pi);
12564
12599
  registerAnswerCommand(pi);
12600
+ registerSummarizeCommand(pi);
12565
12601
  registerAttachments(pi);
12566
12602
  registerImage(pi);
12567
12603
  const style = readUtilSettings().render.style;
@@ -30664,8 +30700,12 @@ function skillSource(s, cwd, vault) {
30664
30700
  if (isUnderDir(dir, `${home}/.agents/skills`) || isUnderDir(dir, `${home}/.pi/agent/skills`)) return "user";
30665
30701
  return "package";
30666
30702
  }
30667
- function effectiveState(state2, source) {
30668
- const mustShow = state2?.mustShow === true;
30703
+ var DEFAULT_MUST_SHOW = /* @__PURE__ */ new Set(["summarize"]);
30704
+ function defaultMustShow(name, source) {
30705
+ return source === "unipi" && DEFAULT_MUST_SHOW.has(name);
30706
+ }
30707
+ function effectiveState(state2, source, name = "") {
30708
+ const mustShow = state2?.mustShow ?? defaultMustShow(name, source);
30669
30709
  return { enabled: mustShow || (state2?.enabled ?? source !== "vault"), mustShow };
30670
30710
  }
30671
30711
  function applyRegistry(catalog, states, cwd, vault) {
@@ -30673,7 +30713,7 @@ function applyRegistry(catalog, states, cwd, vault) {
30673
30713
  const disabled = /* @__PURE__ */ new Set();
30674
30714
  const mustShow = /* @__PURE__ */ new Set();
30675
30715
  for (const skill of catalog) {
30676
- const eff = effectiveState(states[skill.name], skillSource(skill, cwd, vault));
30716
+ const eff = effectiveState(states[skill.name], skillSource(skill, cwd, vault), skill.name);
30677
30717
  if (eff.mustShow) mustShow.add(skill.name);
30678
30718
  if (!eff.enabled) disabled.add(skill.name);
30679
30719
  else listed.push(skill);
@@ -30864,16 +30904,16 @@ init_core();
30864
30904
  import { Key as Key13, matchesKey as matchesKey13, truncateToWidth as truncateToWidth22, visibleWidth as visibleWidth17 } from "@earendil-works/pi-tui";
30865
30905
  var COLUMNS = ["enabled", "mustShow"];
30866
30906
  var LETTER = { enabled: "E", mustShow: "M" };
30867
- function defaultFor(column, source) {
30868
- if (column === "enabled") return source !== "vault";
30869
- return false;
30907
+ function defaultFor(column, skill) {
30908
+ if (column === "enabled") return skill.source !== "vault";
30909
+ return defaultMustShow(skill.name, skill.source);
30870
30910
  }
30871
30911
  function cellValue(layers, skill, column) {
30872
30912
  const p = layers.project[skill.name]?.[column];
30873
30913
  if (typeof p === "boolean") return { value: p, from: "project" };
30874
30914
  const g = layers.global[skill.name]?.[column];
30875
30915
  if (typeof g === "boolean") return { value: g, from: "global" };
30876
- return { value: defaultFor(column, skill.source), from: "default" };
30916
+ return { value: defaultFor(column, skill), from: "default" };
30877
30917
  }
30878
30918
  function printable(data) {
30879
30919
  if (data.length !== 1) return void 0;
@@ -31401,6 +31441,7 @@ function skillRegistry(pi) {
31401
31441
  if (inVault) disabled.add(s.name);
31402
31442
  return !inVault;
31403
31443
  });
31444
+ for (const s of pool) if (defaultMustShow(s.name, skillSource(s, cwd, vault))) mustShow.add(s.name);
31404
31445
  }
31405
31446
  const setSkills = (list) => {
31406
31447
  options.skills = list;
@@ -39665,6 +39706,7 @@ var COMMAND_REGISTRY = {
39665
39706
  "unipi:cleanup": "utility",
39666
39707
  "unipi:doctor": "utility",
39667
39708
  "unipi:answer": "utility",
39709
+ "unipi:summarize": "utility",
39668
39710
  // skill-registry
39669
39711
  "unipi:skills": "skill-registry",
39670
39712
  // ask-user (1 command)
@@ -39727,6 +39769,7 @@ var COMMAND_DESCRIPTIONS = {
39727
39769
  "unipi:cleanup": "Remove stale UniPi temp files and leftovers (preview first)",
39728
39770
  "unipi:doctor": "Check UniPi's runtime: config, model cache, Decision Model, skills",
39729
39771
  "unipi:answer": "Answer the last reply \u2014 fixed input under the scrollable reply, per question, or web form",
39772
+ "unipi:summarize": "Summarize this session \u2014 answer first, then findings, open items, questions; add text to set a focus",
39730
39773
  "unipi:skills": "Manage skills \u2014 on/off per project, listed or not, the skill vault",
39731
39774
  "unipi:kanboard": "Kanboard \u2014 capture tasks, run them, open the board",
39732
39775
  "unipi:kanboard-add": "Kanboard \u2014 capture a task into Backlog",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/updater",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
4
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": "index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "3.0.0-alpha.23"
37
+ "@pi-unipi/core": "3.0.0-alpha.24"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -35,9 +35,28 @@ pi install npm:@pi-unipi/utility
35
35
  | `/unipi:cleanup` | Lists stale UniPi files, then asks before it removes them. `--dry-run` only lists. `--yes` does not ask. |
36
36
  | `/unipi:doctor` | Checks folders, config files, Node, the model cache, the Decision Model and skill exposure. |
37
37
  | `/unipi:answer [reply\|questions\|web]` | Opens a screen to answer the last agent reply. |
38
+ | `/unipi:summarize [focus]` | Asks the agent for a summary of the session. Add text to set a focus. |
38
39
 
39
40
  The skill manager is in [Skill Registry](../skill-registry/README.md) (`/unipi:skills`).
40
41
 
42
+ ## Summaries
43
+
44
+ Utility ships the `summarize` skill. The skill sets the shape of the agent's
45
+ final reply after work:
46
+
47
+ 1. One sentence with the answer or the result.
48
+ 2. Numbered sections. Each section starts with one bold sentence.
49
+ 3. Problems and fixes, when the work hit a problem.
50
+ 4. Not verified, then Still open.
51
+ 5. Questions for you. Each question has a type: pick one, pick any or your answer.
52
+
53
+ The agent uses the skill for every final reply, unless you ask for full detail
54
+ or raw output. `/unipi:summarize` asks for this summary at any time.
55
+
56
+ The skill is "must show" by default, so exposure judging does not hide it. To
57
+ turn this off, open `/unipi:skills` and clear **M** on `summarize`. Clear **E**
58
+ to remove the skill.
59
+
41
60
  ## Agent tools
42
61
 
43
62
  | Tool | What it does |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/utility",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
4
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",
@@ -36,7 +36,7 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@pi-unipi/core": "3.0.0-alpha.23"
39
+ "@pi-unipi/core": "3.0.0-alpha.24"
40
40
  },
41
41
  "devDependencies": {},
42
42
  "peerDependencies": {
@@ -0,0 +1,304 @@
1
+ ---
2
+ name: summarize
3
+ description: |
4
+ Use for every final reply that ends a piece of work (a task, a fix, an
5
+ investigation), and for /unipi:summarize, unless the user asked for full
6
+ detail, raw output, or exact text. Shape: one-sentence answer, numbered
7
+ sections, problems and fixes, not verified, still open, typed questions.
8
+ ---
9
+
10
+ # Summarize for the user
11
+
12
+ Use this skill for the final reply after work (a task, an investigation, a
13
+ fix), and when the user runs /unipi:summarize. "The work" below means the
14
+ session, or the part of it you are summarizing: the user's messages, your
15
+ tool calls and their results, and your replies.
16
+
17
+ ## When not to use it
18
+
19
+ - The user asked for full detail, raw output, a file, code, or exact text.
20
+ Give them that.
21
+ - The reply is short by nature: a one-line answer, a yes or no, a command,
22
+ a question back to the user. Answer in plain sentences.
23
+ - The reply is in the middle of the work, not at its end.
24
+
25
+ Scale the shape to the work. A small task gets the answer sentence and one
26
+ or two bullets. Use the full shape only when the work has several findings,
27
+ changes, or open items.
28
+
29
+ The reader is the user. They asked for the work but did not watch it. They
30
+ read the summary once, quickly, top to bottom. Each line should tell them
31
+ more than the line before. The answer comes first, then the reasons, then
32
+ the details, then what is left for them.
33
+
34
+ You did the work. Write as "I" and call the reader "you".
35
+
36
+ ## Step 1: pick the level
37
+
38
+ Read the user's own messages in the work before you write. Pick one level.
39
+
40
+ - Product level (the default). The user talks about features, screens,
41
+ customers, money, speed, or decisions. Describe what people see and why it
42
+ matters. Use at most three code names in total, and only ones the user needs
43
+ to act on (a file to open, a command to run, a setting to change).
44
+ - Technical level. The user's messages use code names, file paths, commands,
45
+ or error text, or they ask how something works inside the code. Match them:
46
+ name the functions, files, and settings that carry the answer, and explain
47
+ the mechanism. Still start each point with the effect, then the mechanism.
48
+ Count your code names (anything in backticks, plus file, function, route,
49
+ and setting names). Use six or fewer in total. Describe the rest in words. Never give style values, line
50
+ numbers, or full lists of files or pages. Say "about 15 table pages".
51
+
52
+ If the user mixes both, use product level for the answer and the findings.
53
+ Put technical names only where the user must act.
54
+
55
+ ## Step 2: the shape
56
+
57
+ ```
58
+ <Answer: one sentence. No heading above it.>
59
+
60
+ ## 1. <First topic>
61
+ **<One-sentence takeaway of this section.>**
62
+ <diagram, if one fits>
63
+ - **<Label>**: <one or two sentences>
64
+ - **<Label>**: <one or two sentences>
65
+
66
+ ## 2. <Next topic>
67
+ ...
68
+
69
+ ## Problems and fixes (only if the work hit a real obstacle)
70
+ - **<Problem>**: <what blocked the work>. **Fix**: <what I did about it>.
71
+
72
+ ## Not verified
73
+ - **<Label>**: <one sentence>
74
+
75
+ ## Still open
76
+ - **<Label>**: <one sentence>
77
+
78
+ ## Questions for you
79
+ ...
80
+ ```
81
+
82
+ Read the shape as a report to a manager: what we achieved, how, what got in
83
+ the way and how we solved it, what we did not check, what needs approval.
84
+
85
+ The answer line:
86
+ - Exactly one sentence, 25 words or fewer, with no semicolon. It gives the
87
+ answer, the result, or the main finding. Keep caveats out of it. The one
88
+ exception: a caveat that flips the answer ("the fix is not deployed yet").
89
+ - It states what is true, never what you did. "I traced each issue and
90
+ wrote a plan" is wrong. "The filter count is right, but the list hides 4
91
+ of the 6 statuses" is right. If nothing changed yet, say that after the
92
+ finding, in the same sentence.
93
+ - No label such as "Status:" or "Summary:". Do not restate the request.
94
+
95
+ Topic sections:
96
+ - Use two to four sections, numbered. A heading names its content in two to
97
+ six words: "1. Why wake got slow", "2. What I changed".
98
+ - Put the most important section first. Build from the answer: what is true
99
+ now, then why, then what changed.
100
+ - Every section starts with one bold sentence: the takeaway of that section.
101
+ The user can read only the bold sentences and still get the whole story.
102
+ - Details follow as bullets. Each bullet starts with a bold label of one to
103
+ four words, a colon, then one or two sentences.
104
+ - A section has four bullets or fewer, and the whole summary has 14 bullets
105
+ or fewer. If you have more, group them or keep the ones that matter to the
106
+ user. A bullet that only lists items ("chat, posts, captions, comments")
107
+ can usually merge into the bullet above it.
108
+
109
+ ## Step 3: plain words
110
+
111
+ Jargon hides in plain text too, not only in backticks. Before you send, find
112
+ every word a manager outside this project would not know: internal system
113
+ names ("warm assignment", "supernode", "lane"), infrastructure names, and
114
+ abbreviations.
115
+
116
+ - Product level: replace each one with what it does ("the spare pod kept
117
+ ready for the user"). If it appears three times or more, explain it once
118
+ in eight words or fewer and keep it.
119
+ - Technical level: keep it, but explain it once in eight words or fewer the
120
+ first time it appears, unless the user used it first.
121
+ - Write a number with a unit the reader knows: "35 seconds", not "35s p50".
122
+
123
+ ## Step 4: break the text
124
+
125
+ - No paragraph longer than three sentences or about 50 words. Break every
126
+ longer block into bullets.
127
+ - Put the important word first in each sentence. Write "**8 of 12** checks
128
+ failed", not "Of the twelve checks that I ran, eight failed".
129
+ - Bold only these: the section takeaway, the bullet labels, and at most one
130
+ key number or name inside a bullet.
131
+ - Use a short paragraph only for one connected line of reasoning, three
132
+ sentences at most.
133
+
134
+ ## Step 5: add a diagram when it fits
135
+
136
+ Add a mermaid diagram when the work has one of these:
137
+ - a flow of three or more steps,
138
+ - a chain of three or more causes,
139
+ - three or more parts that send things to each other.
140
+
141
+ Do not draw a diagram for a plain list.
142
+
143
+ Diagram rules:
144
+ - Use `flowchart TD` or `sequenceDiagram`. Never use `flowchart LR`: wide
145
+ diagrams break in a terminal.
146
+ - The diagram shows the subject of its own section, nothing else.
147
+ - Use 7 nodes or fewer. If you need more, split the diagram into two, each
148
+ with its own one-line caption.
149
+ - Labels are one to four plain words. Node ids are single letters: A, B, C.
150
+ Put labels in square brackets. Do not put parentheses, quotes, or colons
151
+ inside a label. Edge labels are one to three words: `A -->|fails| B`.
152
+ - Use the same names in the diagram and in the text.
153
+ - Put the diagram right after the bold takeaway of its section.
154
+ - Use two diagrams at most, and one when the summary is product level.
155
+
156
+ ## Step 6: problems and fixes
157
+
158
+ Add this section only when the work hit a real obstacle: something failed,
159
+ blocked progress, or forced a change of plan. Leave it out for normal work.
160
+
161
+ - One bullet per obstacle, three or fewer. Format:
162
+ **<Problem in 2 to 4 words>**: what went wrong, in one sentence.
163
+ **Fix**: what I did about it, in one sentence.
164
+ - If the obstacle is not solved, write **Fix**: none yet, and put the open
165
+ work under "Still open".
166
+ - Do not repeat an obstacle that a topic section already explains.
167
+
168
+ ## Step 7: not verified, then still open
169
+
170
+ Write two short sections. Leave out a section when it is empty. Never write
171
+ a bullet that says there is nothing to report.
172
+
173
+ - "Not verified": what the user should trust less. Numbers that came from
174
+ docs or estimates, tests that did not run, causes that are not confirmed.
175
+ - "Still open": work that is not done yet, and risks.
176
+
177
+ Use bullets with bold labels, three or fewer in each section. Include only
178
+ items that bear on what the user asked.
179
+
180
+ ## Step 8: questions for you
181
+
182
+ Ask only when the work is blocked on a user decision or on information that
183
+ only the user has. If nothing is blocked, leave out this section.
184
+
185
+ Ask two questions or fewer. Ask a third only when the work is blocked on it. Label each question with its type:
186
+
187
+ ```
188
+ **Q1 (pick one)**: Which tier should I cut first?
189
+ - a) Diff rendering, about 1,800 lines **(recommended: largest cut, no runtime risk)**
190
+ - b) Memory fallback, about 800 lines
191
+ - c) Compactor display code, about 900 lines
192
+
193
+ **Q2 (pick any)**: Which tables should get translations in this pass?
194
+ - a) Onboarding review
195
+ - b) Users
196
+ - c) Orders
197
+
198
+ **Q3 (your answer)**: Which three slow users should I check?
199
+ ```
200
+
201
+ - Use "pick one" for a single choice, "pick any" when several can apply, and
202
+ "your answer" when the user must type something: a name, a number, a
203
+ preference, or access.
204
+ - Take the question and the options from the work. Never invent a question
205
+ the work did not raise. A question about a step you only propose is an
206
+ offer, so leave it out.
207
+ - Give two or three options. Merge or drop the weakest.
208
+ - Mark an option **(recommended: reason)** only when the work gives a reason.
209
+ Mark one at most. You can mark a risky option **(not recommended: reason)**.
210
+ - A thing the user must do (not a choice) is one line: **To do**: Set the
211
+ search key on the server.
212
+ - A step that waits for the user's permission is one line:
213
+ **Needs approval**: Deploy the fix to production.
214
+ - Do not offer more work ("Do you want me to...", "I can also...").
215
+
216
+ ## Sentences
217
+
218
+ - One idea per sentence, 20 words or fewer when you can. Active voice.
219
+ - Simple verbs. No semicolons. One name for one thing, every time.
220
+ - Keep the confidence of the work with one short word inline: "likely",
221
+ "about", "estimated". Put every longer caveat at the end (see Step 5):
222
+ where a number came from, what was not tested, what was not confirmed.
223
+ Never write a section or a bullet about what was measured versus claimed.
224
+ - Copy cause and effect only as the work states it. Never add a fact, number,
225
+ cause, or opinion that is not in the work. This applies to diagram arrows
226
+ too: draw an arrow between two things only when the work says one leads to
227
+ the other.
228
+ - Keep the certainty of the work in both directions. If the work states a
229
+ fact plainly, do not soften it to "likely". If the work guesses, do not
230
+ harden it.
231
+ - Copy numbers exactly: "$26.60", not "about $27".
232
+ - Put a baseline next to a number when the work has one that measures the
233
+ same thing: "3.2 s (was 11 s)".
234
+
235
+ ## Cut
236
+
237
+ - Each fact or caveat appears once.
238
+ - Three examples at most in any list inside a sentence.
239
+ - No framing phrases ("The bigger finding is", "In short", "Overall").
240
+ - No "not X but Y", no "X rather than Y", and no "X, not Y" ("mid-June, not months ago", "from a
241
+ test, not production"). State the positive claim. If the user believed
242
+ the wrong thing, say the right thing once.
243
+ - No em dashes. No emoji. No opener, no closer, no offer of more help.
244
+ - No inflated words: robust, seamless, comprehensive, crucial, leverage,
245
+ pivotal, significant, enhance, streamline.
246
+
247
+ ## Length
248
+
249
+ - Product level: 120 to 240 words, not counting diagrams.
250
+ - Technical level or very large work: 300 words at most.
251
+ - If the draft is longer, cut the least important bullets first. Do not cut
252
+ the answer, the bold takeaways, or the questions.
253
+
254
+ ## Example
255
+
256
+ The work: the user said "people keep getting logged out, fix it". The agent
257
+ found a setting in seconds that the code read as minutes, fixed it, tested on
258
+ staging, and found a second, unrelated bug.
259
+
260
+ ```
261
+ Logins now last 24 hours again, after I fixed a setting that ended every session after 24 minutes.
262
+
263
+ ## 1. Why people were logged out
264
+ **The session timer used the wrong unit since the 3 May release.**
265
+ - **Cause**: the setting was written in minutes but read as seconds.
266
+ - **Effect**: every session ended after **24 minutes** (should be 24 hours).
267
+
268
+ ## 2. What I changed
269
+ **I fixed the unit and tested the fix on staging.**
270
+ - **Fix**: the setting now states its unit, and the code reads it the same way.
271
+ - **Test**: a staging login stayed active for 3 hours with no logout.
272
+
273
+ ## Problems and fixes
274
+ - **Bug did not show locally**: my machine kept old sessions alive. **Fix**: I tested on staging with a fresh login.
275
+
276
+ ## Not verified
277
+ - **Long sessions**: the staging test ran 3 hours, so a full 24-hour session is untested.
278
+
279
+ ## Still open
280
+ - **Production**: the fix is not deployed yet.
281
+ - **Second bug**: the "remember me" box does nothing. It is a separate problem.
282
+
283
+ ## Questions for you
284
+ **Q1 (pick one)**: When should I deploy?
285
+ - a) Now **(recommended: users are logged out every 24 minutes)**
286
+ - b) With Friday's release
287
+ ```
288
+
289
+ ## Check before you send
290
+
291
+ - Is the answer one sentence, with no label and no restated request?
292
+ - Does every section start with a bold takeaway?
293
+ - Is any paragraph longer than three sentences? Break it.
294
+ - Did you match the user's level?
295
+ - If there is a flow of three or more steps, is there a diagram?
296
+ - Is each question labeled with its type, with a recommendation only where
297
+ the work gives a reason?
298
+ - Does any fact, caveat, or number appear twice? Delete the second one.
299
+ - Is there a caveat longer than one word in the answer, a takeaway, or a
300
+ bullet? Move it to the end.
301
+ - Is the word count inside the limit for the level?
302
+ - Would a manager outside this project understand every word? Replace or
303
+ explain the rest.
304
+ - Does every diagram arrow match a cause or step the work states?
@@ -4,7 +4,7 @@ scope: agent
4
4
  description: |
5
5
  What the @pi-unipi/utility extension does for the user: the /unipi:settings
6
6
  hub, /unipi:continue (/unipi:retry), /unipi:cleanup, /unipi:doctor,
7
- and automatic session naming. It gives the agent no tools.
7
+ /unipi:summarize, and automatic session naming. It gives the agent no tools.
8
8
  ---
9
9
 
10
10
  # @pi-unipi/utility
@@ -17,6 +17,7 @@ description: |
17
17
  | `/unipi:continue` / `/unipi:retry` | Take another turn without new text |
18
18
  | `/unipi:cleanup` | Remove stale UniPi temp files (previews and asks first; `--dry-run`, `--yes`) |
19
19
  | `/unipi:doctor` | Check config, model cache, Decision Model key, skill exposure |
20
+ | `/unipi:summarize [focus]` | Ask the agent for a session summary (uses the `summarize` skill) |
20
21
 
21
22
  ## Notes for the agent
22
23
 
@@ -27,6 +27,7 @@ import {
27
27
  import { registerUtilityCommands } from "./commands.js";
28
28
  import { registerAutoRename } from "./rename/index.js";
29
29
  import { registerAnswerCommand } from "./answer/index.js";
30
+ import { registerSummarizeCommand } from "./summarize/index.js";
30
31
  import { registerAttachments } from "./attach/index.js";
31
32
  import { imageCatalogEntries, loadImageConfig, refreshImageModelCache, registerImage } from "./image/index.js";
32
33
  import { registerToolRenderers } from "./render/tools.js";
@@ -66,6 +67,7 @@ export default function (pi: ExtensionAPI) {
66
67
  registerUtilityCommands(pi);
67
68
  registerAutoRename(pi);
68
69
  registerAnswerCommand(pi);
70
+ registerSummarizeCommand(pi);
69
71
  registerAttachments(pi);
70
72
  registerImage(pi);
71
73
  const style = readUtilSettings().render.style;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @pi-unipi/utility — /unipi:summarize [focus]
3
+ *
4
+ * Asks the agent for a summary of the session in the shape the bundled
5
+ * `summarize` skill sets. The command sends `/skill:summarize`, so pi
6
+ * inlines the skill file into the turn, and the agent cannot skip it.
7
+ */
8
+
9
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
10
+ import { UNIPI_PREFIX, UTILITY_COMMANDS } from "@pi-unipi/core";
11
+
12
+ export const SUMMARIZE_SKILL = "summarize";
13
+
14
+ /** The prompt the command sends. `focus` narrows the summary; empty means the whole session. */
15
+ export function summarizePrompt(focus: string): string {
16
+ const f = focus.trim();
17
+ const scope = f
18
+ ? `Summarize this session for me, with the focus on: ${f}`
19
+ : "Summarize this session for me: what we did, why, and where it stands now.";
20
+ return `/skill:${SUMMARIZE_SKILL} ${scope} Use only what happened in this session. Do not call tools.`;
21
+ }
22
+
23
+ /** True when pi can expand `/skill:summarize` (the skill is loaded and on). */
24
+ export function hasSummarizeSkill(pi: ExtensionAPI): boolean {
25
+ try {
26
+ return pi.getCommands().some((c) => c.name === `skill:${SUMMARIZE_SKILL}`);
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ export function registerSummarizeCommand(pi: ExtensionAPI): void {
33
+ pi.registerCommand(`${UNIPI_PREFIX}${UTILITY_COMMANDS.SUMMARIZE}`, {
34
+ description: "Summarize this session for you (answer first, then findings, open items, questions); add text to set a focus",
35
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
36
+ if (!ctx.isIdle()) {
37
+ if (ctx.hasUI) ctx.ui.notify("Agent is busy. Press ESC to interrupt, then try again.", "warning");
38
+ return;
39
+ }
40
+ if (!hasSummarizeSkill(pi)) {
41
+ if (ctx.hasUI) ctx.ui.notify("The summarize skill is off or missing. Turn it on in /unipi:skills.", "warning");
42
+ return;
43
+ }
44
+ pi.sendUserMessage(summarizePrompt(args ?? ""), { expandPromptTemplates: true });
45
+ // sendUserMessage does not wait. Print mode (-p) exits when the handler
46
+ // returns, so wait for the summary turn to start and end.
47
+ await new Promise((r) => setTimeout(r, 0));
48
+ await ctx.waitForIdle?.();
49
+ },
50
+ });
51
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/watchdog",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
4
4
  "description": "Jev watchdog for long-running tool calls and background tasks — judges stuck work via TypeSafe System One and kills or warns",
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/background-tasks": "3.0.0-alpha.23",
36
- "@pi-unipi/core": "3.0.0-alpha.23"
35
+ "@pi-unipi/background-tasks": "3.0.0-alpha.24",
36
+ "@pi-unipi/core": "3.0.0-alpha.24"
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/web-api",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
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": "3.0.0-alpha.23",
34
+ "@pi-unipi/core": "3.0.0-alpha.24",
35
35
  "defuddle": "^0.18.1",
36
36
  "linkedom": "^0.18.12",
37
37
  "wreq-js": "^2.3.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/workflow",
3
- "version": "3.0.0-alpha.23",
3
+ "version": "3.0.0-alpha.24",
4
4
  "description": "Plan mode and permission modes for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@pi-unipi/core": "3.0.0-alpha.23"
30
+ "@pi-unipi/core": "3.0.0-alpha.24"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@earendil-works/pi-ai": "^0.87.1",