@turmind/halo 1.6.4 → 1.6.5
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 +17 -9
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/87-6a2716c98a819c4e.js +1 -0
- package/admin-out/_next/static/chunks/989-066a874961bd519f.js +1 -0
- package/admin-out/_next/static/chunks/app/page-812bcbb46db628b6.js +1 -0
- package/admin-out/_next/static/css/e890aa0d13e94290.css +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/canvas-extensions.md +38 -7
- package/bundled-docs/design/cron.md +12 -4
- package/bundled-docs/design/halo-city.md +14 -149
- package/bundled-docs/design/storage.md +6 -2
- package/bundled-docs/design/stt-local.md +106 -0
- package/bundled-docs/design/web.md +1 -1
- package/bundled-docs/dev/api.md +11 -8
- package/bundled-docs/dev/desktop-packaging.md +37 -9
- package/bundled-docs/dev/env.md +1 -1
- package/bundled-docs/requirements/chat.md +8 -0
- package/bundled-docs/requirements/workspace.md +4 -0
- package/dist/index.js +1342 -538
- package/package.json +2 -1
- package/templates/skills/cron/SKILL.md +2 -1
- package/templates/skills/cron/manage-cron.py +2 -1
- package/admin-out/_next/static/chunks/147-d4200c70445aabb6.js +0 -1
- package/admin-out/_next/static/chunks/989-5d7743ff56d00dd7.js +0 -1
- package/admin-out/_next/static/chunks/app/page-8881b4f87a3d5ae8.js +0 -1
- package/admin-out/_next/static/css/3b4dcd0ac6b8eae8.css +0 -1
- /package/admin-out/_next/static/{kfp1cMc36J6hjZ06L9mm- → 8v1hCXbUqJhyMuK7LVeet}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{kfp1cMc36J6hjZ06L9mm- → 8v1hCXbUqJhyMuK7LVeet}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -1381,6 +1381,9 @@ function globalHubModelsDir() {
|
|
|
1381
1381
|
function globalExtensionsDir() {
|
|
1382
1382
|
return path6.join(globalDir(), "extensions");
|
|
1383
1383
|
}
|
|
1384
|
+
function globalSttModelsDir() {
|
|
1385
|
+
return path6.join(globalDir(), "stt-models");
|
|
1386
|
+
}
|
|
1384
1387
|
function globalLogsDir() {
|
|
1385
1388
|
return path6.join(globalDir(), "logs");
|
|
1386
1389
|
}
|
|
@@ -1566,7 +1569,79 @@ function parseManifest(raw, dirName, entryExists) {
|
|
|
1566
1569
|
return { error: "version must be semver (x.y.z[-pre])" };
|
|
1567
1570
|
if (m.description !== void 0 && (typeof m.description !== "string" || m.description.length > 200))
|
|
1568
1571
|
return { error: "description must be a string (\u2264 200 chars)" };
|
|
1569
|
-
|
|
1572
|
+
let activity;
|
|
1573
|
+
if (m.activity !== void 0) {
|
|
1574
|
+
if (typeof m.activity !== "object" || m.activity === null || Array.isArray(m.activity))
|
|
1575
|
+
return { error: "activity must be an object" };
|
|
1576
|
+
const a = m.activity;
|
|
1577
|
+
activity = { order: 100 };
|
|
1578
|
+
if (a.icon !== void 0) {
|
|
1579
|
+
if (typeof a.icon !== "string" || a.icon === "" || a.icon.startsWith("/") || a.icon.split(/[\\/]/).includes("..") || !/\.(svg|png)$/i.test(a.icon)) {
|
|
1580
|
+
return { error: "activity.icon must be a relative .svg or .png path inside the extension directory" };
|
|
1581
|
+
}
|
|
1582
|
+
if (!entryExists(a.icon))
|
|
1583
|
+
return { error: `activity icon not found: ${a.icon}` };
|
|
1584
|
+
activity.icon = a.icon;
|
|
1585
|
+
}
|
|
1586
|
+
for (const k of ["title", "title_zh"]) {
|
|
1587
|
+
const v = a[k];
|
|
1588
|
+
if (v === void 0)
|
|
1589
|
+
continue;
|
|
1590
|
+
if (typeof v !== "string" || v.trim() === "" || v.length > 64)
|
|
1591
|
+
return { error: `activity.${k} must be a non-empty string (\u2264 64 chars)` };
|
|
1592
|
+
activity[k] = v;
|
|
1593
|
+
}
|
|
1594
|
+
for (const k of ["description", "description_zh"]) {
|
|
1595
|
+
const v = a[k];
|
|
1596
|
+
if (v === void 0)
|
|
1597
|
+
continue;
|
|
1598
|
+
if (typeof v !== "string" || v.length > 200)
|
|
1599
|
+
return { error: `activity.${k} must be a string (\u2264 200 chars)` };
|
|
1600
|
+
activity[k] = v;
|
|
1601
|
+
}
|
|
1602
|
+
if (a.order !== void 0) {
|
|
1603
|
+
if (typeof a.order !== "number" || !Number.isInteger(a.order) || a.order < 0 || a.order > 1e3)
|
|
1604
|
+
return { error: "activity.order must be an integer 0\u20131000" };
|
|
1605
|
+
activity.order = a.order;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
let chatbar;
|
|
1609
|
+
if (m.chatbar !== void 0) {
|
|
1610
|
+
if (typeof m.chatbar !== "object" || m.chatbar === null || Array.isArray(m.chatbar))
|
|
1611
|
+
return { error: "chatbar must be an object" };
|
|
1612
|
+
const c = m.chatbar;
|
|
1613
|
+
chatbar = { height: 160, order: 100 };
|
|
1614
|
+
if (c.icon !== void 0) {
|
|
1615
|
+
if (typeof c.icon !== "string" || c.icon === "" || c.icon.startsWith("/") || c.icon.split(/[\\/]/).includes("..") || !/\.(svg|png)$/i.test(c.icon)) {
|
|
1616
|
+
return { error: "chatbar.icon must be a relative .svg or .png path inside the extension directory" };
|
|
1617
|
+
}
|
|
1618
|
+
if (!entryExists(c.icon))
|
|
1619
|
+
return { error: `chatbar icon not found: ${c.icon}` };
|
|
1620
|
+
chatbar.icon = c.icon;
|
|
1621
|
+
}
|
|
1622
|
+
for (const k of ["title", "title_zh"]) {
|
|
1623
|
+
const v = c[k];
|
|
1624
|
+
if (v === void 0)
|
|
1625
|
+
continue;
|
|
1626
|
+
if (typeof v !== "string" || v.trim() === "" || v.length > 64)
|
|
1627
|
+
return { error: `chatbar.${k} must be a non-empty string (\u2264 64 chars)` };
|
|
1628
|
+
chatbar[k] = v;
|
|
1629
|
+
}
|
|
1630
|
+
if (c.height !== void 0) {
|
|
1631
|
+
if (typeof c.height !== "number" || !Number.isInteger(c.height) || c.height < 80 || c.height > 480)
|
|
1632
|
+
return { error: "chatbar.height must be an integer 80\u2013480" };
|
|
1633
|
+
chatbar.height = c.height;
|
|
1634
|
+
}
|
|
1635
|
+
if (c.order !== void 0) {
|
|
1636
|
+
if (typeof c.order !== "number" || !Number.isInteger(c.order) || c.order < 0 || c.order > 1e3)
|
|
1637
|
+
return { error: "chatbar.order must be an integer 0\u20131000" };
|
|
1638
|
+
chatbar.order = c.order;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (activity || chatbar) {
|
|
1642
|
+
if (!Array.isArray(m.extensions))
|
|
1643
|
+
return { error: "extensions must be an array (empty is fine with activity or chatbar)" };
|
|
1644
|
+
} else if (!Array.isArray(m.extensions) || m.extensions.length === 0)
|
|
1570
1645
|
return { error: "extensions must be a non-empty array" };
|
|
1571
1646
|
for (const e of m.extensions) {
|
|
1572
1647
|
if (typeof e !== "string" || !FILE_EXT_RE.test(e))
|
|
@@ -1635,6 +1710,8 @@ function parseManifest(raw, dirName, entryExists) {
|
|
|
1635
1710
|
bundle,
|
|
1636
1711
|
...platforms ? { platforms } : {},
|
|
1637
1712
|
...settings ? { settings } : {},
|
|
1713
|
+
...activity ? { activity } : {},
|
|
1714
|
+
...chatbar ? { chatbar } : {},
|
|
1638
1715
|
...m.homepage !== void 0 ? { homepage: m.homepage } : {},
|
|
1639
1716
|
...m.license !== void 0 ? { license: m.license } : {}
|
|
1640
1717
|
};
|
|
@@ -1711,7 +1788,7 @@ var init_registry2 = __esm({
|
|
|
1711
1788
|
ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
1712
1789
|
VERSION_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
|
|
1713
1790
|
FILE_EXT_RE = /^\.[a-z0-9]+$/;
|
|
1714
|
-
CAPABILITIES = /* @__PURE__ */ new Set(["save", "media", "transcribe", "fs-read"]);
|
|
1791
|
+
CAPABILITIES = /* @__PURE__ */ new Set(["save", "media", "transcribe", "fs-read", "stt-local"]);
|
|
1715
1792
|
PLATFORMS = /* @__PURE__ */ new Set(["web", "desktop-mac", "desktop-win", "desktop-linux"]);
|
|
1716
1793
|
SETTING_KEY_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
1717
1794
|
SETTING_TYPES = /* @__PURE__ */ new Set(["string", "int", "float", "boolean", "enum"]);
|
|
@@ -4072,7 +4149,7 @@ var init_init = __esm({
|
|
|
4072
4149
|
path14.resolve(__dirname, "..", "bundled-docs"),
|
|
4073
4150
|
path14.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
4074
4151
|
];
|
|
4075
|
-
TEMPLATE_VERSION =
|
|
4152
|
+
TEMPLATE_VERSION = 102;
|
|
4076
4153
|
VERSION_FILE = ".template-version";
|
|
4077
4154
|
SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
|
|
4078
4155
|
BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
@@ -7849,27 +7926,27 @@ var init_workspace = __esm({
|
|
|
7849
7926
|
constructor(projectRoot) {
|
|
7850
7927
|
this.projectRoot = projectRoot;
|
|
7851
7928
|
}
|
|
7852
|
-
async readFile(
|
|
7853
|
-
const fullPath = this.validatePath(
|
|
7929
|
+
async readFile(path90) {
|
|
7930
|
+
const fullPath = this.validatePath(path90);
|
|
7854
7931
|
return readFile(fullPath, "utf-8");
|
|
7855
7932
|
}
|
|
7856
|
-
async writeFile(
|
|
7857
|
-
const fullPath = this.validatePath(
|
|
7933
|
+
async writeFile(path90, content) {
|
|
7934
|
+
const fullPath = this.validatePath(path90);
|
|
7858
7935
|
const dir = resolve(fullPath, "..");
|
|
7859
7936
|
await mkdir(dir, { recursive: true });
|
|
7860
7937
|
await writeFile(fullPath, content, "utf-8");
|
|
7861
7938
|
}
|
|
7862
|
-
validatePath(
|
|
7863
|
-
const fullPath = isAbsolute(
|
|
7939
|
+
validatePath(path90) {
|
|
7940
|
+
const fullPath = isAbsolute(path90) ? resolve(path90) : resolve(this.projectRoot, path90);
|
|
7864
7941
|
const root = resolve(this.projectRoot);
|
|
7865
7942
|
if (fullPath !== root && !fullPath.startsWith(root + sep)) {
|
|
7866
|
-
throw new WorkspaceError(`Path "${
|
|
7943
|
+
throw new WorkspaceError(`Path "${path90}" is outside the workspace directory "${this.projectRoot}"`);
|
|
7867
7944
|
}
|
|
7868
7945
|
try {
|
|
7869
7946
|
const real = realpathSync2(fullPath);
|
|
7870
7947
|
const realRoot = realpathSync2(root);
|
|
7871
7948
|
if (real !== realRoot && !real.startsWith(realRoot + sep)) {
|
|
7872
|
-
throw new WorkspaceError(`Path "${
|
|
7949
|
+
throw new WorkspaceError(`Path "${path90}" is outside the workspace directory "${this.projectRoot}"`);
|
|
7873
7950
|
}
|
|
7874
7951
|
} catch (err) {
|
|
7875
7952
|
if (err instanceof WorkspaceError)
|
|
@@ -8056,12 +8133,12 @@ var init_git_manager = __esm({
|
|
|
8056
8133
|
const code = parts[0];
|
|
8057
8134
|
const status = code[0];
|
|
8058
8135
|
const isRenameOrCopy = status === "R" || status === "C";
|
|
8059
|
-
const
|
|
8060
|
-
if (!
|
|
8136
|
+
const path90 = isRenameOrCopy ? parts[2] : parts[1];
|
|
8137
|
+
if (!path90 || seen.has(path90))
|
|
8061
8138
|
continue;
|
|
8062
|
-
seen.add(
|
|
8139
|
+
seen.add(path90);
|
|
8063
8140
|
files.push({
|
|
8064
|
-
path:
|
|
8141
|
+
path: path90,
|
|
8065
8142
|
status,
|
|
8066
8143
|
from: isRenameOrCopy ? parts[1] : void 0
|
|
8067
8144
|
});
|
|
@@ -8209,9 +8286,9 @@ var init_git_manager = __esm({
|
|
|
8209
8286
|
* empty for an added file or the root commit) and `modified` is the version
|
|
8210
8287
|
* at `commit` (empty for a deletion). The working-tree/staged path above is
|
|
8211
8288
|
* left entirely untouched (commit === undefined preserves existing behavior). */
|
|
8212
|
-
async getFileDiff(
|
|
8289
|
+
async getFileDiff(path90, staged, from, commit) {
|
|
8213
8290
|
if (commit) {
|
|
8214
|
-
const parentPath = from ||
|
|
8291
|
+
const parentPath = from || path90;
|
|
8215
8292
|
let original2 = "";
|
|
8216
8293
|
try {
|
|
8217
8294
|
original2 = await this.git.show([`${commit}^:${parentPath}`]);
|
|
@@ -8220,13 +8297,13 @@ var init_git_manager = __esm({
|
|
|
8220
8297
|
}
|
|
8221
8298
|
let modified2 = "";
|
|
8222
8299
|
try {
|
|
8223
|
-
modified2 = await this.git.show([`${commit}:${
|
|
8300
|
+
modified2 = await this.git.show([`${commit}:${path90}`]);
|
|
8224
8301
|
} catch {
|
|
8225
8302
|
modified2 = "";
|
|
8226
8303
|
}
|
|
8227
8304
|
return { original: original2, modified: modified2 };
|
|
8228
8305
|
}
|
|
8229
|
-
const headPath = from ||
|
|
8306
|
+
const headPath = from || path90;
|
|
8230
8307
|
let original = "";
|
|
8231
8308
|
try {
|
|
8232
8309
|
original = await this.git.show([`HEAD:${headPath}`]);
|
|
@@ -8236,13 +8313,13 @@ var init_git_manager = __esm({
|
|
|
8236
8313
|
let modified = "";
|
|
8237
8314
|
if (staged) {
|
|
8238
8315
|
try {
|
|
8239
|
-
modified = await this.git.show([`:${
|
|
8316
|
+
modified = await this.git.show([`:${path90}`]);
|
|
8240
8317
|
} catch {
|
|
8241
8318
|
modified = "";
|
|
8242
8319
|
}
|
|
8243
8320
|
} else {
|
|
8244
8321
|
try {
|
|
8245
|
-
modified = await this.workspace.readFile(
|
|
8322
|
+
modified = await this.workspace.readFile(path90);
|
|
8246
8323
|
} catch {
|
|
8247
8324
|
modified = "";
|
|
8248
8325
|
}
|
|
@@ -14226,7 +14303,7 @@ async function initObservability(haloProcess = "server") {
|
|
|
14226
14303
|
return true;
|
|
14227
14304
|
}
|
|
14228
14305
|
const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
|
|
14229
|
-
providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "1.6.
|
|
14306
|
+
providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "1.6.5", haloProcess);
|
|
14230
14307
|
console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, halo.process=${haloProcess}, capture_content=${config.observability.captureContent})`);
|
|
14231
14308
|
return true;
|
|
14232
14309
|
}
|
|
@@ -15747,8 +15824,8 @@ ${q.text}` : q.text).join("\n\n");
|
|
|
15747
15824
|
try {
|
|
15748
15825
|
await this.runAgentTurn(session, input);
|
|
15749
15826
|
} catch (err) {
|
|
15750
|
-
const
|
|
15751
|
-
console.error(`[SessionManager] Drain run error for ${session.id}: ${
|
|
15827
|
+
const errMsg4 = err instanceof Error ? err.message : String(err);
|
|
15828
|
+
console.error(`[SessionManager] Drain run error for ${session.id}: ${errMsg4}`);
|
|
15752
15829
|
throw err;
|
|
15753
15830
|
}
|
|
15754
15831
|
const nextTurnFollows = session.selfKick && !session.interruptRequested || session.messageQueue.length > 0;
|
|
@@ -16044,8 +16121,8 @@ ${q.text}` : q.text);
|
|
|
16044
16121
|
}
|
|
16045
16122
|
console.debug(`[SessionManager] querySession: ${targetSessionId} is IDLE \u2014 draining now`);
|
|
16046
16123
|
this.runSession(targetSessionId, "").catch((err) => {
|
|
16047
|
-
const
|
|
16048
|
-
console.error(`[SessionManager] Query run error for ${targetSessionId}: ${
|
|
16124
|
+
const errMsg4 = err instanceof Error ? err.message : String(err);
|
|
16125
|
+
console.error(`[SessionManager] Query run error for ${targetSessionId}: ${errMsg4}`);
|
|
16049
16126
|
});
|
|
16050
16127
|
return JSON.stringify({ code: 0, message: `Message sent to session ${targetSessionId}.` });
|
|
16051
16128
|
}
|
|
@@ -18950,8 +19027,8 @@ function applyPathPick(value, ref, pick) {
|
|
|
18950
19027
|
const before = value.slice(0, markerStart);
|
|
18951
19028
|
const kind = ref.kind === "scope" ? "scope" : ref.kind === "image" ? "image" : pick.isImage ? "image" : "file";
|
|
18952
19029
|
const needsQuote = pick.insertPath.includes(" ");
|
|
18953
|
-
const
|
|
18954
|
-
return `${before}@${kind} ${
|
|
19030
|
+
const path90 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
|
|
19031
|
+
return `${before}@${kind} ${path90}`;
|
|
18955
19032
|
}
|
|
18956
19033
|
var init_path_suggest_engine = __esm({
|
|
18957
19034
|
"src/tui/path-suggest-engine.ts"() {
|
|
@@ -23737,8 +23814,8 @@ function detectOrphans(tree, declared) {
|
|
|
23737
23814
|
if (!block || typeof block !== "object")
|
|
23738
23815
|
continue;
|
|
23739
23816
|
for (const k of Object.keys(block)) {
|
|
23740
|
-
const
|
|
23741
|
-
if (!declaredMap.has(
|
|
23817
|
+
const path90 = `${namespaceId}.${kindWord}.${k}`;
|
|
23818
|
+
if (!declaredMap.has(path90)) {
|
|
23742
23819
|
out.push({ namespaceId, kind: kindWord === "params" ? "param" : "secret", key: k });
|
|
23743
23820
|
}
|
|
23744
23821
|
}
|
|
@@ -24772,11 +24849,14 @@ var init_brute_force = __esm({
|
|
|
24772
24849
|
});
|
|
24773
24850
|
|
|
24774
24851
|
// ../server/dist/middleware/web-token.js
|
|
24852
|
+
function requestToken(c, opts) {
|
|
24853
|
+
return opts?.headerOnly ? /^Bearer\s+(.+)$/i.exec(c.req.header("authorization") ?? "")?.[1]?.trim() || c.req.header("x-token") : c.req.header("x-token") || c.req.query("token");
|
|
24854
|
+
}
|
|
24775
24855
|
function resolveTokenAuth(c, db3, opts) {
|
|
24776
24856
|
const ip = getClientIp(c);
|
|
24777
24857
|
if (isLockedOut(TOKEN_BUCKET, ip))
|
|
24778
24858
|
return { ok: false, reason: "locked_out" };
|
|
24779
|
-
const token =
|
|
24859
|
+
const token = requestToken(c, opts);
|
|
24780
24860
|
if (!token) {
|
|
24781
24861
|
return { ok: false, reason: "missing_token" };
|
|
24782
24862
|
}
|
|
@@ -24811,6 +24891,233 @@ var init_web_token = __esm({
|
|
|
24811
24891
|
}
|
|
24812
24892
|
});
|
|
24813
24893
|
|
|
24894
|
+
// ../server/dist/middleware/auth.js
|
|
24895
|
+
import { Hono as Hono11 } from "hono";
|
|
24896
|
+
import { getCookie, setCookie } from "hono/cookie";
|
|
24897
|
+
import crypto5 from "node:crypto";
|
|
24898
|
+
function passwordHash() {
|
|
24899
|
+
return config.server.password ?? "";
|
|
24900
|
+
}
|
|
24901
|
+
function passwordEnvPlaintext() {
|
|
24902
|
+
return config.server.passwordEnvPlaintext;
|
|
24903
|
+
}
|
|
24904
|
+
function jwtSecret() {
|
|
24905
|
+
const secret = config.server.jwtSecret ?? "";
|
|
24906
|
+
if (!secret) {
|
|
24907
|
+
throw new Error("jwt_secret missing \u2014 run `halo setup` to initialize");
|
|
24908
|
+
}
|
|
24909
|
+
return Buffer.from(secret, "base64");
|
|
24910
|
+
}
|
|
24911
|
+
function base64url(data) {
|
|
24912
|
+
const buf = typeof data === "string" ? Buffer.from(data) : data;
|
|
24913
|
+
return buf.toString("base64url");
|
|
24914
|
+
}
|
|
24915
|
+
function jwtSign(payload) {
|
|
24916
|
+
const header = base64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
24917
|
+
const body = base64url(JSON.stringify(payload));
|
|
24918
|
+
const sig = crypto5.createHmac("sha256", jwtSecret()).update(`${header}.${body}`).digest();
|
|
24919
|
+
return `${header}.${body}.${base64url(sig)}`;
|
|
24920
|
+
}
|
|
24921
|
+
function jwtVerify(token) {
|
|
24922
|
+
const parts = token.split(".");
|
|
24923
|
+
if (parts.length !== 3)
|
|
24924
|
+
return null;
|
|
24925
|
+
const [header, body, sig] = parts;
|
|
24926
|
+
let secret;
|
|
24927
|
+
try {
|
|
24928
|
+
secret = jwtSecret();
|
|
24929
|
+
} catch {
|
|
24930
|
+
return null;
|
|
24931
|
+
}
|
|
24932
|
+
const expected = base64url(crypto5.createHmac("sha256", secret).update(`${header}.${body}`).digest());
|
|
24933
|
+
if (sig !== expected)
|
|
24934
|
+
return null;
|
|
24935
|
+
try {
|
|
24936
|
+
const payload = JSON.parse(Buffer.from(body, "base64url").toString());
|
|
24937
|
+
if (typeof payload.exp === "number" && Date.now() / 1e3 > payload.exp)
|
|
24938
|
+
return null;
|
|
24939
|
+
return payload;
|
|
24940
|
+
} catch {
|
|
24941
|
+
return null;
|
|
24942
|
+
}
|
|
24943
|
+
}
|
|
24944
|
+
function createToken() {
|
|
24945
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
24946
|
+
return jwtSign({ iat: now, exp: now + TOKEN_MAX_AGE });
|
|
24947
|
+
}
|
|
24948
|
+
function constantTimeEqualString(a, b) {
|
|
24949
|
+
const ab = Buffer.from(a, "utf-8");
|
|
24950
|
+
const bb = Buffer.from(b, "utf-8");
|
|
24951
|
+
const max = Math.max(ab.length, bb.length);
|
|
24952
|
+
const ap = Buffer.alloc(max);
|
|
24953
|
+
ab.copy(ap);
|
|
24954
|
+
const bp = Buffer.alloc(max);
|
|
24955
|
+
bb.copy(bp);
|
|
24956
|
+
const eq17 = crypto5.timingSafeEqual(ap, bp);
|
|
24957
|
+
return eq17 && ab.length === bb.length;
|
|
24958
|
+
}
|
|
24959
|
+
function validateToken(token) {
|
|
24960
|
+
if (!token)
|
|
24961
|
+
return null;
|
|
24962
|
+
const payload = jwtVerify(token);
|
|
24963
|
+
if (payload?.scope)
|
|
24964
|
+
return null;
|
|
24965
|
+
return payload;
|
|
24966
|
+
}
|
|
24967
|
+
function mintScopedToken(scope, maxAgeSec) {
|
|
24968
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
24969
|
+
return jwtSign({ scope, iat: now, exp: now + maxAgeSec });
|
|
24970
|
+
}
|
|
24971
|
+
function verifyScopedToken(token, scope) {
|
|
24972
|
+
if (!token)
|
|
24973
|
+
return false;
|
|
24974
|
+
const payload = jwtVerify(token);
|
|
24975
|
+
return payload !== null && payload.scope === scope;
|
|
24976
|
+
}
|
|
24977
|
+
function shouldRefresh(payload) {
|
|
24978
|
+
const iat = payload.iat;
|
|
24979
|
+
if (!iat)
|
|
24980
|
+
return true;
|
|
24981
|
+
return Date.now() / 1e3 - iat > REFRESH_AFTER;
|
|
24982
|
+
}
|
|
24983
|
+
function isAuthDisabled() {
|
|
24984
|
+
return passwordHash().length === 0 && passwordEnvPlaintext() == null;
|
|
24985
|
+
}
|
|
24986
|
+
function isAuthenticated(cookie) {
|
|
24987
|
+
if (isAuthDisabled())
|
|
24988
|
+
return true;
|
|
24989
|
+
return validateToken(cookie) !== null;
|
|
24990
|
+
}
|
|
24991
|
+
function getTokenFromCookieHeader(cookieHeader) {
|
|
24992
|
+
if (!cookieHeader)
|
|
24993
|
+
return void 0;
|
|
24994
|
+
const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${COOKIE_NAME}=([^;]+)`));
|
|
24995
|
+
return match?.[1];
|
|
24996
|
+
}
|
|
24997
|
+
function setTokenCookie(c, token) {
|
|
24998
|
+
setCookie(c, COOKIE_NAME, token, {
|
|
24999
|
+
path: "/",
|
|
25000
|
+
// WS upgrade requests carry cookies on the underlying HTTP handshake;
|
|
25001
|
+
// the server reads them via the Cookie header, never via JS — so the
|
|
25002
|
+
// cookie can stay httpOnly to avoid XSS exfiltration.
|
|
25003
|
+
httpOnly: true,
|
|
25004
|
+
maxAge: TOKEN_MAX_AGE,
|
|
25005
|
+
sameSite: "Lax"
|
|
25006
|
+
});
|
|
25007
|
+
}
|
|
25008
|
+
function createAuthRoutes() {
|
|
25009
|
+
const app2 = new Hono11();
|
|
25010
|
+
app2.post("/auth/login", async (c) => {
|
|
25011
|
+
const ip = getClientIp(c);
|
|
25012
|
+
if (isLockedOut2(ip)) {
|
|
25013
|
+
return c.json({ error: "Too many attempts, try again later" }, 429);
|
|
25014
|
+
}
|
|
25015
|
+
const body = await c.req.json().catch(() => ({ password: "" }));
|
|
25016
|
+
if (!body.password) {
|
|
25017
|
+
recordFailure2(ip);
|
|
25018
|
+
return c.json({ error: "Invalid password" }, 401);
|
|
25019
|
+
}
|
|
25020
|
+
const envPlain = passwordEnvPlaintext();
|
|
25021
|
+
const ok = envPlain != null ? constantTimeEqualString(body.password, envPlain) : await verifyPassword(body.password, passwordHash());
|
|
25022
|
+
if (!ok) {
|
|
25023
|
+
recordFailure2(ip);
|
|
25024
|
+
return c.json({ error: "Invalid password" }, 401);
|
|
25025
|
+
}
|
|
25026
|
+
clearFailures2(ip);
|
|
25027
|
+
const token = createToken();
|
|
25028
|
+
setTokenCookie(c, token);
|
|
25029
|
+
return c.json({ ok: true });
|
|
25030
|
+
});
|
|
25031
|
+
app2.get("/auth/check", (c) => {
|
|
25032
|
+
const token = getCookie(c, COOKIE_NAME);
|
|
25033
|
+
const payload = validateToken(token);
|
|
25034
|
+
if (payload) {
|
|
25035
|
+
if (shouldRefresh(payload)) {
|
|
25036
|
+
const newToken = createToken();
|
|
25037
|
+
setTokenCookie(c, newToken);
|
|
25038
|
+
}
|
|
25039
|
+
return c.json({ authenticated: true, badge: config.server.badge, goalModeEnabled: config.goalModeEnabled });
|
|
25040
|
+
}
|
|
25041
|
+
return c.json({ authenticated: false, badge: config.server.badge }, 401);
|
|
25042
|
+
});
|
|
25043
|
+
app2.post("/auth/logout", (c) => {
|
|
25044
|
+
setCookie(c, COOKIE_NAME, "", { path: "/", maxAge: 0 });
|
|
25045
|
+
return c.json({ ok: true });
|
|
25046
|
+
});
|
|
25047
|
+
app2.post("/auth/change-password", async (c) => {
|
|
25048
|
+
const body = await c.req.json().catch(() => ({}));
|
|
25049
|
+
const oldPassword = typeof body.oldPassword === "string" ? body.oldPassword : "";
|
|
25050
|
+
const newPassword = typeof body.newPassword === "string" ? body.newPassword : "";
|
|
25051
|
+
if (passwordEnvPlaintext() != null) {
|
|
25052
|
+
return c.json({ error: "Password is managed by the HALO_PASSWORD environment variable" }, 400);
|
|
25053
|
+
}
|
|
25054
|
+
if (!await verifyPassword(oldPassword, passwordHash())) {
|
|
25055
|
+
return c.json({ error: "Incorrect current password" }, 401);
|
|
25056
|
+
}
|
|
25057
|
+
const weak = passwordStrengthError(newPassword);
|
|
25058
|
+
if (weak)
|
|
25059
|
+
return c.json({ error: weak }, 400);
|
|
25060
|
+
if (newPassword === oldPassword) {
|
|
25061
|
+
return c.json({ error: "New password must differ from the current password" }, 400);
|
|
25062
|
+
}
|
|
25063
|
+
updateConfigLeaves({ "server.password": await hashPassword(newPassword) });
|
|
25064
|
+
return c.json({ ok: true });
|
|
25065
|
+
});
|
|
25066
|
+
return app2;
|
|
25067
|
+
}
|
|
25068
|
+
function passwordStrengthError(pw) {
|
|
25069
|
+
if (pw.length < 8)
|
|
25070
|
+
return "Password must be at least 8 characters";
|
|
25071
|
+
if (!/[A-Za-z]/.test(pw))
|
|
25072
|
+
return "Password must contain at least one letter";
|
|
25073
|
+
if (!/[0-9]/.test(pw))
|
|
25074
|
+
return "Password must contain at least one digit";
|
|
25075
|
+
return null;
|
|
25076
|
+
}
|
|
25077
|
+
function authMiddleware() {
|
|
25078
|
+
return async (c, next) => {
|
|
25079
|
+
const path90 = c.req.path;
|
|
25080
|
+
if (PUBLIC_PATHS.some((p) => path90 === p || path90.startsWith(p + "?"))) {
|
|
25081
|
+
return next();
|
|
25082
|
+
}
|
|
25083
|
+
if (EXTENSION_ASSET_PATH.test(path90)) {
|
|
25084
|
+
return next();
|
|
25085
|
+
}
|
|
25086
|
+
const token = getCookie(c, COOKIE_NAME);
|
|
25087
|
+
const payload = validateToken(token);
|
|
25088
|
+
if (!payload) {
|
|
25089
|
+
if (!path90.startsWith("/api/")) {
|
|
25090
|
+
return next();
|
|
25091
|
+
}
|
|
25092
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
25093
|
+
}
|
|
25094
|
+
if (shouldRefresh(payload)) {
|
|
25095
|
+
const newToken = createToken();
|
|
25096
|
+
setTokenCookie(c, newToken);
|
|
25097
|
+
}
|
|
25098
|
+
return next();
|
|
25099
|
+
};
|
|
25100
|
+
}
|
|
25101
|
+
var TOKEN_MAX_AGE, REFRESH_AFTER, COOKIE_NAME, ADMIN_LOGIN_BUCKET, isLockedOut2, recordFailure2, clearFailures2, PUBLIC_PATHS, EXTENSION_ASSET_PATH;
|
|
25102
|
+
var init_auth = __esm({
|
|
25103
|
+
"../server/dist/middleware/auth.js"() {
|
|
25104
|
+
"use strict";
|
|
25105
|
+
init_config();
|
|
25106
|
+
init_password_hash();
|
|
25107
|
+
init_setup_config();
|
|
25108
|
+
init_brute_force();
|
|
25109
|
+
TOKEN_MAX_AGE = config.auth.tokenMaxAge;
|
|
25110
|
+
REFRESH_AFTER = config.auth.refreshAfter;
|
|
25111
|
+
COOKIE_NAME = "halo_token";
|
|
25112
|
+
ADMIN_LOGIN_BUCKET = "admin-login";
|
|
25113
|
+
isLockedOut2 = (ip) => isLockedOut(ADMIN_LOGIN_BUCKET, ip);
|
|
25114
|
+
recordFailure2 = (ip) => recordFailure(ADMIN_LOGIN_BUCKET, ip);
|
|
25115
|
+
clearFailures2 = (ip) => clearFailures(ADMIN_LOGIN_BUCKET, ip);
|
|
25116
|
+
PUBLIC_PATHS = ["/api/auth/login", "/api/auth/check", "/api/auth/logout", "/api/health", "/api/web/chat", "/api/web/sessions", "/api/web/stop", "/api/web/history", "/api/web/subscribe", "/api/web/file", "/api/show/state", "/api/show/session", "/api/metrics"];
|
|
25117
|
+
EXTENSION_ASSET_PATH = /^\/api\/extensions\/[^/]+\/[^/]+\/[^/]+\/./;
|
|
25118
|
+
}
|
|
25119
|
+
});
|
|
25120
|
+
|
|
24814
25121
|
// ../server/dist/channels/web/handler.js
|
|
24815
25122
|
import fs58 from "node:fs";
|
|
24816
25123
|
function buildWebSessionPrefix(accountId) {
|
|
@@ -24833,14 +25140,14 @@ function createWebChannel(deps) {
|
|
|
24833
25140
|
return findActiveSessionId(sm, accountId, prefix, activeOverrides, "full") ?? void 0;
|
|
24834
25141
|
}
|
|
24835
25142
|
function resolveWorkspace(account, override, scaffold = true) {
|
|
24836
|
-
let
|
|
25143
|
+
let path90 = account.workspacePath;
|
|
24837
25144
|
if (override && override !== account.workspacePath) {
|
|
24838
25145
|
if (account.accessLevel !== "full") {
|
|
24839
25146
|
return { ok: false, error: "workspace override requires a full-access token" };
|
|
24840
25147
|
}
|
|
24841
|
-
|
|
25148
|
+
path90 = override;
|
|
24842
25149
|
}
|
|
24843
|
-
const resolved = scaffold ? resolveAccountWorkspace({ ...account, workspacePath:
|
|
25150
|
+
const resolved = scaffold ? resolveAccountWorkspace({ ...account, workspacePath: path90 }) : fs58.existsSync(path90) ? path90 : null;
|
|
24844
25151
|
if (!resolved)
|
|
24845
25152
|
return { ok: false, error: "workspace not found" };
|
|
24846
25153
|
return { ok: true, path: resolved };
|
|
@@ -25250,7 +25557,7 @@ var init_session_types = __esm({
|
|
|
25250
25557
|
});
|
|
25251
25558
|
|
|
25252
25559
|
// ../server/dist/routes/halo-city.js
|
|
25253
|
-
import { Hono as
|
|
25560
|
+
import { Hono as Hono12 } from "hono";
|
|
25254
25561
|
import fs59 from "node:fs";
|
|
25255
25562
|
import path67 from "node:path";
|
|
25256
25563
|
import Database7 from "better-sqlite3";
|
|
@@ -25537,20 +25844,25 @@ function discoverWorkspaces(registry2) {
|
|
|
25537
25844
|
return out;
|
|
25538
25845
|
}
|
|
25539
25846
|
function createShowRoutes(registry2) {
|
|
25540
|
-
const app2 = new
|
|
25847
|
+
const app2 = new Hono12();
|
|
25541
25848
|
function auth(c) {
|
|
25849
|
+
const cookie = getTokenFromCookieHeader(c.req.header("cookie"));
|
|
25850
|
+
if (!requestToken(c) && cookie && isAuthenticated(cookie)) {
|
|
25851
|
+
return { ok: true, accessLevel: "full", scoped: null };
|
|
25852
|
+
}
|
|
25542
25853
|
const a = resolveTokenAuth(c, getChannelDb());
|
|
25543
25854
|
if (!a.ok)
|
|
25544
25855
|
return { ok: false, response: tokenAuthJsonError(c, a.reason) };
|
|
25545
|
-
|
|
25856
|
+
const { account } = a;
|
|
25857
|
+
const globalView = account.accessLevel === "full" || account.accessLevel === "observer";
|
|
25858
|
+
return { ok: true, accessLevel: account.accessLevel, scoped: globalView ? null : account };
|
|
25546
25859
|
}
|
|
25547
25860
|
app2.get("/show/state", (c) => {
|
|
25548
25861
|
const a = auth(c);
|
|
25549
25862
|
if (!a.ok)
|
|
25550
25863
|
return a.response;
|
|
25551
|
-
const {
|
|
25552
|
-
const
|
|
25553
|
-
const targets = globalView ? discoverWorkspaces(registry2) : /* @__PURE__ */ new Map([[account.workspacePath, path67.basename(account.workspacePath)]]);
|
|
25864
|
+
const { scoped } = a;
|
|
25865
|
+
const targets = scoped ? /* @__PURE__ */ new Map([[scoped.workspacePath, path67.basename(scoped.workspacePath)]]) : discoverWorkspaces(registry2);
|
|
25554
25866
|
const workspaces = [];
|
|
25555
25867
|
for (const [wsPath, label] of targets) {
|
|
25556
25868
|
try {
|
|
@@ -25566,7 +25878,7 @@ function createShowRoutes(registry2) {
|
|
|
25566
25878
|
return c.json({
|
|
25567
25879
|
serverTime: Date.now(),
|
|
25568
25880
|
uptime: process.uptime(),
|
|
25569
|
-
accessLevel:
|
|
25881
|
+
accessLevel: a.accessLevel,
|
|
25570
25882
|
skills: scanSkills2(GLOBAL_SKILLS_DIR),
|
|
25571
25883
|
workspaces
|
|
25572
25884
|
});
|
|
@@ -25575,19 +25887,18 @@ function createShowRoutes(registry2) {
|
|
|
25575
25887
|
const a = auth(c);
|
|
25576
25888
|
if (!a.ok)
|
|
25577
25889
|
return a.response;
|
|
25578
|
-
const {
|
|
25890
|
+
const { scoped } = a;
|
|
25579
25891
|
const wsPath = c.req.query("ws") ?? "";
|
|
25580
25892
|
const id = c.req.query("id") ?? "";
|
|
25581
25893
|
if (!wsPath || !id)
|
|
25582
25894
|
return c.json({ error: "ws and id required" }, 400);
|
|
25583
|
-
|
|
25584
|
-
if (!globalView) {
|
|
25895
|
+
if (scoped) {
|
|
25585
25896
|
let allowed = false;
|
|
25586
25897
|
try {
|
|
25587
|
-
allowed = fs59.realpathSync(wsPath) === fs59.realpathSync(
|
|
25898
|
+
allowed = fs59.realpathSync(wsPath) === fs59.realpathSync(scoped.workspacePath);
|
|
25588
25899
|
} catch {
|
|
25589
25900
|
}
|
|
25590
|
-
if (!allowed || !canAddressSession(
|
|
25901
|
+
if (!allowed || !canAddressSession(scoped, id))
|
|
25591
25902
|
return c.json({ error: "forbidden" }, 403);
|
|
25592
25903
|
}
|
|
25593
25904
|
try {
|
|
@@ -25640,6 +25951,7 @@ var init_halo_city = __esm({
|
|
|
25640
25951
|
"use strict";
|
|
25641
25952
|
init_channel_db();
|
|
25642
25953
|
init_web_token();
|
|
25954
|
+
init_auth();
|
|
25643
25955
|
init_handler();
|
|
25644
25956
|
init_agent_loader();
|
|
25645
25957
|
init_session_store();
|
|
@@ -25655,7 +25967,7 @@ var init_halo_city = __esm({
|
|
|
25655
25967
|
});
|
|
25656
25968
|
|
|
25657
25969
|
// ../server/dist/routes/metrics.js
|
|
25658
|
-
import { Hono as
|
|
25970
|
+
import { Hono as Hono13 } from "hono";
|
|
25659
25971
|
function family(name, type, help, samples) {
|
|
25660
25972
|
const lines = [`# HELP ${name} ${help}`, `# TYPE ${name} ${type}`];
|
|
25661
25973
|
for (const s of samples)
|
|
@@ -25663,7 +25975,7 @@ function family(name, type, help, samples) {
|
|
|
25663
25975
|
return lines.join("\n");
|
|
25664
25976
|
}
|
|
25665
25977
|
function createMetricsRoutes(registry2) {
|
|
25666
|
-
const app2 = new
|
|
25978
|
+
const app2 = new Hono13();
|
|
25667
25979
|
function auth(c) {
|
|
25668
25980
|
const a = resolveTokenAuth(c, getChannelDb());
|
|
25669
25981
|
if (!a.ok) {
|
|
@@ -25748,9 +26060,9 @@ var init_metrics = __esm({
|
|
|
25748
26060
|
});
|
|
25749
26061
|
|
|
25750
26062
|
// ../server/dist/routes/commands.js
|
|
25751
|
-
import { Hono as
|
|
26063
|
+
import { Hono as Hono14 } from "hono";
|
|
25752
26064
|
function createCommandRoutes(registry2, smRegistry) {
|
|
25753
|
-
const app2 = new
|
|
26065
|
+
const app2 = new Hono14();
|
|
25754
26066
|
app2.get("/commands", async (c) => {
|
|
25755
26067
|
const projectId = c.req.query("projectId");
|
|
25756
26068
|
const sessionId = c.req.query("sessionId");
|
|
@@ -25777,233 +26089,6 @@ var init_commands3 = __esm({
|
|
|
25777
26089
|
}
|
|
25778
26090
|
});
|
|
25779
26091
|
|
|
25780
|
-
// ../server/dist/middleware/auth.js
|
|
25781
|
-
import { Hono as Hono14 } from "hono";
|
|
25782
|
-
import { getCookie, setCookie } from "hono/cookie";
|
|
25783
|
-
import crypto5 from "node:crypto";
|
|
25784
|
-
function passwordHash() {
|
|
25785
|
-
return config.server.password ?? "";
|
|
25786
|
-
}
|
|
25787
|
-
function passwordEnvPlaintext() {
|
|
25788
|
-
return config.server.passwordEnvPlaintext;
|
|
25789
|
-
}
|
|
25790
|
-
function jwtSecret() {
|
|
25791
|
-
const secret = config.server.jwtSecret ?? "";
|
|
25792
|
-
if (!secret) {
|
|
25793
|
-
throw new Error("jwt_secret missing \u2014 run `halo setup` to initialize");
|
|
25794
|
-
}
|
|
25795
|
-
return Buffer.from(secret, "base64");
|
|
25796
|
-
}
|
|
25797
|
-
function base64url(data) {
|
|
25798
|
-
const buf = typeof data === "string" ? Buffer.from(data) : data;
|
|
25799
|
-
return buf.toString("base64url");
|
|
25800
|
-
}
|
|
25801
|
-
function jwtSign(payload) {
|
|
25802
|
-
const header = base64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
25803
|
-
const body = base64url(JSON.stringify(payload));
|
|
25804
|
-
const sig = crypto5.createHmac("sha256", jwtSecret()).update(`${header}.${body}`).digest();
|
|
25805
|
-
return `${header}.${body}.${base64url(sig)}`;
|
|
25806
|
-
}
|
|
25807
|
-
function jwtVerify(token) {
|
|
25808
|
-
const parts = token.split(".");
|
|
25809
|
-
if (parts.length !== 3)
|
|
25810
|
-
return null;
|
|
25811
|
-
const [header, body, sig] = parts;
|
|
25812
|
-
let secret;
|
|
25813
|
-
try {
|
|
25814
|
-
secret = jwtSecret();
|
|
25815
|
-
} catch {
|
|
25816
|
-
return null;
|
|
25817
|
-
}
|
|
25818
|
-
const expected = base64url(crypto5.createHmac("sha256", secret).update(`${header}.${body}`).digest());
|
|
25819
|
-
if (sig !== expected)
|
|
25820
|
-
return null;
|
|
25821
|
-
try {
|
|
25822
|
-
const payload = JSON.parse(Buffer.from(body, "base64url").toString());
|
|
25823
|
-
if (typeof payload.exp === "number" && Date.now() / 1e3 > payload.exp)
|
|
25824
|
-
return null;
|
|
25825
|
-
return payload;
|
|
25826
|
-
} catch {
|
|
25827
|
-
return null;
|
|
25828
|
-
}
|
|
25829
|
-
}
|
|
25830
|
-
function createToken() {
|
|
25831
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
25832
|
-
return jwtSign({ iat: now, exp: now + TOKEN_MAX_AGE });
|
|
25833
|
-
}
|
|
25834
|
-
function constantTimeEqualString(a, b) {
|
|
25835
|
-
const ab = Buffer.from(a, "utf-8");
|
|
25836
|
-
const bb = Buffer.from(b, "utf-8");
|
|
25837
|
-
const max = Math.max(ab.length, bb.length);
|
|
25838
|
-
const ap = Buffer.alloc(max);
|
|
25839
|
-
ab.copy(ap);
|
|
25840
|
-
const bp = Buffer.alloc(max);
|
|
25841
|
-
bb.copy(bp);
|
|
25842
|
-
const eq17 = crypto5.timingSafeEqual(ap, bp);
|
|
25843
|
-
return eq17 && ab.length === bb.length;
|
|
25844
|
-
}
|
|
25845
|
-
function validateToken(token) {
|
|
25846
|
-
if (!token)
|
|
25847
|
-
return null;
|
|
25848
|
-
const payload = jwtVerify(token);
|
|
25849
|
-
if (payload?.scope)
|
|
25850
|
-
return null;
|
|
25851
|
-
return payload;
|
|
25852
|
-
}
|
|
25853
|
-
function mintScopedToken(scope, maxAgeSec) {
|
|
25854
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
25855
|
-
return jwtSign({ scope, iat: now, exp: now + maxAgeSec });
|
|
25856
|
-
}
|
|
25857
|
-
function verifyScopedToken(token, scope) {
|
|
25858
|
-
if (!token)
|
|
25859
|
-
return false;
|
|
25860
|
-
const payload = jwtVerify(token);
|
|
25861
|
-
return payload !== null && payload.scope === scope;
|
|
25862
|
-
}
|
|
25863
|
-
function shouldRefresh(payload) {
|
|
25864
|
-
const iat = payload.iat;
|
|
25865
|
-
if (!iat)
|
|
25866
|
-
return true;
|
|
25867
|
-
return Date.now() / 1e3 - iat > REFRESH_AFTER;
|
|
25868
|
-
}
|
|
25869
|
-
function isAuthDisabled() {
|
|
25870
|
-
return passwordHash().length === 0 && passwordEnvPlaintext() == null;
|
|
25871
|
-
}
|
|
25872
|
-
function isAuthenticated(cookie) {
|
|
25873
|
-
if (isAuthDisabled())
|
|
25874
|
-
return true;
|
|
25875
|
-
return validateToken(cookie) !== null;
|
|
25876
|
-
}
|
|
25877
|
-
function getTokenFromCookieHeader(cookieHeader) {
|
|
25878
|
-
if (!cookieHeader)
|
|
25879
|
-
return void 0;
|
|
25880
|
-
const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${COOKIE_NAME}=([^;]+)`));
|
|
25881
|
-
return match?.[1];
|
|
25882
|
-
}
|
|
25883
|
-
function setTokenCookie(c, token) {
|
|
25884
|
-
setCookie(c, COOKIE_NAME, token, {
|
|
25885
|
-
path: "/",
|
|
25886
|
-
// WS upgrade requests carry cookies on the underlying HTTP handshake;
|
|
25887
|
-
// the server reads them via the Cookie header, never via JS — so the
|
|
25888
|
-
// cookie can stay httpOnly to avoid XSS exfiltration.
|
|
25889
|
-
httpOnly: true,
|
|
25890
|
-
maxAge: TOKEN_MAX_AGE,
|
|
25891
|
-
sameSite: "Lax"
|
|
25892
|
-
});
|
|
25893
|
-
}
|
|
25894
|
-
function createAuthRoutes() {
|
|
25895
|
-
const app2 = new Hono14();
|
|
25896
|
-
app2.post("/auth/login", async (c) => {
|
|
25897
|
-
const ip = getClientIp(c);
|
|
25898
|
-
if (isLockedOut2(ip)) {
|
|
25899
|
-
return c.json({ error: "Too many attempts, try again later" }, 429);
|
|
25900
|
-
}
|
|
25901
|
-
const body = await c.req.json().catch(() => ({ password: "" }));
|
|
25902
|
-
if (!body.password) {
|
|
25903
|
-
recordFailure2(ip);
|
|
25904
|
-
return c.json({ error: "Invalid password" }, 401);
|
|
25905
|
-
}
|
|
25906
|
-
const envPlain = passwordEnvPlaintext();
|
|
25907
|
-
const ok = envPlain != null ? constantTimeEqualString(body.password, envPlain) : await verifyPassword(body.password, passwordHash());
|
|
25908
|
-
if (!ok) {
|
|
25909
|
-
recordFailure2(ip);
|
|
25910
|
-
return c.json({ error: "Invalid password" }, 401);
|
|
25911
|
-
}
|
|
25912
|
-
clearFailures2(ip);
|
|
25913
|
-
const token = createToken();
|
|
25914
|
-
setTokenCookie(c, token);
|
|
25915
|
-
return c.json({ ok: true });
|
|
25916
|
-
});
|
|
25917
|
-
app2.get("/auth/check", (c) => {
|
|
25918
|
-
const token = getCookie(c, COOKIE_NAME);
|
|
25919
|
-
const payload = validateToken(token);
|
|
25920
|
-
if (payload) {
|
|
25921
|
-
if (shouldRefresh(payload)) {
|
|
25922
|
-
const newToken = createToken();
|
|
25923
|
-
setTokenCookie(c, newToken);
|
|
25924
|
-
}
|
|
25925
|
-
return c.json({ authenticated: true, badge: config.server.badge, goalModeEnabled: config.goalModeEnabled });
|
|
25926
|
-
}
|
|
25927
|
-
return c.json({ authenticated: false, badge: config.server.badge }, 401);
|
|
25928
|
-
});
|
|
25929
|
-
app2.post("/auth/logout", (c) => {
|
|
25930
|
-
setCookie(c, COOKIE_NAME, "", { path: "/", maxAge: 0 });
|
|
25931
|
-
return c.json({ ok: true });
|
|
25932
|
-
});
|
|
25933
|
-
app2.post("/auth/change-password", async (c) => {
|
|
25934
|
-
const body = await c.req.json().catch(() => ({}));
|
|
25935
|
-
const oldPassword = typeof body.oldPassword === "string" ? body.oldPassword : "";
|
|
25936
|
-
const newPassword = typeof body.newPassword === "string" ? body.newPassword : "";
|
|
25937
|
-
if (passwordEnvPlaintext() != null) {
|
|
25938
|
-
return c.json({ error: "Password is managed by the HALO_PASSWORD environment variable" }, 400);
|
|
25939
|
-
}
|
|
25940
|
-
if (!await verifyPassword(oldPassword, passwordHash())) {
|
|
25941
|
-
return c.json({ error: "Incorrect current password" }, 401);
|
|
25942
|
-
}
|
|
25943
|
-
const weak = passwordStrengthError(newPassword);
|
|
25944
|
-
if (weak)
|
|
25945
|
-
return c.json({ error: weak }, 400);
|
|
25946
|
-
if (newPassword === oldPassword) {
|
|
25947
|
-
return c.json({ error: "New password must differ from the current password" }, 400);
|
|
25948
|
-
}
|
|
25949
|
-
updateConfigLeaves({ "server.password": await hashPassword(newPassword) });
|
|
25950
|
-
return c.json({ ok: true });
|
|
25951
|
-
});
|
|
25952
|
-
return app2;
|
|
25953
|
-
}
|
|
25954
|
-
function passwordStrengthError(pw) {
|
|
25955
|
-
if (pw.length < 8)
|
|
25956
|
-
return "Password must be at least 8 characters";
|
|
25957
|
-
if (!/[A-Za-z]/.test(pw))
|
|
25958
|
-
return "Password must contain at least one letter";
|
|
25959
|
-
if (!/[0-9]/.test(pw))
|
|
25960
|
-
return "Password must contain at least one digit";
|
|
25961
|
-
return null;
|
|
25962
|
-
}
|
|
25963
|
-
function authMiddleware() {
|
|
25964
|
-
return async (c, next) => {
|
|
25965
|
-
const path89 = c.req.path;
|
|
25966
|
-
if (PUBLIC_PATHS.some((p) => path89 === p || path89.startsWith(p + "?"))) {
|
|
25967
|
-
return next();
|
|
25968
|
-
}
|
|
25969
|
-
if (EXTENSION_ASSET_PATH.test(path89)) {
|
|
25970
|
-
return next();
|
|
25971
|
-
}
|
|
25972
|
-
const token = getCookie(c, COOKIE_NAME);
|
|
25973
|
-
const payload = validateToken(token);
|
|
25974
|
-
if (!payload) {
|
|
25975
|
-
if (!path89.startsWith("/api/")) {
|
|
25976
|
-
return next();
|
|
25977
|
-
}
|
|
25978
|
-
return c.json({ error: "Unauthorized" }, 401);
|
|
25979
|
-
}
|
|
25980
|
-
if (shouldRefresh(payload)) {
|
|
25981
|
-
const newToken = createToken();
|
|
25982
|
-
setTokenCookie(c, newToken);
|
|
25983
|
-
}
|
|
25984
|
-
return next();
|
|
25985
|
-
};
|
|
25986
|
-
}
|
|
25987
|
-
var TOKEN_MAX_AGE, REFRESH_AFTER, COOKIE_NAME, ADMIN_LOGIN_BUCKET, isLockedOut2, recordFailure2, clearFailures2, PUBLIC_PATHS, EXTENSION_ASSET_PATH;
|
|
25988
|
-
var init_auth = __esm({
|
|
25989
|
-
"../server/dist/middleware/auth.js"() {
|
|
25990
|
-
"use strict";
|
|
25991
|
-
init_config();
|
|
25992
|
-
init_password_hash();
|
|
25993
|
-
init_setup_config();
|
|
25994
|
-
init_brute_force();
|
|
25995
|
-
TOKEN_MAX_AGE = config.auth.tokenMaxAge;
|
|
25996
|
-
REFRESH_AFTER = config.auth.refreshAfter;
|
|
25997
|
-
COOKIE_NAME = "halo_token";
|
|
25998
|
-
ADMIN_LOGIN_BUCKET = "admin-login";
|
|
25999
|
-
isLockedOut2 = (ip) => isLockedOut(ADMIN_LOGIN_BUCKET, ip);
|
|
26000
|
-
recordFailure2 = (ip) => recordFailure(ADMIN_LOGIN_BUCKET, ip);
|
|
26001
|
-
clearFailures2 = (ip) => clearFailures(ADMIN_LOGIN_BUCKET, ip);
|
|
26002
|
-
PUBLIC_PATHS = ["/api/auth/login", "/api/auth/check", "/api/auth/logout", "/api/health", "/api/web/chat", "/api/web/sessions", "/api/web/stop", "/api/web/history", "/api/web/subscribe", "/api/web/file", "/api/show/state", "/api/show/session", "/api/metrics"];
|
|
26003
|
-
EXTENSION_ASSET_PATH = /^\/api\/extensions\/[^/]+\/[^/]+\/[^/]+\/./;
|
|
26004
|
-
}
|
|
26005
|
-
});
|
|
26006
|
-
|
|
26007
26092
|
// ../server/dist/routes/transcribe-ws.js
|
|
26008
26093
|
import { WebSocketServer } from "ws";
|
|
26009
26094
|
import { StartStreamTranscriptionCommand, TranscribeStreamingClient } from "@aws-sdk/client-transcribe-streaming";
|
|
@@ -26217,19 +26302,688 @@ var init_transcribe_ws = __esm({
|
|
|
26217
26302
|
}
|
|
26218
26303
|
});
|
|
26219
26304
|
|
|
26305
|
+
// ../server/dist/stt/models.js
|
|
26306
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
26307
|
+
import fs60 from "node:fs";
|
|
26308
|
+
import fsp from "node:fs/promises";
|
|
26309
|
+
import http2 from "node:http";
|
|
26310
|
+
import https2 from "node:https";
|
|
26311
|
+
import path68 from "node:path";
|
|
26312
|
+
function httpGet(url, headers, signal, hops = 5) {
|
|
26313
|
+
return new Promise((resolve2, reject) => {
|
|
26314
|
+
const u = new URL(url);
|
|
26315
|
+
const req = (u.protocol === "https:" ? https2 : http2).get(u, { headers: { "User-Agent": "halo-stt", ...headers }, signal }, (res) => {
|
|
26316
|
+
res.on("error", () => {
|
|
26317
|
+
});
|
|
26318
|
+
const location = res.headers.location;
|
|
26319
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && location) {
|
|
26320
|
+
res.resume();
|
|
26321
|
+
if (hops === 0)
|
|
26322
|
+
reject(new Error("too many redirects"));
|
|
26323
|
+
else
|
|
26324
|
+
resolve2(httpGet(new URL(location, u).toString(), headers, signal, hops - 1));
|
|
26325
|
+
return;
|
|
26326
|
+
}
|
|
26327
|
+
resolve2(res);
|
|
26328
|
+
});
|
|
26329
|
+
req.on("error", reject);
|
|
26330
|
+
});
|
|
26331
|
+
}
|
|
26332
|
+
function createSttModelManager(opts = {}) {
|
|
26333
|
+
const files = opts.files ?? STT_MODEL_FILES;
|
|
26334
|
+
const connectTimeoutMs = opts.connectTimeoutMs ?? 15e3;
|
|
26335
|
+
const stallTimeoutMs = opts.stallTimeoutMs ?? 3e4;
|
|
26336
|
+
const progressIntervalMs = opts.progressIntervalMs ?? 500;
|
|
26337
|
+
const filePath = (rel) => path68.join(opts.dir ?? globalSttModelsDir(), rel);
|
|
26338
|
+
const total = files.reduce((n, f) => n + f.size, 0);
|
|
26339
|
+
const listeners2 = /* @__PURE__ */ new Set();
|
|
26340
|
+
let inflight = null;
|
|
26341
|
+
let received = 0;
|
|
26342
|
+
let lastError = "";
|
|
26343
|
+
let lastEmit = 0;
|
|
26344
|
+
let current = null;
|
|
26345
|
+
let closed = false;
|
|
26346
|
+
const fileReady = (f) => {
|
|
26347
|
+
try {
|
|
26348
|
+
return fs60.statSync(filePath(f.path)).size === f.size;
|
|
26349
|
+
} catch {
|
|
26350
|
+
return false;
|
|
26351
|
+
}
|
|
26352
|
+
};
|
|
26353
|
+
function status() {
|
|
26354
|
+
if (inflight)
|
|
26355
|
+
return { state: "downloading", received, total };
|
|
26356
|
+
if (files.every(fileReady))
|
|
26357
|
+
return { state: "ready" };
|
|
26358
|
+
if (lastError)
|
|
26359
|
+
return { state: "error", message: lastError };
|
|
26360
|
+
return { state: "missing" };
|
|
26361
|
+
}
|
|
26362
|
+
function emit2() {
|
|
26363
|
+
lastEmit = Date.now();
|
|
26364
|
+
const s = status();
|
|
26365
|
+
for (const l of listeners2)
|
|
26366
|
+
l(s);
|
|
26367
|
+
}
|
|
26368
|
+
async function fetchFile(f, url, base) {
|
|
26369
|
+
const final = filePath(f.path);
|
|
26370
|
+
const part = `${final}.part`;
|
|
26371
|
+
fs60.mkdirSync(path68.dirname(final), { recursive: true });
|
|
26372
|
+
let have = 0;
|
|
26373
|
+
try {
|
|
26374
|
+
have = fs60.statSync(part).size;
|
|
26375
|
+
} catch {
|
|
26376
|
+
}
|
|
26377
|
+
if (have > f.size) {
|
|
26378
|
+
fs60.rmSync(part, { force: true });
|
|
26379
|
+
have = 0;
|
|
26380
|
+
}
|
|
26381
|
+
received = base + have;
|
|
26382
|
+
if (have === f.size) {
|
|
26383
|
+
const hash = createHash2("sha256");
|
|
26384
|
+
for await (const chunk of fs60.createReadStream(part))
|
|
26385
|
+
hash.update(chunk);
|
|
26386
|
+
if (hash.digest("hex") === f.sha256) {
|
|
26387
|
+
fs60.renameSync(part, final);
|
|
26388
|
+
return;
|
|
26389
|
+
}
|
|
26390
|
+
fs60.rmSync(part, { force: true });
|
|
26391
|
+
have = 0;
|
|
26392
|
+
}
|
|
26393
|
+
const ac = new AbortController();
|
|
26394
|
+
current = ac;
|
|
26395
|
+
let reason = "";
|
|
26396
|
+
let timer2 = null;
|
|
26397
|
+
const arm = (ms, why) => {
|
|
26398
|
+
if (timer2)
|
|
26399
|
+
clearTimeout(timer2);
|
|
26400
|
+
timer2 = setTimeout(() => {
|
|
26401
|
+
reason = why;
|
|
26402
|
+
ac.abort();
|
|
26403
|
+
}, ms);
|
|
26404
|
+
};
|
|
26405
|
+
arm(connectTimeoutMs, `no data within ${connectTimeoutMs / 1e3}s`);
|
|
26406
|
+
try {
|
|
26407
|
+
const res = await httpGet(url, have > 0 ? { Range: `bytes=${have}-` } : {}, ac.signal);
|
|
26408
|
+
if (res.statusCode === 416) {
|
|
26409
|
+
fs60.rmSync(part, { force: true });
|
|
26410
|
+
throw new Error("HTTP 416");
|
|
26411
|
+
}
|
|
26412
|
+
if (res.statusCode !== 200 && res.statusCode !== 206)
|
|
26413
|
+
throw new Error(`HTTP ${res.statusCode}`);
|
|
26414
|
+
const resume = res.statusCode === 206;
|
|
26415
|
+
if (resume && !(res.headers["content-range"] ?? "").startsWith(`bytes ${have}-`)) {
|
|
26416
|
+
throw new Error(`unexpected Content-Range ${res.headers["content-range"] ?? "(none)"}`);
|
|
26417
|
+
}
|
|
26418
|
+
if (have > 0)
|
|
26419
|
+
console.log(`[STT] ${f.path}: ${resume ? `resuming at ${have} bytes` : `source ignored Range, restarting from 0 (had ${have} bytes)`}`);
|
|
26420
|
+
const hash = createHash2("sha256");
|
|
26421
|
+
let got = 0;
|
|
26422
|
+
if (resume) {
|
|
26423
|
+
for await (const chunk of fs60.createReadStream(part))
|
|
26424
|
+
hash.update(chunk);
|
|
26425
|
+
got = have;
|
|
26426
|
+
}
|
|
26427
|
+
const fh = await fsp.open(part, resume ? "a" : "w");
|
|
26428
|
+
try {
|
|
26429
|
+
for await (const value of res) {
|
|
26430
|
+
arm(stallTimeoutMs, `stalled for ${stallTimeoutMs / 1e3}s`);
|
|
26431
|
+
got += value.length;
|
|
26432
|
+
if (got > f.size)
|
|
26433
|
+
break;
|
|
26434
|
+
hash.update(value);
|
|
26435
|
+
await fh.write(value);
|
|
26436
|
+
received = base + got;
|
|
26437
|
+
if (Date.now() - lastEmit >= progressIntervalMs)
|
|
26438
|
+
emit2();
|
|
26439
|
+
}
|
|
26440
|
+
} finally {
|
|
26441
|
+
await fh.close();
|
|
26442
|
+
}
|
|
26443
|
+
if (got < f.size)
|
|
26444
|
+
throw new Error(`truncated (${got} of ${f.size} bytes)`);
|
|
26445
|
+
if (got > f.size || hash.digest("hex") !== f.sha256) {
|
|
26446
|
+
fs60.rmSync(part, { force: true });
|
|
26447
|
+
throw new Error(got > f.size ? `size mismatch (more than ${f.size} bytes)` : "sha256 mismatch");
|
|
26448
|
+
}
|
|
26449
|
+
fs60.renameSync(part, final);
|
|
26450
|
+
} catch (err) {
|
|
26451
|
+
if (reason)
|
|
26452
|
+
throw new Error(reason);
|
|
26453
|
+
throw err;
|
|
26454
|
+
} finally {
|
|
26455
|
+
if (timer2)
|
|
26456
|
+
clearTimeout(timer2);
|
|
26457
|
+
ac.abort();
|
|
26458
|
+
current = null;
|
|
26459
|
+
}
|
|
26460
|
+
}
|
|
26461
|
+
async function run() {
|
|
26462
|
+
let base = files.filter(fileReady).reduce((n, f) => n + f.size, 0);
|
|
26463
|
+
received = base;
|
|
26464
|
+
for (const f of files) {
|
|
26465
|
+
if (fileReady(f))
|
|
26466
|
+
continue;
|
|
26467
|
+
const failures = [];
|
|
26468
|
+
for (const src of f.sources) {
|
|
26469
|
+
if (closed)
|
|
26470
|
+
return;
|
|
26471
|
+
const t0 = Date.now();
|
|
26472
|
+
try {
|
|
26473
|
+
await fetchFile(f, src.url, base);
|
|
26474
|
+
console.log(`[STT] downloaded ${f.path} from ${src.name} (${(f.size / 1048576).toFixed(1)} MB in ${((Date.now() - t0) / 1e3).toFixed(1)}s)`);
|
|
26475
|
+
failures.length = 0;
|
|
26476
|
+
break;
|
|
26477
|
+
} catch (err) {
|
|
26478
|
+
if (closed)
|
|
26479
|
+
return;
|
|
26480
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26481
|
+
failures.push(`${src.name}: ${msg}`);
|
|
26482
|
+
console.log(`[STT] download ${f.path} from ${src.name} failed: ${msg}`);
|
|
26483
|
+
}
|
|
26484
|
+
}
|
|
26485
|
+
if (failures.length > 0) {
|
|
26486
|
+
lastError = `${path68.basename(f.path)}: all sources failed (${failures.join("; ")})`;
|
|
26487
|
+
return;
|
|
26488
|
+
}
|
|
26489
|
+
base += f.size;
|
|
26490
|
+
received = base;
|
|
26491
|
+
}
|
|
26492
|
+
}
|
|
26493
|
+
function ensure() {
|
|
26494
|
+
if (inflight)
|
|
26495
|
+
return inflight;
|
|
26496
|
+
if (closed || files.every(fileReady))
|
|
26497
|
+
return Promise.resolve();
|
|
26498
|
+
lastError = "";
|
|
26499
|
+
console.log("[STT] model download start");
|
|
26500
|
+
inflight = run().finally(() => {
|
|
26501
|
+
inflight = null;
|
|
26502
|
+
emit2();
|
|
26503
|
+
});
|
|
26504
|
+
emit2();
|
|
26505
|
+
return inflight;
|
|
26506
|
+
}
|
|
26507
|
+
return {
|
|
26508
|
+
status,
|
|
26509
|
+
ensure,
|
|
26510
|
+
subscribe(listener) {
|
|
26511
|
+
listeners2.add(listener);
|
|
26512
|
+
return () => {
|
|
26513
|
+
listeners2.delete(listener);
|
|
26514
|
+
};
|
|
26515
|
+
},
|
|
26516
|
+
filePath,
|
|
26517
|
+
close() {
|
|
26518
|
+
closed = true;
|
|
26519
|
+
current?.abort();
|
|
26520
|
+
}
|
|
26521
|
+
};
|
|
26522
|
+
}
|
|
26523
|
+
var SENSE_VOICE_MODEL, SENSE_VOICE_TOKENS, SILERO_VAD_MODEL, HF_SENSE_VOICE, MS_SENSE_VOICE, STT_MODEL_FILES, sttModels;
|
|
26524
|
+
var init_models = __esm({
|
|
26525
|
+
"../server/dist/stt/models.js"() {
|
|
26526
|
+
"use strict";
|
|
26527
|
+
init_paths();
|
|
26528
|
+
SENSE_VOICE_MODEL = "sense-voice-zh-en-ja-ko-yue-2024-07-17/model.int8.onnx";
|
|
26529
|
+
SENSE_VOICE_TOKENS = "sense-voice-zh-en-ja-ko-yue-2024-07-17/tokens.txt";
|
|
26530
|
+
SILERO_VAD_MODEL = "silero-vad-v4/silero_vad.onnx";
|
|
26531
|
+
HF_SENSE_VOICE = "https://huggingface.co/csukuangfj/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/resolve/2365baeacb507f821a0c8120fcee3d484dba7a07";
|
|
26532
|
+
MS_SENSE_VOICE = "https://www.modelscope.cn/api/v1/models/pengzhendong/sherpa-onnx-sense-voice-zh-en-ja-ko-yue/repo?Revision=master&FilePath=";
|
|
26533
|
+
STT_MODEL_FILES = [
|
|
26534
|
+
{
|
|
26535
|
+
path: SENSE_VOICE_MODEL,
|
|
26536
|
+
size: 239233841,
|
|
26537
|
+
sha256: "c71f0ce00bec95b07744e116345e33d8cbbe08cef896382cf907bf4b51a2cd51",
|
|
26538
|
+
sources: [
|
|
26539
|
+
{ name: "huggingface", url: `${HF_SENSE_VOICE}/model.int8.onnx` },
|
|
26540
|
+
{ name: "modelscope", url: `${MS_SENSE_VOICE}model.int8.onnx` }
|
|
26541
|
+
]
|
|
26542
|
+
},
|
|
26543
|
+
{
|
|
26544
|
+
path: SENSE_VOICE_TOKENS,
|
|
26545
|
+
size: 315894,
|
|
26546
|
+
sha256: "f449eb28dc567533d7fa59be34e2abca8784f771850c78a47fb731a31429a1dc",
|
|
26547
|
+
sources: [
|
|
26548
|
+
{ name: "huggingface", url: `${HF_SENSE_VOICE}/tokens.txt` },
|
|
26549
|
+
{ name: "modelscope", url: `${MS_SENSE_VOICE}tokens.txt` }
|
|
26550
|
+
]
|
|
26551
|
+
},
|
|
26552
|
+
{
|
|
26553
|
+
path: SILERO_VAD_MODEL,
|
|
26554
|
+
size: 1807522,
|
|
26555
|
+
sha256: "a35ebf52fd3ce5f1469b2a36158dba761bc47b973ea3382b3186ca15b1f5af28",
|
|
26556
|
+
sources: [
|
|
26557
|
+
{ name: "huggingface", url: "https://huggingface.co/csukuangfj/vad/resolve/fba88cd2e921609e7675c3aaf51e0b9b295da4bc/silero_vad.onnx" },
|
|
26558
|
+
{ name: "modelscope", url: "https://www.modelscope.cn/api/v1/models/pengzhendong/silero-vad/repo?Revision=master&FilePath=v4/silero_vad.onnx" }
|
|
26559
|
+
]
|
|
26560
|
+
}
|
|
26561
|
+
];
|
|
26562
|
+
sttModels = createSttModelManager();
|
|
26563
|
+
}
|
|
26564
|
+
});
|
|
26565
|
+
|
|
26566
|
+
// ../server/dist/stt/engine.js
|
|
26567
|
+
import os8 from "node:os";
|
|
26568
|
+
function createSttEngine(deps = {}) {
|
|
26569
|
+
const loadSherpa = deps.loadSherpa ?? defaultLoadSherpa;
|
|
26570
|
+
const models = deps.models ?? sttModels;
|
|
26571
|
+
let sherpa = null;
|
|
26572
|
+
let recognizer = null;
|
|
26573
|
+
let loading = null;
|
|
26574
|
+
const queue = [];
|
|
26575
|
+
let decoding = false;
|
|
26576
|
+
function load() {
|
|
26577
|
+
if (recognizer)
|
|
26578
|
+
return Promise.resolve();
|
|
26579
|
+
loading ??= doLoad().finally(() => {
|
|
26580
|
+
loading = null;
|
|
26581
|
+
});
|
|
26582
|
+
return loading;
|
|
26583
|
+
}
|
|
26584
|
+
async function doLoad() {
|
|
26585
|
+
const t0 = Date.now();
|
|
26586
|
+
try {
|
|
26587
|
+
const m = await loadSherpa();
|
|
26588
|
+
recognizer = await m.OfflineRecognizer.createAsync({
|
|
26589
|
+
featConfig: { sampleRate: SAMPLE_RATE, featureDim: 80 },
|
|
26590
|
+
modelConfig: {
|
|
26591
|
+
// language '' = auto: setting one barely changed results in testing.
|
|
26592
|
+
senseVoice: { model: models.filePath(SENSE_VOICE_MODEL), language: "", useInverseTextNormalization: 1 },
|
|
26593
|
+
tokens: models.filePath(SENSE_VOICE_TOKENS),
|
|
26594
|
+
numThreads: NUM_THREADS,
|
|
26595
|
+
provider: "cpu",
|
|
26596
|
+
debug: 0
|
|
26597
|
+
}
|
|
26598
|
+
});
|
|
26599
|
+
sherpa = m;
|
|
26600
|
+
console.log(`[STT] engine loaded in ${Date.now() - t0}ms (threads=${NUM_THREADS})`);
|
|
26601
|
+
} catch (err) {
|
|
26602
|
+
console.log(`[STT] engine load failed: ${errMsg2(err)}`);
|
|
26603
|
+
throw new SttError("engine", `speech engine unavailable: ${errMsg2(err)}`);
|
|
26604
|
+
}
|
|
26605
|
+
}
|
|
26606
|
+
async function pump() {
|
|
26607
|
+
if (decoding || !recognizer)
|
|
26608
|
+
return;
|
|
26609
|
+
decoding = true;
|
|
26610
|
+
while (queue.length > 0) {
|
|
26611
|
+
const job = queue.shift();
|
|
26612
|
+
try {
|
|
26613
|
+
const stream = recognizer.createStream();
|
|
26614
|
+
stream.acceptWaveform({ samples: job.samples, sampleRate: SAMPLE_RATE });
|
|
26615
|
+
await recognizer.decodeAsync(stream);
|
|
26616
|
+
job.owner.done(job, recognizer.getResult(stream));
|
|
26617
|
+
} catch (err) {
|
|
26618
|
+
job.owner.fail(new SttError("engine", `decode failed: ${errMsg2(err)}`));
|
|
26619
|
+
}
|
|
26620
|
+
}
|
|
26621
|
+
decoding = false;
|
|
26622
|
+
}
|
|
26623
|
+
function open(handlers) {
|
|
26624
|
+
if (!sherpa)
|
|
26625
|
+
throw new SttError("engine", "speech engine not loaded");
|
|
26626
|
+
let vad;
|
|
26627
|
+
try {
|
|
26628
|
+
vad = new sherpa.Vad({
|
|
26629
|
+
sileroVad: {
|
|
26630
|
+
model: models.filePath(SILERO_VAD_MODEL),
|
|
26631
|
+
threshold: VAD_THRESHOLD,
|
|
26632
|
+
minSilenceDuration: VAD_MIN_SILENCE_S,
|
|
26633
|
+
minSpeechDuration: VAD_MIN_SPEECH_S,
|
|
26634
|
+
maxSpeechDuration: VAD_MAX_SPEECH_S,
|
|
26635
|
+
windowSize: VAD_WINDOW
|
|
26636
|
+
},
|
|
26637
|
+
sampleRate: SAMPLE_RATE,
|
|
26638
|
+
numThreads: 1,
|
|
26639
|
+
debug: false
|
|
26640
|
+
}, RING_S);
|
|
26641
|
+
} catch (err) {
|
|
26642
|
+
throw new SttError("engine", `VAD unavailable: ${errMsg2(err)}`);
|
|
26643
|
+
}
|
|
26644
|
+
const ringLen = RING_S * SAMPLE_RATE;
|
|
26645
|
+
const ring = new Float32Array(ringLen);
|
|
26646
|
+
const win = new Float32Array(VAD_WINDOW);
|
|
26647
|
+
let winFill = 0;
|
|
26648
|
+
let total = 0;
|
|
26649
|
+
let carry = null;
|
|
26650
|
+
let prevEnd = 0;
|
|
26651
|
+
let pendingSamples = 0;
|
|
26652
|
+
let inFlight2 = 0;
|
|
26653
|
+
let segments = 0;
|
|
26654
|
+
let decodedSamples = 0;
|
|
26655
|
+
let stopped = false;
|
|
26656
|
+
let drained = null;
|
|
26657
|
+
const checkDrain = () => {
|
|
26658
|
+
if (drained && (stopped || inFlight2 === 0)) {
|
|
26659
|
+
const d = drained;
|
|
26660
|
+
drained = null;
|
|
26661
|
+
d();
|
|
26662
|
+
}
|
|
26663
|
+
};
|
|
26664
|
+
const dropQueued = () => {
|
|
26665
|
+
for (let i = queue.length - 1; i >= 0; i--) {
|
|
26666
|
+
if (queue[i].owner !== owner)
|
|
26667
|
+
continue;
|
|
26668
|
+
pendingSamples -= queue[i].samples.length;
|
|
26669
|
+
inFlight2--;
|
|
26670
|
+
queue.splice(i, 1);
|
|
26671
|
+
}
|
|
26672
|
+
};
|
|
26673
|
+
function failStream(err) {
|
|
26674
|
+
if (stopped)
|
|
26675
|
+
return;
|
|
26676
|
+
stopped = true;
|
|
26677
|
+
dropQueued();
|
|
26678
|
+
handlers.onError(err);
|
|
26679
|
+
checkDrain();
|
|
26680
|
+
}
|
|
26681
|
+
const owner = {
|
|
26682
|
+
done(job, result) {
|
|
26683
|
+
pendingSamples -= job.samples.length;
|
|
26684
|
+
inFlight2--;
|
|
26685
|
+
if (!stopped) {
|
|
26686
|
+
segments++;
|
|
26687
|
+
decodedSamples += job.samples.length;
|
|
26688
|
+
const text6 = (result.text ?? "").trim();
|
|
26689
|
+
if (text6)
|
|
26690
|
+
handlers.onFinal({ start: round3(job.start / SAMPLE_RATE), end: round3(job.end / SAMPLE_RATE), text: text6, lang: (result.lang ?? "").replace(/<\|([^|]*)\|>/g, "$1") });
|
|
26691
|
+
}
|
|
26692
|
+
checkDrain();
|
|
26693
|
+
},
|
|
26694
|
+
fail(err) {
|
|
26695
|
+
inFlight2--;
|
|
26696
|
+
failStream(err);
|
|
26697
|
+
checkDrain();
|
|
26698
|
+
}
|
|
26699
|
+
};
|
|
26700
|
+
function enqueue(segStart, segEnd) {
|
|
26701
|
+
const start3 = Math.max(segStart - PAD_BEFORE_S * SAMPLE_RATE, prevEnd, total - ringLen, 0);
|
|
26702
|
+
const end = Math.min(segEnd + PAD_AFTER_S * SAMPLE_RATE, total);
|
|
26703
|
+
if (end <= start3)
|
|
26704
|
+
return;
|
|
26705
|
+
prevEnd = end;
|
|
26706
|
+
if (queue.length >= MAX_QUEUED_SEGMENTS) {
|
|
26707
|
+
failStream(new SttError("busy", `decode queue full (${MAX_QUEUED_SEGMENTS} segments waiting)`));
|
|
26708
|
+
return;
|
|
26709
|
+
}
|
|
26710
|
+
if ((pendingSamples + end - start3) / SAMPLE_RATE > MAX_PENDING_AUDIO_S) {
|
|
26711
|
+
failStream(new SttError("busy", `more than ${MAX_PENDING_AUDIO_S}s of audio waiting to be decoded`));
|
|
26712
|
+
return;
|
|
26713
|
+
}
|
|
26714
|
+
const samples = new Float32Array(end - start3);
|
|
26715
|
+
for (let i = 0; i < samples.length; i++)
|
|
26716
|
+
samples[i] = ring[(start3 + i) % ringLen];
|
|
26717
|
+
pendingSamples += samples.length;
|
|
26718
|
+
inFlight2++;
|
|
26719
|
+
queue.push({ owner, samples, start: start3, end });
|
|
26720
|
+
void pump();
|
|
26721
|
+
}
|
|
26722
|
+
function drainVad() {
|
|
26723
|
+
while (!stopped && !vad.isEmpty()) {
|
|
26724
|
+
const seg = vad.front(false);
|
|
26725
|
+
vad.pop();
|
|
26726
|
+
enqueue(seg.start, seg.start + seg.samples.length);
|
|
26727
|
+
}
|
|
26728
|
+
}
|
|
26729
|
+
function feed(samples) {
|
|
26730
|
+
vad.acceptWaveform(samples);
|
|
26731
|
+
drainVad();
|
|
26732
|
+
}
|
|
26733
|
+
return {
|
|
26734
|
+
write(pcm) {
|
|
26735
|
+
if (stopped)
|
|
26736
|
+
return;
|
|
26737
|
+
try {
|
|
26738
|
+
const bytes = carry ? Buffer.concat([carry, pcm]) : pcm;
|
|
26739
|
+
const n = bytes.length >> 1;
|
|
26740
|
+
carry = bytes.length & 1 ? Buffer.from(bytes.subarray(bytes.length - 1)) : null;
|
|
26741
|
+
for (let i = 0; i < n && !stopped; i++) {
|
|
26742
|
+
const f = bytes.readInt16LE(i * 2) / 32768;
|
|
26743
|
+
ring[total % ringLen] = f;
|
|
26744
|
+
total++;
|
|
26745
|
+
win[winFill++] = f;
|
|
26746
|
+
if (winFill === VAD_WINDOW) {
|
|
26747
|
+
winFill = 0;
|
|
26748
|
+
feed(win);
|
|
26749
|
+
}
|
|
26750
|
+
}
|
|
26751
|
+
} catch (err) {
|
|
26752
|
+
failStream(new SttError("engine", `VAD failed: ${errMsg2(err)}`));
|
|
26753
|
+
}
|
|
26754
|
+
},
|
|
26755
|
+
end() {
|
|
26756
|
+
if (!stopped) {
|
|
26757
|
+
try {
|
|
26758
|
+
if (winFill > 0) {
|
|
26759
|
+
win.fill(0, winFill);
|
|
26760
|
+
winFill = 0;
|
|
26761
|
+
feed(win);
|
|
26762
|
+
}
|
|
26763
|
+
vad.flush();
|
|
26764
|
+
drainVad();
|
|
26765
|
+
} catch (err) {
|
|
26766
|
+
failStream(new SttError("engine", `VAD failed: ${errMsg2(err)}`));
|
|
26767
|
+
}
|
|
26768
|
+
}
|
|
26769
|
+
return new Promise((resolve2) => {
|
|
26770
|
+
drained = resolve2;
|
|
26771
|
+
checkDrain();
|
|
26772
|
+
});
|
|
26773
|
+
},
|
|
26774
|
+
abort() {
|
|
26775
|
+
stopped = true;
|
|
26776
|
+
dropQueued();
|
|
26777
|
+
checkDrain();
|
|
26778
|
+
},
|
|
26779
|
+
stats: () => ({ segments, audioSeconds: round3(decodedSamples / SAMPLE_RATE) })
|
|
26780
|
+
};
|
|
26781
|
+
}
|
|
26782
|
+
return { load, open };
|
|
26783
|
+
}
|
|
26784
|
+
var SAMPLE_RATE, VAD_WINDOW, VAD_THRESHOLD, VAD_MIN_SILENCE_S, VAD_MIN_SPEECH_S, VAD_MAX_SPEECH_S, PAD_BEFORE_S, PAD_AFTER_S, RING_S, MAX_QUEUED_SEGMENTS, MAX_PENDING_AUDIO_S, NUM_THREADS, SttError, defaultLoadSherpa, errMsg2, round3, sttEngine;
|
|
26785
|
+
var init_engine = __esm({
|
|
26786
|
+
"../server/dist/stt/engine.js"() {
|
|
26787
|
+
"use strict";
|
|
26788
|
+
init_models();
|
|
26789
|
+
SAMPLE_RATE = 16e3;
|
|
26790
|
+
VAD_WINDOW = 512;
|
|
26791
|
+
VAD_THRESHOLD = 0.5;
|
|
26792
|
+
VAD_MIN_SILENCE_S = 0.5;
|
|
26793
|
+
VAD_MIN_SPEECH_S = 0.25;
|
|
26794
|
+
VAD_MAX_SPEECH_S = 20;
|
|
26795
|
+
PAD_BEFORE_S = 0.3;
|
|
26796
|
+
PAD_AFTER_S = 0.2;
|
|
26797
|
+
RING_S = 30;
|
|
26798
|
+
MAX_QUEUED_SEGMENTS = 32;
|
|
26799
|
+
MAX_PENDING_AUDIO_S = 60;
|
|
26800
|
+
NUM_THREADS = Math.min(4, Math.max(1, Math.floor(os8.availableParallelism() / 2)));
|
|
26801
|
+
SttError = class extends Error {
|
|
26802
|
+
code;
|
|
26803
|
+
constructor(code, message) {
|
|
26804
|
+
super(message);
|
|
26805
|
+
this.code = code;
|
|
26806
|
+
this.name = "SttError";
|
|
26807
|
+
}
|
|
26808
|
+
};
|
|
26809
|
+
defaultLoadSherpa = async () => (await import("sherpa-onnx-node")).default;
|
|
26810
|
+
errMsg2 = (err) => err instanceof Error ? err.message : String(err);
|
|
26811
|
+
round3 = (s) => Math.round(s * 1e3) / 1e3;
|
|
26812
|
+
sttEngine = createSttEngine();
|
|
26813
|
+
}
|
|
26814
|
+
});
|
|
26815
|
+
|
|
26816
|
+
// ../server/dist/routes/stt-ws.js
|
|
26817
|
+
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
26818
|
+
function parseQuery2(req) {
|
|
26819
|
+
const url = req.url ?? "";
|
|
26820
|
+
const i = url.indexOf("?");
|
|
26821
|
+
return new URLSearchParams(i < 0 ? "" : url.slice(i + 1));
|
|
26822
|
+
}
|
|
26823
|
+
function createSttProxy(deps = {}) {
|
|
26824
|
+
const models = deps.models ?? sttModels;
|
|
26825
|
+
const engine = deps.engine ?? sttEngine;
|
|
26826
|
+
const wss2 = new WebSocketServer2({
|
|
26827
|
+
noServer: true,
|
|
26828
|
+
maxPayload: MAX_FRAME_BYTES2,
|
|
26829
|
+
// Same cookie gate as /ws; the extension iframe is same-origin with the
|
|
26830
|
+
// admin, so its WS handshake carries the login cookie.
|
|
26831
|
+
verifyClient: (info, callback) => {
|
|
26832
|
+
if (!isAuthenticated(getTokenFromCookieHeader(info.req.headers.cookie)))
|
|
26833
|
+
return callback(false, 401, "Unauthorized");
|
|
26834
|
+
const ext = getExtension(parseQuery2(info.req).get("ext") ?? "");
|
|
26835
|
+
if (!ext || !ext.capabilities.includes("stt-local"))
|
|
26836
|
+
return callback(false, 403, "Forbidden");
|
|
26837
|
+
callback(true);
|
|
26838
|
+
}
|
|
26839
|
+
});
|
|
26840
|
+
wss2.on("connection", (ws, req) => {
|
|
26841
|
+
const extId = parseQuery2(req).get("ext");
|
|
26842
|
+
const openedAt = Date.now();
|
|
26843
|
+
let closed = false;
|
|
26844
|
+
let failed = false;
|
|
26845
|
+
let ended = false;
|
|
26846
|
+
let stream = null;
|
|
26847
|
+
let unsubscribe = null;
|
|
26848
|
+
ws.on("error", (err) => {
|
|
26849
|
+
console.log(`[STT] client error ext=${extId}: ${err.message}`);
|
|
26850
|
+
});
|
|
26851
|
+
const send2 = (frame) => {
|
|
26852
|
+
if (ws.readyState === ws.OPEN)
|
|
26853
|
+
ws.send(JSON.stringify(frame));
|
|
26854
|
+
};
|
|
26855
|
+
const fail2 = (code, message) => {
|
|
26856
|
+
if (failed || closed)
|
|
26857
|
+
return;
|
|
26858
|
+
failed = true;
|
|
26859
|
+
console.log(`[STT] stream error ext=${extId} code=${code}`);
|
|
26860
|
+
send2({ type: "error", code, message });
|
|
26861
|
+
ws.close(1011, code);
|
|
26862
|
+
};
|
|
26863
|
+
async function startEngine() {
|
|
26864
|
+
try {
|
|
26865
|
+
await engine.load();
|
|
26866
|
+
if (closed || failed || ended)
|
|
26867
|
+
return;
|
|
26868
|
+
stream = engine.open({
|
|
26869
|
+
// Recognized text goes to the client only — never to the log.
|
|
26870
|
+
onFinal: (f) => send2({ type: "final", ...f }),
|
|
26871
|
+
onError: (err) => fail2(err.code, err.message)
|
|
26872
|
+
});
|
|
26873
|
+
} catch (err) {
|
|
26874
|
+
if (err instanceof SttError)
|
|
26875
|
+
fail2(err.code, err.message);
|
|
26876
|
+
else
|
|
26877
|
+
fail2("io", err instanceof Error ? err.message : String(err));
|
|
26878
|
+
return;
|
|
26879
|
+
}
|
|
26880
|
+
if (closed || failed)
|
|
26881
|
+
return;
|
|
26882
|
+
send2({ type: "ready" });
|
|
26883
|
+
}
|
|
26884
|
+
const onModel = (s) => {
|
|
26885
|
+
if (closed || failed)
|
|
26886
|
+
return;
|
|
26887
|
+
send2({ type: "model", ...s });
|
|
26888
|
+
if (s.state === "ready" || s.state === "error") {
|
|
26889
|
+
unsubscribe?.();
|
|
26890
|
+
unsubscribe = null;
|
|
26891
|
+
}
|
|
26892
|
+
if (s.state === "ready")
|
|
26893
|
+
void startEngine();
|
|
26894
|
+
else if (s.state === "error")
|
|
26895
|
+
fail2("model", s.message ?? "model download failed");
|
|
26896
|
+
};
|
|
26897
|
+
console.log(`[STT] stream open ext=${extId}`);
|
|
26898
|
+
ws.on("message", (data, isBinary) => {
|
|
26899
|
+
if (ended || failed)
|
|
26900
|
+
return;
|
|
26901
|
+
if (isBinary) {
|
|
26902
|
+
if (!stream)
|
|
26903
|
+
return;
|
|
26904
|
+
stream.write(Buffer.isBuffer(data) ? data : Buffer.concat(Array.isArray(data) ? data : [Buffer.from(data)]));
|
|
26905
|
+
return;
|
|
26906
|
+
}
|
|
26907
|
+
let msg;
|
|
26908
|
+
try {
|
|
26909
|
+
msg = JSON.parse(data.toString());
|
|
26910
|
+
} catch {
|
|
26911
|
+
msg = null;
|
|
26912
|
+
}
|
|
26913
|
+
if (msg?.type !== "end") {
|
|
26914
|
+
fail2("bad-request", 'expected binary PCM or {"type":"end"}');
|
|
26915
|
+
return;
|
|
26916
|
+
}
|
|
26917
|
+
ended = true;
|
|
26918
|
+
if (!stream) {
|
|
26919
|
+
ws.close(1e3);
|
|
26920
|
+
return;
|
|
26921
|
+
}
|
|
26922
|
+
void stream.end().then(() => {
|
|
26923
|
+
if (!closed && !failed)
|
|
26924
|
+
ws.close(1e3);
|
|
26925
|
+
});
|
|
26926
|
+
});
|
|
26927
|
+
ws.on("close", () => {
|
|
26928
|
+
closed = true;
|
|
26929
|
+
unsubscribe?.();
|
|
26930
|
+
unsubscribe = null;
|
|
26931
|
+
stream?.abort();
|
|
26932
|
+
const st = stream?.stats();
|
|
26933
|
+
console.log(`[STT] stream closed after ${((Date.now() - openedAt) / 1e3).toFixed(1)}s ext=${extId}${st ? ` segments=${st.segments} audio=${st.audioSeconds.toFixed(1)}s` : ""}`);
|
|
26934
|
+
});
|
|
26935
|
+
const initial = models.status();
|
|
26936
|
+
if (initial.state === "ready") {
|
|
26937
|
+
onModel(initial);
|
|
26938
|
+
return;
|
|
26939
|
+
}
|
|
26940
|
+
let pushed = false;
|
|
26941
|
+
unsubscribe = models.subscribe((s) => {
|
|
26942
|
+
pushed = true;
|
|
26943
|
+
onModel(s);
|
|
26944
|
+
});
|
|
26945
|
+
void models.ensure();
|
|
26946
|
+
if (!pushed)
|
|
26947
|
+
onModel(models.status());
|
|
26948
|
+
});
|
|
26949
|
+
return {
|
|
26950
|
+
handleUpgrade(req, socket, head) {
|
|
26951
|
+
wss2.handleUpgrade(req, socket, head, (ws) => wss2.emit("connection", ws, req));
|
|
26952
|
+
},
|
|
26953
|
+
close() {
|
|
26954
|
+
models.close();
|
|
26955
|
+
for (const client of wss2.clients)
|
|
26956
|
+
client.close(1001, "Server shutting down");
|
|
26957
|
+
wss2.close();
|
|
26958
|
+
}
|
|
26959
|
+
};
|
|
26960
|
+
}
|
|
26961
|
+
var STT_PATH, MAX_FRAME_BYTES2;
|
|
26962
|
+
var init_stt_ws = __esm({
|
|
26963
|
+
"../server/dist/routes/stt-ws.js"() {
|
|
26964
|
+
"use strict";
|
|
26965
|
+
init_auth();
|
|
26966
|
+
init_registry2();
|
|
26967
|
+
init_models();
|
|
26968
|
+
init_engine();
|
|
26969
|
+
STT_PATH = "/api/stt/stream";
|
|
26970
|
+
MAX_FRAME_BYTES2 = 1024 * 1024;
|
|
26971
|
+
}
|
|
26972
|
+
});
|
|
26973
|
+
|
|
26220
26974
|
// ../server/dist/jupyter/manager.js
|
|
26221
26975
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
26222
26976
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
26223
|
-
import
|
|
26977
|
+
import fs61 from "node:fs";
|
|
26224
26978
|
import net from "node:net";
|
|
26225
|
-
import
|
|
26226
|
-
import
|
|
26979
|
+
import os9 from "node:os";
|
|
26980
|
+
import path69 from "node:path";
|
|
26227
26981
|
import { WebSocket } from "ws";
|
|
26228
26982
|
function resolveExecutable(configured) {
|
|
26229
26983
|
const isExe = (p) => {
|
|
26230
26984
|
try {
|
|
26231
|
-
|
|
26232
|
-
return
|
|
26985
|
+
fs61.accessSync(p, fs61.constants.X_OK);
|
|
26986
|
+
return fs61.statSync(p).isFile();
|
|
26233
26987
|
} catch {
|
|
26234
26988
|
return false;
|
|
26235
26989
|
}
|
|
@@ -26237,9 +26991,9 @@ function resolveExecutable(configured) {
|
|
|
26237
26991
|
if (configured)
|
|
26238
26992
|
return isExe(configured) ? configured : null;
|
|
26239
26993
|
const name = process.platform === "win32" ? "jupyter.exe" : "jupyter";
|
|
26240
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26241
|
-
if (dir && isExe(
|
|
26242
|
-
return
|
|
26994
|
+
for (const dir of (process.env.PATH ?? "").split(path69.delimiter)) {
|
|
26995
|
+
if (dir && isExe(path69.join(dir, name)))
|
|
26996
|
+
return path69.join(dir, name);
|
|
26243
26997
|
}
|
|
26244
26998
|
return null;
|
|
26245
26999
|
}
|
|
@@ -26267,9 +27021,9 @@ function createJupyterManager(deps = {}) {
|
|
|
26267
27021
|
let err = "";
|
|
26268
27022
|
let child;
|
|
26269
27023
|
try {
|
|
26270
|
-
child = spawnFn(exe, ["server", "--version"], { cwd:
|
|
27024
|
+
child = spawnFn(exe, ["server", "--version"], { cwd: os9.homedir(), env: cleanChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
26271
27025
|
} catch (e) {
|
|
26272
|
-
resolve2({ ok: false, error:
|
|
27026
|
+
resolve2({ ok: false, error: errMsg3(e) });
|
|
26273
27027
|
return;
|
|
26274
27028
|
}
|
|
26275
27029
|
const timer2 = setTimeout(() => child.kill(), PROBE_TIMEOUT_MS);
|
|
@@ -26350,7 +27104,7 @@ function createJupyterManager(deps = {}) {
|
|
|
26350
27104
|
"--ServerApp.ip=127.0.0.1",
|
|
26351
27105
|
`--ServerApp.port=${port}`,
|
|
26352
27106
|
"--ServerApp.port_retries=0",
|
|
26353
|
-
`--ServerApp.root_dir=${
|
|
27107
|
+
`--ServerApp.root_dir=${path69.parse(process.cwd()).root}`,
|
|
26354
27108
|
"--ServerApp.terminals_enabled=False",
|
|
26355
27109
|
"--ServerApp.shutdown_no_activity_timeout=600",
|
|
26356
27110
|
"--MappingKernelManager.cull_idle_timeout=3600",
|
|
@@ -26360,7 +27114,7 @@ function createJupyterManager(deps = {}) {
|
|
|
26360
27114
|
// its terminal does too, and kernels match the terminal.
|
|
26361
27115
|
...process.getuid?.() === 0 ? ["--ServerApp.allow_root=True"] : []
|
|
26362
27116
|
];
|
|
26363
|
-
const child = spawnFn(exe, args, { cwd:
|
|
27117
|
+
const child = spawnFn(exe, args, { cwd: os9.homedir(), env: { ...cleanChildEnv(), JUPYTER_TOKEN: token }, stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
|
|
26364
27118
|
proc = child;
|
|
26365
27119
|
let tail = "";
|
|
26366
27120
|
child.stderr?.on("data", (d) => {
|
|
@@ -26428,7 +27182,7 @@ function createJupyterManager(deps = {}) {
|
|
|
26428
27182
|
if (kernels.get(id) !== k || k.sockets.size > 0)
|
|
26429
27183
|
return;
|
|
26430
27184
|
console.log(`[Jupyter] kernel ${id}: no client for ${KERNEL_GRACE_MS / 1e3}s, shutting it down`);
|
|
26431
|
-
kernelOp(id, "delete").catch((e) => console.log(`[Jupyter] kernel ${id} shutdown failed: ${
|
|
27185
|
+
kernelOp(id, "delete").catch((e) => console.log(`[Jupyter] kernel ${id} shutdown failed: ${errMsg3(e)}`));
|
|
26432
27186
|
}, KERNEL_GRACE_MS);
|
|
26433
27187
|
k.grace.unref?.();
|
|
26434
27188
|
}
|
|
@@ -26538,7 +27292,7 @@ function createJupyterManager(deps = {}) {
|
|
|
26538
27292
|
}
|
|
26539
27293
|
};
|
|
26540
27294
|
}
|
|
26541
|
-
var JUPYTER_HINT, KERNEL_GRACE_MS, START_TIMEOUT_MS, PROBE_TIMEOUT_MS, STOP_TIMEOUT_MS, JupyterError,
|
|
27295
|
+
var JUPYTER_HINT, KERNEL_GRACE_MS, START_TIMEOUT_MS, PROBE_TIMEOUT_MS, STOP_TIMEOUT_MS, JupyterError, errMsg3, lastLine;
|
|
26542
27296
|
var init_manager = __esm({
|
|
26543
27297
|
"../server/dist/jupyter/manager.js"() {
|
|
26544
27298
|
"use strict";
|
|
@@ -26558,16 +27312,16 @@ var init_manager = __esm({
|
|
|
26558
27312
|
this.hint = hint;
|
|
26559
27313
|
}
|
|
26560
27314
|
};
|
|
26561
|
-
|
|
27315
|
+
errMsg3 = (err) => err instanceof Error ? err.message : String(err);
|
|
26562
27316
|
lastLine = (s) => s.trim().split("\n").pop()?.trim() ?? "";
|
|
26563
27317
|
}
|
|
26564
27318
|
});
|
|
26565
27319
|
|
|
26566
27320
|
// ../server/dist/routes/jupyter.js
|
|
26567
|
-
import
|
|
26568
|
-
import
|
|
27321
|
+
import fs62 from "node:fs/promises";
|
|
27322
|
+
import path70 from "node:path";
|
|
26569
27323
|
import { Hono as Hono15 } from "hono";
|
|
26570
|
-
import { WebSocketServer as
|
|
27324
|
+
import { WebSocketServer as WebSocketServer3 } from "ws";
|
|
26571
27325
|
function fail(c, err) {
|
|
26572
27326
|
if (err instanceof JupyterError)
|
|
26573
27327
|
return c.json({ error: err.message, ...err.hint ? { hint: err.hint } : {} }, err.status);
|
|
@@ -26596,8 +27350,8 @@ function createJupyterRoutes(jm) {
|
|
|
26596
27350
|
return c.json({ error: "Project not found" }, 400);
|
|
26597
27351
|
if (!validatePath(nbPath, projectPath))
|
|
26598
27352
|
return c.json({ error: "Path outside the workspace" }, 400);
|
|
26599
|
-
const cwd = await
|
|
26600
|
-
if (!cwd || !(await
|
|
27353
|
+
const cwd = await fs62.realpath(path70.dirname(path70.resolve(projectPath, nbPath))).catch(() => null);
|
|
27354
|
+
if (!cwd || !(await fs62.stat(cwd)).isDirectory())
|
|
26601
27355
|
return c.json({ error: "Notebook directory not found" }, 400);
|
|
26602
27356
|
try {
|
|
26603
27357
|
return c.json(await jm.startKernel(name, cwd));
|
|
@@ -26641,9 +27395,9 @@ function queryOf(req) {
|
|
|
26641
27395
|
return new URLSearchParams(i < 0 ? "" : url.slice(i + 1));
|
|
26642
27396
|
}
|
|
26643
27397
|
function createJupyterChannels(jm) {
|
|
26644
|
-
const wss2 = new
|
|
27398
|
+
const wss2 = new WebSocketServer3({
|
|
26645
27399
|
noServer: true,
|
|
26646
|
-
maxPayload:
|
|
27400
|
+
maxPayload: MAX_FRAME_BYTES3,
|
|
26647
27401
|
// The client speaks the legacy JSON protocol; never echo a subprotocol.
|
|
26648
27402
|
handleProtocols: () => false,
|
|
26649
27403
|
// Same cookie gate as /ws and the transcribe proxy.
|
|
@@ -26737,7 +27491,7 @@ function createJupyterChannels(jm) {
|
|
|
26737
27491
|
}
|
|
26738
27492
|
};
|
|
26739
27493
|
}
|
|
26740
|
-
var JUPYTER_CHANNELS_PATH, KERNEL_NAME_RE, SESSION_ID_RE,
|
|
27494
|
+
var JUPYTER_CHANNELS_PATH, KERNEL_NAME_RE, SESSION_ID_RE, MAX_FRAME_BYTES3;
|
|
26741
27495
|
var init_jupyter = __esm({
|
|
26742
27496
|
"../server/dist/routes/jupyter.js"() {
|
|
26743
27497
|
"use strict";
|
|
@@ -26747,7 +27501,7 @@ var init_jupyter = __esm({
|
|
|
26747
27501
|
JUPYTER_CHANNELS_PATH = "/api/jupyter/channels";
|
|
26748
27502
|
KERNEL_NAME_RE = /^[\w.-]{1,128}$/;
|
|
26749
27503
|
SESSION_ID_RE = /^[\w-]{1,128}$/;
|
|
26750
|
-
|
|
27504
|
+
MAX_FRAME_BYTES3 = 64 * 1024 * 1024;
|
|
26751
27505
|
}
|
|
26752
27506
|
});
|
|
26753
27507
|
|
|
@@ -26768,8 +27522,8 @@ var init_upgrade_router = __esm({
|
|
|
26768
27522
|
});
|
|
26769
27523
|
|
|
26770
27524
|
// ../server/dist/ws/git-dir-watcher.js
|
|
26771
|
-
import
|
|
26772
|
-
import
|
|
27525
|
+
import fs63 from "node:fs";
|
|
27526
|
+
import path71 from "node:path";
|
|
26773
27527
|
var GitDirWatcher;
|
|
26774
27528
|
var init_git_dir_watcher = __esm({
|
|
26775
27529
|
"../server/dist/ws/git-dir-watcher.js"() {
|
|
@@ -26793,8 +27547,8 @@ var init_git_dir_watcher = __esm({
|
|
|
26793
27547
|
return;
|
|
26794
27548
|
this.stop();
|
|
26795
27549
|
this.workspaceRoot = workspaceRoot;
|
|
26796
|
-
const gitDir =
|
|
26797
|
-
if (
|
|
27550
|
+
const gitDir = path71.join(workspaceRoot, ".git");
|
|
27551
|
+
if (fs63.existsSync(gitDir)) {
|
|
26798
27552
|
this.watchGitDir(gitDir);
|
|
26799
27553
|
} else {
|
|
26800
27554
|
this.watchParentForGit(workspaceRoot);
|
|
@@ -26803,7 +27557,7 @@ var init_git_dir_watcher = __esm({
|
|
|
26803
27557
|
/** Watch the `.git` dir for index/HEAD changes (steady state). */
|
|
26804
27558
|
watchGitDir(gitDir) {
|
|
26805
27559
|
try {
|
|
26806
|
-
this.watcher =
|
|
27560
|
+
this.watcher = fs63.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
26807
27561
|
this.handleGitEvent(filename);
|
|
26808
27562
|
});
|
|
26809
27563
|
this.watcher.on("error", (err) => {
|
|
@@ -26822,7 +27576,7 @@ var init_git_dir_watcher = __esm({
|
|
|
26822
27576
|
* Exists solely during the non-repo window; upgraded away on first `.git`. */
|
|
26823
27577
|
watchParentForGit(workspaceRoot) {
|
|
26824
27578
|
try {
|
|
26825
|
-
this.watcher =
|
|
27579
|
+
this.watcher = fs63.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
26826
27580
|
this.handleParentEvent(filename);
|
|
26827
27581
|
});
|
|
26828
27582
|
this.watcher.on("error", (err) => {
|
|
@@ -26858,8 +27612,8 @@ var init_git_dir_watcher = __esm({
|
|
|
26858
27612
|
const root = this.workspaceRoot;
|
|
26859
27613
|
if (!root)
|
|
26860
27614
|
return;
|
|
26861
|
-
const gitDir =
|
|
26862
|
-
if (!
|
|
27615
|
+
const gitDir = path71.join(root, ".git");
|
|
27616
|
+
if (!fs63.existsSync(gitDir))
|
|
26863
27617
|
return;
|
|
26864
27618
|
this.closeWatcher();
|
|
26865
27619
|
this.watchGitDir(gitDir);
|
|
@@ -27277,7 +28031,7 @@ var init_terminal_manager = __esm({
|
|
|
27277
28031
|
});
|
|
27278
28032
|
|
|
27279
28033
|
// ../server/dist/ws/handler.js
|
|
27280
|
-
import
|
|
28034
|
+
import path72 from "node:path";
|
|
27281
28035
|
function setupWebSocketHandler(deps) {
|
|
27282
28036
|
const { wss: wss2, registry: registry2 } = deps;
|
|
27283
28037
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -27300,7 +28054,7 @@ function setupWebSocketHandler(deps) {
|
|
|
27300
28054
|
ackedChatIds.add(id);
|
|
27301
28055
|
}
|
|
27302
28056
|
function resolveProjectPath2(projectId) {
|
|
27303
|
-
if (
|
|
28057
|
+
if (path72.isAbsolute(projectId))
|
|
27304
28058
|
return projectId;
|
|
27305
28059
|
return null;
|
|
27306
28060
|
}
|
|
@@ -27901,11 +28655,11 @@ var init_handler2 = __esm({
|
|
|
27901
28655
|
import Database8 from "better-sqlite3";
|
|
27902
28656
|
import { drizzle as drizzle5 } from "drizzle-orm/better-sqlite3";
|
|
27903
28657
|
import { sqliteTable as sqliteTable5, text as text5, integer as integer5 } from "drizzle-orm/sqlite-core";
|
|
27904
|
-
import
|
|
27905
|
-
import
|
|
28658
|
+
import path73 from "node:path";
|
|
28659
|
+
import fs64 from "node:fs";
|
|
27906
28660
|
function createCronDb(globalDir2) {
|
|
27907
|
-
|
|
27908
|
-
const dbPath =
|
|
28661
|
+
fs64.mkdirSync(globalDir2, { recursive: true });
|
|
28662
|
+
const dbPath = path73.join(globalDir2, "cron.db");
|
|
27909
28663
|
const sqlite = new Database8(dbPath);
|
|
27910
28664
|
sqlite.pragma("journal_mode = WAL");
|
|
27911
28665
|
sqlite.exec(CREATE_SQL5);
|
|
@@ -28015,7 +28769,12 @@ var init_cron_db = __esm({
|
|
|
28015
28769
|
/** Session this run's cli was spawned on (`-s`). The orphan sweep's
|
|
28016
28770
|
* fingerprint — a stateless job's per-run id can't be re-derived from
|
|
28017
28771
|
* the job row. NULL on skipped rows and rows from before the column. */
|
|
28018
|
-
sessionId: text5("session_id")
|
|
28772
|
+
sessionId: text5("session_id"),
|
|
28773
|
+
/** Workspace the run fired against (the job's at fire time) — where a
|
|
28774
|
+
* stateless run's session lives, even after the job moves workspace or is
|
|
28775
|
+
* deleted. NULL on rows from before the column whose job was already gone
|
|
28776
|
+
* (deleteRuns falls back to the job row). */
|
|
28777
|
+
workspacePath: text5("workspace_path")
|
|
28019
28778
|
});
|
|
28020
28779
|
CREATE_SQL5 = `
|
|
28021
28780
|
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
@@ -28057,7 +28816,8 @@ CREATE TABLE IF NOT EXISTS cron_runs (
|
|
|
28057
28816
|
log_path TEXT,
|
|
28058
28817
|
dispatch_results TEXT,
|
|
28059
28818
|
pid INTEGER,
|
|
28060
|
-
session_id TEXT
|
|
28819
|
+
session_id TEXT,
|
|
28820
|
+
workspace_path TEXT
|
|
28061
28821
|
);
|
|
28062
28822
|
CREATE INDEX IF NOT EXISTS idx_cron_runs_job ON cron_runs(job_id, started_at);
|
|
28063
28823
|
CREATE INDEX IF NOT EXISTS idx_cron_runs_status ON cron_runs(status);
|
|
@@ -28098,6 +28858,15 @@ CREATE INDEX IF NOT EXISTS idx_cron_runs_status ON cron_runs(status);
|
|
|
28098
28858
|
addColumnIfMissing(s, "cron_jobs", "working_dir", "TEXT");
|
|
28099
28859
|
addColumnIfMissing(s, "cron_jobs", "stateless", "INTEGER NOT NULL DEFAULT 0");
|
|
28100
28860
|
addColumnIfMissing(s, "cron_runs", "session_id", "TEXT");
|
|
28861
|
+
},
|
|
28862
|
+
// v6: `cron_runs.workspace_path` — the workspace a run's session is in.
|
|
28863
|
+
// Backfilled from the job row; runs of an already-deleted job stay NULL.
|
|
28864
|
+
(s) => {
|
|
28865
|
+
addColumnIfMissing(s, "cron_runs", "workspace_path", "TEXT");
|
|
28866
|
+
s.exec(`
|
|
28867
|
+
UPDATE cron_runs SET workspace_path = (SELECT workspace_path FROM cron_jobs WHERE cron_jobs.id = cron_runs.job_id)
|
|
28868
|
+
WHERE workspace_path IS NULL
|
|
28869
|
+
`);
|
|
28101
28870
|
}
|
|
28102
28871
|
];
|
|
28103
28872
|
_cronDb = null;
|
|
@@ -28105,8 +28874,8 @@ CREATE INDEX IF NOT EXISTS idx_cron_runs_status ON cron_runs(status);
|
|
|
28105
28874
|
});
|
|
28106
28875
|
|
|
28107
28876
|
// ../server/dist/a2a/exposure.js
|
|
28108
|
-
import
|
|
28109
|
-
import
|
|
28877
|
+
import fs65 from "node:fs";
|
|
28878
|
+
import path74 from "node:path";
|
|
28110
28879
|
import crypto6 from "node:crypto";
|
|
28111
28880
|
function samePath(a, b, platform = process.platform) {
|
|
28112
28881
|
if (platform !== "win32")
|
|
@@ -28119,7 +28888,7 @@ function realBase() {
|
|
|
28119
28888
|
if (baseCache?.raw !== raw) {
|
|
28120
28889
|
let real = null;
|
|
28121
28890
|
try {
|
|
28122
|
-
real =
|
|
28891
|
+
real = fs65.realpathSync(raw);
|
|
28123
28892
|
} catch {
|
|
28124
28893
|
console.warn(`[A2A] base dir ${raw} not found \u2014 nothing is exposed`);
|
|
28125
28894
|
}
|
|
@@ -28144,14 +28913,14 @@ function resolveExposedWorkspace(base, rel) {
|
|
|
28144
28913
|
}
|
|
28145
28914
|
let real;
|
|
28146
28915
|
try {
|
|
28147
|
-
real =
|
|
28916
|
+
real = fs65.realpathSync(path74.join(base, ...segs));
|
|
28148
28917
|
} catch {
|
|
28149
28918
|
return null;
|
|
28150
28919
|
}
|
|
28151
|
-
if (!real.startsWith(base.endsWith(
|
|
28920
|
+
if (!real.startsWith(base.endsWith(path74.sep) ? base : base + path74.sep))
|
|
28152
28921
|
return null;
|
|
28153
28922
|
try {
|
|
28154
|
-
if (!
|
|
28923
|
+
if (!fs65.statSync(path74.join(real, CARD_FILE)).isFile())
|
|
28155
28924
|
return null;
|
|
28156
28925
|
} catch {
|
|
28157
28926
|
return null;
|
|
@@ -28166,14 +28935,14 @@ function publicOrigin(c) {
|
|
|
28166
28935
|
return `${proto}://${c.req.header("host") ?? url.host}`;
|
|
28167
28936
|
}
|
|
28168
28937
|
function buildCard(workspace, interfaceUrl, caps) {
|
|
28169
|
-
const raw = JSON.parse(
|
|
28938
|
+
const raw = JSON.parse(fs65.readFileSync(path74.join(workspace, CARD_FILE), "utf8"));
|
|
28170
28939
|
if (typeof raw.name !== "string" || typeof raw.description !== "string" || !Array.isArray(raw.skills)) {
|
|
28171
28940
|
throw new Error('agent-card.json needs string "name", string "description" and array "skills"');
|
|
28172
28941
|
}
|
|
28173
28942
|
return {
|
|
28174
28943
|
name: raw.name,
|
|
28175
28944
|
description: raw.description,
|
|
28176
|
-
version: typeof raw.version === "string" ? raw.version : "1.6.
|
|
28945
|
+
version: typeof raw.version === "string" ? raw.version : "1.6.5",
|
|
28177
28946
|
supportedInterfaces: [{ url: interfaceUrl, protocolBinding: "JSONRPC", protocolVersion: A2A_VERSION }],
|
|
28178
28947
|
capabilities: { streaming: caps.streaming, pushNotifications: true, extendedAgentCard: false },
|
|
28179
28948
|
...caps.tokenAuth === false ? {} : {
|
|
@@ -28200,7 +28969,7 @@ var init_exposure = __esm({
|
|
|
28200
28969
|
init_channel_db();
|
|
28201
28970
|
init_media_store();
|
|
28202
28971
|
init_wire();
|
|
28203
|
-
CARD_FILE =
|
|
28972
|
+
CARD_FILE = path74.join(".halo", "agent-card.json");
|
|
28204
28973
|
IO_MODES = ["text/plain", ...VISION_IMAGE_MIME_TYPES];
|
|
28205
28974
|
baseCache = null;
|
|
28206
28975
|
homeStrategies = {
|
|
@@ -28214,7 +28983,7 @@ var init_exposure = __esm({
|
|
|
28214
28983
|
return { ok: false, status: 404 };
|
|
28215
28984
|
let bound;
|
|
28216
28985
|
try {
|
|
28217
|
-
bound =
|
|
28986
|
+
bound = fs65.realpathSync(auth.account.workspacePath);
|
|
28218
28987
|
} catch {
|
|
28219
28988
|
return { ok: false, status: 404 };
|
|
28220
28989
|
}
|
|
@@ -28656,17 +29425,17 @@ var init_routes = __esm({
|
|
|
28656
29425
|
});
|
|
28657
29426
|
|
|
28658
29427
|
// ../server/dist/a2a/agentcore.js
|
|
28659
|
-
import
|
|
28660
|
-
import
|
|
28661
|
-
import
|
|
29428
|
+
import fs66 from "node:fs";
|
|
29429
|
+
import os10 from "node:os";
|
|
29430
|
+
import path75 from "node:path";
|
|
28662
29431
|
import crypto8 from "node:crypto";
|
|
28663
29432
|
import { Hono as Hono17 } from "hono";
|
|
28664
29433
|
function readLease(file) {
|
|
28665
29434
|
let raw;
|
|
28666
29435
|
let mtimeMs;
|
|
28667
29436
|
try {
|
|
28668
|
-
raw =
|
|
28669
|
-
mtimeMs =
|
|
29437
|
+
raw = fs66.readFileSync(file, "utf8");
|
|
29438
|
+
mtimeMs = fs66.statSync(file).mtimeMs;
|
|
28670
29439
|
} catch (err) {
|
|
28671
29440
|
if (err.code === "ENOENT")
|
|
28672
29441
|
return null;
|
|
@@ -28680,16 +29449,16 @@ function readLease(file) {
|
|
|
28680
29449
|
}
|
|
28681
29450
|
}
|
|
28682
29451
|
function leaseJson(owner, now) {
|
|
28683
|
-
return JSON.stringify({ owner, host:
|
|
29452
|
+
return JSON.stringify({ owner, host: os10.hostname(), pid: process.pid, heartbeatAt: now });
|
|
28684
29453
|
}
|
|
28685
29454
|
function writeLease(file, owner, now) {
|
|
28686
29455
|
const tmp = `${file}.${owner}.tmp`;
|
|
28687
|
-
|
|
28688
|
-
|
|
29456
|
+
fs66.writeFileSync(tmp, leaseJson(owner, now));
|
|
29457
|
+
fs66.renameSync(tmp, file);
|
|
28689
29458
|
}
|
|
28690
29459
|
function tryAcquireLease(file, owner, now = Date.now()) {
|
|
28691
29460
|
try {
|
|
28692
|
-
|
|
29461
|
+
fs66.writeFileSync(file, leaseJson(owner, now), { flag: "wx" });
|
|
28693
29462
|
return "held";
|
|
28694
29463
|
} catch (err) {
|
|
28695
29464
|
if (err.code !== "EEXIST")
|
|
@@ -28717,7 +29486,7 @@ function heartbeatLease(file, owner, now = Date.now()) {
|
|
|
28717
29486
|
function releaseLease(file, owner) {
|
|
28718
29487
|
try {
|
|
28719
29488
|
if (readLease(file)?.body?.owner === owner)
|
|
28720
|
-
|
|
29489
|
+
fs66.unlinkSync(file);
|
|
28721
29490
|
} catch {
|
|
28722
29491
|
}
|
|
28723
29492
|
}
|
|
@@ -28739,7 +29508,7 @@ function upstreamStrategies(workspace, accessLevel) {
|
|
|
28739
29508
|
function seedAgentCard(workspace) {
|
|
28740
29509
|
const card = { name: "Halo", description: "Halo multi-agent workspace on Amazon Bedrock AgentCore. Send a task as plain text.", skills: [] };
|
|
28741
29510
|
try {
|
|
28742
|
-
|
|
29511
|
+
fs66.writeFileSync(path75.join(workspace, CARD_FILE), `${JSON.stringify(card, null, 2)}
|
|
28743
29512
|
`, { flag: "wx" });
|
|
28744
29513
|
return true;
|
|
28745
29514
|
} catch (err) {
|
|
@@ -28787,16 +29556,16 @@ function createAgentCoreA2ARoutes(deps) {
|
|
|
28787
29556
|
return app2;
|
|
28788
29557
|
}
|
|
28789
29558
|
function createAgentCoreA2A(registry2, rawWorkspace, onLost) {
|
|
28790
|
-
const workspace =
|
|
28791
|
-
const haloDir =
|
|
29559
|
+
const workspace = fs66.realpathSync(rawWorkspace);
|
|
29560
|
+
const haloDir = path75.join(workspace, ".halo");
|
|
28792
29561
|
if (!config.a2a.publicUrl)
|
|
28793
29562
|
console.warn("[AgentCoreA2A] HALO_A2A_PUBLIC_URL is not set \u2014 the card advertises the container's own URL, not the AgentCore invoke URL");
|
|
28794
29563
|
const accessLevel = upstreamAccessLevel();
|
|
28795
|
-
const lease = new WorkspaceLease(
|
|
29564
|
+
const lease = new WorkspaceLease(path75.join(haloDir, LEASE_FILE), {
|
|
28796
29565
|
onAcquired() {
|
|
28797
29566
|
setA2ADb(createA2ADb(haloDir));
|
|
28798
29567
|
setRunsDb(createRunsDb(haloDir));
|
|
28799
|
-
|
|
29568
|
+
fs66.rmSync(path75.join(haloDir, RUNTIME_LOCK_FILE), { force: true });
|
|
28800
29569
|
registry2.getOrCreate(workspace);
|
|
28801
29570
|
startPushSender({ maxAgeMs: PUSH_MAX_AGE_MS });
|
|
28802
29571
|
reconcileOpenDispatches();
|
|
@@ -28810,9 +29579,9 @@ function createAgentCoreA2A(registry2, rawWorkspace, onLost) {
|
|
|
28810
29579
|
const activate = () => {
|
|
28811
29580
|
if (active)
|
|
28812
29581
|
return;
|
|
28813
|
-
|
|
29582
|
+
fs66.mkdirSync(haloDir, { recursive: true });
|
|
28814
29583
|
if (seedAgentCard(workspace))
|
|
28815
|
-
console.log(`[AgentCoreA2A] seeded ${
|
|
29584
|
+
console.log(`[AgentCoreA2A] seeded ${path75.join(workspace, CARD_FILE)}`);
|
|
28816
29585
|
active = true;
|
|
28817
29586
|
};
|
|
28818
29587
|
return { app: createAgentCoreA2ARoutes({ registry: registry2, workspace, accessLevel, lease, activate }), lease, workspace };
|
|
@@ -28977,12 +29746,12 @@ var init_dispatcher = __esm({
|
|
|
28977
29746
|
|
|
28978
29747
|
// ../server/dist/cron/runner.js
|
|
28979
29748
|
import { spawn as spawn3, execFileSync } from "node:child_process";
|
|
28980
|
-
import
|
|
28981
|
-
import
|
|
29749
|
+
import fs67 from "node:fs";
|
|
29750
|
+
import path76 from "node:path";
|
|
28982
29751
|
import { homedir as homedir24 } from "node:os";
|
|
28983
29752
|
import { Cron } from "croner";
|
|
28984
29753
|
import Database9 from "better-sqlite3";
|
|
28985
|
-
import { and as and11, eq as eq14, gte as gte3, inArray as inArray2, lt as lt5, or as or2 } from "drizzle-orm";
|
|
29754
|
+
import { and as and11, eq as eq14, gte as gte3, inArray as inArray2, lt as lt5, notInArray, or as or2 } from "drizzle-orm";
|
|
28986
29755
|
function resolveHaloCli() {
|
|
28987
29756
|
if (process.env.HALO_CLI)
|
|
28988
29757
|
return process.env.HALO_CLI;
|
|
@@ -29083,9 +29852,9 @@ function jobChangedFields(job) {
|
|
|
29083
29852
|
function sessionKeyFor(workspacePath, sessionId) {
|
|
29084
29853
|
let ws;
|
|
29085
29854
|
try {
|
|
29086
|
-
ws =
|
|
29855
|
+
ws = fs67.realpathSync(workspacePath);
|
|
29087
29856
|
} catch {
|
|
29088
|
-
ws =
|
|
29857
|
+
ws = path76.resolve(workspacePath);
|
|
29089
29858
|
}
|
|
29090
29859
|
return `${ws}
|
|
29091
29860
|
${sessionId}`;
|
|
@@ -29094,10 +29863,10 @@ function setCronSessionRegistry(r) {
|
|
|
29094
29863
|
_sessionRegistry = r;
|
|
29095
29864
|
}
|
|
29096
29865
|
function logsDir() {
|
|
29097
|
-
return
|
|
29866
|
+
return path76.join(homedir24(), ".halo", "global", "logs", "cron");
|
|
29098
29867
|
}
|
|
29099
29868
|
function logPathFor(runId) {
|
|
29100
|
-
return
|
|
29869
|
+
return path76.join(logsDir(), `${runId}.log`);
|
|
29101
29870
|
}
|
|
29102
29871
|
function newRunId() {
|
|
29103
29872
|
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -29158,7 +29927,7 @@ function sweepOrphanRuns(opts) {
|
|
|
29158
29927
|
}
|
|
29159
29928
|
}
|
|
29160
29929
|
function startCronDaemon() {
|
|
29161
|
-
|
|
29930
|
+
fs67.mkdirSync(logsDir(), { recursive: true });
|
|
29162
29931
|
sweepOrphanRuns();
|
|
29163
29932
|
reloadAll();
|
|
29164
29933
|
_daemonTimers.push(setInterval(() => {
|
|
@@ -29288,6 +30057,7 @@ function reconcileFromDb() {
|
|
|
29288
30057
|
if (dv !== null && _lastCronDataVersion === dv)
|
|
29289
30058
|
return;
|
|
29290
30059
|
_lastCronDataVersion = dv;
|
|
30060
|
+
deleteOrphanRuns().catch((err) => console.warn(`[Cron] deleting runs of deleted jobs failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
29291
30061
|
const db3 = getCronDb();
|
|
29292
30062
|
const rows = db3.select().from(cronJobs).all();
|
|
29293
30063
|
const liveIds = /* @__PURE__ */ new Set();
|
|
@@ -29342,7 +30112,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
29342
30112
|
const runId = newRunId();
|
|
29343
30113
|
const startedAt = Date.now();
|
|
29344
30114
|
const logPath = logPathFor(runId);
|
|
29345
|
-
|
|
30115
|
+
fs67.mkdirSync(logsDir(), { recursive: true });
|
|
29346
30116
|
const sessionId = job.stateless ? perRunSessionId(job, startedAt) : effectiveSessionId(job);
|
|
29347
30117
|
const sessionKey = sessionKeyFor(job.workspacePath, sessionId);
|
|
29348
30118
|
const host = _sessionRegistry?.peek(job.workspacePath);
|
|
@@ -29359,7 +30129,8 @@ async function runJob(jobId, triggerKind) {
|
|
|
29359
30129
|
exitCode: null,
|
|
29360
30130
|
failureReason: skipReason,
|
|
29361
30131
|
logPath: null,
|
|
29362
|
-
dispatchResults: null
|
|
30132
|
+
dispatchResults: null,
|
|
30133
|
+
workspacePath: job.workspacePath
|
|
29363
30134
|
}).run();
|
|
29364
30135
|
broadcast({ type: "cron:run_changed", jobId, runId, status: "skipped" });
|
|
29365
30136
|
return runId;
|
|
@@ -29379,11 +30150,12 @@ async function runJob(jobId, triggerKind) {
|
|
|
29379
30150
|
failureReason: null,
|
|
29380
30151
|
logPath,
|
|
29381
30152
|
dispatchResults: null,
|
|
29382
|
-
sessionId
|
|
30153
|
+
sessionId,
|
|
30154
|
+
workspacePath: job.workspacePath
|
|
29383
30155
|
}).run();
|
|
29384
30156
|
broadcast({ type: "cron:run_changed", jobId, runId, status: "running" });
|
|
29385
30157
|
const targets = parseTargets(job.targets);
|
|
29386
|
-
if (!
|
|
30158
|
+
if (!fs67.existsSync(job.workspacePath)) {
|
|
29387
30159
|
finalize(runId, jobId, {
|
|
29388
30160
|
status: "failed",
|
|
29389
30161
|
output: null,
|
|
@@ -29426,7 +30198,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
29426
30198
|
args.push("--access", accessLevel);
|
|
29427
30199
|
const [spawnBin, spawnArgs] = process.platform === "win32" && cli.endsWith(".cmd") ? ["cmd.exe", ["/c", cli, ...args]] : [cli, args];
|
|
29428
30200
|
const timeoutSec = job.timeoutSec ?? CLI_TIMEOUT_SEC;
|
|
29429
|
-
const teeStream =
|
|
30201
|
+
const teeStream = fs67.createWriteStream(logPath, { flags: "a" });
|
|
29430
30202
|
teeStream.write(`=== ${(/* @__PURE__ */ new Date()).toISOString()} cron run ${runId} job=${jobId} trigger=${triggerKind} ===
|
|
29431
30203
|
`);
|
|
29432
30204
|
teeStream.write(`agent=${job.agentId} session=${sessionId}${job.stateless ? " (stateless)" : ""}${accessLevel ? ` access=${accessLevel}` : ""} workspace=${job.workspacePath} dir=${job.workingDir ?? "."} schedule=${job.schedule}
|
|
@@ -29552,8 +30324,8 @@ function readSessionRow(workspacePath, sessionId) {
|
|
|
29552
30324
|
try {
|
|
29553
30325
|
let entry = _sessionRows.get(workspacePath);
|
|
29554
30326
|
if (!entry) {
|
|
29555
|
-
const file =
|
|
29556
|
-
if (!
|
|
30327
|
+
const file = path76.join(workspacePath, ".halo", "halo.db");
|
|
30328
|
+
if (!fs67.existsSync(file))
|
|
29557
30329
|
return null;
|
|
29558
30330
|
entry = { db: new Database9(file, { readonly: true, fileMustExist: true }), version: -1, rows: /* @__PURE__ */ new Map() };
|
|
29559
30331
|
_sessionRows.set(workspacePath, entry);
|
|
@@ -29624,10 +30396,14 @@ function parseTargets(raw) {
|
|
|
29624
30396
|
async function pruneOldRuns(jobId) {
|
|
29625
30397
|
const db3 = getCronDb();
|
|
29626
30398
|
const job = db3.select({ stateless: cronJobs.stateless }).from(cronJobs).where(eq14(cronJobs.id, jobId)).get();
|
|
29627
|
-
|
|
30399
|
+
if (!job) {
|
|
30400
|
+
await deleteJobRuns(jobId);
|
|
30401
|
+
return;
|
|
30402
|
+
}
|
|
30403
|
+
const keep = job.stateless ? STATELESS_RUNS_PER_JOB_KEEP : RUNS_PER_JOB_KEEP;
|
|
29628
30404
|
const doomed = rawSqlite(db3).prepare(`
|
|
29629
30405
|
SELECT id, job_id AS jobId, session_id AS sessionId, started_at AS startedAt,
|
|
29630
|
-
completed_at AS completedAt, status
|
|
30406
|
+
completed_at AS completedAt, status, workspace_path AS workspacePath
|
|
29631
30407
|
FROM cron_runs
|
|
29632
30408
|
WHERE job_id = ?
|
|
29633
30409
|
AND id NOT IN (
|
|
@@ -29642,31 +30418,44 @@ async function pruneOldRuns(jobId) {
|
|
|
29642
30418
|
async function deleteJobRuns(jobId) {
|
|
29643
30419
|
await deleteRuns(getCronDb().select(DOOMED_RUN_COLUMNS).from(cronRuns).where(eq14(cronRuns.jobId, jobId)).all());
|
|
29644
30420
|
}
|
|
30421
|
+
async function deleteOrphanRuns() {
|
|
30422
|
+
const db3 = getCronDb();
|
|
30423
|
+
await deleteRuns(db3.select(DOOMED_RUN_COLUMNS).from(cronRuns).where(notInArray(cronRuns.jobId, db3.select({ id: cronJobs.id }).from(cronJobs))).all());
|
|
30424
|
+
}
|
|
29645
30425
|
async function deleteRuns(runs) {
|
|
29646
30426
|
if (runs.length === 0)
|
|
29647
30427
|
return;
|
|
29648
30428
|
const db3 = getCronDb();
|
|
29649
30429
|
const workspaceOf = /* @__PURE__ */ new Map();
|
|
29650
|
-
const
|
|
30430
|
+
const openWorkspace = (workspacePath) => {
|
|
30431
|
+
let ws = workspaceOf.get(workspacePath);
|
|
30432
|
+
if (ws === void 0) {
|
|
30433
|
+
ws = fs67.existsSync(path76.join(workspacePath, ".halo", "halo.db")) ? { workspacePath, db: getWorkspaceDb(workspacePath).db } : null;
|
|
30434
|
+
workspaceOf.set(workspacePath, ws);
|
|
30435
|
+
}
|
|
30436
|
+
return ws;
|
|
30437
|
+
};
|
|
30438
|
+
const keptRows = /* @__PURE__ */ new Set();
|
|
29651
30439
|
let deleted = 0;
|
|
29652
30440
|
for (const run of runs) {
|
|
29653
30441
|
if (run.sessionId !== perRunSessionId({ id: run.jobId }, run.startedAt))
|
|
29654
30442
|
continue;
|
|
30443
|
+
if (run.status === "running" || run.completedAt === null) {
|
|
30444
|
+
keptRows.add(run.id);
|
|
30445
|
+
console.log(`[Cron] run ${run.id}: still in progress, kept with its session ${run.sessionId}`);
|
|
30446
|
+
continue;
|
|
30447
|
+
}
|
|
29655
30448
|
try {
|
|
29656
|
-
|
|
29657
|
-
|
|
29658
|
-
const workspacePath = db3.select({ ws: cronJobs.workspacePath }).from(cronJobs).where(eq14(cronJobs.id, run.jobId)).get()?.ws;
|
|
29659
|
-
ws = workspacePath && fs66.existsSync(path75.join(workspacePath, ".halo", "halo.db")) ? { workspacePath, db: getWorkspaceDb(workspacePath).db } : null;
|
|
29660
|
-
workspaceOf.set(run.jobId, ws);
|
|
29661
|
-
}
|
|
30449
|
+
const workspacePath = run.workspacePath ?? db3.select({ ws: cronJobs.workspacePath }).from(cronJobs).where(eq14(cronJobs.id, run.jobId)).get()?.ws;
|
|
30450
|
+
const ws = workspacePath ? openWorkspace(workspacePath) : null;
|
|
29662
30451
|
if (ws && await deleteRunSession(ws, run))
|
|
29663
30452
|
deleted++;
|
|
29664
30453
|
} catch (err) {
|
|
29665
|
-
|
|
30454
|
+
keptRows.add(run.id);
|
|
29666
30455
|
console.warn(`[Cron] run ${run.id}: deleting session ${run.sessionId} failed, run row kept for the next prune: ${err instanceof Error ? err.message : String(err)}`);
|
|
29667
30456
|
}
|
|
29668
30457
|
}
|
|
29669
|
-
const ids = runs.map((r) => r.id).filter((id) => !
|
|
30458
|
+
const ids = runs.map((r) => r.id).filter((id) => !keptRows.has(id));
|
|
29670
30459
|
if (ids.length > 0)
|
|
29671
30460
|
db3.delete(cronRuns).where(inArray2(cronRuns.id, ids)).run();
|
|
29672
30461
|
if (deleted > 0) {
|
|
@@ -29680,8 +30469,6 @@ async function deleteRunSession({ workspacePath, db: db3 }, run) {
|
|
|
29680
30469
|
console.log(`[Cron] run ${run.id}: kept session ${sid} (${why}); pruning the run row only`);
|
|
29681
30470
|
return false;
|
|
29682
30471
|
};
|
|
29683
|
-
if (run.status === "running" || run.completedAt === null)
|
|
29684
|
-
return keep("its run is still in progress");
|
|
29685
30472
|
const host = _sessionRegistry?.peek(workspacePath);
|
|
29686
30473
|
if (host?.hasActiveWorkInTree(sid))
|
|
29687
30474
|
return keep("a turn is running in it");
|
|
@@ -29700,15 +30487,15 @@ async function deleteRunSession({ workspacePath, db: db3 }, run) {
|
|
|
29700
30487
|
}
|
|
29701
30488
|
async function pruneOldLogs() {
|
|
29702
30489
|
const dir = logsDir();
|
|
29703
|
-
if (!
|
|
30490
|
+
if (!fs67.existsSync(dir))
|
|
29704
30491
|
return;
|
|
29705
30492
|
const cutoff = Date.now() - LOG_RETENTION_DAYS * DAY_MS2;
|
|
29706
|
-
for (const name of
|
|
29707
|
-
const p =
|
|
30493
|
+
for (const name of fs67.readdirSync(dir)) {
|
|
30494
|
+
const p = path76.join(dir, name);
|
|
29708
30495
|
try {
|
|
29709
|
-
const stat =
|
|
30496
|
+
const stat = fs67.statSync(p);
|
|
29710
30497
|
if (stat.mtimeMs < cutoff)
|
|
29711
|
-
|
|
30498
|
+
fs67.rmSync(p, { force: true });
|
|
29712
30499
|
} catch {
|
|
29713
30500
|
}
|
|
29714
30501
|
}
|
|
@@ -29751,7 +30538,8 @@ var init_runner = __esm({
|
|
|
29751
30538
|
sessionId: cronRuns.sessionId,
|
|
29752
30539
|
startedAt: cronRuns.startedAt,
|
|
29753
30540
|
completedAt: cronRuns.completedAt,
|
|
29754
|
-
status: cronRuns.status
|
|
30541
|
+
status: cronRuns.status,
|
|
30542
|
+
workspacePath: cronRuns.workspacePath
|
|
29755
30543
|
};
|
|
29756
30544
|
}
|
|
29757
30545
|
});
|
|
@@ -29759,8 +30547,8 @@ var init_runner = __esm({
|
|
|
29759
30547
|
// ../server/dist/routes/cron.js
|
|
29760
30548
|
import { Hono as Hono18 } from "hono";
|
|
29761
30549
|
import { eq as eq15, desc as desc3, lt as lt6, and as and12 } from "drizzle-orm";
|
|
29762
|
-
import
|
|
29763
|
-
import
|
|
30550
|
+
import fs68 from "node:fs/promises";
|
|
30551
|
+
import path77 from "node:path";
|
|
29764
30552
|
import { Cron as Cron2 } from "croner";
|
|
29765
30553
|
function newJobId() {
|
|
29766
30554
|
return `cron-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -29805,7 +30593,7 @@ async function parseWorkingDir(workspacePath, v) {
|
|
|
29805
30593
|
return { workingDir: null };
|
|
29806
30594
|
try {
|
|
29807
30595
|
const abs = await resolveWorkingDir(workspacePath, s);
|
|
29808
|
-
return { workingDir:
|
|
30596
|
+
return { workingDir: path77.relative(path77.resolve(workspacePath), abs) || null };
|
|
29809
30597
|
} catch (err) {
|
|
29810
30598
|
return { error: `workingDir: ${err instanceof Error ? err.message : String(err)}` };
|
|
29811
30599
|
}
|
|
@@ -30110,7 +30898,7 @@ function createCronRoutes() {
|
|
|
30110
30898
|
return c.json({ log: null });
|
|
30111
30899
|
let log2 = null;
|
|
30112
30900
|
try {
|
|
30113
|
-
log2 = await
|
|
30901
|
+
log2 = await fs68.readFile(row.logPath, "utf-8");
|
|
30114
30902
|
} catch {
|
|
30115
30903
|
}
|
|
30116
30904
|
return c.json({ log: log2 });
|
|
@@ -30176,8 +30964,8 @@ var init_cron = __esm({
|
|
|
30176
30964
|
});
|
|
30177
30965
|
|
|
30178
30966
|
// ../server/dist/extensions/install.js
|
|
30179
|
-
import
|
|
30180
|
-
import
|
|
30967
|
+
import fs69 from "node:fs";
|
|
30968
|
+
import path78 from "node:path";
|
|
30181
30969
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
30182
30970
|
import JSZip2 from "jszip";
|
|
30183
30971
|
function safeEntryPath(name) {
|
|
@@ -30208,43 +30996,43 @@ async function unpackZip(zipBuf, dest) {
|
|
|
30208
30996
|
const perm = entry.unixPermissions;
|
|
30209
30997
|
if (typeof perm === "number" && (perm & S_IFMT) === S_IFLNK)
|
|
30210
30998
|
throw new ExtensionInstallError(`zip entry is a symlink: ${entry.name}`);
|
|
30211
|
-
const abs =
|
|
30999
|
+
const abs = path78.join(dest, rel);
|
|
30212
31000
|
if (entry.dir) {
|
|
30213
|
-
|
|
31001
|
+
fs69.mkdirSync(abs, { recursive: true });
|
|
30214
31002
|
continue;
|
|
30215
31003
|
}
|
|
30216
|
-
|
|
30217
|
-
|
|
31004
|
+
fs69.mkdirSync(path78.dirname(abs), { recursive: true });
|
|
31005
|
+
fs69.writeFileSync(abs, await entry.async("nodebuffer"));
|
|
30218
31006
|
}
|
|
30219
31007
|
}
|
|
30220
31008
|
function stripWrapperDir(dir) {
|
|
30221
|
-
if (
|
|
31009
|
+
if (fs69.existsSync(path78.join(dir, MANIFEST_FILE)))
|
|
30222
31010
|
return;
|
|
30223
|
-
const children =
|
|
31011
|
+
const children = fs69.readdirSync(dir, { withFileTypes: true });
|
|
30224
31012
|
if (children.length !== 1 || !children[0].isDirectory())
|
|
30225
31013
|
return;
|
|
30226
|
-
const inner =
|
|
30227
|
-
if (!
|
|
31014
|
+
const inner = path78.join(dir, children[0].name);
|
|
31015
|
+
if (!fs69.existsSync(path78.join(inner, MANIFEST_FILE)))
|
|
30228
31016
|
return;
|
|
30229
|
-
for (const name of
|
|
30230
|
-
|
|
31017
|
+
for (const name of fs69.readdirSync(inner)) {
|
|
31018
|
+
fs69.renameSync(path78.join(inner, name), path78.join(dir, name));
|
|
30231
31019
|
}
|
|
30232
|
-
|
|
31020
|
+
fs69.rmdirSync(inner);
|
|
30233
31021
|
}
|
|
30234
31022
|
function rand() {
|
|
30235
31023
|
return randomBytes3(3).toString("hex");
|
|
30236
31024
|
}
|
|
30237
31025
|
async function installExtensionZip(zipBuf, expectId) {
|
|
30238
31026
|
const root = extensionsRoot();
|
|
30239
|
-
|
|
30240
|
-
const stage =
|
|
30241
|
-
|
|
31027
|
+
fs69.mkdirSync(root, { recursive: true });
|
|
31028
|
+
const stage = path78.join(root, `.tmp-unpack-${rand()}`);
|
|
31029
|
+
fs69.mkdirSync(stage);
|
|
30242
31030
|
try {
|
|
30243
31031
|
await unpackZip(zipBuf, stage);
|
|
30244
31032
|
stripWrapperDir(stage);
|
|
30245
31033
|
let manifestId;
|
|
30246
31034
|
try {
|
|
30247
|
-
const raw = JSON.parse(
|
|
31035
|
+
const raw = JSON.parse(fs69.readFileSync(path78.join(stage, MANIFEST_FILE), "utf-8"));
|
|
30248
31036
|
manifestId = typeof raw.id === "string" ? raw.id : "";
|
|
30249
31037
|
} catch (err) {
|
|
30250
31038
|
throw new ExtensionInstallError(`${MANIFEST_FILE} missing or unreadable at the zip root: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -30256,36 +31044,36 @@ async function installExtensionZip(zipBuf, expectId) {
|
|
|
30256
31044
|
const verdict2 = readExtensionDir(stage, manifestId);
|
|
30257
31045
|
if ("error" in verdict2)
|
|
30258
31046
|
throw new ExtensionInstallError(`invalid extension: ${verdict2.error}`);
|
|
30259
|
-
const target =
|
|
30260
|
-
const old =
|
|
30261
|
-
const hadOld =
|
|
31047
|
+
const target = path78.join(root, manifestId);
|
|
31048
|
+
const old = path78.join(root, `.old-${manifestId}-${rand()}`);
|
|
31049
|
+
const hadOld = fs69.existsSync(target);
|
|
30262
31050
|
if (hadOld)
|
|
30263
|
-
|
|
31051
|
+
fs69.renameSync(target, old);
|
|
30264
31052
|
try {
|
|
30265
|
-
|
|
31053
|
+
fs69.renameSync(stage, target);
|
|
30266
31054
|
} catch (err) {
|
|
30267
31055
|
if (hadOld)
|
|
30268
|
-
|
|
31056
|
+
fs69.renameSync(old, target);
|
|
30269
31057
|
throw err;
|
|
30270
31058
|
}
|
|
30271
31059
|
if (hadOld)
|
|
30272
|
-
|
|
31060
|
+
fs69.rmSync(old, { recursive: true, force: true });
|
|
30273
31061
|
const info = readExtensionDir(target);
|
|
30274
31062
|
if ("error" in info)
|
|
30275
31063
|
throw new ExtensionInstallError(`installed but failed re-validation: ${info.error}`);
|
|
30276
31064
|
console.log(`[Extensions] installed ${info.id}@${info.version}${hadOld ? " (replaced previous)" : ""}`);
|
|
30277
31065
|
return info;
|
|
30278
31066
|
} finally {
|
|
30279
|
-
|
|
31067
|
+
fs69.rmSync(stage, { recursive: true, force: true });
|
|
30280
31068
|
}
|
|
30281
31069
|
}
|
|
30282
31070
|
function uninstallExtension(id) {
|
|
30283
31071
|
if (!isExtensionId(id))
|
|
30284
31072
|
throw new ExtensionInstallError(`"${id}" is not a valid extension id`);
|
|
30285
|
-
const dir =
|
|
30286
|
-
if (!
|
|
31073
|
+
const dir = path78.join(extensionsRoot(), id);
|
|
31074
|
+
if (!fs69.existsSync(dir))
|
|
30287
31075
|
return false;
|
|
30288
|
-
|
|
31076
|
+
fs69.rmSync(dir, { recursive: true, force: true });
|
|
30289
31077
|
console.log(`[Extensions] removed ${id}`);
|
|
30290
31078
|
return true;
|
|
30291
31079
|
}
|
|
@@ -30302,7 +31090,7 @@ var init_install2 = __esm({
|
|
|
30302
31090
|
});
|
|
30303
31091
|
|
|
30304
31092
|
// ../server/dist/extensions/watcher.js
|
|
30305
|
-
import
|
|
31093
|
+
import fs70 from "node:fs";
|
|
30306
31094
|
function scheduleFlush() {
|
|
30307
31095
|
if (flushTimer)
|
|
30308
31096
|
return;
|
|
@@ -30313,15 +31101,23 @@ function scheduleFlush() {
|
|
|
30313
31101
|
}
|
|
30314
31102
|
function rescanAndBroadcast() {
|
|
30315
31103
|
const snap = scanExtensions();
|
|
31104
|
+
reconcileSttModels(snap);
|
|
30316
31105
|
const key = snapshotKey(snap);
|
|
30317
31106
|
if (key === lastKey)
|
|
30318
31107
|
return;
|
|
30319
31108
|
lastKey = key;
|
|
30320
31109
|
broadcast({ type: "extension:changed", ...snap });
|
|
30321
31110
|
}
|
|
31111
|
+
function reconcileSttModels(snap) {
|
|
31112
|
+
if (!snap.extensions.some((e) => e.capabilities.includes("stt-local")))
|
|
31113
|
+
return;
|
|
31114
|
+
const { state } = sttModels.status();
|
|
31115
|
+
if (state !== "ready" && state !== "downloading")
|
|
31116
|
+
void sttModels.ensure();
|
|
31117
|
+
}
|
|
30322
31118
|
function openWatcher(root) {
|
|
30323
31119
|
try {
|
|
30324
|
-
watcher =
|
|
31120
|
+
watcher = fs70.watch(root, { persistent: false, recursive: false }, () => scheduleFlush());
|
|
30325
31121
|
watcher.on("error", (err) => {
|
|
30326
31122
|
console.warn(`[Extensions] watch error on ${root}: ${err.message} \u2014 retrying in 1s`);
|
|
30327
31123
|
closeWatcher();
|
|
@@ -30347,11 +31143,13 @@ function start() {
|
|
|
30347
31143
|
return;
|
|
30348
31144
|
const root = extensionsRoot();
|
|
30349
31145
|
try {
|
|
30350
|
-
|
|
31146
|
+
fs70.mkdirSync(root, { recursive: true });
|
|
30351
31147
|
} catch (err) {
|
|
30352
31148
|
console.warn(`[Extensions] cannot create ${root}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30353
31149
|
}
|
|
30354
|
-
|
|
31150
|
+
const snap = scanExtensions();
|
|
31151
|
+
lastKey = snapshotKey(snap);
|
|
31152
|
+
reconcileSttModels(snap);
|
|
30355
31153
|
openWatcher(root);
|
|
30356
31154
|
const { extensions, errors } = getSnapshot();
|
|
30357
31155
|
if (extensions.length > 0 || errors.length > 0) {
|
|
@@ -30372,6 +31170,7 @@ var init_watcher = __esm({
|
|
|
30372
31170
|
"../server/dist/extensions/watcher.js"() {
|
|
30373
31171
|
"use strict";
|
|
30374
31172
|
init_broadcast();
|
|
31173
|
+
init_models();
|
|
30375
31174
|
init_registry2();
|
|
30376
31175
|
watcher = null;
|
|
30377
31176
|
flushTimer = null;
|
|
@@ -30382,8 +31181,8 @@ var init_watcher = __esm({
|
|
|
30382
31181
|
|
|
30383
31182
|
// ../server/dist/routes/extensions.js
|
|
30384
31183
|
import { Hono as Hono19 } from "hono";
|
|
30385
|
-
import
|
|
30386
|
-
import
|
|
31184
|
+
import fs71 from "node:fs";
|
|
31185
|
+
import path79 from "node:path";
|
|
30387
31186
|
import { Readable as Readable2 } from "node:stream";
|
|
30388
31187
|
function assetMime(fileName) {
|
|
30389
31188
|
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -30401,15 +31200,15 @@ function resolveAsset(id, version, rest) {
|
|
|
30401
31200
|
let dir;
|
|
30402
31201
|
let real;
|
|
30403
31202
|
try {
|
|
30404
|
-
dir =
|
|
30405
|
-
real =
|
|
31203
|
+
dir = fs71.realpathSync(path79.join(extensionsRoot(), id));
|
|
31204
|
+
real = fs71.realpathSync(path79.join(dir, ...segments));
|
|
30406
31205
|
} catch {
|
|
30407
31206
|
return null;
|
|
30408
31207
|
}
|
|
30409
|
-
if (real !== dir && !real.startsWith(dir +
|
|
31208
|
+
if (real !== dir && !real.startsWith(dir + path79.sep))
|
|
30410
31209
|
return null;
|
|
30411
31210
|
try {
|
|
30412
|
-
if (!
|
|
31211
|
+
if (!fs71.statSync(real).isFile())
|
|
30413
31212
|
return null;
|
|
30414
31213
|
} catch {
|
|
30415
31214
|
return null;
|
|
@@ -30471,8 +31270,8 @@ function createExtensionRoutes() {
|
|
|
30471
31270
|
const abs = resolveAsset(c.req.param("id"), c.req.param("version"), c.req.param("rest"));
|
|
30472
31271
|
if (!abs)
|
|
30473
31272
|
return c.json({ error: "Not found" }, 404);
|
|
30474
|
-
const stat =
|
|
30475
|
-
const nodeStream =
|
|
31273
|
+
const stat = fs71.statSync(abs);
|
|
31274
|
+
const nodeStream = fs71.createReadStream(abs);
|
|
30476
31275
|
c.req.raw.signal?.addEventListener("abort", () => nodeStream.destroy(), { once: true });
|
|
30477
31276
|
return new Response(Readable2.toWeb(nodeStream), {
|
|
30478
31277
|
headers: {
|
|
@@ -30526,7 +31325,7 @@ var init_extensions = __esm({
|
|
|
30526
31325
|
});
|
|
30527
31326
|
|
|
30528
31327
|
// ../server/dist/models/watcher.js
|
|
30529
|
-
import
|
|
31328
|
+
import fs72 from "node:fs";
|
|
30530
31329
|
function snapshot2() {
|
|
30531
31330
|
const { effective } = loadProviders();
|
|
30532
31331
|
return {
|
|
@@ -30554,7 +31353,7 @@ function reloadAndBroadcast() {
|
|
|
30554
31353
|
}
|
|
30555
31354
|
function openWatcher2(root) {
|
|
30556
31355
|
try {
|
|
30557
|
-
watcher2 =
|
|
31356
|
+
watcher2 = fs72.watch(root, { persistent: false, recursive: false }, () => scheduleFlush2());
|
|
30558
31357
|
watcher2.on("error", (err) => {
|
|
30559
31358
|
console.warn(`[Models] watch error on ${root}: ${err.message} \u2014 retrying in 1s`);
|
|
30560
31359
|
closeWatcher2();
|
|
@@ -30580,7 +31379,7 @@ function start2() {
|
|
|
30580
31379
|
return;
|
|
30581
31380
|
const root = globalHubModelsDir();
|
|
30582
31381
|
try {
|
|
30583
|
-
|
|
31382
|
+
fs72.mkdirSync(root, { recursive: true });
|
|
30584
31383
|
} catch (err) {
|
|
30585
31384
|
console.warn(`[Models] cannot create ${root}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30586
31385
|
}
|
|
@@ -30857,7 +31656,7 @@ var init_ticker = __esm({
|
|
|
30857
31656
|
});
|
|
30858
31657
|
|
|
30859
31658
|
// ../server/dist/evolution/spawn.js
|
|
30860
|
-
import
|
|
31659
|
+
import path80 from "node:path";
|
|
30861
31660
|
import { spawn as spawn4 } from "node:child_process";
|
|
30862
31661
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
30863
31662
|
var __dirname2, WRAPPER_PATH, realEvoSpawner;
|
|
@@ -30865,8 +31664,8 @@ var init_spawn = __esm({
|
|
|
30865
31664
|
"../server/dist/evolution/spawn.js"() {
|
|
30866
31665
|
"use strict";
|
|
30867
31666
|
init_child_env();
|
|
30868
|
-
__dirname2 =
|
|
30869
|
-
WRAPPER_PATH =
|
|
31667
|
+
__dirname2 = path80.dirname(fileURLToPath2(import.meta.url));
|
|
31668
|
+
WRAPPER_PATH = path80.resolve(__dirname2, "evo-wrapper.js");
|
|
30870
31669
|
realEvoSpawner = (mode, id) => {
|
|
30871
31670
|
const child = spawn4(process.execPath, [WRAPPER_PATH, `--mode=${mode}`, `--id=${id}`], {
|
|
30872
31671
|
detached: true,
|
|
@@ -31697,12 +32496,12 @@ var init_handler3 = __esm({
|
|
|
31697
32496
|
});
|
|
31698
32497
|
|
|
31699
32498
|
// ../server/dist/routes/channel-accounts.js
|
|
31700
|
-
import
|
|
32499
|
+
import fs73 from "node:fs";
|
|
31701
32500
|
function accountListFields(a) {
|
|
31702
32501
|
return {
|
|
31703
32502
|
accountId: a.accountId,
|
|
31704
32503
|
workspacePath: a.workspacePath,
|
|
31705
|
-
workspaceMissing: !
|
|
32504
|
+
workspaceMissing: !fs73.existsSync(a.workspacePath),
|
|
31706
32505
|
label: a.label,
|
|
31707
32506
|
enabled: a.enabled,
|
|
31708
32507
|
accessLevel: a.accessLevel,
|
|
@@ -32238,7 +33037,7 @@ var init_event_adapter2 = __esm({
|
|
|
32238
33037
|
});
|
|
32239
33038
|
|
|
32240
33039
|
// ../server/dist/channels/wechat/cdn.js
|
|
32241
|
-
import { createDecipheriv, createCipheriv, randomBytes as randomBytes4, createHash as
|
|
33040
|
+
import { createDecipheriv, createCipheriv, randomBytes as randomBytes4, createHash as createHash3 } from "node:crypto";
|
|
32242
33041
|
function decryptAesEcb(ciphertext, key) {
|
|
32243
33042
|
const decipher = createDecipheriv("aes-128-ecb", key, null);
|
|
32244
33043
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
@@ -32292,7 +33091,7 @@ function prepareUpload(plaintext) {
|
|
|
32292
33091
|
aeskey,
|
|
32293
33092
|
aeskeyHex: aeskey.toString("hex"),
|
|
32294
33093
|
rawsize: plaintext.length,
|
|
32295
|
-
rawfilemd5:
|
|
33094
|
+
rawfilemd5: createHash3("md5").update(plaintext).digest("hex"),
|
|
32296
33095
|
filesize: aesEcbPaddedSize(plaintext.length)
|
|
32297
33096
|
};
|
|
32298
33097
|
}
|
|
@@ -32344,8 +33143,8 @@ var init_cdn = __esm({
|
|
|
32344
33143
|
});
|
|
32345
33144
|
|
|
32346
33145
|
// ../server/dist/channels/wechat/send-media.js
|
|
32347
|
-
import
|
|
32348
|
-
import
|
|
33146
|
+
import fs74 from "node:fs/promises";
|
|
33147
|
+
import path81 from "node:path";
|
|
32349
33148
|
function inferKind(filePath) {
|
|
32350
33149
|
const cls = classifyMedia(filePath);
|
|
32351
33150
|
if (cls === "image")
|
|
@@ -32357,7 +33156,7 @@ function inferKind(filePath) {
|
|
|
32357
33156
|
async function sendMediaFile(params) {
|
|
32358
33157
|
const { baseUrl, token, toUserId, contextToken, contextTokenAt, filePath } = params;
|
|
32359
33158
|
const kind = params.kind ?? inferKind(filePath);
|
|
32360
|
-
const plaintext = await
|
|
33159
|
+
const plaintext = await fs74.readFile(filePath);
|
|
32361
33160
|
const prep = prepareUpload(plaintext);
|
|
32362
33161
|
console.log(`[WeChat] sendMediaFile: path=${filePath} kind=${kind} rawsize=${prep.rawsize} filesize=${prep.filesize}`);
|
|
32363
33162
|
const mediaType = kind === "image" ? UploadMediaType.IMAGE : kind === "video" ? UploadMediaType.VIDEO : UploadMediaType.FILE;
|
|
@@ -32395,7 +33194,7 @@ async function sendMediaFile(params) {
|
|
|
32395
33194
|
aesKeyBase64,
|
|
32396
33195
|
fileSizeCiphertext: prep.filesize,
|
|
32397
33196
|
fileSize: prep.rawsize,
|
|
32398
|
-
fileName:
|
|
33197
|
+
fileName: path81.basename(filePath)
|
|
32399
33198
|
});
|
|
32400
33199
|
const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
32401
33200
|
const body = {
|
|
@@ -32558,8 +33357,8 @@ var init_login = __esm({
|
|
|
32558
33357
|
});
|
|
32559
33358
|
|
|
32560
33359
|
// ../server/dist/channels/wechat/handler.js
|
|
32561
|
-
import
|
|
32562
|
-
import
|
|
33360
|
+
import fs75 from "node:fs";
|
|
33361
|
+
import path82 from "node:path";
|
|
32563
33362
|
import QRCode from "qrcode";
|
|
32564
33363
|
function wxRoute(fromUserId, contextToken, contextTokenAt) {
|
|
32565
33364
|
return (prev) => {
|
|
@@ -32980,7 +33779,7 @@ async function handleSlashCommand(args) {
|
|
|
32980
33779
|
await reply(login.message);
|
|
32981
33780
|
return true;
|
|
32982
33781
|
}
|
|
32983
|
-
const tmpPath =
|
|
33782
|
+
const tmpPath = path82.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
|
|
32984
33783
|
await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
|
|
32985
33784
|
await sendMediaFile({
|
|
32986
33785
|
baseUrl: account.baseUrl,
|
|
@@ -32989,7 +33788,7 @@ async function handleSlashCommand(args) {
|
|
|
32989
33788
|
contextToken,
|
|
32990
33789
|
filePath: tmpPath
|
|
32991
33790
|
});
|
|
32992
|
-
await
|
|
33791
|
+
await fs75.promises.unlink(tmpPath).catch(() => {
|
|
32993
33792
|
});
|
|
32994
33793
|
await reply(t("wechat.qr_sent", lang, { level, path: account.workspacePath }));
|
|
32995
33794
|
waitLogin({ sessionKey: login.sessionKey }).then(async (result2) => {
|
|
@@ -33069,8 +33868,8 @@ var init_handler4 = __esm({
|
|
|
33069
33868
|
|
|
33070
33869
|
// ../server/dist/routes/wechat.js
|
|
33071
33870
|
import { Hono as Hono21 } from "hono";
|
|
33072
|
-
import
|
|
33073
|
-
import
|
|
33871
|
+
import path83 from "node:path";
|
|
33872
|
+
import fs76 from "node:fs";
|
|
33074
33873
|
function createWechatRoutes(deps) {
|
|
33075
33874
|
const { db: db3, channel } = deps;
|
|
33076
33875
|
const app2 = new Hono21();
|
|
@@ -33079,7 +33878,7 @@ function createWechatRoutes(deps) {
|
|
|
33079
33878
|
return {
|
|
33080
33879
|
...stripSecrets(acc),
|
|
33081
33880
|
workspacePath: acc.workspacePath,
|
|
33082
|
-
workspaceMissing: !
|
|
33881
|
+
workspaceMissing: !fs76.existsSync(acc.workspacePath)
|
|
33083
33882
|
};
|
|
33084
33883
|
});
|
|
33085
33884
|
return c.json({ accounts });
|
|
@@ -33095,7 +33894,7 @@ function createWechatRoutes(deps) {
|
|
|
33095
33894
|
return c.json({ error: "sessionKey required" }, 400);
|
|
33096
33895
|
if (!body.workspacePath)
|
|
33097
33896
|
return c.json({ error: "workspacePath required" }, 400);
|
|
33098
|
-
if (!
|
|
33897
|
+
if (!path83.isAbsolute(body.workspacePath))
|
|
33099
33898
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
33100
33899
|
const levelError = accessLevelError(body.accessLevel, CHAT_ACCESS_LEVELS);
|
|
33101
33900
|
if (levelError)
|
|
@@ -33308,7 +34107,7 @@ var init_descriptor2 = __esm({
|
|
|
33308
34107
|
import { Hono as Hono22 } from "hono";
|
|
33309
34108
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
33310
34109
|
import crypto10 from "node:crypto";
|
|
33311
|
-
import
|
|
34110
|
+
import fs77 from "node:fs";
|
|
33312
34111
|
function streamWithKeepalive(c, chunks) {
|
|
33313
34112
|
return streamSSE2(c, async (stream) => {
|
|
33314
34113
|
const timer2 = setInterval(() => {
|
|
@@ -33499,30 +34298,30 @@ function createWebRoutes(deps) {
|
|
|
33499
34298
|
const filePath = c.req.query("path");
|
|
33500
34299
|
if (!filePath)
|
|
33501
34300
|
return c.json({ error: "path required" }, 400);
|
|
33502
|
-
const
|
|
33503
|
-
const resolved =
|
|
33504
|
-
const root =
|
|
33505
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
34301
|
+
const path90 = await import("node:path");
|
|
34302
|
+
const resolved = path90.default.resolve(account.workspacePath, filePath);
|
|
34303
|
+
const root = path90.default.resolve(account.workspacePath);
|
|
34304
|
+
if (resolved !== root && !resolved.startsWith(root + path90.default.sep)) {
|
|
33506
34305
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
33507
34306
|
}
|
|
33508
34307
|
let real;
|
|
33509
34308
|
let realRoot;
|
|
33510
34309
|
try {
|
|
33511
|
-
real =
|
|
33512
|
-
realRoot =
|
|
34310
|
+
real = fs77.realpathSync(resolved);
|
|
34311
|
+
realRoot = fs77.realpathSync(root);
|
|
33513
34312
|
} catch {
|
|
33514
34313
|
return c.json({ error: "file not found" }, 404);
|
|
33515
34314
|
}
|
|
33516
|
-
if (real !== realRoot && !real.startsWith(realRoot +
|
|
34315
|
+
if (real !== realRoot && !real.startsWith(realRoot + path90.default.sep)) {
|
|
33517
34316
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
33518
34317
|
}
|
|
33519
34318
|
if (isHiddenWorkspacePath(real, realRoot)) {
|
|
33520
34319
|
return c.json({ error: "path is not accessible" }, 403);
|
|
33521
34320
|
}
|
|
33522
|
-
const stat =
|
|
34321
|
+
const stat = fs77.statSync(real);
|
|
33523
34322
|
if (stat.isDirectory())
|
|
33524
34323
|
return c.json({ error: "cannot serve directory" }, 400);
|
|
33525
|
-
const ext =
|
|
34324
|
+
const ext = path90.default.extname(real).toLowerCase();
|
|
33526
34325
|
const nonImageMimes = {
|
|
33527
34326
|
".mp4": "video/mp4",
|
|
33528
34327
|
".webm": "video/webm",
|
|
@@ -33530,8 +34329,8 @@ function createWebRoutes(deps) {
|
|
|
33530
34329
|
".txt": "text/plain"
|
|
33531
34330
|
};
|
|
33532
34331
|
const contentType = imageMimeFromExt(ext) || nonImageMimes[ext] || "application/octet-stream";
|
|
33533
|
-
const fileName =
|
|
33534
|
-
const data =
|
|
34332
|
+
const fileName = path90.default.basename(resolved);
|
|
34333
|
+
const data = fs77.readFileSync(real);
|
|
33535
34334
|
c.header("content-type", contentType);
|
|
33536
34335
|
c.header("content-disposition", `inline; filename="${fileName}"`);
|
|
33537
34336
|
return c.body(data);
|
|
@@ -33753,10 +34552,10 @@ async function authTest(botToken) {
|
|
|
33753
34552
|
return slackPost("auth.test", botToken, {});
|
|
33754
34553
|
}
|
|
33755
34554
|
async function uploadFile(args) {
|
|
33756
|
-
const
|
|
33757
|
-
const
|
|
33758
|
-
const filename = args.filename ??
|
|
33759
|
-
const stat =
|
|
34555
|
+
const fs81 = await import("node:fs");
|
|
34556
|
+
const path90 = await import("node:path");
|
|
34557
|
+
const filename = args.filename ?? path90.basename(args.filePath);
|
|
34558
|
+
const stat = fs81.statSync(args.filePath);
|
|
33760
34559
|
if (!stat.isFile())
|
|
33761
34560
|
throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
|
|
33762
34561
|
const length = stat.size;
|
|
@@ -33774,7 +34573,7 @@ async function uploadFile(args) {
|
|
|
33774
34573
|
if (step1Parsed.ok === false || !step1Parsed.upload_url || !step1Parsed.file_id) {
|
|
33775
34574
|
throw new Error(`[slack:files.getUploadURLExternal] ${step1Parsed.error ?? "unknown"}`);
|
|
33776
34575
|
}
|
|
33777
|
-
const buf =
|
|
34576
|
+
const buf = fs81.readFileSync(args.filePath);
|
|
33778
34577
|
const step2 = await fetch(step1Parsed.upload_url, {
|
|
33779
34578
|
method: "POST",
|
|
33780
34579
|
body: buf
|
|
@@ -33919,7 +34718,7 @@ var init_api2 = __esm({
|
|
|
33919
34718
|
});
|
|
33920
34719
|
|
|
33921
34720
|
// ../server/dist/channels/slack/handler.js
|
|
33922
|
-
import
|
|
34721
|
+
import path84 from "node:path";
|
|
33923
34722
|
import { WebSocket as WebSocket2 } from "ws";
|
|
33924
34723
|
function slashToBang(text6, commandNames) {
|
|
33925
34724
|
if (commandNames.length === 0)
|
|
@@ -34050,7 +34849,7 @@ function startSlackChannel(deps) {
|
|
|
34050
34849
|
const route = bridge.getRoute(sessionId);
|
|
34051
34850
|
if (!account || !route)
|
|
34052
34851
|
return;
|
|
34053
|
-
const resolved =
|
|
34852
|
+
const resolved = path84.resolve(filePath);
|
|
34054
34853
|
await sendMediaOrReport({
|
|
34055
34854
|
filePath,
|
|
34056
34855
|
account,
|
|
@@ -34692,10 +35491,10 @@ async function replyMessage(args) {
|
|
|
34692
35491
|
return { message_id: parsed.data?.message_id ?? "" };
|
|
34693
35492
|
}
|
|
34694
35493
|
async function uploadImage(args) {
|
|
34695
|
-
const
|
|
34696
|
-
const
|
|
34697
|
-
const buf =
|
|
34698
|
-
const filename =
|
|
35494
|
+
const fs81 = await import("node:fs");
|
|
35495
|
+
const path90 = await import("node:path");
|
|
35496
|
+
const buf = fs81.readFileSync(args.filePath);
|
|
35497
|
+
const filename = path90.basename(args.filePath);
|
|
34699
35498
|
const send2 = async (token) => {
|
|
34700
35499
|
const form = new FormData();
|
|
34701
35500
|
form.append("image_type", "message");
|
|
@@ -34711,10 +35510,10 @@ async function uploadImage(args) {
|
|
|
34711
35510
|
return { imageKey: parsed.data.image_key };
|
|
34712
35511
|
}
|
|
34713
35512
|
async function uploadFile2(args) {
|
|
34714
|
-
const
|
|
34715
|
-
const
|
|
34716
|
-
const buf =
|
|
34717
|
-
const filename = args.filename ??
|
|
35513
|
+
const fs81 = await import("node:fs");
|
|
35514
|
+
const path90 = await import("node:path");
|
|
35515
|
+
const buf = fs81.readFileSync(args.filePath);
|
|
35516
|
+
const filename = args.filename ?? path90.basename(args.filePath);
|
|
34718
35517
|
const send2 = async (token) => {
|
|
34719
35518
|
const form = new FormData();
|
|
34720
35519
|
form.append("file_type", args.fileType);
|
|
@@ -34781,7 +35580,7 @@ var init_api3 = __esm({
|
|
|
34781
35580
|
});
|
|
34782
35581
|
|
|
34783
35582
|
// ../server/dist/channels/feishu/handler.js
|
|
34784
|
-
import
|
|
35583
|
+
import path85 from "node:path";
|
|
34785
35584
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
34786
35585
|
function buildSessionPrefixForThread2(chatId, rootId) {
|
|
34787
35586
|
return sessionPrefix("feishu", `${chatId}:${rootId}`);
|
|
@@ -34984,7 +35783,7 @@ function startFeishuChannel(deps) {
|
|
|
34984
35783
|
const route = bridge.getRoute(sessionId);
|
|
34985
35784
|
if (!account || !route)
|
|
34986
35785
|
return;
|
|
34987
|
-
const resolved =
|
|
35786
|
+
const resolved = path85.resolve(filePath);
|
|
34988
35787
|
await sendMediaOrReport({
|
|
34989
35788
|
filePath,
|
|
34990
35789
|
account,
|
|
@@ -35151,7 +35950,7 @@ async function replyToInbound(args) {
|
|
|
35151
35950
|
async function sendFeishuMedia(args) {
|
|
35152
35951
|
const { account, inboundMessageId, isP2P, chatId, filePath } = args;
|
|
35153
35952
|
const cls = classifyMedia(filePath);
|
|
35154
|
-
const ext =
|
|
35953
|
+
const ext = path85.extname(filePath).toLowerCase();
|
|
35155
35954
|
if (cls === "image") {
|
|
35156
35955
|
const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
|
|
35157
35956
|
await replyToInboundRaw({
|
|
@@ -35540,8 +36339,8 @@ var init_event_adapter5 = __esm({
|
|
|
35540
36339
|
});
|
|
35541
36340
|
|
|
35542
36341
|
// ../server/dist/channels/wecom/handler.js
|
|
35543
|
-
import
|
|
35544
|
-
import
|
|
36342
|
+
import path86 from "node:path";
|
|
36343
|
+
import fs78 from "node:fs/promises";
|
|
35545
36344
|
import { WSClient as WSClient2, generateReqId } from "@wecom/aibot-node-sdk";
|
|
35546
36345
|
function pickConversation(body) {
|
|
35547
36346
|
const userid = body.from?.userid ?? "unknown";
|
|
@@ -35664,13 +36463,13 @@ async function replyText(wsClient, route, text6) {
|
|
|
35664
36463
|
}
|
|
35665
36464
|
async function sendWecomMedia(args) {
|
|
35666
36465
|
const { wsClient, route, filePath } = args;
|
|
35667
|
-
const ext =
|
|
36466
|
+
const ext = path86.extname(filePath).toLowerCase();
|
|
35668
36467
|
const cls = classifyMedia(filePath);
|
|
35669
36468
|
const type = cls === "image" && [".png", ".jpg", ".jpeg", ".gif"].includes(ext) ? "image" : cls === "video" && ext === ".mp4" ? "video" : "file";
|
|
35670
|
-
const buf = await
|
|
36469
|
+
const buf = await fs78.readFile(filePath);
|
|
35671
36470
|
if (buf.length > MAX_WECOM_DOWNLOAD_BYTES)
|
|
35672
36471
|
throw new Error("file exceeds 20MB");
|
|
35673
|
-
const { media_id: mediaId } = await wsClient.uploadMedia(buf, { type, filename:
|
|
36472
|
+
const { media_id: mediaId } = await wsClient.uploadMedia(buf, { type, filename: path86.basename(filePath) });
|
|
35674
36473
|
if (route.reqId)
|
|
35675
36474
|
await wsClient.replyMedia({ headers: { req_id: route.reqId } }, type, mediaId);
|
|
35676
36475
|
else
|
|
@@ -35715,7 +36514,7 @@ function startWecomChannel(deps) {
|
|
|
35715
36514
|
const wsClient = states.get(accountId)?.wsClient;
|
|
35716
36515
|
if (!account || !route || !wsClient)
|
|
35717
36516
|
return;
|
|
35718
|
-
const resolved =
|
|
36517
|
+
const resolved = path86.resolve(filePath);
|
|
35719
36518
|
await sendMediaOrReport({
|
|
35720
36519
|
filePath,
|
|
35721
36520
|
account,
|
|
@@ -36140,18 +36939,18 @@ import { cors } from "hono/cors";
|
|
|
36140
36939
|
import { compress } from "hono/compress";
|
|
36141
36940
|
import { serve } from "@hono/node-server";
|
|
36142
36941
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
36143
|
-
import { WebSocketServer as
|
|
36144
|
-
import
|
|
36145
|
-
import
|
|
36942
|
+
import { WebSocketServer as WebSocketServer4 } from "ws";
|
|
36943
|
+
import path87 from "node:path";
|
|
36944
|
+
import fs79 from "node:fs";
|
|
36146
36945
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
36147
36946
|
import { homedir as homedir25 } from "node:os";
|
|
36148
36947
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
36149
36948
|
function findProjectRoot() {
|
|
36150
36949
|
let dir = process.cwd();
|
|
36151
36950
|
for (let i = 0; i < 32; i++) {
|
|
36152
|
-
if (
|
|
36951
|
+
if (fs79.existsSync(path87.join(dir, "pnpm-workspace.yaml")))
|
|
36153
36952
|
return dir;
|
|
36154
|
-
const parent =
|
|
36953
|
+
const parent = path87.dirname(dir);
|
|
36155
36954
|
if (parent === dir)
|
|
36156
36955
|
break;
|
|
36157
36956
|
dir = parent;
|
|
@@ -36161,15 +36960,15 @@ function findProjectRoot() {
|
|
|
36161
36960
|
return process.cwd();
|
|
36162
36961
|
}
|
|
36163
36962
|
function acquireSingleInstanceLock(lockFile) {
|
|
36164
|
-
|
|
36165
|
-
const fd =
|
|
36963
|
+
fs79.mkdirSync(path87.dirname(lockFile), { recursive: true });
|
|
36964
|
+
const fd = fs79.openSync(lockFile, fs79.constants.O_RDWR | fs79.constants.O_CREAT, 420);
|
|
36166
36965
|
const result = spawnSync4("flock", ["-n", "3"], {
|
|
36167
36966
|
stdio: ["ignore", "ignore", "ignore", fd]
|
|
36168
36967
|
});
|
|
36169
36968
|
if (result.error) {
|
|
36170
36969
|
const content = readLockPid(fd);
|
|
36171
36970
|
if (content > 0 && isProcessAlive(content)) {
|
|
36172
|
-
|
|
36971
|
+
fs79.closeSync(fd);
|
|
36173
36972
|
console.error(`[Server] another halo server is already running (pid ${content}). kill it first.`);
|
|
36174
36973
|
process.exit(1);
|
|
36175
36974
|
}
|
|
@@ -36178,19 +36977,19 @@ function acquireSingleInstanceLock(lockFile) {
|
|
|
36178
36977
|
if (content > 0 && !isProcessAlive(content)) {
|
|
36179
36978
|
console.warn(`[Server] removing stale server.lock (pid ${content} not running)`);
|
|
36180
36979
|
} else {
|
|
36181
|
-
|
|
36980
|
+
fs79.closeSync(fd);
|
|
36182
36981
|
console.error(`[Server] another halo server is already running${content > 0 ? ` (pid ${content})` : ""}. kill it first.`);
|
|
36183
36982
|
process.exit(1);
|
|
36184
36983
|
}
|
|
36185
36984
|
}
|
|
36186
|
-
|
|
36187
|
-
|
|
36188
|
-
|
|
36985
|
+
fs79.ftruncateSync(fd);
|
|
36986
|
+
fs79.writeSync(fd, String(process.pid));
|
|
36987
|
+
fs79.fsyncSync(fd);
|
|
36189
36988
|
}
|
|
36190
36989
|
function readLockPid(fd) {
|
|
36191
36990
|
try {
|
|
36192
36991
|
const buf = Buffer.alloc(32);
|
|
36193
|
-
const n =
|
|
36992
|
+
const n = fs79.readSync(fd, buf, 0, 32, 0);
|
|
36194
36993
|
return parseInt(buf.subarray(0, n).toString("utf-8").trim(), 10) || 0;
|
|
36195
36994
|
} catch {
|
|
36196
36995
|
return 0;
|
|
@@ -36218,7 +37017,7 @@ function isProcessAlive(pid) {
|
|
|
36218
37017
|
}
|
|
36219
37018
|
}
|
|
36220
37019
|
function readGitSha() {
|
|
36221
|
-
if (!
|
|
37020
|
+
if (!fs79.existsSync(path87.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs79.existsSync(path87.join(PROJECT_ROOT, ".git")))
|
|
36222
37021
|
return null;
|
|
36223
37022
|
try {
|
|
36224
37023
|
const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
|
|
@@ -36239,14 +37038,14 @@ function applySandboxPaths() {
|
|
|
36239
37038
|
}
|
|
36240
37039
|
function resolveFrontendDir() {
|
|
36241
37040
|
if (process.env.HALO_FRONTEND_DIR)
|
|
36242
|
-
return
|
|
36243
|
-
const bundled =
|
|
36244
|
-
if (
|
|
37041
|
+
return path87.resolve(process.env.HALO_FRONTEND_DIR);
|
|
37042
|
+
const bundled = path87.resolve(__dirname3, "..", "admin-out");
|
|
37043
|
+
if (fs79.existsSync(path87.join(bundled, "index.html")))
|
|
36245
37044
|
return bundled;
|
|
36246
|
-
const sibling =
|
|
36247
|
-
if (
|
|
37045
|
+
const sibling = path87.resolve(__dirname3, "..", "..", "admin", "out");
|
|
37046
|
+
if (fs79.existsSync(path87.join(sibling, "index.html")))
|
|
36248
37047
|
return sibling;
|
|
36249
|
-
return
|
|
37048
|
+
return path87.resolve(PROJECT_ROOT, "packages", "admin", "out");
|
|
36250
37049
|
}
|
|
36251
37050
|
async function gracefulShutdown(signal) {
|
|
36252
37051
|
console.log(`[Server] Received ${signal}, shutting down gracefully...`);
|
|
@@ -36262,6 +37061,7 @@ async function gracefulShutdown(signal) {
|
|
|
36262
37061
|
});
|
|
36263
37062
|
wss.close();
|
|
36264
37063
|
transcribe?.close();
|
|
37064
|
+
stt?.close();
|
|
36265
37065
|
jupyterChannels?.close();
|
|
36266
37066
|
await jupyter?.stop();
|
|
36267
37067
|
if (server && typeof server.close === "function") {
|
|
@@ -36280,7 +37080,7 @@ async function gracefulShutdown(signal) {
|
|
|
36280
37080
|
await shutdownObservability();
|
|
36281
37081
|
process.exit(0);
|
|
36282
37082
|
}
|
|
36283
|
-
var __dirname3, PORT, HALO_VERSION, PROJECT_ROOT, HALO_HOME4, GIT_SHA, AGENTCORE, sandboxWarning, app, allowlist, jupyter, channelDb, OWNS_RUNTIMES, registry, agentcoreLease, FRONTEND_DIR, server, wss, transcribe, jupyterChannels, upgradeRoutes;
|
|
37083
|
+
var __dirname3, PORT, HALO_VERSION, PROJECT_ROOT, HALO_HOME4, GIT_SHA, AGENTCORE, sandboxWarning, app, allowlist, jupyter, channelDb, OWNS_RUNTIMES, registry, agentcoreLease, FRONTEND_DIR, server, wss, transcribe, stt, jupyterChannels, upgradeRoutes;
|
|
36284
37084
|
var init_dist2 = __esm({
|
|
36285
37085
|
async "../server/dist/index.js"() {
|
|
36286
37086
|
"use strict";
|
|
@@ -36297,6 +37097,7 @@ var init_dist2 = __esm({
|
|
|
36297
37097
|
init_metrics();
|
|
36298
37098
|
init_commands3();
|
|
36299
37099
|
init_transcribe_ws();
|
|
37100
|
+
init_stt_ws();
|
|
36300
37101
|
init_jupyter();
|
|
36301
37102
|
init_manager();
|
|
36302
37103
|
init_upgrade_router();
|
|
@@ -36334,13 +37135,13 @@ var init_dist2 = __esm({
|
|
|
36334
37135
|
init_sandbox();
|
|
36335
37136
|
init_init();
|
|
36336
37137
|
init_git_ssh();
|
|
36337
|
-
__dirname3 =
|
|
37138
|
+
__dirname3 = path87.dirname(fileURLToPath3(import.meta.url));
|
|
36338
37139
|
PORT = config.server.port;
|
|
36339
|
-
HALO_VERSION = "1.6.
|
|
37140
|
+
HALO_VERSION = "1.6.5";
|
|
36340
37141
|
PROJECT_ROOT = findProjectRoot();
|
|
36341
|
-
HALO_HOME4 =
|
|
37142
|
+
HALO_HOME4 = path87.join(homedir25(), ".halo");
|
|
36342
37143
|
GIT_SHA = readGitSha();
|
|
36343
|
-
if (!
|
|
37144
|
+
if (!fs79.existsSync(path87.join(HALO_HOME4, "global", ".template-version"))) {
|
|
36344
37145
|
process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
|
|
36345
37146
|
process.exit(1);
|
|
36346
37147
|
}
|
|
@@ -36351,7 +37152,7 @@ var init_dist2 = __esm({
|
|
|
36351
37152
|
process.exit(1);
|
|
36352
37153
|
}
|
|
36353
37154
|
if (!AGENTCORE)
|
|
36354
|
-
acquireSingleInstanceLock(
|
|
37155
|
+
acquireSingleInstanceLock(path87.join(HALO_HOME4, "global", "server.lock"));
|
|
36355
37156
|
commandRegistry.assertDispatchCommands(DISPATCH_COMMANDS);
|
|
36356
37157
|
await initObservability();
|
|
36357
37158
|
initLogger();
|
|
@@ -36421,12 +37222,12 @@ var init_dist2 = __esm({
|
|
|
36421
37222
|
app.route("/api", createJupyterRoutes(jupyter));
|
|
36422
37223
|
}
|
|
36423
37224
|
start2();
|
|
36424
|
-
channelDb = createChannelDb(
|
|
37225
|
+
channelDb = createChannelDb(path87.join(HALO_HOME4, "secrets"));
|
|
36425
37226
|
setChannelDb(channelDb);
|
|
36426
|
-
setEvoDb(createEvoDb(
|
|
36427
|
-
setCronDb(createCronDb(
|
|
37227
|
+
setEvoDb(createEvoDb(path87.join(HALO_HOME4, "global")));
|
|
37228
|
+
setCronDb(createCronDb(path87.join(HALO_HOME4, "global")));
|
|
36428
37229
|
if (!AGENTCORE)
|
|
36429
|
-
setRunsDb(createRunsDb(
|
|
37230
|
+
setRunsDb(createRunsDb(path87.join(HALO_HOME4, "global")));
|
|
36430
37231
|
setEvoSpawner(realEvoSpawner);
|
|
36431
37232
|
if (!AGENTCORE) {
|
|
36432
37233
|
startEvoTicker();
|
|
@@ -36441,7 +37242,7 @@ var init_dist2 = __esm({
|
|
|
36441
37242
|
setCronSessionRegistry(registry);
|
|
36442
37243
|
if (OWNS_RUNTIMES && !AGENTCORE) {
|
|
36443
37244
|
for (const ws of listRunningWorkspaces()) {
|
|
36444
|
-
if (!
|
|
37245
|
+
if (!fs79.existsSync(path87.join(ws, ".halo")))
|
|
36445
37246
|
continue;
|
|
36446
37247
|
if (!claimWorkspaceRuntime(ws))
|
|
36447
37248
|
continue;
|
|
@@ -36467,7 +37268,7 @@ var init_dist2 = __esm({
|
|
|
36467
37268
|
startCronDaemon();
|
|
36468
37269
|
}
|
|
36469
37270
|
if (!AGENTCORE) {
|
|
36470
|
-
setA2ADb(createA2ADb(
|
|
37271
|
+
setA2ADb(createA2ADb(path87.join(HALO_HOME4, "global")));
|
|
36471
37272
|
setA2AOutboundRegistry(registry);
|
|
36472
37273
|
app.route("/a2a", createA2ARoutes({ registry, ownsRuntimes: OWNS_RUNTIMES }));
|
|
36473
37274
|
app.route("/", createA2APushRoutes());
|
|
@@ -36483,11 +37284,11 @@ var init_dist2 = __esm({
|
|
|
36483
37284
|
}
|
|
36484
37285
|
FRONTEND_DIR = resolveFrontendDir();
|
|
36485
37286
|
if (!AGENTCORE) {
|
|
36486
|
-
app.use("/*", serveStatic({ root:
|
|
37287
|
+
app.use("/*", serveStatic({ root: path87.relative(process.cwd(), FRONTEND_DIR) }));
|
|
36487
37288
|
app.get("/*", (c) => {
|
|
36488
|
-
const indexPath =
|
|
37289
|
+
const indexPath = path87.join(FRONTEND_DIR, "index.html");
|
|
36489
37290
|
try {
|
|
36490
|
-
const html =
|
|
37291
|
+
const html = fs79.readFileSync(indexPath, "utf-8");
|
|
36491
37292
|
return c.html(html);
|
|
36492
37293
|
} catch {
|
|
36493
37294
|
return c.text("Frontend not built. Run: cd packages/admin && npx next build", 503);
|
|
@@ -36506,7 +37307,7 @@ var init_dist2 = __esm({
|
|
|
36506
37307
|
for (const line of AGENTCORE ? lines.slice(0, 1) : lines)
|
|
36507
37308
|
logAlways(line);
|
|
36508
37309
|
});
|
|
36509
|
-
wss = new
|
|
37310
|
+
wss = new WebSocketServer4({
|
|
36510
37311
|
noServer: true,
|
|
36511
37312
|
verifyClient: (info, callback) => {
|
|
36512
37313
|
const token = getTokenFromCookieHeader(info.req.headers.cookie);
|
|
@@ -36522,12 +37323,15 @@ var init_dist2 = __esm({
|
|
|
36522
37323
|
setBroadcastWss(wss);
|
|
36523
37324
|
}
|
|
36524
37325
|
transcribe = AGENTCORE ? null : createTranscribeProxy();
|
|
37326
|
+
stt = AGENTCORE ? null : createSttProxy();
|
|
36525
37327
|
jupyterChannels = jupyter ? createJupyterChannels(jupyter) : null;
|
|
36526
37328
|
upgradeRoutes = new Map(AGENTCORE ? [] : [
|
|
36527
37329
|
["/ws", (req, socket, head) => wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req))]
|
|
36528
37330
|
]);
|
|
36529
37331
|
if (transcribe)
|
|
36530
37332
|
upgradeRoutes.set(TRANSCRIBE_PATH, transcribe.handleUpgrade);
|
|
37333
|
+
if (stt)
|
|
37334
|
+
upgradeRoutes.set(STT_PATH, stt.handleUpgrade);
|
|
36531
37335
|
if (jupyterChannels)
|
|
36532
37336
|
upgradeRoutes.set(JUPYTER_CHANNELS_PATH, jupyterChannels.handleUpgrade);
|
|
36533
37337
|
server.on("upgrade", createUpgradeRouter(upgradeRoutes));
|
|
@@ -36695,8 +37499,8 @@ var init_halo_client = __esm({
|
|
|
36695
37499
|
}
|
|
36696
37500
|
return await res.json();
|
|
36697
37501
|
}
|
|
36698
|
-
sessionUrl(
|
|
36699
|
-
const url = new URL(`${this.opts.baseUrl}${
|
|
37502
|
+
sessionUrl(path90, workspace, sessionId) {
|
|
37503
|
+
const url = new URL(`${this.opts.baseUrl}${path90}`);
|
|
36700
37504
|
url.searchParams.set("workspace", workspace);
|
|
36701
37505
|
url.searchParams.set("sessionId", sessionId);
|
|
36702
37506
|
return url;
|
|
@@ -36844,7 +37648,7 @@ var init_version = __esm({
|
|
|
36844
37648
|
});
|
|
36845
37649
|
|
|
36846
37650
|
// ../acp-adapter/dist/adapter.js
|
|
36847
|
-
import
|
|
37651
|
+
import path88 from "node:path";
|
|
36848
37652
|
function composePrompt(blocks) {
|
|
36849
37653
|
const segments = [];
|
|
36850
37654
|
let run = "";
|
|
@@ -37037,7 +37841,7 @@ var init_adapter = __esm({
|
|
|
37037
37841
|
if (cursor !== void 0 && !/^\d+$/.test(cursor))
|
|
37038
37842
|
throw newError(`invalid cursor: ${cursor}`, -32602);
|
|
37039
37843
|
const page = await this.client.listSessions(this.config.workspace, cursor === void 0 ? void 0 : Number(cursor));
|
|
37040
|
-
if (p.cwd !== void 0 &&
|
|
37844
|
+
if (p.cwd !== void 0 && path88.resolve(p.cwd) !== path88.resolve(page.workspace))
|
|
37041
37845
|
return { sessions: [] };
|
|
37042
37846
|
return {
|
|
37043
37847
|
sessions: page.sessions.map((s) => ({
|
|
@@ -37651,8 +38455,8 @@ process.emitWarning = (warning, ...args) => {
|
|
|
37651
38455
|
|
|
37652
38456
|
// src/index.ts
|
|
37653
38457
|
init_server_supervisor();
|
|
37654
|
-
import
|
|
37655
|
-
import
|
|
38458
|
+
import path89 from "node:path";
|
|
38459
|
+
import fs80 from "node:fs";
|
|
37656
38460
|
import { homedir as homedir26, userInfo } from "node:os";
|
|
37657
38461
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
37658
38462
|
|
|
@@ -37943,7 +38747,7 @@ function installUserUnit(opts, run = runCommand) {
|
|
|
37943
38747
|
}
|
|
37944
38748
|
|
|
37945
38749
|
// src/index.ts
|
|
37946
|
-
var VERSION = "1.6.
|
|
38750
|
+
var VERSION = "1.6.5";
|
|
37947
38751
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
37948
38752
|
|
|
37949
38753
|
Commands:
|
|
@@ -38285,8 +39089,8 @@ async function cmdSetup(argv = []) {
|
|
|
38285
39089
|
const { buildBindProviderPlan: buildBindProviderPlan2, envKeyPlaceholders: envKeyPlaceholders2, KEEP_CURRENT: KEEP_CURRENT2, AWS_CHAIN_PROVIDER: AWS_CHAIN_PROVIDER2 } = await Promise.resolve().then(() => (init_setup_bind_provider(), setup_bind_provider_exports));
|
|
38286
39090
|
const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
|
|
38287
39091
|
const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
|
|
38288
|
-
const haloHome2 =
|
|
38289
|
-
const globalDir2 =
|
|
39092
|
+
const haloHome2 = path89.join(homedir26(), ".halo");
|
|
39093
|
+
const globalDir2 = path89.join(haloHome2, "global");
|
|
38290
39094
|
ensureHaloHome2(haloHome2);
|
|
38291
39095
|
process.stderr.write(`[setup] ${haloHome2}/ ready
|
|
38292
39096
|
`);
|
|
@@ -38639,7 +39443,7 @@ async function cmdUpgrade() {
|
|
|
38639
39443
|
`);
|
|
38640
39444
|
process.exit(1);
|
|
38641
39445
|
}
|
|
38642
|
-
const broken = verifyNativeModules2(
|
|
39446
|
+
const broken = verifyNativeModules2(path89.join(rootDir, PKG));
|
|
38643
39447
|
if (broken.length > 0) {
|
|
38644
39448
|
process.stderr.write(
|
|
38645
39449
|
`
|
|
@@ -38670,7 +39474,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
|
|
|
38670
39474
|
async function cmdAgents(flags) {
|
|
38671
39475
|
const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
38672
39476
|
await initRuntime2();
|
|
38673
|
-
const workspace =
|
|
39477
|
+
const workspace = path89.resolve(flags.workspace ?? process.cwd());
|
|
38674
39478
|
const agents = await listAgents2(workspace);
|
|
38675
39479
|
if (agents.length === 0) {
|
|
38676
39480
|
process.stderr.write("No agents found.\n");
|
|
@@ -38686,7 +39490,7 @@ async function cmdAgents(flags) {
|
|
|
38686
39490
|
async function cmdSessions(flags) {
|
|
38687
39491
|
const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
38688
39492
|
await initRuntime2();
|
|
38689
|
-
const workspace =
|
|
39493
|
+
const workspace = path89.resolve(flags.workspace ?? process.cwd());
|
|
38690
39494
|
const sessions = listSessions2(workspace);
|
|
38691
39495
|
if (sessions.length === 0) {
|
|
38692
39496
|
process.stderr.write("No sessions found.\n");
|
|
@@ -38717,7 +39521,7 @@ async function cmdModels(argv) {
|
|
|
38717
39521
|
process.exitCode = 1;
|
|
38718
39522
|
return;
|
|
38719
39523
|
}
|
|
38720
|
-
const plan = mod.planModelsInstall(
|
|
39524
|
+
const plan = mod.planModelsInstall(path89.resolve(dir));
|
|
38721
39525
|
for (const line of mod.formatPlan(plan)) process.stdout.write(`${line}
|
|
38722
39526
|
`);
|
|
38723
39527
|
if (plan.needsConfirm && !values.yes) {
|
|
@@ -38826,11 +39630,11 @@ ${stdinContent}` : stdinContent;
|
|
|
38826
39630
|
harness.destroy();
|
|
38827
39631
|
}
|
|
38828
39632
|
}
|
|
38829
|
-
var SERVER_LOCK_PATH =
|
|
38830
|
-
var SERVER_LOG_PATH =
|
|
39633
|
+
var SERVER_LOCK_PATH = path89.join(homedir26(), ".halo", "global", "server.lock");
|
|
39634
|
+
var SERVER_LOG_PATH = path89.join(homedir26(), ".halo", "logs", "server.log");
|
|
38831
39635
|
function readServerPid() {
|
|
38832
39636
|
try {
|
|
38833
|
-
const txt =
|
|
39637
|
+
const txt = fs80.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
|
|
38834
39638
|
const n = parseInt(txt, 10);
|
|
38835
39639
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
38836
39640
|
} catch {
|
|
@@ -38937,8 +39741,8 @@ async function cmdServerStart(argv, restart) {
|
|
|
38937
39741
|
}
|
|
38938
39742
|
if (plan.note) writeDim(plan.note);
|
|
38939
39743
|
const { spawn: spawn5 } = await import("node:child_process");
|
|
38940
|
-
|
|
38941
|
-
const out =
|
|
39744
|
+
fs80.mkdirSync(path89.dirname(SERVER_LOG_PATH), { recursive: true });
|
|
39745
|
+
const out = fs80.openSync(SERVER_LOG_PATH, "a");
|
|
38942
39746
|
const child = spawn5(process.argv[0], [process.argv[1], "server", "start"], {
|
|
38943
39747
|
detached: true,
|
|
38944
39748
|
stdio: ["ignore", out, out],
|
|
@@ -39018,8 +39822,8 @@ ${HELP_AGENTCORE}`);
|
|
|
39018
39822
|
process.exitCode = 1;
|
|
39019
39823
|
return;
|
|
39020
39824
|
}
|
|
39021
|
-
const workspace =
|
|
39022
|
-
if (!
|
|
39825
|
+
const workspace = path89.resolve(raw);
|
|
39826
|
+
if (!fs80.statSync(workspace, { throwIfNoEntry: false })?.isDirectory()) {
|
|
39023
39827
|
process.stderr.write(`Workspace is not a directory: ${workspace}
|
|
39024
39828
|
`);
|
|
39025
39829
|
process.exitCode = 1;
|
|
@@ -39125,7 +39929,7 @@ async function cmdServerLogs(argv) {
|
|
|
39125
39929
|
lines: { type: "string", short: "n" }
|
|
39126
39930
|
}
|
|
39127
39931
|
});
|
|
39128
|
-
if (!
|
|
39932
|
+
if (!fs80.existsSync(SERVER_LOG_PATH)) {
|
|
39129
39933
|
process.stderr.write(`No log file at ${SERVER_LOG_PATH}
|
|
39130
39934
|
`);
|
|
39131
39935
|
return;
|
|
@@ -39143,7 +39947,7 @@ function exitMissingSetup() {
|
|
|
39143
39947
|
process.exit(1);
|
|
39144
39948
|
}
|
|
39145
39949
|
function isHaloHomeReady() {
|
|
39146
|
-
return
|
|
39950
|
+
return fs80.existsSync(path89.join(homedir26(), ".halo", "global", ".template-version"));
|
|
39147
39951
|
}
|
|
39148
39952
|
async function main2() {
|
|
39149
39953
|
const argv = process.argv.slice(2);
|