@zihanw/pi-forge 0.5.0 → 0.5.1

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 (100) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +2 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/context-diff-history.d.ts +61 -0
  5. package/dist/context-diff-history.d.ts.map +1 -0
  6. package/dist/context-diff-history.js +84 -0
  7. package/dist/context-diff-history.js.map +1 -0
  8. package/dist/context-diff-snapshot.d.ts +19 -0
  9. package/dist/context-diff-snapshot.d.ts.map +1 -0
  10. package/dist/context-diff-snapshot.js +146 -0
  11. package/dist/context-diff-snapshot.js.map +1 -0
  12. package/dist/context-diff.d.ts +70 -0
  13. package/dist/context-diff.d.ts.map +1 -0
  14. package/dist/context-diff.js +259 -0
  15. package/dist/context-diff.js.map +1 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +5 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/lifecycle.d.ts +2 -0
  20. package/dist/lifecycle.d.ts.map +1 -1
  21. package/dist/lifecycle.js +2 -0
  22. package/dist/lifecycle.js.map +1 -1
  23. package/dist/payload-capture.d.ts +10 -0
  24. package/dist/payload-capture.d.ts.map +1 -1
  25. package/dist/payload-capture.js +39 -9
  26. package/dist/payload-capture.js.map +1 -1
  27. package/dist/payload-command.d.ts +2 -0
  28. package/dist/payload-command.d.ts.map +1 -1
  29. package/dist/payload-command.js +36 -6
  30. package/dist/payload-command.js.map +1 -1
  31. package/dist/payload-state.d.ts +3 -0
  32. package/dist/payload-state.d.ts.map +1 -1
  33. package/dist/payload-state.js +5 -0
  34. package/dist/payload-state.js.map +1 -1
  35. package/dist/preview.d.ts.map +1 -1
  36. package/dist/preview.js +17 -3
  37. package/dist/preview.js.map +1 -1
  38. package/dist/runtime/tool-policy-runtime.d.ts.map +1 -1
  39. package/dist/runtime/tool-policy-runtime.js +13 -3
  40. package/dist/runtime/tool-policy-runtime.js.map +1 -1
  41. package/dist/runtime/web-editor-runtime.d.ts +2 -1
  42. package/dist/runtime/web-editor-runtime.d.ts.map +1 -1
  43. package/dist/runtime/web-editor-runtime.js +8 -3
  44. package/dist/runtime/web-editor-runtime.js.map +1 -1
  45. package/dist/ui-contribution/contrib-port.d.ts +170 -0
  46. package/dist/ui-contribution/contrib-port.d.ts.map +1 -0
  47. package/dist/ui-contribution/contrib-port.js +640 -0
  48. package/dist/ui-contribution/contrib-port.js.map +1 -0
  49. package/dist/ui-contribution/index.d.ts +3 -0
  50. package/dist/ui-contribution/index.d.ts.map +1 -0
  51. package/dist/ui-contribution/index.js +2 -0
  52. package/dist/ui-contribution/index.js.map +1 -0
  53. package/dist/web-editor/client-script.generated.d.ts.map +1 -1
  54. package/dist/web-editor/client-script.generated.js +1 -1
  55. package/dist/web-editor/client-script.generated.js.map +1 -1
  56. package/dist/web-editor/client-styles.generated.d.ts.map +1 -1
  57. package/dist/web-editor/client-styles.generated.js +1 -1
  58. package/dist/web-editor/client-styles.generated.js.map +1 -1
  59. package/dist/web-editor/contrib-service.d.ts +41 -0
  60. package/dist/web-editor/contrib-service.d.ts.map +1 -0
  61. package/dist/web-editor/contrib-service.js +174 -0
  62. package/dist/web-editor/contrib-service.js.map +1 -0
  63. package/dist/web-editor/line-diff.d.ts +28 -0
  64. package/dist/web-editor/line-diff.d.ts.map +1 -0
  65. package/dist/web-editor/line-diff.js +216 -0
  66. package/dist/web-editor/line-diff.js.map +1 -0
  67. package/dist/web-editor/schema-form.d.ts +63 -0
  68. package/dist/web-editor/schema-form.d.ts.map +1 -0
  69. package/dist/web-editor/schema-form.js +213 -0
  70. package/dist/web-editor/schema-form.js.map +1 -0
  71. package/dist/web-editor/server.d.ts.map +1 -1
  72. package/dist/web-editor/server.js +71 -6
  73. package/dist/web-editor/server.js.map +1 -1
  74. package/dist/web-editor/styles.d.ts.map +1 -1
  75. package/dist/web-editor/styles.js +188 -21
  76. package/dist/web-editor/styles.js.map +1 -1
  77. package/dist/web-editor/types.d.ts +12 -0
  78. package/dist/web-editor/types.d.ts.map +1 -1
  79. package/dist/web-host.d.ts +2 -0
  80. package/dist/web-host.d.ts.map +1 -1
  81. package/dist/web-host.js +1 -0
  82. package/dist/web-host.js.map +1 -1
  83. package/docs/README.md +1 -0
  84. package/docs/concepts/agent-profiles.md +1 -1
  85. package/docs/design/0.5.1-plan.md +237 -0
  86. package/docs/design/architecture-0.5.md +12 -1
  87. package/docs/design/context-diff-plan.md +8 -2
  88. package/docs/development/release.md +7 -1
  89. package/docs/guides/delegation.md +3 -3
  90. package/docs/guides/web-editor.md +1 -1
  91. package/docs/reference/commands.md +1 -1
  92. package/docs/reference/configuration.md +2 -2
  93. package/docs/reference/public-api.md +14 -2
  94. package/docs/reference/stack-schema.md +1 -1
  95. package/docs/reference/ui-contribution-port.md +51 -0
  96. package/docs/zh-CN/concepts/agent-profiles.md +1 -1
  97. package/docs/zh-CN/concepts/prompt-stacks.md +1 -1
  98. package/docs/zh-CN/guides/delegation.md +2 -2
  99. package/docs/zh-CN/reference/commands.md +1 -1
  100. package/package.json +6 -1
@@ -0,0 +1,237 @@
1
+ # pi-forge 0.5.1 — Combined Release Plan
2
+
3
+ Status: active. Target release: 0.5.1 (first feature release after 0.5.0).
4
+ Owner of truth: this document. Supersedes/folds in:
5
+ - `docs/design/context-diff-plan.md` (context diff, now Lane 2)
6
+ - The subagent UI-contribution discussion from the 0.5.x review thread (now Lane 1)
7
+
8
+ Theme: **observability + subagent usability**. 0.5.0 was the architecture-split
9
+ release; 0.5.1 ships per-turn prompt observability and makes the optional
10
+ subagent package configurable without hand-editing JSON.
11
+
12
+ ## Lane ordering (deliberate)
13
+
14
+ **Lane 1 ships first.** Its tab-registry refactor turns the web editor's tab
15
+ system data-driven, which Lane 2's Preview dock builds on. Both lanes touch the
16
+ client build pipeline and the tab area — do not run them in parallel in the
17
+ same working tree.
18
+
19
+ ---
20
+
21
+ ## Lane 1 — UI contribution framework + subagent config UI
22
+
23
+ ### Goal
24
+
25
+ Let the optional `@zihanw/pi-forge-subagents` package own its configuration UI
26
+ (schema, validation, defaults, file writes) while the rendered settings tab
27
+ appears inside the pi-forge web editor when — and only when — the subagent
28
+ package is installed.
29
+
30
+ Non-goal: arbitrary JS/component injection into the forge web UI. The event bus
31
+ carries JSON only; that rule does not bend for UI.
32
+
33
+ ### Architecture (schema-driven contribution, "plan A")
34
+
35
+ ```
36
+ ┌─ pi-forge-subagents ─────────────┐ ┌─ pi-forge ─────────────────┐
37
+ │ UI contribution provider: │ bus │ web host discovers provider │
38
+ │ · getContribution() → { tabId, │ ◄────── │ at startup (capability │
39
+ │ title, icon, schema, values } │ JSON │ negotiation) │
40
+ │ · writeValues(patch) → validate │ ──────► │ generic schema-form tab │
41
+ │ + write subagents.json │ │ (self-contained Vue comp.) │
42
+ └──────────────────────────────────┘ │ PUT /api/contrib/<id> proxy │
43
+ └─────────────────────────────┘
44
+ ```
45
+
46
+ - **Contribution protocol**: new versioned port, separate version counter from
47
+ the v1 host port. All payloads are plain recursively-validated JSON (schema +
48
+ values), same discipline as `host-port.ts`.
49
+ - **Forge side** is fully generic: it knows nothing about subagents. No
50
+ provider discovered → no contributed tab, zero cost.
51
+ - **Config ownership stays put**: `subagents.json` (project/global + legacy
52
+ fallback) remains owned and written by the subagent package. Forge's web
53
+ server only proxies the bus call.
54
+
55
+ ### Forge-side changes (~1.5–2d)
56
+
57
+ 1. Tab registry: replace the hardcoded `"policy" | "regex" | "stack"` union in
58
+ `vue-tab-host.ts` with a data-driven registry (string tab ids, dynamic
59
+ buttons in `App.vue`).
60
+ 2. Generic schema-form renderer: self-contained Vue component, bridged via the
61
+ vue-host mechanism. Restricted field types for v1: boolean, number, enum,
62
+ string, plus a record/table shape for per-profile entries. **Do NOT add
63
+ imperative code to `legacy-editor.ts`.**
64
+ 3. Web server: `PUT /api/contrib/<tabId>` route proxying `writeValues` over the
65
+ bus; contribution descriptors fetched at page load through `/api/contrib`.
66
+ 4. Contribution discovery: web host acts as client toward the provider port;
67
+ tolerate provider disappear/reappear across sessions.
68
+
69
+ ### Subagents-side changes (~1d)
70
+
71
+ 1. Provider endpoint implementing the contribution protocol.
72
+ 2. `ForgeSubagentSettings` → schema mapping (backend, timeoutMs,
73
+ approval flag, summary-in-description flag, per-profile
74
+ enabled/backend/timeout table).
75
+ 3. Server-side re-validation on write, reusing existing validators
76
+ (`isValidSubagentTimeoutMs` et al.). Never trust the web client.
77
+ 4. TUI companion: `/forge-agent config` subcommand for the same settings
78
+ (internal to the package, no cross-repo coordination).
79
+
80
+ ### Known v1 limitation
81
+
82
+ Pure schema cannot express "dropdown fed by live forge data" (e.g. a profileId
83
+ picker backed by `listProfiles`). v1: plain text input + validation errors.
84
+ v2 candidate: a "remote data source" field type. Do not gold-plate v1.
85
+
86
+ ### Lane 1B — subagent call-time UX (subagents package only)
87
+
88
+ Two usability extras that live entirely in `@zihanw/pi-forge-subagents`; no
89
+ forge-side coupling, safe to build in parallel with the forge contribution
90
+ framework.
91
+
92
+ **1. Call-time model override** (~0.5d)
93
+
94
+ Main agent may pick the execution model per `forge_subagent` call; default
95
+ remains the profile's model. Feasibility already verified: the wire carries
96
+ model facts (`ForgeBackendFacts.model`, `ForgePrepareResponse.model`), and the
97
+ pi-subagent-runtime subprocess backend already spawns with `--model` — only
98
+ the tool surface lacks the knob.
99
+
100
+ - Add optional `model` parameter (`provider/id` string, parsed + validated) to
101
+ `ForgeSubagentParameters`.
102
+ - Resolution order: `model` param > profile default.
103
+ - Same policy as the `backend` param: override allowed for interactively
104
+ approved runs; unattended invocation is pinned to the profile/configured
105
+ model.
106
+ - Approval summary's existing `Model:` line shows the effective model for free.
107
+ - Update tool description accordingly.
108
+
109
+ **2. Restore rich TUI rendering for subagent runs** (~0.5–1d)
110
+
111
+ The 0.4 tool shipped `renderCall`/`renderResult` (pi-tui Container/Markdown,
112
+ collapsed/expanded states, live progress, usage stats); the 0.5 split dropped
113
+ them, falling back to the default tool display. Streaming infrastructure is
114
+ still intact (`onUpdate` progress pushes, `details.progress` ring buffer at
115
+ `MAX_PROGRESS_ITEMS`).
116
+
117
+ - Port the 0.4.1 renderers from git history (`v0.4.1:src/subagent-tool.ts`)
118
+ into `forge-subagent.ts`, adapted to the current details shape and including
119
+ the approval receipt.
120
+ - `@earendil-works/pi-tui` is already an optional peer dependency.
121
+ - Reference the historical render tests where recoverable.
122
+
123
+ ---
124
+
125
+ ## Lane 2 — Context diff (folded from context-diff-plan.md)
126
+
127
+ ### Goal
128
+
129
+ Per-turn observability for prompt changes so users can optimize KV-cache reuse:
130
+
131
+ - Mark which blocks of the prompt changed after each turn.
132
+ - Show token delta (added/removed/modified) vs the previous turn.
133
+ - Mark where the KV-cache prefix survives ("cache boundary").
134
+
135
+ Secondary goal: merge with live preview — while editing a stack,
136
+ debounce-compile and diff against the previous compile, so edits show their
137
+ prompt/token impact immediately.
138
+
139
+ ### Existing building blocks
140
+
141
+ - `src/payload-capture.ts` — real provider request payload (secret-redacted),
142
+ with deliberately rough `approxTokens` (chars/4).
143
+ - Pi's authoritative assistant `message_end` usage — input, output, cache read,
144
+ and cache write token buckets correlated FIFO with captured provider requests.
145
+ - `src/preview.ts` — edit-time compile output split into sections (system +
146
+ per-message), each with chars/approxTokens.
147
+ - Legacy editor already polls payload state every 2s.
148
+
149
+ ### Two data sources
150
+
151
+ | Scenario | Source | Question answered |
152
+ |---|---|---|
153
+ | Edit time (live preview merge point) | debounced compile vs previous compile | "what does this edit change, how many tokens" |
154
+ | Run time (request structure) | diff of consecutive real payloads | "where would the reusable serialized prefix end, and what changed" |
155
+ | Run time (provider truth) | correlated assistant usage | "how many prompt tokens and cache reads/writes did the provider report" |
156
+
157
+ ### Core engine: `src/context-diff.ts` (host-neutral pure functions)
158
+
159
+ ```
160
+ TurnSnapshot { turnId, capturedAt, stackId, blocks: Block[] }
161
+ Block { key, role, text, chars, approxTokens, hash }
162
+ TurnDiff { blocks: DiffBlock[], prefixTokens, prefixRatio, deltaTokens, summary }
163
+ DiffBlock { status: same|added|removed|modified, before?, after?, tokenDelta }
164
+ ```
165
+
166
+ Cache-boundary algorithm: KV-cache hits depend on the longest common prefix of
167
+ the serialized request. Walk the block arrays in order while hashes match; at
168
+ the first mismatch, trim a char-level common prefix inside that block and
169
+ convert to tokens. Render a boundary marker: "cache valid up to ~18,204 tokens
170
+ (63% of prompt)". No Myers diff needed — prefix + block classification
171
+ suffices.
172
+
173
+ Honesty note: diff token figures remain chars/4 estimates and are always labeled
174
+ as such. Provider prompt/cache usage is displayed separately. Cache hit rate is
175
+ `cacheRead / (input + cacheRead + cacheWrite)` only after cache activity has
176
+ been observed for that provider/model; an all-zero normalized cache bucket is
177
+ otherwise reported as unavailable because it cannot distinguish a true miss
178
+ from a provider that did not report cache detail.
179
+
180
+ ### UI
181
+
182
+ Promote the Preview modal into a dockable right-side panel with three tabs:
183
+
184
+ - **Compiled** — current preview content, auto-refresh with 500ms debounce
185
+ while editing (this is the live preview).
186
+ - **Draft diff** and **Run diff** — git-style unified/split line views with
187
+ old/new line numbers, inline changed spans, and selectable changes-only,
188
+ three-line, or full context. Run metadata keeps estimated reusable prefix
189
+ separate from actual provider usage and cache-hit rate.
190
+
191
+ Run-time mode: payload poll captures a new payload → rolling history (last 20
192
+ turns) → auto-compute diff.
193
+ Edit mode: diff current edited compile vs the active on-disk version.
194
+
195
+ Implementation constraint: self-contained Vue component bridged via the
196
+ vue-host mechanism, riding Lane 1's data-driven dock/tab registry. Do NOT add
197
+ more imperative code to `legacy-editor.ts`. Do NOT refactor the legacy editor
198
+ in the same lane.
199
+
200
+ ### Phases / estimate
201
+
202
+ | Phase | Content | Effort |
203
+ |---|---|---|
204
+ | 0 | Design freeze + golden fixtures (turn payload sets) | 0.5d |
205
+ | 1 | `context-diff.ts` engine + unit tests (prefix/add/remove/modify/token rollups) | 1d |
206
+ | 2 | Server endpoint + rolling snapshot state on the web host | 0.5d |
207
+ | 3 | Web UI: preview dock + Diff view (self-contained Vue, bridged, on Lane 1's registry) | 1.5d |
208
+ | 4 | Browser tests + docs + changelog | 0.5–1d |
209
+
210
+ Lane 2 total ~4–5 working days. Descope option (~3d): run-time diff only,
211
+ live preview reduced to plain auto-refresh without edit-time diffing.
212
+
213
+ ---
214
+
215
+ ## Combined estimate
216
+
217
+ | Lane | Content | Effort |
218
+ |---|---|---|
219
+ | 1 | UI contribution framework (forge) + provider & config UI (subagents) | ~2.5–3d |
220
+ | 1B | Call-time model override + rich TUI restore (subagents only) | ~1–1.5d |
221
+ | 2 | Context diff engine + dock UI | ~4–5d |
222
+
223
+ ~8.5–9.5 working days total, ~2 calendar weeks with review. Both lanes require
224
+ coordinated releases: forge 0.5.1 must understand the contribution protocol
225
+ before (or simultaneously with) subagents 0.5.1 advertising it — the
226
+ capability negotiation makes order-independent rollout safe. Lane 1B lives
227
+ entirely in subagents 0.5.1 and ships with it; the promo should present both
228
+ packages together.
229
+
230
+ ## Release narrative
231
+
232
+ 0.5.1 headlines "observability + subagent UX". The promo video leads with the
233
+ context diff money shot (edit one system-prompt line, watch the cache-boundary
234
+ marker jump). Promo pipeline follows the AIGC/VIDEO_PRODUCTION ep02 reference:
235
+ `PLAN.md` → script draft → `narration.json` → roughcut → review; terminology
236
+ rule: plain words first, formal term named once ("KV cache" on first mention,
237
+ then "prefix cache / cache reuse region").
@@ -36,6 +36,15 @@ It is not a platformization release.
36
36
  12. **Public surfaces are exactly three intentional entry points:** package root default factory, package root named extension API (`registerMacro`, `registerSlot`, and their contract types), and `@zihanw/pi-forge/subagent`. All other root re-exports and `src/*` aliases are removed. `check-package` enforces this allowlist.
37
37
  13. **Migration is a small utility plus release notes, not a framework.** A v1-to-v2 script converts mechanical `variables`/macro fields with explicit diagnostics; removed behavior is never silently approximated.
38
38
 
39
+ ## Accepted 0.5.1 amendment: generic settings contributions
40
+
41
+ Dogfooding invalidated decisions 10 and 12 as forward-looking constraints, while preserving their 0.5.0 historical outcome. The accepted 0.5.1 amendment is:
42
+
43
+ 1. `@zihanw/pi-forge/ui-contribution` is a fourth intentional, experimental entry point. It is a generic, versioned, data-only event-bus port; main pi-forge owns the renderer and HTTP proxy but has no subagent-specific schema or persistence logic.
44
+ 2. Optional packages may contribute restricted schema-driven Settings pages. Schemas and values are recursively validated JSON data; provider handlers may be asynchronous and receive generation-bound cancellation before side effects.
45
+ 3. `pi-forge-subagents` remains the sole owner of both `subagents.json` files. It obtains profile choices only through `/subagent`, contributes plain settings descriptors through `/ui-contribution`, and performs all subagent validation and persistence itself.
46
+ 4. This amendment does not authorize a general plugin UI/component runtime, arbitrary browser code, a second resource registry, or main-package reads/writes of optional-package configuration.
47
+
39
48
  ## Extension port contract (0.5.0)
40
49
 
41
50
  The 0.5.0 extension contract is part of the breaking release. It is a trusted-extension port, not a security boundary.
@@ -77,6 +86,7 @@ flowchart LR
77
86
  Workspace --> Compiler["forge-v1 compiler"]
78
87
  Compiler --> Extensions["Trusted extension port"]
79
88
  Workspace -. "event-bus host port v1" .-> Optional["pi-forge-subagents"]
89
+ WebEditor["Generic Settings renderer"] -. "UI contribution port v1" .-> Optional
80
90
  Optional --> Runtime["pi-subagent-runtime"]
81
91
  ```
82
92
 
@@ -193,6 +203,7 @@ Lane 4e: release.
193
203
  - Schema v2, extension contract, and migration notes are documented.
194
204
  - Main package has no subagent runtime dependency and passes packed smoke tests alone.
195
205
  - Optional package passes packed smoke tests through host port v1 and owns only its dedicated config files.
206
+ - UI contribution port passes schema validation, async generation cancellation, provider churn, and packed optional-consumer tests.
196
207
  - No non-allowlisted root exports or `src/*` aliases remain.
197
208
  - User-facing breaking changes are documented in English and Chinese.
198
209
 
@@ -205,7 +216,7 @@ These items come from the archived full plan and are intentionally not part of 0
205
216
  - automatic dependency-direction checking;
206
217
  - full `PromptStackService` / `AgentProfileService` application facades;
207
218
  - complete host RPC operation catalogue, progress events, and richer lifecycle features beyond host port v1;
208
- - optional-package standalone delegation UI or a main-editor contribution port;
219
+ - optional-package standalone delegation UI and arbitrary contributed UI components;
209
220
  - full public-surface classification register and consumer audit repeat;
210
221
  - rolling Pi compatibility matrix and scheduled latest-Pi probe;
211
222
  - sandbox, staged writes, new prompt features, richer imports, and orchestration.
@@ -1,7 +1,13 @@
1
1
  # Context Diff — design plan (post-0.5.0)
2
2
 
3
- Status: planned. Target release: 0.5.1 (first feature release after 0.5.0).
4
- Owner of truth: this document; discussion record lives in the 0.5.x review thread.
3
+ Status: folded into `docs/design/0.5.1-plan.md` as Lane 2 — that file is now the owner of truth. Kept for discussion history.
4
+
5
+ Implemented follow-up: the dock now provides git-style line diffs and correlates
6
+ captured requests with Pi's assistant usage. The structural cache-prefix figure
7
+ remains a chars/4 estimate; provider-reported cache hit rate is shown separately
8
+ and is unavailable until cache reporting is observed for that provider/model.
9
+
10
+ Original status: planned. Target release: 0.5.1 (first feature release after 0.5.0).
5
11
 
6
12
  ## Goal
7
13
 
@@ -6,12 +6,18 @@
6
6
 
7
7
  1. Confirm the changelog and user documentation describe the intended version and experimental surfaces accurately.
8
8
  2. Publish and smoke-test any required `@zihanw/pi-subagent-runtime` version first.
9
- 3. Install dependencies from the lockfile and run `npm run verify`.
9
+ 3. Install dependencies from the lockfile and run `npm run verify`; require the
10
+ Ubuntu, macOS, and Windows GitHub Actions jobs to pass for the release commit.
10
11
  4. Test a packed installation against the documented minimum and current Pi versions.
11
12
  5. Exercise ordinary stack/profile use independently of delegation.
12
13
  6. Exercise both configured foreground backends and confirm unsupported host capabilities fail closed before provider transport.
13
14
  7. Inspect `npm pack --dry-run` for package size and unexpected or missing files.
14
15
 
16
+ The macOS and Windows jobs run the same complete verification surface as Linux,
17
+ including the real-browser editor suite against the hosted runner's system
18
+ Chrome. Compatibility-version and scheduled latest-Pi probes remain Linux-only;
19
+ they test dependency drift rather than operating-system behavior.
20
+
15
21
  ## Dependency policy
16
22
 
17
23
  Published manifests use wildcard peer dependencies for Pi-host-provided SDK packages. Exact versions belong in development dependencies and the lockfile so tests are reproducible without restricting compatible host releases.
@@ -15,11 +15,11 @@ Profiles are not delegatable by default. Enable each eligible ID in the trusted
15
15
  "backend": "pi-subprocess-readonly",
16
16
  "timeoutMs": 60000,
17
17
  "profiles": {
18
- "reviewer": {
18
+ "project:reviewer": {
19
19
  "enabled": true,
20
20
  "timeoutMs": 300000
21
21
  },
22
- "rpc-reviewer": {
22
+ "project:rpc-reviewer": {
23
23
  "enabled": true,
24
24
  "backend": "pi-rpc-readonly",
25
25
  "timeoutMs": 180000
@@ -28,7 +28,7 @@ Profiles are not delegatable by default. Enable each eligible ID in the trusted
28
28
  }
29
29
  ```
30
30
 
31
- Legacy `.pi/forge/config.json.subagents` is accepted as read-only fallback with a warning. Enablement follows the profile's scope. A global `~/.pi/forge/subagents.json` may define general `backend` and `timeoutMs` defaults and may authorize `global:<id>` profiles through its own `profiles` map. The trusted project's `subagents.json` authorizes `project:<id>` profiles. Same-ID global and project profiles never inherit enablement, backend, or timeout policy from one another. Disabled or unlisted profiles are hidden from discovery and rejected even if guessed.
31
+ Legacy `.pi/forge/config.json.subagents` is accepted as read-only fallback with a warning. Authorization keys should be canonical selectors: `project:<id>` or `global:<id>`. Bare keys remain a compatibility spelling for project profiles only, even inside `~/.pi/forge/subagents.json`; use an explicit key such as `global:reviewer` to authorize a global profile. Same-ID global and project profiles never inherit enablement, backend, or timeout policy from one another. Disabled or unlisted profiles are hidden from discovery and rejected even if guessed.
32
32
 
33
33
  ## Discover, plan, and run
34
34
 
@@ -43,7 +43,7 @@ The stack workspace provides:
43
43
  - payload arming and redacted captured-payload inspection;
44
44
  - light and dark themes.
45
45
 
46
- Existing IDs are immutable during edit. Use **Fork** to create a different ID without breaking profile references or the active selection. The toolbar scope selector (default `project`) chooses where new stacks, imports, and forks are written: `global` targets the user-global `~/.pi/forge/prompt-stacks`, `project` targets `.pi/forge/prompt-stacks`. Stack rows show a `global` badge, and save/delete routes use `global:<id>` for exact global mutations. Legacy stacks remain editable in place.
46
+ Existing IDs are immutable during edit. Use **More → Fork** to create a different ID without breaking profile references or the active selection. The compact selector attached to **New stack** (default `Project`) chooses where new stacks, imports, and forks are written: `Global` targets the user-global `~/.pi/forge/prompt-stacks`, `Project` targets `.pi/forge/prompt-stacks`. Less-used capture, fork, import, export, and delete actions live under **More** so the stack and Preview/Diff panes keep the available viewport. Stack rows show a `global` badge, and save/delete routes use `global:<id>` for exact global mutations. Legacy stacks remain editable in place.
47
47
 
48
48
  Saves, imports, forks, and deletes reload stack state into the current Pi session. When another surface changes a referenced stack, returning to profiles refreshes profile resolution.
49
49
 
@@ -49,7 +49,7 @@ The commands below are provided by the optional `@zihanw/pi-forge-subagents` pac
49
49
  | `/forge-agent plan <profile> [--backend <id>] <task>` | Prepare, validate, display, and discard an exact plan without provider transport. |
50
50
  | `/forge-agent run <profile> [--backend <id>] <task>` | Review and approve an exact foreground read-only run. |
51
51
 
52
- Only profiles explicitly authorized in the matching scope are accepted: the project `subagents.json` authorizes `project:<id>` profiles and the global `subagents.json` authorizes `global:<id>` profiles; `.pi/forge/config.json.subagents` remains a read-only legacy fallback. The model-callable equivalents are `forge_subagent_profiles` (local discovery) and `forge_subagent` (execution). See the [delegation safety guide](../guides/delegation.md).
52
+ Only explicitly scoped profiles are accepted: use `project:<id>` or `global:<id>` keys in `subagents.json`. A bare authorization key always means `project:<id>`, even in the global file; `.pi/forge/config.json.subagents` remains a read-only legacy fallback. The model-callable equivalents are `forge_subagent_profiles` (local discovery) and `forge_subagent` (execution). See the [delegation safety guide](../guides/delegation.md).
53
53
 
54
54
  ## Payload inspection
55
55
 
@@ -38,7 +38,7 @@ Trusted project `subagents.json` may override defaults, authorize individual pro
38
38
  "allowAgentInvocationWithoutApproval": false,
39
39
  "summaryInToolDescription": false,
40
40
  "profiles": {
41
- "reviewer": {
41
+ "project:reviewer": {
42
42
  "enabled": true,
43
43
  "backend": "pi-rpc-readonly",
44
44
  "timeoutMs": 180000
@@ -51,7 +51,7 @@ Valid timeouts are 1,000–3,600,000 ms. Invalid fields warn and fall back to th
51
51
 
52
52
  `summaryInToolDescription` (default `false`) embeds a compact, bounded summary of enabled subagent profiles directly in the `forge_subagent` tool description so the parent model can pick a profile without a discovery call. Ready profiles appear first, and unavailable enabled profiles include their first resolution error. It may be set in user or trusted-project `subagents.json` and applies wherever it is enabled.
53
53
 
54
- `profiles` in global `subagents.json` authorizes `global:<id>` profiles; the trusted project's `profiles` authorizes `project:<id>` profiles. Same-ID profiles never inherit enablement, backend, or timeout policy from each other. `allowAgentInvocationWithoutApproval` is project-only, requires trust, and fails closed when malformed. Deleting a profile does not modify `subagents.json`; remove any enabled entry for the deleted profile manually.
54
+ Profile authorization keys should use canonical selectors: `project:<id>` or `global:<id>`. A bare key is a compatibility spelling for `project:<id>` regardless of which config file contains it; it never authorizes a global profile. Therefore a global profile must be written explicitly as `"global:reviewer": { "enabled": true }` in `~/.pi/forge/subagents.json`. Same-ID profiles never inherit enablement, backend, or timeout policy from each other. `allowAgentInvocationWithoutApproval` is project-only, requires trust, and fails closed when malformed. Deleting a profile does not modify `subagents.json`; remove any enabled entry for the deleted profile manually.
55
55
 
56
56
  Treat project configuration as an authorization boundary. In particular, do not commit unattended delegation unless every permitted parent agent may transmit compiled prompt and readable project content without another human approval.
57
57
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  pi-forge is pre-1.0. This document defines the intentional integration surfaces of the 0.5.0 line.
6
6
 
7
- ## The three intentional entry points
7
+ ## The four intentional entry points
8
8
 
9
9
  `check-package` enforces this allowlist; nothing else is importable from the package.
10
10
 
@@ -60,10 +60,22 @@ The experimental host port over the Pi event bus: discovery, profile listing/sna
60
60
 
61
61
  The optional `@zihanw/pi-forge-subagents` package consumes this port and owns subagent execution and configuration.
62
62
 
63
+ ### 4. `@zihanw/pi-forge/ui-contribution`: versioned settings port
64
+
65
+ ```ts
66
+ import {
67
+ UiContributionProvider,
68
+ UiContributionClient,
69
+ UI_CONTRIBUTION_PORT_VERSION,
70
+ } from "@zihanw/pi-forge/ui-contribution";
71
+ ```
72
+
73
+ The experimental generic Settings integration surface. Optional packages contribute recursively validated, JSON-compatible schemas and values over the Pi event bus; pi-forge owns only the renderer and web proxy. Providers own validation and persistence, may resolve operations asynchronously, and receive an abort signal tied to provider generation so stale requests can stop before side effects. The full contract is documented in the [UI contribution port reference](ui-contribution-port.md).
74
+
63
75
  ## Compatibility policy
64
76
 
65
77
  - **Stable** surfaces (root factory, macro/slot registration) preserve source compatibility within the documented release range unless a changelog entry announces a breaking release.
66
- - **Experimental** surfaces (the `/subagent` host port) are typed, tested, and documented, but may change deliberately as integration experience exposes missing semantics.
78
+ - **Experimental** surfaces (the `/subagent` and `/ui-contribution` ports) are typed, tested, and documented, but may change deliberately as integration experience exposes missing semantics.
67
79
  - Everything not listed above is internal and may change without notice. In particular: no `src/*` subpath aliases exist, `./examples/*` is not an import surface (examples ship as browsable files), and removed 0.4 surfaces (the execution contract re-exports, loader/profile/catalog helpers) now live either nowhere or in `@zihanw/pi-forge-subagents`.
68
80
 
69
81
  ## Removed in 0.5.0
@@ -95,7 +95,7 @@ Patterns are exact by default and support `*` wildcards:
95
95
  }
96
96
  ```
97
97
 
98
- Each resource may have a non-empty `allow` list or `deny` list, never both. Tool allow keeps matching active tools; deny removes matching active tools. Unmatched allow patterns are surfaced during validation/preflight.
98
+ Each resource may have a non-empty `allow` list or `deny` list, never both. A selective tool `allow` list chooses matching tools from Pi's complete registered tool catalog, so it can activate a registered tool that was inactive when the stack was selected. A tool `deny` list removes matching tools from the active baseline. `allow: ["*"]` remains unrestricted and does not activate every registered tool. Unmatched allow patterns are surfaced during validation/preflight.
99
99
 
100
100
  Tool policy changes Pi's active tool list, is reasserted before input/turns, and has a tool-call guard. It preserves external additions in the restorable baseline and restores that baseline when policy no longer applies or the extension shuts down.
101
101
 
@@ -0,0 +1,51 @@
1
+ # UI contribution port contract
2
+
3
+ [Documentation](../README.md)
4
+
5
+ Status: experimental, versioned (`UI_CONTRIBUTION_PORT_VERSION = 1`). The `@zihanw/pi-forge/ui-contribution` entry point is the generic cross-extension port that lets optional packages contribute schema-driven pages to the pi-forge web editor's top-level **Settings** surface. The forge side knows nothing about specific providers; the first consumer is [`@zihanw/pi-forge-subagents`](https://github.com/MacroSony/pi-forge-subagents), which contributes its Subagent Settings page when installed.
6
+
7
+ ## Ownership boundary
8
+
9
+ - **The contributing package owns** its tab's form schema, current values, server-side validation on write, and persistence (for example, the subagent package owns `subagents.json`). It implements the provider side of the port.
10
+ - **pi-forge owns** provider discovery over the bus, rendering contributed pages through the generic schema-form renderer, and proxying browser writes back over the bus through the web server routes. It never interprets or stores contributed configuration itself. Contributed pages are not stack tabs and never mount in the stack Preview dock.
11
+ - **Never crosses the port:** functions, components, live contexts, internal registries, or any non-JSON-compatible value. All payloads are plain recursively validated JSON. The port is not a trust boundary — providers must re-validate everything they receive and never trust the web client.
12
+
13
+ ## Transport and lifecycle
14
+
15
+ `UiContributionTransport` is a minimal `{ emit(channel, data), on(channel, handler) }` interface; the production wiring is `pi.events`. Messages travel on the dedicated `@zihanw/pi-forge/ui-contribution/v1` channel namespace with its own version counter, separate from the `/subagent` host port. Wire messages are plain JSON-compatible data validated recursively at both boundaries (exact field sets, typed enums, plain objects only, unknown fields rejected).
16
+
17
+ Channels: `discover`, `available`, `request`, `reply`, `unavailable`.
18
+
19
+ Lifecycle rules:
20
+
21
+ 1. Version negotiation: `discover` carries `protocolVersion` plus a supported `minVersion`/`maxVersion` range; a compatible provider answers `available` with its own `protocolVersion`, range, `capabilities`, `hostId`, and `generation`.
22
+ 2. A second compatible provider fails discovery explicitly (`duplicate`).
23
+ 3. `request`/`reply` messages carry `requestId` + `hostId` + `generation`; stale-generation and wrong-host requests are rejected server-side, mismatched replies ignored client-side.
24
+ 4. Provider disposal sends `unavailable`. The web editor clears that provider's contributed Settings pages and re-discovers when a provider reappears across sessions; late-surfacing providers are picked up without a page reload path change. The local HTTP listing also carries a forge-owned monotonic, opaque provider-session key so a fast restart refreshes a still-visible form even when browser polling never observes the empty interval. Browser PUT handling binds each response to the session that received it; a delayed success from an older session cannot mark or overwrite the newer session and the preserved draft is retried instead.
25
+ 5. Operation handlers may return a result or a promise of one and must never throw across the bus; rejected promises and thrown failures are converted to `{ ok: false, error }` results. Each invocation receives `{ signal, generation }`; stopping the provider aborts the signal. Handlers that await before persistence must check it before side effects. Replies from a stopped generation are discarded as a final transport guard.
26
+
27
+ The Settings host keeps the first descriptor for each `tabId`; later duplicates are ignored. Browser button IDs use a Settings-specific prefix and cannot collide with built-in stack tabs. Providers should still emit unique stable `tabId` values because `writeValues` routes by that identifier. In-progress drafts and save status are tracked per tab, so switching between contributed pages does not discard a pending edit or leak its status into another page.
28
+
29
+ ## Operations
30
+
31
+ ### Discovery
32
+
33
+ The web host acts as the client: `UiContributionClient.discover()` announces on the channel namespace and waits (bounded timeout) for an `available` announcement. Discovered tab descriptors are fetched at page load through `GET /api/contrib`.
34
+
35
+ ### `listContributions`
36
+
37
+ Request: `{}`. Response: `{ tabs: UiContributionTabDescriptor[] }` — each descriptor carries `tabId`, `title`, `icon`, a `FormSchema`, and the current `values`. Read-only; listing a tab contributes it to the editor but performs no other side effect. Every HTTP `GET /api/contrib` refreshes this operation, so providers may update plain-data option catalogs without restarting their session.
38
+
39
+ ### `writeValues`
40
+
41
+ Request: `{ tabId, patch }` — a partial values patch for one contributed tab. Omitted top-level fields preserve their stored values. A supplied `record` field is the complete keyed table produced by the form, so omitted rows represent deletion. The provider merges those semantics over its current values, re-validates server-side, and persists the result to its own storage. Response: `{ ok: true, values? }` with the canonical stored values, or `{ ok: false, errors }` with per-field error strings keyed by field key (dotted paths for record rows). The web server exposes this as `PUT /api/contrib/<tabId>` and rejects malformed or oversized request bodies with 400/413 before any bus call.
42
+
43
+ The browser serializes autosave requests. If the form changes while a PUT is in flight, the latest complete normalized snapshot is queued and written only after the current request settles. This ordering prevents older provider writes from landing after newer ones.
44
+
45
+ ## Form schema
46
+
47
+ v1 field types are deliberately restricted: `boolean`, `number`, `enum`, `string`, and `record` (a keyed table of entries — for example per-profile settings). Fields carry `key`, `label`, optional `description`/`required`/`default`, enum `options` (plain strings or `{ value, label }`), numeric `min`/`max`, string `maxLength`/`pattern`/`placeholder`, and record sub-fields (`recordFields`, `keyLabel`, `keyPlaceholder`). A record may also provide `keyOptions` (the same string-or-labelled-option shape as enum options); the generic renderer then uses a selector for row identity and prevents choosing a key already used by another row. Providers remain responsible for refreshing those plain-data options and validating them again on write.
48
+
49
+ ## Errors
50
+
51
+ `UiContributionPortError` carries `code: "timeout" | "duplicate" | "unavailable" | "protocol" | "invalid"`. Operation-level failures return `{ ok: false, error }` results rather than throwing across the bus.
@@ -43,4 +43,4 @@ Agent profile 是项目级或用户全局、带 schema version 的预设,只
43
43
 
44
44
  `/profile status` 会把 profile 源定义变化和当前模型/思考等级/stack drift 分开显示。Provenance 只用于 branch 状态报告;reload、resume、tree navigation 和 compaction 不会重新应用 profile。
45
45
 
46
- 普通 profile 默认不能委派。Delegation 授权由可选包 `@zihanw/pi-forge-subagents` 通过专用文件持有:可信项目 `.pi/forge/subagents.json` 只授权 `project:<id>`,用户全局 `~/.pi/forge/subagents.json` 只授权 `global:<id>`,并可按 profile 覆盖 backend/timeout;同 ID 的全局和项目 profile 永不互相继承授权。主包不读取任何 subagent 配置,删除 profile 也不会改动 `subagents.json`。启用前见[前台 delegation](../guides/delegation.md)。
46
+ 普通 profile 默认不能委派。Delegation 授权由可选包 `@zihanw/pi-forge-subagents` 通过专用文件持有,使用 `project:<id>` 或 `global:<id>` 完整 key,并可按 profile 覆盖 backend/timeout。裸授权 key 无论位于哪个文件都只是项目 profile 的兼容别名;同 ID 的全局和项目 profile 永不互相继承授权。主包不读取任何 subagent 配置,删除 profile 也不会改动 `subagents.json`。启用前见[前台 delegation](../guides/delegation.md)。
@@ -27,7 +27,7 @@ Prompt stack 是一份有序、声明式的 prompt 与策略描述,由固定 *
27
27
 
28
28
  ## 策略边界
29
29
 
30
- 工具 `allow`/`deny` 会修改 Pi active tools,并在 tool call 时再次检查。Skill policy 只过滤 pi-forge 渲染给模型的列表;它不能阻止明确调用,也不是安全边界。若必须控制模型可见 skill 列表,请使用 `replace`,因为 Pi 的基础 prompt 可能已经在 `append`/`prepend` 内容之前列出 skills。
30
+ 工具 `allow`/`deny` 会修改 Pi active tools,并在 tool call 时再次检查。具体的 `allow` 列表会从 Pi 的完整已注册工具目录中选择,因此可以启用 stack 激活前处于 inactive 状态的工具;`deny` 只从原 active baseline 中移除工具,`allow: ["*"]` 仍表示不限制且不会启用全部工具。Skill policy 只过滤 pi-forge 渲染给模型的列表;它不能阻止明确调用,也不是安全边界。若必须控制模型可见 skill 列表,请使用 `replace`,因为 Pi 的基础 prompt 可能已经在 `append`/`prepend` 内容之前列出 skills。
31
31
 
32
32
  ## Scope 与自动启用
33
33
 
@@ -15,7 +15,7 @@ Profile 默认不能委派。请在可信项目的 `.pi/forge/subagents.json`
15
15
  "backend": "pi-subprocess-readonly",
16
16
  "timeoutMs": 60000,
17
17
  "profiles": {
18
- "reviewer": {
18
+ "project:reviewer": {
19
19
  "enabled": true,
20
20
  "timeoutMs": 300000
21
21
  }
@@ -23,7 +23,7 @@ Profile 默认不能委派。请在可信项目的 `.pi/forge/subagents.json`
23
23
  }
24
24
  ```
25
25
 
26
- 授权跟随 profile scope:项目 `subagents.json` 的 `profiles.<id>` 只授权 `project:<id>`,全局 `subagents.json` 的 `profiles.<id>` 只授权 `global:<id>`。同 ID 的全局和项目 profile 不会互相继承 enable/backend/timeout。未启用或未列出的 ID 不会被 discovery 返回,即使猜中 ID 也会被拒绝。
26
+ 授权 key 应使用完整 selector:`project:<id>` 或 `global:<id>`。裸 key 仅为项目 profile 的兼容写法,即使写在 `~/.pi/forge/subagents.json` 中也只授权 `project:<id>`;授权全局 profile 必须显式写成 `"global:reviewer": { "enabled": true }`。同 ID 的全局和项目 profile 不会互相继承 enable/backend/timeout。未启用或未列出的 ID 不会被 discovery 返回,即使猜中 ID 也会被拒绝。
27
27
 
28
28
  ## Plan 与运行
29
29
 
@@ -49,7 +49,7 @@
49
49
  | `/forge-agent plan <profile> [--backend <id>] <task>` | 准备、显示并丢弃计划,不联系 provider |
50
50
  | `/forge-agent run <profile> [--backend <id>] <task>` | 审批并执行前台只读任务 |
51
51
 
52
- 只接受匹配 scope 明确授权的 profile:项目 `subagents.json` 授权 `project:<id>`,全局 `subagents.json` 授权 `global:<id>`;也可使用 `.pi/forge/config.json.subagents` 作为只读兼容来源。模型工具为 `forge_subagent_profiles` 和 `forge_subagent`。见[安全说明](../guides/delegation.md)。
52
+ 只接受明确 scope 的授权:请在 `subagents.json` 中使用 `project:<id>` 或 `global:<id>` key。裸授权 key 始终表示 `project:<id>`,即使它位于全局配置中;`.pi/forge/config.json.subagents` 仅作为只读兼容来源。模型工具为 `forge_subagent_profiles` 和 `forge_subagent`。见[安全说明](../guides/delegation.md)。
53
53
 
54
54
  ## Payload
55
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zihanw/pi-forge",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Pi extension for prompt stacks, one-shot agent profiles, policy, import, and debugging.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,6 +17,11 @@
17
17
  "types": "./dist/subagent/index.d.ts",
18
18
  "import": "./dist/subagent/index.js",
19
19
  "default": "./dist/subagent/index.js"
20
+ },
21
+ "./ui-contribution": {
22
+ "types": "./dist/ui-contribution/index.d.ts",
23
+ "import": "./dist/ui-contribution/index.js",
24
+ "default": "./dist/ui-contribution/index.js"
20
25
  }
21
26
  },
22
27
  "keywords": [