pipane 0.1.5 → 0.1.7
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/README.md +45 -1
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/bin/pipane-rendezvous.js +21 -0
- package/bin/pipane.js +21 -1
- package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/client/assets/__vite-browser-external-DtLbhLGx.js +1 -0
- package/dist/client/assets/app-runtime-Rw-O-AwW.js +1 -0
- package/dist/client/assets/backend-landing-page-B7SbyIXQ.js +1 -0
- package/dist/client/assets/index-DwbBcYUf.js +2 -0
- package/dist/client/assets/index-iblQYBAl.css +1 -0
- package/dist/client/assets/main-LGItV9Aj.js +2807 -0
- package/dist/client/assets/pairing-page-D-WxlWZR.js +1 -0
- package/dist/client/assets/remote-backend-manager-BWuGh30I.js +1 -0
- package/dist/client/assets/rendezvous-trust-api-C2Zob5i4.js +4 -0
- package/dist/client/assets/webrtc-frame-transport-BijNnA7N.js +1 -0
- package/dist/client/assets/ws-agent-adapter-BRysf-Y7.js +6 -0
- package/dist/client/index.html +2 -2
- package/dist/server/rendezvous/rendezvous-hub.js +426 -0
- package/dist/server/rendezvous/server.js +247 -0
- package/dist/server/rendezvous/trust-store.js +432 -0
- package/dist/server/server/auth-guard.js +61 -0
- package/dist/server/server/backend-connection-authorizer.js +29 -0
- package/dist/server/server/backend-identity.js +167 -0
- package/dist/server/server/backend-protocol-handler.js +132 -0
- package/dist/server/server/backend-trust-store.js +157 -0
- package/dist/server/server/backend-webrtc.js +239 -0
- package/dist/server/server/bundled-extensions.js +6 -0
- package/dist/server/server/conversation-file-access.js +97 -0
- package/dist/server/server/extension-status.js +31 -0
- package/dist/server/server/frame-connection.js +48 -0
- package/dist/server/server/frame-router.js +45 -0
- package/dist/server/server/ice-servers.js +24 -0
- package/dist/server/server/local-backend-api.js +389 -0
- package/dist/server/server/local-settings.js +28 -0
- package/dist/server/server/pi-rpc-protocol.js +407 -0
- package/dist/server/server/pi-runtime.js +1 -1
- package/dist/server/server/process-pool.js +179 -136
- package/dist/server/server/rendezvous-client.js +282 -0
- package/dist/server/server/rest-api.js +121 -231
- package/dist/server/server/server.js +188 -134
- package/dist/server/server/session-actor.js +202 -0
- package/dist/server/server/session-index.js +111 -18
- package/dist/server/server/session-jsonl.js +128 -8
- package/dist/server/server/session-path.js +145 -0
- package/dist/server/server/session-registry.js +67 -0
- package/dist/server/server/update-api.js +28 -0
- package/dist/server/server/update-check.js +33 -13
- package/dist/server/server/update-manager.js +231 -0
- package/dist/server/server/worktree-name.js +124 -0
- package/dist/server/server/ws-handler.js +784 -478
- package/dist/server/shared/backend-api.js +1 -0
- package/dist/server/shared/backend-protocol.js +164 -0
- package/dist/server/shared/jsonl-sync.js +41 -0
- package/dist/server/shared/node-trust-crypto.js +61 -0
- package/dist/server/shared/rendezvous-protocol.js +243 -0
- package/dist/server/shared/rpc-timeouts.js +4 -0
- package/dist/server/shared/thinking-levels.js +94 -0
- package/dist/server/shared/tool-runtime.js +1 -0
- package/dist/server/shared/trust-protocol.js +97 -0
- package/dist/server/shared/updates.js +4 -0
- package/dist/server/shared/ws-protocol.js +473 -0
- package/docs/protocol.md +127 -0
- package/extensions/canvas.ts +1 -1
- package/package.json +42 -17
- package/dist/client/assets/_node-stub_node_crypto-C_7epg3G.js +0 -1
- package/dist/client/assets/_node-stub_node_fs-B-VOzeXW.js +0 -1
- package/dist/client/assets/_node-stub_node_http-CROXaVGJ.js +0 -1
- package/dist/client/assets/_node-stub_node_os-TTKJha15.js +0 -1
- package/dist/client/assets/_node-stub_node_path-DJCJiKv7.js +0 -1
- package/dist/client/assets/_node-stub_stream-DDexIdn_.js +0 -1
- package/dist/client/assets/index-BtDAXAo1.css +0 -1
- package/dist/client/assets/index-CAQqlK61.js +0 -131
- package/dist/client/assets/index-CJEeBmBj.js +0 -3
- package/dist/client/assets/index-DUEhKmPQ.js +0 -1
- package/dist/client/assets/index-XEVKscP-.js +0 -4443
- package/dist/client/assets/index-woI4agCy.js +0 -45
- package/dist/server/server/attached-session.js +0 -209
- package/dist/server/server/load-trace-store.js +0 -48
- package/dist/server/server/session-lifecycle.js +0 -155
- package/patches/@mariozechner+pi-web-ui+0.55.3.patch +0 -3437
|
@@ -1,289 +1,179 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Stateless handlers that read session data from JSONL files on disk.
|
|
5
|
-
*/
|
|
6
|
-
import { existsSync, readFileSync, readdirSync, watchFile } from "node:fs";
|
|
7
|
-
import { unlink } from "node:fs/promises";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { buildSessionContext, parseSessionEntries } from "@mariozechner/pi-coding-agent";
|
|
10
|
-
import { SessionIndex } from "./session-index.js";
|
|
11
|
-
import { LocalSettingsStore } from "./local-settings.js";
|
|
12
|
-
let localSettingsStore;
|
|
13
|
-
let sessionIndex;
|
|
14
|
-
let localSettingsWatcherStarted = false;
|
|
15
|
-
function startLocalSettingsWatcher(onLocalSettingsReloaded) {
|
|
16
|
-
if (localSettingsWatcherStarted)
|
|
17
|
-
return;
|
|
18
|
-
localSettingsWatcherStarted = true;
|
|
19
|
-
let debounceTimer = null;
|
|
20
|
-
watchFile(localSettingsStore.path, { interval: 500 }, () => {
|
|
21
|
-
if (debounceTimer)
|
|
22
|
-
clearTimeout(debounceTimer);
|
|
23
|
-
debounceTimer = setTimeout(async () => {
|
|
24
|
-
const changed = localSettingsStore.reloadFromDiskIfValid();
|
|
25
|
-
if (!changed)
|
|
26
|
-
return;
|
|
27
|
-
await sessionIndex.invalidateAll();
|
|
28
|
-
onLocalSettingsReloaded?.();
|
|
29
|
-
}, 150);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
async function readJsonBody(req) {
|
|
1
|
+
/** HTTP compatibility facade over the carrier-neutral backend service. */
|
|
2
|
+
import { LocalBackendApi, LocalBackendApiError } from "./local-backend-api.js";
|
|
3
|
+
async function readJsonBody(req, maxBytes = Number.POSITIVE_INFINITY) {
|
|
33
4
|
const chunks = [];
|
|
34
|
-
|
|
5
|
+
let size = 0;
|
|
6
|
+
for await (const chunk of req) {
|
|
7
|
+
size += chunk.length;
|
|
8
|
+
if (size > maxBytes)
|
|
9
|
+
throw new LocalBackendApiError("Request body is too large", 413, "invalid_request");
|
|
35
10
|
chunks.push(chunk);
|
|
36
|
-
|
|
37
|
-
return JSON.parse(
|
|
11
|
+
}
|
|
12
|
+
return JSON.parse(Buffer.concat(chunks).toString() || "{}");
|
|
13
|
+
}
|
|
14
|
+
function sendError(res, error) {
|
|
15
|
+
if (error instanceof LocalBackendApiError) {
|
|
16
|
+
res.status(error.status).json({ error: error.message, code: error.code });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
38
20
|
}
|
|
39
21
|
export function registerRestApi(app, options = {}) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
sessionIndex = new SessionIndex({
|
|
43
|
-
cwdDisplayFormatter: (cwd) => localSettingsStore.formatCwdTitle(cwd),
|
|
44
|
-
});
|
|
45
|
-
startLocalSettingsWatcher(options.onLocalSettingsReloaded);
|
|
46
|
-
app.post("/api/debug/load-trace/event", async (req, res) => {
|
|
22
|
+
const api = options.api ?? new LocalBackendApi(options);
|
|
23
|
+
app.get("/api/sessions", async (_req, res) => {
|
|
47
24
|
try {
|
|
48
|
-
|
|
49
|
-
res.status(404).json({ error: "Tracing disabled" });
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const chunks = [];
|
|
53
|
-
for await (const chunk of req)
|
|
54
|
-
chunks.push(chunk);
|
|
55
|
-
const body = JSON.parse(Buffer.concat(chunks).toString() || "{}");
|
|
56
|
-
const traceId = String(body.traceId || req.headers["x-pi-trace-id"] || "");
|
|
57
|
-
if (!traceId) {
|
|
58
|
-
res.status(400).json({ error: "Missing traceId" });
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
traceStore.record(traceId, {
|
|
62
|
-
ts: new Date().toISOString(),
|
|
63
|
-
source: "frontend",
|
|
64
|
-
kind: body.durationMs != null ? "span" : "instant",
|
|
65
|
-
name: String(body.name || "frontend_event"),
|
|
66
|
-
durationMs: typeof body.durationMs === "number" ? body.durationMs : undefined,
|
|
67
|
-
attrs: body.attrs && typeof body.attrs === "object" ? body.attrs : undefined,
|
|
68
|
-
});
|
|
69
|
-
res.json({ ok: true });
|
|
25
|
+
res.json(await api.listSessions());
|
|
70
26
|
}
|
|
71
|
-
catch (
|
|
72
|
-
res
|
|
27
|
+
catch (error) {
|
|
28
|
+
sendError(res, error);
|
|
73
29
|
}
|
|
74
30
|
});
|
|
75
|
-
app.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
31
|
+
app.delete("/api/sessions", async (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const body = await readJsonBody(req);
|
|
34
|
+
if (typeof body.path !== "string")
|
|
35
|
+
throw new LocalBackendApiError("Missing 'path' string", 400, "invalid_request");
|
|
36
|
+
await api.deleteSession(body.path);
|
|
37
|
+
res.json({ success: true });
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
sendError(res, error);
|
|
79
41
|
}
|
|
80
|
-
res.json({ traces: traceStore.getLatest() });
|
|
81
42
|
});
|
|
82
|
-
app.get("/api/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
43
|
+
app.get("/api/sessions/fork-messages", async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
if (typeof req.query.path !== "string")
|
|
46
|
+
throw new LocalBackendApiError("Missing 'path' query parameter", 400, "invalid_request");
|
|
47
|
+
res.json({ messages: await api.listForkMessages(req.query.path) });
|
|
86
48
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
res.status(404).json({ error: "Trace not found" });
|
|
90
|
-
return;
|
|
49
|
+
catch (error) {
|
|
50
|
+
sendError(res, error);
|
|
91
51
|
}
|
|
92
|
-
res.json(trace);
|
|
93
52
|
});
|
|
94
|
-
app.get("/api/sessions", async (
|
|
53
|
+
app.get("/api/sessions/raw", async (req, res) => {
|
|
95
54
|
try {
|
|
96
|
-
|
|
55
|
+
if (typeof req.query.path !== "string")
|
|
56
|
+
throw new LocalBackendApiError("Missing 'path' query parameter", 400, "invalid_request");
|
|
57
|
+
res.type("text/plain").send(await api.getRawSession(req.query.path));
|
|
97
58
|
}
|
|
98
|
-
catch (
|
|
99
|
-
res
|
|
59
|
+
catch (error) {
|
|
60
|
+
sendError(res, error);
|
|
100
61
|
}
|
|
101
62
|
});
|
|
102
|
-
app.get("/api/
|
|
63
|
+
app.get("/api/files/content", async (req, res) => {
|
|
103
64
|
try {
|
|
104
|
-
|
|
65
|
+
if (typeof req.query.sessionPath !== "string" || typeof req.query.path !== "string") {
|
|
66
|
+
throw new LocalBackendApiError("Missing 'sessionPath' or 'path' query parameter", 400, "invalid_request");
|
|
67
|
+
}
|
|
68
|
+
res.json(await api.getFileContent(req.query.sessionPath, req.query.path));
|
|
105
69
|
}
|
|
106
|
-
catch (
|
|
107
|
-
res
|
|
70
|
+
catch (error) {
|
|
71
|
+
sendError(res, error);
|
|
108
72
|
}
|
|
109
73
|
});
|
|
110
|
-
app.post("/api/
|
|
74
|
+
app.post("/api/files/uploads", async (req, res) => {
|
|
111
75
|
try {
|
|
112
|
-
const body = await readJsonBody(req);
|
|
113
|
-
if (typeof body.
|
|
114
|
-
|
|
115
|
-
return;
|
|
76
|
+
const body = await readJsonBody(req, 512 * 1024);
|
|
77
|
+
if (typeof body.fileName !== "string" || typeof body.mimeType !== "string" || typeof body.size !== "number") {
|
|
78
|
+
throw new LocalBackendApiError("Missing file upload metadata", 400, "invalid_request");
|
|
116
79
|
}
|
|
117
|
-
res.json(
|
|
80
|
+
res.status(201).json(await api.createFileUpload({
|
|
81
|
+
fileName: body.fileName,
|
|
82
|
+
mimeType: body.mimeType,
|
|
83
|
+
size: body.size,
|
|
84
|
+
}));
|
|
118
85
|
}
|
|
119
|
-
catch (
|
|
120
|
-
res
|
|
86
|
+
catch (error) {
|
|
87
|
+
sendError(res, error);
|
|
121
88
|
}
|
|
122
89
|
});
|
|
123
|
-
app.
|
|
90
|
+
app.post("/api/files/uploads/:uploadId/chunks", async (req, res) => {
|
|
124
91
|
try {
|
|
125
|
-
const body = await readJsonBody(req);
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
return;
|
|
92
|
+
const body = await readJsonBody(req, 512 * 1024);
|
|
93
|
+
if (typeof body.offset !== "number" || typeof body.data !== "string") {
|
|
94
|
+
throw new LocalBackendApiError("Missing file upload chunk", 400, "invalid_request");
|
|
129
95
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
await sessionIndex.invalidateAll();
|
|
136
|
-
options.onLocalSettingsReloaded?.();
|
|
137
|
-
res.json(result);
|
|
96
|
+
res.json(await api.appendFileUpload({
|
|
97
|
+
uploadId: req.params.uploadId,
|
|
98
|
+
offset: body.offset,
|
|
99
|
+
data: body.data,
|
|
100
|
+
}));
|
|
138
101
|
}
|
|
139
|
-
catch (
|
|
140
|
-
res
|
|
102
|
+
catch (error) {
|
|
103
|
+
sendError(res, error);
|
|
141
104
|
}
|
|
142
105
|
});
|
|
143
|
-
app.
|
|
106
|
+
app.post("/api/files/uploads/:uploadId/complete", async (req, res) => {
|
|
144
107
|
try {
|
|
145
|
-
|
|
146
|
-
if (typeof body.content !== "string") {
|
|
147
|
-
res.status(400).json({ error: "Missing 'content' string" });
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const result = localSettingsStore.save(body.content);
|
|
151
|
-
if (!result.valid) {
|
|
152
|
-
res.status(400).json(result);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
await sessionIndex.invalidateAll();
|
|
156
|
-
options.onLocalSettingsReloaded?.();
|
|
157
|
-
res.json(result);
|
|
108
|
+
res.json(await api.completeFileUpload(req.params.uploadId));
|
|
158
109
|
}
|
|
159
|
-
catch (
|
|
160
|
-
res
|
|
110
|
+
catch (error) {
|
|
111
|
+
sendError(res, error);
|
|
161
112
|
}
|
|
162
113
|
});
|
|
163
|
-
app.
|
|
114
|
+
app.get("/api/browse", async (req, res) => {
|
|
164
115
|
try {
|
|
165
|
-
|
|
166
|
-
for await (const chunk of req)
|
|
167
|
-
chunks.push(chunk);
|
|
168
|
-
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
169
|
-
const { path: sessionPath } = body;
|
|
170
|
-
if (!sessionPath || typeof sessionPath !== "string") {
|
|
171
|
-
res.status(400).json({ error: "Missing session path" });
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (!sessionPath.endsWith(".jsonl") || !existsSync(sessionPath)) {
|
|
175
|
-
res.status(404).json({ error: "Session not found" });
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
await unlink(sessionPath);
|
|
179
|
-
res.json({ success: true });
|
|
116
|
+
res.json(await api.browseDirectory(typeof req.query.path === "string" ? req.query.path : ""));
|
|
180
117
|
}
|
|
181
|
-
catch (
|
|
182
|
-
res
|
|
118
|
+
catch (error) {
|
|
119
|
+
sendError(res, error);
|
|
183
120
|
}
|
|
184
121
|
});
|
|
185
|
-
app.
|
|
122
|
+
app.post("/api/directories", async (req, res) => {
|
|
186
123
|
try {
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
if (!existsSync(sessionPath)) {
|
|
193
|
-
res.status(404).json({ error: "Session file not found" });
|
|
194
|
-
return;
|
|
124
|
+
const body = await readJsonBody(req, 16 * 1024);
|
|
125
|
+
if (typeof body.parentPath !== "string" || typeof body.name !== "string") {
|
|
126
|
+
throw new LocalBackendApiError("Missing 'parentPath' or 'name' string", 400, "invalid_request");
|
|
195
127
|
}
|
|
196
|
-
|
|
197
|
-
const entries = parseSessionEntries(content);
|
|
198
|
-
const context = buildSessionContext(entries);
|
|
199
|
-
res.json({
|
|
200
|
-
messages: context.messages,
|
|
201
|
-
model: context.model,
|
|
202
|
-
thinkingLevel: context.thinkingLevel,
|
|
203
|
-
});
|
|
128
|
+
res.status(201).json(await api.createDirectory(body.parentPath, body.name));
|
|
204
129
|
}
|
|
205
|
-
catch (
|
|
206
|
-
res
|
|
130
|
+
catch (error) {
|
|
131
|
+
sendError(res, error);
|
|
207
132
|
}
|
|
208
133
|
});
|
|
209
|
-
app.get("/api/
|
|
134
|
+
app.get("/api/settings/local", async (_req, res) => {
|
|
210
135
|
try {
|
|
211
|
-
|
|
212
|
-
if (!sessionPath || !sessionPath.endsWith(".jsonl")) {
|
|
213
|
-
res.status(400).json({ error: "Missing or invalid session path" });
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (!existsSync(sessionPath)) {
|
|
217
|
-
res.status(404).json({ error: "Session file not found" });
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const content = readFileSync(sessionPath, "utf8");
|
|
221
|
-
const entries = parseSessionEntries(content);
|
|
222
|
-
const messages = [];
|
|
223
|
-
for (const entry of entries) {
|
|
224
|
-
if (entry.type !== "message")
|
|
225
|
-
continue;
|
|
226
|
-
const msg = entry.message;
|
|
227
|
-
if (!msg || msg.role !== "user")
|
|
228
|
-
continue;
|
|
229
|
-
let text = "";
|
|
230
|
-
if (typeof msg.content === "string") {
|
|
231
|
-
text = msg.content;
|
|
232
|
-
}
|
|
233
|
-
else if (Array.isArray(msg.content)) {
|
|
234
|
-
text = msg.content
|
|
235
|
-
.filter((c) => c.type === "text")
|
|
236
|
-
.map((c) => c.text)
|
|
237
|
-
.join("");
|
|
238
|
-
}
|
|
239
|
-
if (text && entry.id) {
|
|
240
|
-
messages.push({ entryId: entry.id, text });
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
res.json({ messages });
|
|
136
|
+
res.json(await api.getLocalSettings());
|
|
244
137
|
}
|
|
245
|
-
catch (
|
|
246
|
-
res
|
|
138
|
+
catch (error) {
|
|
139
|
+
sendError(res, error);
|
|
247
140
|
}
|
|
248
141
|
});
|
|
249
|
-
app.
|
|
142
|
+
app.post("/api/settings/local/validate", async (req, res) => {
|
|
250
143
|
try {
|
|
251
|
-
const
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
if (!existsSync(sessionPath)) {
|
|
257
|
-
res.status(404).json({ error: "Session file not found" });
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const content = readFileSync(sessionPath, "utf8");
|
|
261
|
-
res.type("text/plain").send(content);
|
|
144
|
+
const body = await readJsonBody(req);
|
|
145
|
+
if (typeof body.content !== "string")
|
|
146
|
+
throw new LocalBackendApiError("Missing 'content' string", 400, "invalid_request");
|
|
147
|
+
res.json(await api.validateLocalSettings(body.content));
|
|
262
148
|
}
|
|
263
|
-
catch (
|
|
264
|
-
res
|
|
149
|
+
catch (error) {
|
|
150
|
+
sendError(res, error);
|
|
265
151
|
}
|
|
266
152
|
});
|
|
267
|
-
app.
|
|
153
|
+
app.patch("/api/settings/local", async (req, res) => {
|
|
268
154
|
try {
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
res.status(404).json({ error: "Path not found" });
|
|
273
|
-
return;
|
|
155
|
+
const body = await readJsonBody(req);
|
|
156
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
157
|
+
throw new LocalBackendApiError("Request body must be a JSON object", 400, "invalid_request");
|
|
274
158
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
159
|
+
const result = await api.patchLocalSettings(body);
|
|
160
|
+
res.status(result.valid ? 200 : 400).json(result);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
sendError(res, error);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
app.put("/api/settings/local", async (req, res) => {
|
|
167
|
+
try {
|
|
168
|
+
const body = await readJsonBody(req);
|
|
169
|
+
if (typeof body.content !== "string")
|
|
170
|
+
throw new LocalBackendApiError("Missing 'content' string", 400, "invalid_request");
|
|
171
|
+
const result = await api.saveLocalSettings(body.content);
|
|
172
|
+
res.status(result.valid ? 200 : 400).json(result);
|
|
284
173
|
}
|
|
285
|
-
catch (
|
|
286
|
-
res
|
|
174
|
+
catch (error) {
|
|
175
|
+
sendError(res, error);
|
|
287
176
|
}
|
|
288
177
|
});
|
|
178
|
+
return api;
|
|
289
179
|
}
|