@computesdk/loops 0.1.1 → 0.1.3
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/API.md +35 -5
- package/README.md +67 -21
- package/bin/loops.mjs +13 -4
- package/bin/pi-rpc-startup.mjs +38 -0
- package/bin/pi-rpc.mjs +27 -0
- package/client.ts +16 -1
- package/deploy/loops.service.example +24 -0
- package/dist/assets/{abnfDiagram-VCTEODGH-CceVg1jC.js → abnfDiagram-VCTEODGH-BjWORKhB.js} +1 -1
- package/dist/assets/{arc-CurbUKc7.js → arc-iaXuj2E3.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-BG6bMida.js → architectureDiagram-5GKGNRK7-BGYlRe5d.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-B11cpZbJ.js → blockDiagram-I7D4REHJ-DbRX-Eko.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-DiXBjlUf.js → c4Diagram-7LVT6UL2-DFNI3QWM.js} +1 -1
- package/dist/assets/channel-mVgxi6SM.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-DMtau_ZO.js → chunk-2Q5K7J3B-DqhjvffG.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-CkDw9Dwr.js → chunk-5VM5RSS4-pskYIVgB.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-DduXbVaR.js → chunk-F27PBJKO-CQcyYiby.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-CA3FEpcX.js → chunk-IMKFNOWR-B0MpoITM.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-_aPFiznH.js → chunk-JWPE2WC7-BsNxH0py.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-BJ1FxtOa.js → chunk-POPQ4Y6H-VnAuAhI3.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-BISx4Qsv.js → chunk-SVP7TREG-fiQQ7YON.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-jN0ScesT.js → chunk-TICWLB2K-agM_rWnc.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Bp2w1xsm.js → chunk-XXDRQBXY-BrnsKK60.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-BNg4EoCj.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-BNg4EoCj.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-RJxH4pHE.js → cose-bilkent-JH36ORCC-skVRi8C0.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-RQb2d53E.js → cynefin-OW5HDTMX-sugAAQ5D.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-BkWuDtvv.js → cynefinDiagram-5FMLGOSQ-BTjQ5HD4.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-Dw_p1MY7.js → dagre-GXQ25YYZ-wS0CVEvh.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-CNvafPnD.js → diagram-S7CK7UJ4-0P_U489Q.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-Ca50sGck.js → diagram-UQ7AKVKN-D85SHcZz.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-B9EkCAhx.js → diagram-VSXAHHWV-CozaqKLI.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-Be2j3KkX.js → diagram-VX7I27RA-kzpgzRzP.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-Iki9IPof.js → diagram-Z3DM3KII-DeIAvld9.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-C_DacKX3.js → ebnfDiagram-PWID7BFC-Cvo7CZD4.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-CN090mdT.js → erDiagram-RLTQ6QDP-BILowZs8.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-z9R5Hzp_.js → flowDiagram-HODETNUW-D58_1bOZ.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-Ctsoo8fA.js → ganttDiagram-EL5Y4UJY-Wc2egLDH.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-BAfnTLBI.js → gitGraphDiagram-WWUBYQGX-prvx2Pvu.js} +1 -1
- package/dist/assets/{highlighted-body-JLMTFACH-JGCpsH-e.js → highlighted-body-JLMTFACH-D3B11uXh.js} +1 -1
- package/dist/assets/index-C68b-bL6.css +1 -0
- package/dist/assets/index-CoHgGrA6.js +900 -0
- package/dist/assets/{infoDiagram-27XIBGKW-BMe5_1z8.js → infoDiagram-27XIBGKW-S1_RXMDl.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-BRSqbbUo.js → ishikawaDiagram-5VMMS53U-DPVVwIcA.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-WkAorMt-.js → journeyDiagram-3NMN7TZE-Dmlf7Ej8.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-aa6aiAN2.js → kanban-definition-UXKFOSKX-D7SJAgUZ.js} +1 -1
- package/dist/assets/{layout-BDC_N0BU.js → layout-DWrir7bj.js} +1 -1
- package/dist/assets/{linear-CznrJ42T.js → linear-B7B0s6Fb.js} +1 -1
- package/dist/assets/{mindmap-definition-YA3MSWOX-CW6ybBUC.js → mindmap-definition-YA3MSWOX-CvytaBvR.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-Dr3qzrfb.js → pegDiagram-XKGWAZYB-unjus9we.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-BkgMYztr.js → pieDiagram-E7YTZNPT-Bc0mRNN1.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-BTswMqBf.js → quadrantDiagram-AXDQQJYC-DCMBYddi.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-DVCTkg3c.js → railroadDiagram-O6MQD6OU-Bbb5CPfd.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-DDSFlh61.js → requirementDiagram-BXWQKSXE-C8qmEESJ.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-DHP89LFt.js → sankeyDiagram-P5KCCOFB-ChzXGPH8.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-CUcSveH3.js → sequenceDiagram-WJ2MYXX4-BeKUAzAe.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-fGlaZz_7.js → sizeCapture-INFHLROL-YJiYQ0Mn.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-DqcVPNAG.js → stateDiagram-D77RDMKH-D6NvtAkq.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-C9eBMjpA.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-L-pgkzCN.js → swimlanes-42K2YHIH-Cjb-j5cm.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-tvNn9kix.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-C__pbpoz.js → timeline-definition-24CTP7MA-CaBJ4VuK.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-B4f-azPR.js → vennDiagram-4TSXK5OY-D8MLav5x.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-01ZelHA6.js → wardleyDiagram-VM6X3IG4-CSy7rn-Q.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-CNfj8huj.js → xychartDiagram-S5SC5T6Z-BtRqCsaE.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/server.mjs +661 -312
- package/package.json +5 -3
- package/src/shared/types.ts +1 -1
- package/dist/assets/channel-Cy2zP8Uh.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-BHTKitbG.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-BHTKitbG.js +0 -1
- package/dist/assets/index-DkxpcXIf.js +0 -900
- package/dist/assets/index-NzHlk7dF.css +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-_t9xbbjh.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-6eL6UTv7.js +0 -8
package/dist/server.mjs
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
// server/index.ts
|
|
2
|
-
import
|
|
3
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
+
import fs9 from "node:fs";
|
|
3
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
4
4
|
import http from "node:http";
|
|
5
|
-
import
|
|
5
|
+
import path9 from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { execFile, execFileSync as execFileSync2, spawn as spawn6 } from "node:child_process";
|
|
7
|
+
import { execFile as execFile2, execFileSync as execFileSync2, spawn as spawn6 } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/http-exception.js
|
|
10
|
+
var HTTPException = class extends Error {
|
|
11
|
+
res;
|
|
12
|
+
status;
|
|
13
|
+
/**
|
|
14
|
+
* Creates an instance of `HTTPException`.
|
|
15
|
+
* @param status - HTTP status code for the exception. Defaults to 500.
|
|
16
|
+
* @param options - Additional options for the exception.
|
|
17
|
+
*/
|
|
18
|
+
constructor(status = 500, options) {
|
|
19
|
+
super(options?.message, { cause: options?.cause });
|
|
20
|
+
this.res = options?.res;
|
|
21
|
+
this.status = status;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Returns the response object associated with the exception.
|
|
25
|
+
* If a response object is not provided, a new response is created with the error message and status code.
|
|
26
|
+
* @returns The response object.
|
|
27
|
+
*/
|
|
28
|
+
getResponse() {
|
|
29
|
+
if (this.res) return new Response(this.res.body, {
|
|
30
|
+
status: this.status,
|
|
31
|
+
headers: this.res.headers
|
|
32
|
+
});
|
|
33
|
+
return new Response(this.message, { status: this.status });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
8
36
|
|
|
9
37
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/request/constants.js
|
|
10
38
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
@@ -79,26 +107,26 @@ var throwNestingLimitExceeded = () => {
|
|
|
79
107
|
};
|
|
80
108
|
|
|
81
109
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/utils/url.js
|
|
82
|
-
var splitPath = (
|
|
83
|
-
const paths =
|
|
110
|
+
var splitPath = (path10) => {
|
|
111
|
+
const paths = path10.split("/");
|
|
84
112
|
if (paths[0] === "") paths.shift();
|
|
85
113
|
return paths;
|
|
86
114
|
};
|
|
87
115
|
var splitRoutingPath = (routePath) => {
|
|
88
|
-
const { groups, path:
|
|
89
|
-
const paths = splitPath(
|
|
116
|
+
const { groups, path: path10 } = extractGroupsFromPath(routePath);
|
|
117
|
+
const paths = splitPath(path10);
|
|
90
118
|
return replaceGroupMarks(paths, groups);
|
|
91
119
|
};
|
|
92
|
-
var extractGroupsFromPath = (
|
|
120
|
+
var extractGroupsFromPath = (path10) => {
|
|
93
121
|
const groups = [];
|
|
94
|
-
|
|
122
|
+
path10 = path10.replace(/\{[^}]+\}/g, (match2, index) => {
|
|
95
123
|
const mark = `@${index}`;
|
|
96
124
|
groups.push([mark, match2]);
|
|
97
125
|
return mark;
|
|
98
126
|
});
|
|
99
127
|
return {
|
|
100
128
|
groups,
|
|
101
|
-
path:
|
|
129
|
+
path: path10
|
|
102
130
|
};
|
|
103
131
|
};
|
|
104
132
|
var replaceGroupMarks = (paths, groups) => {
|
|
@@ -161,8 +189,8 @@ var getPath = (request) => {
|
|
|
161
189
|
const queryIndex = url.indexOf("?", i);
|
|
162
190
|
const hashIndex = url.indexOf("#", i);
|
|
163
191
|
const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
|
|
164
|
-
const
|
|
165
|
-
return tryDecodeURI(
|
|
192
|
+
const path10 = url.slice(start, end);
|
|
193
|
+
return tryDecodeURI(path10.includes("%25") ? path10.replace(/%25/g, "%2525") : path10);
|
|
166
194
|
} else if (charCode === 63 || charCode === 35) break;
|
|
167
195
|
}
|
|
168
196
|
return url.slice(start, i);
|
|
@@ -175,9 +203,9 @@ var mergePath = (base, sub, ...rest) => {
|
|
|
175
203
|
if (rest.length) sub = mergePath(sub, ...rest);
|
|
176
204
|
return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
|
|
177
205
|
};
|
|
178
|
-
var checkOptionalParameter = (
|
|
179
|
-
if (
|
|
180
|
-
const segments =
|
|
206
|
+
var checkOptionalParameter = (path10) => {
|
|
207
|
+
if (path10.charCodeAt(path10.length - 1) !== 63 || !path10.includes(":")) return null;
|
|
208
|
+
const segments = path10.split("/");
|
|
181
209
|
const results = [];
|
|
182
210
|
let basePath = "";
|
|
183
211
|
segments.forEach((segment) => {
|
|
@@ -288,9 +316,9 @@ var HonoRequest = class {
|
|
|
288
316
|
*/
|
|
289
317
|
path;
|
|
290
318
|
bodyCache = {};
|
|
291
|
-
constructor(request,
|
|
319
|
+
constructor(request, path10 = "/", matchResult = [[]]) {
|
|
292
320
|
this.raw = request;
|
|
293
|
-
this.path =
|
|
321
|
+
this.path = path10;
|
|
294
322
|
this.#matchResult = matchResult;
|
|
295
323
|
}
|
|
296
324
|
param(key) {
|
|
@@ -1020,8 +1048,8 @@ var Hono = class Hono2 {
|
|
|
1020
1048
|
return this;
|
|
1021
1049
|
};
|
|
1022
1050
|
});
|
|
1023
|
-
this.on = (method,
|
|
1024
|
-
for (const p of [
|
|
1051
|
+
this.on = (method, path10, ...handlers) => {
|
|
1052
|
+
for (const p of [path10].flat()) {
|
|
1025
1053
|
this.#path = p;
|
|
1026
1054
|
for (const m of [method].flat()) {
|
|
1027
1055
|
const methodName = m.toUpperCase();
|
|
@@ -1075,8 +1103,8 @@ var Hono = class Hono2 {
|
|
|
1075
1103
|
* app.route("/api", app2) // GET /api/user
|
|
1076
1104
|
* ```
|
|
1077
1105
|
*/
|
|
1078
|
-
route(
|
|
1079
|
-
const subApp = this.basePath(
|
|
1106
|
+
route(path10, app2) {
|
|
1107
|
+
const subApp = this.basePath(path10);
|
|
1080
1108
|
app2.routes.map((r) => {
|
|
1081
1109
|
let handler;
|
|
1082
1110
|
if (app2.errorHandler === errorHandler) handler = r.handler;
|
|
@@ -1101,9 +1129,9 @@ var Hono = class Hono2 {
|
|
|
1101
1129
|
* const api = new Hono().basePath('/api')
|
|
1102
1130
|
* ```
|
|
1103
1131
|
*/
|
|
1104
|
-
basePath(
|
|
1132
|
+
basePath(path10) {
|
|
1105
1133
|
const subApp = this.#clone();
|
|
1106
|
-
subApp._basePath = mergePath(this._basePath,
|
|
1134
|
+
subApp._basePath = mergePath(this._basePath, path10);
|
|
1107
1135
|
return subApp;
|
|
1108
1136
|
}
|
|
1109
1137
|
/**
|
|
@@ -1179,7 +1207,7 @@ var Hono = class Hono2 {
|
|
|
1179
1207
|
* })
|
|
1180
1208
|
* ```
|
|
1181
1209
|
*/
|
|
1182
|
-
mount(
|
|
1210
|
+
mount(path10, applicationHandler, options) {
|
|
1183
1211
|
let replaceRequest;
|
|
1184
1212
|
let optionHandler;
|
|
1185
1213
|
if (options) {
|
|
@@ -1202,7 +1230,7 @@ var Hono = class Hono2 {
|
|
|
1202
1230
|
return [c.env, executionContext];
|
|
1203
1231
|
};
|
|
1204
1232
|
replaceRequest ||= (() => {
|
|
1205
|
-
const mergedPath = mergePath(this._basePath,
|
|
1233
|
+
const mergedPath = mergePath(this._basePath, path10);
|
|
1206
1234
|
const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
|
|
1207
1235
|
return (request) => {
|
|
1208
1236
|
const url = new URL(request.url);
|
|
@@ -1215,18 +1243,18 @@ var Hono = class Hono2 {
|
|
|
1215
1243
|
if (res) return res;
|
|
1216
1244
|
await next();
|
|
1217
1245
|
};
|
|
1218
|
-
this.#addRoute("ALL", mergePath(
|
|
1246
|
+
this.#addRoute("ALL", mergePath(path10, "*"), handler);
|
|
1219
1247
|
return this;
|
|
1220
1248
|
}
|
|
1221
|
-
#addRoute(method,
|
|
1222
|
-
|
|
1249
|
+
#addRoute(method, path10, handler, baseRoutePath) {
|
|
1250
|
+
path10 = mergePath(this._basePath, path10);
|
|
1223
1251
|
const r = {
|
|
1224
1252
|
basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
1225
|
-
path:
|
|
1253
|
+
path: path10,
|
|
1226
1254
|
method,
|
|
1227
1255
|
handler
|
|
1228
1256
|
};
|
|
1229
|
-
this.router.add(method,
|
|
1257
|
+
this.router.add(method, path10, [handler, r]);
|
|
1230
1258
|
this.routes.push(r);
|
|
1231
1259
|
}
|
|
1232
1260
|
#handleError(err, c) {
|
|
@@ -1235,10 +1263,10 @@ var Hono = class Hono2 {
|
|
|
1235
1263
|
}
|
|
1236
1264
|
#dispatch(request, executionCtx, env, method) {
|
|
1237
1265
|
if (method === "HEAD") return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
|
|
1238
|
-
const
|
|
1239
|
-
const matchResult = this.router.match(method,
|
|
1266
|
+
const path10 = this.getPath(request, { env });
|
|
1267
|
+
const matchResult = this.router.match(method, path10);
|
|
1240
1268
|
const c = new Context(request, {
|
|
1241
|
-
path:
|
|
1269
|
+
path: path10,
|
|
1242
1270
|
matchResult,
|
|
1243
1271
|
env,
|
|
1244
1272
|
executionCtx,
|
|
@@ -1326,19 +1354,19 @@ var createNullObject = () => /* @__PURE__ */ Object.create(null);
|
|
|
1326
1354
|
|
|
1327
1355
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
1328
1356
|
var emptyParam = [];
|
|
1329
|
-
function match(method,
|
|
1357
|
+
function match(method, path10) {
|
|
1330
1358
|
const matchers = this.buildAllMatchers();
|
|
1331
|
-
const match2 = ((method2,
|
|
1359
|
+
const match2 = ((method2, path11) => {
|
|
1332
1360
|
const matcher = matchers[method2] || matchers["ALL"];
|
|
1333
|
-
const staticMatch = matcher[2][
|
|
1361
|
+
const staticMatch = matcher[2][path11];
|
|
1334
1362
|
if (staticMatch) return staticMatch;
|
|
1335
|
-
const match3 =
|
|
1363
|
+
const match3 = path11.match(matcher[0]);
|
|
1336
1364
|
if (!match3) return [[], emptyParam];
|
|
1337
1365
|
const index = match3.indexOf("", 1);
|
|
1338
1366
|
return [matcher[1][index], match3];
|
|
1339
1367
|
});
|
|
1340
1368
|
this.match = match2;
|
|
1341
|
-
return match2(method,
|
|
1369
|
+
return match2(method, path10);
|
|
1342
1370
|
}
|
|
1343
1371
|
|
|
1344
1372
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
@@ -1428,14 +1456,14 @@ var Trie = class {
|
|
|
1428
1456
|
#root = new Node();
|
|
1429
1457
|
#index = 0;
|
|
1430
1458
|
paths = createNullObject();
|
|
1431
|
-
insert(
|
|
1459
|
+
insert(path10, isStatic) {
|
|
1432
1460
|
if (isStatic) {
|
|
1433
|
-
this.#root.insert(
|
|
1461
|
+
this.#root.insert(path10.split(""), 0, [], this.#context, true);
|
|
1434
1462
|
return;
|
|
1435
1463
|
}
|
|
1436
1464
|
const paramAssoc = [];
|
|
1437
1465
|
const groups = [];
|
|
1438
|
-
let markedPath =
|
|
1466
|
+
let markedPath = path10;
|
|
1439
1467
|
for (let i = 0; ; ) {
|
|
1440
1468
|
let replaced = false;
|
|
1441
1469
|
markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
|
|
@@ -1456,7 +1484,7 @@ var Trie = class {
|
|
|
1456
1484
|
}
|
|
1457
1485
|
}
|
|
1458
1486
|
this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
|
|
1459
|
-
this.paths[
|
|
1487
|
+
this.paths[path10] = [this.#index++, paramAssoc];
|
|
1460
1488
|
}
|
|
1461
1489
|
buildRegExp() {
|
|
1462
1490
|
let regexp = this.#root.buildRegExpStr();
|
|
@@ -1489,11 +1517,11 @@ var Trie = class {
|
|
|
1489
1517
|
|
|
1490
1518
|
// ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
1491
1519
|
var wildcardRegExpCache = createNullObject();
|
|
1492
|
-
function buildWildcardRegExp(
|
|
1493
|
-
return wildcardRegExpCache[
|
|
1520
|
+
function buildWildcardRegExp(path10) {
|
|
1521
|
+
return wildcardRegExpCache[path10] ??= new RegExp(`^${path10.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ".*" : `/:${LABEL_REG_EXP_STR}`)}$`);
|
|
1494
1522
|
}
|
|
1495
|
-
function findMiddleware(middleware,
|
|
1496
|
-
for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) if (buildWildcardRegExp(k).test(
|
|
1523
|
+
function findMiddleware(middleware, path10) {
|
|
1524
|
+
for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) if (buildWildcardRegExp(k).test(path10)) return [...middleware[k]];
|
|
1497
1525
|
}
|
|
1498
1526
|
var RegExpRouter = class {
|
|
1499
1527
|
name = "RegExpRouter";
|
|
@@ -1505,14 +1533,14 @@ var RegExpRouter = class {
|
|
|
1505
1533
|
this.#routes = { ["ALL"]: createNullObject() };
|
|
1506
1534
|
this.#tries = { ["ALL"]: new Trie() };
|
|
1507
1535
|
}
|
|
1508
|
-
#insertPath(method,
|
|
1536
|
+
#insertPath(method, path10) {
|
|
1509
1537
|
try {
|
|
1510
|
-
this.#tries[method].insert(
|
|
1538
|
+
this.#tries[method].insert(path10, !/\*|\/:/.test(path10));
|
|
1511
1539
|
} catch (e) {
|
|
1512
|
-
throw e === PATH_ERROR ? new UnsupportedPathError(
|
|
1540
|
+
throw e === PATH_ERROR ? new UnsupportedPathError(path10) : e;
|
|
1513
1541
|
}
|
|
1514
1542
|
}
|
|
1515
|
-
add(method,
|
|
1543
|
+
add(method, path10, handler) {
|
|
1516
1544
|
const middleware = this.#middleware;
|
|
1517
1545
|
const routes = this.#routes;
|
|
1518
1546
|
if (!middleware) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
@@ -1526,24 +1554,24 @@ var RegExpRouter = class {
|
|
|
1526
1554
|
}
|
|
1527
1555
|
}
|
|
1528
1556
|
}
|
|
1529
|
-
if (
|
|
1557
|
+
if (path10 === "/*") path10 = "*";
|
|
1530
1558
|
const methods = method === "ALL" ? Object.keys(middleware) : [method];
|
|
1531
|
-
if (/\*$/.test(
|
|
1532
|
-
const re = buildWildcardRegExp(
|
|
1533
|
-
for (const m of methods) if (!middleware[m][
|
|
1534
|
-
this.#insertPath(m,
|
|
1535
|
-
middleware[m][
|
|
1559
|
+
if (/\*$/.test(path10)) {
|
|
1560
|
+
const re = buildWildcardRegExp(path10);
|
|
1561
|
+
for (const m of methods) if (!middleware[m][path10]) {
|
|
1562
|
+
this.#insertPath(m, path10);
|
|
1563
|
+
middleware[m][path10] = findMiddleware(middleware[m], path10) || findMiddleware(middleware["ALL"], path10) || [];
|
|
1536
1564
|
}
|
|
1537
|
-
for (const handlerMap of [middleware, routes]) for (const m of methods) for (const p in handlerMap[m]) re.test(p) && handlerMap[m][p].push([handler,
|
|
1565
|
+
for (const handlerMap of [middleware, routes]) for (const m of methods) for (const p in handlerMap[m]) re.test(p) && handlerMap[m][p].push([handler, path10]);
|
|
1538
1566
|
return;
|
|
1539
1567
|
}
|
|
1540
|
-
const paths = checkOptionalParameter(
|
|
1541
|
-
for (const
|
|
1542
|
-
if (!routes[m][
|
|
1543
|
-
this.#insertPath(m,
|
|
1544
|
-
routes[m][
|
|
1568
|
+
const paths = checkOptionalParameter(path10) || [path10];
|
|
1569
|
+
for (const path11 of paths) for (const m of methods) {
|
|
1570
|
+
if (!routes[m][path11]) {
|
|
1571
|
+
this.#insertPath(m, path11);
|
|
1572
|
+
routes[m][path11] = findMiddleware(middleware[m], path11) || findMiddleware(middleware["ALL"], path11) || [];
|
|
1545
1573
|
}
|
|
1546
|
-
routes[m][
|
|
1574
|
+
routes[m][path11].push([handler, path11]);
|
|
1547
1575
|
}
|
|
1548
1576
|
}
|
|
1549
1577
|
match = match;
|
|
@@ -1561,11 +1589,11 @@ var RegExpRouter = class {
|
|
|
1561
1589
|
const staticMap = createNullObject();
|
|
1562
1590
|
const handlerData = [];
|
|
1563
1591
|
const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
|
|
1564
|
-
for (const r of [middleware, routes]) for (const
|
|
1565
|
-
const handlers = r[
|
|
1566
|
-
const pathData = trie.paths[
|
|
1592
|
+
for (const r of [middleware, routes]) for (const path10 in r) {
|
|
1593
|
+
const handlers = r[path10];
|
|
1594
|
+
const pathData = trie.paths[path10];
|
|
1567
1595
|
if (!pathData) {
|
|
1568
|
-
staticMap[
|
|
1596
|
+
staticMap[path10] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
|
|
1569
1597
|
continue;
|
|
1570
1598
|
}
|
|
1571
1599
|
handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [h, trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
|
|
@@ -1589,15 +1617,15 @@ var SmartRouter = class {
|
|
|
1589
1617
|
constructor(init) {
|
|
1590
1618
|
this.#routers = init.routers;
|
|
1591
1619
|
}
|
|
1592
|
-
add(method,
|
|
1620
|
+
add(method, path10, handler) {
|
|
1593
1621
|
if (!this.#routes) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
1594
1622
|
this.#routes.push([
|
|
1595
1623
|
method,
|
|
1596
|
-
|
|
1624
|
+
path10,
|
|
1597
1625
|
handler
|
|
1598
1626
|
]);
|
|
1599
1627
|
}
|
|
1600
|
-
match(method,
|
|
1628
|
+
match(method, path10) {
|
|
1601
1629
|
if (!this.#routes) throw new Error("Fatal error");
|
|
1602
1630
|
const routers = this.#routers;
|
|
1603
1631
|
const routes = this.#routes;
|
|
@@ -1608,7 +1636,7 @@ var SmartRouter = class {
|
|
|
1608
1636
|
const router = routers[i];
|
|
1609
1637
|
try {
|
|
1610
1638
|
for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) router.add(...routes[i2]);
|
|
1611
|
-
res = router.match(method,
|
|
1639
|
+
res = router.match(method, path10);
|
|
1612
1640
|
} catch (e) {
|
|
1613
1641
|
if (e instanceof UnsupportedPathError) continue;
|
|
1614
1642
|
throw e;
|
|
@@ -1637,9 +1665,9 @@ var Node3 = class Node4 {
|
|
|
1637
1665
|
#patterns = [];
|
|
1638
1666
|
#pattern;
|
|
1639
1667
|
#params = emptyParams;
|
|
1640
|
-
insert(method,
|
|
1668
|
+
insert(method, path10, handler) {
|
|
1641
1669
|
let curNode = this;
|
|
1642
|
-
const parts = splitRoutingPath(
|
|
1670
|
+
const parts = splitRoutingPath(path10);
|
|
1643
1671
|
const possibleKeys = /* @__PURE__ */ new Set();
|
|
1644
1672
|
let i = 0;
|
|
1645
1673
|
for (const p of parts) {
|
|
@@ -1675,11 +1703,11 @@ var Node3 = class Node4 {
|
|
|
1675
1703
|
}
|
|
1676
1704
|
}
|
|
1677
1705
|
}
|
|
1678
|
-
search(method,
|
|
1706
|
+
search(method, path10) {
|
|
1679
1707
|
const handlerSets = [];
|
|
1680
1708
|
this.#params = emptyParams;
|
|
1681
1709
|
let curNodes = [this];
|
|
1682
|
-
const parts = splitPath(
|
|
1710
|
+
const parts = splitPath(path10);
|
|
1683
1711
|
const curNodesQueue = [];
|
|
1684
1712
|
const len = parts.length;
|
|
1685
1713
|
let partOffsets = null;
|
|
@@ -1715,13 +1743,13 @@ var Node3 = class Node4 {
|
|
|
1715
1743
|
if (matcher !== true) {
|
|
1716
1744
|
if (!partOffsets) {
|
|
1717
1745
|
partOffsets = [];
|
|
1718
|
-
let offset =
|
|
1746
|
+
let offset = path10[0] === "/" ? 1 : 0;
|
|
1719
1747
|
for (let p = 0; p < len; p++) {
|
|
1720
1748
|
partOffsets[p] = offset;
|
|
1721
1749
|
offset += parts[p].length + 1;
|
|
1722
1750
|
}
|
|
1723
1751
|
}
|
|
1724
|
-
const restPathString =
|
|
1752
|
+
const restPathString = path10.slice(partOffsets[i]);
|
|
1725
1753
|
const m = matcher.exec(restPathString);
|
|
1726
1754
|
if (m) {
|
|
1727
1755
|
if (!Object.hasOwn(params, name)) params[name] = m[0];
|
|
@@ -1762,11 +1790,11 @@ var Node3 = class Node4 {
|
|
|
1762
1790
|
var TrieRouter = class {
|
|
1763
1791
|
name = "TrieRouter";
|
|
1764
1792
|
#node = new Node3();
|
|
1765
|
-
add(method,
|
|
1766
|
-
for (const result of checkOptionalParameter(
|
|
1793
|
+
add(method, path10, handler) {
|
|
1794
|
+
for (const result of checkOptionalParameter(path10) || [path10]) this.#node.insert(method, result, handler);
|
|
1767
1795
|
}
|
|
1768
|
-
match(method,
|
|
1769
|
-
return this.#node.search(method,
|
|
1796
|
+
match(method, path10) {
|
|
1797
|
+
return this.#node.search(method, path10);
|
|
1770
1798
|
}
|
|
1771
1799
|
};
|
|
1772
1800
|
|
|
@@ -2347,19 +2375,19 @@ var flushHeaders = (outgoing) => {
|
|
|
2347
2375
|
}
|
|
2348
2376
|
};
|
|
2349
2377
|
var responseViaCache = async (res, outgoing) => {
|
|
2350
|
-
let [status, body,
|
|
2378
|
+
let [status, body, header2] = res[cacheKey];
|
|
2351
2379
|
let hasContentLength = false;
|
|
2352
|
-
if (!
|
|
2353
|
-
|
|
2354
|
-
} else if (
|
|
2355
|
-
hasContentLength =
|
|
2356
|
-
|
|
2357
|
-
} else if (Array.isArray(
|
|
2358
|
-
const headerObj = new Headers(
|
|
2380
|
+
if (!header2) {
|
|
2381
|
+
header2 = { "content-type": "text/plain; charset=UTF-8" };
|
|
2382
|
+
} else if (header2 instanceof Headers) {
|
|
2383
|
+
hasContentLength = header2.has("content-length");
|
|
2384
|
+
header2 = buildOutgoingHttpHeaders(header2);
|
|
2385
|
+
} else if (Array.isArray(header2)) {
|
|
2386
|
+
const headerObj = new Headers(header2);
|
|
2359
2387
|
hasContentLength = headerObj.has("content-length");
|
|
2360
|
-
|
|
2388
|
+
header2 = buildOutgoingHttpHeaders(headerObj);
|
|
2361
2389
|
} else {
|
|
2362
|
-
for (const key in
|
|
2390
|
+
for (const key in header2) {
|
|
2363
2391
|
if (key.length === 14 && key.toLowerCase() === "content-length") {
|
|
2364
2392
|
hasContentLength = true;
|
|
2365
2393
|
break;
|
|
@@ -2368,14 +2396,14 @@ var responseViaCache = async (res, outgoing) => {
|
|
|
2368
2396
|
}
|
|
2369
2397
|
if (!hasContentLength) {
|
|
2370
2398
|
if (typeof body === "string") {
|
|
2371
|
-
|
|
2399
|
+
header2["Content-Length"] = Buffer.byteLength(body);
|
|
2372
2400
|
} else if (body instanceof Uint8Array) {
|
|
2373
|
-
|
|
2401
|
+
header2["Content-Length"] = body.byteLength;
|
|
2374
2402
|
} else if (body instanceof Blob) {
|
|
2375
|
-
|
|
2403
|
+
header2["Content-Length"] = body.size;
|
|
2376
2404
|
}
|
|
2377
2405
|
}
|
|
2378
|
-
outgoing.writeHead(status,
|
|
2406
|
+
outgoing.writeHead(status, header2);
|
|
2379
2407
|
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
2380
2408
|
outgoing.end(body);
|
|
2381
2409
|
} else if (body instanceof Blob) {
|
|
@@ -2572,6 +2600,9 @@ var PiRpc = class {
|
|
|
2572
2600
|
options;
|
|
2573
2601
|
constructor(options) {
|
|
2574
2602
|
this.options = options;
|
|
2603
|
+
if (options.onEvent) this.listeners.add(options.onEvent);
|
|
2604
|
+
if (options.onUiRequest) this.uiListeners.add(options.onUiRequest);
|
|
2605
|
+
if (options.onExit) this.exitListeners.add(options.onExit);
|
|
2575
2606
|
}
|
|
2576
2607
|
get running() {
|
|
2577
2608
|
return !!this.proc && !this.exited;
|
|
@@ -2584,12 +2615,16 @@ var PiRpc = class {
|
|
|
2584
2615
|
}
|
|
2585
2616
|
async start() {
|
|
2586
2617
|
if (this.proc) return;
|
|
2587
|
-
const
|
|
2618
|
+
const command = this.options.command ?? "pi";
|
|
2619
|
+
const script = /\.m?js$/i.test(command);
|
|
2620
|
+
const proc = spawn(script ? process.execPath : command, [...script ? [command] : [], "--mode", "rpc", ...this.options.args ?? []], {
|
|
2588
2621
|
cwd: this.options.cwd,
|
|
2589
2622
|
env: { ...process.env, ...this.options.env },
|
|
2590
2623
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2591
2624
|
});
|
|
2592
2625
|
this.proc = proc;
|
|
2626
|
+
proc.stdin.on("error", () => {
|
|
2627
|
+
});
|
|
2593
2628
|
proc.stdout.setEncoding("utf8");
|
|
2594
2629
|
proc.stdout.on("data", (chunk) => {
|
|
2595
2630
|
this.buffer += chunk;
|
|
@@ -2614,7 +2649,11 @@ var PiRpc = class {
|
|
|
2614
2649
|
const done = (code) => {
|
|
2615
2650
|
if (this.exited) return;
|
|
2616
2651
|
this.exited = { code };
|
|
2617
|
-
for (const [, p] of this.pending)
|
|
2652
|
+
for (const [, p] of this.pending) {
|
|
2653
|
+
const noise = this.stderr.split("\n").map((l) => l.trim()).filter((l) => l && !/^warn(ing)?\b/i.test(l)).slice(-3).join(" \xB7 ").slice(-300);
|
|
2654
|
+
const how = code === 0 ? "pi exited before answering" : `pi exited (code ${code})`;
|
|
2655
|
+
p.reject(new Error(noise ? `${how}: ${noise}` : how));
|
|
2656
|
+
}
|
|
2618
2657
|
this.pending.clear();
|
|
2619
2658
|
for (const fn of this.exitListeners) {
|
|
2620
2659
|
try {
|
|
@@ -2623,7 +2662,7 @@ var PiRpc = class {
|
|
|
2623
2662
|
}
|
|
2624
2663
|
}
|
|
2625
2664
|
};
|
|
2626
|
-
proc.on("
|
|
2665
|
+
proc.on("close", done);
|
|
2627
2666
|
proc.on("error", (e) => {
|
|
2628
2667
|
this.stderr += `
|
|
2629
2668
|
spawn error: ${e.message}`;
|
|
@@ -2695,7 +2734,9 @@ spawn error: ${e.message}`;
|
|
|
2695
2734
|
const entry = this.pending.get(id);
|
|
2696
2735
|
this.pending.set(id, { resolve: (d) => settle(() => resolve(d)), reject: (e) => settle(() => reject(e)) });
|
|
2697
2736
|
try {
|
|
2698
|
-
this.proc.stdin.write(payload)
|
|
2737
|
+
this.proc.stdin.write(payload, (error) => {
|
|
2738
|
+
if (error && this.pending.delete(id)) settle(() => reject(error));
|
|
2739
|
+
});
|
|
2699
2740
|
} catch (e) {
|
|
2700
2741
|
this.pending.delete(id);
|
|
2701
2742
|
settle(() => reject(e));
|
|
@@ -2704,12 +2745,11 @@ spawn error: ${e.message}`;
|
|
|
2704
2745
|
}
|
|
2705
2746
|
/** Answer an extension dialog. Without an answer the extension's tool call waits forever. */
|
|
2706
2747
|
async respondUi(id, response) {
|
|
2707
|
-
if (!this.proc || this.exited)
|
|
2708
|
-
const record = { type: "extension_ui_response", id
|
|
2709
|
-
|
|
2710
|
-
this.proc.stdin.write(JSON.stringify(record) + "\n");
|
|
2711
|
-
}
|
|
2712
|
-
}
|
|
2748
|
+
if (!this.proc || this.exited) throw new Error("Pi is not running. Retry the agent before answering.");
|
|
2749
|
+
const record = { ...response, type: "extension_ui_response", id };
|
|
2750
|
+
await new Promise((resolve, reject) => {
|
|
2751
|
+
this.proc.stdin.write(JSON.stringify(record) + "\n", (error) => error ? reject(error) : resolve());
|
|
2752
|
+
});
|
|
2713
2753
|
}
|
|
2714
2754
|
prompt(message, behavior) {
|
|
2715
2755
|
return this.send({ type: "prompt", message, ...behavior ? { streamingBehavior: behavior } : {} });
|
|
@@ -2728,9 +2768,8 @@ spawn error: ${e.message}`;
|
|
|
2728
2768
|
proc.kill("SIGKILL");
|
|
2729
2769
|
} catch {
|
|
2730
2770
|
}
|
|
2731
|
-
resolve();
|
|
2732
2771
|
}, 3e3);
|
|
2733
|
-
proc.once("
|
|
2772
|
+
proc.once("close", () => {
|
|
2734
2773
|
clearTimeout(t);
|
|
2735
2774
|
resolve();
|
|
2736
2775
|
});
|
|
@@ -3065,6 +3104,13 @@ function itemsFromMessages(messages) {
|
|
|
3065
3104
|
}
|
|
3066
3105
|
return items;
|
|
3067
3106
|
}
|
|
3107
|
+
function readSessionMessages(file) {
|
|
3108
|
+
try {
|
|
3109
|
+
return fs2.readFileSync(file, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l)).filter((e) => e.type === "message" && e.message).map((e) => e.message);
|
|
3110
|
+
} catch {
|
|
3111
|
+
return [];
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3068
3114
|
var Chat = class {
|
|
3069
3115
|
id;
|
|
3070
3116
|
options;
|
|
@@ -3075,6 +3121,8 @@ var Chat = class {
|
|
|
3075
3121
|
live = null;
|
|
3076
3122
|
liveLoops = /* @__PURE__ */ new Map();
|
|
3077
3123
|
dialogs = [];
|
|
3124
|
+
answeringUi = /* @__PURE__ */ new Set();
|
|
3125
|
+
dialogTimers = /* @__PURE__ */ new Map();
|
|
3078
3126
|
notices = [];
|
|
3079
3127
|
runs = [];
|
|
3080
3128
|
model = null;
|
|
@@ -3082,6 +3130,9 @@ var Chat = class {
|
|
|
3082
3130
|
refreshing = null;
|
|
3083
3131
|
lastBroadcast = 0;
|
|
3084
3132
|
stderr = "";
|
|
3133
|
+
exitError = "";
|
|
3134
|
+
refreshError = "";
|
|
3135
|
+
stopping = false;
|
|
3085
3136
|
file = null;
|
|
3086
3137
|
sessionName = null;
|
|
3087
3138
|
autoNamed = false;
|
|
@@ -3091,14 +3142,22 @@ var Chat = class {
|
|
|
3091
3142
|
this.model = options.model ?? null;
|
|
3092
3143
|
this.pi = new PiRpc({
|
|
3093
3144
|
command: options.piCommand,
|
|
3094
|
-
args: ["--session-dir", options.sessionDir, "--session-id", id, ...options.args ?? [], ...options.model ? ["--model", options.model] : []],
|
|
3145
|
+
args: ["--session-dir", options.sessionDir, ...options.sessionFile ? ["--session", options.sessionFile] : ["--session-id", id], ...options.args ?? [], ...options.model ? ["--model", options.model] : []],
|
|
3095
3146
|
cwd: options.cwd,
|
|
3096
3147
|
onEvent: (event) => this.onEvent(event),
|
|
3097
|
-
onExit: (code) => {
|
|
3098
|
-
this.stderr =
|
|
3148
|
+
onExit: (code, stderr) => {
|
|
3149
|
+
this.stderr = stderr;
|
|
3150
|
+
const hadDialog = this.dialogs.length > 0;
|
|
3151
|
+
this.dialogs = [];
|
|
3152
|
+
for (const { timer } of this.dialogTimers.values()) clearTimeout(timer);
|
|
3153
|
+
this.dialogTimers.clear();
|
|
3099
3154
|
this.live = null;
|
|
3155
|
+
if (!this.stopping) {
|
|
3156
|
+
const diagnostic = stderr.split("\n").map((l) => l.trim()).filter((l) => l && !/^warn(ing)?\b/i.test(l)).slice(-3).join(" \xB7 ").slice(-300);
|
|
3157
|
+
this.exitError = hadDialog ? "Pi exited while an extension decision was pending. Retry the agent; if you selected a custom Pi executable, use the default Loops runner for startup-dialog support." : code === 0 ? "Pi exited unexpectedly." : `Pi exited (code ${code ?? "unknown"}).`;
|
|
3158
|
+
if (diagnostic) this.exitError += ` ${diagnostic}`;
|
|
3159
|
+
}
|
|
3100
3160
|
this.broadcast(true);
|
|
3101
|
-
if (code != null) this.touch();
|
|
3102
3161
|
}
|
|
3103
3162
|
});
|
|
3104
3163
|
}
|
|
@@ -3129,10 +3188,21 @@ var Chat = class {
|
|
|
3129
3188
|
async start() {
|
|
3130
3189
|
if (this.started) return;
|
|
3131
3190
|
this.started = true;
|
|
3132
|
-
|
|
3191
|
+
let signalDialog;
|
|
3192
|
+
const dialogReady = new Promise((resolve) => {
|
|
3193
|
+
signalDialog = resolve;
|
|
3194
|
+
});
|
|
3133
3195
|
this.pi.onUiRequest((request) => {
|
|
3134
|
-
if (["select", "confirm", "input", "editor"].includes(request.method))
|
|
3135
|
-
|
|
3196
|
+
if (["select", "confirm", "input", "editor"].includes(request.method)) {
|
|
3197
|
+
this.dialogs.push(request);
|
|
3198
|
+
if (typeof request.timeout === "number" && Number.isFinite(request.timeout) && request.timeout > 0) {
|
|
3199
|
+
const timeout = Math.min(request.timeout, 2 ** 31 - 1);
|
|
3200
|
+
const timer = setTimeout(() => this.closeDialog(request.id), timeout);
|
|
3201
|
+
timer.unref();
|
|
3202
|
+
this.dialogTimers.set(request.id, { timer, deadline: Date.now() + timeout });
|
|
3203
|
+
}
|
|
3204
|
+
signalDialog();
|
|
3205
|
+
} else if (request.method === "notify") {
|
|
3136
3206
|
console.log(`[${this.id}] ${request.level ?? "info"}: ${request.message ?? ""}`);
|
|
3137
3207
|
this.notices.push({ t: Date.now(), level: String(request.level ?? "info"), message: String(request.message ?? "") });
|
|
3138
3208
|
this.notices = this.notices.slice(-20);
|
|
@@ -3140,13 +3210,15 @@ var Chat = class {
|
|
|
3140
3210
|
void this.refresh();
|
|
3141
3211
|
this.broadcast(true);
|
|
3142
3212
|
});
|
|
3213
|
+
await this.pi.start();
|
|
3143
3214
|
if (!this.pi.running) {
|
|
3144
3215
|
this.stderr = this.pi.stderrText;
|
|
3145
3216
|
return;
|
|
3146
3217
|
}
|
|
3147
|
-
await this.refresh(true);
|
|
3218
|
+
await Promise.race([this.refresh(true), dialogReady]);
|
|
3148
3219
|
}
|
|
3149
3220
|
async stop() {
|
|
3221
|
+
this.stopping = true;
|
|
3150
3222
|
await this.pi.stop();
|
|
3151
3223
|
}
|
|
3152
3224
|
/** Re-read the session's entries (cursor-based) and messages, then rebuild the runs. */
|
|
@@ -3174,9 +3246,10 @@ var Chat = class {
|
|
|
3174
3246
|
await this.rename(auto).catch(() => {
|
|
3175
3247
|
});
|
|
3176
3248
|
}
|
|
3249
|
+
this.refreshError = "";
|
|
3177
3250
|
} catch (e) {
|
|
3178
3251
|
console.error(`[${this.id}] refresh failed:`, e.message ?? e);
|
|
3179
|
-
this.
|
|
3252
|
+
this.refreshError = String(e.message ?? e);
|
|
3180
3253
|
} finally {
|
|
3181
3254
|
this.refreshing = null;
|
|
3182
3255
|
this.broadcast(true);
|
|
@@ -3231,6 +3304,8 @@ var Chat = class {
|
|
|
3231
3304
|
void this.refresh();
|
|
3232
3305
|
this.broadcast(true);
|
|
3233
3306
|
} else if (type === "extension_ui_request") {
|
|
3307
|
+
} else if (type === "extension_ui_request_cancelled") {
|
|
3308
|
+
this.closeDialog(String(event.id));
|
|
3234
3309
|
} else if (type.startsWith("session_") || type === "compaction_start" || type === "compaction_end") {
|
|
3235
3310
|
if (type === "session_idle" || type === "session_start") void this.refresh(true);
|
|
3236
3311
|
}
|
|
@@ -3261,11 +3336,36 @@ var Chat = class {
|
|
|
3261
3336
|
} catch {
|
|
3262
3337
|
}
|
|
3263
3338
|
}
|
|
3264
|
-
|
|
3339
|
+
closeDialog(id) {
|
|
3340
|
+
const timed = this.dialogTimers.get(id);
|
|
3341
|
+
if (timed) {
|
|
3342
|
+
clearTimeout(timed.timer);
|
|
3343
|
+
this.dialogTimers.delete(id);
|
|
3344
|
+
}
|
|
3265
3345
|
this.dialogs = this.dialogs.filter((d) => d.id !== id);
|
|
3266
|
-
await this.pi.respondUi(id, response);
|
|
3267
3346
|
this.broadcast(true);
|
|
3268
3347
|
}
|
|
3348
|
+
async answerUi(id, response) {
|
|
3349
|
+
if (!this.isUp) throw new Error("Pi is not running. Retry the agent before answering.");
|
|
3350
|
+
if ((this.dialogTimers.get(id)?.deadline ?? Infinity) <= Date.now()) this.closeDialog(id);
|
|
3351
|
+
const dialog = this.dialogs.find((d) => d.id === id);
|
|
3352
|
+
if (!dialog) throw new Error("This decision is no longer pending. Refresh the chat.");
|
|
3353
|
+
if (this.answeringUi.has(id)) throw new Error("An answer to this decision is already being submitted.");
|
|
3354
|
+
if (!response || typeof response !== "object" || Array.isArray(response)) throw new Error("Invalid decision response.");
|
|
3355
|
+
if (response.cancelled !== true) {
|
|
3356
|
+
if (dialog.method === "select" && (typeof response.value !== "string" || !dialog.options?.includes(response.value))) throw new Error("Choose one of the available options.");
|
|
3357
|
+
if (dialog.method === "confirm" && typeof response.confirmed !== "boolean") throw new Error("A confirmation is required.");
|
|
3358
|
+
if (["input", "editor"].includes(dialog.method) && typeof response.value !== "string") throw new Error("A text response is required.");
|
|
3359
|
+
}
|
|
3360
|
+
this.answeringUi.add(id);
|
|
3361
|
+
try {
|
|
3362
|
+
await this.pi.respondUi(id, response);
|
|
3363
|
+
if (!this.isUp || !this.dialogs.some((d) => d.id === id)) throw new Error("This decision is no longer pending. Refresh the chat.");
|
|
3364
|
+
this.closeDialog(id);
|
|
3365
|
+
} finally {
|
|
3366
|
+
this.answeringUi.delete(id);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3269
3369
|
async abort() {
|
|
3270
3370
|
await this.pi.send({ type: "abort" }).catch(() => {
|
|
3271
3371
|
});
|
|
@@ -3337,7 +3437,7 @@ var Chat = class {
|
|
|
3337
3437
|
live: this.live,
|
|
3338
3438
|
runs,
|
|
3339
3439
|
model: this.model,
|
|
3340
|
-
error: this.pi.running ? void 0 : this.
|
|
3440
|
+
error: this.pi.running ? this.refreshError || void 0 : this.exitError || this.refreshError || void 0,
|
|
3341
3441
|
questions: runs.flatMap((r) => r.questions),
|
|
3342
3442
|
dialogs: this.dialogs,
|
|
3343
3443
|
notices: this.notices.slice(-6),
|
|
@@ -3353,11 +3453,17 @@ function listChats(sessionDir, cwd = process.cwd()) {
|
|
|
3353
3453
|
const full = path2.join(sessionDir, f);
|
|
3354
3454
|
let title;
|
|
3355
3455
|
let firstUser;
|
|
3456
|
+
let recordedCwd;
|
|
3457
|
+
let sessionId;
|
|
3356
3458
|
const entries = [];
|
|
3357
3459
|
try {
|
|
3358
3460
|
for (const line of fs2.readFileSync(full, "utf8").split("\n")) {
|
|
3359
3461
|
if (!line.trim()) continue;
|
|
3360
3462
|
const e = JSON.parse(line);
|
|
3463
|
+
if (e.type === "session" && typeof e.cwd === "string") {
|
|
3464
|
+
recordedCwd = e.cwd;
|
|
3465
|
+
sessionId = e.id;
|
|
3466
|
+
}
|
|
3361
3467
|
entries.push(e);
|
|
3362
3468
|
if ((e.type === "session_name" || e.type === "name") && e.name) title = String(e.name);
|
|
3363
3469
|
if (!firstUser && e.type === "message" && e.message?.role === "user") {
|
|
@@ -3368,15 +3474,23 @@ function listChats(sessionDir, cwd = process.cwd()) {
|
|
|
3368
3474
|
} catch {
|
|
3369
3475
|
}
|
|
3370
3476
|
summary = summarize(cwd, entries);
|
|
3371
|
-
return { id: path2.basename(f, ".jsonl").replace(/^.*_/, ""), last: fs2.statSync(full).mtimeMs, title: title ?? (firstUser ? firstUser.replace(/\s+/g, " ").slice(0, 46) : void 0), summary };
|
|
3477
|
+
return { id: path2.basename(f, ".jsonl").replace(/^.*_/, ""), last: fs2.statSync(full).mtimeMs, title: title ?? (firstUser ? firstUser.replace(/\s+/g, " ").slice(0, 46) : void 0), summary, cwd: recordedCwd, sessionId };
|
|
3372
3478
|
});
|
|
3373
3479
|
} catch {
|
|
3374
3480
|
return [];
|
|
3375
3481
|
}
|
|
3376
|
-
|
|
3482
|
+
const canonical2 = (p) => {
|
|
3483
|
+
try {
|
|
3484
|
+
return fs2.realpathSync(p);
|
|
3485
|
+
} catch {
|
|
3486
|
+
return path2.resolve(p);
|
|
3487
|
+
}
|
|
3488
|
+
};
|
|
3489
|
+
return files.filter((f) => /^chat-/.test(f.id) && (f.sessionId === void 0 || f.sessionId === f.id) && f.cwd && canonical2(f.cwd) === canonical2(cwd)).sort((a, b) => b.last - a.last).map((f) => ({ id: f.id, title: f.title ?? goalName("New chat"), ts: f.last, last: f.last, summary: f.summary }));
|
|
3377
3490
|
}
|
|
3378
3491
|
var ChatManager = class {
|
|
3379
3492
|
chats = /* @__PURE__ */ new Map();
|
|
3493
|
+
operations = /* @__PURE__ */ new Map();
|
|
3380
3494
|
listeners = /* @__PURE__ */ new Set();
|
|
3381
3495
|
base;
|
|
3382
3496
|
/** Where a chat lives. Set by the server from the workspace registry, which owns the persistence. */
|
|
@@ -3400,33 +3514,66 @@ var ChatManager = class {
|
|
|
3400
3514
|
get(id) {
|
|
3401
3515
|
return this.chats.get(id) ?? null;
|
|
3402
3516
|
}
|
|
3403
|
-
async
|
|
3404
|
-
const
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3517
|
+
async serialize(id, operation) {
|
|
3518
|
+
const result = (this.operations.get(id) ?? Promise.resolve()).then(operation);
|
|
3519
|
+
const settled = result.then(() => {
|
|
3520
|
+
}, () => {
|
|
3521
|
+
});
|
|
3522
|
+
this.operations.set(id, settled);
|
|
3523
|
+
try {
|
|
3524
|
+
return await result;
|
|
3525
|
+
} finally {
|
|
3526
|
+
if (this.operations.get(id) === settled) this.operations.delete(id);
|
|
3408
3527
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3528
|
+
}
|
|
3529
|
+
open(id) {
|
|
3530
|
+
return this.serialize(id, async () => {
|
|
3531
|
+
const where = this.bind(id);
|
|
3532
|
+
const existing = this.chats.get(id);
|
|
3533
|
+
if (existing && existing.workspacePath !== where.cwd) throw new Error("chat directory does not match its Pi session");
|
|
3534
|
+
if (existing) return existing;
|
|
3535
|
+
const chat = new Chat(id, { ...this.base, ...where, onUpdate: (c) => {
|
|
3536
|
+
for (const fn of this.listeners) {
|
|
3537
|
+
try {
|
|
3538
|
+
fn(c);
|
|
3539
|
+
} catch {
|
|
3540
|
+
}
|
|
3415
3541
|
}
|
|
3416
|
-
}
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3542
|
+
} });
|
|
3543
|
+
this.chats.set(id, chat);
|
|
3544
|
+
await chat.start();
|
|
3545
|
+
return chat;
|
|
3546
|
+
});
|
|
3421
3547
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3548
|
+
retry(id) {
|
|
3549
|
+
return this.serialize(id, async () => {
|
|
3550
|
+
const where = this.bind(id);
|
|
3551
|
+
const existing = this.chats.get(id);
|
|
3552
|
+
if (existing?.isUp) throw new Error("chat is already running");
|
|
3553
|
+
await existing?.stop();
|
|
3554
|
+
const chat = new Chat(id, { ...this.base, ...where, onUpdate: (c) => {
|
|
3555
|
+
for (const fn of this.listeners) {
|
|
3556
|
+
try {
|
|
3557
|
+
fn(c);
|
|
3558
|
+
} catch {
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
} });
|
|
3562
|
+
this.chats.set(id, chat);
|
|
3563
|
+
await chat.start();
|
|
3564
|
+
return chat;
|
|
3565
|
+
});
|
|
3566
|
+
}
|
|
3567
|
+
close(id, afterClose) {
|
|
3568
|
+
return this.serialize(id, async () => {
|
|
3569
|
+
const chat = this.chats.get(id);
|
|
3570
|
+
await chat?.stop();
|
|
3571
|
+
this.chats.delete(id);
|
|
3572
|
+
afterClose?.();
|
|
3573
|
+
});
|
|
3426
3574
|
}
|
|
3427
3575
|
async closeAll() {
|
|
3428
|
-
await Promise.all([...this.chats.
|
|
3429
|
-
this.chats.clear();
|
|
3576
|
+
await Promise.all([.../* @__PURE__ */ new Set([...this.chats.keys(), ...this.operations.keys()])].map((id) => this.close(id)));
|
|
3430
3577
|
}
|
|
3431
3578
|
openChats() {
|
|
3432
3579
|
return [...this.chats.values()];
|
|
@@ -4180,13 +4327,52 @@ import fs7 from "node:fs";
|
|
|
4180
4327
|
import path7 from "node:path";
|
|
4181
4328
|
import { execFileSync } from "node:child_process";
|
|
4182
4329
|
var slug = (p) => path7.basename(p).replace(/[^a-z0-9._-]+/gi, "-").toLowerCase() || "repo";
|
|
4330
|
+
var canonical = (p) => {
|
|
4331
|
+
const abs = path7.resolve(p);
|
|
4332
|
+
try {
|
|
4333
|
+
return fs7.realpathSync(abs);
|
|
4334
|
+
} catch {
|
|
4335
|
+
return abs;
|
|
4336
|
+
}
|
|
4337
|
+
};
|
|
4338
|
+
var available = (p) => {
|
|
4339
|
+
try {
|
|
4340
|
+
return fs7.statSync(p).isDirectory();
|
|
4341
|
+
} catch {
|
|
4342
|
+
return false;
|
|
4343
|
+
}
|
|
4344
|
+
};
|
|
4345
|
+
function header(file) {
|
|
4346
|
+
const fd = fs7.openSync(file, "r");
|
|
4347
|
+
try {
|
|
4348
|
+
const chunks = [];
|
|
4349
|
+
let size = 0;
|
|
4350
|
+
while (size < 1024 * 1024) {
|
|
4351
|
+
const buf = Buffer.alloc(4096);
|
|
4352
|
+
const n = fs7.readSync(fd, buf, 0, buf.length, null);
|
|
4353
|
+
if (!n) break;
|
|
4354
|
+
const end = buf.subarray(0, n).indexOf(10);
|
|
4355
|
+
chunks.push(buf.subarray(0, end < 0 ? n : end));
|
|
4356
|
+
size += end < 0 ? n : end;
|
|
4357
|
+
if (end >= 0) break;
|
|
4358
|
+
}
|
|
4359
|
+
if (size >= 1024 * 1024) throw new Error("session header too large");
|
|
4360
|
+
const result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4361
|
+
if (typeof result.cwd !== "string" || !result.cwd || result.id !== void 0 && typeof result.id !== "string") throw new Error("invalid session header");
|
|
4362
|
+
return result;
|
|
4363
|
+
} finally {
|
|
4364
|
+
fs7.closeSync(fd);
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4183
4367
|
var Workspaces = class {
|
|
4184
|
-
/** Where workspaces.json and the per-repo session dirs live. */
|
|
4185
4368
|
root;
|
|
4186
4369
|
file;
|
|
4187
4370
|
data;
|
|
4188
4371
|
byId = /* @__PURE__ */ new Map();
|
|
4189
4372
|
chatMap;
|
|
4373
|
+
files = /* @__PURE__ */ new Map();
|
|
4374
|
+
// A new chat may not have written its first session file yet. This is never persisted.
|
|
4375
|
+
pending = /* @__PURE__ */ new Set();
|
|
4190
4376
|
constructor({ root, cwd }) {
|
|
4191
4377
|
this.root = root;
|
|
4192
4378
|
this.file = path7.join(root, "workspaces.json");
|
|
@@ -4194,139 +4380,185 @@ var Workspaces = class {
|
|
|
4194
4380
|
let data = { workspaces: [], chats: {} };
|
|
4195
4381
|
if (fs7.existsSync(this.file)) {
|
|
4196
4382
|
try {
|
|
4197
|
-
|
|
4383
|
+
const parsed = JSON.parse(fs7.readFileSync(this.file, "utf8"));
|
|
4384
|
+
if (!Array.isArray(parsed.workspaces) || !parsed.chats || typeof parsed.chats !== "object" || Array.isArray(parsed.chats)) throw new Error("invalid registry");
|
|
4385
|
+
data = parsed;
|
|
4198
4386
|
} catch {
|
|
4199
|
-
|
|
4200
|
-
fs7.renameSync(this.file, `${this.file}.corrupt-${Date.now()}`);
|
|
4201
|
-
} catch {
|
|
4202
|
-
}
|
|
4203
|
-
console.error(`[workspaces] could not read ${this.file}; kept a copy and rebuilding from sessions`);
|
|
4387
|
+
fs7.renameSync(this.file, `${this.file}.corrupt-${Date.now()}`);
|
|
4204
4388
|
}
|
|
4205
4389
|
}
|
|
4206
|
-
this.data = {
|
|
4207
|
-
this.chatMap = new Map(Object.entries(
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4390
|
+
this.data = { version: 2, workspaces: [], chats: {}, default: data.default };
|
|
4391
|
+
this.chatMap = new Map(Object.entries(data.chats));
|
|
4392
|
+
if (data.files && typeof data.files === "object") {
|
|
4393
|
+
for (const [chat, files] of Object.entries(data.files)) {
|
|
4394
|
+
if (Array.isArray(files) && files.every((f) => typeof f === "string")) this.files.set(chat, new Set(files));
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
for (const w of data.workspaces) {
|
|
4398
|
+
if (!w || typeof w.id !== "string" || typeof w.path !== "string" || this.byId.has(w.id)) continue;
|
|
4399
|
+
const p = canonical(w.path);
|
|
4400
|
+
const legacy = path7.join(p, ".pi", "loops-ui", "sessions");
|
|
4401
|
+
const sessionDir = w.sessionDir ?? (fs7.existsSync(legacy) ? legacy : path7.join(root, "workspaces", w.id, "sessions"));
|
|
4402
|
+
this.byId.set(w.id, { id: w.id, path: p, sessionDir, title: path7.basename(p) || p, open: w.open !== false, available: available(p) });
|
|
4403
|
+
}
|
|
4404
|
+
this.recoverSessions(void 0, data.version !== 2);
|
|
4405
|
+
if (cwd && ![...this.byId.values()].some((w) => w.path === canonical(cwd))) {
|
|
4406
|
+
this.add(cwd);
|
|
4407
|
+
}
|
|
4211
4408
|
this.save();
|
|
4212
4409
|
}
|
|
4213
4410
|
save() {
|
|
4411
|
+
this.data.workspaces = [...this.byId.values()].map(({ id, path: p, sessionDir, open }) => ({ id, path: p, sessionDir, open }));
|
|
4214
4412
|
this.data.chats = Object.fromEntries(this.chatMap);
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
} catch {
|
|
4220
|
-
}
|
|
4413
|
+
this.data.files = Object.fromEntries([...this.files].map(([chat, files]) => [chat, [...files]]));
|
|
4414
|
+
const tmp = `${this.file}.tmp`;
|
|
4415
|
+
fs7.writeFileSync(tmp, JSON.stringify(this.data, null, 2) + "\n");
|
|
4416
|
+
fs7.renameSync(tmp, this.file);
|
|
4221
4417
|
}
|
|
4222
4418
|
get defaultId() {
|
|
4223
|
-
return this.
|
|
4419
|
+
return this.get()?.id ?? "";
|
|
4224
4420
|
}
|
|
4225
4421
|
list() {
|
|
4226
|
-
return [...this.byId.values()];
|
|
4422
|
+
return [...this.byId.values()].map((w) => ({ ...w, available: available(w.path) }));
|
|
4227
4423
|
}
|
|
4228
4424
|
get(id) {
|
|
4229
|
-
|
|
4425
|
+
const w = id != null ? this.byId.get(id) : this.byId.get(this.data.default ?? "")?.open ? this.byId.get(this.data.default) : [...this.byId.values()].find((x) => x.open);
|
|
4426
|
+
return w ? { ...w, available: available(w.path) } : null;
|
|
4230
4427
|
}
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
const abs = path7.resolve(dir);
|
|
4234
|
-
const existing = [...this.byId.values()].find((w) => w.path === abs);
|
|
4428
|
+
register(p, sessionDir, preferredId) {
|
|
4429
|
+
const existing = [...this.byId.values()].find((w) => w.path === p);
|
|
4235
4430
|
if (existing) return existing;
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
for (let i = 2; this.byId.has(id); i++) id = `${slug(abs)}-${i}`;
|
|
4241
|
-
const legacy = path7.join(abs, ".pi", "loops-ui", "sessions");
|
|
4242
|
-
const sessionDir = fs7.existsSync(legacy) ? legacy : path7.join(this.root, "workspaces", id, "sessions");
|
|
4243
|
-
const ws = { id, path: abs, sessionDir, title: path7.basename(abs) || abs };
|
|
4244
|
-
fs7.mkdirSync(ws.sessionDir, { recursive: true });
|
|
4431
|
+
let id = preferredId && !this.byId.has(preferredId) ? preferredId : slug(p);
|
|
4432
|
+
for (let i = 2; this.byId.has(id); i++) id = `${slug(p)}-${i}`;
|
|
4433
|
+
const legacy = path7.join(p, ".pi", "loops-ui", "sessions");
|
|
4434
|
+
const ws = { id, path: p, sessionDir: sessionDir ?? (fs7.existsSync(legacy) ? legacy : path7.join(this.root, "workspaces", id, "sessions")), title: path7.basename(p) || p, open: true, available: available(p) };
|
|
4245
4435
|
this.byId.set(id, ws);
|
|
4246
|
-
if (persist) {
|
|
4247
|
-
this.data.workspaces = [...this.data.workspaces.filter((w) => w.path !== abs), { id, path: abs }];
|
|
4248
|
-
this.save();
|
|
4249
|
-
}
|
|
4250
4436
|
return ws;
|
|
4251
4437
|
}
|
|
4252
|
-
|
|
4438
|
+
add(dir) {
|
|
4439
|
+
const p = canonical(dir);
|
|
4440
|
+
if (!available(p)) throw new Error(`${p} is not a directory`);
|
|
4441
|
+
const ws = this.register(p);
|
|
4442
|
+
ws.open = true;
|
|
4443
|
+
fs7.mkdirSync(ws.sessionDir, { recursive: true });
|
|
4444
|
+
if (!this.data.default || !this.byId.get(this.data.default)?.open) this.data.default = ws.id;
|
|
4445
|
+
this.recoverSessions([ws.sessionDir], false);
|
|
4446
|
+
this.save();
|
|
4447
|
+
return this.get(ws.id);
|
|
4448
|
+
}
|
|
4449
|
+
/** Re-scan sessions, including legacy session directories. */
|
|
4253
4450
|
restore() {
|
|
4254
|
-
|
|
4255
|
-
try {
|
|
4256
|
-
this.add(w.path);
|
|
4257
|
-
} catch {
|
|
4258
|
-
}
|
|
4259
|
-
}
|
|
4260
|
-
this.recoverSessions();
|
|
4451
|
+
this.recoverSessions(void 0, false);
|
|
4261
4452
|
this.save();
|
|
4262
4453
|
}
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
let ids = [];
|
|
4270
|
-
try {
|
|
4271
|
-
ids = fs7.readdirSync(root);
|
|
4272
|
-
} catch {
|
|
4273
|
-
return;
|
|
4274
|
-
}
|
|
4275
|
-
for (const id of ids) {
|
|
4276
|
-
const dir = path7.join(root, id, "sessions");
|
|
4277
|
-
let files = [];
|
|
4454
|
+
recoverSessions(onlyDirs, rebind = true) {
|
|
4455
|
+
const dirs = new Set(onlyDirs ?? [...this.byId.values()].map((w) => w.sessionDir));
|
|
4456
|
+
const base = path7.join(this.root, "workspaces");
|
|
4457
|
+
if (!onlyDirs && fs7.existsSync(base)) for (const id of fs7.readdirSync(base)) dirs.add(path7.join(base, id, "sessions"));
|
|
4458
|
+
for (const dir of dirs) {
|
|
4459
|
+
let names;
|
|
4278
4460
|
try {
|
|
4279
|
-
|
|
4461
|
+
names = fs7.readdirSync(dir).filter((n) => n.endsWith(".jsonl"));
|
|
4280
4462
|
} catch {
|
|
4281
4463
|
continue;
|
|
4282
4464
|
}
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
} catch {
|
|
4292
|
-
}
|
|
4293
|
-
}
|
|
4294
|
-
let ws = this.byId.get(id) ?? null;
|
|
4295
|
-
if (!ws && cwd) {
|
|
4465
|
+
for (const name of names) {
|
|
4466
|
+
const chat = path7.basename(name, ".jsonl").replace(/^.*_/, "");
|
|
4467
|
+
if (!chat.startsWith("chat-")) continue;
|
|
4468
|
+
const file = path7.join(dir, name);
|
|
4469
|
+
const files = this.files.get(chat) ?? /* @__PURE__ */ new Set();
|
|
4470
|
+
files.add(file);
|
|
4471
|
+
this.files.set(chat, files);
|
|
4472
|
+
let cwd;
|
|
4296
4473
|
try {
|
|
4297
|
-
|
|
4298
|
-
if (
|
|
4474
|
+
const head = header(file);
|
|
4475
|
+
if (head.id !== void 0 && head.id !== chat) continue;
|
|
4476
|
+
cwd = canonical(head.cwd);
|
|
4299
4477
|
} catch {
|
|
4478
|
+
continue;
|
|
4300
4479
|
}
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
if (/^chat-/.test(chat) && !this.chatMap.has(chat)) this.chatMap.set(chat, ws.id);
|
|
4480
|
+
const parent = path7.dirname(dir);
|
|
4481
|
+
const preferred = path7.dirname(parent) === base ? path7.basename(parent) : void 0;
|
|
4482
|
+
const ws = this.register(cwd, dir, preferred);
|
|
4483
|
+
if (rebind || !this.chatMap.has(chat)) this.chatMap.set(chat, ws.id);
|
|
4306
4484
|
}
|
|
4307
4485
|
}
|
|
4308
4486
|
}
|
|
4309
4487
|
remove(id) {
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
for (const [chat, ws] of this.chatMap) if (ws === id) this.chatMap.delete(chat);
|
|
4488
|
+
const ws = this.byId.get(id);
|
|
4489
|
+
if (!ws) throw new Error(`unknown workspace: ${id}`);
|
|
4490
|
+
ws.open = false;
|
|
4314
4491
|
this.save();
|
|
4315
4492
|
}
|
|
4316
|
-
|
|
4493
|
+
hasChat(chat) {
|
|
4494
|
+
return this.chatMap.has(chat) && this.byId.has(this.chatMap.get(chat));
|
|
4495
|
+
}
|
|
4317
4496
|
ofChat(chat) {
|
|
4318
|
-
|
|
4497
|
+
const ws = this.get(this.chatMap.get(chat) ?? "");
|
|
4498
|
+
if (!ws) throw new Error(`unknown chat: ${chat}`);
|
|
4499
|
+
const files = this.candidates(chat, ws);
|
|
4500
|
+
if (files.length === 0 && !this.pending.has(chat)) throw new Error(`missing session for chat: ${chat}`);
|
|
4501
|
+
if (files.length > 1) throw new Error(`ambiguous session for chat: ${chat}`);
|
|
4502
|
+
if (files.length === 1 && !this.validFile(files[0], chat, ws)) throw new Error(`invalid session for chat: ${chat}`);
|
|
4503
|
+
return ws;
|
|
4319
4504
|
}
|
|
4320
4505
|
bindChat(chat, ws) {
|
|
4321
|
-
if (this.byId.has(ws)) {
|
|
4322
|
-
|
|
4506
|
+
if (!this.byId.has(ws)) throw new Error(`unknown workspace: ${ws}`);
|
|
4507
|
+
const existing = this.chatMap.get(chat);
|
|
4508
|
+
if (existing && existing !== ws) throw new Error(`chat ${chat} is already bound to ${existing}`);
|
|
4509
|
+
if (!existing) this.pending.add(chat);
|
|
4510
|
+
this.chatMap.set(chat, ws);
|
|
4511
|
+
this.save();
|
|
4512
|
+
}
|
|
4513
|
+
unbindChat(chat) {
|
|
4514
|
+
if (this.chatMap.delete(chat)) {
|
|
4515
|
+
this.pending.delete(chat);
|
|
4516
|
+
this.files.delete(chat);
|
|
4323
4517
|
this.save();
|
|
4324
4518
|
}
|
|
4325
4519
|
}
|
|
4326
|
-
|
|
4327
|
-
|
|
4520
|
+
/** All known session directories for this workspace, including recovered secondary directories. */
|
|
4521
|
+
sessionDirs(id) {
|
|
4522
|
+
const ws = this.get(id);
|
|
4523
|
+
if (!ws) return [];
|
|
4524
|
+
const dirs = /* @__PURE__ */ new Set([ws.sessionDir]);
|
|
4525
|
+
for (const [chat, files] of this.files) {
|
|
4526
|
+
if (this.chatMap.get(chat) === id) for (const file of files) dirs.add(path7.dirname(file));
|
|
4527
|
+
}
|
|
4528
|
+
return [...dirs];
|
|
4529
|
+
}
|
|
4530
|
+
candidates(chat, ws) {
|
|
4531
|
+
const files = new Set(this.files.get(chat) ?? []);
|
|
4532
|
+
const oldSize = files.size;
|
|
4533
|
+
for (const dir of this.sessionDirs(ws.id)) {
|
|
4534
|
+
try {
|
|
4535
|
+
for (const name of fs7.readdirSync(dir)) {
|
|
4536
|
+
if (name.endsWith(".jsonl") && path7.basename(name, ".jsonl").replace(/^.*_/, "") === chat) files.add(path7.join(dir, name));
|
|
4537
|
+
}
|
|
4538
|
+
} catch {
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
if (files.size) this.pending.delete(chat);
|
|
4542
|
+
if (files.size !== oldSize) {
|
|
4543
|
+
this.files.set(chat, files);
|
|
4544
|
+
this.save();
|
|
4545
|
+
}
|
|
4546
|
+
return [...files];
|
|
4547
|
+
}
|
|
4548
|
+
validFile(file, chat, ws) {
|
|
4549
|
+
try {
|
|
4550
|
+
const h = header(file);
|
|
4551
|
+
return (h.id === void 0 || h.id === chat) && canonical(h.cwd) === ws.path;
|
|
4552
|
+
} catch {
|
|
4553
|
+
return false;
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
sessionFile(chat) {
|
|
4557
|
+
if (!this.hasChat(chat)) return null;
|
|
4558
|
+
const ws = this.get(this.chatMap.get(chat));
|
|
4559
|
+
const files = this.candidates(chat, ws);
|
|
4560
|
+
return files.length === 1 && this.validFile(files[0], chat, ws) ? files[0] : null;
|
|
4328
4561
|
}
|
|
4329
|
-
/** A repo is loopable when git can name a commit in it; that is what the sidebar shows. */
|
|
4330
4562
|
static loopable(dir) {
|
|
4331
4563
|
try {
|
|
4332
4564
|
execFileSync("git", ["rev-parse", "HEAD"], { cwd: dir, stdio: "pipe" });
|
|
@@ -4337,9 +4569,48 @@ var Workspaces = class {
|
|
|
4337
4569
|
}
|
|
4338
4570
|
};
|
|
4339
4571
|
|
|
4572
|
+
// server/directories.ts
|
|
4573
|
+
import fs8 from "node:fs/promises";
|
|
4574
|
+
import os4 from "node:os";
|
|
4575
|
+
import path8 from "node:path";
|
|
4576
|
+
import { execFile } from "node:child_process";
|
|
4577
|
+
function directoryPath(value) {
|
|
4578
|
+
if (typeof value !== "string" || !value.trim()) throw new Error("an absolute directory path is required");
|
|
4579
|
+
const expanded = value.trim().replace(/^~(?=\/|$)/, os4.homedir());
|
|
4580
|
+
if (!path8.isAbsolute(expanded)) throw new Error("use an absolute path on the server, or ~/ for its home directory");
|
|
4581
|
+
return expanded;
|
|
4582
|
+
}
|
|
4583
|
+
async function browseDirectories(value, hidden = false) {
|
|
4584
|
+
const requested = value === void 0 ? os4.homedir() : directoryPath(value);
|
|
4585
|
+
const dir = await fs8.realpath(requested);
|
|
4586
|
+
const entries = [];
|
|
4587
|
+
let scanned = 0, truncated = false;
|
|
4588
|
+
const handle = await fs8.opendir(dir);
|
|
4589
|
+
for await (const entry of handle) {
|
|
4590
|
+
if (++scanned > 1e4) {
|
|
4591
|
+
truncated = true;
|
|
4592
|
+
break;
|
|
4593
|
+
}
|
|
4594
|
+
if (!hidden && entry.name.startsWith(".")) continue;
|
|
4595
|
+
const full = path8.join(dir, entry.name);
|
|
4596
|
+
const symlink = entry.isSymbolicLink();
|
|
4597
|
+
if (!entry.isDirectory() && !(symlink && await fs8.stat(full).then((s) => s.isDirectory(), () => false))) continue;
|
|
4598
|
+
entries.push({ name: entry.name, path: full, symlink });
|
|
4599
|
+
if (entries.length >= 500) {
|
|
4600
|
+
truncated = true;
|
|
4601
|
+
break;
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
4605
|
+
const loopable = await new Promise((resolve) => {
|
|
4606
|
+
execFile("git", ["rev-parse", "--verify", "HEAD"], { cwd: dir, timeout: 2e3 }, (e) => resolve(!e));
|
|
4607
|
+
});
|
|
4608
|
+
return { path: dir, parent: path8.dirname(dir) === dir ? null : path8.dirname(dir), home: os4.homedir(), entries, truncated, loopable };
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4340
4611
|
// server/index.ts
|
|
4341
|
-
var HERE =
|
|
4342
|
-
var APP_ROOT =
|
|
4612
|
+
var HERE = path9.dirname(fileURLToPath(import.meta.url));
|
|
4613
|
+
var APP_ROOT = path9.resolve(HERE, "..");
|
|
4343
4614
|
var arg = (name, fallback) => {
|
|
4344
4615
|
const i = process.argv.indexOf(`--${name}`);
|
|
4345
4616
|
return i >= 0 ? process.argv[i + 1] : process.env[`LOOPS_UI_${name.toUpperCase().replace(/-/g, "_")}`] ?? fallback;
|
|
@@ -4347,16 +4618,24 @@ var arg = (name, fallback) => {
|
|
|
4347
4618
|
var PORT = Number(arg("port", "8001"));
|
|
4348
4619
|
var HOST = arg("host", "127.0.0.1");
|
|
4349
4620
|
var LOOPBACK = ["127.0.0.1", "localhost", "::1"].includes(HOST);
|
|
4350
|
-
var
|
|
4621
|
+
var PUBLIC_ORIGINS = [...new Set((arg("public-origin", "") ?? "").split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
|
|
4622
|
+
const url = new URL(s);
|
|
4623
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
4624
|
+
throw new Error(`--public-origin must contain HTTP(S) origins, not paths or credentials: ${s}`);
|
|
4625
|
+
}
|
|
4626
|
+
return url.origin;
|
|
4627
|
+
}))];
|
|
4628
|
+
var SEED = arg("cwd");
|
|
4629
|
+
var CWD = SEED ? path9.resolve(SEED) : null;
|
|
4351
4630
|
var DEV = process.argv.includes("--dev") || process.env.NODE_ENV === "development";
|
|
4352
4631
|
var VITE = arg("vite", "http://localhost:5173");
|
|
4353
|
-
var PI = arg("pi", "pi");
|
|
4354
|
-
var DATA =
|
|
4632
|
+
var PI = arg("pi", path9.join(APP_ROOT, "bin", "pi-rpc.mjs"));
|
|
4633
|
+
var DATA = path9.resolve(arg("data", process.env.LOOPS_UI_DATA ?? path9.join(process.env.HOME ?? process.cwd(), ".pi", "loops-ui")));
|
|
4355
4634
|
var MODEL = arg("model", void 0) || null;
|
|
4356
|
-
var EXTENSIONS = (arg("extensions",
|
|
4635
|
+
var EXTENSIONS = (arg("extensions", path9.resolve(APP_ROOT, "../pi-loops/extensions/loop.ts")) ?? "").split(",").filter(Boolean);
|
|
4357
4636
|
function prerequisites() {
|
|
4358
4637
|
const out = [];
|
|
4359
|
-
const onPath = PI.includes("/") || /^[A-Za-z]:\\/.test(PI) ?
|
|
4638
|
+
const onPath = PI.includes("/") || /^[A-Za-z]:\\/.test(PI) ? fs9.existsSync(PI) : (() => {
|
|
4360
4639
|
try {
|
|
4361
4640
|
execFileSync2(process.platform === "win32" ? "where" : "which", [PI], { stdio: ["ignore", "pipe", "ignore"] });
|
|
4362
4641
|
return true;
|
|
@@ -4366,18 +4645,18 @@ function prerequisites() {
|
|
|
4366
4645
|
})();
|
|
4367
4646
|
if (!onPath) out.push({ name: "pi", detail: `\`${PI}\` is not on PATH`, fix: "install Pi, or pass --pi <path to the pi binary>" });
|
|
4368
4647
|
for (const e of EXTENSIONS) {
|
|
4369
|
-
if (!
|
|
4648
|
+
if (!fs9.existsSync(e)) out.push({ name: "extension", detail: `${e} does not exist`, fix: "pi install npm:@computesdk/pi-loops, or pass --extensions <path to extensions/loop.ts>" });
|
|
4370
4649
|
}
|
|
4371
4650
|
return out;
|
|
4372
4651
|
}
|
|
4373
4652
|
var PROBLEMS = prerequisites();
|
|
4374
|
-
|
|
4653
|
+
fs9.mkdirSync(DATA, { recursive: true });
|
|
4375
4654
|
var TOKEN = (() => {
|
|
4376
4655
|
const explicit = arg("token", void 0);
|
|
4377
|
-
const file =
|
|
4656
|
+
const file = path9.join(DATA, "token");
|
|
4378
4657
|
const stored = (() => {
|
|
4379
4658
|
try {
|
|
4380
|
-
return
|
|
4659
|
+
return fs9.readFileSync(file, "utf8").trim() || null;
|
|
4381
4660
|
} catch {
|
|
4382
4661
|
return null;
|
|
4383
4662
|
}
|
|
@@ -4385,7 +4664,7 @@ var TOKEN = (() => {
|
|
|
4385
4664
|
if (explicit) {
|
|
4386
4665
|
if (!LOOPBACK) {
|
|
4387
4666
|
try {
|
|
4388
|
-
|
|
4667
|
+
fs9.writeFileSync(file, explicit + "\n", { mode: 384 });
|
|
4389
4668
|
} catch {
|
|
4390
4669
|
}
|
|
4391
4670
|
}
|
|
@@ -4394,32 +4673,53 @@ var TOKEN = (() => {
|
|
|
4394
4673
|
if (stored) return stored;
|
|
4395
4674
|
if (LOOPBACK) return null;
|
|
4396
4675
|
const made = randomBytes(24).toString("base64url");
|
|
4397
|
-
|
|
4676
|
+
fs9.writeFileSync(file, made + "\n", { mode: 384 });
|
|
4398
4677
|
return made;
|
|
4399
4678
|
})();
|
|
4400
4679
|
if (!LOOPBACK && !TOKEN) {
|
|
4401
4680
|
console.error(`refusing to listen on ${HOST} without a token: this UI drives an agent that has bash. Pass --token <secret> (or let it generate one in ${DATA}/token).`);
|
|
4402
4681
|
process.exit(2);
|
|
4403
4682
|
}
|
|
4404
|
-
var workspaces = new Workspaces({ root: DATA, cwd: CWD });
|
|
4683
|
+
var workspaces = new Workspaces({ root: DATA, ...CWD ? { cwd: CWD } : {} });
|
|
4684
|
+
var requireWorkspace = (id) => {
|
|
4685
|
+
const ws = workspaces.get(id);
|
|
4686
|
+
if (!ws) throw new HTTPException(404, { message: id ? `unknown workspace: ${id}` : "choose a workspace first" });
|
|
4687
|
+
if (!ws.available) throw new HTTPException(409, { message: `workspace directory is unavailable: ${ws.path}` });
|
|
4688
|
+
return ws;
|
|
4689
|
+
};
|
|
4690
|
+
var requireChatWorkspace = (id) => {
|
|
4691
|
+
if (!workspaces.hasChat(id)) throw new HTTPException(404, { message: `unknown chat: ${id}` });
|
|
4692
|
+
let ws;
|
|
4693
|
+
try {
|
|
4694
|
+
ws = workspaces.ofChat(id);
|
|
4695
|
+
} catch (e) {
|
|
4696
|
+
throw new HTTPException(409, { message: e.message });
|
|
4697
|
+
}
|
|
4698
|
+
return requireWorkspace(ws.id);
|
|
4699
|
+
};
|
|
4405
4700
|
var manager = new ChatManager({
|
|
4406
4701
|
piCommand: PI,
|
|
4407
4702
|
args: EXTENSIONS.flatMap((e) => ["-e", e]),
|
|
4408
4703
|
model: MODEL,
|
|
4409
4704
|
// a chat's repo comes from the registry: the browser never hands us a path
|
|
4410
4705
|
bind: (chat) => {
|
|
4411
|
-
const ws =
|
|
4412
|
-
return { cwd: ws.path, sessionDir: ws.sessionDir };
|
|
4706
|
+
const ws = requireChatWorkspace(chat);
|
|
4707
|
+
return { cwd: ws.path, sessionDir: ws.sessionDir, sessionFile: workspaces.sessionFile(chat) ?? void 0 };
|
|
4413
4708
|
}
|
|
4414
4709
|
});
|
|
4415
|
-
var wsOf = (chat) => chat ?
|
|
4710
|
+
var wsOf = (chat) => chat ? requireChatWorkspace(chat) : requireWorkspace();
|
|
4416
4711
|
var wsOfRun = (run3) => {
|
|
4417
|
-
const byIndex = workspaces.list().find((w) =>
|
|
4712
|
+
const byIndex = workspaces.list().find((w) => fs9.existsSync(path9.join(w.path, STATE_DIR, `${run3}.json`)));
|
|
4418
4713
|
if (byIndex) return byIndex;
|
|
4419
4714
|
const chat = manager.findRun(run3);
|
|
4420
|
-
|
|
4715
|
+
if (chat) return requireChatWorkspace(chat.id);
|
|
4716
|
+
throw new HTTPException(404, { message: `unknown run: ${run3}` });
|
|
4717
|
+
};
|
|
4718
|
+
var listWorkspaceChats = (ws) => {
|
|
4719
|
+
const all = workspaces.sessionDirs(ws.id).flatMap((sessionDir) => manager.list({ ...ws, sessionDir }));
|
|
4720
|
+
return [...new Map(all.map((chat) => [chat.id, chat])).values()].sort((a, b) => b.last - a.last);
|
|
4421
4721
|
};
|
|
4422
|
-
var chatsOf = (wsId) => wsId ?
|
|
4722
|
+
var chatsOf = (wsId) => wsId ? listWorkspaceChats(requireWorkspace(wsId)) : workspaces.list().filter((w) => w.available).flatMap(listWorkspaceChats);
|
|
4423
4723
|
var withLive = (state) => {
|
|
4424
4724
|
for (const run3 of state.runs ?? []) {
|
|
4425
4725
|
const url = liveUrl(run3.id);
|
|
@@ -4442,7 +4742,7 @@ var watchOf = (wsId) => {
|
|
|
4442
4742
|
if (!w) {
|
|
4443
4743
|
const read = () => {
|
|
4444
4744
|
try {
|
|
4445
|
-
return JSON.parse(
|
|
4745
|
+
return JSON.parse(fs9.readFileSync(path9.join(ws.path, ".pi", "loops.json"), "utf8"));
|
|
4446
4746
|
} catch {
|
|
4447
4747
|
return {};
|
|
4448
4748
|
}
|
|
@@ -4450,7 +4750,7 @@ var watchOf = (wsId) => {
|
|
|
4450
4750
|
w = { cwd: ws.path, accepted: read() };
|
|
4451
4751
|
watches.set(ws.id, w);
|
|
4452
4752
|
try {
|
|
4453
|
-
|
|
4753
|
+
fs9.watch(path9.join(ws.path, ".pi"), (_e, name) => {
|
|
4454
4754
|
if (name && name !== "loops.json") return;
|
|
4455
4755
|
clearTimeout(watchTimers.get(ws.id));
|
|
4456
4756
|
watchTimers.set(ws.id, setTimeout(() => checkConfig(ws.id), 120));
|
|
@@ -4472,10 +4772,10 @@ function notifyProposals() {
|
|
|
4472
4772
|
}
|
|
4473
4773
|
function checkConfig(wsId) {
|
|
4474
4774
|
const w = watchOf(wsId);
|
|
4475
|
-
const CONFIG =
|
|
4775
|
+
const CONFIG = path9.join(w.cwd, ".pi", "loops.json");
|
|
4476
4776
|
const raw2 = (() => {
|
|
4477
4777
|
try {
|
|
4478
|
-
return
|
|
4778
|
+
return fs9.readFileSync(CONFIG, "utf8");
|
|
4479
4779
|
} catch {
|
|
4480
4780
|
return "";
|
|
4481
4781
|
}
|
|
@@ -4496,13 +4796,13 @@ function checkConfig(wsId) {
|
|
|
4496
4796
|
notifyProposals();
|
|
4497
4797
|
}
|
|
4498
4798
|
var inside = (root, p) => {
|
|
4499
|
-
const abs =
|
|
4500
|
-
return abs === root || abs.startsWith(root +
|
|
4799
|
+
const abs = path9.resolve(root, p);
|
|
4800
|
+
return abs === root || abs.startsWith(root + path9.sep) ? abs : null;
|
|
4501
4801
|
};
|
|
4502
4802
|
var stateOf = (run3) => manager.findRun(run3)?.stateFor(run3) ?? readState(wsOfRun(run3).path, run3);
|
|
4503
4803
|
var app = new Hono3();
|
|
4504
|
-
var OWN_HOSTS = /* @__PURE__ */ new Set([`127.0.0.1:${PORT}`, `localhost:${PORT}`, `[::1]:${PORT}
|
|
4505
|
-
var OWN_ORIGINS = new Set([
|
|
4804
|
+
var OWN_HOSTS = /* @__PURE__ */ new Set([`127.0.0.1:${PORT}`, `localhost:${PORT}`, `[::1]:${PORT}`, ...PUBLIC_ORIGINS.map((o) => new URL(o).host)]);
|
|
4805
|
+
var OWN_ORIGINS = /* @__PURE__ */ new Set([`http://127.0.0.1:${PORT}`, `http://localhost:${PORT}`, `http://[::1]:${PORT}`, ...PUBLIC_ORIGINS]);
|
|
4506
4806
|
var ALLOWED_ORIGINS = (arg("allow-origin", "") ?? "").split(",").map((o) => o.trim()).filter(Boolean);
|
|
4507
4807
|
var originAllowed = (origin) => {
|
|
4508
4808
|
if (ALLOWED_ORIGINS.includes("*")) return true;
|
|
@@ -4540,6 +4840,11 @@ app.use("/api/v1/*", async (c, next) => {
|
|
|
4540
4840
|
await next();
|
|
4541
4841
|
});
|
|
4542
4842
|
var api = new Hono3();
|
|
4843
|
+
api.onError((error, c) => {
|
|
4844
|
+
if (error instanceof HTTPException) return c.json({ error: error.message }, error.status);
|
|
4845
|
+
console.error(error);
|
|
4846
|
+
return c.json({ error: error.message }, 500);
|
|
4847
|
+
});
|
|
4543
4848
|
api.get("/health", (c) => c.json({ ok: PROBLEMS.length === 0, problems: PROBLEMS, cwd: CWD, data: DATA, pi: PI, extensions: EXTENSIONS, workspaces: workspaces.list() }));
|
|
4544
4849
|
api.get("/credentials", (c) => c.json({ dir: agentDir2(), providers: credentialStatus() }));
|
|
4545
4850
|
api.post("/credentials", async (c) => {
|
|
@@ -4550,7 +4855,17 @@ api.post("/credentials", async (c) => {
|
|
|
4550
4855
|
if (!key) return c.json({ error: "a key is required" }, 400);
|
|
4551
4856
|
try {
|
|
4552
4857
|
const file = await writeApiKey(provider, key);
|
|
4553
|
-
|
|
4858
|
+
let model = typeof body?.model === "string" && body.model.trim() ? body.model.trim() : "";
|
|
4859
|
+
if (!model) {
|
|
4860
|
+
for (const chat of manager.openChats()) {
|
|
4861
|
+
const found = (await chat.availableModels()).find((m) => m.provider === provider);
|
|
4862
|
+
if (found) {
|
|
4863
|
+
model = found.id;
|
|
4864
|
+
break;
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
const verified = model ? { model, ...await verifyModel({ pi: PI, model, cwd: CWD ?? DATA }) } : null;
|
|
4554
4869
|
return c.json({ ok: true, provider, file, configured: credentialStatus().find((s) => s.id === provider)?.configured ?? true, verified });
|
|
4555
4870
|
} catch (e) {
|
|
4556
4871
|
return c.json({ error: String(e.message ?? e).slice(0, 200) }, 400);
|
|
@@ -4564,16 +4879,20 @@ api.get("/chats/:id/events", async (c) => {
|
|
|
4564
4879
|
return streamSSE(c, async (stream2) => {
|
|
4565
4880
|
const send = (event, data, cursor) => stream2.writeSSE({ event, data: JSON.stringify(data), ...cursor ? { id: cursor } : {} }).catch(() => {
|
|
4566
4881
|
});
|
|
4567
|
-
await send("state", snapshotOf(chat, space.id), chat.entryCursor);
|
|
4568
4882
|
const entry = { chat: id, send };
|
|
4569
4883
|
streams.add(entry);
|
|
4884
|
+
const aborted = new Promise((resolve) => stream2.onAbort(resolve));
|
|
4570
4885
|
const ping = setInterval(() => {
|
|
4571
4886
|
void stream2.writeSSE({ event: "ping", data: "{}" }).catch(() => {
|
|
4572
4887
|
});
|
|
4573
4888
|
}, 2e4);
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4889
|
+
try {
|
|
4890
|
+
await send("state", snapshotOf(chat, space.id), chat.entryCursor);
|
|
4891
|
+
await aborted;
|
|
4892
|
+
} finally {
|
|
4893
|
+
clearInterval(ping);
|
|
4894
|
+
streams.delete(entry);
|
|
4895
|
+
}
|
|
4577
4896
|
});
|
|
4578
4897
|
});
|
|
4579
4898
|
api.get("/state", async (c) => {
|
|
@@ -4585,34 +4904,48 @@ api.get("/state", async (c) => {
|
|
|
4585
4904
|
});
|
|
4586
4905
|
api.post("/chat", async (c) => {
|
|
4587
4906
|
const body = await c.req.json().catch(() => ({}));
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4907
|
+
if (!body || typeof body !== "object") return c.json({ error: "a chat request object is required" }, 400);
|
|
4908
|
+
let id;
|
|
4909
|
+
let ws;
|
|
4910
|
+
if (body.id !== void 0) {
|
|
4911
|
+
id = String(body.id);
|
|
4912
|
+
ws = requireChatWorkspace(id);
|
|
4913
|
+
if (body.ws !== void 0 && body.ws !== ws.id) return c.json({ error: "an existing chat cannot move to another workspace" }, 409);
|
|
4914
|
+
} else {
|
|
4915
|
+
if (typeof body.ws !== "string" || !body.ws) return c.json({ error: "workspace required for a new chat" }, 400);
|
|
4916
|
+
ws = requireWorkspace(body.ws);
|
|
4917
|
+
if (!ws.open) return c.json({ error: "open the workspace before creating a chat" }, 409);
|
|
4918
|
+
id = `chat-${randomUUID()}`;
|
|
4919
|
+
workspaces.bindChat(id, ws.id);
|
|
4920
|
+
}
|
|
4591
4921
|
watchOf(ws.id);
|
|
4592
4922
|
await manager.open(id);
|
|
4593
4923
|
return c.json({ chat: id, ws: ws.id, chats: chatsOf(ws.id) });
|
|
4594
4924
|
});
|
|
4595
4925
|
api.post("/chat/delete", async (c) => {
|
|
4596
4926
|
const { chat: id } = await c.req.json();
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
fs8.rmSync(file);
|
|
4604
|
-
} catch {
|
|
4605
|
-
}
|
|
4606
|
-
}
|
|
4927
|
+
requireChatWorkspace(String(id));
|
|
4928
|
+
await manager.close(String(id), () => {
|
|
4929
|
+
const file = workspaces.sessionFile(String(id));
|
|
4930
|
+
if (file && fs9.existsSync(file)) fs9.unlinkSync(file);
|
|
4931
|
+
workspaces.unbindChat(String(id));
|
|
4932
|
+
});
|
|
4607
4933
|
return c.json({ ok: true });
|
|
4608
4934
|
});
|
|
4609
4935
|
api.get("/workspaces", (c) => c.json(workspaces.list()));
|
|
4936
|
+
api.get("/directories", async (c) => {
|
|
4937
|
+
try {
|
|
4938
|
+
return c.json(await browseDirectories(c.req.query("path"), c.req.query("hidden") === "1"));
|
|
4939
|
+
} catch (e) {
|
|
4940
|
+
const error = e;
|
|
4941
|
+
return c.json({ error: error.message }, error.code === "EACCES" || error.code === "EPERM" ? 403 : error.code === "ENOENT" || error.code === "ENOTDIR" ? 404 : 400);
|
|
4942
|
+
}
|
|
4943
|
+
});
|
|
4610
4944
|
api.post("/workspaces", async (c) => {
|
|
4611
4945
|
const body = await c.req.json().catch(() => ({}));
|
|
4612
4946
|
try {
|
|
4613
|
-
const dir =
|
|
4614
|
-
if (!dir || dir
|
|
4615
|
-
if (!fs8.existsSync(dir) || !fs8.statSync(dir).isDirectory()) throw new Error(`${dir} is not a directory`);
|
|
4947
|
+
const dir = directoryPath(body.path);
|
|
4948
|
+
if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) throw new Error(`${dir} is not a directory`);
|
|
4616
4949
|
const ws = workspaces.add(dir);
|
|
4617
4950
|
watchOf(ws.id);
|
|
4618
4951
|
return c.json({ ok: true, workspace: ws, loopable: Workspaces.loopable(ws.path) });
|
|
@@ -4622,6 +4955,7 @@ api.post("/workspaces", async (c) => {
|
|
|
4622
4955
|
});
|
|
4623
4956
|
api.post("/workspaces/remove", async (c) => {
|
|
4624
4957
|
const { id } = await c.req.json();
|
|
4958
|
+
if (!workspaces.get(String(id))) return c.json({ error: `unknown workspace: ${id}` }, 404);
|
|
4625
4959
|
try {
|
|
4626
4960
|
workspaces.remove(String(id));
|
|
4627
4961
|
return c.json({ ok: true });
|
|
@@ -4647,11 +4981,25 @@ api.post("/abort", async (c) => {
|
|
|
4647
4981
|
await chat.abort();
|
|
4648
4982
|
return c.json({ ok: true });
|
|
4649
4983
|
});
|
|
4984
|
+
api.post("/chat/retry", async (c) => {
|
|
4985
|
+
const { chat: id } = await c.req.json();
|
|
4986
|
+
requireChatWorkspace(String(id));
|
|
4987
|
+
try {
|
|
4988
|
+
await manager.retry(String(id));
|
|
4989
|
+
return c.json({ ok: true });
|
|
4990
|
+
} catch (e) {
|
|
4991
|
+
return c.json({ ok: false, error: String(e.message ?? e) }, 409);
|
|
4992
|
+
}
|
|
4993
|
+
});
|
|
4650
4994
|
api.post("/ui", async (c) => {
|
|
4651
4995
|
const { chat: id, id: dialogId, response } = await c.req.json();
|
|
4652
4996
|
const chat = await manager.open(String(id));
|
|
4653
|
-
|
|
4654
|
-
|
|
4997
|
+
try {
|
|
4998
|
+
await chat.answerUi(String(dialogId), response ?? {});
|
|
4999
|
+
return c.json({ ok: true });
|
|
5000
|
+
} catch (e) {
|
|
5001
|
+
return c.json({ ok: false, error: String(e.message ?? e) }, 409);
|
|
5002
|
+
}
|
|
4655
5003
|
});
|
|
4656
5004
|
api.post("/answer", async (c) => {
|
|
4657
5005
|
const { chat: id, question, choice } = await c.req.json();
|
|
@@ -4696,9 +5044,8 @@ api.get("/session", async (c) => {
|
|
|
4696
5044
|
if (!run3 || !name) return c.json([]);
|
|
4697
5045
|
const state = stateOf(run3);
|
|
4698
5046
|
const file = state?.rounds?.flatMap((r) => r.attempts).find((a) => a.label === name)?.session ?? state?.rounds?.flatMap((r) => r.attempts).find((a) => a.label?.toLowerCase() === name.toLowerCase())?.session;
|
|
4699
|
-
if (!file || !
|
|
4700
|
-
|
|
4701
|
-
return c.json(chat.sessionItems(file));
|
|
5047
|
+
if (!file || !fs9.existsSync(file)) return c.json([]);
|
|
5048
|
+
return c.json(itemsFromMessages(readSessionMessages(file)));
|
|
4702
5049
|
});
|
|
4703
5050
|
api.post("/talk", async (c) => {
|
|
4704
5051
|
const { chat: id, run: run3, name, text } = await c.req.json();
|
|
@@ -4715,21 +5062,22 @@ api.get("/shot", (c) => {
|
|
|
4715
5062
|
const repo = wsOfRun(run3).path;
|
|
4716
5063
|
if (file.startsWith(STATE_DIR)) {
|
|
4717
5064
|
const abs2 = inside(repo, file);
|
|
4718
|
-
if (!abs2 || !
|
|
4719
|
-
return new Response(
|
|
5065
|
+
if (!abs2 || !fs9.existsSync(abs2)) return c.notFound();
|
|
5066
|
+
return new Response(fs9.readFileSync(abs2), { headers: { "content-type": abs2.endsWith(".jpg") || abs2.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
|
|
4720
5067
|
}
|
|
4721
5068
|
const dir = attempt === "CURRENT" ? state?.best?.dir ?? null : state?.rounds?.flatMap((r) => r.attempts).find((a) => a.label === attempt || a.label?.toLowerCase() === attempt.toLowerCase())?.dir;
|
|
4722
5069
|
if (!dir) return c.notFound();
|
|
4723
|
-
const abs = inside(repo,
|
|
4724
|
-
if (!abs || !
|
|
4725
|
-
return new Response(
|
|
5070
|
+
const abs = inside(repo, path9.join(dir, file));
|
|
5071
|
+
if (!abs || !fs9.existsSync(abs)) return c.notFound();
|
|
5072
|
+
return new Response(fs9.readFileSync(abs), { headers: { "content-type": abs.endsWith(".jpg") || abs.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
|
|
4726
5073
|
});
|
|
4727
5074
|
api.get("/sheet", (c) => {
|
|
4728
5075
|
const file = c.req.query("f") ?? "";
|
|
4729
|
-
const ws = workspaces.list().find((w) =>
|
|
5076
|
+
const ws = c.req.query("run") ? wsOfRun(c.req.query("run")) : workspaces.list().find((w) => fs9.existsSync(path9.join(w.path, file)));
|
|
5077
|
+
if (!ws) return c.notFound();
|
|
4730
5078
|
const abs = inside(ws.path, file);
|
|
4731
|
-
if (!abs || !abs.includes(STATE_DIR) || !
|
|
4732
|
-
return new Response(
|
|
5079
|
+
if (!abs || !abs.includes(STATE_DIR) || !fs9.existsSync(abs)) return c.notFound();
|
|
5080
|
+
return new Response(fs9.readFileSync(abs), { headers: { "content-type": "image/png", "cache-control": "no-cache" } });
|
|
4733
5081
|
});
|
|
4734
5082
|
api.get("/diff", async (c) => {
|
|
4735
5083
|
const run3 = c.req.query("run") ?? "";
|
|
@@ -4742,8 +5090,8 @@ api.get("/diff", async (c) => {
|
|
|
4742
5090
|
const from = state.baseCommit;
|
|
4743
5091
|
if (!to || !from) return c.text("");
|
|
4744
5092
|
return c.text(await new Promise((resolve) => {
|
|
4745
|
-
|
|
4746
|
-
|
|
5093
|
+
execFile2("git", ["diff", "--stat", `${from}..${to}`], { cwd: wsOfRun(run3).path, maxBuffer: 1 << 26 }, (_e, stat) => {
|
|
5094
|
+
execFile2("git", ["diff", `${from}..${to}`], { cwd: wsOfRun(run3).path, maxBuffer: 1 << 26 }, (_e2, diff) => resolve(`${stat}
|
|
4747
5095
|
${diff}`.slice(0, 4e5)));
|
|
4748
5096
|
});
|
|
4749
5097
|
}));
|
|
@@ -4766,7 +5114,7 @@ api.get("/proposals", (c) => c.json(proposals));
|
|
|
4766
5114
|
api.post("/proposals/:id/apply", (c) => {
|
|
4767
5115
|
const p = proposals.find((x) => x.id === c.req.param("id"));
|
|
4768
5116
|
if (!p) return c.json({ ok: false, error: "no such proposal" }, 404);
|
|
4769
|
-
const cwd =
|
|
5117
|
+
const cwd = requireWorkspace(p.ws).path;
|
|
4770
5118
|
acceptConfig(cwd, p.after);
|
|
4771
5119
|
const w = p.ws ? watches.get(p.ws) : null;
|
|
4772
5120
|
if (w) w.accepted = p.after;
|
|
@@ -4793,7 +5141,7 @@ api.post("/config", async (c) => {
|
|
|
4793
5141
|
writeSetting(ws.path, String(key), value);
|
|
4794
5142
|
const w = watchOf(ws.id);
|
|
4795
5143
|
try {
|
|
4796
|
-
w.accepted = JSON.parse(
|
|
5144
|
+
w.accepted = JSON.parse(fs9.readFileSync(path9.join(ws.path, ".pi", "loops.json"), "utf8"));
|
|
4797
5145
|
} catch {
|
|
4798
5146
|
}
|
|
4799
5147
|
return c.json({ ok: true });
|
|
@@ -4810,7 +5158,7 @@ api.get("/size", async (c) => {
|
|
|
4810
5158
|
const state = stateOf(run3);
|
|
4811
5159
|
if (!state) return c.json({ bytes: 0, dirs: 0 });
|
|
4812
5160
|
const dirs = (state.rounds ?? []).flatMap((r) => r.attempts).map((a) => a.dir).filter(Boolean);
|
|
4813
|
-
const bytes = await new Promise((resolve) =>
|
|
5161
|
+
const bytes = await new Promise((resolve) => execFile2("du", ["-sk", ...dirs], { cwd: wsOfRun(run3).path }, (_e, out) => resolve(Number(String(out ?? "").split("\n").map((l) => Number(l.split(/\s+/)[0]) || 0).reduce((a, b) => a + b, 0)) * 1024)));
|
|
4814
5162
|
return c.json({ bytes, dirs: dirs.length });
|
|
4815
5163
|
});
|
|
4816
5164
|
api.get("/files", (c) => {
|
|
@@ -4840,7 +5188,8 @@ api.get("/meta", (c) => c.json({
|
|
|
4840
5188
|
}));
|
|
4841
5189
|
app.route("/api", api);
|
|
4842
5190
|
app.route("/api/v1", api);
|
|
4843
|
-
|
|
5191
|
+
app.all("/api/*", (c) => c.json({ error: `unknown API route: ${c.req.path}` }, 404));
|
|
5192
|
+
var DIST = path9.join(APP_ROOT, "dist");
|
|
4844
5193
|
app.get("*", async (c) => {
|
|
4845
5194
|
const url = c.req.path;
|
|
4846
5195
|
if (DEV) {
|
|
@@ -4849,14 +5198,14 @@ app.get("*", async (c) => {
|
|
|
4849
5198
|
return new Response(res.body, { status: res.status, headers: { "content-type": res.headers.get("content-type") ?? "text/html" } });
|
|
4850
5199
|
}
|
|
4851
5200
|
const abs = inside(DIST, url === "/" ? "index.html" : url.replace(/^\//, ""));
|
|
4852
|
-
if (abs &&
|
|
4853
|
-
const ext =
|
|
5201
|
+
if (abs && fs9.existsSync(abs) && fs9.statSync(abs).isFile()) {
|
|
5202
|
+
const ext = path9.extname(abs);
|
|
4854
5203
|
const type = ext === ".js" ? "text/javascript" : ext === ".css" ? "text/css" : ext === ".woff2" ? "font/woff2" : ext === ".svg" ? "image/svg+xml" : ext === ".png" ? "image/png" : "text/html";
|
|
4855
|
-
return new Response(
|
|
5204
|
+
return new Response(fs9.readFileSync(abs), { headers: { "content-type": type } });
|
|
4856
5205
|
}
|
|
4857
|
-
const index =
|
|
4858
|
-
if (!
|
|
4859
|
-
return new Response(
|
|
5206
|
+
const index = path9.join(DIST, "index.html");
|
|
5207
|
+
if (!fs9.existsSync(index)) return c.text("run `npm run build` first", 500);
|
|
5208
|
+
return new Response(fs9.readFileSync(index), { headers: { "content-type": "text/html" } });
|
|
4860
5209
|
});
|
|
4861
5210
|
var server = http.createServer(getRequestListener(app.fetch));
|
|
4862
5211
|
setInterval(() => {
|
|
@@ -4876,7 +5225,7 @@ function openBrowser(url) {
|
|
|
4876
5225
|
server.listen(PORT, HOST, () => {
|
|
4877
5226
|
const reach = HOST === "0.0.0.0" ? "127.0.0.1" : HOST;
|
|
4878
5227
|
const shown = reach.includes(":") ? `[${reach}]` : reach;
|
|
4879
|
-
const base = `http://${shown}:${PORT}`;
|
|
5228
|
+
const base = PUBLIC_ORIGINS[0] ?? `http://${shown}:${PORT}`;
|
|
4880
5229
|
const url = TOKEN ? `${base}/?server=${encodeURIComponent(base)}&token=${encodeURIComponent(TOKEN)}` : base;
|
|
4881
5230
|
console.log(`
|
|
4882
5231
|
open ${url}`);
|