@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.
- package/CHANGELOG.md +160 -0
- package/PUBLIC_API.md +28 -0
- package/README.md +137 -11
- package/README.zh-CN.md +137 -11
- package/SUBAGENT_ADAPTER_CONTRACT.md +199 -0
- package/dist/agent-profile.d.ts +71 -0
- package/dist/agent-profile.d.ts.map +1 -0
- package/dist/agent-profile.js +303 -0
- package/dist/agent-profile.js.map +1 -0
- package/dist/forge-config.d.ts +8 -0
- package/dist/forge-config.d.ts.map +1 -0
- package/dist/forge-config.js +40 -0
- package/dist/forge-config.js.map +1 -0
- package/dist/forge-extensions.d.ts.map +1 -1
- package/dist/forge-extensions.js +19 -3
- package/dist/forge-extensions.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +75 -361
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +11 -3
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +90 -4
- package/dist/lifecycle.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +116 -5
- package/dist/loader.js.map +1 -1
- package/dist/payload-capture.d.ts.map +1 -1
- package/dist/payload-capture.js +27 -0
- package/dist/payload-capture.js.map +1 -1
- package/dist/payload-command.d.ts +3 -3
- package/dist/payload-command.d.ts.map +1 -1
- package/dist/payload-command.js.map +1 -1
- package/dist/preview.d.ts +2 -2
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +2 -3
- package/dist/preview.js.map +1 -1
- package/dist/profile-command.d.ts +12 -0
- package/dist/profile-command.d.ts.map +1 -0
- package/dist/profile-command.js +291 -0
- package/dist/profile-command.js.map +1 -0
- package/dist/profile-service.d.ts +103 -0
- package/dist/profile-service.d.ts.map +1 -0
- package/dist/profile-service.js +215 -0
- package/dist/profile-service.js.map +1 -0
- package/dist/runtime/profile-runtime.d.ts +13 -0
- package/dist/runtime/profile-runtime.d.ts.map +1 -0
- package/dist/runtime/profile-runtime.js +47 -0
- package/dist/runtime/profile-runtime.js.map +1 -0
- package/dist/runtime/prompt-stack-runtime.d.ts +22 -0
- package/dist/runtime/prompt-stack-runtime.d.ts.map +1 -0
- package/dist/runtime/prompt-stack-runtime.js +104 -0
- package/dist/runtime/prompt-stack-runtime.js.map +1 -0
- package/dist/runtime/subagent-runtime.d.ts +30 -0
- package/dist/runtime/subagent-runtime.d.ts.map +1 -0
- package/dist/runtime/subagent-runtime.js +114 -0
- package/dist/runtime/subagent-runtime.js.map +1 -0
- package/dist/runtime/tool-policy-runtime.d.ts +15 -0
- package/dist/runtime/tool-policy-runtime.d.ts.map +1 -0
- package/dist/runtime/tool-policy-runtime.js +170 -0
- package/dist/runtime/tool-policy-runtime.js.map +1 -0
- package/dist/runtime/web-editor-runtime.d.ts +9 -0
- package/dist/runtime/web-editor-runtime.d.ts.map +1 -0
- package/dist/runtime/web-editor-runtime.js +131 -0
- package/dist/runtime/web-editor-runtime.js.map +1 -0
- package/dist/runtime-state.d.ts +4 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +2 -0
- package/dist/runtime-state.js.map +1 -1
- package/dist/storage.d.ts +3 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +9 -0
- package/dist/storage.js.map +1 -1
- package/dist/subagent/backend-registry.d.ts +75 -0
- package/dist/subagent/backend-registry.d.ts.map +1 -0
- package/dist/subagent/backend-registry.js +463 -0
- package/dist/subagent/backend-registry.js.map +1 -0
- package/dist/subagent/canonical.d.ts +10 -0
- package/dist/subagent/canonical.d.ts.map +1 -0
- package/dist/subagent/canonical.js +52 -0
- package/dist/subagent/canonical.js.map +1 -0
- package/dist/subagent/context.d.ts +8 -0
- package/dist/subagent/context.d.ts.map +1 -0
- package/dist/subagent/context.js +125 -0
- package/dist/subagent/context.js.map +1 -0
- package/dist/subagent/contract.d.ts +11 -0
- package/dist/subagent/contract.d.ts.map +1 -0
- package/dist/subagent/contract.js +11 -0
- package/dist/subagent/contract.js.map +1 -0
- package/dist/subagent/diagnostics.d.ts +3 -0
- package/dist/subagent/diagnostics.d.ts.map +1 -0
- package/dist/subagent/diagnostics.js +5 -0
- package/dist/subagent/diagnostics.js.map +1 -0
- package/dist/subagent/index.d.ts +13 -0
- package/dist/subagent/index.d.ts.map +1 -0
- package/dist/subagent/index.js +13 -0
- package/dist/subagent/index.js.map +1 -0
- package/dist/subagent/pi-model-runtime.d.ts +8 -0
- package/dist/subagent/pi-model-runtime.d.ts.map +1 -0
- package/dist/subagent/pi-model-runtime.js +22 -0
- package/dist/subagent/pi-model-runtime.js.map +1 -0
- package/dist/subagent/pi-sdk-backend.d.ts +23 -0
- package/dist/subagent/pi-sdk-backend.d.ts.map +1 -0
- package/dist/subagent/pi-sdk-backend.js +383 -0
- package/dist/subagent/pi-sdk-backend.js.map +1 -0
- package/dist/subagent/pi-subprocess-backend.d.ts +72 -0
- package/dist/subagent/pi-subprocess-backend.d.ts.map +1 -0
- package/dist/subagent/pi-subprocess-backend.js +756 -0
- package/dist/subagent/pi-subprocess-backend.js.map +1 -0
- package/dist/subagent/plan.d.ts +14 -0
- package/dist/subagent/plan.d.ts.map +1 -0
- package/dist/subagent/plan.js +160 -0
- package/dist/subagent/plan.js.map +1 -0
- package/dist/subagent/preflight.d.ts +4 -0
- package/dist/subagent/preflight.d.ts.map +1 -0
- package/dist/subagent/preflight.js +108 -0
- package/dist/subagent/preflight.js.map +1 -0
- package/dist/subagent/request.d.ts +4 -0
- package/dist/subagent/request.d.ts.map +1 -0
- package/dist/subagent/request.js +122 -0
- package/dist/subagent/request.js.map +1 -0
- package/dist/subagent/response.d.ts +8 -0
- package/dist/subagent/response.d.ts.map +1 -0
- package/dist/subagent/response.js +155 -0
- package/dist/subagent/response.js.map +1 -0
- package/dist/subagent/subprocess-bridge.d.ts +21 -0
- package/dist/subagent/subprocess-bridge.d.ts.map +1 -0
- package/dist/subagent/subprocess-bridge.js +87 -0
- package/dist/subagent/subprocess-bridge.js.map +1 -0
- package/dist/subagent/subprocess-report.d.ts +4 -0
- package/dist/subagent/subprocess-report.d.ts.map +1 -0
- package/dist/subagent/subprocess-report.js +55 -0
- package/dist/subagent/subprocess-report.js.map +1 -0
- package/dist/subagent/tools.d.ts +4 -0
- package/dist/subagent/tools.d.ts.map +1 -0
- package/dist/subagent/tools.js +42 -0
- package/dist/subagent/tools.js.map +1 -0
- package/dist/subagent/types.d.ts +384 -0
- package/dist/subagent/types.d.ts.map +1 -0
- package/dist/subagent/types.js +3 -0
- package/dist/subagent/types.js.map +1 -0
- package/dist/subagent/validation.d.ts +35 -0
- package/dist/subagent/validation.d.ts.map +1 -0
- package/dist/subagent/validation.js +500 -0
- package/dist/subagent/validation.js.map +1 -0
- package/dist/subagent-command.d.ts +4 -0
- package/dist/subagent-command.d.ts.map +1 -0
- package/dist/subagent-command.js +153 -0
- package/dist/subagent-command.js.map +1 -0
- package/dist/subagent-contract.d.ts +8 -0
- package/dist/subagent-contract.d.ts.map +1 -0
- package/dist/subagent-contract.js +8 -0
- package/dist/subagent-contract.js.map +1 -0
- package/dist/subagent-host.d.ts +44 -0
- package/dist/subagent-host.d.ts.map +1 -0
- package/dist/subagent-host.js +291 -0
- package/dist/subagent-host.js.map +1 -0
- package/dist/subagent-profile-tool.d.ts +26 -0
- package/dist/subagent-profile-tool.d.ts.map +1 -0
- package/dist/subagent-profile-tool.js +93 -0
- package/dist/subagent-profile-tool.js.map +1 -0
- package/dist/subagent-tool.d.ts +50 -0
- package/dist/subagent-tool.d.ts.map +1 -0
- package/dist/subagent-tool.js +385 -0
- package/dist/subagent-tool.js.map +1 -0
- package/dist/web-editor/client/api.d.ts +9 -0
- package/dist/web-editor/client/api.d.ts.map +1 -0
- package/dist/web-editor/client/api.js +26 -0
- package/dist/web-editor/client/api.js.map +1 -0
- package/dist/web-editor/client/dom.d.ts +13 -0
- package/dist/web-editor/client/dom.d.ts.map +1 -0
- package/dist/web-editor/client/dom.js +30 -0
- package/dist/web-editor/client/dom.js.map +1 -0
- package/dist/web-editor/client/inspector.d.ts +22 -0
- package/dist/web-editor/client/inspector.d.ts.map +1 -0
- package/dist/web-editor/client/inspector.js +226 -0
- package/dist/web-editor/client/inspector.js.map +1 -0
- package/dist/web-editor/client/main.d.ts +2 -0
- package/dist/web-editor/client/main.d.ts.map +1 -0
- package/dist/web-editor/client/main.js +1468 -0
- package/dist/web-editor/client/main.js.map +1 -0
- package/dist/web-editor/client/policy-editor.d.ts +16 -0
- package/dist/web-editor/client/policy-editor.d.ts.map +1 -0
- package/dist/web-editor/client/policy-editor.js +330 -0
- package/dist/web-editor/client/policy-editor.js.map +1 -0
- package/dist/web-editor/client/regex-editor.d.ts +19 -0
- package/dist/web-editor/client/regex-editor.d.ts.map +1 -0
- package/dist/web-editor/client/regex-editor.js +281 -0
- package/dist/web-editor/client/regex-editor.js.map +1 -0
- package/dist/web-editor/client/types.d.ts +60 -0
- package/dist/web-editor/client/types.d.ts.map +1 -0
- package/dist/web-editor/client/types.js +2 -0
- package/dist/web-editor/client/types.js.map +1 -0
- package/dist/web-editor/client-script.d.ts +2 -0
- package/dist/web-editor/client-script.d.ts.map +1 -0
- package/dist/web-editor/client-script.generated.d.ts +2 -0
- package/dist/web-editor/client-script.generated.d.ts.map +1 -0
- package/dist/web-editor/client-script.generated.js +3 -0
- package/dist/web-editor/client-script.generated.js.map +1 -0
- package/dist/web-editor/client-script.js +2 -0
- package/dist/web-editor/client-script.js.map +1 -0
- package/dist/web-editor/page.d.ts.map +1 -1
- package/dist/web-editor/page.js +6 -3249
- package/dist/web-editor/page.js.map +1 -1
- package/dist/web-editor/styles.d.ts +2 -0
- package/dist/web-editor/styles.d.ts.map +1 -0
- package/dist/web-editor/styles.js +996 -0
- package/dist/web-editor/styles.js.map +1 -0
- package/dist/web-host.d.ts +3 -3
- package/dist/web-host.d.ts.map +1 -1
- package/dist/web-host.js +6 -0
- package/dist/web-host.js.map +1 -1
- package/package.json +42 -14
- package/src/compiler.ts +0 -578
- package/src/extension-registry.ts +0 -33
- package/src/forge-extensions.ts +0 -223
- package/src/index.ts +0 -445
- package/src/lifecycle.ts +0 -171
- package/src/loader.ts +0 -394
- package/src/macro-engine.ts +0 -358
- package/src/payload-capture.ts +0 -85
- package/src/payload-command.ts +0 -138
- package/src/policy.ts +0 -42
- package/src/preset-command.ts +0 -280
- package/src/preview.ts +0 -226
- package/src/regex.ts +0 -500
- package/src/render-helpers.ts +0 -169
- package/src/runtime-state.ts +0 -40
- package/src/sillytavern-importer/items.ts +0 -98
- package/src/sillytavern-importer/macros.ts +0 -159
- package/src/sillytavern-importer/prompt-order.ts +0 -54
- package/src/sillytavern-importer/regex.ts +0 -270
- package/src/sillytavern-importer/report.ts +0 -202
- package/src/sillytavern-importer/types.ts +0 -120
- package/src/sillytavern-importer.ts +0 -152
- package/src/slot-renderers.ts +0 -414
- package/src/stack-migration.ts +0 -159
- package/src/storage.ts +0 -45
- package/src/types.ts +0 -209
- package/src/web-editor/index.ts +0 -2
- package/src/web-editor/page.ts +0 -3330
- package/src/web-editor/server.ts +0 -294
- package/src/web-editor/types.ts +0 -98
- package/src/web-host.ts +0 -232
package/src/web-editor/server.ts
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
3
|
-
import type { Socket } from "node:net";
|
|
4
|
-
|
|
5
|
-
import { convertSillyTavernPreset } from "../sillytavern-importer.ts";
|
|
6
|
-
import type { PromptStack } from "../types.ts";
|
|
7
|
-
import { renderEditorHtml } from "./page.ts";
|
|
8
|
-
import type { WebEditorHost, WebEditorOperationResult, WebEditorServer, WebEditorServerOptions } from "./types.ts";
|
|
9
|
-
|
|
10
|
-
// Port 0 asks Node to bind any available localhost port.
|
|
11
|
-
export const DEFAULT_WEB_EDITOR_PORT = 0;
|
|
12
|
-
|
|
13
|
-
export async function startWebEditorServer(host: WebEditorHost, options: WebEditorServerOptions = {}): Promise<WebEditorServer> {
|
|
14
|
-
let currentHost = host;
|
|
15
|
-
const token = randomBytes(24).toString("base64url");
|
|
16
|
-
const sockets = new Set<Socket>();
|
|
17
|
-
const server = createServer((req, res) => {
|
|
18
|
-
void handleRequest(currentHost, token, req, res).catch((error) => {
|
|
19
|
-
sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
server.on("connection", (socket) => {
|
|
23
|
-
sockets.add(socket);
|
|
24
|
-
socket.on("close", () => sockets.delete(socket));
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
await new Promise<void>((resolve, reject) => {
|
|
28
|
-
server.once("error", reject);
|
|
29
|
-
server.listen(options.port ?? DEFAULT_WEB_EDITOR_PORT, "127.0.0.1", () => {
|
|
30
|
-
server.off("error", reject);
|
|
31
|
-
resolve();
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const address = server.address();
|
|
36
|
-
if (!address || typeof address === "string") {
|
|
37
|
-
await closeServer(server, sockets);
|
|
38
|
-
throw new Error("Failed to start pi-forge editor server.");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const url = `http://127.0.0.1:${address.port}/?token=${encodeURIComponent(token)}`;
|
|
42
|
-
return {
|
|
43
|
-
url,
|
|
44
|
-
port: address.port,
|
|
45
|
-
updateHost: (nextHost) => {
|
|
46
|
-
currentHost = nextHost;
|
|
47
|
-
},
|
|
48
|
-
close: () => closeServer(server, sockets),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function handleRequest(host: WebEditorHost, token: string, req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
53
|
-
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
54
|
-
|
|
55
|
-
if (url.pathname === "/" && req.method === "GET") {
|
|
56
|
-
if (url.searchParams.get("token") !== token) {
|
|
57
|
-
sendText(res, 403, "Invalid pi-forge editor token.");
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
sendHtml(res, renderEditorHtml());
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!url.pathname.startsWith("/api/")) {
|
|
65
|
-
sendText(res, 404, "Not found.");
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!hasValidToken(req, url, token)) {
|
|
70
|
-
sendJson(res, 403, { error: "Invalid pi-forge editor token." });
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const parts = url.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part));
|
|
75
|
-
|
|
76
|
-
if (req.method === "GET" && parts[1] === "stacks" && parts.length === 2) {
|
|
77
|
-
sendJson(res, 200, { stacks: host.listStacks(), cwd: host.cwd });
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (req.method === "GET" && parts[1] === "resources" && parts.length === 2) {
|
|
82
|
-
sendJson(res, 200, host.listResources());
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (req.method === "POST" && parts[1] === "stacks" && parts.length === 2) {
|
|
87
|
-
const body = await readJsonBody(req);
|
|
88
|
-
const parsed = readStackPayload(body);
|
|
89
|
-
if (!parsed.ok) {
|
|
90
|
-
sendJson(res, 400, { error: parsed.error });
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const options = isPlainObject(body)
|
|
94
|
-
? { activate: body.activate === true, overwrite: body.overwrite === true }
|
|
95
|
-
: {};
|
|
96
|
-
const result = await host.createStack(parsed.stack, options);
|
|
97
|
-
if (result.ok && parsed.importFormat) {
|
|
98
|
-
sendJson(res, 200, { ...result, importFormat: parsed.importFormat, importReport: parsed.importReport });
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
sendOperation(res, result);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (req.method === "GET" && parts[1] === "stacks" && parts.length === 3) {
|
|
106
|
-
const loaded = host.getStack(parts[2]!);
|
|
107
|
-
if (!loaded) {
|
|
108
|
-
sendJson(res, 404, { error: `Unknown prompt stack: ${parts[2]}` });
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
sendJson(res, 200, loaded);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (req.method === "PUT" && parts[1] === "stacks" && parts.length === 3) {
|
|
116
|
-
const body = await readJsonBody(req);
|
|
117
|
-
const parsed = readStackPayload(body);
|
|
118
|
-
if (!parsed.ok) {
|
|
119
|
-
sendJson(res, 400, { error: parsed.error });
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
sendOperation(res, await host.saveStack(parts[2]!, parsed.stack));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (req.method === "DELETE" && parts[1] === "stacks" && parts.length === 3) {
|
|
127
|
-
sendOperation(res, await host.deleteStack(parts[2]!));
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (req.method === "POST" && parts[1] === "stacks" && parts.length === 4 && parts[3] === "validate") {
|
|
132
|
-
const body = await readJsonBody(req);
|
|
133
|
-
const parsed = readStackPayload(body);
|
|
134
|
-
if (!parsed.ok) {
|
|
135
|
-
sendJson(res, 400, { error: parsed.error });
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
sendJson(res, 200, { diagnostics: host.validateStack(parsed.stack) });
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (req.method === "POST" && parts[1] === "stacks" && parts.length === 4 && parts[3] === "preview") {
|
|
143
|
-
const body = await readJsonBody(req);
|
|
144
|
-
const parsed = readStackPayload(body);
|
|
145
|
-
if (!parsed.ok) {
|
|
146
|
-
sendJson(res, 400, { error: parsed.error });
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
sendOperation(res, host.previewStack(parts[2]!, parsed.stack));
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (req.method === "GET" && parts[1] === "payload" && parts.length === 2) {
|
|
154
|
-
sendOperation(res, host.getPayload());
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (req.method === "POST" && parts[1] === "payload" && parts.length === 3 && parts[2] === "arm") {
|
|
159
|
-
const body = await readJsonBody(req);
|
|
160
|
-
const savePath = isPlainObject(body) && typeof body.savePath === "string" && body.savePath.trim() ? body.savePath.trim() : undefined;
|
|
161
|
-
sendOperation(res, host.armPayload(savePath));
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (req.method === "DELETE" && parts[1] === "payload" && parts.length === 2) {
|
|
166
|
-
sendOperation(res, host.clearPayload());
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (req.method === "POST" && parts[1] === "stacks" && parts.length === 4 && parts[3] === "activate") {
|
|
171
|
-
sendOperation(res, host.activateStack(parts[2]!));
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (req.method === "POST" && parts[1] === "disable" && parts.length === 2) {
|
|
176
|
-
sendOperation(res, host.disableStacks());
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (req.method === "POST" && parts[1] === "reload" && parts.length === 2) {
|
|
181
|
-
sendOperation(res, await host.reloadStacks());
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
sendJson(res, 404, { error: "Unknown pi-forge editor API route." });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function hasValidToken(req: IncomingMessage, url: URL, token: string): boolean {
|
|
189
|
-
const header = req.headers["x-pi-forge-token"];
|
|
190
|
-
return header === token || url.searchParams.get("token") === token;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function readJsonBody(req: IncomingMessage): Promise<unknown> {
|
|
194
|
-
const chunks: Buffer[] = [];
|
|
195
|
-
let size = 0;
|
|
196
|
-
const maxBytes = 2_000_000;
|
|
197
|
-
|
|
198
|
-
for await (const chunk of req) {
|
|
199
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
200
|
-
size += buffer.length;
|
|
201
|
-
if (size > maxBytes) throw new Error("Request body is too large.");
|
|
202
|
-
chunks.push(buffer);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const text = Buffer.concat(chunks).toString("utf8");
|
|
206
|
-
return text.trim() ? JSON.parse(text) : {};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function readStackPayload(body: unknown): { ok: true; stack: PromptStack; importFormat?: "sillytavern"; importReport?: string } | { ok: false; error: string } {
|
|
210
|
-
const rawStack = isPlainObject(body) && "stack" in body ? body.stack : body;
|
|
211
|
-
if (!isPlainObject(rawStack)) return { ok: false, error: "Stack payload must be a JSON object." };
|
|
212
|
-
|
|
213
|
-
if (isSillyTavernPresetPayload(rawStack)) {
|
|
214
|
-
const sourceName = isPlainObject(body) && typeof body.sourceName === "string" ? body.sourceName : undefined;
|
|
215
|
-
const characterId = readCharacterId(body);
|
|
216
|
-
const result = convertSillyTavernPreset(rawStack, { sourceName, characterId });
|
|
217
|
-
if ("error" in result) return { ok: false, error: `SillyTavern import error: ${result.error}` };
|
|
218
|
-
return { ok: true, stack: result.stack, importFormat: "sillytavern", importReport: result.report };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (typeof rawStack.id !== "string" || !rawStack.id.trim()) return { ok: false, error: "Stack id must be a non-empty string." };
|
|
222
|
-
if (!Array.isArray(rawStack.items)) return { ok: false, error: "Stack items must be an array." };
|
|
223
|
-
|
|
224
|
-
for (const [index, item] of rawStack.items.entries()) {
|
|
225
|
-
if (!isPlainObject(item)) return { ok: false, error: `Item ${index + 1} must be an object.` };
|
|
226
|
-
if (item.kind !== "block" && item.kind !== "slot") return { ok: false, error: `Item ${index + 1} kind must be block or slot.` };
|
|
227
|
-
if (typeof item.id !== "string" || !item.id.trim()) return { ok: false, error: `Item ${index + 1} id must be a non-empty string.` };
|
|
228
|
-
if (item.kind === "block" && typeof item.content !== "string") return { ok: false, error: `Block item ${item.id} content must be a string.` };
|
|
229
|
-
if (item.kind === "slot" && typeof item.slot !== "string") return { ok: false, error: `Slot item ${item.id} slot must be a string.` };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return { ok: true, stack: rawStack as unknown as PromptStack };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function isSillyTavernPresetPayload(value: Record<string, unknown>): boolean {
|
|
236
|
-
return Array.isArray(value.prompts) && !Array.isArray(value.items);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function readCharacterId(body: unknown): number | undefined {
|
|
240
|
-
if (!isPlainObject(body)) return undefined;
|
|
241
|
-
const raw = body.characterId;
|
|
242
|
-
if (typeof raw === "number" && Number.isInteger(raw)) return raw;
|
|
243
|
-
if (typeof raw === "string" && raw.trim()) {
|
|
244
|
-
const parsed = Number(raw.trim());
|
|
245
|
-
if (Number.isInteger(parsed)) return parsed;
|
|
246
|
-
}
|
|
247
|
-
return undefined;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function sendOperation<T>(res: ServerResponse, result: WebEditorOperationResult<T>): void {
|
|
251
|
-
if (!result.ok) {
|
|
252
|
-
sendJson(res, result.status ?? 400, { error: result.error });
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
sendJson(res, 200, result);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function sendHtml(res: ServerResponse, html: string): void {
|
|
259
|
-
res.writeHead(200, {
|
|
260
|
-
"content-type": "text/html; charset=utf-8",
|
|
261
|
-
"cache-control": "no-store",
|
|
262
|
-
"connection": "close",
|
|
263
|
-
});
|
|
264
|
-
res.end(html);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function sendText(res: ServerResponse, status: number, text: string): void {
|
|
268
|
-
res.writeHead(status, {
|
|
269
|
-
"content-type": "text/plain; charset=utf-8",
|
|
270
|
-
"cache-control": "no-store",
|
|
271
|
-
"connection": "close",
|
|
272
|
-
});
|
|
273
|
-
res.end(text);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function sendJson(res: ServerResponse, status: number, value: unknown): void {
|
|
277
|
-
res.writeHead(status, {
|
|
278
|
-
"content-type": "application/json; charset=utf-8",
|
|
279
|
-
"cache-control": "no-store",
|
|
280
|
-
"connection": "close",
|
|
281
|
-
});
|
|
282
|
-
res.end(JSON.stringify(value));
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function closeServer(server: Server, sockets: Set<Socket>): Promise<void> {
|
|
286
|
-
return new Promise((resolve, reject) => {
|
|
287
|
-
server.close((error) => error ? reject(error) : resolve());
|
|
288
|
-
for (const socket of sockets) socket.destroy();
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
293
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
294
|
-
}
|
package/src/web-editor/types.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import type { PromptStack, PromptStackDiagnostic } from "../types.ts";
|
|
2
|
-
|
|
3
|
-
export interface WebEditorStackSummary {
|
|
4
|
-
id: string;
|
|
5
|
-
name?: string;
|
|
6
|
-
filePath: string;
|
|
7
|
-
active: boolean;
|
|
8
|
-
autoActivate?: boolean;
|
|
9
|
-
mode?: string;
|
|
10
|
-
itemCount: number;
|
|
11
|
-
errors: number;
|
|
12
|
-
warnings: number;
|
|
13
|
-
diagnostics: PromptStackDiagnostic[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface WebEditorHost {
|
|
17
|
-
cwd: string;
|
|
18
|
-
listStacks(): WebEditorStackSummary[];
|
|
19
|
-
listResources(): WebEditorPolicyResources;
|
|
20
|
-
getStack(id: string): { stack: PromptStack; filePath: string; diagnostics: PromptStackDiagnostic[] } | undefined;
|
|
21
|
-
createStack(stack: PromptStack, options: WebEditorCreateStackOptions): Promise<WebEditorOperationResult<{ stack: WebEditorStackSummary; stacks: WebEditorStackSummary[] }>>;
|
|
22
|
-
saveStack(id: string, stack: PromptStack): Promise<WebEditorOperationResult<{ stack: WebEditorStackSummary; stacks: WebEditorStackSummary[] }>>;
|
|
23
|
-
deleteStack(id: string): Promise<WebEditorOperationResult<{ activeId?: string; stacks: WebEditorStackSummary[] }>>;
|
|
24
|
-
validateStack(stack: PromptStack): PromptStackDiagnostic[];
|
|
25
|
-
previewStack(id: string, stack: PromptStack): WebEditorOperationResult<{ text: string; preview?: WebEditorPreview; diagnostics: PromptStackDiagnostic[] }>;
|
|
26
|
-
getPayload(): WebEditorOperationResult<WebEditorPayloadSnapshot>;
|
|
27
|
-
armPayload(savePath?: string): WebEditorOperationResult<WebEditorPayloadSnapshot>;
|
|
28
|
-
clearPayload(): WebEditorOperationResult<WebEditorPayloadSnapshot>;
|
|
29
|
-
activateStack(id: string): WebEditorOperationResult<{ activeId?: string; stacks: WebEditorStackSummary[] }>;
|
|
30
|
-
disableStacks(): WebEditorOperationResult<{ activeId?: string; stacks: WebEditorStackSummary[] }>;
|
|
31
|
-
reloadStacks(): Promise<WebEditorOperationResult<{ activeId?: string; stacks: WebEditorStackSummary[] }>>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface WebEditorPreviewSection {
|
|
35
|
-
id: string;
|
|
36
|
-
title: string;
|
|
37
|
-
role?: string;
|
|
38
|
-
content: string;
|
|
39
|
-
chars: number;
|
|
40
|
-
approxTokens: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface WebEditorPreview {
|
|
44
|
-
stackId: string;
|
|
45
|
-
generatedAt: string;
|
|
46
|
-
system: WebEditorPreviewSection;
|
|
47
|
-
messages: WebEditorPreviewSection[];
|
|
48
|
-
totalChars: number;
|
|
49
|
-
approxTokens: number;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface WebEditorPayloadCapture {
|
|
53
|
-
capturedAt: string;
|
|
54
|
-
stackId?: string;
|
|
55
|
-
savePath?: string;
|
|
56
|
-
payload?: unknown;
|
|
57
|
-
text: string;
|
|
58
|
-
chars: number;
|
|
59
|
-
approxTokens: number;
|
|
60
|
-
truncated: boolean;
|
|
61
|
-
error?: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type WebEditorPayloadSnapshot =
|
|
65
|
-
| { status: "idle" }
|
|
66
|
-
| { status: "armed"; armedAt?: string; savePath?: string }
|
|
67
|
-
| { status: "captured"; capture: WebEditorPayloadCapture };
|
|
68
|
-
|
|
69
|
-
export interface WebEditorCreateStackOptions {
|
|
70
|
-
activate?: boolean;
|
|
71
|
-
overwrite?: boolean;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface WebEditorPolicyResource {
|
|
75
|
-
name: string;
|
|
76
|
-
description?: string;
|
|
77
|
-
source?: string;
|
|
78
|
-
active?: boolean;
|
|
79
|
-
hidden?: boolean;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface WebEditorPolicyResources {
|
|
83
|
-
tools: WebEditorPolicyResource[];
|
|
84
|
-
skills: WebEditorPolicyResource[];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export type WebEditorOperationResult<T> = ({ ok: true } & T) | { ok: false; status?: number; error: string };
|
|
88
|
-
|
|
89
|
-
export interface WebEditorServer {
|
|
90
|
-
url: string;
|
|
91
|
-
port: number;
|
|
92
|
-
updateHost(host: WebEditorHost): void;
|
|
93
|
-
close(): Promise<void>;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface WebEditorServerOptions {
|
|
97
|
-
port?: number;
|
|
98
|
-
}
|
package/src/web-host.ts
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import {
|
|
5
|
-
isInsidePromptStackStorage,
|
|
6
|
-
promptStackPath,
|
|
7
|
-
validatePromptStack,
|
|
8
|
-
} from "./loader.ts";
|
|
9
|
-
import type { LoadedPromptStack, PromptStack, PromptStackDiagnostic } from "./types.ts";
|
|
10
|
-
import type {
|
|
11
|
-
WebEditorCreateStackOptions,
|
|
12
|
-
WebEditorHost,
|
|
13
|
-
WebEditorOperationResult,
|
|
14
|
-
WebEditorPayloadSnapshot,
|
|
15
|
-
WebEditorPolicyResources,
|
|
16
|
-
WebEditorPreview,
|
|
17
|
-
WebEditorStackSummary,
|
|
18
|
-
} from "./web-editor/index.ts";
|
|
19
|
-
|
|
20
|
-
export interface WebHostRuntime {
|
|
21
|
-
getStacks(): LoadedPromptStack[];
|
|
22
|
-
getActive(): LoadedPromptStack | undefined;
|
|
23
|
-
getActiveId(): string | undefined;
|
|
24
|
-
getSelectedActiveId(): string | undefined;
|
|
25
|
-
setActive(id: string | undefined): boolean;
|
|
26
|
-
reloadStacks(preferredId?: string): Promise<void>;
|
|
27
|
-
buildPreview(target: LoadedPromptStack): {
|
|
28
|
-
text: string;
|
|
29
|
-
preview: WebEditorPreview;
|
|
30
|
-
diagnostics: PromptStackDiagnostic[];
|
|
31
|
-
};
|
|
32
|
-
getPolicyResources(): WebEditorPolicyResources;
|
|
33
|
-
getPayload(): WebEditorOperationResult<WebEditorPayloadSnapshot>;
|
|
34
|
-
armPayload(savePath?: string): WebEditorOperationResult<WebEditorPayloadSnapshot>;
|
|
35
|
-
clearPayload(): WebEditorOperationResult<WebEditorPayloadSnapshot>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function createWebEditorHost(ctx: ExtensionCommandContext, runtime: WebHostRuntime): WebEditorHost {
|
|
39
|
-
return {
|
|
40
|
-
cwd: ctx.cwd,
|
|
41
|
-
listStacks: () => stackSummaries(runtime.getStacks(), runtime.getActive()),
|
|
42
|
-
listResources: () => runtime.getPolicyResources(),
|
|
43
|
-
getStack: (id) => {
|
|
44
|
-
const loaded = runtime.getStacks().find((candidate) => candidate.stack.id === id);
|
|
45
|
-
return loaded ? { stack: loaded.stack, filePath: loaded.filePath, diagnostics: loaded.diagnostics } : undefined;
|
|
46
|
-
},
|
|
47
|
-
createStack: (stack, options) => createStackFile(ctx, runtime, stack, options),
|
|
48
|
-
saveStack: (id, stack) => saveStackFile(ctx, runtime, id, stack),
|
|
49
|
-
deleteStack: (id) => deleteStackFile(ctx, runtime, id),
|
|
50
|
-
validateStack: (stack) => validatePromptStack(stack),
|
|
51
|
-
previewStack: (id, stack) => {
|
|
52
|
-
const target = runtime.getStacks().find((candidate) => candidate.stack.id === id);
|
|
53
|
-
if (!target) return { ok: false, status: 404, error: `Unknown prompt stack: ${id}` };
|
|
54
|
-
const diagnostics = validatePromptStack(stack);
|
|
55
|
-
const preview = runtime.buildPreview({ stack, filePath: target.filePath, diagnostics });
|
|
56
|
-
return { ok: true, text: preview.text, preview: preview.preview, diagnostics: preview.diagnostics };
|
|
57
|
-
},
|
|
58
|
-
getPayload: () => runtime.getPayload(),
|
|
59
|
-
armPayload: (savePath) => runtime.armPayload(savePath),
|
|
60
|
-
clearPayload: () => runtime.clearPayload(),
|
|
61
|
-
activateStack: (id) => {
|
|
62
|
-
if (!runtime.setActive(id)) return { ok: false, status: 404, error: `Unknown prompt stack: ${id}` };
|
|
63
|
-
return { ok: true, activeId: runtime.getActiveId(), stacks: stackSummaries(runtime.getStacks(), runtime.getActive()) };
|
|
64
|
-
},
|
|
65
|
-
disableStacks: () => {
|
|
66
|
-
runtime.setActive("none");
|
|
67
|
-
return { ok: true, activeId: runtime.getActiveId(), stacks: stackSummaries(runtime.getStacks(), runtime.getActive()) };
|
|
68
|
-
},
|
|
69
|
-
reloadStacks: async () => {
|
|
70
|
-
await runtime.reloadStacks(runtime.getSelectedActiveId());
|
|
71
|
-
return { ok: true, activeId: runtime.getActiveId(), stacks: stackSummaries(runtime.getStacks(), runtime.getActive()) };
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function stackSummary(loaded: LoadedPromptStack, active: LoadedPromptStack | undefined): WebEditorStackSummary {
|
|
77
|
-
const errors = loaded.diagnostics.filter((d) => d.level === "error").length;
|
|
78
|
-
const warnings = loaded.diagnostics.filter((d) => d.level === "warning").length;
|
|
79
|
-
return {
|
|
80
|
-
id: loaded.stack.id,
|
|
81
|
-
name: loaded.stack.name,
|
|
82
|
-
filePath: loaded.filePath,
|
|
83
|
-
active: loaded === active,
|
|
84
|
-
autoActivate: loaded.stack.autoActivate,
|
|
85
|
-
mode: loaded.stack.mode ?? "replace",
|
|
86
|
-
itemCount: loaded.stack.items.length,
|
|
87
|
-
errors,
|
|
88
|
-
warnings,
|
|
89
|
-
diagnostics: loaded.diagnostics,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function stackSummaries(stacks: LoadedPromptStack[], active: LoadedPromptStack | undefined): WebEditorStackSummary[] {
|
|
94
|
-
return stacks.map((loaded) => stackSummary(loaded, active));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function loadWebEditorSettings(ctx: ExtensionCommandContext): { preferredPort?: number; configPath: string; warnings: string[] } {
|
|
98
|
-
const configPath = join(ctx.cwd, ".pi", "forge", "config.json");
|
|
99
|
-
if (!ctx.isProjectTrusted() || !existsSync(configPath)) {
|
|
100
|
-
return { configPath, warnings: [] };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let raw: unknown;
|
|
104
|
-
try {
|
|
105
|
-
raw = JSON.parse(readFileSync(configPath, "utf8"));
|
|
106
|
-
} catch (error) {
|
|
107
|
-
return {
|
|
108
|
-
configPath,
|
|
109
|
-
warnings: [`pi-forge: failed to read ${configPath}; using an available editor port. ${error instanceof Error ? error.message : String(error)}`],
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!isPlainObject(raw)) {
|
|
114
|
-
return {
|
|
115
|
-
configPath,
|
|
116
|
-
warnings: [`pi-forge: ${configPath} must be a JSON object; using an available editor port.`],
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const webEditorConfig = isPlainObject(raw.webEditor) ? raw.webEditor : undefined;
|
|
121
|
-
const rawPort = webEditorConfig?.port ?? raw.webEditorPort;
|
|
122
|
-
if (rawPort === undefined) return { configPath, warnings: [] };
|
|
123
|
-
|
|
124
|
-
if (typeof rawPort === "number" && Number.isInteger(rawPort) && rawPort >= 1 && rawPort <= 65535) {
|
|
125
|
-
return { preferredPort: rawPort, configPath, warnings: [] };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
configPath,
|
|
130
|
-
warnings: [`pi-forge: ${configPath} webEditor.port must be an integer from 1 to 65535; using an available editor port.`],
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function saveStackFile(
|
|
135
|
-
ctx: ExtensionCommandContext,
|
|
136
|
-
runtime: WebHostRuntime,
|
|
137
|
-
id: string,
|
|
138
|
-
stack: PromptStack,
|
|
139
|
-
): Promise<WebEditorOperationResult<{ stack: WebEditorStackSummary; stacks: WebEditorStackSummary[] }>> {
|
|
140
|
-
if (!ctx.isProjectTrusted()) {
|
|
141
|
-
return { ok: false, status: 403, error: "Project is not trusted; refusing to save prompt stacks." };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const target = runtime.getStacks().find((candidate) => candidate.stack.id === id);
|
|
145
|
-
if (!target) return { ok: false, status: 404, error: `Unknown prompt stack: ${id}` };
|
|
146
|
-
if (!isInsidePromptStackStorage(ctx.cwd, target.filePath)) {
|
|
147
|
-
return { ok: false, status: 403, error: "Refusing to save outside prompt-stack storage." };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
writeFileSync(target.filePath, `${JSON.stringify(stack, null, 2)}\n`, "utf8");
|
|
151
|
-
const preferredId = runtime.getActive()?.stack.id === id ? stack.id : runtime.getSelectedActiveId();
|
|
152
|
-
await runtime.reloadStacks(preferredId);
|
|
153
|
-
const saved = runtime.getStacks().find((candidate) => candidate.stack.id === stack.id) ?? runtime.getStacks().find((candidate) => candidate.filePath === target.filePath);
|
|
154
|
-
if (!saved) return { ok: false, status: 500, error: "Saved stack could not be reloaded." };
|
|
155
|
-
return { ok: true, stack: stackSummary(saved, runtime.getActive()), stacks: stackSummaries(runtime.getStacks(), runtime.getActive()) };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async function createStackFile(
|
|
159
|
-
ctx: ExtensionCommandContext,
|
|
160
|
-
runtime: WebHostRuntime,
|
|
161
|
-
stack: PromptStack,
|
|
162
|
-
options: WebEditorCreateStackOptions,
|
|
163
|
-
): Promise<WebEditorOperationResult<{ stack: WebEditorStackSummary; stacks: WebEditorStackSummary[] }>> {
|
|
164
|
-
if (!ctx.isProjectTrusted()) {
|
|
165
|
-
return { ok: false, status: 403, error: "Project is not trusted; refusing to create prompt stacks." };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const idError = validateWebStackId(stack.id);
|
|
169
|
-
if (idError) return { ok: false, status: 400, error: idError };
|
|
170
|
-
|
|
171
|
-
const existingById = runtime.getStacks().find((candidate) => candidate.stack.id === stack.id);
|
|
172
|
-
if (existingById && !options.overwrite) {
|
|
173
|
-
return { ok: false, status: 409, error: `Prompt stack already exists: ${stack.id}` };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const targetPath = existingById && options.overwrite ? existingById.filePath : promptStackPath(ctx.cwd, stack.id);
|
|
177
|
-
if (!isInsidePromptStackStorage(ctx.cwd, targetPath)) {
|
|
178
|
-
return { ok: false, status: 403, error: "Refusing to create outside prompt-stack storage." };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (!existingById && existsSync(targetPath) && !options.overwrite) {
|
|
182
|
-
return { ok: false, status: 409, error: `Prompt stack already exists: ${stack.id}` };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const previousSelection = runtime.getSelectedActiveId();
|
|
186
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
187
|
-
writeFileSync(targetPath, `${JSON.stringify(stack, null, 2)}\n`, "utf8");
|
|
188
|
-
await runtime.reloadStacks(options.activate ? stack.id : (previousSelection ?? "none"));
|
|
189
|
-
if (options.activate) runtime.setActive(stack.id);
|
|
190
|
-
|
|
191
|
-
const created = runtime.getStacks().find((candidate) => candidate.stack.id === stack.id);
|
|
192
|
-
if (!created) return { ok: false, status: 500, error: "Created stack could not be reloaded." };
|
|
193
|
-
return { ok: true, stack: stackSummary(created, runtime.getActive()), stacks: stackSummaries(runtime.getStacks(), runtime.getActive()) };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function deleteStackFile(
|
|
197
|
-
ctx: ExtensionCommandContext,
|
|
198
|
-
runtime: WebHostRuntime,
|
|
199
|
-
id: string,
|
|
200
|
-
): Promise<WebEditorOperationResult<{ activeId?: string; stacks: WebEditorStackSummary[] }>> {
|
|
201
|
-
if (!ctx.isProjectTrusted()) {
|
|
202
|
-
return { ok: false, status: 403, error: "Project is not trusted; refusing to delete prompt stacks." };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const target = runtime.getStacks().find((candidate) => candidate.stack.id === id);
|
|
206
|
-
if (!target) return { ok: false, status: 404, error: `Unknown prompt stack: ${id}` };
|
|
207
|
-
if (!isInsidePromptStackStorage(ctx.cwd, target.filePath)) {
|
|
208
|
-
return { ok: false, status: 403, error: "Refusing to delete outside prompt-stack storage." };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const wasActive = runtime.getActive()?.stack.id === id;
|
|
212
|
-
unlinkSync(target.filePath);
|
|
213
|
-
if (wasActive) {
|
|
214
|
-
runtime.setActive("none");
|
|
215
|
-
await runtime.reloadStacks("none");
|
|
216
|
-
} else {
|
|
217
|
-
await runtime.reloadStacks(runtime.getSelectedActiveId());
|
|
218
|
-
}
|
|
219
|
-
return { ok: true, activeId: runtime.getActiveId(), stacks: stackSummaries(runtime.getStacks(), runtime.getActive()) };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function validateWebStackId(id: string): string | undefined {
|
|
223
|
-
if (!id.trim()) return "Stack id must not be empty.";
|
|
224
|
-
if (!/^[A-Za-z0-9_-]+$/.test(id)) {
|
|
225
|
-
return "Stack id may only contain letters, numbers, underscore, and dash.";
|
|
226
|
-
}
|
|
227
|
-
return undefined;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
231
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
232
|
-
}
|