@zihanw/pi-forge 0.3.2 → 0.4.0-beta.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 (244) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/PUBLIC_API.md +28 -0
  3. package/README.md +137 -11
  4. package/README.zh-CN.md +137 -11
  5. package/SUBAGENT_ADAPTER_CONTRACT.md +199 -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 +8 -0
  11. package/dist/forge-config.d.ts.map +1 -0
  12. package/dist/forge-config.js +40 -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 +7 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +75 -361
  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 +215 -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 +30 -0
  55. package/dist/runtime/subagent-runtime.d.ts.map +1 -0
  56. package/dist/runtime/subagent-runtime.js +114 -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 +3 -0
  71. package/dist/storage.d.ts.map +1 -1
  72. package/dist/storage.js +9 -0
  73. package/dist/storage.js.map +1 -1
  74. package/dist/subagent/backend-registry.d.ts +75 -0
  75. package/dist/subagent/backend-registry.d.ts.map +1 -0
  76. package/dist/subagent/backend-registry.js +463 -0
  77. package/dist/subagent/backend-registry.js.map +1 -0
  78. package/dist/subagent/canonical.d.ts +10 -0
  79. package/dist/subagent/canonical.d.ts.map +1 -0
  80. package/dist/subagent/canonical.js +52 -0
  81. package/dist/subagent/canonical.js.map +1 -0
  82. package/dist/subagent/context.d.ts +8 -0
  83. package/dist/subagent/context.d.ts.map +1 -0
  84. package/dist/subagent/context.js +125 -0
  85. package/dist/subagent/context.js.map +1 -0
  86. package/dist/subagent/contract.d.ts +11 -0
  87. package/dist/subagent/contract.d.ts.map +1 -0
  88. package/dist/subagent/contract.js +11 -0
  89. package/dist/subagent/contract.js.map +1 -0
  90. package/dist/subagent/diagnostics.d.ts +3 -0
  91. package/dist/subagent/diagnostics.d.ts.map +1 -0
  92. package/dist/subagent/diagnostics.js +5 -0
  93. package/dist/subagent/diagnostics.js.map +1 -0
  94. package/dist/subagent/index.d.ts +13 -0
  95. package/dist/subagent/index.d.ts.map +1 -0
  96. package/dist/subagent/index.js +13 -0
  97. package/dist/subagent/index.js.map +1 -0
  98. package/dist/subagent/pi-model-runtime.d.ts +8 -0
  99. package/dist/subagent/pi-model-runtime.d.ts.map +1 -0
  100. package/dist/subagent/pi-model-runtime.js +22 -0
  101. package/dist/subagent/pi-model-runtime.js.map +1 -0
  102. package/dist/subagent/pi-sdk-backend.d.ts +23 -0
  103. package/dist/subagent/pi-sdk-backend.d.ts.map +1 -0
  104. package/dist/subagent/pi-sdk-backend.js +383 -0
  105. package/dist/subagent/pi-sdk-backend.js.map +1 -0
  106. package/dist/subagent/pi-subprocess-backend.d.ts +72 -0
  107. package/dist/subagent/pi-subprocess-backend.d.ts.map +1 -0
  108. package/dist/subagent/pi-subprocess-backend.js +756 -0
  109. package/dist/subagent/pi-subprocess-backend.js.map +1 -0
  110. package/dist/subagent/plan.d.ts +14 -0
  111. package/dist/subagent/plan.d.ts.map +1 -0
  112. package/dist/subagent/plan.js +160 -0
  113. package/dist/subagent/plan.js.map +1 -0
  114. package/dist/subagent/preflight.d.ts +4 -0
  115. package/dist/subagent/preflight.d.ts.map +1 -0
  116. package/dist/subagent/preflight.js +108 -0
  117. package/dist/subagent/preflight.js.map +1 -0
  118. package/dist/subagent/request.d.ts +4 -0
  119. package/dist/subagent/request.d.ts.map +1 -0
  120. package/dist/subagent/request.js +122 -0
  121. package/dist/subagent/request.js.map +1 -0
  122. package/dist/subagent/response.d.ts +8 -0
  123. package/dist/subagent/response.d.ts.map +1 -0
  124. package/dist/subagent/response.js +155 -0
  125. package/dist/subagent/response.js.map +1 -0
  126. package/dist/subagent/subprocess-bridge.d.ts +21 -0
  127. package/dist/subagent/subprocess-bridge.d.ts.map +1 -0
  128. package/dist/subagent/subprocess-bridge.js +87 -0
  129. package/dist/subagent/subprocess-bridge.js.map +1 -0
  130. package/dist/subagent/subprocess-report.d.ts +4 -0
  131. package/dist/subagent/subprocess-report.d.ts.map +1 -0
  132. package/dist/subagent/subprocess-report.js +55 -0
  133. package/dist/subagent/subprocess-report.js.map +1 -0
  134. package/dist/subagent/tools.d.ts +4 -0
  135. package/dist/subagent/tools.d.ts.map +1 -0
  136. package/dist/subagent/tools.js +42 -0
  137. package/dist/subagent/tools.js.map +1 -0
  138. package/dist/subagent/types.d.ts +384 -0
  139. package/dist/subagent/types.d.ts.map +1 -0
  140. package/dist/subagent/types.js +3 -0
  141. package/dist/subagent/types.js.map +1 -0
  142. package/dist/subagent/validation.d.ts +35 -0
  143. package/dist/subagent/validation.d.ts.map +1 -0
  144. package/dist/subagent/validation.js +500 -0
  145. package/dist/subagent/validation.js.map +1 -0
  146. package/dist/subagent-command.d.ts +4 -0
  147. package/dist/subagent-command.d.ts.map +1 -0
  148. package/dist/subagent-command.js +153 -0
  149. package/dist/subagent-command.js.map +1 -0
  150. package/dist/subagent-contract.d.ts +8 -0
  151. package/dist/subagent-contract.d.ts.map +1 -0
  152. package/dist/subagent-contract.js +8 -0
  153. package/dist/subagent-contract.js.map +1 -0
  154. package/dist/subagent-host.d.ts +44 -0
  155. package/dist/subagent-host.d.ts.map +1 -0
  156. package/dist/subagent-host.js +291 -0
  157. package/dist/subagent-host.js.map +1 -0
  158. package/dist/subagent-profile-tool.d.ts +26 -0
  159. package/dist/subagent-profile-tool.d.ts.map +1 -0
  160. package/dist/subagent-profile-tool.js +93 -0
  161. package/dist/subagent-profile-tool.js.map +1 -0
  162. package/dist/subagent-tool.d.ts +50 -0
  163. package/dist/subagent-tool.d.ts.map +1 -0
  164. package/dist/subagent-tool.js +385 -0
  165. package/dist/subagent-tool.js.map +1 -0
  166. package/dist/web-editor/client/api.d.ts +9 -0
  167. package/dist/web-editor/client/api.d.ts.map +1 -0
  168. package/dist/web-editor/client/api.js +26 -0
  169. package/dist/web-editor/client/api.js.map +1 -0
  170. package/dist/web-editor/client/dom.d.ts +13 -0
  171. package/dist/web-editor/client/dom.d.ts.map +1 -0
  172. package/dist/web-editor/client/dom.js +30 -0
  173. package/dist/web-editor/client/dom.js.map +1 -0
  174. package/dist/web-editor/client/inspector.d.ts +22 -0
  175. package/dist/web-editor/client/inspector.d.ts.map +1 -0
  176. package/dist/web-editor/client/inspector.js +226 -0
  177. package/dist/web-editor/client/inspector.js.map +1 -0
  178. package/dist/web-editor/client/main.d.ts +2 -0
  179. package/dist/web-editor/client/main.d.ts.map +1 -0
  180. package/dist/web-editor/client/main.js +1468 -0
  181. package/dist/web-editor/client/main.js.map +1 -0
  182. package/dist/web-editor/client/policy-editor.d.ts +16 -0
  183. package/dist/web-editor/client/policy-editor.d.ts.map +1 -0
  184. package/dist/web-editor/client/policy-editor.js +330 -0
  185. package/dist/web-editor/client/policy-editor.js.map +1 -0
  186. package/dist/web-editor/client/regex-editor.d.ts +19 -0
  187. package/dist/web-editor/client/regex-editor.d.ts.map +1 -0
  188. package/dist/web-editor/client/regex-editor.js +281 -0
  189. package/dist/web-editor/client/regex-editor.js.map +1 -0
  190. package/dist/web-editor/client/types.d.ts +60 -0
  191. package/dist/web-editor/client/types.d.ts.map +1 -0
  192. package/dist/web-editor/client/types.js +2 -0
  193. package/dist/web-editor/client/types.js.map +1 -0
  194. package/dist/web-editor/client-script.d.ts +2 -0
  195. package/dist/web-editor/client-script.d.ts.map +1 -0
  196. package/dist/web-editor/client-script.generated.d.ts +2 -0
  197. package/dist/web-editor/client-script.generated.d.ts.map +1 -0
  198. package/dist/web-editor/client-script.generated.js +3 -0
  199. package/dist/web-editor/client-script.generated.js.map +1 -0
  200. package/dist/web-editor/client-script.js +2 -0
  201. package/dist/web-editor/client-script.js.map +1 -0
  202. package/dist/web-editor/page.d.ts.map +1 -1
  203. package/dist/web-editor/page.js +6 -3249
  204. package/dist/web-editor/page.js.map +1 -1
  205. package/dist/web-editor/styles.d.ts +2 -0
  206. package/dist/web-editor/styles.d.ts.map +1 -0
  207. package/dist/web-editor/styles.js +996 -0
  208. package/dist/web-editor/styles.js.map +1 -0
  209. package/dist/web-host.d.ts +3 -3
  210. package/dist/web-host.d.ts.map +1 -1
  211. package/dist/web-host.js +6 -0
  212. package/dist/web-host.js.map +1 -1
  213. package/package.json +42 -14
  214. package/src/compiler.ts +0 -578
  215. package/src/extension-registry.ts +0 -33
  216. package/src/forge-extensions.ts +0 -223
  217. package/src/index.ts +0 -445
  218. package/src/lifecycle.ts +0 -171
  219. package/src/loader.ts +0 -394
  220. package/src/macro-engine.ts +0 -358
  221. package/src/payload-capture.ts +0 -85
  222. package/src/payload-command.ts +0 -138
  223. package/src/policy.ts +0 -42
  224. package/src/preset-command.ts +0 -280
  225. package/src/preview.ts +0 -226
  226. package/src/regex.ts +0 -500
  227. package/src/render-helpers.ts +0 -169
  228. package/src/runtime-state.ts +0 -40
  229. package/src/sillytavern-importer/items.ts +0 -98
  230. package/src/sillytavern-importer/macros.ts +0 -159
  231. package/src/sillytavern-importer/prompt-order.ts +0 -54
  232. package/src/sillytavern-importer/regex.ts +0 -270
  233. package/src/sillytavern-importer/report.ts +0 -202
  234. package/src/sillytavern-importer/types.ts +0 -120
  235. package/src/sillytavern-importer.ts +0 -152
  236. package/src/slot-renderers.ts +0 -414
  237. package/src/stack-migration.ts +0 -159
  238. package/src/storage.ts +0 -45
  239. package/src/types.ts +0 -209
  240. package/src/web-editor/index.ts +0 -2
  241. package/src/web-editor/page.ts +0 -3330
  242. package/src/web-editor/server.ts +0 -294
  243. package/src/web-editor/types.ts +0 -98
  244. package/src/web-host.ts +0 -232
@@ -1,280 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
4
- import { isDisabledPromptStackId, promptStackPath, promptStackReadDirs } from "./loader.ts";
5
- import { renderDiagnostics, renderPreview, showText } from "./preview.ts";
6
- import type { PiForgeRuntimeState } from "./runtime-state.ts";
7
- import { importSillyTavernPreset } from "./sillytavern-importer.ts";
8
- import { migrateLegacyPromptStacks, renderMigrationReport } from "./stack-migration.ts";
9
- import { forgeExtensionsDir, globalForgeExtensionsDir } from "./storage.ts";
10
- import type { LoadedPromptStack } from "./types.ts";
11
-
12
- export interface PresetCommandDeps {
13
- selectedActiveId(): string | undefined;
14
- setActive(id: string | undefined, ctx?: ExtensionCommandContext): boolean;
15
- reloadStacks(ctx: ExtensionCommandContext, preferredId?: string): Promise<void>;
16
- openWebEditor(ctx: ExtensionCommandContext, mode?: "open" | "restart"): Promise<void>;
17
- stopWebEditor(ctx: ExtensionCommandContext): Promise<void>;
18
- }
19
-
20
- export function registerPresetCommand(pi: ExtensionAPI, state: PiForgeRuntimeState, deps: PresetCommandDeps): void {
21
- pi.registerCommand("preset", {
22
- description: "Manage pi-forge prompt stacks: list, use, preview, validate, reload, ui",
23
- getArgumentCompletions: (prefix) => {
24
- const parts = prefix.trimStart().split(/\s+/);
25
- if (parts.length <= 1 && !prefix.endsWith(" ")) {
26
- const commands = ["list", "use", "preview", "validate", "diagnostics", "reload", "status", "import-silly", "migrate-stacks", "ui"];
27
- return commands.filter((cmd) => cmd.startsWith(parts[0] ?? "")).map((cmd) => ({ value: cmd, label: cmd }));
28
- }
29
- const first = parts[0];
30
- if (["use", "preview", "validate"].includes(first)) {
31
- const fragment = parts[1] ?? "";
32
- const ids = ["none", ...state.stacks.map((loaded) => loaded.stack.id)];
33
- return ids.filter((id) => id.startsWith(fragment)).map((id) => ({ value: `${first} ${id}`, label: id }));
34
- }
35
- if (first === "ui" && parts.length <= 2) {
36
- const fragment = parts[1] ?? "";
37
- const subs = ["stop", "restart"];
38
- return subs.filter((s) => s.startsWith(fragment)).map((s) => ({ value: `ui ${s}`, label: s }));
39
- }
40
- if (first === "migrate-stacks") {
41
- const fragment = parts[parts.length - 1] ?? "";
42
- const flags = ["--dry-run", "--overwrite", "--delete-legacy"];
43
- return flags.filter((flag) => flag.startsWith(fragment)).map((flag) => ({ value: `${parts.slice(0, -1).join(" ")} ${flag}`.trim(), label: flag }));
44
- }
45
- return null;
46
- },
47
- handler: async (args, ctx) => {
48
- await handlePresetCommand(state, deps, args, ctx);
49
- },
50
- });
51
- }
52
-
53
- async function handlePresetCommand(
54
- state: PiForgeRuntimeState,
55
- deps: PresetCommandDeps,
56
- args: string,
57
- ctx: ExtensionCommandContext,
58
- ): Promise<void> {
59
- const trimmed = args.trim();
60
- const [command = "list", ...rest] = trimmed ? trimmed.split(/\s+/) : ["list"];
61
-
62
- switch (command) {
63
- case "list":
64
- case "status":
65
- await showText(ctx, "pi-forge prompt stacks", renderStackList(state, ctx));
66
- return;
67
-
68
- case "reload":
69
- await deps.reloadStacks(ctx, deps.selectedActiveId());
70
- ctx.ui.notify(`pi-forge: reloaded ${state.stacks.length} prompt stack(s).`, "info");
71
- return;
72
-
73
- case "ui": {
74
- const sub = rest[0];
75
- if (sub === "stop") {
76
- await deps.stopWebEditor(ctx);
77
- return;
78
- }
79
- await deps.openWebEditor(ctx, sub === "restart" ? "restart" : "open");
80
- return;
81
- }
82
-
83
- case "use": {
84
- const id = rest[0];
85
- if (!id) {
86
- ctx.ui.notify("Usage: /preset use <id|none>", "warning");
87
- return;
88
- }
89
- if (!deps.setActive(id, ctx)) {
90
- ctx.ui.notify(`Unknown prompt stack: ${id}`, "error");
91
- return;
92
- }
93
- ctx.ui.notify(state.active ? `pi-forge: active prompt stack ${state.active.stack.id}` : "pi-forge: prompt stack disabled", "info");
94
- return;
95
- }
96
-
97
- case "preview": {
98
- const target = rest[0] ? state.stacks.find((loaded) => loaded.stack.id === rest[0]) : state.active;
99
- if (!target) {
100
- ctx.ui.notify(rest[0] ? `Unknown prompt stack: ${rest[0]}` : "No active prompt stack.", "warning");
101
- return;
102
- }
103
- await showText(ctx, `pi-forge preview: ${target.stack.id}`, renderPreview(ctx, target, state.sessionVariables));
104
- return;
105
- }
106
-
107
- case "validate": {
108
- const target = rest[0] ? state.stacks.find((loaded) => loaded.stack.id === rest[0]) : state.active;
109
- if (!target) {
110
- ctx.ui.notify(rest[0] ? `Unknown prompt stack: ${rest[0]}` : "No active prompt stack.", "warning");
111
- return;
112
- }
113
- await showText(ctx, `pi-forge validation: ${target.stack.id}`, renderDiagnostics(target.diagnostics));
114
- return;
115
- }
116
-
117
- case "diagnostics": {
118
- await showText(ctx, "pi-forge diagnostics", renderCurrentDiagnostics(state));
119
- return;
120
- }
121
-
122
- case "migrate-stacks": {
123
- const flags = new Set(rest);
124
- const dryRun = flags.has("--dry-run");
125
- if (!ctx.isProjectTrusted() && !dryRun) {
126
- ctx.ui.notify("pi-forge: project is not trusted; refusing to migrate prompt stacks.", "warning");
127
- return;
128
- }
129
- const report = migrateLegacyPromptStacks(ctx.cwd, {
130
- dryRun,
131
- overwrite: flags.has("--overwrite"),
132
- deleteLegacy: flags.has("--delete-legacy"),
133
- });
134
- if (!dryRun) await deps.reloadStacks(ctx, deps.selectedActiveId());
135
- const changed = report.copied + report.overwritten;
136
- const summary = dryRun
137
- ? `pi-forge: migration dry run found ${report.files.length} legacy stack file(s).`
138
- : `pi-forge: migrated ${changed} legacy stack file(s), skipped ${report.skipped}, errors ${report.errors}.`;
139
- ctx.ui.notify(summary, report.errors ? "warning" : "info");
140
- await showText(ctx, "pi-forge prompt-stack migration", renderMigrationReport(report));
141
- return;
142
- }
143
-
144
- case "import-silly":
145
- await handleImportSilly(state, deps, rest, ctx);
146
- return;
147
-
148
- default:
149
- ctx.ui.notify(`Unknown /preset subcommand: ${command}`, "warning");
150
- return;
151
- }
152
- }
153
-
154
- async function handleImportSilly(
155
- state: PiForgeRuntimeState,
156
- deps: PresetCommandDeps,
157
- rest: string[],
158
- ctx: ExtensionCommandContext,
159
- ): Promise<void> {
160
- if (!ctx.isProjectTrusted()) {
161
- ctx.ui.notify("pi-forge: project is not trusted; refusing to write imported prompt stacks.", "warning");
162
- return;
163
- }
164
-
165
- const sourcePath = rest[0];
166
- if (!sourcePath) {
167
- ctx.ui.notify("Usage: /preset import-silly <path> [character_id] [--dry-run] [--overwrite]", "warning");
168
- return;
169
- }
170
-
171
- const charIdToken = rest[1]?.startsWith("--") ? undefined : rest[1];
172
- const flags = new Set(rest.slice(charIdToken ? 2 : 1));
173
- const dryRun = flags.has("--dry-run");
174
- let overwrite = flags.has("--overwrite");
175
-
176
- const resolvedPath = sourcePath.startsWith("/") ? sourcePath : join(ctx.cwd, sourcePath);
177
- if (!existsSync(resolvedPath)) {
178
- ctx.ui.notify(`File not found: ${resolvedPath}`, "error");
179
- return;
180
- }
181
-
182
- const charId = charIdToken ? Number(charIdToken) : undefined;
183
- if (charIdToken && (Number.isNaN(charId) || !Number.isFinite(charId))) {
184
- ctx.ui.notify(`Invalid character_id: ${charIdToken}`, "error");
185
- return;
186
- }
187
-
188
- const result = importSillyTavernPreset(resolvedPath, charId);
189
- if ("error" in result) {
190
- ctx.ui.notify(`pi-forge import error: ${result.error}`, "error");
191
- return;
192
- }
193
-
194
- const existingStack = state.stacks.find((candidate) => candidate.stack.id === result.stack.id);
195
- const stackPath = existingStack?.filePath ?? promptStackPath(ctx.cwd, result.stack.id);
196
- const stacksDir = dirname(stackPath);
197
- const reportDir = join(ctx.cwd, ".pi", "forge", "import-reports");
198
- const reportPath = join(reportDir, `${result.stack.id}.md`);
199
-
200
- if (dryRun) {
201
- await showText(ctx, `pi-forge import dry run: ${result.stack.id}`, `Would write stack to: ${stackPath}\nWould write report to: ${reportPath}\n\n## Generated stack JSON\n\n\`\`\`json\n${JSON.stringify(result.stack, null, 2)}\n\`\`\`\n\n${result.report}`);
202
- return;
203
- }
204
-
205
- const existingPaths = [stackPath, reportPath].filter((path) => existsSync(path));
206
- if (existingPaths.length > 0 && !overwrite) {
207
- if (!ctx.hasUI) {
208
- ctx.ui.notify(`pi-forge: import would overwrite existing file(s): ${existingPaths.join(", ")}. Re-run with --overwrite.`, "error");
209
- return;
210
- }
211
- overwrite = await ctx.ui.confirm("Overwrite pi-forge import output?", `These file(s) already exist:\n${existingPaths.join("\n")}\n\nOverwrite them?`);
212
- if (!overwrite) {
213
- ctx.ui.notify("pi-forge: import cancelled; existing files were left unchanged.", "info");
214
- return;
215
- }
216
- }
217
-
218
- if (!existsSync(stacksDir)) mkdirSync(stacksDir, { recursive: true });
219
- writeFileSync(stackPath, JSON.stringify(result.stack, null, 2), "utf8");
220
-
221
- if (!existsSync(reportDir)) mkdirSync(reportDir, { recursive: true });
222
- writeFileSync(reportPath, result.report, "utf8");
223
-
224
- await deps.reloadStacks(ctx, deps.selectedActiveId());
225
-
226
- ctx.ui.notify(`pi-forge: imported ${result.stack.id} (${result.stack.items.length} items)`, "info");
227
- await showText(ctx, `pi-forge import report: ${result.stack.id}`, `Stack written to: ${stackPath}\nReport written to: ${reportPath}\n\n${result.report}`);
228
- }
229
-
230
- function renderStackList(state: PiForgeRuntimeState, ctx: ExtensionCommandContext): string {
231
- const lines = [
232
- "Prompt stack directories:",
233
- ...promptStackReadDirs(ctx.cwd).map((dir, index) => ` ${index === 0 ? "primary" : "legacy"}: ${dir}`),
234
- "Forge extension directories:",
235
- ` global: ${globalForgeExtensionsDir()}`,
236
- ` project: ${forgeExtensionsDir(ctx.cwd)}`,
237
- `Loaded forge extensions: ${state.forgeExtensionPaths.length}`,
238
- `Active stack: ${state.active?.stack.id ?? "(none)"}`,
239
- "",
240
- ];
241
-
242
- if (state.stacks.length === 0) {
243
- lines.push("No prompt stacks found.", "Create .pi/forge/prompt-stacks/default.json to auto-activate a stack.");
244
- return lines.join("\n");
245
- }
246
-
247
- for (const loaded of state.stacks) {
248
- const marker = loaded === state.active ? "*" : " ";
249
- const errors = loaded.diagnostics.filter((d) => d.level === "error").length;
250
- const warnings = loaded.diagnostics.filter((d) => d.level === "warning").length;
251
- const suffix = errors || warnings ? ` (${errors} errors, ${warnings} warnings)` : "";
252
- lines.push(`${marker} ${loaded.stack.id}${loaded.stack.name ? ` \u2014 ${loaded.stack.name}` : ""}${suffix}`);
253
- lines.push(` ${loaded.filePath}`);
254
- }
255
-
256
- lines.push("", "Commands:", " /preset use <id|none>", " /preset preview [id]", " /preset validate [id]", " /preset diagnostics", " /preset reload", " /preset migrate-stacks [--dry-run] [--overwrite] [--delete-legacy]", " /preset ui [stop|restart]");
257
- return lines.join("\n");
258
- }
259
-
260
- function renderCurrentDiagnostics(state: PiForgeRuntimeState): string {
261
- const lines = ["# pi-forge diagnostics", ""];
262
- lines.push("## Active stack load/validation diagnostics", "");
263
- lines.push(state.active ? renderDiagnostics(state.active.diagnostics) : "No active prompt stack.");
264
- lines.push("", "## pi-forge extension diagnostics", "");
265
- lines.push(renderDiagnostics(state.forgeExtensionDiagnostics));
266
- lines.push("", "## Loaded pi-forge extensions", "");
267
- lines.push(state.forgeExtensionPaths.length > 0 ? state.forgeExtensionPaths.map((path) => `- ${path}`).join("\n") : "(none)");
268
- lines.push("", "## Latest runtime compile diagnostics", "");
269
- lines.push(renderDiagnostics(state.latestCompileDiagnostics));
270
- return lines.join("\n");
271
- }
272
-
273
- export function selectedActiveId(state: PiForgeRuntimeState): string | undefined {
274
- if (state.active) return state.active.stack.id;
275
- return isDisabledPromptStackId(state.lastPersistedActiveId) ? "none" : undefined;
276
- }
277
-
278
- export function findStack(state: PiForgeRuntimeState, id: string): LoadedPromptStack | undefined {
279
- return state.stacks.find((candidate) => candidate.stack.id === id);
280
- }
package/src/preview.ts DELETED
@@ -1,226 +0,0 @@
1
- import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
- import type { BuildSystemPromptOptions, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
3
- import {
4
- agentMessageToPreviewText,
5
- compileMessages,
6
- compileSystemPrompt,
7
- createPromptVariableStore,
8
- getLatestUserMessage,
9
- } from "./compiler.ts";
10
- import { estimatePayloadTokens } from "./payload-capture.ts";
11
- import type { CompileMessageSource, LoadedPromptStack, PromptStackDiagnostic, PromptVariableValue } from "./types.ts";
12
- import type { WebEditorPreview, WebEditorPreviewSection } from "./web-editor/index.ts";
13
-
14
- export function renderPreview(
15
- ctx: ExtensionCommandContext,
16
- target: LoadedPromptStack,
17
- sessionVariables: Record<string, PromptVariableValue>,
18
- ): string {
19
- return buildPreview(ctx, target, sessionVariables).text;
20
- }
21
-
22
- export function buildPreview(
23
- ctx: ExtensionCommandContext,
24
- target: LoadedPromptStack,
25
- sessionVariables: Record<string, PromptVariableValue>,
26
- optionsOverride?: BuildSystemPromptOptions,
27
- ): { text: string; preview: WebEditorPreview; diagnostics: PromptStackDiagnostic[] } {
28
- const options = optionsOverride ?? ctx.getSystemPromptOptions();
29
- const sessionMessages = getPreviewSessionMessages(ctx);
30
- const latestUserMessage = getLatestUserMessage(sessionMessages);
31
- const previewVariables = createPromptVariableStore(sessionVariables);
32
- const runtime = { options, ctx, latestUserMessage, now: new Date(), variables: previewVariables };
33
- const system = compileSystemPrompt(target.stack, runtime, ctx.getSystemPrompt());
34
- const messages = compileMessages(target.stack, runtime, sessionMessages);
35
- const diagnostics = [...target.diagnostics, ...system.diagnostics, ...messages.diagnostics];
36
- const messageSections = messages.messages.map((message, index) => {
37
- const content = agentMessageToPreviewText(message);
38
- const source = messages.messageSources[index];
39
- return previewSection(`message-${index}`, previewMessageTitle(source, index), content, message.role);
40
- });
41
- const systemSection = previewSection("system", "System prompt", system.systemPrompt || "(empty)");
42
- const totalChars = systemSection.chars + messageSections.reduce((sum, section) => sum + section.chars, 0);
43
- const preview: WebEditorPreview = {
44
- stackId: target.stack.id,
45
- generatedAt: new Date().toISOString(),
46
- system: systemSection,
47
- messages: messageSections,
48
- totalChars,
49
- approxTokens: estimatePayloadTokens(`${system.systemPrompt}\n${messageSections.map((section) => section.content).join("\n")}`),
50
- };
51
-
52
- const text = [
53
- `# Prompt stack preview: ${target.stack.id}`,
54
- "",
55
- "## System prompt",
56
- "",
57
- system.systemPrompt || "(empty)",
58
- "",
59
- "## Message layout",
60
- "",
61
- renderPreviewSectionText(messageSections),
62
- "",
63
- "## Diagnostics",
64
- "",
65
- renderDiagnostics(diagnostics),
66
- ].join("\n");
67
-
68
- return { text, preview, diagnostics };
69
- }
70
-
71
- interface SessionEntryLike {
72
- id?: unknown;
73
- parentId?: unknown;
74
- type?: unknown;
75
- timestamp?: unknown;
76
- message?: unknown;
77
- summary?: unknown;
78
- firstKeptEntryId?: unknown;
79
- tokensBefore?: unknown;
80
- fromId?: unknown;
81
- customType?: unknown;
82
- content?: unknown;
83
- display?: unknown;
84
- details?: unknown;
85
- }
86
-
87
- function getPreviewSessionMessages(ctx: ExtensionCommandContext): AgentMessage[] {
88
- const entries = getCurrentBranchEntries(ctx).map(asSessionEntry);
89
- const compaction = latestCompactionEntry(entries);
90
- const messages: AgentMessage[] = [];
91
-
92
- const appendMessage = (entry: SessionEntryLike): void => {
93
- if (entry.type === "message" && isAgentMessage(entry.message)) {
94
- messages.push(entry.message);
95
- return;
96
- }
97
- if (entry.type === "custom_message" && typeof entry.customType === "string") {
98
- messages.push({
99
- role: "custom",
100
- customType: entry.customType,
101
- content: typeof entry.content === "string" || Array.isArray(entry.content) ? entry.content : "",
102
- display: entry.display === true,
103
- details: entry.details,
104
- timestamp: entryTimestamp(entry),
105
- } as AgentMessage);
106
- return;
107
- }
108
- if (entry.type === "branch_summary" && typeof entry.summary === "string" && entry.summary) {
109
- messages.push({
110
- role: "branchSummary",
111
- summary: entry.summary,
112
- fromId: typeof entry.fromId === "string" ? entry.fromId : "",
113
- timestamp: entryTimestamp(entry),
114
- } as AgentMessage);
115
- }
116
- };
117
-
118
- if (compaction && typeof compaction.summary === "string") {
119
- messages.push({
120
- role: "compactionSummary",
121
- summary: compaction.summary,
122
- tokensBefore: typeof compaction.tokensBefore === "number" ? compaction.tokensBefore : 0,
123
- timestamp: entryTimestamp(compaction),
124
- } as AgentMessage);
125
-
126
- const compactionIndex = entries.findIndex((entry) => entry.type === "compaction" && entry.id === compaction.id);
127
- let foundFirstKept = false;
128
- for (let index = 0; index < compactionIndex; index++) {
129
- const entry = entries[index]!;
130
- if (entry.id === compaction.firstKeptEntryId) foundFirstKept = true;
131
- if (foundFirstKept) appendMessage(entry);
132
- }
133
- for (let index = compactionIndex + 1; index < entries.length; index++) {
134
- appendMessage(entries[index]!);
135
- }
136
- return messages;
137
- }
138
-
139
- for (const entry of entries) appendMessage(entry);
140
- return messages;
141
- }
142
-
143
- function latestCompactionEntry(entries: SessionEntryLike[]): SessionEntryLike | undefined {
144
- for (let index = entries.length - 1; index >= 0; index--) {
145
- const entry = entries[index]!;
146
- if (entry.type === "compaction") return entry;
147
- }
148
- return undefined;
149
- }
150
-
151
- function getCurrentBranchEntries(ctx: ExtensionCommandContext): unknown[] {
152
- const leafId = ctx.sessionManager.getLeafId();
153
- if (leafId === null) return [];
154
- const sessionManager = ctx.sessionManager as {
155
- getBranch?: (fromId?: string) => unknown[];
156
- getEntries: () => unknown[];
157
- };
158
- return sessionManager.getBranch ? sessionManager.getBranch(leafId ?? undefined) : sessionManager.getEntries();
159
- }
160
-
161
- function asSessionEntry(value: unknown): SessionEntryLike {
162
- return value && typeof value === "object" ? value as SessionEntryLike : {};
163
- }
164
-
165
- function isAgentMessage(value: unknown): value is AgentMessage {
166
- return !!value && typeof value === "object" && typeof (value as { role?: unknown }).role === "string";
167
- }
168
-
169
- function entryTimestamp(entry: SessionEntryLike): number {
170
- if (typeof entry.timestamp === "number") return entry.timestamp;
171
- if (typeof entry.timestamp === "string") {
172
- const timestamp = new Date(entry.timestamp).getTime();
173
- if (Number.isFinite(timestamp)) return timestamp;
174
- }
175
- return Date.now();
176
- }
177
-
178
- function previewMessageTitle(source: CompileMessageSource | undefined, index: number): string {
179
- if (source?.kind === "stack-item") {
180
- return source.itemName?.trim() || source.itemId || `Stack item ${index + 1}`;
181
- }
182
- if (source?.kind === "chat-history") {
183
- const label = source.itemName?.trim() || "Chat history";
184
- return `${label} #${source.historyIndex ?? index + 1}`;
185
- }
186
- if (source?.kind === "implicit-history") {
187
- return `Conversation history #${source.historyIndex ?? index + 1}`;
188
- }
189
- return `Message ${index + 1}`;
190
- }
191
-
192
- function renderPreviewSectionText(sections: WebEditorPreviewSection[], maxChars = 8000): string {
193
- let text = "";
194
- for (const section of sections) {
195
- const role = section.role ? ` (${section.role})` : "";
196
- text += `\n--- ${section.title}${role} ---\n`;
197
- text += section.content;
198
- text += "\n";
199
- if (text.length > maxChars) return `${text.slice(0, maxChars)}\n\n[preview truncated]`;
200
- }
201
- return text.trimStart();
202
- }
203
-
204
- function previewSection(id: string, title: string, content: string, role?: string): WebEditorPreviewSection {
205
- return {
206
- id,
207
- title,
208
- role,
209
- content,
210
- chars: content.length,
211
- approxTokens: estimatePayloadTokens(content),
212
- };
213
- }
214
-
215
- export function renderDiagnostics(diagnostics: PromptStackDiagnostic[]): string {
216
- if (diagnostics.length === 0) return "No diagnostics.";
217
- return diagnostics.map((d) => `${d.level.toUpperCase()}${d.itemId ? ` [${d.itemId}]` : ""}: ${d.message}`).join("\n");
218
- }
219
-
220
- export async function showText(ctx: ExtensionCommandContext, title: string, text: string): Promise<void> {
221
- if (ctx.hasUI) {
222
- await ctx.ui.editor(title, text);
223
- return;
224
- }
225
- console.log(text);
226
- }