@zihanw/pi-forge 0.3.2 → 0.4.0

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 (231) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/PUBLIC_API.md +5 -0
  3. package/README.md +93 -478
  4. package/README.zh-CN.md +88 -461
  5. package/SUBAGENT_ADAPTER_CONTRACT.md +5 -0
  6. package/dist/agent-profile.d.ts +71 -0
  7. package/dist/agent-profile.d.ts.map +1 -0
  8. package/dist/agent-profile.js +303 -0
  9. package/dist/agent-profile.js.map +1 -0
  10. package/dist/forge-config.d.ts +88 -0
  11. package/dist/forge-config.d.ts.map +1 -0
  12. package/dist/forge-config.js +290 -0
  13. package/dist/forge-config.js.map +1 -0
  14. package/dist/forge-extensions.d.ts.map +1 -1
  15. package/dist/forge-extensions.js +19 -3
  16. package/dist/forge-extensions.js.map +1 -1
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +97 -358
  20. package/dist/index.js.map +1 -1
  21. package/dist/lifecycle.d.ts +11 -3
  22. package/dist/lifecycle.d.ts.map +1 -1
  23. package/dist/lifecycle.js +90 -4
  24. package/dist/lifecycle.js.map +1 -1
  25. package/dist/loader.d.ts.map +1 -1
  26. package/dist/loader.js +116 -5
  27. package/dist/loader.js.map +1 -1
  28. package/dist/payload-capture.d.ts.map +1 -1
  29. package/dist/payload-capture.js +27 -0
  30. package/dist/payload-capture.js.map +1 -1
  31. package/dist/payload-command.d.ts +3 -3
  32. package/dist/payload-command.d.ts.map +1 -1
  33. package/dist/payload-command.js.map +1 -1
  34. package/dist/preview.d.ts +2 -2
  35. package/dist/preview.d.ts.map +1 -1
  36. package/dist/preview.js +2 -3
  37. package/dist/preview.js.map +1 -1
  38. package/dist/profile-command.d.ts +12 -0
  39. package/dist/profile-command.d.ts.map +1 -0
  40. package/dist/profile-command.js +291 -0
  41. package/dist/profile-command.js.map +1 -0
  42. package/dist/profile-service.d.ts +103 -0
  43. package/dist/profile-service.d.ts.map +1 -0
  44. package/dist/profile-service.js +220 -0
  45. package/dist/profile-service.js.map +1 -0
  46. package/dist/runtime/profile-runtime.d.ts +13 -0
  47. package/dist/runtime/profile-runtime.d.ts.map +1 -0
  48. package/dist/runtime/profile-runtime.js +47 -0
  49. package/dist/runtime/profile-runtime.js.map +1 -0
  50. package/dist/runtime/prompt-stack-runtime.d.ts +22 -0
  51. package/dist/runtime/prompt-stack-runtime.d.ts.map +1 -0
  52. package/dist/runtime/prompt-stack-runtime.js +104 -0
  53. package/dist/runtime/prompt-stack-runtime.js.map +1 -0
  54. package/dist/runtime/subagent-runtime.d.ts +45 -0
  55. package/dist/runtime/subagent-runtime.d.ts.map +1 -0
  56. package/dist/runtime/subagent-runtime.js +335 -0
  57. package/dist/runtime/subagent-runtime.js.map +1 -0
  58. package/dist/runtime/tool-policy-runtime.d.ts +15 -0
  59. package/dist/runtime/tool-policy-runtime.d.ts.map +1 -0
  60. package/dist/runtime/tool-policy-runtime.js +170 -0
  61. package/dist/runtime/tool-policy-runtime.js.map +1 -0
  62. package/dist/runtime/web-editor-runtime.d.ts +9 -0
  63. package/dist/runtime/web-editor-runtime.d.ts.map +1 -0
  64. package/dist/runtime/web-editor-runtime.js +131 -0
  65. package/dist/runtime/web-editor-runtime.js.map +1 -0
  66. package/dist/runtime-state.d.ts +4 -0
  67. package/dist/runtime-state.d.ts.map +1 -1
  68. package/dist/runtime-state.js +2 -0
  69. package/dist/runtime-state.js.map +1 -1
  70. package/dist/storage.d.ts +4 -0
  71. package/dist/storage.d.ts.map +1 -1
  72. package/dist/storage.js +24 -1
  73. package/dist/storage.js.map +1 -1
  74. package/dist/subagent/canonical.d.ts +22 -0
  75. package/dist/subagent/canonical.d.ts.map +1 -0
  76. package/dist/subagent/canonical.js +24 -0
  77. package/dist/subagent/canonical.js.map +1 -0
  78. package/dist/subagent/context.d.ts +8 -0
  79. package/dist/subagent/context.d.ts.map +1 -0
  80. package/dist/subagent/context.js +125 -0
  81. package/dist/subagent/context.js.map +1 -0
  82. package/dist/subagent/contract.d.ts +10 -0
  83. package/dist/subagent/contract.d.ts.map +1 -0
  84. package/dist/subagent/contract.js +10 -0
  85. package/dist/subagent/contract.js.map +1 -0
  86. package/dist/subagent/index.d.ts +14 -0
  87. package/dist/subagent/index.d.ts.map +1 -0
  88. package/dist/subagent/index.js +14 -0
  89. package/dist/subagent/index.js.map +1 -0
  90. package/dist/subagent/plan.d.ts +18 -0
  91. package/dist/subagent/plan.d.ts.map +1 -0
  92. package/dist/subagent/plan.js +157 -0
  93. package/dist/subagent/plan.js.map +1 -0
  94. package/dist/subagent/preflight.d.ts +4 -0
  95. package/dist/subagent/preflight.d.ts.map +1 -0
  96. package/dist/subagent/preflight.js +108 -0
  97. package/dist/subagent/preflight.js.map +1 -0
  98. package/dist/subagent/request.d.ts +4 -0
  99. package/dist/subagent/request.d.ts.map +1 -0
  100. package/dist/subagent/request.js +122 -0
  101. package/dist/subagent/request.js.map +1 -0
  102. package/dist/subagent/response.d.ts +8 -0
  103. package/dist/subagent/response.d.ts.map +1 -0
  104. package/dist/subagent/response.js +155 -0
  105. package/dist/subagent/response.js.map +1 -0
  106. package/dist/subagent/tools.d.ts +4 -0
  107. package/dist/subagent/tools.d.ts.map +1 -0
  108. package/dist/subagent/tools.js +42 -0
  109. package/dist/subagent/tools.js.map +1 -0
  110. package/dist/subagent/types.d.ts +268 -0
  111. package/dist/subagent/types.d.ts.map +1 -0
  112. package/dist/subagent/types.js +3 -0
  113. package/dist/subagent/types.js.map +1 -0
  114. package/dist/subagent/validation.d.ts +35 -0
  115. package/dist/subagent/validation.d.ts.map +1 -0
  116. package/dist/subagent/validation.js +314 -0
  117. package/dist/subagent/validation.js.map +1 -0
  118. package/dist/subagent-command.d.ts +4 -0
  119. package/dist/subagent-command.d.ts.map +1 -0
  120. package/dist/subagent-command.js +246 -0
  121. package/dist/subagent-command.js.map +1 -0
  122. package/dist/subagent-host.d.ts +44 -0
  123. package/dist/subagent-host.d.ts.map +1 -0
  124. package/dist/subagent-host.js +292 -0
  125. package/dist/subagent-host.js.map +1 -0
  126. package/dist/subagent-profile-tool.d.ts +49 -0
  127. package/dist/subagent-profile-tool.d.ts.map +1 -0
  128. package/dist/subagent-profile-tool.js +124 -0
  129. package/dist/subagent-profile-tool.js.map +1 -0
  130. package/dist/subagent-tool.d.ts +53 -0
  131. package/dist/subagent-tool.d.ts.map +1 -0
  132. package/dist/subagent-tool.js +456 -0
  133. package/dist/subagent-tool.js.map +1 -0
  134. package/dist/web-editor/client-script.d.ts +2 -0
  135. package/dist/web-editor/client-script.d.ts.map +1 -0
  136. package/dist/web-editor/client-script.generated.d.ts +2 -0
  137. package/dist/web-editor/client-script.generated.d.ts.map +1 -0
  138. package/dist/web-editor/client-script.generated.js +3 -0
  139. package/dist/web-editor/client-script.generated.js.map +1 -0
  140. package/dist/web-editor/client-script.js +2 -0
  141. package/dist/web-editor/client-script.js.map +1 -0
  142. package/dist/web-editor/client-styles.d.ts +2 -0
  143. package/dist/web-editor/client-styles.d.ts.map +1 -0
  144. package/dist/web-editor/client-styles.generated.d.ts +2 -0
  145. package/dist/web-editor/client-styles.generated.d.ts.map +1 -0
  146. package/dist/web-editor/client-styles.generated.js +3 -0
  147. package/dist/web-editor/client-styles.generated.js.map +1 -0
  148. package/dist/web-editor/client-styles.js +2 -0
  149. package/dist/web-editor/client-styles.js.map +1 -0
  150. package/dist/web-editor/page.d.ts +2 -0
  151. package/dist/web-editor/page.d.ts.map +1 -1
  152. package/dist/web-editor/page.js +14 -3319
  153. package/dist/web-editor/page.js.map +1 -1
  154. package/dist/web-editor/server.d.ts.map +1 -1
  155. package/dist/web-editor/server.js +148 -0
  156. package/dist/web-editor/server.js.map +1 -1
  157. package/dist/web-editor/styles.d.ts +2 -0
  158. package/dist/web-editor/styles.d.ts.map +1 -0
  159. package/dist/web-editor/styles.js +1053 -0
  160. package/dist/web-editor/styles.js.map +1 -0
  161. package/dist/web-editor/types.d.ts +79 -0
  162. package/dist/web-editor/types.d.ts.map +1 -1
  163. package/dist/web-host.d.ts +16 -5
  164. package/dist/web-host.d.ts.map +1 -1
  165. package/dist/web-host.js +307 -0
  166. package/dist/web-host.js.map +1 -1
  167. package/docs/README.md +41 -0
  168. package/docs/concepts/agent-profiles.md +60 -0
  169. package/docs/concepts/prompt-stacks.md +90 -0
  170. package/docs/design/README.md +17 -0
  171. package/docs/design/roadmap-0.4-archive.md +216 -0
  172. package/docs/design/subagents/design-review.md +220 -0
  173. package/docs/design/subagents/interface-design.md +274 -0
  174. package/docs/design/subagents/sdk-spike-findings.md +117 -0
  175. package/docs/development/complexity-review.md +86 -0
  176. package/docs/development/release.md +31 -0
  177. package/docs/development/roadmap.md +42 -0
  178. package/docs/development/setup.md +75 -0
  179. package/docs/getting-started.md +93 -0
  180. package/docs/guides/custom-macros-and-slots.md +68 -0
  181. package/docs/guides/debugging.md +39 -0
  182. package/docs/guides/delegation.md +99 -0
  183. package/docs/guides/sillytavern-import.md +47 -0
  184. package/docs/guides/use-cases.md +65 -0
  185. package/docs/guides/web-editor.md +75 -0
  186. package/docs/reference/commands.md +60 -0
  187. package/docs/reference/configuration.md +64 -0
  188. package/docs/reference/features.md +279 -0
  189. package/docs/reference/macros-and-slots.md +82 -0
  190. package/docs/reference/public-api.md +28 -0
  191. package/docs/reference/stack-schema.md +167 -0
  192. package/docs/reference/subagent-adapter.md +204 -0
  193. package/docs/zh-CN/README.md +37 -0
  194. package/docs/zh-CN/concepts/agent-profiles.md +44 -0
  195. package/docs/zh-CN/concepts/prompt-stacks.md +40 -0
  196. package/docs/zh-CN/getting-started.md +79 -0
  197. package/docs/zh-CN/guides/delegation.md +66 -0
  198. package/docs/zh-CN/guides/web-editor.md +45 -0
  199. package/docs/zh-CN/reference/commands.md +58 -0
  200. package/package.json +55 -13
  201. package/src/compiler.ts +0 -578
  202. package/src/extension-registry.ts +0 -33
  203. package/src/forge-extensions.ts +0 -223
  204. package/src/index.ts +0 -445
  205. package/src/lifecycle.ts +0 -171
  206. package/src/loader.ts +0 -394
  207. package/src/macro-engine.ts +0 -358
  208. package/src/payload-capture.ts +0 -85
  209. package/src/payload-command.ts +0 -138
  210. package/src/policy.ts +0 -42
  211. package/src/preset-command.ts +0 -280
  212. package/src/preview.ts +0 -226
  213. package/src/regex.ts +0 -500
  214. package/src/render-helpers.ts +0 -169
  215. package/src/runtime-state.ts +0 -40
  216. package/src/sillytavern-importer/items.ts +0 -98
  217. package/src/sillytavern-importer/macros.ts +0 -159
  218. package/src/sillytavern-importer/prompt-order.ts +0 -54
  219. package/src/sillytavern-importer/regex.ts +0 -270
  220. package/src/sillytavern-importer/report.ts +0 -202
  221. package/src/sillytavern-importer/types.ts +0 -120
  222. package/src/sillytavern-importer.ts +0 -152
  223. package/src/slot-renderers.ts +0 -414
  224. package/src/stack-migration.ts +0 -159
  225. package/src/storage.ts +0 -45
  226. package/src/types.ts +0 -209
  227. package/src/web-editor/index.ts +0 -2
  228. package/src/web-editor/page.ts +0 -3330
  229. package/src/web-editor/server.ts +0 -294
  230. package/src/web-editor/types.ts +0 -98
  231. package/src/web-host.ts +0 -232
package/README.md CHANGED
@@ -1,540 +1,155 @@
1
1
  # pi-forge
2
2
 
3
- [English](README.md) | [简体中文](README.zh-CN.md)
3
+ [English](README.md) | [简体中文](README.zh-CN.md) · [Documentation](docs/README.md)
4
4
 
5
5
  ![pi-forge header](https://raw.githubusercontent.com/MacroSony/pi-forge/main/assets/pi-forge-header-concept-1.png)
6
6
 
7
- **pi-forge** lets you customize how Pi thinks and behaves. It gives you prompt stacks — JSON files that can replace, append to, or prepend Pi's default system prompt while controlling the AI's personality, visible tools, conversation history layout, template variables, and prompt transforms.
7
+ **pi-forge** lets you customize how [Pi](https://github.com/badlogic/pi-mono) thinks and behaves. Prompt stacks control prompt composition and tool policy; agent profiles apply a model, thinking level, and stack as a reusable one-shot preset.
8
8
 
9
- Think of it as a character sheet for your AI agent.
9
+ Think of it as a character sheet and workbench for your AI agent.
10
10
 
11
- ## What you can do with it
11
+ ## Highlights
12
12
 
13
- - **Give Pi a personality** — turn it into a creative writer, a roleplay partner, a strict code reviewer, or anything in between.
14
- - **Switch contexts instantly** — one command to swap between "coding mode", "writing mode", and "translation mode".
15
- - **Control what the AI sees** — choose which tools, skills, and project context appear in each prompt.
16
- - **Limit tools and skills per stack** — enforce active tool policy and filter skill visibility for focused modes.
17
- - **Use template variables** — define static values such as `{{char}}` / `{{user}}`, and use ST-style turn/session variable macros inside prompt text.
18
- - **Transform outgoing and finalized text** — run deterministic regex replacements on selected history, compiled prompt text, or finalized assistant messages.
19
- - **Import SillyTavern presets** — bring your existing ST character presets into Pi with one command.
20
- - **Debug your prompts** — intercept and inspect exactly what gets sent to the model.
13
+ - Compose Pi's system prompt, conversation history, tools, skills, project context, and runtime data as ordered blocks and slots.
14
+ - Switch between coding, reviewing, writing, roleplay, and translation modes with one command.
15
+ - Save and apply complete model/thinking/stack profiles.
16
+ - Enforce per-stack tool policy and filter model-visible skills.
17
+ - Use static, turn, and session variables with nested template macros.
18
+ - Apply deterministic regex transforms to outgoing prompts or finalized assistant messages.
19
+ - Import SillyTavern presets and inspect the migration report.
20
+ - Edit stacks and profiles in a local browser UI and inspect the exact provider payload.
21
+ - Run an explicitly enabled profile as an experimental, approval-gated foreground subagent.
21
22
 
22
- ## Quick start
23
+ ## Install
23
24
 
24
- ### Install
25
+ pi-forge requires Node.js 22.19 or newer.
25
26
 
26
27
  ```bash
27
28
  pi install npm:@zihanw/pi-forge
28
29
  ```
29
30
 
30
- ### Your first prompt stack
31
+ Restart Pi after installing or updating the extension. Pi supplies its SDK packages to extensions at runtime; pi-forge keeps exact Pi versions only for reproducible development and tests. See [compatibility and setup](docs/development/setup.md#pi-compatibility) for the supported/tested policy.
31
32
 
32
- Create `.pi/forge/prompt-stacks/default.json` from [examples/default-prompt-stack.json](examples/default-prompt-stack.json).
33
+ ## Five-minute start
33
34
 
34
- The default example mirrors Pi's own prompt builder from `@earendil-works/pi-coding-agent/dist/core/system-prompt.js`, but splits it into movable pi-forge slots: role, tools, guidelines, Pi docs guidance, appended system prompt text, project context, skills, date/cwd, and chat history.
35
+ ### 1. Create a prompt stack
36
+
37
+ Create `.pi/forge/prompt-stacks/default.json` from [the default Pi mirror](examples/default-prompt-stack.json):
35
38
 
36
39
  ```bash
37
40
  mkdir -p .pi/forge/prompt-stacks
38
- $EDITOR .pi/forge/prompt-stacks/default.json
39
- ```
40
-
41
- Paste the example JSON into that file. If you are working inside this repository, you can copy it directly with `cp examples/default-prompt-stack.json .pi/forge/prompt-stacks/default.json`.
42
-
43
- That's it. Restart Pi or run `/preset reload`. If no stack is already selected, `default.json` auto-activates. If you previously chose another stack or `/preset use none`, run `/preset use default`.
44
-
45
- ### Visual editor
46
-
47
- Prefer clicking over typing JSON? pi-forge has a built-in web editor:
48
-
49
- ```
50
- /preset ui
51
- ```
52
-
53
- Drag, drop, create, edit, validate, inspect full previews and captured payloads, manage variables/context/regex rules in tabs, switch dark mode, recover through raw stack JSON, import, export, fork, and delete stacks — all in your browser. New stacks start from the default Pi prompt mirror layout. Stack metadata is collapsible so the active editor stays in view. The policy tab shows registered tools and loaded skills with selected-pattern chips and filtering, so allow/deny rules can be built from exact names while still supporting wildcards.
54
-
55
- Import accepts native pi-forge stack JSON and SillyTavern preset JSON. SillyTavern presets are converted to prompt stacks automatically; if a preset contains multiple `character_id` configs, the editor asks which one to use.
56
-
57
- The editor runs on an available `127.0.0.1` port with a session token, so multiple Pi instances can run editors at the same time. If Pi reinitializes the extension after session navigation or a new session, `/preset ui` reuses the existing editor URL for the same project instead of orphaning the old server. Writes require a trusted project and stay inside prompt-stack storage. New stacks are written to `.pi/forge/prompt-stacks`; existing legacy stacks under `.pi/prompt-stacks` remain readable and editable. Successful save, import, fork, and delete actions reload into the current Pi session. Use `/preset ui restart` or `/preset ui stop` when needed.
58
-
59
- To copy old stacks into the new location, run `/preset migrate-stacks`. Add `--dry-run` to preview, `--overwrite` to replace existing target files, and `--delete-legacy` to remove old files after successful copy.
60
-
61
- To prefer a specific port, create `.pi/forge/config.json`. If that port is busy, pi-forge falls back to another available port and shows the actual URL:
62
-
63
- ```json
64
- {
65
- "webEditor": {
66
- "port": 41738
67
- }
68
- }
69
- ```
70
-
71
- ## Use cases
72
-
73
- ### 🎭 Roleplay & creative writing
74
-
75
- Turn Pi into a character. Define their personality in the system prompt, inject writing style rules as user messages, and use `{{lastUserMessage}}` to re-insert the user's input after the conversation history.
76
-
77
- Useful pattern:
78
- - Put long-term character rules in a `system` block.
79
- - Keep Pi runtime context (tools, skills, project) in `user` slots.
80
- - Set the `chat-history` slot to skip the latest user message.
81
- - Add a final `user` block with `{{lastUserMessage}}`.
82
-
83
- This keeps the latest request clear and avoids duplicating it.
84
-
85
- For a baseline stack to fork before turning Pi into a character, start from [examples/default-prompt-stack.json](examples/default-prompt-stack.json).
86
-
87
- ### 🧑‍💻 Focused code review
88
-
89
- Create a `reviewer.json` stack with a strict review block: "prioritize correctness, regressions, security, and missing tests." Keep the `tools`, `project-context`, `variables`, and `chat-history` slots enabled so Pi can still inspect the repo and see any template variables you expose.
90
-
91
- Use `mode: "append"` if you want to keep Pi's normal coding behavior and only add the sharper review lens.
92
-
93
- ### 🌐 Translation mode
94
-
95
- Create a small `translator.json` stack with one system block for tone and target language, then keep `chat-history` and `{{lastUserMessage}}` in the layout. This works well for switching between bilingual editing, literal translation, and localization review without changing your default assistant.
96
-
97
- ### 🔀 Multi-mode switching
98
-
99
- Create separate stacks for different tasks:
100
-
101
- ```
102
- .pi/forge/prompt-stacks/
103
- coder.json # strict coding assistant
104
- writer.json # creative writing partner
105
- translator.json # bilingual translator
41
+ cp examples/default-prompt-stack.json .pi/forge/prompt-stacks/default.json
106
42
  ```
107
43
 
108
- Switch with `/preset use coder`, `/preset use writer`, etc.
44
+ If you installed from npm rather than cloning this repository, open `/preset ui` and create a new stack; the editor starts with the same Pi-mirror layout.
109
45
 
110
- ### 🧪 Presets that show off pi-forge
46
+ Restart Pi or run:
111
47
 
112
- - **Pi mirror** — start from [examples/default-prompt-stack.json](examples/default-prompt-stack.json). It preserves normal Pi behavior while making every runtime section movable and inspectable.
113
- - **Focused reviewer** — see [examples/reviewer-prompt-stack.json](examples/reviewer-prompt-stack.json). It denies file-writing tools, wraps prior chat history as background, removes the latest user message from history, then reinserts `{{lastUserMessage}}` as the explicit review target.
114
- - **Read-only scout** — use `tools.allow` for `read`, `grep`, `find`, and `ls`; omit editing tools; cap `chat-history` with `maxChars`. Good for exploration turns where the model should report findings without changing files.
115
- - **Surgical patcher** — keep the Pi mirror, require `read`, `edit`, and `bash`, strip assistant thinking from inserted history, and move `project-context` near the final user turn. Good for focused implementation passes.
116
- - **SillyTavern DM writer** — see [examples/sillytavern-dm-writer-prompt-stack.json](examples/sillytavern-dm-writer-prompt-stack.json). It defines a Dungeon Master character with `{{char}}` / `{{user}}`, wraps prior adventure history, reinserts `{{lastUserMessage}}` as the current player action, and uses regex cleanup for OOC notes, secret-roll markers, dice notation, and `Player:` prefixes.
117
- - **Payload lab** — include `active-model`, `date-cwd`, and variables slots, then add `compiled` regex rules for deterministic redaction or formatting. Pair it with `/payload next` or the web editor's capture view to audit exactly what changed.
118
- - **Docs-only Pi expert** — allow only read/search tools, enable the `pi-docs` slot, and keep project context. Useful when you want answers grounded in the installed Pi docs instead of general memory.
119
-
120
- ### 🔧 Template variables
121
-
122
- ```json
123
- "variables": {
124
- "char": "Konata",
125
- "user": "User"
126
- }
127
- ```
128
-
129
- Use static variables for stable prompt constants, and ST-style macros for local prompt-time mutation:
130
-
131
- ```
132
- {{setvar::mood::focused}}
133
- {{getvar::mood}}
134
- {{setsessionvar::topic::compiler cleanup}}
48
+ ```text
49
+ /preset reload
50
+ /preset use default
135
51
  ```
136
52
 
137
- For durable project memory, use normal files in the repo rather than pi-forge prompt variables.
53
+ `default.json` auto-activates when no stack or restored session selection takes precedence.
138
54
 
139
- ### 📦 SillyTavern migration
55
+ ### 2. Open the visual editor
140
56
 
141
- Bring your ST presets into Pi:
142
-
143
- ```
144
- /preset import-silly ~/SillyTavern/presets/my-preset.json
57
+ ```text
58
+ /preset ui
145
59
  ```
146
60
 
147
- pi-forge converts the preset to a prompt stack and generates a migration report showing what was handled and what needs manual tweaking.
148
-
149
- Deterministic SillyTavern `promptOnly` regex scripts are converted to pi-forge `regex.rules` as history-stage rules when they can be represented safely, including full-match token conversion, trim strings, depth fields, and clear user/assistant placements. Display-only, mixed prompt/display, DOM/browser, CSS/HTML decoration, JavaScript, unsupported placements, and invalid regex scripts stay report-only for manual review.
150
-
151
- ### 🔍 Prompt debugging
152
-
153
- See exactly what gets sent to the model:
61
+ The local editor can create, fork, validate, preview, import, export, and delete prompt stacks. Its **Agent profiles** view manages one-shot model/thinking/stack presets and experimental delegation settings. Writes require a trusted project.
154
62
 
155
- ```
156
- /payload next save=.pi/forge/payloads/last.json
157
- ```
158
-
159
- Or open `/preset ui`, click **Arm payload**, send the next Pi prompt, and inspect the redacted provider payload in the browser.
63
+ ### 3. Save a profile
160
64
 
161
- Or preview your compiled prompt without sending anything:
65
+ Configure Pi normally, then capture and reuse the current settings:
162
66
 
67
+ ```text
68
+ /profile save reviewer
69
+ /profile use reviewer
163
70
  ```
164
- /preset preview
165
- ```
166
-
167
- ## How it works
168
71
 
169
- A prompt stack is a JSON file with two kinds of items:
72
+ A profile applies once. Later manual changes to the model or thinking level remain in effect until the profile is applied again; an active prompt stack continues enforcing its tool policy.
170
73
 
171
- | Kind | What it does |
172
- |------|-------------|
173
- | **Block** | Static text inserted at a specific position (system prompt, user message, assistant message) |
174
- | **Slot** | Dynamic content from Pi's runtime — tools, skills, chat history, date, project context, etc. |
74
+ ## The basic model
175
75
 
176
- Items are arranged in order. When the stack is active, pi-forge:
76
+ A prompt stack is an ordered JSON document containing:
177
77
 
178
- 1. Builds a system prompt from your `system`-role blocks and slots, then applies it with the stack's `mode`.
179
- 2. Inserts `user`/`assistant` blocks and slots around the conversation history.
180
- 3. Expands `{{macros}}` like `{{lastUserMessage}}`, `{{date}}`, and custom variables.
181
- 4. Applies stack tool policy to Pi's active tool set and filters pi-forge-rendered tool/skill slots.
182
- 5. Applies enabled outgoing regex rules for the `history` and `compiled` stages.
183
- 6. Optionally applies destructive `finalize` regex rules when an assistant message finishes.
78
+ | Item | Purpose |
79
+ |---|---|
80
+ | **Block** | Static `system`, `user`, `assistant`, or hidden `custom` text |
81
+ | **Slot** | Runtime content such as tools, skills, project context, variables, date/cwd, or chat history |
184
82
 
185
- ### Slots at a glance
83
+ Stacks can `replace`, `append`, or `prepend` Pi's base system prompt. During compilation, pi-forge expands macros, inserts conversation content, enforces tool policy, filters its skill listing, and applies enabled regex rules.
186
84
 
187
- | Slot | What it inserts |
188
- |------|----------------|
189
- | `chat-history` | The current conversation |
190
- | `tools` | Available tools and their descriptions |
191
- | `tool-guidelines` | Tool usage instructions |
192
- | `skills` | Loaded Pi skills |
193
- | `project-context` | Project instructions and context files |
194
- | `variables` | Static/session/turn template variables |
195
- | `date` / `cwd` / `date-cwd` | Current date, optional current time, and working directory |
196
- | `active-model` | Which model is being used |
197
- | `append-system-prompt` | User's appended system prompt text |
198
- | `pi-docs` | Pi documentation guidance |
85
+ Agent profiles are project-local references to an exact provider/model, thinking level, and prompt stack. They intentionally do not duplicate tool or skill policy—the referenced stack remains the source of truth.
199
86
 
200
- ### Modes
87
+ Start with these examples:
201
88
 
202
- - **replace** (default) — your stack replaces Pi's system prompt entirely.
203
- - **append** — your stack is added after Pi's default system prompt.
204
- - **prepend** — your stack is added before Pi's default system prompt.
89
+ - [Default Pi mirror](examples/default-prompt-stack.json) keeps normal Pi behavior while making its sections movable.
90
+ - [Focused reviewer](examples/reviewer-prompt-stack.json) creates a read-only review layout with an explicit latest-user target.
91
+ - [SillyTavern DM writer](examples/sillytavern-dm-writer-prompt-stack.json) demonstrates characters, variables, history placement, and regex cleanup.
92
+ - [Custom system-status extension](examples/custom-system-status-extension/README.md) registers a trusted macro and slot.
205
93
 
206
94
  ## Common commands
207
95
 
208
- ### Managing stacks
209
-
210
- | Command | What it does |
211
- |---------|-------------|
212
- | `/preset list` | Show all available stacks |
213
- | `/preset use <id>` | Activate a stack |
214
- | `/preset use none` | Disable prompt stacks for the session |
215
- | `/preset preview [id]` | See the compiled prompt |
216
- | `/preset validate [id]` | Check a stack for issues |
217
- | `/preset status` | Show the active stack and diagnostics summary |
218
- | `/preset diagnostics` | Show runtime diagnostics |
219
- | `/preset reload` | Reload stacks from disk |
220
- | `/preset migrate-stacks [--dry-run] [--overwrite] [--delete-legacy]` | Copy legacy `.pi/prompt-stacks` files into `.pi/forge/prompt-stacks` |
221
- | `/preset ui [stop\|restart]` | Open, stop, or restart the web editor |
222
-
223
- ### Import & debug
224
-
225
- | Command | What it does |
226
- |---------|-------------|
227
- | `/preset import-silly <path>` | Import a SillyTavern preset |
228
- | `/intercept` | Show the next provider payload |
229
- | `/payload next [save=<path>]` | Show, save, and expose the next payload to the web editor |
230
-
231
- ## Common macros
232
-
233
- Use these in block content to insert dynamic values:
234
-
235
- | Macro | Expands to |
236
- |-------|-----------|
237
- | `{{lastUserMessage}}` | The user's latest message |
238
- | `{{date}}` | Current date (YYYY-MM-DD) |
239
- | `{{time}}` | Current time (HH:MM:SS) |
240
- | `{{cwd}}` | Current working directory |
241
- | `{{tools}}` | Comma-separated tool names |
242
- | `{{selectedTools}}` | Alias for selected tool names |
243
- | `{{activeModel}}` | Current model (provider/id) |
244
- | `{{char}}` / `{{user}}` | Custom variables from your stack |
245
-
246
- ### Variable macros
96
+ | Command | Purpose |
97
+ |---|---|
98
+ | `/preset ui [stop\|restart]` | Open or manage the web editor |
99
+ | `/preset list` | List prompt stacks |
100
+ | `/preset use <id\|none>` | Select or disable a stack |
101
+ | `/preset preview [id]` | Compile a stack without sending a request |
102
+ | `/preset validate [id]` | Validate one stack or all stacks |
103
+ | `/preset diagnostics` | Show runtime and extension diagnostics |
104
+ | `/profile list` | List and preflight profiles |
105
+ | `/profile save <id> [--overwrite]` | Capture the current runtime as a profile |
106
+ | `/profile use <id>` | Preflight and apply a profile once |
107
+ | `/profile status` | Show last-applied provenance and runtime drift |
108
+ | `/payload next [save=<path>]` | Inspect the next redacted provider payload |
247
109
 
248
- ```
249
- {{setvar::name::value}} set a turn variable (cleared each message)
250
- {{setsessionvar::name::value}} set a session variable (persists)
251
- {{setvar::session::name::value}} also set a session variable
252
- {{getvar::name}} read a variable (turn → session → static)
253
- {{getturnvar::name}} read only a turn variable
254
- {{getsessionvar::name}} read only a session variable
255
- {{clearvar::name}} clear a variable
256
- {{clearturnvar::name}} clear a turn variable
257
- {{clearsessionvar::name}} clear a session variable
258
- ```
259
-
260
- ### Filter and conditional macros
261
-
262
- Nested macros are supported, and `::` separators are parsed only at the current macro depth.
263
-
264
- | Macro | Expands to |
265
- |-------|-----------|
266
- | `{{trim::value}}` | `value` with leading/trailing whitespace removed |
267
- | `{{upper::value}}` | Uppercase `value` |
268
- | `{{lower::value}}` | Lowercase `value` |
269
- | `{{json::value}}` | JSON string literal for `value` |
270
- | `{{xml::value}}` | XML-escaped `value` |
271
- | `{{ifvar::name::then::else}}` | `then` when a variable exists, otherwise `else` |
272
- | `{{ifeq::name::expected::then::else}}` | `then` when a variable equals `expected`, otherwise `else` |
273
- | `{{iftools::tool::then::else}}` | `then` when the selected tool list includes `tool`, otherwise `else` |
274
- | `{{ifslot::slot::then::else}}` | `then` when the enabled stack items include `slot`, otherwise `else` |
275
-
276
- Conditional branches are lazy: only the selected branch is expanded, so skipped branches cannot set or clear variables. The final `else` argument is optional and defaults to empty text.
277
-
278
- ### Trusted custom macros and slots
279
-
280
- Custom macros and slots are registered by trusted extension code, not embedded in prompt-stack JSON. For project-local customization, put registration modules in `.pi/forge/extensions/`. For machine-wide personal customization, put them in `~/.pi/forge/extensions/`. pi-forge loads global modules first, then project-local modules, after project trust and before stack validation. Both locations reload on `/preset reload`.
281
-
282
- These modules receive the registration API from pi-forge, so they do not need to import `@zihanw/pi-forge` or know where pi-forge is installed.
283
-
284
- ```ts
285
- // .pi/forge/extensions/ticket-context.ts
286
- export default function register(api) {
287
- api.registerMacro({
288
- name: "ticketId",
289
- description: "Current ticket id from session variables.",
290
- render: (ctx) => ctx.variables.toMacroText(ctx.variables.get("ticket.id")),
291
- });
292
-
293
- api.registerSlot({
294
- name: "ticket-context",
295
- description: "Render ticket context for the current task.",
296
- options: {
297
- heading: { type: "string", default: "Ticket context" },
298
- },
299
- render: (ctx) => [
300
- String(ctx.options.heading ?? "Ticket context") + ":",
301
- "- Ticket: " + ctx.variables.toMacroText(ctx.variables.get("ticket.id")),
302
- "- Project: " + ctx.helpers.normalizePath(ctx.runtime.options.cwd),
303
- ].join("\n"),
304
- });
305
- }
306
- ```
307
-
308
- The stack remains declarative:
309
-
310
- ```json
311
- {
312
- "kind": "slot",
313
- "id": "ticket-context",
314
- "enabled": true,
315
- "role": "system",
316
- "slot": "ticket-context",
317
- "options": {
318
- "heading": "Current ticket"
319
- }
320
- }
321
- ```
322
-
323
- Supported module files are `.ts`, `.js`, `.mjs`, `.cjs`, and `index.*` inside a subdirectory. TypeScript modules should stick to syntax Node can strip at runtime, or you can use `.js` / `.mjs` instead. A module can export either `default function register(api)` or `export function register(api)`. Registered macro and slot names must be unique across built-ins, global extensions, and project extensions; duplicate names show as extension load warnings.
324
-
325
- The API includes `cwd`, `forgeDir`, `extensionPath`, `helpers`, `registerMacro`, `registerSlot`, `getRegisteredMacros`, and `getRegisteredSlots`. For global modules, `forgeDir` is `~/.pi/forge`; for project modules, it is `<project>/.pi/forge`.
110
+ See the [complete command reference](docs/reference/commands.md).
326
111
 
327
- Missing custom slots are validation warnings until the registering module is loaded. Built-in macros and slots use the same registry internally, so `getRegisteredMacros()` and `getRegisteredSlots()` can be used as implementation references. `/preset diagnostics` shows loaded pi-forge extension files and load failures.
112
+ ## Experimental foreground delegation
328
113
 
329
- For a complete copyable extension and stack, see [examples/custom-system-status-extension](examples/custom-system-status-extension). It registers a `{{cpuLoad}}` macro and a `machine-status` slot from `.pi/forge/extensions/system-status.ts`.
114
+ pi-forge can run an explicitly enabled profile as a clean, foreground Pi subprocess. The model can discover eligible profiles with `forge_subagent_profiles` and invoke one with `forge_subagent`; humans use `/forge-agent plan` and `/forge-agent run`.
330
115
 
331
- Reusable Pi packages can still import `registerMacro` and `registerSlot` from `@zihanw/pi-forge`. The `.pi/forge/extensions` and `~/.pi/forge/extensions` loaders are intended for small trusted customizations without package boilerplate.
116
+ This feature is **experimental** and profiles are not delegatable by default. Enable each profile in the trusted project's `.pi/forge/config.json` or its web-editor delegation card. Interactive execution presents an immutable plan for approval unless the project explicitly authorizes unattended model invocation.
332
117
 
333
- ## Stack reference
118
+ > **Security boundary:** The current backends are shared-user processes, not operating-system sandboxes. “Read-only” describes the model-visible tool policy. The child retains the invoking user's OS read permissions, and readable content may be sent to the selected provider and retained in Pi's session data. Timeout and cancellation are best effort, and `/tree` cannot undo provider requests, billing, or external effects.
334
119
 
335
- ### Full item types
120
+ Read [foreground delegation and its safety model](docs/guides/delegation.md) before enabling it.
336
121
 
337
- **Block:**
338
-
339
- ```json
340
- {
341
- "kind": "block",
342
- "id": "unique-id",
343
- "name": "Readable label",
344
- "enabled": true,
345
- "role": "system",
346
- "content": "Your text here. Use {{macros}} for dynamic content."
347
- }
348
- ```
349
-
350
- Valid roles: `system`, `user`, `assistant`, `custom`.
351
-
352
- **Slot:**
353
-
354
- ```json
355
- {
356
- "kind": "slot",
357
- "id": "unique-id",
358
- "name": "Chat History",
359
- "enabled": true,
360
- "role": "user",
361
- "slot": "chat-history",
362
- "options": {
363
- "includeLastUserMessage": false
364
- }
365
- }
366
- ```
367
-
368
- ### Chat history options
369
-
370
- ```json
371
- "options": {
372
- "includeLastUserMessage": false,
373
- "stripAssistantThinking": true,
374
- "includeSummaries": true,
375
- "toolMode": "keep",
376
- "roles": ["user", "assistant"],
377
- "maxMessages": 40,
378
- "maxChars": 20000
379
- }
380
- ```
122
+ ## Documentation
381
123
 
382
- Set to `false` when you use `{{lastUserMessage}}` after the history — prevents the user's message from appearing twice.
124
+ ### Learn
383
125
 
384
- Set `stripAssistantThinking` to `true` to remove prior assistant thinking blocks from inserted chat history. Visible assistant text, tool calls, and tool result messages are preserved. This only affects history inserted by that slot and does not alter the current agent loop or stored transcript.
126
+ - [Getting started](docs/getting-started.md)
127
+ - [Prompt-stack concepts](docs/concepts/prompt-stacks.md)
128
+ - [Agent-profile concepts](docs/concepts/agent-profiles.md)
129
+ - [Web editor](docs/guides/web-editor.md)
130
+ - [Prompt-stack patterns and examples](docs/guides/use-cases.md)
131
+ - [SillyTavern import](docs/guides/sillytavern-import.md)
132
+ - [Custom macros and slots](docs/guides/custom-macros-and-slots.md)
133
+ - [Prompt and payload debugging](docs/guides/debugging.md)
385
134
 
386
- Use `includeSummaries: false` to omit Pi branch/compaction summary messages, `roles` to keep only specific message roles, `toolMode: "drop"` to remove prior tool-call/tool-result history, and `maxMessages` / `maxChars` to keep only recent history. When filters or limits can break tool-call pairs, pi-forge removes dangling tool calls/results instead of sending inconsistent tool history.
135
+ ### Reference
387
136
 
388
- ### Date slot options
137
+ - [Commands](docs/reference/commands.md)
138
+ - [Stack schema and policy](docs/reference/stack-schema.md)
139
+ - [Macros and slots](docs/reference/macros-and-slots.md)
140
+ - [Configuration](docs/reference/configuration.md)
141
+ - [Public API policy](docs/reference/public-api.md)
142
+ - [Experimental subagent adapter](docs/reference/subagent-adapter.md)
389
143
 
390
- Set `"includeTime": true` on a `date` or `date-cwd` slot to include the current time in `HH:MM:SS` after the current date.
144
+ ### Develop and design
391
145
 
392
- ### Structured slot format options
146
+ - [Development setup](docs/development/setup.md)
147
+ - [Release process](docs/development/release.md)
148
+ - [Roadmap](docs/development/roadmap.md)
149
+ - [Historical design archive](docs/design/README.md)
393
150
 
394
- Structured runtime slots default to XML-style wrappers. Add `"format": "plain"` to `tools`, `tool-guidelines`, `skills`, `project-context`, or `variables` slots for compact newline-separated output.
395
-
396
- ```json
397
- {
398
- "kind": "slot",
399
- "id": "tools",
400
- "enabled": true,
401
- "role": "system",
402
- "slot": "tools",
403
- "options": {
404
- "format": "plain"
405
- }
406
- }
407
- ```
408
-
409
- The default Pi mirror uses a few extra slot options:
410
-
411
- ```json
412
- {
413
- "slot": "tools",
414
- "options": {
415
- "format": "plain",
416
- "onlyWithSnippets": true
417
- }
418
- }
419
- ```
420
-
421
- `tools.onlyWithSnippets` matches Pi's default "Available tools" section by hiding tools that do not provide prompt snippets. `tool-guidelines.heading`, `tool-guidelines.includePiDefaultGuidelines`, and `tool-guidelines.piStyle` make the guidelines slot match Pi's default heading and bullets. `skills.requireReadTool` hides skills unless the read tool is active, matching Pi's default behavior.
422
-
423
- ### Tool and skill policy
424
-
425
- Prompt stacks can constrain tools and skills with stack-level `allow` or `deny` lists. Patterns are exact by default and support `*` wildcards.
426
-
427
- ```json
428
- {
429
- "tools": {
430
- "allow": ["read", "bash"]
431
- },
432
- "skills": {
433
- "deny": ["browser-danger"]
434
- }
435
- }
436
- ```
437
-
438
- Use `allow` when only matching tools or skills should remain active. Use `deny` when everything except matching tools or skills should remain active. A single resource policy cannot contain both non-empty lists; mixed `allow` and `deny` entries are validation errors.
439
-
440
- Tool policy is enforced through Pi's active tool list while the stack is active. pi-forge remembers the previous active tools and restores them when prompt stacks are disabled or switched to an unrestricted stack.
441
-
442
- Skill policy filters skills rendered by pi-forge's `skills` slot. If a stack uses `mode: "append"` or `"prepend"`, Pi's base prompt may already contain unfiltered skills; use `mode: "replace"` when skill visibility must be controlled.
443
-
444
- ### Regex transforms
445
-
446
- Prompt stacks can run deterministic regex replacements on model-bound prompt text and, optionally, finalized assistant messages. Outgoing rules support `history` and `compiled` stages. Destructive final-message cleanup uses `effect: "finalize"` at `stage: "compiled"` with the `messages` target. True display-only streaming transforms and provider-payload rewrites are not active yet.
447
-
448
- ```json
449
- "regex": {
450
- "schemaVersion": 1,
451
- "rules": [
452
- {
453
- "id": "trim-ooc",
454
- "enabled": true,
455
- "stage": "history",
456
- "effect": "outgoing",
457
- "pattern": "\\(OOC:[^)]+\\)",
458
- "flags": "gi",
459
- "replace": "",
460
- "roles": ["assistant"],
461
- "maxMessages": 20
462
- }
463
- ]
464
- }
465
- ```
466
-
467
- Use `stage: "history"` to transform messages inserted by the `chat-history` slot. Use `stage: "compiled"` with optional `targets: ["system"]`, `["messages"]`, or both to transform the final compiled prompt. Message rules can filter by `roles`, `maxMessages`, `maxChars`, `minDepth`, and `maxDepth`, where depth `0` is the latest message. Replacements use JavaScript syntax (`$&` for the full match, `$1` for captures; `$0` is also accepted as a full-match alias, and `$$` escapes a literal `$`). `trimStrings` removes literal strings from expanded replacement matches/captures, matching SillyTavern's Trim Out behavior. Supported regex flags are `g`, `i`, `m`, `s`, and `u`.
468
-
469
- To clean a completed assistant message after streaming, use `effect: "finalize"`:
470
-
471
- ```json
472
- {
473
- "id": "finalize-ooc",
474
- "enabled": true,
475
- "stage": "compiled",
476
- "effect": "finalize",
477
- "targets": ["messages"],
478
- "roles": ["assistant"],
479
- "pattern": "\\s*\\(OOC:[^)]+\\)",
480
- "flags": "gi",
481
- "replace": ""
482
- }
483
- ```
484
-
485
- Warning: `finalize` runs at `message_end`, after raw output may already have streamed in the TUI. It returns a cleaned replacement message to Pi, so the original model output is not preserved in the stored transcript.
486
-
487
- `effect: "outgoing"` changes model input. `effect: "finalize"` changes finalized assistant transcript content. `effect: "display"` and `"both"` validate with warnings but are ignored at runtime until true display transforms are implemented.
488
-
489
- SillyTavern imports convert deterministic prompt-only `{{match}}` / `$0` full-match replacements to JavaScript `$&` (both `$0` and `$&` work in pi-forge), preserve original regex metadata in `source.sillytavern`, and run as history-stage rules so depth stays chat-relative. Display-only/browser/unsupported-placement scripts stay report-only. The web editor has a structured Regex dialog for these rule fields and preserves advanced unknown fields for raw JSON editing.
490
-
491
- ### Variables slot options
492
-
493
- ```json
494
- {
495
- "kind": "slot",
496
- "id": "variables",
497
- "enabled": true,
498
- "role": "user",
499
- "slot": "variables",
500
- "options": {
501
- "includeStatic": true,
502
- "includeSession": true,
503
- "includeTurn": false,
504
- "format": "xml"
505
- }
506
- }
507
- ```
508
-
509
- ## Package setup for development
510
-
511
- ```bash
512
- git clone <repo>
513
- cd pi-forge
514
- npm install
515
- npm run build
516
- # .pi/settings.json already points at the package root
517
- pi # start Pi, trust the project, /reload if needed
518
- ```
519
-
520
- Run tests:
521
-
522
- ```bash
523
- npm test
524
- ```
525
-
526
- Typecheck:
527
-
528
- ```bash
529
- npm run typecheck
530
- ```
531
-
532
- Build package output:
533
-
534
- ```bash
535
- npm run build
536
- ```
151
+ Chinese user documentation starts at [docs/zh-CN/README.md](docs/zh-CN/README.md).
537
152
 
538
153
  ## License
539
154
 
540
- MIT
155
+ [MIT](LICENSE)