@turmind/halo 1.6.4 → 1.6.6
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 +18 -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/a2a.md +1 -1
- package/bundled-docs/design/canvas-extensions.md +38 -7
- package/bundled-docs/design/cron.md +13 -5
- package/bundled-docs/design/feishu.md +2 -0
- package/bundled-docs/design/halo-city.md +18 -149
- package/bundled-docs/design/slack.md +2 -2
- 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/design/wecom.md +1 -1
- package/bundled-docs/dev/add-model-provider.md +1 -1
- package/bundled-docs/dev/api.md +13 -10
- 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 +1509 -604
- package/package.json +2 -1
- package/templates/models/mimo-token-plan-china.yaml +3 -55
- 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- → Yc05ORrVk5IE9P1Gd8Ojh}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{kfp1cMc36J6hjZ06L9mm- → Yc05ORrVk5IE9P1Gd8Ojh}/_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 = 103;
|
|
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.6", 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 };
|
|
@@ -25249,10 +25556,27 @@ var init_session_types = __esm({
|
|
|
25249
25556
|
}
|
|
25250
25557
|
});
|
|
25251
25558
|
|
|
25559
|
+
// ../server/dist/routes/etag.js
|
|
25560
|
+
function etagMatches(header, etag) {
|
|
25561
|
+
if (!header)
|
|
25562
|
+
return false;
|
|
25563
|
+
const bare = etag.replace(/^W\//, "");
|
|
25564
|
+
return header.split(",").some((t2) => {
|
|
25565
|
+
const tag = t2.trim();
|
|
25566
|
+
return tag === "*" || tag.replace(/^W\//, "") === bare;
|
|
25567
|
+
});
|
|
25568
|
+
}
|
|
25569
|
+
var init_etag = __esm({
|
|
25570
|
+
"../server/dist/routes/etag.js"() {
|
|
25571
|
+
"use strict";
|
|
25572
|
+
}
|
|
25573
|
+
});
|
|
25574
|
+
|
|
25252
25575
|
// ../server/dist/routes/halo-city.js
|
|
25253
|
-
import { Hono as
|
|
25576
|
+
import { Hono as Hono12 } from "hono";
|
|
25254
25577
|
import fs59 from "node:fs";
|
|
25255
25578
|
import path67 from "node:path";
|
|
25579
|
+
import crypto6 from "node:crypto";
|
|
25256
25580
|
import Database7 from "better-sqlite3";
|
|
25257
25581
|
function scanSkills2(dir) {
|
|
25258
25582
|
let names;
|
|
@@ -25536,21 +25860,30 @@ function discoverWorkspaces(registry2) {
|
|
|
25536
25860
|
}
|
|
25537
25861
|
return out;
|
|
25538
25862
|
}
|
|
25863
|
+
function stateEtag(accessLevel, skills, workspaces, uptime) {
|
|
25864
|
+
const json = JSON.stringify({ accessLevel, skills, workspaces, uptimeMin: Math.floor(uptime / 60) });
|
|
25865
|
+
return `W/"${crypto6.createHash("sha1").update(json).digest("base64url")}"`;
|
|
25866
|
+
}
|
|
25539
25867
|
function createShowRoutes(registry2) {
|
|
25540
|
-
const app2 = new
|
|
25868
|
+
const app2 = new Hono12();
|
|
25541
25869
|
function auth(c) {
|
|
25870
|
+
const cookie = getTokenFromCookieHeader(c.req.header("cookie"));
|
|
25871
|
+
if (!requestToken(c) && cookie && isAuthenticated(cookie)) {
|
|
25872
|
+
return { ok: true, accessLevel: "full", scoped: null };
|
|
25873
|
+
}
|
|
25542
25874
|
const a = resolveTokenAuth(c, getChannelDb());
|
|
25543
25875
|
if (!a.ok)
|
|
25544
25876
|
return { ok: false, response: tokenAuthJsonError(c, a.reason) };
|
|
25545
|
-
|
|
25877
|
+
const { account } = a;
|
|
25878
|
+
const globalView = account.accessLevel === "full" || account.accessLevel === "observer";
|
|
25879
|
+
return { ok: true, accessLevel: account.accessLevel, scoped: globalView ? null : account };
|
|
25546
25880
|
}
|
|
25547
25881
|
app2.get("/show/state", (c) => {
|
|
25548
25882
|
const a = auth(c);
|
|
25549
25883
|
if (!a.ok)
|
|
25550
25884
|
return a.response;
|
|
25551
|
-
const {
|
|
25552
|
-
const
|
|
25553
|
-
const targets = globalView ? discoverWorkspaces(registry2) : /* @__PURE__ */ new Map([[account.workspacePath, path67.basename(account.workspacePath)]]);
|
|
25885
|
+
const { scoped } = a;
|
|
25886
|
+
const targets = scoped ? /* @__PURE__ */ new Map([[scoped.workspacePath, path67.basename(scoped.workspacePath)]]) : discoverWorkspaces(registry2);
|
|
25554
25887
|
const workspaces = [];
|
|
25555
25888
|
for (const [wsPath, label] of targets) {
|
|
25556
25889
|
try {
|
|
@@ -25563,11 +25896,18 @@ function createShowRoutes(registry2) {
|
|
|
25563
25896
|
}
|
|
25564
25897
|
}
|
|
25565
25898
|
workspaces.sort((x, y) => workspaceBirth(x.path) - workspaceBirth(y.path) || x.key.localeCompare(y.key));
|
|
25899
|
+
const uptime = process.uptime();
|
|
25900
|
+
const skills = scanSkills2(GLOBAL_SKILLS_DIR);
|
|
25901
|
+
const etag = stateEtag(a.accessLevel, skills, workspaces, uptime);
|
|
25902
|
+
c.header("ETag", etag);
|
|
25903
|
+
c.header("Cache-Control", "private, no-cache");
|
|
25904
|
+
if (etagMatches(c.req.header("if-none-match"), etag))
|
|
25905
|
+
return c.body(null, 304);
|
|
25566
25906
|
return c.json({
|
|
25567
25907
|
serverTime: Date.now(),
|
|
25568
|
-
uptime
|
|
25569
|
-
accessLevel:
|
|
25570
|
-
skills
|
|
25908
|
+
uptime,
|
|
25909
|
+
accessLevel: a.accessLevel,
|
|
25910
|
+
skills,
|
|
25571
25911
|
workspaces
|
|
25572
25912
|
});
|
|
25573
25913
|
});
|
|
@@ -25575,19 +25915,18 @@ function createShowRoutes(registry2) {
|
|
|
25575
25915
|
const a = auth(c);
|
|
25576
25916
|
if (!a.ok)
|
|
25577
25917
|
return a.response;
|
|
25578
|
-
const {
|
|
25918
|
+
const { scoped } = a;
|
|
25579
25919
|
const wsPath = c.req.query("ws") ?? "";
|
|
25580
25920
|
const id = c.req.query("id") ?? "";
|
|
25581
25921
|
if (!wsPath || !id)
|
|
25582
25922
|
return c.json({ error: "ws and id required" }, 400);
|
|
25583
|
-
|
|
25584
|
-
if (!globalView) {
|
|
25923
|
+
if (scoped) {
|
|
25585
25924
|
let allowed = false;
|
|
25586
25925
|
try {
|
|
25587
|
-
allowed = fs59.realpathSync(wsPath) === fs59.realpathSync(
|
|
25926
|
+
allowed = fs59.realpathSync(wsPath) === fs59.realpathSync(scoped.workspacePath);
|
|
25588
25927
|
} catch {
|
|
25589
25928
|
}
|
|
25590
|
-
if (!allowed || !canAddressSession(
|
|
25929
|
+
if (!allowed || !canAddressSession(scoped, id))
|
|
25591
25930
|
return c.json({ error: "forbidden" }, 403);
|
|
25592
25931
|
}
|
|
25593
25932
|
try {
|
|
@@ -25640,11 +25979,13 @@ var init_halo_city = __esm({
|
|
|
25640
25979
|
"use strict";
|
|
25641
25980
|
init_channel_db();
|
|
25642
25981
|
init_web_token();
|
|
25982
|
+
init_auth();
|
|
25643
25983
|
init_handler();
|
|
25644
25984
|
init_agent_loader();
|
|
25645
25985
|
init_session_store();
|
|
25646
25986
|
init_session_types();
|
|
25647
25987
|
init_mtime_cache();
|
|
25988
|
+
init_etag();
|
|
25648
25989
|
SESSIONS_PER_WS = 80;
|
|
25649
25990
|
_skillCache = createMtimeCache();
|
|
25650
25991
|
_metaCache = /* @__PURE__ */ new Map();
|
|
@@ -25655,7 +25996,7 @@ var init_halo_city = __esm({
|
|
|
25655
25996
|
});
|
|
25656
25997
|
|
|
25657
25998
|
// ../server/dist/routes/metrics.js
|
|
25658
|
-
import { Hono as
|
|
25999
|
+
import { Hono as Hono13 } from "hono";
|
|
25659
26000
|
function family(name, type, help, samples) {
|
|
25660
26001
|
const lines = [`# HELP ${name} ${help}`, `# TYPE ${name} ${type}`];
|
|
25661
26002
|
for (const s of samples)
|
|
@@ -25663,7 +26004,7 @@ function family(name, type, help, samples) {
|
|
|
25663
26004
|
return lines.join("\n");
|
|
25664
26005
|
}
|
|
25665
26006
|
function createMetricsRoutes(registry2) {
|
|
25666
|
-
const app2 = new
|
|
26007
|
+
const app2 = new Hono13();
|
|
25667
26008
|
function auth(c) {
|
|
25668
26009
|
const a = resolveTokenAuth(c, getChannelDb());
|
|
25669
26010
|
if (!a.ok) {
|
|
@@ -25748,9 +26089,9 @@ var init_metrics = __esm({
|
|
|
25748
26089
|
});
|
|
25749
26090
|
|
|
25750
26091
|
// ../server/dist/routes/commands.js
|
|
25751
|
-
import { Hono as
|
|
26092
|
+
import { Hono as Hono14 } from "hono";
|
|
25752
26093
|
function createCommandRoutes(registry2, smRegistry) {
|
|
25753
|
-
const app2 = new
|
|
26094
|
+
const app2 = new Hono14();
|
|
25754
26095
|
app2.get("/commands", async (c) => {
|
|
25755
26096
|
const projectId = c.req.query("projectId");
|
|
25756
26097
|
const sessionId = c.req.query("sessionId");
|
|
@@ -25777,233 +26118,6 @@ var init_commands3 = __esm({
|
|
|
25777
26118
|
}
|
|
25778
26119
|
});
|
|
25779
26120
|
|
|
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
26121
|
// ../server/dist/routes/transcribe-ws.js
|
|
26008
26122
|
import { WebSocketServer } from "ws";
|
|
26009
26123
|
import { StartStreamTranscriptionCommand, TranscribeStreamingClient } from "@aws-sdk/client-transcribe-streaming";
|
|
@@ -26217,19 +26331,688 @@ var init_transcribe_ws = __esm({
|
|
|
26217
26331
|
}
|
|
26218
26332
|
});
|
|
26219
26333
|
|
|
26334
|
+
// ../server/dist/stt/models.js
|
|
26335
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
26336
|
+
import fs60 from "node:fs";
|
|
26337
|
+
import fsp from "node:fs/promises";
|
|
26338
|
+
import http2 from "node:http";
|
|
26339
|
+
import https2 from "node:https";
|
|
26340
|
+
import path68 from "node:path";
|
|
26341
|
+
function httpGet(url, headers, signal, hops = 5) {
|
|
26342
|
+
return new Promise((resolve2, reject) => {
|
|
26343
|
+
const u = new URL(url);
|
|
26344
|
+
const req = (u.protocol === "https:" ? https2 : http2).get(u, { headers: { "User-Agent": "halo-stt", ...headers }, signal }, (res) => {
|
|
26345
|
+
res.on("error", () => {
|
|
26346
|
+
});
|
|
26347
|
+
const location = res.headers.location;
|
|
26348
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && location) {
|
|
26349
|
+
res.resume();
|
|
26350
|
+
if (hops === 0)
|
|
26351
|
+
reject(new Error("too many redirects"));
|
|
26352
|
+
else
|
|
26353
|
+
resolve2(httpGet(new URL(location, u).toString(), headers, signal, hops - 1));
|
|
26354
|
+
return;
|
|
26355
|
+
}
|
|
26356
|
+
resolve2(res);
|
|
26357
|
+
});
|
|
26358
|
+
req.on("error", reject);
|
|
26359
|
+
});
|
|
26360
|
+
}
|
|
26361
|
+
function createSttModelManager(opts = {}) {
|
|
26362
|
+
const files = opts.files ?? STT_MODEL_FILES;
|
|
26363
|
+
const connectTimeoutMs = opts.connectTimeoutMs ?? 15e3;
|
|
26364
|
+
const stallTimeoutMs = opts.stallTimeoutMs ?? 3e4;
|
|
26365
|
+
const progressIntervalMs = opts.progressIntervalMs ?? 500;
|
|
26366
|
+
const filePath = (rel) => path68.join(opts.dir ?? globalSttModelsDir(), rel);
|
|
26367
|
+
const total = files.reduce((n, f) => n + f.size, 0);
|
|
26368
|
+
const listeners2 = /* @__PURE__ */ new Set();
|
|
26369
|
+
let inflight = null;
|
|
26370
|
+
let received = 0;
|
|
26371
|
+
let lastError = "";
|
|
26372
|
+
let lastEmit = 0;
|
|
26373
|
+
let current = null;
|
|
26374
|
+
let closed = false;
|
|
26375
|
+
const fileReady = (f) => {
|
|
26376
|
+
try {
|
|
26377
|
+
return fs60.statSync(filePath(f.path)).size === f.size;
|
|
26378
|
+
} catch {
|
|
26379
|
+
return false;
|
|
26380
|
+
}
|
|
26381
|
+
};
|
|
26382
|
+
function status() {
|
|
26383
|
+
if (inflight)
|
|
26384
|
+
return { state: "downloading", received, total };
|
|
26385
|
+
if (files.every(fileReady))
|
|
26386
|
+
return { state: "ready" };
|
|
26387
|
+
if (lastError)
|
|
26388
|
+
return { state: "error", message: lastError };
|
|
26389
|
+
return { state: "missing" };
|
|
26390
|
+
}
|
|
26391
|
+
function emit2() {
|
|
26392
|
+
lastEmit = Date.now();
|
|
26393
|
+
const s = status();
|
|
26394
|
+
for (const l of listeners2)
|
|
26395
|
+
l(s);
|
|
26396
|
+
}
|
|
26397
|
+
async function fetchFile(f, url, base) {
|
|
26398
|
+
const final = filePath(f.path);
|
|
26399
|
+
const part = `${final}.part`;
|
|
26400
|
+
fs60.mkdirSync(path68.dirname(final), { recursive: true });
|
|
26401
|
+
let have = 0;
|
|
26402
|
+
try {
|
|
26403
|
+
have = fs60.statSync(part).size;
|
|
26404
|
+
} catch {
|
|
26405
|
+
}
|
|
26406
|
+
if (have > f.size) {
|
|
26407
|
+
fs60.rmSync(part, { force: true });
|
|
26408
|
+
have = 0;
|
|
26409
|
+
}
|
|
26410
|
+
received = base + have;
|
|
26411
|
+
if (have === f.size) {
|
|
26412
|
+
const hash = createHash2("sha256");
|
|
26413
|
+
for await (const chunk of fs60.createReadStream(part))
|
|
26414
|
+
hash.update(chunk);
|
|
26415
|
+
if (hash.digest("hex") === f.sha256) {
|
|
26416
|
+
fs60.renameSync(part, final);
|
|
26417
|
+
return;
|
|
26418
|
+
}
|
|
26419
|
+
fs60.rmSync(part, { force: true });
|
|
26420
|
+
have = 0;
|
|
26421
|
+
}
|
|
26422
|
+
const ac = new AbortController();
|
|
26423
|
+
current = ac;
|
|
26424
|
+
let reason = "";
|
|
26425
|
+
let timer2 = null;
|
|
26426
|
+
const arm = (ms, why) => {
|
|
26427
|
+
if (timer2)
|
|
26428
|
+
clearTimeout(timer2);
|
|
26429
|
+
timer2 = setTimeout(() => {
|
|
26430
|
+
reason = why;
|
|
26431
|
+
ac.abort();
|
|
26432
|
+
}, ms);
|
|
26433
|
+
};
|
|
26434
|
+
arm(connectTimeoutMs, `no data within ${connectTimeoutMs / 1e3}s`);
|
|
26435
|
+
try {
|
|
26436
|
+
const res = await httpGet(url, have > 0 ? { Range: `bytes=${have}-` } : {}, ac.signal);
|
|
26437
|
+
if (res.statusCode === 416) {
|
|
26438
|
+
fs60.rmSync(part, { force: true });
|
|
26439
|
+
throw new Error("HTTP 416");
|
|
26440
|
+
}
|
|
26441
|
+
if (res.statusCode !== 200 && res.statusCode !== 206)
|
|
26442
|
+
throw new Error(`HTTP ${res.statusCode}`);
|
|
26443
|
+
const resume = res.statusCode === 206;
|
|
26444
|
+
if (resume && !(res.headers["content-range"] ?? "").startsWith(`bytes ${have}-`)) {
|
|
26445
|
+
throw new Error(`unexpected Content-Range ${res.headers["content-range"] ?? "(none)"}`);
|
|
26446
|
+
}
|
|
26447
|
+
if (have > 0)
|
|
26448
|
+
console.log(`[STT] ${f.path}: ${resume ? `resuming at ${have} bytes` : `source ignored Range, restarting from 0 (had ${have} bytes)`}`);
|
|
26449
|
+
const hash = createHash2("sha256");
|
|
26450
|
+
let got = 0;
|
|
26451
|
+
if (resume) {
|
|
26452
|
+
for await (const chunk of fs60.createReadStream(part))
|
|
26453
|
+
hash.update(chunk);
|
|
26454
|
+
got = have;
|
|
26455
|
+
}
|
|
26456
|
+
const fh = await fsp.open(part, resume ? "a" : "w");
|
|
26457
|
+
try {
|
|
26458
|
+
for await (const value of res) {
|
|
26459
|
+
arm(stallTimeoutMs, `stalled for ${stallTimeoutMs / 1e3}s`);
|
|
26460
|
+
got += value.length;
|
|
26461
|
+
if (got > f.size)
|
|
26462
|
+
break;
|
|
26463
|
+
hash.update(value);
|
|
26464
|
+
await fh.write(value);
|
|
26465
|
+
received = base + got;
|
|
26466
|
+
if (Date.now() - lastEmit >= progressIntervalMs)
|
|
26467
|
+
emit2();
|
|
26468
|
+
}
|
|
26469
|
+
} finally {
|
|
26470
|
+
await fh.close();
|
|
26471
|
+
}
|
|
26472
|
+
if (got < f.size)
|
|
26473
|
+
throw new Error(`truncated (${got} of ${f.size} bytes)`);
|
|
26474
|
+
if (got > f.size || hash.digest("hex") !== f.sha256) {
|
|
26475
|
+
fs60.rmSync(part, { force: true });
|
|
26476
|
+
throw new Error(got > f.size ? `size mismatch (more than ${f.size} bytes)` : "sha256 mismatch");
|
|
26477
|
+
}
|
|
26478
|
+
fs60.renameSync(part, final);
|
|
26479
|
+
} catch (err) {
|
|
26480
|
+
if (reason)
|
|
26481
|
+
throw new Error(reason);
|
|
26482
|
+
throw err;
|
|
26483
|
+
} finally {
|
|
26484
|
+
if (timer2)
|
|
26485
|
+
clearTimeout(timer2);
|
|
26486
|
+
ac.abort();
|
|
26487
|
+
current = null;
|
|
26488
|
+
}
|
|
26489
|
+
}
|
|
26490
|
+
async function run() {
|
|
26491
|
+
let base = files.filter(fileReady).reduce((n, f) => n + f.size, 0);
|
|
26492
|
+
received = base;
|
|
26493
|
+
for (const f of files) {
|
|
26494
|
+
if (fileReady(f))
|
|
26495
|
+
continue;
|
|
26496
|
+
const failures = [];
|
|
26497
|
+
for (const src of f.sources) {
|
|
26498
|
+
if (closed)
|
|
26499
|
+
return;
|
|
26500
|
+
const t0 = Date.now();
|
|
26501
|
+
try {
|
|
26502
|
+
await fetchFile(f, src.url, base);
|
|
26503
|
+
console.log(`[STT] downloaded ${f.path} from ${src.name} (${(f.size / 1048576).toFixed(1)} MB in ${((Date.now() - t0) / 1e3).toFixed(1)}s)`);
|
|
26504
|
+
failures.length = 0;
|
|
26505
|
+
break;
|
|
26506
|
+
} catch (err) {
|
|
26507
|
+
if (closed)
|
|
26508
|
+
return;
|
|
26509
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26510
|
+
failures.push(`${src.name}: ${msg}`);
|
|
26511
|
+
console.log(`[STT] download ${f.path} from ${src.name} failed: ${msg}`);
|
|
26512
|
+
}
|
|
26513
|
+
}
|
|
26514
|
+
if (failures.length > 0) {
|
|
26515
|
+
lastError = `${path68.basename(f.path)}: all sources failed (${failures.join("; ")})`;
|
|
26516
|
+
return;
|
|
26517
|
+
}
|
|
26518
|
+
base += f.size;
|
|
26519
|
+
received = base;
|
|
26520
|
+
}
|
|
26521
|
+
}
|
|
26522
|
+
function ensure() {
|
|
26523
|
+
if (inflight)
|
|
26524
|
+
return inflight;
|
|
26525
|
+
if (closed || files.every(fileReady))
|
|
26526
|
+
return Promise.resolve();
|
|
26527
|
+
lastError = "";
|
|
26528
|
+
console.log("[STT] model download start");
|
|
26529
|
+
inflight = run().finally(() => {
|
|
26530
|
+
inflight = null;
|
|
26531
|
+
emit2();
|
|
26532
|
+
});
|
|
26533
|
+
emit2();
|
|
26534
|
+
return inflight;
|
|
26535
|
+
}
|
|
26536
|
+
return {
|
|
26537
|
+
status,
|
|
26538
|
+
ensure,
|
|
26539
|
+
subscribe(listener) {
|
|
26540
|
+
listeners2.add(listener);
|
|
26541
|
+
return () => {
|
|
26542
|
+
listeners2.delete(listener);
|
|
26543
|
+
};
|
|
26544
|
+
},
|
|
26545
|
+
filePath,
|
|
26546
|
+
close() {
|
|
26547
|
+
closed = true;
|
|
26548
|
+
current?.abort();
|
|
26549
|
+
}
|
|
26550
|
+
};
|
|
26551
|
+
}
|
|
26552
|
+
var SENSE_VOICE_MODEL, SENSE_VOICE_TOKENS, SILERO_VAD_MODEL, HF_SENSE_VOICE, MS_SENSE_VOICE, STT_MODEL_FILES, sttModels;
|
|
26553
|
+
var init_models = __esm({
|
|
26554
|
+
"../server/dist/stt/models.js"() {
|
|
26555
|
+
"use strict";
|
|
26556
|
+
init_paths();
|
|
26557
|
+
SENSE_VOICE_MODEL = "sense-voice-zh-en-ja-ko-yue-2024-07-17/model.int8.onnx";
|
|
26558
|
+
SENSE_VOICE_TOKENS = "sense-voice-zh-en-ja-ko-yue-2024-07-17/tokens.txt";
|
|
26559
|
+
SILERO_VAD_MODEL = "silero-vad-v4/silero_vad.onnx";
|
|
26560
|
+
HF_SENSE_VOICE = "https://huggingface.co/csukuangfj/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/resolve/2365baeacb507f821a0c8120fcee3d484dba7a07";
|
|
26561
|
+
MS_SENSE_VOICE = "https://www.modelscope.cn/api/v1/models/pengzhendong/sherpa-onnx-sense-voice-zh-en-ja-ko-yue/repo?Revision=master&FilePath=";
|
|
26562
|
+
STT_MODEL_FILES = [
|
|
26563
|
+
{
|
|
26564
|
+
path: SENSE_VOICE_MODEL,
|
|
26565
|
+
size: 239233841,
|
|
26566
|
+
sha256: "c71f0ce00bec95b07744e116345e33d8cbbe08cef896382cf907bf4b51a2cd51",
|
|
26567
|
+
sources: [
|
|
26568
|
+
{ name: "huggingface", url: `${HF_SENSE_VOICE}/model.int8.onnx` },
|
|
26569
|
+
{ name: "modelscope", url: `${MS_SENSE_VOICE}model.int8.onnx` }
|
|
26570
|
+
]
|
|
26571
|
+
},
|
|
26572
|
+
{
|
|
26573
|
+
path: SENSE_VOICE_TOKENS,
|
|
26574
|
+
size: 315894,
|
|
26575
|
+
sha256: "f449eb28dc567533d7fa59be34e2abca8784f771850c78a47fb731a31429a1dc",
|
|
26576
|
+
sources: [
|
|
26577
|
+
{ name: "huggingface", url: `${HF_SENSE_VOICE}/tokens.txt` },
|
|
26578
|
+
{ name: "modelscope", url: `${MS_SENSE_VOICE}tokens.txt` }
|
|
26579
|
+
]
|
|
26580
|
+
},
|
|
26581
|
+
{
|
|
26582
|
+
path: SILERO_VAD_MODEL,
|
|
26583
|
+
size: 1807522,
|
|
26584
|
+
sha256: "a35ebf52fd3ce5f1469b2a36158dba761bc47b973ea3382b3186ca15b1f5af28",
|
|
26585
|
+
sources: [
|
|
26586
|
+
{ name: "huggingface", url: "https://huggingface.co/csukuangfj/vad/resolve/fba88cd2e921609e7675c3aaf51e0b9b295da4bc/silero_vad.onnx" },
|
|
26587
|
+
{ name: "modelscope", url: "https://www.modelscope.cn/api/v1/models/pengzhendong/silero-vad/repo?Revision=master&FilePath=v4/silero_vad.onnx" }
|
|
26588
|
+
]
|
|
26589
|
+
}
|
|
26590
|
+
];
|
|
26591
|
+
sttModels = createSttModelManager();
|
|
26592
|
+
}
|
|
26593
|
+
});
|
|
26594
|
+
|
|
26595
|
+
// ../server/dist/stt/engine.js
|
|
26596
|
+
import os8 from "node:os";
|
|
26597
|
+
function createSttEngine(deps = {}) {
|
|
26598
|
+
const loadSherpa = deps.loadSherpa ?? defaultLoadSherpa;
|
|
26599
|
+
const models = deps.models ?? sttModels;
|
|
26600
|
+
let sherpa = null;
|
|
26601
|
+
let recognizer = null;
|
|
26602
|
+
let loading = null;
|
|
26603
|
+
const queue = [];
|
|
26604
|
+
let decoding = false;
|
|
26605
|
+
function load() {
|
|
26606
|
+
if (recognizer)
|
|
26607
|
+
return Promise.resolve();
|
|
26608
|
+
loading ??= doLoad().finally(() => {
|
|
26609
|
+
loading = null;
|
|
26610
|
+
});
|
|
26611
|
+
return loading;
|
|
26612
|
+
}
|
|
26613
|
+
async function doLoad() {
|
|
26614
|
+
const t0 = Date.now();
|
|
26615
|
+
try {
|
|
26616
|
+
const m = await loadSherpa();
|
|
26617
|
+
recognizer = await m.OfflineRecognizer.createAsync({
|
|
26618
|
+
featConfig: { sampleRate: SAMPLE_RATE, featureDim: 80 },
|
|
26619
|
+
modelConfig: {
|
|
26620
|
+
// language '' = auto: setting one barely changed results in testing.
|
|
26621
|
+
senseVoice: { model: models.filePath(SENSE_VOICE_MODEL), language: "", useInverseTextNormalization: 1 },
|
|
26622
|
+
tokens: models.filePath(SENSE_VOICE_TOKENS),
|
|
26623
|
+
numThreads: NUM_THREADS,
|
|
26624
|
+
provider: "cpu",
|
|
26625
|
+
debug: 0
|
|
26626
|
+
}
|
|
26627
|
+
});
|
|
26628
|
+
sherpa = m;
|
|
26629
|
+
console.log(`[STT] engine loaded in ${Date.now() - t0}ms (threads=${NUM_THREADS})`);
|
|
26630
|
+
} catch (err) {
|
|
26631
|
+
console.log(`[STT] engine load failed: ${errMsg2(err)}`);
|
|
26632
|
+
throw new SttError("engine", `speech engine unavailable: ${errMsg2(err)}`);
|
|
26633
|
+
}
|
|
26634
|
+
}
|
|
26635
|
+
async function pump() {
|
|
26636
|
+
if (decoding || !recognizer)
|
|
26637
|
+
return;
|
|
26638
|
+
decoding = true;
|
|
26639
|
+
while (queue.length > 0) {
|
|
26640
|
+
const job = queue.shift();
|
|
26641
|
+
try {
|
|
26642
|
+
const stream = recognizer.createStream();
|
|
26643
|
+
stream.acceptWaveform({ samples: job.samples, sampleRate: SAMPLE_RATE });
|
|
26644
|
+
await recognizer.decodeAsync(stream);
|
|
26645
|
+
job.owner.done(job, recognizer.getResult(stream));
|
|
26646
|
+
} catch (err) {
|
|
26647
|
+
job.owner.fail(new SttError("engine", `decode failed: ${errMsg2(err)}`));
|
|
26648
|
+
}
|
|
26649
|
+
}
|
|
26650
|
+
decoding = false;
|
|
26651
|
+
}
|
|
26652
|
+
function open(handlers) {
|
|
26653
|
+
if (!sherpa)
|
|
26654
|
+
throw new SttError("engine", "speech engine not loaded");
|
|
26655
|
+
let vad;
|
|
26656
|
+
try {
|
|
26657
|
+
vad = new sherpa.Vad({
|
|
26658
|
+
sileroVad: {
|
|
26659
|
+
model: models.filePath(SILERO_VAD_MODEL),
|
|
26660
|
+
threshold: VAD_THRESHOLD,
|
|
26661
|
+
minSilenceDuration: VAD_MIN_SILENCE_S,
|
|
26662
|
+
minSpeechDuration: VAD_MIN_SPEECH_S,
|
|
26663
|
+
maxSpeechDuration: VAD_MAX_SPEECH_S,
|
|
26664
|
+
windowSize: VAD_WINDOW
|
|
26665
|
+
},
|
|
26666
|
+
sampleRate: SAMPLE_RATE,
|
|
26667
|
+
numThreads: 1,
|
|
26668
|
+
debug: false
|
|
26669
|
+
}, RING_S);
|
|
26670
|
+
} catch (err) {
|
|
26671
|
+
throw new SttError("engine", `VAD unavailable: ${errMsg2(err)}`);
|
|
26672
|
+
}
|
|
26673
|
+
const ringLen = RING_S * SAMPLE_RATE;
|
|
26674
|
+
const ring = new Float32Array(ringLen);
|
|
26675
|
+
const win = new Float32Array(VAD_WINDOW);
|
|
26676
|
+
let winFill = 0;
|
|
26677
|
+
let total = 0;
|
|
26678
|
+
let carry = null;
|
|
26679
|
+
let prevEnd = 0;
|
|
26680
|
+
let pendingSamples = 0;
|
|
26681
|
+
let inFlight2 = 0;
|
|
26682
|
+
let segments = 0;
|
|
26683
|
+
let decodedSamples = 0;
|
|
26684
|
+
let stopped = false;
|
|
26685
|
+
let drained = null;
|
|
26686
|
+
const checkDrain = () => {
|
|
26687
|
+
if (drained && (stopped || inFlight2 === 0)) {
|
|
26688
|
+
const d = drained;
|
|
26689
|
+
drained = null;
|
|
26690
|
+
d();
|
|
26691
|
+
}
|
|
26692
|
+
};
|
|
26693
|
+
const dropQueued = () => {
|
|
26694
|
+
for (let i = queue.length - 1; i >= 0; i--) {
|
|
26695
|
+
if (queue[i].owner !== owner)
|
|
26696
|
+
continue;
|
|
26697
|
+
pendingSamples -= queue[i].samples.length;
|
|
26698
|
+
inFlight2--;
|
|
26699
|
+
queue.splice(i, 1);
|
|
26700
|
+
}
|
|
26701
|
+
};
|
|
26702
|
+
function failStream(err) {
|
|
26703
|
+
if (stopped)
|
|
26704
|
+
return;
|
|
26705
|
+
stopped = true;
|
|
26706
|
+
dropQueued();
|
|
26707
|
+
handlers.onError(err);
|
|
26708
|
+
checkDrain();
|
|
26709
|
+
}
|
|
26710
|
+
const owner = {
|
|
26711
|
+
done(job, result) {
|
|
26712
|
+
pendingSamples -= job.samples.length;
|
|
26713
|
+
inFlight2--;
|
|
26714
|
+
if (!stopped) {
|
|
26715
|
+
segments++;
|
|
26716
|
+
decodedSamples += job.samples.length;
|
|
26717
|
+
const text6 = (result.text ?? "").trim();
|
|
26718
|
+
if (text6)
|
|
26719
|
+
handlers.onFinal({ start: round3(job.start / SAMPLE_RATE), end: round3(job.end / SAMPLE_RATE), text: text6, lang: (result.lang ?? "").replace(/<\|([^|]*)\|>/g, "$1") });
|
|
26720
|
+
}
|
|
26721
|
+
checkDrain();
|
|
26722
|
+
},
|
|
26723
|
+
fail(err) {
|
|
26724
|
+
inFlight2--;
|
|
26725
|
+
failStream(err);
|
|
26726
|
+
checkDrain();
|
|
26727
|
+
}
|
|
26728
|
+
};
|
|
26729
|
+
function enqueue(segStart, segEnd) {
|
|
26730
|
+
const start3 = Math.max(segStart - PAD_BEFORE_S * SAMPLE_RATE, prevEnd, total - ringLen, 0);
|
|
26731
|
+
const end = Math.min(segEnd + PAD_AFTER_S * SAMPLE_RATE, total);
|
|
26732
|
+
if (end <= start3)
|
|
26733
|
+
return;
|
|
26734
|
+
prevEnd = end;
|
|
26735
|
+
if (queue.length >= MAX_QUEUED_SEGMENTS) {
|
|
26736
|
+
failStream(new SttError("busy", `decode queue full (${MAX_QUEUED_SEGMENTS} segments waiting)`));
|
|
26737
|
+
return;
|
|
26738
|
+
}
|
|
26739
|
+
if ((pendingSamples + end - start3) / SAMPLE_RATE > MAX_PENDING_AUDIO_S) {
|
|
26740
|
+
failStream(new SttError("busy", `more than ${MAX_PENDING_AUDIO_S}s of audio waiting to be decoded`));
|
|
26741
|
+
return;
|
|
26742
|
+
}
|
|
26743
|
+
const samples = new Float32Array(end - start3);
|
|
26744
|
+
for (let i = 0; i < samples.length; i++)
|
|
26745
|
+
samples[i] = ring[(start3 + i) % ringLen];
|
|
26746
|
+
pendingSamples += samples.length;
|
|
26747
|
+
inFlight2++;
|
|
26748
|
+
queue.push({ owner, samples, start: start3, end });
|
|
26749
|
+
void pump();
|
|
26750
|
+
}
|
|
26751
|
+
function drainVad() {
|
|
26752
|
+
while (!stopped && !vad.isEmpty()) {
|
|
26753
|
+
const seg = vad.front(false);
|
|
26754
|
+
vad.pop();
|
|
26755
|
+
enqueue(seg.start, seg.start + seg.samples.length);
|
|
26756
|
+
}
|
|
26757
|
+
}
|
|
26758
|
+
function feed(samples) {
|
|
26759
|
+
vad.acceptWaveform(samples);
|
|
26760
|
+
drainVad();
|
|
26761
|
+
}
|
|
26762
|
+
return {
|
|
26763
|
+
write(pcm) {
|
|
26764
|
+
if (stopped)
|
|
26765
|
+
return;
|
|
26766
|
+
try {
|
|
26767
|
+
const bytes = carry ? Buffer.concat([carry, pcm]) : pcm;
|
|
26768
|
+
const n = bytes.length >> 1;
|
|
26769
|
+
carry = bytes.length & 1 ? Buffer.from(bytes.subarray(bytes.length - 1)) : null;
|
|
26770
|
+
for (let i = 0; i < n && !stopped; i++) {
|
|
26771
|
+
const f = bytes.readInt16LE(i * 2) / 32768;
|
|
26772
|
+
ring[total % ringLen] = f;
|
|
26773
|
+
total++;
|
|
26774
|
+
win[winFill++] = f;
|
|
26775
|
+
if (winFill === VAD_WINDOW) {
|
|
26776
|
+
winFill = 0;
|
|
26777
|
+
feed(win);
|
|
26778
|
+
}
|
|
26779
|
+
}
|
|
26780
|
+
} catch (err) {
|
|
26781
|
+
failStream(new SttError("engine", `VAD failed: ${errMsg2(err)}`));
|
|
26782
|
+
}
|
|
26783
|
+
},
|
|
26784
|
+
end() {
|
|
26785
|
+
if (!stopped) {
|
|
26786
|
+
try {
|
|
26787
|
+
if (winFill > 0) {
|
|
26788
|
+
win.fill(0, winFill);
|
|
26789
|
+
winFill = 0;
|
|
26790
|
+
feed(win);
|
|
26791
|
+
}
|
|
26792
|
+
vad.flush();
|
|
26793
|
+
drainVad();
|
|
26794
|
+
} catch (err) {
|
|
26795
|
+
failStream(new SttError("engine", `VAD failed: ${errMsg2(err)}`));
|
|
26796
|
+
}
|
|
26797
|
+
}
|
|
26798
|
+
return new Promise((resolve2) => {
|
|
26799
|
+
drained = resolve2;
|
|
26800
|
+
checkDrain();
|
|
26801
|
+
});
|
|
26802
|
+
},
|
|
26803
|
+
abort() {
|
|
26804
|
+
stopped = true;
|
|
26805
|
+
dropQueued();
|
|
26806
|
+
checkDrain();
|
|
26807
|
+
},
|
|
26808
|
+
stats: () => ({ segments, audioSeconds: round3(decodedSamples / SAMPLE_RATE) })
|
|
26809
|
+
};
|
|
26810
|
+
}
|
|
26811
|
+
return { load, open };
|
|
26812
|
+
}
|
|
26813
|
+
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;
|
|
26814
|
+
var init_engine = __esm({
|
|
26815
|
+
"../server/dist/stt/engine.js"() {
|
|
26816
|
+
"use strict";
|
|
26817
|
+
init_models();
|
|
26818
|
+
SAMPLE_RATE = 16e3;
|
|
26819
|
+
VAD_WINDOW = 512;
|
|
26820
|
+
VAD_THRESHOLD = 0.5;
|
|
26821
|
+
VAD_MIN_SILENCE_S = 0.5;
|
|
26822
|
+
VAD_MIN_SPEECH_S = 0.25;
|
|
26823
|
+
VAD_MAX_SPEECH_S = 20;
|
|
26824
|
+
PAD_BEFORE_S = 0.3;
|
|
26825
|
+
PAD_AFTER_S = 0.2;
|
|
26826
|
+
RING_S = 30;
|
|
26827
|
+
MAX_QUEUED_SEGMENTS = 32;
|
|
26828
|
+
MAX_PENDING_AUDIO_S = 60;
|
|
26829
|
+
NUM_THREADS = Math.min(4, Math.max(1, Math.floor(os8.availableParallelism() / 2)));
|
|
26830
|
+
SttError = class extends Error {
|
|
26831
|
+
code;
|
|
26832
|
+
constructor(code, message) {
|
|
26833
|
+
super(message);
|
|
26834
|
+
this.code = code;
|
|
26835
|
+
this.name = "SttError";
|
|
26836
|
+
}
|
|
26837
|
+
};
|
|
26838
|
+
defaultLoadSherpa = async () => (await import("sherpa-onnx-node")).default;
|
|
26839
|
+
errMsg2 = (err) => err instanceof Error ? err.message : String(err);
|
|
26840
|
+
round3 = (s) => Math.round(s * 1e3) / 1e3;
|
|
26841
|
+
sttEngine = createSttEngine();
|
|
26842
|
+
}
|
|
26843
|
+
});
|
|
26844
|
+
|
|
26845
|
+
// ../server/dist/routes/stt-ws.js
|
|
26846
|
+
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
26847
|
+
function parseQuery2(req) {
|
|
26848
|
+
const url = req.url ?? "";
|
|
26849
|
+
const i = url.indexOf("?");
|
|
26850
|
+
return new URLSearchParams(i < 0 ? "" : url.slice(i + 1));
|
|
26851
|
+
}
|
|
26852
|
+
function createSttProxy(deps = {}) {
|
|
26853
|
+
const models = deps.models ?? sttModels;
|
|
26854
|
+
const engine = deps.engine ?? sttEngine;
|
|
26855
|
+
const wss2 = new WebSocketServer2({
|
|
26856
|
+
noServer: true,
|
|
26857
|
+
maxPayload: MAX_FRAME_BYTES2,
|
|
26858
|
+
// Same cookie gate as /ws; the extension iframe is same-origin with the
|
|
26859
|
+
// admin, so its WS handshake carries the login cookie.
|
|
26860
|
+
verifyClient: (info, callback) => {
|
|
26861
|
+
if (!isAuthenticated(getTokenFromCookieHeader(info.req.headers.cookie)))
|
|
26862
|
+
return callback(false, 401, "Unauthorized");
|
|
26863
|
+
const ext = getExtension(parseQuery2(info.req).get("ext") ?? "");
|
|
26864
|
+
if (!ext || !ext.capabilities.includes("stt-local"))
|
|
26865
|
+
return callback(false, 403, "Forbidden");
|
|
26866
|
+
callback(true);
|
|
26867
|
+
}
|
|
26868
|
+
});
|
|
26869
|
+
wss2.on("connection", (ws, req) => {
|
|
26870
|
+
const extId = parseQuery2(req).get("ext");
|
|
26871
|
+
const openedAt = Date.now();
|
|
26872
|
+
let closed = false;
|
|
26873
|
+
let failed = false;
|
|
26874
|
+
let ended = false;
|
|
26875
|
+
let stream = null;
|
|
26876
|
+
let unsubscribe = null;
|
|
26877
|
+
ws.on("error", (err) => {
|
|
26878
|
+
console.log(`[STT] client error ext=${extId}: ${err.message}`);
|
|
26879
|
+
});
|
|
26880
|
+
const send2 = (frame) => {
|
|
26881
|
+
if (ws.readyState === ws.OPEN)
|
|
26882
|
+
ws.send(JSON.stringify(frame));
|
|
26883
|
+
};
|
|
26884
|
+
const fail2 = (code, message) => {
|
|
26885
|
+
if (failed || closed)
|
|
26886
|
+
return;
|
|
26887
|
+
failed = true;
|
|
26888
|
+
console.log(`[STT] stream error ext=${extId} code=${code}`);
|
|
26889
|
+
send2({ type: "error", code, message });
|
|
26890
|
+
ws.close(1011, code);
|
|
26891
|
+
};
|
|
26892
|
+
async function startEngine() {
|
|
26893
|
+
try {
|
|
26894
|
+
await engine.load();
|
|
26895
|
+
if (closed || failed || ended)
|
|
26896
|
+
return;
|
|
26897
|
+
stream = engine.open({
|
|
26898
|
+
// Recognized text goes to the client only — never to the log.
|
|
26899
|
+
onFinal: (f) => send2({ type: "final", ...f }),
|
|
26900
|
+
onError: (err) => fail2(err.code, err.message)
|
|
26901
|
+
});
|
|
26902
|
+
} catch (err) {
|
|
26903
|
+
if (err instanceof SttError)
|
|
26904
|
+
fail2(err.code, err.message);
|
|
26905
|
+
else
|
|
26906
|
+
fail2("io", err instanceof Error ? err.message : String(err));
|
|
26907
|
+
return;
|
|
26908
|
+
}
|
|
26909
|
+
if (closed || failed)
|
|
26910
|
+
return;
|
|
26911
|
+
send2({ type: "ready" });
|
|
26912
|
+
}
|
|
26913
|
+
const onModel = (s) => {
|
|
26914
|
+
if (closed || failed)
|
|
26915
|
+
return;
|
|
26916
|
+
send2({ type: "model", ...s });
|
|
26917
|
+
if (s.state === "ready" || s.state === "error") {
|
|
26918
|
+
unsubscribe?.();
|
|
26919
|
+
unsubscribe = null;
|
|
26920
|
+
}
|
|
26921
|
+
if (s.state === "ready")
|
|
26922
|
+
void startEngine();
|
|
26923
|
+
else if (s.state === "error")
|
|
26924
|
+
fail2("model", s.message ?? "model download failed");
|
|
26925
|
+
};
|
|
26926
|
+
console.log(`[STT] stream open ext=${extId}`);
|
|
26927
|
+
ws.on("message", (data, isBinary) => {
|
|
26928
|
+
if (ended || failed)
|
|
26929
|
+
return;
|
|
26930
|
+
if (isBinary) {
|
|
26931
|
+
if (!stream)
|
|
26932
|
+
return;
|
|
26933
|
+
stream.write(Buffer.isBuffer(data) ? data : Buffer.concat(Array.isArray(data) ? data : [Buffer.from(data)]));
|
|
26934
|
+
return;
|
|
26935
|
+
}
|
|
26936
|
+
let msg;
|
|
26937
|
+
try {
|
|
26938
|
+
msg = JSON.parse(data.toString());
|
|
26939
|
+
} catch {
|
|
26940
|
+
msg = null;
|
|
26941
|
+
}
|
|
26942
|
+
if (msg?.type !== "end") {
|
|
26943
|
+
fail2("bad-request", 'expected binary PCM or {"type":"end"}');
|
|
26944
|
+
return;
|
|
26945
|
+
}
|
|
26946
|
+
ended = true;
|
|
26947
|
+
if (!stream) {
|
|
26948
|
+
ws.close(1e3);
|
|
26949
|
+
return;
|
|
26950
|
+
}
|
|
26951
|
+
void stream.end().then(() => {
|
|
26952
|
+
if (!closed && !failed)
|
|
26953
|
+
ws.close(1e3);
|
|
26954
|
+
});
|
|
26955
|
+
});
|
|
26956
|
+
ws.on("close", () => {
|
|
26957
|
+
closed = true;
|
|
26958
|
+
unsubscribe?.();
|
|
26959
|
+
unsubscribe = null;
|
|
26960
|
+
stream?.abort();
|
|
26961
|
+
const st = stream?.stats();
|
|
26962
|
+
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` : ""}`);
|
|
26963
|
+
});
|
|
26964
|
+
const initial = models.status();
|
|
26965
|
+
if (initial.state === "ready") {
|
|
26966
|
+
onModel(initial);
|
|
26967
|
+
return;
|
|
26968
|
+
}
|
|
26969
|
+
let pushed = false;
|
|
26970
|
+
unsubscribe = models.subscribe((s) => {
|
|
26971
|
+
pushed = true;
|
|
26972
|
+
onModel(s);
|
|
26973
|
+
});
|
|
26974
|
+
void models.ensure();
|
|
26975
|
+
if (!pushed)
|
|
26976
|
+
onModel(models.status());
|
|
26977
|
+
});
|
|
26978
|
+
return {
|
|
26979
|
+
handleUpgrade(req, socket, head) {
|
|
26980
|
+
wss2.handleUpgrade(req, socket, head, (ws) => wss2.emit("connection", ws, req));
|
|
26981
|
+
},
|
|
26982
|
+
close() {
|
|
26983
|
+
models.close();
|
|
26984
|
+
for (const client of wss2.clients)
|
|
26985
|
+
client.close(1001, "Server shutting down");
|
|
26986
|
+
wss2.close();
|
|
26987
|
+
}
|
|
26988
|
+
};
|
|
26989
|
+
}
|
|
26990
|
+
var STT_PATH, MAX_FRAME_BYTES2;
|
|
26991
|
+
var init_stt_ws = __esm({
|
|
26992
|
+
"../server/dist/routes/stt-ws.js"() {
|
|
26993
|
+
"use strict";
|
|
26994
|
+
init_auth();
|
|
26995
|
+
init_registry2();
|
|
26996
|
+
init_models();
|
|
26997
|
+
init_engine();
|
|
26998
|
+
STT_PATH = "/api/stt/stream";
|
|
26999
|
+
MAX_FRAME_BYTES2 = 1024 * 1024;
|
|
27000
|
+
}
|
|
27001
|
+
});
|
|
27002
|
+
|
|
26220
27003
|
// ../server/dist/jupyter/manager.js
|
|
26221
27004
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
26222
27005
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
26223
|
-
import
|
|
27006
|
+
import fs61 from "node:fs";
|
|
26224
27007
|
import net from "node:net";
|
|
26225
|
-
import
|
|
26226
|
-
import
|
|
27008
|
+
import os9 from "node:os";
|
|
27009
|
+
import path69 from "node:path";
|
|
26227
27010
|
import { WebSocket } from "ws";
|
|
26228
27011
|
function resolveExecutable(configured) {
|
|
26229
27012
|
const isExe = (p) => {
|
|
26230
27013
|
try {
|
|
26231
|
-
|
|
26232
|
-
return
|
|
27014
|
+
fs61.accessSync(p, fs61.constants.X_OK);
|
|
27015
|
+
return fs61.statSync(p).isFile();
|
|
26233
27016
|
} catch {
|
|
26234
27017
|
return false;
|
|
26235
27018
|
}
|
|
@@ -26237,9 +27020,9 @@ function resolveExecutable(configured) {
|
|
|
26237
27020
|
if (configured)
|
|
26238
27021
|
return isExe(configured) ? configured : null;
|
|
26239
27022
|
const name = process.platform === "win32" ? "jupyter.exe" : "jupyter";
|
|
26240
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26241
|
-
if (dir && isExe(
|
|
26242
|
-
return
|
|
27023
|
+
for (const dir of (process.env.PATH ?? "").split(path69.delimiter)) {
|
|
27024
|
+
if (dir && isExe(path69.join(dir, name)))
|
|
27025
|
+
return path69.join(dir, name);
|
|
26243
27026
|
}
|
|
26244
27027
|
return null;
|
|
26245
27028
|
}
|
|
@@ -26267,9 +27050,9 @@ function createJupyterManager(deps = {}) {
|
|
|
26267
27050
|
let err = "";
|
|
26268
27051
|
let child;
|
|
26269
27052
|
try {
|
|
26270
|
-
child = spawnFn(exe, ["server", "--version"], { cwd:
|
|
27053
|
+
child = spawnFn(exe, ["server", "--version"], { cwd: os9.homedir(), env: cleanChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
26271
27054
|
} catch (e) {
|
|
26272
|
-
resolve2({ ok: false, error:
|
|
27055
|
+
resolve2({ ok: false, error: errMsg3(e) });
|
|
26273
27056
|
return;
|
|
26274
27057
|
}
|
|
26275
27058
|
const timer2 = setTimeout(() => child.kill(), PROBE_TIMEOUT_MS);
|
|
@@ -26350,7 +27133,7 @@ function createJupyterManager(deps = {}) {
|
|
|
26350
27133
|
"--ServerApp.ip=127.0.0.1",
|
|
26351
27134
|
`--ServerApp.port=${port}`,
|
|
26352
27135
|
"--ServerApp.port_retries=0",
|
|
26353
|
-
`--ServerApp.root_dir=${
|
|
27136
|
+
`--ServerApp.root_dir=${path69.parse(process.cwd()).root}`,
|
|
26354
27137
|
"--ServerApp.terminals_enabled=False",
|
|
26355
27138
|
"--ServerApp.shutdown_no_activity_timeout=600",
|
|
26356
27139
|
"--MappingKernelManager.cull_idle_timeout=3600",
|
|
@@ -26360,7 +27143,7 @@ function createJupyterManager(deps = {}) {
|
|
|
26360
27143
|
// its terminal does too, and kernels match the terminal.
|
|
26361
27144
|
...process.getuid?.() === 0 ? ["--ServerApp.allow_root=True"] : []
|
|
26362
27145
|
];
|
|
26363
|
-
const child = spawnFn(exe, args, { cwd:
|
|
27146
|
+
const child = spawnFn(exe, args, { cwd: os9.homedir(), env: { ...cleanChildEnv(), JUPYTER_TOKEN: token }, stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
|
|
26364
27147
|
proc = child;
|
|
26365
27148
|
let tail = "";
|
|
26366
27149
|
child.stderr?.on("data", (d) => {
|
|
@@ -26428,7 +27211,7 @@ function createJupyterManager(deps = {}) {
|
|
|
26428
27211
|
if (kernels.get(id) !== k || k.sockets.size > 0)
|
|
26429
27212
|
return;
|
|
26430
27213
|
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: ${
|
|
27214
|
+
kernelOp(id, "delete").catch((e) => console.log(`[Jupyter] kernel ${id} shutdown failed: ${errMsg3(e)}`));
|
|
26432
27215
|
}, KERNEL_GRACE_MS);
|
|
26433
27216
|
k.grace.unref?.();
|
|
26434
27217
|
}
|
|
@@ -26538,7 +27321,7 @@ function createJupyterManager(deps = {}) {
|
|
|
26538
27321
|
}
|
|
26539
27322
|
};
|
|
26540
27323
|
}
|
|
26541
|
-
var JUPYTER_HINT, KERNEL_GRACE_MS, START_TIMEOUT_MS, PROBE_TIMEOUT_MS, STOP_TIMEOUT_MS, JupyterError,
|
|
27324
|
+
var JUPYTER_HINT, KERNEL_GRACE_MS, START_TIMEOUT_MS, PROBE_TIMEOUT_MS, STOP_TIMEOUT_MS, JupyterError, errMsg3, lastLine;
|
|
26542
27325
|
var init_manager = __esm({
|
|
26543
27326
|
"../server/dist/jupyter/manager.js"() {
|
|
26544
27327
|
"use strict";
|
|
@@ -26558,16 +27341,16 @@ var init_manager = __esm({
|
|
|
26558
27341
|
this.hint = hint;
|
|
26559
27342
|
}
|
|
26560
27343
|
};
|
|
26561
|
-
|
|
27344
|
+
errMsg3 = (err) => err instanceof Error ? err.message : String(err);
|
|
26562
27345
|
lastLine = (s) => s.trim().split("\n").pop()?.trim() ?? "";
|
|
26563
27346
|
}
|
|
26564
27347
|
});
|
|
26565
27348
|
|
|
26566
27349
|
// ../server/dist/routes/jupyter.js
|
|
26567
|
-
import
|
|
26568
|
-
import
|
|
27350
|
+
import fs62 from "node:fs/promises";
|
|
27351
|
+
import path70 from "node:path";
|
|
26569
27352
|
import { Hono as Hono15 } from "hono";
|
|
26570
|
-
import { WebSocketServer as
|
|
27353
|
+
import { WebSocketServer as WebSocketServer3 } from "ws";
|
|
26571
27354
|
function fail(c, err) {
|
|
26572
27355
|
if (err instanceof JupyterError)
|
|
26573
27356
|
return c.json({ error: err.message, ...err.hint ? { hint: err.hint } : {} }, err.status);
|
|
@@ -26596,8 +27379,8 @@ function createJupyterRoutes(jm) {
|
|
|
26596
27379
|
return c.json({ error: "Project not found" }, 400);
|
|
26597
27380
|
if (!validatePath(nbPath, projectPath))
|
|
26598
27381
|
return c.json({ error: "Path outside the workspace" }, 400);
|
|
26599
|
-
const cwd = await
|
|
26600
|
-
if (!cwd || !(await
|
|
27382
|
+
const cwd = await fs62.realpath(path70.dirname(path70.resolve(projectPath, nbPath))).catch(() => null);
|
|
27383
|
+
if (!cwd || !(await fs62.stat(cwd)).isDirectory())
|
|
26601
27384
|
return c.json({ error: "Notebook directory not found" }, 400);
|
|
26602
27385
|
try {
|
|
26603
27386
|
return c.json(await jm.startKernel(name, cwd));
|
|
@@ -26641,9 +27424,9 @@ function queryOf(req) {
|
|
|
26641
27424
|
return new URLSearchParams(i < 0 ? "" : url.slice(i + 1));
|
|
26642
27425
|
}
|
|
26643
27426
|
function createJupyterChannels(jm) {
|
|
26644
|
-
const wss2 = new
|
|
27427
|
+
const wss2 = new WebSocketServer3({
|
|
26645
27428
|
noServer: true,
|
|
26646
|
-
maxPayload:
|
|
27429
|
+
maxPayload: MAX_FRAME_BYTES3,
|
|
26647
27430
|
// The client speaks the legacy JSON protocol; never echo a subprotocol.
|
|
26648
27431
|
handleProtocols: () => false,
|
|
26649
27432
|
// Same cookie gate as /ws and the transcribe proxy.
|
|
@@ -26737,7 +27520,7 @@ function createJupyterChannels(jm) {
|
|
|
26737
27520
|
}
|
|
26738
27521
|
};
|
|
26739
27522
|
}
|
|
26740
|
-
var JUPYTER_CHANNELS_PATH, KERNEL_NAME_RE, SESSION_ID_RE,
|
|
27523
|
+
var JUPYTER_CHANNELS_PATH, KERNEL_NAME_RE, SESSION_ID_RE, MAX_FRAME_BYTES3;
|
|
26741
27524
|
var init_jupyter = __esm({
|
|
26742
27525
|
"../server/dist/routes/jupyter.js"() {
|
|
26743
27526
|
"use strict";
|
|
@@ -26747,7 +27530,7 @@ var init_jupyter = __esm({
|
|
|
26747
27530
|
JUPYTER_CHANNELS_PATH = "/api/jupyter/channels";
|
|
26748
27531
|
KERNEL_NAME_RE = /^[\w.-]{1,128}$/;
|
|
26749
27532
|
SESSION_ID_RE = /^[\w-]{1,128}$/;
|
|
26750
|
-
|
|
27533
|
+
MAX_FRAME_BYTES3 = 64 * 1024 * 1024;
|
|
26751
27534
|
}
|
|
26752
27535
|
});
|
|
26753
27536
|
|
|
@@ -26768,8 +27551,8 @@ var init_upgrade_router = __esm({
|
|
|
26768
27551
|
});
|
|
26769
27552
|
|
|
26770
27553
|
// ../server/dist/ws/git-dir-watcher.js
|
|
26771
|
-
import
|
|
26772
|
-
import
|
|
27554
|
+
import fs63 from "node:fs";
|
|
27555
|
+
import path71 from "node:path";
|
|
26773
27556
|
var GitDirWatcher;
|
|
26774
27557
|
var init_git_dir_watcher = __esm({
|
|
26775
27558
|
"../server/dist/ws/git-dir-watcher.js"() {
|
|
@@ -26793,8 +27576,8 @@ var init_git_dir_watcher = __esm({
|
|
|
26793
27576
|
return;
|
|
26794
27577
|
this.stop();
|
|
26795
27578
|
this.workspaceRoot = workspaceRoot;
|
|
26796
|
-
const gitDir =
|
|
26797
|
-
if (
|
|
27579
|
+
const gitDir = path71.join(workspaceRoot, ".git");
|
|
27580
|
+
if (fs63.existsSync(gitDir)) {
|
|
26798
27581
|
this.watchGitDir(gitDir);
|
|
26799
27582
|
} else {
|
|
26800
27583
|
this.watchParentForGit(workspaceRoot);
|
|
@@ -26803,7 +27586,7 @@ var init_git_dir_watcher = __esm({
|
|
|
26803
27586
|
/** Watch the `.git` dir for index/HEAD changes (steady state). */
|
|
26804
27587
|
watchGitDir(gitDir) {
|
|
26805
27588
|
try {
|
|
26806
|
-
this.watcher =
|
|
27589
|
+
this.watcher = fs63.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
26807
27590
|
this.handleGitEvent(filename);
|
|
26808
27591
|
});
|
|
26809
27592
|
this.watcher.on("error", (err) => {
|
|
@@ -26822,7 +27605,7 @@ var init_git_dir_watcher = __esm({
|
|
|
26822
27605
|
* Exists solely during the non-repo window; upgraded away on first `.git`. */
|
|
26823
27606
|
watchParentForGit(workspaceRoot) {
|
|
26824
27607
|
try {
|
|
26825
|
-
this.watcher =
|
|
27608
|
+
this.watcher = fs63.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
26826
27609
|
this.handleParentEvent(filename);
|
|
26827
27610
|
});
|
|
26828
27611
|
this.watcher.on("error", (err) => {
|
|
@@ -26858,8 +27641,8 @@ var init_git_dir_watcher = __esm({
|
|
|
26858
27641
|
const root = this.workspaceRoot;
|
|
26859
27642
|
if (!root)
|
|
26860
27643
|
return;
|
|
26861
|
-
const gitDir =
|
|
26862
|
-
if (!
|
|
27644
|
+
const gitDir = path71.join(root, ".git");
|
|
27645
|
+
if (!fs63.existsSync(gitDir))
|
|
26863
27646
|
return;
|
|
26864
27647
|
this.closeWatcher();
|
|
26865
27648
|
this.watchGitDir(gitDir);
|
|
@@ -27277,7 +28060,7 @@ var init_terminal_manager = __esm({
|
|
|
27277
28060
|
});
|
|
27278
28061
|
|
|
27279
28062
|
// ../server/dist/ws/handler.js
|
|
27280
|
-
import
|
|
28063
|
+
import path72 from "node:path";
|
|
27281
28064
|
function setupWebSocketHandler(deps) {
|
|
27282
28065
|
const { wss: wss2, registry: registry2 } = deps;
|
|
27283
28066
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -27300,7 +28083,7 @@ function setupWebSocketHandler(deps) {
|
|
|
27300
28083
|
ackedChatIds.add(id);
|
|
27301
28084
|
}
|
|
27302
28085
|
function resolveProjectPath2(projectId) {
|
|
27303
|
-
if (
|
|
28086
|
+
if (path72.isAbsolute(projectId))
|
|
27304
28087
|
return projectId;
|
|
27305
28088
|
return null;
|
|
27306
28089
|
}
|
|
@@ -27901,11 +28684,11 @@ var init_handler2 = __esm({
|
|
|
27901
28684
|
import Database8 from "better-sqlite3";
|
|
27902
28685
|
import { drizzle as drizzle5 } from "drizzle-orm/better-sqlite3";
|
|
27903
28686
|
import { sqliteTable as sqliteTable5, text as text5, integer as integer5 } from "drizzle-orm/sqlite-core";
|
|
27904
|
-
import
|
|
27905
|
-
import
|
|
28687
|
+
import path73 from "node:path";
|
|
28688
|
+
import fs64 from "node:fs";
|
|
27906
28689
|
function createCronDb(globalDir2) {
|
|
27907
|
-
|
|
27908
|
-
const dbPath =
|
|
28690
|
+
fs64.mkdirSync(globalDir2, { recursive: true });
|
|
28691
|
+
const dbPath = path73.join(globalDir2, "cron.db");
|
|
27909
28692
|
const sqlite = new Database8(dbPath);
|
|
27910
28693
|
sqlite.pragma("journal_mode = WAL");
|
|
27911
28694
|
sqlite.exec(CREATE_SQL5);
|
|
@@ -28015,7 +28798,12 @@ var init_cron_db = __esm({
|
|
|
28015
28798
|
/** Session this run's cli was spawned on (`-s`). The orphan sweep's
|
|
28016
28799
|
* fingerprint — a stateless job's per-run id can't be re-derived from
|
|
28017
28800
|
* the job row. NULL on skipped rows and rows from before the column. */
|
|
28018
|
-
sessionId: text5("session_id")
|
|
28801
|
+
sessionId: text5("session_id"),
|
|
28802
|
+
/** Workspace the run fired against (the job's at fire time) — where a
|
|
28803
|
+
* stateless run's session lives, even after the job moves workspace or is
|
|
28804
|
+
* deleted. NULL on rows from before the column whose job was already gone
|
|
28805
|
+
* (deleteRuns falls back to the job row). */
|
|
28806
|
+
workspacePath: text5("workspace_path")
|
|
28019
28807
|
});
|
|
28020
28808
|
CREATE_SQL5 = `
|
|
28021
28809
|
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
@@ -28057,7 +28845,8 @@ CREATE TABLE IF NOT EXISTS cron_runs (
|
|
|
28057
28845
|
log_path TEXT,
|
|
28058
28846
|
dispatch_results TEXT,
|
|
28059
28847
|
pid INTEGER,
|
|
28060
|
-
session_id TEXT
|
|
28848
|
+
session_id TEXT,
|
|
28849
|
+
workspace_path TEXT
|
|
28061
28850
|
);
|
|
28062
28851
|
CREATE INDEX IF NOT EXISTS idx_cron_runs_job ON cron_runs(job_id, started_at);
|
|
28063
28852
|
CREATE INDEX IF NOT EXISTS idx_cron_runs_status ON cron_runs(status);
|
|
@@ -28098,6 +28887,15 @@ CREATE INDEX IF NOT EXISTS idx_cron_runs_status ON cron_runs(status);
|
|
|
28098
28887
|
addColumnIfMissing(s, "cron_jobs", "working_dir", "TEXT");
|
|
28099
28888
|
addColumnIfMissing(s, "cron_jobs", "stateless", "INTEGER NOT NULL DEFAULT 0");
|
|
28100
28889
|
addColumnIfMissing(s, "cron_runs", "session_id", "TEXT");
|
|
28890
|
+
},
|
|
28891
|
+
// v6: `cron_runs.workspace_path` — the workspace a run's session is in.
|
|
28892
|
+
// Backfilled from the job row; runs of an already-deleted job stay NULL.
|
|
28893
|
+
(s) => {
|
|
28894
|
+
addColumnIfMissing(s, "cron_runs", "workspace_path", "TEXT");
|
|
28895
|
+
s.exec(`
|
|
28896
|
+
UPDATE cron_runs SET workspace_path = (SELECT workspace_path FROM cron_jobs WHERE cron_jobs.id = cron_runs.job_id)
|
|
28897
|
+
WHERE workspace_path IS NULL
|
|
28898
|
+
`);
|
|
28101
28899
|
}
|
|
28102
28900
|
];
|
|
28103
28901
|
_cronDb = null;
|
|
@@ -28105,9 +28903,9 @@ CREATE INDEX IF NOT EXISTS idx_cron_runs_status ON cron_runs(status);
|
|
|
28105
28903
|
});
|
|
28106
28904
|
|
|
28107
28905
|
// ../server/dist/a2a/exposure.js
|
|
28108
|
-
import
|
|
28109
|
-
import
|
|
28110
|
-
import
|
|
28906
|
+
import fs65 from "node:fs";
|
|
28907
|
+
import path74 from "node:path";
|
|
28908
|
+
import crypto7 from "node:crypto";
|
|
28111
28909
|
function samePath(a, b, platform = process.platform) {
|
|
28112
28910
|
if (platform !== "win32")
|
|
28113
28911
|
return a === b;
|
|
@@ -28119,7 +28917,7 @@ function realBase() {
|
|
|
28119
28917
|
if (baseCache?.raw !== raw) {
|
|
28120
28918
|
let real = null;
|
|
28121
28919
|
try {
|
|
28122
|
-
real =
|
|
28920
|
+
real = fs65.realpathSync(raw);
|
|
28123
28921
|
} catch {
|
|
28124
28922
|
console.warn(`[A2A] base dir ${raw} not found \u2014 nothing is exposed`);
|
|
28125
28923
|
}
|
|
@@ -28144,14 +28942,14 @@ function resolveExposedWorkspace(base, rel) {
|
|
|
28144
28942
|
}
|
|
28145
28943
|
let real;
|
|
28146
28944
|
try {
|
|
28147
|
-
real =
|
|
28945
|
+
real = fs65.realpathSync(path74.join(base, ...segs));
|
|
28148
28946
|
} catch {
|
|
28149
28947
|
return null;
|
|
28150
28948
|
}
|
|
28151
|
-
if (!real.startsWith(base.endsWith(
|
|
28949
|
+
if (!real.startsWith(base.endsWith(path74.sep) ? base : base + path74.sep))
|
|
28152
28950
|
return null;
|
|
28153
28951
|
try {
|
|
28154
|
-
if (!
|
|
28952
|
+
if (!fs65.statSync(path74.join(real, CARD_FILE)).isFile())
|
|
28155
28953
|
return null;
|
|
28156
28954
|
} catch {
|
|
28157
28955
|
return null;
|
|
@@ -28166,14 +28964,14 @@ function publicOrigin(c) {
|
|
|
28166
28964
|
return `${proto}://${c.req.header("host") ?? url.host}`;
|
|
28167
28965
|
}
|
|
28168
28966
|
function buildCard(workspace, interfaceUrl, caps) {
|
|
28169
|
-
const raw = JSON.parse(
|
|
28967
|
+
const raw = JSON.parse(fs65.readFileSync(path74.join(workspace, CARD_FILE), "utf8"));
|
|
28170
28968
|
if (typeof raw.name !== "string" || typeof raw.description !== "string" || !Array.isArray(raw.skills)) {
|
|
28171
28969
|
throw new Error('agent-card.json needs string "name", string "description" and array "skills"');
|
|
28172
28970
|
}
|
|
28173
28971
|
return {
|
|
28174
28972
|
name: raw.name,
|
|
28175
28973
|
description: raw.description,
|
|
28176
|
-
version: typeof raw.version === "string" ? raw.version : "1.6.
|
|
28974
|
+
version: typeof raw.version === "string" ? raw.version : "1.6.6",
|
|
28177
28975
|
supportedInterfaces: [{ url: interfaceUrl, protocolBinding: "JSONRPC", protocolVersion: A2A_VERSION }],
|
|
28178
28976
|
capabilities: { streaming: caps.streaming, pushNotifications: true, extendedAgentCard: false },
|
|
28179
28977
|
...caps.tokenAuth === false ? {} : {
|
|
@@ -28189,7 +28987,7 @@ function buildCard(workspace, interfaceUrl, caps) {
|
|
|
28189
28987
|
};
|
|
28190
28988
|
}
|
|
28191
28989
|
function etagOf(body) {
|
|
28192
|
-
return `"${
|
|
28990
|
+
return `W/"${crypto7.createHash("sha1").update(body).digest("base64url")}"`;
|
|
28193
28991
|
}
|
|
28194
28992
|
var CARD_FILE, IO_MODES, baseCache, homeStrategies;
|
|
28195
28993
|
var init_exposure = __esm({
|
|
@@ -28200,7 +28998,7 @@ var init_exposure = __esm({
|
|
|
28200
28998
|
init_channel_db();
|
|
28201
28999
|
init_media_store();
|
|
28202
29000
|
init_wire();
|
|
28203
|
-
CARD_FILE =
|
|
29001
|
+
CARD_FILE = path74.join(".halo", "agent-card.json");
|
|
28204
29002
|
IO_MODES = ["text/plain", ...VISION_IMAGE_MIME_TYPES];
|
|
28205
29003
|
baseCache = null;
|
|
28206
29004
|
homeStrategies = {
|
|
@@ -28214,7 +29012,7 @@ var init_exposure = __esm({
|
|
|
28214
29012
|
return { ok: false, status: 404 };
|
|
28215
29013
|
let bound;
|
|
28216
29014
|
try {
|
|
28217
|
-
bound =
|
|
29015
|
+
bound = fs65.realpathSync(auth.account.workspacePath);
|
|
28218
29016
|
} catch {
|
|
28219
29017
|
return { ok: false, status: 404 };
|
|
28220
29018
|
}
|
|
@@ -28232,7 +29030,7 @@ var init_exposure = __esm({
|
|
|
28232
29030
|
// ../server/dist/a2a/routes.js
|
|
28233
29031
|
import { Hono as Hono16 } from "hono";
|
|
28234
29032
|
import { streamSSE } from "hono/streaming";
|
|
28235
|
-
import
|
|
29033
|
+
import crypto8 from "node:crypto";
|
|
28236
29034
|
function str(v) {
|
|
28237
29035
|
return typeof v === "string" ? v : "";
|
|
28238
29036
|
}
|
|
@@ -28342,7 +29140,7 @@ async function dispatchMessage(x, p) {
|
|
|
28342
29140
|
}
|
|
28343
29141
|
const newSession = !sessionId;
|
|
28344
29142
|
if (newSession)
|
|
28345
|
-
sessionId = `${sessionPrefixFor(x.caller)}${Date.now().toString(36)}${
|
|
29143
|
+
sessionId = `${sessionPrefixFor(x.caller)}${Date.now().toString(36)}${crypto8.randomBytes(3).toString("hex")}`;
|
|
28346
29144
|
const followUp = task !== null;
|
|
28347
29145
|
if (!task) {
|
|
28348
29146
|
try {
|
|
@@ -28546,7 +29344,7 @@ function createA2ARoutes(deps) {
|
|
|
28546
29344
|
const etag = etagOf(body);
|
|
28547
29345
|
c.header("Cache-Control", "private, max-age=300");
|
|
28548
29346
|
c.header("ETag", etag);
|
|
28549
|
-
if (c.req.header("if-none-match")
|
|
29347
|
+
if (etagMatches(c.req.header("if-none-match"), etag))
|
|
28550
29348
|
return c.body(null, 304);
|
|
28551
29349
|
return c.body(body, 200, { "Content-Type": "application/json" });
|
|
28552
29350
|
});
|
|
@@ -28621,6 +29419,7 @@ var init_routes = __esm({
|
|
|
28621
29419
|
init_commands2();
|
|
28622
29420
|
init_accounts();
|
|
28623
29421
|
init_session_prefix();
|
|
29422
|
+
init_etag();
|
|
28624
29423
|
init_url_policy();
|
|
28625
29424
|
init_files();
|
|
28626
29425
|
init_exposure();
|
|
@@ -28656,17 +29455,17 @@ var init_routes = __esm({
|
|
|
28656
29455
|
});
|
|
28657
29456
|
|
|
28658
29457
|
// ../server/dist/a2a/agentcore.js
|
|
28659
|
-
import
|
|
28660
|
-
import
|
|
28661
|
-
import
|
|
28662
|
-
import
|
|
29458
|
+
import fs66 from "node:fs";
|
|
29459
|
+
import os10 from "node:os";
|
|
29460
|
+
import path75 from "node:path";
|
|
29461
|
+
import crypto9 from "node:crypto";
|
|
28663
29462
|
import { Hono as Hono17 } from "hono";
|
|
28664
29463
|
function readLease(file) {
|
|
28665
29464
|
let raw;
|
|
28666
29465
|
let mtimeMs;
|
|
28667
29466
|
try {
|
|
28668
|
-
raw =
|
|
28669
|
-
mtimeMs =
|
|
29467
|
+
raw = fs66.readFileSync(file, "utf8");
|
|
29468
|
+
mtimeMs = fs66.statSync(file).mtimeMs;
|
|
28670
29469
|
} catch (err) {
|
|
28671
29470
|
if (err.code === "ENOENT")
|
|
28672
29471
|
return null;
|
|
@@ -28680,16 +29479,16 @@ function readLease(file) {
|
|
|
28680
29479
|
}
|
|
28681
29480
|
}
|
|
28682
29481
|
function leaseJson(owner, now) {
|
|
28683
|
-
return JSON.stringify({ owner, host:
|
|
29482
|
+
return JSON.stringify({ owner, host: os10.hostname(), pid: process.pid, heartbeatAt: now });
|
|
28684
29483
|
}
|
|
28685
29484
|
function writeLease(file, owner, now) {
|
|
28686
29485
|
const tmp = `${file}.${owner}.tmp`;
|
|
28687
|
-
|
|
28688
|
-
|
|
29486
|
+
fs66.writeFileSync(tmp, leaseJson(owner, now));
|
|
29487
|
+
fs66.renameSync(tmp, file);
|
|
28689
29488
|
}
|
|
28690
29489
|
function tryAcquireLease(file, owner, now = Date.now()) {
|
|
28691
29490
|
try {
|
|
28692
|
-
|
|
29491
|
+
fs66.writeFileSync(file, leaseJson(owner, now), { flag: "wx" });
|
|
28693
29492
|
return "held";
|
|
28694
29493
|
} catch (err) {
|
|
28695
29494
|
if (err.code !== "EEXIST")
|
|
@@ -28717,7 +29516,7 @@ function heartbeatLease(file, owner, now = Date.now()) {
|
|
|
28717
29516
|
function releaseLease(file, owner) {
|
|
28718
29517
|
try {
|
|
28719
29518
|
if (readLease(file)?.body?.owner === owner)
|
|
28720
|
-
|
|
29519
|
+
fs66.unlinkSync(file);
|
|
28721
29520
|
} catch {
|
|
28722
29521
|
}
|
|
28723
29522
|
}
|
|
@@ -28739,7 +29538,7 @@ function upstreamStrategies(workspace, accessLevel) {
|
|
|
28739
29538
|
function seedAgentCard(workspace) {
|
|
28740
29539
|
const card = { name: "Halo", description: "Halo multi-agent workspace on Amazon Bedrock AgentCore. Send a task as plain text.", skills: [] };
|
|
28741
29540
|
try {
|
|
28742
|
-
|
|
29541
|
+
fs66.writeFileSync(path75.join(workspace, CARD_FILE), `${JSON.stringify(card, null, 2)}
|
|
28743
29542
|
`, { flag: "wx" });
|
|
28744
29543
|
return true;
|
|
28745
29544
|
} catch (err) {
|
|
@@ -28787,16 +29586,16 @@ function createAgentCoreA2ARoutes(deps) {
|
|
|
28787
29586
|
return app2;
|
|
28788
29587
|
}
|
|
28789
29588
|
function createAgentCoreA2A(registry2, rawWorkspace, onLost) {
|
|
28790
|
-
const workspace =
|
|
28791
|
-
const haloDir =
|
|
29589
|
+
const workspace = fs66.realpathSync(rawWorkspace);
|
|
29590
|
+
const haloDir = path75.join(workspace, ".halo");
|
|
28792
29591
|
if (!config.a2a.publicUrl)
|
|
28793
29592
|
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
29593
|
const accessLevel = upstreamAccessLevel();
|
|
28795
|
-
const lease = new WorkspaceLease(
|
|
29594
|
+
const lease = new WorkspaceLease(path75.join(haloDir, LEASE_FILE), {
|
|
28796
29595
|
onAcquired() {
|
|
28797
29596
|
setA2ADb(createA2ADb(haloDir));
|
|
28798
29597
|
setRunsDb(createRunsDb(haloDir));
|
|
28799
|
-
|
|
29598
|
+
fs66.rmSync(path75.join(haloDir, RUNTIME_LOCK_FILE), { force: true });
|
|
28800
29599
|
registry2.getOrCreate(workspace);
|
|
28801
29600
|
startPushSender({ maxAgeMs: PUSH_MAX_AGE_MS });
|
|
28802
29601
|
reconcileOpenDispatches();
|
|
@@ -28810,9 +29609,9 @@ function createAgentCoreA2A(registry2, rawWorkspace, onLost) {
|
|
|
28810
29609
|
const activate = () => {
|
|
28811
29610
|
if (active)
|
|
28812
29611
|
return;
|
|
28813
|
-
|
|
29612
|
+
fs66.mkdirSync(haloDir, { recursive: true });
|
|
28814
29613
|
if (seedAgentCard(workspace))
|
|
28815
|
-
console.log(`[AgentCoreA2A] seeded ${
|
|
29614
|
+
console.log(`[AgentCoreA2A] seeded ${path75.join(workspace, CARD_FILE)}`);
|
|
28816
29615
|
active = true;
|
|
28817
29616
|
};
|
|
28818
29617
|
return { app: createAgentCoreA2ARoutes({ registry: registry2, workspace, accessLevel, lease, activate }), lease, workspace };
|
|
@@ -28842,7 +29641,7 @@ var init_agentcore = __esm({
|
|
|
28842
29641
|
WorkspaceLease = class {
|
|
28843
29642
|
file;
|
|
28844
29643
|
hooks;
|
|
28845
|
-
owner =
|
|
29644
|
+
owner = crypto9.randomUUID();
|
|
28846
29645
|
_held = false;
|
|
28847
29646
|
timer = null;
|
|
28848
29647
|
waitLogged = false;
|
|
@@ -28977,12 +29776,12 @@ var init_dispatcher = __esm({
|
|
|
28977
29776
|
|
|
28978
29777
|
// ../server/dist/cron/runner.js
|
|
28979
29778
|
import { spawn as spawn3, execFileSync } from "node:child_process";
|
|
28980
|
-
import
|
|
28981
|
-
import
|
|
29779
|
+
import fs67 from "node:fs";
|
|
29780
|
+
import path76 from "node:path";
|
|
28982
29781
|
import { homedir as homedir24 } from "node:os";
|
|
28983
29782
|
import { Cron } from "croner";
|
|
28984
29783
|
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";
|
|
29784
|
+
import { and as and11, eq as eq14, gte as gte3, inArray as inArray2, lt as lt5, notInArray, or as or2 } from "drizzle-orm";
|
|
28986
29785
|
function resolveHaloCli() {
|
|
28987
29786
|
if (process.env.HALO_CLI)
|
|
28988
29787
|
return process.env.HALO_CLI;
|
|
@@ -29083,9 +29882,9 @@ function jobChangedFields(job) {
|
|
|
29083
29882
|
function sessionKeyFor(workspacePath, sessionId) {
|
|
29084
29883
|
let ws;
|
|
29085
29884
|
try {
|
|
29086
|
-
ws =
|
|
29885
|
+
ws = fs67.realpathSync(workspacePath);
|
|
29087
29886
|
} catch {
|
|
29088
|
-
ws =
|
|
29887
|
+
ws = path76.resolve(workspacePath);
|
|
29089
29888
|
}
|
|
29090
29889
|
return `${ws}
|
|
29091
29890
|
${sessionId}`;
|
|
@@ -29094,10 +29893,10 @@ function setCronSessionRegistry(r) {
|
|
|
29094
29893
|
_sessionRegistry = r;
|
|
29095
29894
|
}
|
|
29096
29895
|
function logsDir() {
|
|
29097
|
-
return
|
|
29896
|
+
return path76.join(homedir24(), ".halo", "global", "logs", "cron");
|
|
29098
29897
|
}
|
|
29099
29898
|
function logPathFor(runId) {
|
|
29100
|
-
return
|
|
29899
|
+
return path76.join(logsDir(), `${runId}.log`);
|
|
29101
29900
|
}
|
|
29102
29901
|
function newRunId() {
|
|
29103
29902
|
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -29158,7 +29957,7 @@ function sweepOrphanRuns(opts) {
|
|
|
29158
29957
|
}
|
|
29159
29958
|
}
|
|
29160
29959
|
function startCronDaemon() {
|
|
29161
|
-
|
|
29960
|
+
fs67.mkdirSync(logsDir(), { recursive: true });
|
|
29162
29961
|
sweepOrphanRuns();
|
|
29163
29962
|
reloadAll();
|
|
29164
29963
|
_daemonTimers.push(setInterval(() => {
|
|
@@ -29288,6 +30087,7 @@ function reconcileFromDb() {
|
|
|
29288
30087
|
if (dv !== null && _lastCronDataVersion === dv)
|
|
29289
30088
|
return;
|
|
29290
30089
|
_lastCronDataVersion = dv;
|
|
30090
|
+
deleteOrphanRuns().catch((err) => console.warn(`[Cron] deleting runs of deleted jobs failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
29291
30091
|
const db3 = getCronDb();
|
|
29292
30092
|
const rows = db3.select().from(cronJobs).all();
|
|
29293
30093
|
const liveIds = /* @__PURE__ */ new Set();
|
|
@@ -29342,7 +30142,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
29342
30142
|
const runId = newRunId();
|
|
29343
30143
|
const startedAt = Date.now();
|
|
29344
30144
|
const logPath = logPathFor(runId);
|
|
29345
|
-
|
|
30145
|
+
fs67.mkdirSync(logsDir(), { recursive: true });
|
|
29346
30146
|
const sessionId = job.stateless ? perRunSessionId(job, startedAt) : effectiveSessionId(job);
|
|
29347
30147
|
const sessionKey = sessionKeyFor(job.workspacePath, sessionId);
|
|
29348
30148
|
const host = _sessionRegistry?.peek(job.workspacePath);
|
|
@@ -29359,7 +30159,8 @@ async function runJob(jobId, triggerKind) {
|
|
|
29359
30159
|
exitCode: null,
|
|
29360
30160
|
failureReason: skipReason,
|
|
29361
30161
|
logPath: null,
|
|
29362
|
-
dispatchResults: null
|
|
30162
|
+
dispatchResults: null,
|
|
30163
|
+
workspacePath: job.workspacePath
|
|
29363
30164
|
}).run();
|
|
29364
30165
|
broadcast({ type: "cron:run_changed", jobId, runId, status: "skipped" });
|
|
29365
30166
|
return runId;
|
|
@@ -29379,11 +30180,12 @@ async function runJob(jobId, triggerKind) {
|
|
|
29379
30180
|
failureReason: null,
|
|
29380
30181
|
logPath,
|
|
29381
30182
|
dispatchResults: null,
|
|
29382
|
-
sessionId
|
|
30183
|
+
sessionId,
|
|
30184
|
+
workspacePath: job.workspacePath
|
|
29383
30185
|
}).run();
|
|
29384
30186
|
broadcast({ type: "cron:run_changed", jobId, runId, status: "running" });
|
|
29385
30187
|
const targets = parseTargets(job.targets);
|
|
29386
|
-
if (!
|
|
30188
|
+
if (!fs67.existsSync(job.workspacePath)) {
|
|
29387
30189
|
finalize(runId, jobId, {
|
|
29388
30190
|
status: "failed",
|
|
29389
30191
|
output: null,
|
|
@@ -29426,7 +30228,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
29426
30228
|
args.push("--access", accessLevel);
|
|
29427
30229
|
const [spawnBin, spawnArgs] = process.platform === "win32" && cli.endsWith(".cmd") ? ["cmd.exe", ["/c", cli, ...args]] : [cli, args];
|
|
29428
30230
|
const timeoutSec = job.timeoutSec ?? CLI_TIMEOUT_SEC;
|
|
29429
|
-
const teeStream =
|
|
30231
|
+
const teeStream = fs67.createWriteStream(logPath, { flags: "a" });
|
|
29430
30232
|
teeStream.write(`=== ${(/* @__PURE__ */ new Date()).toISOString()} cron run ${runId} job=${jobId} trigger=${triggerKind} ===
|
|
29431
30233
|
`);
|
|
29432
30234
|
teeStream.write(`agent=${job.agentId} session=${sessionId}${job.stateless ? " (stateless)" : ""}${accessLevel ? ` access=${accessLevel}` : ""} workspace=${job.workspacePath} dir=${job.workingDir ?? "."} schedule=${job.schedule}
|
|
@@ -29552,8 +30354,8 @@ function readSessionRow(workspacePath, sessionId) {
|
|
|
29552
30354
|
try {
|
|
29553
30355
|
let entry = _sessionRows.get(workspacePath);
|
|
29554
30356
|
if (!entry) {
|
|
29555
|
-
const file =
|
|
29556
|
-
if (!
|
|
30357
|
+
const file = path76.join(workspacePath, ".halo", "halo.db");
|
|
30358
|
+
if (!fs67.existsSync(file))
|
|
29557
30359
|
return null;
|
|
29558
30360
|
entry = { db: new Database9(file, { readonly: true, fileMustExist: true }), version: -1, rows: /* @__PURE__ */ new Map() };
|
|
29559
30361
|
_sessionRows.set(workspacePath, entry);
|
|
@@ -29624,10 +30426,14 @@ function parseTargets(raw) {
|
|
|
29624
30426
|
async function pruneOldRuns(jobId) {
|
|
29625
30427
|
const db3 = getCronDb();
|
|
29626
30428
|
const job = db3.select({ stateless: cronJobs.stateless }).from(cronJobs).where(eq14(cronJobs.id, jobId)).get();
|
|
29627
|
-
|
|
30429
|
+
if (!job) {
|
|
30430
|
+
await deleteJobRuns(jobId);
|
|
30431
|
+
return;
|
|
30432
|
+
}
|
|
30433
|
+
const keep = job.stateless ? STATELESS_RUNS_PER_JOB_KEEP : RUNS_PER_JOB_KEEP;
|
|
29628
30434
|
const doomed = rawSqlite(db3).prepare(`
|
|
29629
30435
|
SELECT id, job_id AS jobId, session_id AS sessionId, started_at AS startedAt,
|
|
29630
|
-
completed_at AS completedAt, status
|
|
30436
|
+
completed_at AS completedAt, status, workspace_path AS workspacePath
|
|
29631
30437
|
FROM cron_runs
|
|
29632
30438
|
WHERE job_id = ?
|
|
29633
30439
|
AND id NOT IN (
|
|
@@ -29642,31 +30448,44 @@ async function pruneOldRuns(jobId) {
|
|
|
29642
30448
|
async function deleteJobRuns(jobId) {
|
|
29643
30449
|
await deleteRuns(getCronDb().select(DOOMED_RUN_COLUMNS).from(cronRuns).where(eq14(cronRuns.jobId, jobId)).all());
|
|
29644
30450
|
}
|
|
30451
|
+
async function deleteOrphanRuns() {
|
|
30452
|
+
const db3 = getCronDb();
|
|
30453
|
+
await deleteRuns(db3.select(DOOMED_RUN_COLUMNS).from(cronRuns).where(notInArray(cronRuns.jobId, db3.select({ id: cronJobs.id }).from(cronJobs))).all());
|
|
30454
|
+
}
|
|
29645
30455
|
async function deleteRuns(runs) {
|
|
29646
30456
|
if (runs.length === 0)
|
|
29647
30457
|
return;
|
|
29648
30458
|
const db3 = getCronDb();
|
|
29649
30459
|
const workspaceOf = /* @__PURE__ */ new Map();
|
|
29650
|
-
const
|
|
30460
|
+
const openWorkspace = (workspacePath) => {
|
|
30461
|
+
let ws = workspaceOf.get(workspacePath);
|
|
30462
|
+
if (ws === void 0) {
|
|
30463
|
+
ws = fs67.existsSync(path76.join(workspacePath, ".halo", "halo.db")) ? { workspacePath, db: getWorkspaceDb(workspacePath).db } : null;
|
|
30464
|
+
workspaceOf.set(workspacePath, ws);
|
|
30465
|
+
}
|
|
30466
|
+
return ws;
|
|
30467
|
+
};
|
|
30468
|
+
const keptRows = /* @__PURE__ */ new Set();
|
|
29651
30469
|
let deleted = 0;
|
|
29652
30470
|
for (const run of runs) {
|
|
29653
30471
|
if (run.sessionId !== perRunSessionId({ id: run.jobId }, run.startedAt))
|
|
29654
30472
|
continue;
|
|
30473
|
+
if (run.status === "running" || run.completedAt === null) {
|
|
30474
|
+
keptRows.add(run.id);
|
|
30475
|
+
console.log(`[Cron] run ${run.id}: still in progress, kept with its session ${run.sessionId}`);
|
|
30476
|
+
continue;
|
|
30477
|
+
}
|
|
29655
30478
|
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
|
-
}
|
|
30479
|
+
const workspacePath = run.workspacePath ?? db3.select({ ws: cronJobs.workspacePath }).from(cronJobs).where(eq14(cronJobs.id, run.jobId)).get()?.ws;
|
|
30480
|
+
const ws = workspacePath ? openWorkspace(workspacePath) : null;
|
|
29662
30481
|
if (ws && await deleteRunSession(ws, run))
|
|
29663
30482
|
deleted++;
|
|
29664
30483
|
} catch (err) {
|
|
29665
|
-
|
|
30484
|
+
keptRows.add(run.id);
|
|
29666
30485
|
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
30486
|
}
|
|
29668
30487
|
}
|
|
29669
|
-
const ids = runs.map((r) => r.id).filter((id) => !
|
|
30488
|
+
const ids = runs.map((r) => r.id).filter((id) => !keptRows.has(id));
|
|
29670
30489
|
if (ids.length > 0)
|
|
29671
30490
|
db3.delete(cronRuns).where(inArray2(cronRuns.id, ids)).run();
|
|
29672
30491
|
if (deleted > 0) {
|
|
@@ -29680,8 +30499,6 @@ async function deleteRunSession({ workspacePath, db: db3 }, run) {
|
|
|
29680
30499
|
console.log(`[Cron] run ${run.id}: kept session ${sid} (${why}); pruning the run row only`);
|
|
29681
30500
|
return false;
|
|
29682
30501
|
};
|
|
29683
|
-
if (run.status === "running" || run.completedAt === null)
|
|
29684
|
-
return keep("its run is still in progress");
|
|
29685
30502
|
const host = _sessionRegistry?.peek(workspacePath);
|
|
29686
30503
|
if (host?.hasActiveWorkInTree(sid))
|
|
29687
30504
|
return keep("a turn is running in it");
|
|
@@ -29700,15 +30517,15 @@ async function deleteRunSession({ workspacePath, db: db3 }, run) {
|
|
|
29700
30517
|
}
|
|
29701
30518
|
async function pruneOldLogs() {
|
|
29702
30519
|
const dir = logsDir();
|
|
29703
|
-
if (!
|
|
30520
|
+
if (!fs67.existsSync(dir))
|
|
29704
30521
|
return;
|
|
29705
30522
|
const cutoff = Date.now() - LOG_RETENTION_DAYS * DAY_MS2;
|
|
29706
|
-
for (const name of
|
|
29707
|
-
const p =
|
|
30523
|
+
for (const name of fs67.readdirSync(dir)) {
|
|
30524
|
+
const p = path76.join(dir, name);
|
|
29708
30525
|
try {
|
|
29709
|
-
const stat =
|
|
30526
|
+
const stat = fs67.statSync(p);
|
|
29710
30527
|
if (stat.mtimeMs < cutoff)
|
|
29711
|
-
|
|
30528
|
+
fs67.rmSync(p, { force: true });
|
|
29712
30529
|
} catch {
|
|
29713
30530
|
}
|
|
29714
30531
|
}
|
|
@@ -29751,7 +30568,8 @@ var init_runner = __esm({
|
|
|
29751
30568
|
sessionId: cronRuns.sessionId,
|
|
29752
30569
|
startedAt: cronRuns.startedAt,
|
|
29753
30570
|
completedAt: cronRuns.completedAt,
|
|
29754
|
-
status: cronRuns.status
|
|
30571
|
+
status: cronRuns.status,
|
|
30572
|
+
workspacePath: cronRuns.workspacePath
|
|
29755
30573
|
};
|
|
29756
30574
|
}
|
|
29757
30575
|
});
|
|
@@ -29759,8 +30577,8 @@ var init_runner = __esm({
|
|
|
29759
30577
|
// ../server/dist/routes/cron.js
|
|
29760
30578
|
import { Hono as Hono18 } from "hono";
|
|
29761
30579
|
import { eq as eq15, desc as desc3, lt as lt6, and as and12 } from "drizzle-orm";
|
|
29762
|
-
import
|
|
29763
|
-
import
|
|
30580
|
+
import fs68 from "node:fs/promises";
|
|
30581
|
+
import path77 from "node:path";
|
|
29764
30582
|
import { Cron as Cron2 } from "croner";
|
|
29765
30583
|
function newJobId() {
|
|
29766
30584
|
return `cron-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -29805,7 +30623,7 @@ async function parseWorkingDir(workspacePath, v) {
|
|
|
29805
30623
|
return { workingDir: null };
|
|
29806
30624
|
try {
|
|
29807
30625
|
const abs = await resolveWorkingDir(workspacePath, s);
|
|
29808
|
-
return { workingDir:
|
|
30626
|
+
return { workingDir: path77.relative(path77.resolve(workspacePath), abs) || null };
|
|
29809
30627
|
} catch (err) {
|
|
29810
30628
|
return { error: `workingDir: ${err instanceof Error ? err.message : String(err)}` };
|
|
29811
30629
|
}
|
|
@@ -30110,7 +30928,7 @@ function createCronRoutes() {
|
|
|
30110
30928
|
return c.json({ log: null });
|
|
30111
30929
|
let log2 = null;
|
|
30112
30930
|
try {
|
|
30113
|
-
log2 = await
|
|
30931
|
+
log2 = await fs68.readFile(row.logPath, "utf-8");
|
|
30114
30932
|
} catch {
|
|
30115
30933
|
}
|
|
30116
30934
|
return c.json({ log: log2 });
|
|
@@ -30176,8 +30994,8 @@ var init_cron = __esm({
|
|
|
30176
30994
|
});
|
|
30177
30995
|
|
|
30178
30996
|
// ../server/dist/extensions/install.js
|
|
30179
|
-
import
|
|
30180
|
-
import
|
|
30997
|
+
import fs69 from "node:fs";
|
|
30998
|
+
import path78 from "node:path";
|
|
30181
30999
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
30182
31000
|
import JSZip2 from "jszip";
|
|
30183
31001
|
function safeEntryPath(name) {
|
|
@@ -30208,43 +31026,43 @@ async function unpackZip(zipBuf, dest) {
|
|
|
30208
31026
|
const perm = entry.unixPermissions;
|
|
30209
31027
|
if (typeof perm === "number" && (perm & S_IFMT) === S_IFLNK)
|
|
30210
31028
|
throw new ExtensionInstallError(`zip entry is a symlink: ${entry.name}`);
|
|
30211
|
-
const abs =
|
|
31029
|
+
const abs = path78.join(dest, rel);
|
|
30212
31030
|
if (entry.dir) {
|
|
30213
|
-
|
|
31031
|
+
fs69.mkdirSync(abs, { recursive: true });
|
|
30214
31032
|
continue;
|
|
30215
31033
|
}
|
|
30216
|
-
|
|
30217
|
-
|
|
31034
|
+
fs69.mkdirSync(path78.dirname(abs), { recursive: true });
|
|
31035
|
+
fs69.writeFileSync(abs, await entry.async("nodebuffer"));
|
|
30218
31036
|
}
|
|
30219
31037
|
}
|
|
30220
31038
|
function stripWrapperDir(dir) {
|
|
30221
|
-
if (
|
|
31039
|
+
if (fs69.existsSync(path78.join(dir, MANIFEST_FILE)))
|
|
30222
31040
|
return;
|
|
30223
|
-
const children =
|
|
31041
|
+
const children = fs69.readdirSync(dir, { withFileTypes: true });
|
|
30224
31042
|
if (children.length !== 1 || !children[0].isDirectory())
|
|
30225
31043
|
return;
|
|
30226
|
-
const inner =
|
|
30227
|
-
if (!
|
|
31044
|
+
const inner = path78.join(dir, children[0].name);
|
|
31045
|
+
if (!fs69.existsSync(path78.join(inner, MANIFEST_FILE)))
|
|
30228
31046
|
return;
|
|
30229
|
-
for (const name of
|
|
30230
|
-
|
|
31047
|
+
for (const name of fs69.readdirSync(inner)) {
|
|
31048
|
+
fs69.renameSync(path78.join(inner, name), path78.join(dir, name));
|
|
30231
31049
|
}
|
|
30232
|
-
|
|
31050
|
+
fs69.rmdirSync(inner);
|
|
30233
31051
|
}
|
|
30234
31052
|
function rand() {
|
|
30235
31053
|
return randomBytes3(3).toString("hex");
|
|
30236
31054
|
}
|
|
30237
31055
|
async function installExtensionZip(zipBuf, expectId) {
|
|
30238
31056
|
const root = extensionsRoot();
|
|
30239
|
-
|
|
30240
|
-
const stage =
|
|
30241
|
-
|
|
31057
|
+
fs69.mkdirSync(root, { recursive: true });
|
|
31058
|
+
const stage = path78.join(root, `.tmp-unpack-${rand()}`);
|
|
31059
|
+
fs69.mkdirSync(stage);
|
|
30242
31060
|
try {
|
|
30243
31061
|
await unpackZip(zipBuf, stage);
|
|
30244
31062
|
stripWrapperDir(stage);
|
|
30245
31063
|
let manifestId;
|
|
30246
31064
|
try {
|
|
30247
|
-
const raw = JSON.parse(
|
|
31065
|
+
const raw = JSON.parse(fs69.readFileSync(path78.join(stage, MANIFEST_FILE), "utf-8"));
|
|
30248
31066
|
manifestId = typeof raw.id === "string" ? raw.id : "";
|
|
30249
31067
|
} catch (err) {
|
|
30250
31068
|
throw new ExtensionInstallError(`${MANIFEST_FILE} missing or unreadable at the zip root: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -30256,36 +31074,36 @@ async function installExtensionZip(zipBuf, expectId) {
|
|
|
30256
31074
|
const verdict2 = readExtensionDir(stage, manifestId);
|
|
30257
31075
|
if ("error" in verdict2)
|
|
30258
31076
|
throw new ExtensionInstallError(`invalid extension: ${verdict2.error}`);
|
|
30259
|
-
const target =
|
|
30260
|
-
const old =
|
|
30261
|
-
const hadOld =
|
|
31077
|
+
const target = path78.join(root, manifestId);
|
|
31078
|
+
const old = path78.join(root, `.old-${manifestId}-${rand()}`);
|
|
31079
|
+
const hadOld = fs69.existsSync(target);
|
|
30262
31080
|
if (hadOld)
|
|
30263
|
-
|
|
31081
|
+
fs69.renameSync(target, old);
|
|
30264
31082
|
try {
|
|
30265
|
-
|
|
31083
|
+
fs69.renameSync(stage, target);
|
|
30266
31084
|
} catch (err) {
|
|
30267
31085
|
if (hadOld)
|
|
30268
|
-
|
|
31086
|
+
fs69.renameSync(old, target);
|
|
30269
31087
|
throw err;
|
|
30270
31088
|
}
|
|
30271
31089
|
if (hadOld)
|
|
30272
|
-
|
|
31090
|
+
fs69.rmSync(old, { recursive: true, force: true });
|
|
30273
31091
|
const info = readExtensionDir(target);
|
|
30274
31092
|
if ("error" in info)
|
|
30275
31093
|
throw new ExtensionInstallError(`installed but failed re-validation: ${info.error}`);
|
|
30276
31094
|
console.log(`[Extensions] installed ${info.id}@${info.version}${hadOld ? " (replaced previous)" : ""}`);
|
|
30277
31095
|
return info;
|
|
30278
31096
|
} finally {
|
|
30279
|
-
|
|
31097
|
+
fs69.rmSync(stage, { recursive: true, force: true });
|
|
30280
31098
|
}
|
|
30281
31099
|
}
|
|
30282
31100
|
function uninstallExtension(id) {
|
|
30283
31101
|
if (!isExtensionId(id))
|
|
30284
31102
|
throw new ExtensionInstallError(`"${id}" is not a valid extension id`);
|
|
30285
|
-
const dir =
|
|
30286
|
-
if (!
|
|
31103
|
+
const dir = path78.join(extensionsRoot(), id);
|
|
31104
|
+
if (!fs69.existsSync(dir))
|
|
30287
31105
|
return false;
|
|
30288
|
-
|
|
31106
|
+
fs69.rmSync(dir, { recursive: true, force: true });
|
|
30289
31107
|
console.log(`[Extensions] removed ${id}`);
|
|
30290
31108
|
return true;
|
|
30291
31109
|
}
|
|
@@ -30302,7 +31120,7 @@ var init_install2 = __esm({
|
|
|
30302
31120
|
});
|
|
30303
31121
|
|
|
30304
31122
|
// ../server/dist/extensions/watcher.js
|
|
30305
|
-
import
|
|
31123
|
+
import fs70 from "node:fs";
|
|
30306
31124
|
function scheduleFlush() {
|
|
30307
31125
|
if (flushTimer)
|
|
30308
31126
|
return;
|
|
@@ -30313,15 +31131,23 @@ function scheduleFlush() {
|
|
|
30313
31131
|
}
|
|
30314
31132
|
function rescanAndBroadcast() {
|
|
30315
31133
|
const snap = scanExtensions();
|
|
31134
|
+
reconcileSttModels(snap);
|
|
30316
31135
|
const key = snapshotKey(snap);
|
|
30317
31136
|
if (key === lastKey)
|
|
30318
31137
|
return;
|
|
30319
31138
|
lastKey = key;
|
|
30320
31139
|
broadcast({ type: "extension:changed", ...snap });
|
|
30321
31140
|
}
|
|
31141
|
+
function reconcileSttModels(snap) {
|
|
31142
|
+
if (!snap.extensions.some((e) => e.capabilities.includes("stt-local")))
|
|
31143
|
+
return;
|
|
31144
|
+
const { state } = sttModels.status();
|
|
31145
|
+
if (state !== "ready" && state !== "downloading")
|
|
31146
|
+
void sttModels.ensure();
|
|
31147
|
+
}
|
|
30322
31148
|
function openWatcher(root) {
|
|
30323
31149
|
try {
|
|
30324
|
-
watcher =
|
|
31150
|
+
watcher = fs70.watch(root, { persistent: false, recursive: false }, () => scheduleFlush());
|
|
30325
31151
|
watcher.on("error", (err) => {
|
|
30326
31152
|
console.warn(`[Extensions] watch error on ${root}: ${err.message} \u2014 retrying in 1s`);
|
|
30327
31153
|
closeWatcher();
|
|
@@ -30347,11 +31173,13 @@ function start() {
|
|
|
30347
31173
|
return;
|
|
30348
31174
|
const root = extensionsRoot();
|
|
30349
31175
|
try {
|
|
30350
|
-
|
|
31176
|
+
fs70.mkdirSync(root, { recursive: true });
|
|
30351
31177
|
} catch (err) {
|
|
30352
31178
|
console.warn(`[Extensions] cannot create ${root}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30353
31179
|
}
|
|
30354
|
-
|
|
31180
|
+
const snap = scanExtensions();
|
|
31181
|
+
lastKey = snapshotKey(snap);
|
|
31182
|
+
reconcileSttModels(snap);
|
|
30355
31183
|
openWatcher(root);
|
|
30356
31184
|
const { extensions, errors } = getSnapshot();
|
|
30357
31185
|
if (extensions.length > 0 || errors.length > 0) {
|
|
@@ -30372,6 +31200,7 @@ var init_watcher = __esm({
|
|
|
30372
31200
|
"../server/dist/extensions/watcher.js"() {
|
|
30373
31201
|
"use strict";
|
|
30374
31202
|
init_broadcast();
|
|
31203
|
+
init_models();
|
|
30375
31204
|
init_registry2();
|
|
30376
31205
|
watcher = null;
|
|
30377
31206
|
flushTimer = null;
|
|
@@ -30382,8 +31211,8 @@ var init_watcher = __esm({
|
|
|
30382
31211
|
|
|
30383
31212
|
// ../server/dist/routes/extensions.js
|
|
30384
31213
|
import { Hono as Hono19 } from "hono";
|
|
30385
|
-
import
|
|
30386
|
-
import
|
|
31214
|
+
import fs71 from "node:fs";
|
|
31215
|
+
import path79 from "node:path";
|
|
30387
31216
|
import { Readable as Readable2 } from "node:stream";
|
|
30388
31217
|
function assetMime(fileName) {
|
|
30389
31218
|
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -30401,15 +31230,15 @@ function resolveAsset(id, version, rest) {
|
|
|
30401
31230
|
let dir;
|
|
30402
31231
|
let real;
|
|
30403
31232
|
try {
|
|
30404
|
-
dir =
|
|
30405
|
-
real =
|
|
31233
|
+
dir = fs71.realpathSync(path79.join(extensionsRoot(), id));
|
|
31234
|
+
real = fs71.realpathSync(path79.join(dir, ...segments));
|
|
30406
31235
|
} catch {
|
|
30407
31236
|
return null;
|
|
30408
31237
|
}
|
|
30409
|
-
if (real !== dir && !real.startsWith(dir +
|
|
31238
|
+
if (real !== dir && !real.startsWith(dir + path79.sep))
|
|
30410
31239
|
return null;
|
|
30411
31240
|
try {
|
|
30412
|
-
if (!
|
|
31241
|
+
if (!fs71.statSync(real).isFile())
|
|
30413
31242
|
return null;
|
|
30414
31243
|
} catch {
|
|
30415
31244
|
return null;
|
|
@@ -30471,8 +31300,8 @@ function createExtensionRoutes() {
|
|
|
30471
31300
|
const abs = resolveAsset(c.req.param("id"), c.req.param("version"), c.req.param("rest"));
|
|
30472
31301
|
if (!abs)
|
|
30473
31302
|
return c.json({ error: "Not found" }, 404);
|
|
30474
|
-
const stat =
|
|
30475
|
-
const nodeStream =
|
|
31303
|
+
const stat = fs71.statSync(abs);
|
|
31304
|
+
const nodeStream = fs71.createReadStream(abs);
|
|
30476
31305
|
c.req.raw.signal?.addEventListener("abort", () => nodeStream.destroy(), { once: true });
|
|
30477
31306
|
return new Response(Readable2.toWeb(nodeStream), {
|
|
30478
31307
|
headers: {
|
|
@@ -30526,7 +31355,7 @@ var init_extensions = __esm({
|
|
|
30526
31355
|
});
|
|
30527
31356
|
|
|
30528
31357
|
// ../server/dist/models/watcher.js
|
|
30529
|
-
import
|
|
31358
|
+
import fs72 from "node:fs";
|
|
30530
31359
|
function snapshot2() {
|
|
30531
31360
|
const { effective } = loadProviders();
|
|
30532
31361
|
return {
|
|
@@ -30554,7 +31383,7 @@ function reloadAndBroadcast() {
|
|
|
30554
31383
|
}
|
|
30555
31384
|
function openWatcher2(root) {
|
|
30556
31385
|
try {
|
|
30557
|
-
watcher2 =
|
|
31386
|
+
watcher2 = fs72.watch(root, { persistent: false, recursive: false }, () => scheduleFlush2());
|
|
30558
31387
|
watcher2.on("error", (err) => {
|
|
30559
31388
|
console.warn(`[Models] watch error on ${root}: ${err.message} \u2014 retrying in 1s`);
|
|
30560
31389
|
closeWatcher2();
|
|
@@ -30580,7 +31409,7 @@ function start2() {
|
|
|
30580
31409
|
return;
|
|
30581
31410
|
const root = globalHubModelsDir();
|
|
30582
31411
|
try {
|
|
30583
|
-
|
|
31412
|
+
fs72.mkdirSync(root, { recursive: true });
|
|
30584
31413
|
} catch (err) {
|
|
30585
31414
|
console.warn(`[Models] cannot create ${root}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30586
31415
|
}
|
|
@@ -30857,7 +31686,7 @@ var init_ticker = __esm({
|
|
|
30857
31686
|
});
|
|
30858
31687
|
|
|
30859
31688
|
// ../server/dist/evolution/spawn.js
|
|
30860
|
-
import
|
|
31689
|
+
import path80 from "node:path";
|
|
30861
31690
|
import { spawn as spawn4 } from "node:child_process";
|
|
30862
31691
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
30863
31692
|
var __dirname2, WRAPPER_PATH, realEvoSpawner;
|
|
@@ -30865,8 +31694,8 @@ var init_spawn = __esm({
|
|
|
30865
31694
|
"../server/dist/evolution/spawn.js"() {
|
|
30866
31695
|
"use strict";
|
|
30867
31696
|
init_child_env();
|
|
30868
|
-
__dirname2 =
|
|
30869
|
-
WRAPPER_PATH =
|
|
31697
|
+
__dirname2 = path80.dirname(fileURLToPath2(import.meta.url));
|
|
31698
|
+
WRAPPER_PATH = path80.resolve(__dirname2, "evo-wrapper.js");
|
|
30870
31699
|
realEvoSpawner = (mode, id) => {
|
|
30871
31700
|
const child = spawn4(process.execPath, [WRAPPER_PATH, `--mode=${mode}`, `--id=${id}`], {
|
|
30872
31701
|
detached: true,
|
|
@@ -31697,12 +32526,12 @@ var init_handler3 = __esm({
|
|
|
31697
32526
|
});
|
|
31698
32527
|
|
|
31699
32528
|
// ../server/dist/routes/channel-accounts.js
|
|
31700
|
-
import
|
|
32529
|
+
import fs73 from "node:fs";
|
|
31701
32530
|
function accountListFields(a) {
|
|
31702
32531
|
return {
|
|
31703
32532
|
accountId: a.accountId,
|
|
31704
32533
|
workspacePath: a.workspacePath,
|
|
31705
|
-
workspaceMissing: !
|
|
32534
|
+
workspaceMissing: !fs73.existsSync(a.workspacePath),
|
|
31706
32535
|
label: a.label,
|
|
31707
32536
|
enabled: a.enabled,
|
|
31708
32537
|
accessLevel: a.accessLevel,
|
|
@@ -31921,7 +32750,7 @@ var init_descriptor = __esm({
|
|
|
31921
32750
|
});
|
|
31922
32751
|
|
|
31923
32752
|
// ../server/dist/channels/wechat/api.js
|
|
31924
|
-
import
|
|
32753
|
+
import crypto10 from "node:crypto";
|
|
31925
32754
|
function buildClientVersion(version) {
|
|
31926
32755
|
const [ma, mi, pa] = version.split(".").map((p) => parseInt(p, 10));
|
|
31927
32756
|
return (ma & 255) << 16 | (mi & 255) << 8 | pa & 255;
|
|
@@ -31930,7 +32759,7 @@ function buildBaseInfo() {
|
|
|
31930
32759
|
return { channel_version: CHANNEL_VERSION };
|
|
31931
32760
|
}
|
|
31932
32761
|
function randomWechatUin() {
|
|
31933
|
-
const n =
|
|
32762
|
+
const n = crypto10.randomBytes(4).readUInt32BE(0);
|
|
31934
32763
|
return Buffer.from(String(n), "utf-8").toString("base64");
|
|
31935
32764
|
}
|
|
31936
32765
|
function commonHeaders() {
|
|
@@ -32238,7 +33067,7 @@ var init_event_adapter2 = __esm({
|
|
|
32238
33067
|
});
|
|
32239
33068
|
|
|
32240
33069
|
// ../server/dist/channels/wechat/cdn.js
|
|
32241
|
-
import { createDecipheriv, createCipheriv, randomBytes as randomBytes4, createHash as
|
|
33070
|
+
import { createDecipheriv, createCipheriv, randomBytes as randomBytes4, createHash as createHash3 } from "node:crypto";
|
|
32242
33071
|
function decryptAesEcb(ciphertext, key) {
|
|
32243
33072
|
const decipher = createDecipheriv("aes-128-ecb", key, null);
|
|
32244
33073
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
@@ -32292,7 +33121,7 @@ function prepareUpload(plaintext) {
|
|
|
32292
33121
|
aeskey,
|
|
32293
33122
|
aeskeyHex: aeskey.toString("hex"),
|
|
32294
33123
|
rawsize: plaintext.length,
|
|
32295
|
-
rawfilemd5:
|
|
33124
|
+
rawfilemd5: createHash3("md5").update(plaintext).digest("hex"),
|
|
32296
33125
|
filesize: aesEcbPaddedSize(plaintext.length)
|
|
32297
33126
|
};
|
|
32298
33127
|
}
|
|
@@ -32344,8 +33173,8 @@ var init_cdn = __esm({
|
|
|
32344
33173
|
});
|
|
32345
33174
|
|
|
32346
33175
|
// ../server/dist/channels/wechat/send-media.js
|
|
32347
|
-
import
|
|
32348
|
-
import
|
|
33176
|
+
import fs74 from "node:fs/promises";
|
|
33177
|
+
import path81 from "node:path";
|
|
32349
33178
|
function inferKind(filePath) {
|
|
32350
33179
|
const cls = classifyMedia(filePath);
|
|
32351
33180
|
if (cls === "image")
|
|
@@ -32357,7 +33186,7 @@ function inferKind(filePath) {
|
|
|
32357
33186
|
async function sendMediaFile(params) {
|
|
32358
33187
|
const { baseUrl, token, toUserId, contextToken, contextTokenAt, filePath } = params;
|
|
32359
33188
|
const kind = params.kind ?? inferKind(filePath);
|
|
32360
|
-
const plaintext = await
|
|
33189
|
+
const plaintext = await fs74.readFile(filePath);
|
|
32361
33190
|
const prep = prepareUpload(plaintext);
|
|
32362
33191
|
console.log(`[WeChat] sendMediaFile: path=${filePath} kind=${kind} rawsize=${prep.rawsize} filesize=${prep.filesize}`);
|
|
32363
33192
|
const mediaType = kind === "image" ? UploadMediaType.IMAGE : kind === "video" ? UploadMediaType.VIDEO : UploadMediaType.FILE;
|
|
@@ -32395,7 +33224,7 @@ async function sendMediaFile(params) {
|
|
|
32395
33224
|
aesKeyBase64,
|
|
32396
33225
|
fileSizeCiphertext: prep.filesize,
|
|
32397
33226
|
fileSize: prep.rawsize,
|
|
32398
|
-
fileName:
|
|
33227
|
+
fileName: path81.basename(filePath)
|
|
32399
33228
|
});
|
|
32400
33229
|
const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
32401
33230
|
const body = {
|
|
@@ -32558,8 +33387,8 @@ var init_login = __esm({
|
|
|
32558
33387
|
});
|
|
32559
33388
|
|
|
32560
33389
|
// ../server/dist/channels/wechat/handler.js
|
|
32561
|
-
import
|
|
32562
|
-
import
|
|
33390
|
+
import fs75 from "node:fs";
|
|
33391
|
+
import path82 from "node:path";
|
|
32563
33392
|
import QRCode from "qrcode";
|
|
32564
33393
|
function wxRoute(fromUserId, contextToken, contextTokenAt) {
|
|
32565
33394
|
return (prev) => {
|
|
@@ -32980,7 +33809,7 @@ async function handleSlashCommand(args) {
|
|
|
32980
33809
|
await reply(login.message);
|
|
32981
33810
|
return true;
|
|
32982
33811
|
}
|
|
32983
|
-
const tmpPath =
|
|
33812
|
+
const tmpPath = path82.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
|
|
32984
33813
|
await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
|
|
32985
33814
|
await sendMediaFile({
|
|
32986
33815
|
baseUrl: account.baseUrl,
|
|
@@ -32989,7 +33818,7 @@ async function handleSlashCommand(args) {
|
|
|
32989
33818
|
contextToken,
|
|
32990
33819
|
filePath: tmpPath
|
|
32991
33820
|
});
|
|
32992
|
-
await
|
|
33821
|
+
await fs75.promises.unlink(tmpPath).catch(() => {
|
|
32993
33822
|
});
|
|
32994
33823
|
await reply(t("wechat.qr_sent", lang, { level, path: account.workspacePath }));
|
|
32995
33824
|
waitLogin({ sessionKey: login.sessionKey }).then(async (result2) => {
|
|
@@ -33069,8 +33898,8 @@ var init_handler4 = __esm({
|
|
|
33069
33898
|
|
|
33070
33899
|
// ../server/dist/routes/wechat.js
|
|
33071
33900
|
import { Hono as Hono21 } from "hono";
|
|
33072
|
-
import
|
|
33073
|
-
import
|
|
33901
|
+
import path83 from "node:path";
|
|
33902
|
+
import fs76 from "node:fs";
|
|
33074
33903
|
function createWechatRoutes(deps) {
|
|
33075
33904
|
const { db: db3, channel } = deps;
|
|
33076
33905
|
const app2 = new Hono21();
|
|
@@ -33079,7 +33908,7 @@ function createWechatRoutes(deps) {
|
|
|
33079
33908
|
return {
|
|
33080
33909
|
...stripSecrets(acc),
|
|
33081
33910
|
workspacePath: acc.workspacePath,
|
|
33082
|
-
workspaceMissing: !
|
|
33911
|
+
workspaceMissing: !fs76.existsSync(acc.workspacePath)
|
|
33083
33912
|
};
|
|
33084
33913
|
});
|
|
33085
33914
|
return c.json({ accounts });
|
|
@@ -33095,7 +33924,7 @@ function createWechatRoutes(deps) {
|
|
|
33095
33924
|
return c.json({ error: "sessionKey required" }, 400);
|
|
33096
33925
|
if (!body.workspacePath)
|
|
33097
33926
|
return c.json({ error: "workspacePath required" }, 400);
|
|
33098
|
-
if (!
|
|
33927
|
+
if (!path83.isAbsolute(body.workspacePath))
|
|
33099
33928
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
33100
33929
|
const levelError = accessLevelError(body.accessLevel, CHAT_ACCESS_LEVELS);
|
|
33101
33930
|
if (levelError)
|
|
@@ -33307,8 +34136,8 @@ var init_descriptor2 = __esm({
|
|
|
33307
34136
|
// ../server/dist/routes/web.js
|
|
33308
34137
|
import { Hono as Hono22 } from "hono";
|
|
33309
34138
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
33310
|
-
import
|
|
33311
|
-
import
|
|
34139
|
+
import crypto11 from "node:crypto";
|
|
34140
|
+
import fs77 from "node:fs";
|
|
33312
34141
|
function streamWithKeepalive(c, chunks) {
|
|
33313
34142
|
return streamSSE2(c, async (stream) => {
|
|
33314
34143
|
const timer2 = setInterval(() => {
|
|
@@ -33345,8 +34174,8 @@ function createWebRoutes(deps) {
|
|
|
33345
34174
|
const bodyError = accountBodyError(body, ACCOUNT_ACCESS_LEVELS);
|
|
33346
34175
|
if (bodyError)
|
|
33347
34176
|
return c.json({ error: bodyError }, 400);
|
|
33348
|
-
const accountId =
|
|
33349
|
-
const token =
|
|
34177
|
+
const accountId = crypto11.randomUUID().slice(0, 8);
|
|
34178
|
+
const token = crypto11.randomBytes(24).toString("base64url");
|
|
33350
34179
|
insertAccount2(db3, {
|
|
33351
34180
|
accountId,
|
|
33352
34181
|
token,
|
|
@@ -33499,30 +34328,30 @@ function createWebRoutes(deps) {
|
|
|
33499
34328
|
const filePath = c.req.query("path");
|
|
33500
34329
|
if (!filePath)
|
|
33501
34330
|
return c.json({ error: "path required" }, 400);
|
|
33502
|
-
const
|
|
33503
|
-
const resolved =
|
|
33504
|
-
const root =
|
|
33505
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
34331
|
+
const path90 = await import("node:path");
|
|
34332
|
+
const resolved = path90.default.resolve(account.workspacePath, filePath);
|
|
34333
|
+
const root = path90.default.resolve(account.workspacePath);
|
|
34334
|
+
if (resolved !== root && !resolved.startsWith(root + path90.default.sep)) {
|
|
33506
34335
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
33507
34336
|
}
|
|
33508
34337
|
let real;
|
|
33509
34338
|
let realRoot;
|
|
33510
34339
|
try {
|
|
33511
|
-
real =
|
|
33512
|
-
realRoot =
|
|
34340
|
+
real = fs77.realpathSync(resolved);
|
|
34341
|
+
realRoot = fs77.realpathSync(root);
|
|
33513
34342
|
} catch {
|
|
33514
34343
|
return c.json({ error: "file not found" }, 404);
|
|
33515
34344
|
}
|
|
33516
|
-
if (real !== realRoot && !real.startsWith(realRoot +
|
|
34345
|
+
if (real !== realRoot && !real.startsWith(realRoot + path90.default.sep)) {
|
|
33517
34346
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
33518
34347
|
}
|
|
33519
34348
|
if (isHiddenWorkspacePath(real, realRoot)) {
|
|
33520
34349
|
return c.json({ error: "path is not accessible" }, 403);
|
|
33521
34350
|
}
|
|
33522
|
-
const stat =
|
|
34351
|
+
const stat = fs77.statSync(real);
|
|
33523
34352
|
if (stat.isDirectory())
|
|
33524
34353
|
return c.json({ error: "cannot serve directory" }, 400);
|
|
33525
|
-
const ext =
|
|
34354
|
+
const ext = path90.default.extname(real).toLowerCase();
|
|
33526
34355
|
const nonImageMimes = {
|
|
33527
34356
|
".mp4": "video/mp4",
|
|
33528
34357
|
".webm": "video/webm",
|
|
@@ -33530,8 +34359,8 @@ function createWebRoutes(deps) {
|
|
|
33530
34359
|
".txt": "text/plain"
|
|
33531
34360
|
};
|
|
33532
34361
|
const contentType = imageMimeFromExt(ext) || nonImageMimes[ext] || "application/octet-stream";
|
|
33533
|
-
const fileName =
|
|
33534
|
-
const data =
|
|
34362
|
+
const fileName = path90.default.basename(resolved);
|
|
34363
|
+
const data = fs77.readFileSync(real);
|
|
33535
34364
|
c.header("content-type", contentType);
|
|
33536
34365
|
c.header("content-disposition", `inline; filename="${fileName}"`);
|
|
33537
34366
|
return c.body(data);
|
|
@@ -33684,7 +34513,7 @@ function formatForFeishu(input) {
|
|
|
33684
34513
|
inFence = !inFence;
|
|
33685
34514
|
continue;
|
|
33686
34515
|
}
|
|
33687
|
-
if (inFence) {
|
|
34516
|
+
if (inFence || /^MEDIA:/.test(raw)) {
|
|
33688
34517
|
out.push(raw);
|
|
33689
34518
|
continue;
|
|
33690
34519
|
}
|
|
@@ -33709,16 +34538,16 @@ var init_markdown = __esm({
|
|
|
33709
34538
|
});
|
|
33710
34539
|
|
|
33711
34540
|
// ../server/dist/channels/slack/event-adapter.js
|
|
33712
|
-
var
|
|
34541
|
+
var SLACK_TEXT_LIMIT, SlackResponder;
|
|
33713
34542
|
var init_event_adapter3 = __esm({
|
|
33714
34543
|
"../server/dist/channels/slack/event-adapter.js"() {
|
|
33715
34544
|
"use strict";
|
|
33716
34545
|
init_markdown();
|
|
33717
34546
|
init_responder();
|
|
33718
|
-
|
|
34547
|
+
SLACK_TEXT_LIMIT = 35e3;
|
|
33719
34548
|
SlackResponder = class extends ChunkedResponder {
|
|
33720
34549
|
constructor(deps) {
|
|
33721
|
-
super(deps, { limit:
|
|
34550
|
+
super(deps, { limit: SLACK_TEXT_LIMIT, logTag: "Slack", format: formatForSlack });
|
|
33722
34551
|
}
|
|
33723
34552
|
};
|
|
33724
34553
|
}
|
|
@@ -33753,10 +34582,10 @@ async function authTest(botToken) {
|
|
|
33753
34582
|
return slackPost("auth.test", botToken, {});
|
|
33754
34583
|
}
|
|
33755
34584
|
async function uploadFile(args) {
|
|
33756
|
-
const
|
|
33757
|
-
const
|
|
33758
|
-
const filename = args.filename ??
|
|
33759
|
-
const stat =
|
|
34585
|
+
const fs81 = await import("node:fs");
|
|
34586
|
+
const path90 = await import("node:path");
|
|
34587
|
+
const filename = args.filename ?? path90.basename(args.filePath);
|
|
34588
|
+
const stat = fs81.statSync(args.filePath);
|
|
33760
34589
|
if (!stat.isFile())
|
|
33761
34590
|
throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
|
|
33762
34591
|
const length = stat.size;
|
|
@@ -33774,7 +34603,7 @@ async function uploadFile(args) {
|
|
|
33774
34603
|
if (step1Parsed.ok === false || !step1Parsed.upload_url || !step1Parsed.file_id) {
|
|
33775
34604
|
throw new Error(`[slack:files.getUploadURLExternal] ${step1Parsed.error ?? "unknown"}`);
|
|
33776
34605
|
}
|
|
33777
|
-
const buf =
|
|
34606
|
+
const buf = fs81.readFileSync(args.filePath);
|
|
33778
34607
|
const step2 = await fetch(step1Parsed.upload_url, {
|
|
33779
34608
|
method: "POST",
|
|
33780
34609
|
body: buf
|
|
@@ -33919,7 +34748,7 @@ var init_api2 = __esm({
|
|
|
33919
34748
|
});
|
|
33920
34749
|
|
|
33921
34750
|
// ../server/dist/channels/slack/handler.js
|
|
33922
|
-
import
|
|
34751
|
+
import path84 from "node:path";
|
|
33923
34752
|
import { WebSocket as WebSocket2 } from "ws";
|
|
33924
34753
|
function slashToBang(text6, commandNames) {
|
|
33925
34754
|
if (commandNames.length === 0)
|
|
@@ -34050,7 +34879,7 @@ function startSlackChannel(deps) {
|
|
|
34050
34879
|
const route = bridge.getRoute(sessionId);
|
|
34051
34880
|
if (!account || !route)
|
|
34052
34881
|
return;
|
|
34053
|
-
const resolved =
|
|
34882
|
+
const resolved = path84.resolve(filePath);
|
|
34054
34883
|
await sendMediaOrReport({
|
|
34055
34884
|
filePath,
|
|
34056
34885
|
account,
|
|
@@ -34454,18 +35283,17 @@ async function dispatch3(accountId, text6, explicitChatId, media) {
|
|
|
34454
35283
|
const { channel, threadTs } = parseChatId(explicitChatId);
|
|
34455
35284
|
const out = [];
|
|
34456
35285
|
if (text6) {
|
|
34457
|
-
|
|
34458
|
-
|
|
34459
|
-
|
|
34460
|
-
|
|
34461
|
-
|
|
34462
|
-
|
|
34463
|
-
|
|
34464
|
-
|
|
34465
|
-
|
|
34466
|
-
error: err instanceof Error ? err.message : String(err)
|
|
34467
|
-
});
|
|
35286
|
+
const chunks = splitText(text6, SLACK_TEXT_LIMIT);
|
|
35287
|
+
let failed;
|
|
35288
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
35289
|
+
try {
|
|
35290
|
+
await postMessage({ botToken: acct.botToken, channel, threadTs, text: formatForSlack(chunk) });
|
|
35291
|
+
} catch (err) {
|
|
35292
|
+
failed = `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`;
|
|
35293
|
+
break;
|
|
35294
|
+
}
|
|
34468
35295
|
}
|
|
35296
|
+
out.push(failed ? { channelType: "slack", accountId, chatId: explicitChatId, ok: false, error: failed } : { channelType: "slack", accountId, chatId: explicitChatId, ok: true });
|
|
34469
35297
|
}
|
|
34470
35298
|
for (const filePath of media?.paths ?? []) {
|
|
34471
35299
|
if (!isMediaPathAllowed(filePath, media.workspacePath)) {
|
|
@@ -34504,6 +35332,9 @@ var init_cron_dispatcher3 = __esm({
|
|
|
34504
35332
|
init_channel_db();
|
|
34505
35333
|
init_accounts5();
|
|
34506
35334
|
init_api2();
|
|
35335
|
+
init_event_adapter3();
|
|
35336
|
+
init_chunk();
|
|
35337
|
+
init_markdown();
|
|
34507
35338
|
init_media();
|
|
34508
35339
|
init_dispatcher();
|
|
34509
35340
|
}
|
|
@@ -34581,16 +35412,16 @@ var init_accounts6 = __esm({
|
|
|
34581
35412
|
});
|
|
34582
35413
|
|
|
34583
35414
|
// ../server/dist/channels/feishu/event-adapter.js
|
|
34584
|
-
var
|
|
35415
|
+
var FEISHU_TEXT_LIMIT, FeishuResponder;
|
|
34585
35416
|
var init_event_adapter4 = __esm({
|
|
34586
35417
|
"../server/dist/channels/feishu/event-adapter.js"() {
|
|
34587
35418
|
"use strict";
|
|
34588
35419
|
init_markdown();
|
|
34589
35420
|
init_responder();
|
|
34590
|
-
|
|
35421
|
+
FEISHU_TEXT_LIMIT = 4500;
|
|
34591
35422
|
FeishuResponder = class extends ChunkedResponder {
|
|
34592
35423
|
constructor(deps) {
|
|
34593
|
-
super(deps, { limit:
|
|
35424
|
+
super(deps, { limit: FEISHU_TEXT_LIMIT, logTag: "Feishu", format: formatForFeishu });
|
|
34594
35425
|
}
|
|
34595
35426
|
};
|
|
34596
35427
|
}
|
|
@@ -34692,10 +35523,10 @@ async function replyMessage(args) {
|
|
|
34692
35523
|
return { message_id: parsed.data?.message_id ?? "" };
|
|
34693
35524
|
}
|
|
34694
35525
|
async function uploadImage(args) {
|
|
34695
|
-
const
|
|
34696
|
-
const
|
|
34697
|
-
const buf =
|
|
34698
|
-
const filename =
|
|
35526
|
+
const fs81 = await import("node:fs");
|
|
35527
|
+
const path90 = await import("node:path");
|
|
35528
|
+
const buf = fs81.readFileSync(args.filePath);
|
|
35529
|
+
const filename = path90.basename(args.filePath);
|
|
34699
35530
|
const send2 = async (token) => {
|
|
34700
35531
|
const form = new FormData();
|
|
34701
35532
|
form.append("image_type", "message");
|
|
@@ -34711,10 +35542,10 @@ async function uploadImage(args) {
|
|
|
34711
35542
|
return { imageKey: parsed.data.image_key };
|
|
34712
35543
|
}
|
|
34713
35544
|
async function uploadFile2(args) {
|
|
34714
|
-
const
|
|
34715
|
-
const
|
|
34716
|
-
const buf =
|
|
34717
|
-
const filename = args.filename ??
|
|
35545
|
+
const fs81 = await import("node:fs");
|
|
35546
|
+
const path90 = await import("node:path");
|
|
35547
|
+
const buf = fs81.readFileSync(args.filePath);
|
|
35548
|
+
const filename = args.filename ?? path90.basename(args.filePath);
|
|
34718
35549
|
const send2 = async (token) => {
|
|
34719
35550
|
const form = new FormData();
|
|
34720
35551
|
form.append("file_type", args.fileType);
|
|
@@ -34781,7 +35612,7 @@ var init_api3 = __esm({
|
|
|
34781
35612
|
});
|
|
34782
35613
|
|
|
34783
35614
|
// ../server/dist/channels/feishu/handler.js
|
|
34784
|
-
import
|
|
35615
|
+
import path85 from "node:path";
|
|
34785
35616
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
34786
35617
|
function buildSessionPrefixForThread2(chatId, rootId) {
|
|
34787
35618
|
return sessionPrefix("feishu", `${chatId}:${rootId}`);
|
|
@@ -34984,7 +35815,7 @@ function startFeishuChannel(deps) {
|
|
|
34984
35815
|
const route = bridge.getRoute(sessionId);
|
|
34985
35816
|
if (!account || !route)
|
|
34986
35817
|
return;
|
|
34987
|
-
const resolved =
|
|
35818
|
+
const resolved = path85.resolve(filePath);
|
|
34988
35819
|
await sendMediaOrReport({
|
|
34989
35820
|
filePath,
|
|
34990
35821
|
account,
|
|
@@ -35151,7 +35982,7 @@ async function replyToInbound(args) {
|
|
|
35151
35982
|
async function sendFeishuMedia(args) {
|
|
35152
35983
|
const { account, inboundMessageId, isP2P, chatId, filePath } = args;
|
|
35153
35984
|
const cls = classifyMedia(filePath);
|
|
35154
|
-
const ext =
|
|
35985
|
+
const ext = path85.extname(filePath).toLowerCase();
|
|
35155
35986
|
if (cls === "image") {
|
|
35156
35987
|
const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
|
|
35157
35988
|
await replyToInboundRaw({
|
|
@@ -35412,25 +36243,28 @@ async function dispatch4(accountId, text6, explicitChatId) {
|
|
|
35412
36243
|
throw new Error('feishu cron target requires an explicit chatId (e.g. "oc_abcd1234"). Create the cron from inside a feishu chat to auto-pin, or pass --targets feishu:<accountId>:<chatId>.');
|
|
35413
36244
|
}
|
|
35414
36245
|
const { chatId } = parseChatId2(explicitChatId);
|
|
35415
|
-
|
|
35416
|
-
|
|
35417
|
-
|
|
35418
|
-
|
|
35419
|
-
|
|
35420
|
-
|
|
35421
|
-
|
|
35422
|
-
|
|
35423
|
-
|
|
35424
|
-
|
|
35425
|
-
|
|
35426
|
-
|
|
35427
|
-
|
|
35428
|
-
|
|
35429
|
-
|
|
35430
|
-
|
|
35431
|
-
|
|
35432
|
-
|
|
36246
|
+
const chunks = splitText(text6, FEISHU_TEXT_LIMIT);
|
|
36247
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
36248
|
+
try {
|
|
36249
|
+
await sendMessage3({
|
|
36250
|
+
appId: acct.appId,
|
|
36251
|
+
appSecret: acct.appSecret,
|
|
36252
|
+
receiveIdType: "chat_id",
|
|
36253
|
+
receiveId: chatId,
|
|
36254
|
+
msgType: "text",
|
|
36255
|
+
content: { text: formatForFeishu(chunk) }
|
|
36256
|
+
});
|
|
36257
|
+
} catch (err) {
|
|
36258
|
+
return [{
|
|
36259
|
+
channelType: "feishu",
|
|
36260
|
+
accountId,
|
|
36261
|
+
chatId: explicitChatId,
|
|
36262
|
+
ok: false,
|
|
36263
|
+
error: `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`
|
|
36264
|
+
}];
|
|
36265
|
+
}
|
|
35433
36266
|
}
|
|
36267
|
+
return [{ channelType: "feishu", accountId, chatId: explicitChatId, ok: true }];
|
|
35434
36268
|
}
|
|
35435
36269
|
function listTargets4() {
|
|
35436
36270
|
const cdb = getChannelDb();
|
|
@@ -35452,6 +36286,9 @@ var init_cron_dispatcher4 = __esm({
|
|
|
35452
36286
|
init_channel_db();
|
|
35453
36287
|
init_accounts6();
|
|
35454
36288
|
init_api3();
|
|
36289
|
+
init_event_adapter4();
|
|
36290
|
+
init_chunk();
|
|
36291
|
+
init_markdown();
|
|
35455
36292
|
init_dispatcher();
|
|
35456
36293
|
}
|
|
35457
36294
|
});
|
|
@@ -35525,23 +36362,23 @@ var init_accounts7 = __esm({
|
|
|
35525
36362
|
});
|
|
35526
36363
|
|
|
35527
36364
|
// ../server/dist/channels/wecom/event-adapter.js
|
|
35528
|
-
var
|
|
36365
|
+
var WECOM_TEXT_LIMIT, WecomResponder;
|
|
35529
36366
|
var init_event_adapter5 = __esm({
|
|
35530
36367
|
"../server/dist/channels/wecom/event-adapter.js"() {
|
|
35531
36368
|
"use strict";
|
|
35532
36369
|
init_responder();
|
|
35533
|
-
|
|
36370
|
+
WECOM_TEXT_LIMIT = 5e3;
|
|
35534
36371
|
WecomResponder = class extends ChunkedResponder {
|
|
35535
36372
|
constructor(deps) {
|
|
35536
|
-
super(deps, { limit:
|
|
36373
|
+
super(deps, { limit: WECOM_TEXT_LIMIT, logTag: "WeCom" });
|
|
35537
36374
|
}
|
|
35538
36375
|
};
|
|
35539
36376
|
}
|
|
35540
36377
|
});
|
|
35541
36378
|
|
|
35542
36379
|
// ../server/dist/channels/wecom/handler.js
|
|
35543
|
-
import
|
|
35544
|
-
import
|
|
36380
|
+
import path86 from "node:path";
|
|
36381
|
+
import fs78 from "node:fs/promises";
|
|
35545
36382
|
import { WSClient as WSClient2, generateReqId } from "@wecom/aibot-node-sdk";
|
|
35546
36383
|
function pickConversation(body) {
|
|
35547
36384
|
const userid = body.from?.userid ?? "unknown";
|
|
@@ -35664,13 +36501,13 @@ async function replyText(wsClient, route, text6) {
|
|
|
35664
36501
|
}
|
|
35665
36502
|
async function sendWecomMedia(args) {
|
|
35666
36503
|
const { wsClient, route, filePath } = args;
|
|
35667
|
-
const ext =
|
|
36504
|
+
const ext = path86.extname(filePath).toLowerCase();
|
|
35668
36505
|
const cls = classifyMedia(filePath);
|
|
35669
36506
|
const type = cls === "image" && [".png", ".jpg", ".jpeg", ".gif"].includes(ext) ? "image" : cls === "video" && ext === ".mp4" ? "video" : "file";
|
|
35670
|
-
const buf = await
|
|
36507
|
+
const buf = await fs78.readFile(filePath);
|
|
35671
36508
|
if (buf.length > MAX_WECOM_DOWNLOAD_BYTES)
|
|
35672
36509
|
throw new Error("file exceeds 20MB");
|
|
35673
|
-
const { media_id: mediaId } = await wsClient.uploadMedia(buf, { type, filename:
|
|
36510
|
+
const { media_id: mediaId } = await wsClient.uploadMedia(buf, { type, filename: path86.basename(filePath) });
|
|
35674
36511
|
if (route.reqId)
|
|
35675
36512
|
await wsClient.replyMedia({ headers: { req_id: route.reqId } }, type, mediaId);
|
|
35676
36513
|
else
|
|
@@ -35715,7 +36552,7 @@ function startWecomChannel(deps) {
|
|
|
35715
36552
|
const wsClient = states.get(accountId)?.wsClient;
|
|
35716
36553
|
if (!account || !route || !wsClient)
|
|
35717
36554
|
return;
|
|
35718
|
-
const resolved =
|
|
36555
|
+
const resolved = path86.resolve(filePath);
|
|
35719
36556
|
await sendMediaOrReport({
|
|
35720
36557
|
filePath,
|
|
35721
36558
|
account,
|
|
@@ -36039,18 +36876,21 @@ async function dispatch5(accountId, text6, explicitChatId) {
|
|
|
36039
36876
|
if (!client) {
|
|
36040
36877
|
return [{ channelType: "wecom", accountId, chatId: explicitChatId, ok: false, error: "wecom long-connect not active" }];
|
|
36041
36878
|
}
|
|
36042
|
-
|
|
36043
|
-
|
|
36044
|
-
|
|
36045
|
-
|
|
36046
|
-
|
|
36047
|
-
|
|
36048
|
-
|
|
36049
|
-
|
|
36050
|
-
|
|
36051
|
-
|
|
36052
|
-
|
|
36879
|
+
const chunks = splitText(text6, WECOM_TEXT_LIMIT);
|
|
36880
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
36881
|
+
try {
|
|
36882
|
+
await client.sendMessage(explicitChatId, { msgtype: "markdown", markdown: { content: chunk } });
|
|
36883
|
+
} catch (err) {
|
|
36884
|
+
return [{
|
|
36885
|
+
channelType: "wecom",
|
|
36886
|
+
accountId,
|
|
36887
|
+
chatId: explicitChatId,
|
|
36888
|
+
ok: false,
|
|
36889
|
+
error: `chunk ${i + 1}/${chunks.length}: ${err instanceof Error ? err.message : String(err)}`
|
|
36890
|
+
}];
|
|
36891
|
+
}
|
|
36053
36892
|
}
|
|
36893
|
+
return [{ channelType: "wecom", accountId, chatId: explicitChatId, ok: true }];
|
|
36054
36894
|
}
|
|
36055
36895
|
function listTargets5() {
|
|
36056
36896
|
const cdb = getChannelDb();
|
|
@@ -36072,6 +36912,8 @@ var init_cron_dispatcher5 = __esm({
|
|
|
36072
36912
|
init_channel_db();
|
|
36073
36913
|
init_accounts7();
|
|
36074
36914
|
init_handler7();
|
|
36915
|
+
init_event_adapter5();
|
|
36916
|
+
init_chunk();
|
|
36075
36917
|
init_dispatcher();
|
|
36076
36918
|
}
|
|
36077
36919
|
});
|
|
@@ -36116,6 +36958,64 @@ var init_descriptors = __esm({
|
|
|
36116
36958
|
}
|
|
36117
36959
|
});
|
|
36118
36960
|
|
|
36961
|
+
// ../server/dist/middleware/compress.js
|
|
36962
|
+
import { compress, COMPRESSIBLE_CONTENT_TYPE_REGEX } from "hono/compress";
|
|
36963
|
+
function compressResponses(threshold) {
|
|
36964
|
+
const inner = compress({ threshold });
|
|
36965
|
+
return (c, next) => inner(c, async () => {
|
|
36966
|
+
await next();
|
|
36967
|
+
if (c.res.status === 304)
|
|
36968
|
+
varyAcceptEncoding(c);
|
|
36969
|
+
else
|
|
36970
|
+
await setProbedLength(c, threshold);
|
|
36971
|
+
});
|
|
36972
|
+
}
|
|
36973
|
+
function varyAcceptEncoding(c) {
|
|
36974
|
+
const vary = c.res.headers.get("Vary");
|
|
36975
|
+
if (vary === "*" || vary && VARY_ACCEPT_ENCODING_RE.test(vary))
|
|
36976
|
+
return;
|
|
36977
|
+
c.header("Vary", vary ? `${vary}, Accept-Encoding` : "Accept-Encoding");
|
|
36978
|
+
}
|
|
36979
|
+
async function setProbedLength(c, threshold) {
|
|
36980
|
+
const res = c.res;
|
|
36981
|
+
const type = res.headers.get("Content-Type");
|
|
36982
|
+
if (res.headers.has("Content-Length") || res.headers.has("Content-Encoding") || res.headers.has("Transfer-Encoding") || c.req.method === "HEAD" || res.status === 204 || res.status === 206 || !res.body || !type || !COMPRESSIBLE_CONTENT_TYPE_REGEX.test(type))
|
|
36983
|
+
return;
|
|
36984
|
+
const reader = res.body.getReader();
|
|
36985
|
+
const head = [];
|
|
36986
|
+
let size = 0;
|
|
36987
|
+
while (size < threshold) {
|
|
36988
|
+
const { done, value } = await reader.read();
|
|
36989
|
+
if (done) {
|
|
36990
|
+
c.res = new Response(Buffer.concat(head, size), res);
|
|
36991
|
+
c.res.headers.set("Content-Length", String(size));
|
|
36992
|
+
return;
|
|
36993
|
+
}
|
|
36994
|
+
head.push(value);
|
|
36995
|
+
size += value.byteLength;
|
|
36996
|
+
}
|
|
36997
|
+
c.res = new Response(new ReadableStream({
|
|
36998
|
+
async pull(ctrl) {
|
|
36999
|
+
const chunk = head.shift();
|
|
37000
|
+
if (chunk)
|
|
37001
|
+
return ctrl.enqueue(chunk);
|
|
37002
|
+
const { done, value } = await reader.read();
|
|
37003
|
+
if (done)
|
|
37004
|
+
ctrl.close();
|
|
37005
|
+
else
|
|
37006
|
+
ctrl.enqueue(value);
|
|
37007
|
+
},
|
|
37008
|
+
cancel: (reason) => reader.cancel(reason)
|
|
37009
|
+
}), res);
|
|
37010
|
+
}
|
|
37011
|
+
var VARY_ACCEPT_ENCODING_RE;
|
|
37012
|
+
var init_compress = __esm({
|
|
37013
|
+
"../server/dist/middleware/compress.js"() {
|
|
37014
|
+
"use strict";
|
|
37015
|
+
VARY_ACCEPT_ENCODING_RE = /(?:^|,)\s*accept-encoding\s*(?:,|$)/i;
|
|
37016
|
+
}
|
|
37017
|
+
});
|
|
37018
|
+
|
|
36119
37019
|
// ../server/dist/listen-banner.js
|
|
36120
37020
|
function formatListenBanner(address, port, version) {
|
|
36121
37021
|
if (address === "::" || address === "0.0.0.0") {
|
|
@@ -36137,21 +37037,20 @@ var init_listen_banner = __esm({
|
|
|
36137
37037
|
var dist_exports = {};
|
|
36138
37038
|
import { Hono as Hono26 } from "hono";
|
|
36139
37039
|
import { cors } from "hono/cors";
|
|
36140
|
-
import { compress } from "hono/compress";
|
|
36141
37040
|
import { serve } from "@hono/node-server";
|
|
36142
37041
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
36143
|
-
import { WebSocketServer as
|
|
36144
|
-
import
|
|
36145
|
-
import
|
|
37042
|
+
import { WebSocketServer as WebSocketServer4 } from "ws";
|
|
37043
|
+
import path87 from "node:path";
|
|
37044
|
+
import fs79 from "node:fs";
|
|
36146
37045
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
36147
37046
|
import { homedir as homedir25 } from "node:os";
|
|
36148
37047
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
36149
37048
|
function findProjectRoot() {
|
|
36150
37049
|
let dir = process.cwd();
|
|
36151
37050
|
for (let i = 0; i < 32; i++) {
|
|
36152
|
-
if (
|
|
37051
|
+
if (fs79.existsSync(path87.join(dir, "pnpm-workspace.yaml")))
|
|
36153
37052
|
return dir;
|
|
36154
|
-
const parent =
|
|
37053
|
+
const parent = path87.dirname(dir);
|
|
36155
37054
|
if (parent === dir)
|
|
36156
37055
|
break;
|
|
36157
37056
|
dir = parent;
|
|
@@ -36161,15 +37060,15 @@ function findProjectRoot() {
|
|
|
36161
37060
|
return process.cwd();
|
|
36162
37061
|
}
|
|
36163
37062
|
function acquireSingleInstanceLock(lockFile) {
|
|
36164
|
-
|
|
36165
|
-
const fd =
|
|
37063
|
+
fs79.mkdirSync(path87.dirname(lockFile), { recursive: true });
|
|
37064
|
+
const fd = fs79.openSync(lockFile, fs79.constants.O_RDWR | fs79.constants.O_CREAT, 420);
|
|
36166
37065
|
const result = spawnSync4("flock", ["-n", "3"], {
|
|
36167
37066
|
stdio: ["ignore", "ignore", "ignore", fd]
|
|
36168
37067
|
});
|
|
36169
37068
|
if (result.error) {
|
|
36170
37069
|
const content = readLockPid(fd);
|
|
36171
37070
|
if (content > 0 && isProcessAlive(content)) {
|
|
36172
|
-
|
|
37071
|
+
fs79.closeSync(fd);
|
|
36173
37072
|
console.error(`[Server] another halo server is already running (pid ${content}). kill it first.`);
|
|
36174
37073
|
process.exit(1);
|
|
36175
37074
|
}
|
|
@@ -36178,19 +37077,19 @@ function acquireSingleInstanceLock(lockFile) {
|
|
|
36178
37077
|
if (content > 0 && !isProcessAlive(content)) {
|
|
36179
37078
|
console.warn(`[Server] removing stale server.lock (pid ${content} not running)`);
|
|
36180
37079
|
} else {
|
|
36181
|
-
|
|
37080
|
+
fs79.closeSync(fd);
|
|
36182
37081
|
console.error(`[Server] another halo server is already running${content > 0 ? ` (pid ${content})` : ""}. kill it first.`);
|
|
36183
37082
|
process.exit(1);
|
|
36184
37083
|
}
|
|
36185
37084
|
}
|
|
36186
|
-
|
|
36187
|
-
|
|
36188
|
-
|
|
37085
|
+
fs79.ftruncateSync(fd);
|
|
37086
|
+
fs79.writeSync(fd, String(process.pid));
|
|
37087
|
+
fs79.fsyncSync(fd);
|
|
36189
37088
|
}
|
|
36190
37089
|
function readLockPid(fd) {
|
|
36191
37090
|
try {
|
|
36192
37091
|
const buf = Buffer.alloc(32);
|
|
36193
|
-
const n =
|
|
37092
|
+
const n = fs79.readSync(fd, buf, 0, 32, 0);
|
|
36194
37093
|
return parseInt(buf.subarray(0, n).toString("utf-8").trim(), 10) || 0;
|
|
36195
37094
|
} catch {
|
|
36196
37095
|
return 0;
|
|
@@ -36218,7 +37117,7 @@ function isProcessAlive(pid) {
|
|
|
36218
37117
|
}
|
|
36219
37118
|
}
|
|
36220
37119
|
function readGitSha() {
|
|
36221
|
-
if (!
|
|
37120
|
+
if (!fs79.existsSync(path87.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs79.existsSync(path87.join(PROJECT_ROOT, ".git")))
|
|
36222
37121
|
return null;
|
|
36223
37122
|
try {
|
|
36224
37123
|
const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
|
|
@@ -36239,14 +37138,14 @@ function applySandboxPaths() {
|
|
|
36239
37138
|
}
|
|
36240
37139
|
function resolveFrontendDir() {
|
|
36241
37140
|
if (process.env.HALO_FRONTEND_DIR)
|
|
36242
|
-
return
|
|
36243
|
-
const bundled =
|
|
36244
|
-
if (
|
|
37141
|
+
return path87.resolve(process.env.HALO_FRONTEND_DIR);
|
|
37142
|
+
const bundled = path87.resolve(__dirname3, "..", "admin-out");
|
|
37143
|
+
if (fs79.existsSync(path87.join(bundled, "index.html")))
|
|
36245
37144
|
return bundled;
|
|
36246
|
-
const sibling =
|
|
36247
|
-
if (
|
|
37145
|
+
const sibling = path87.resolve(__dirname3, "..", "..", "admin", "out");
|
|
37146
|
+
if (fs79.existsSync(path87.join(sibling, "index.html")))
|
|
36248
37147
|
return sibling;
|
|
36249
|
-
return
|
|
37148
|
+
return path87.resolve(PROJECT_ROOT, "packages", "admin", "out");
|
|
36250
37149
|
}
|
|
36251
37150
|
async function gracefulShutdown(signal) {
|
|
36252
37151
|
console.log(`[Server] Received ${signal}, shutting down gracefully...`);
|
|
@@ -36262,6 +37161,7 @@ async function gracefulShutdown(signal) {
|
|
|
36262
37161
|
});
|
|
36263
37162
|
wss.close();
|
|
36264
37163
|
transcribe?.close();
|
|
37164
|
+
stt?.close();
|
|
36265
37165
|
jupyterChannels?.close();
|
|
36266
37166
|
await jupyter?.stop();
|
|
36267
37167
|
if (server && typeof server.close === "function") {
|
|
@@ -36280,7 +37180,7 @@ async function gracefulShutdown(signal) {
|
|
|
36280
37180
|
await shutdownObservability();
|
|
36281
37181
|
process.exit(0);
|
|
36282
37182
|
}
|
|
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;
|
|
37183
|
+
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
37184
|
var init_dist2 = __esm({
|
|
36285
37185
|
async "../server/dist/index.js"() {
|
|
36286
37186
|
"use strict";
|
|
@@ -36297,6 +37197,7 @@ var init_dist2 = __esm({
|
|
|
36297
37197
|
init_metrics();
|
|
36298
37198
|
init_commands3();
|
|
36299
37199
|
init_transcribe_ws();
|
|
37200
|
+
init_stt_ws();
|
|
36300
37201
|
init_jupyter();
|
|
36301
37202
|
init_manager();
|
|
36302
37203
|
init_upgrade_router();
|
|
@@ -36327,6 +37228,7 @@ var init_dist2 = __esm({
|
|
|
36327
37228
|
init_registry4();
|
|
36328
37229
|
init_descriptors();
|
|
36329
37230
|
init_auth();
|
|
37231
|
+
init_compress();
|
|
36330
37232
|
init_logger();
|
|
36331
37233
|
init_listen_banner();
|
|
36332
37234
|
init_otel();
|
|
@@ -36334,13 +37236,13 @@ var init_dist2 = __esm({
|
|
|
36334
37236
|
init_sandbox();
|
|
36335
37237
|
init_init();
|
|
36336
37238
|
init_git_ssh();
|
|
36337
|
-
__dirname3 =
|
|
37239
|
+
__dirname3 = path87.dirname(fileURLToPath3(import.meta.url));
|
|
36338
37240
|
PORT = config.server.port;
|
|
36339
|
-
HALO_VERSION = "1.6.
|
|
37241
|
+
HALO_VERSION = "1.6.6";
|
|
36340
37242
|
PROJECT_ROOT = findProjectRoot();
|
|
36341
|
-
HALO_HOME4 =
|
|
37243
|
+
HALO_HOME4 = path87.join(homedir25(), ".halo");
|
|
36342
37244
|
GIT_SHA = readGitSha();
|
|
36343
|
-
if (!
|
|
37245
|
+
if (!fs79.existsSync(path87.join(HALO_HOME4, "global", ".template-version"))) {
|
|
36344
37246
|
process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
|
|
36345
37247
|
process.exit(1);
|
|
36346
37248
|
}
|
|
@@ -36351,7 +37253,7 @@ var init_dist2 = __esm({
|
|
|
36351
37253
|
process.exit(1);
|
|
36352
37254
|
}
|
|
36353
37255
|
if (!AGENTCORE)
|
|
36354
|
-
acquireSingleInstanceLock(
|
|
37256
|
+
acquireSingleInstanceLock(path87.join(HALO_HOME4, "global", "server.lock"));
|
|
36355
37257
|
commandRegistry.assertDispatchCommands(DISPATCH_COMMANDS);
|
|
36356
37258
|
await initObservability();
|
|
36357
37259
|
initLogger();
|
|
@@ -36380,7 +37282,7 @@ var init_dist2 = __esm({
|
|
|
36380
37282
|
maxAge: 86400,
|
|
36381
37283
|
credentials: allowlist.length > 0
|
|
36382
37284
|
}));
|
|
36383
|
-
app.use("/*",
|
|
37285
|
+
app.use("/*", compressResponses(1024));
|
|
36384
37286
|
jupyter = AGENTCORE ? null : createJupyterManager();
|
|
36385
37287
|
if (!AGENTCORE) {
|
|
36386
37288
|
app.use("/api/*", authMiddleware());
|
|
@@ -36421,12 +37323,12 @@ var init_dist2 = __esm({
|
|
|
36421
37323
|
app.route("/api", createJupyterRoutes(jupyter));
|
|
36422
37324
|
}
|
|
36423
37325
|
start2();
|
|
36424
|
-
channelDb = createChannelDb(
|
|
37326
|
+
channelDb = createChannelDb(path87.join(HALO_HOME4, "secrets"));
|
|
36425
37327
|
setChannelDb(channelDb);
|
|
36426
|
-
setEvoDb(createEvoDb(
|
|
36427
|
-
setCronDb(createCronDb(
|
|
37328
|
+
setEvoDb(createEvoDb(path87.join(HALO_HOME4, "global")));
|
|
37329
|
+
setCronDb(createCronDb(path87.join(HALO_HOME4, "global")));
|
|
36428
37330
|
if (!AGENTCORE)
|
|
36429
|
-
setRunsDb(createRunsDb(
|
|
37331
|
+
setRunsDb(createRunsDb(path87.join(HALO_HOME4, "global")));
|
|
36430
37332
|
setEvoSpawner(realEvoSpawner);
|
|
36431
37333
|
if (!AGENTCORE) {
|
|
36432
37334
|
startEvoTicker();
|
|
@@ -36441,7 +37343,7 @@ var init_dist2 = __esm({
|
|
|
36441
37343
|
setCronSessionRegistry(registry);
|
|
36442
37344
|
if (OWNS_RUNTIMES && !AGENTCORE) {
|
|
36443
37345
|
for (const ws of listRunningWorkspaces()) {
|
|
36444
|
-
if (!
|
|
37346
|
+
if (!fs79.existsSync(path87.join(ws, ".halo")))
|
|
36445
37347
|
continue;
|
|
36446
37348
|
if (!claimWorkspaceRuntime(ws))
|
|
36447
37349
|
continue;
|
|
@@ -36467,7 +37369,7 @@ var init_dist2 = __esm({
|
|
|
36467
37369
|
startCronDaemon();
|
|
36468
37370
|
}
|
|
36469
37371
|
if (!AGENTCORE) {
|
|
36470
|
-
setA2ADb(createA2ADb(
|
|
37372
|
+
setA2ADb(createA2ADb(path87.join(HALO_HOME4, "global")));
|
|
36471
37373
|
setA2AOutboundRegistry(registry);
|
|
36472
37374
|
app.route("/a2a", createA2ARoutes({ registry, ownsRuntimes: OWNS_RUNTIMES }));
|
|
36473
37375
|
app.route("/", createA2APushRoutes());
|
|
@@ -36483,11 +37385,11 @@ var init_dist2 = __esm({
|
|
|
36483
37385
|
}
|
|
36484
37386
|
FRONTEND_DIR = resolveFrontendDir();
|
|
36485
37387
|
if (!AGENTCORE) {
|
|
36486
|
-
app.use("/*", serveStatic({ root:
|
|
37388
|
+
app.use("/*", serveStatic({ root: path87.relative(process.cwd(), FRONTEND_DIR) }));
|
|
36487
37389
|
app.get("/*", (c) => {
|
|
36488
|
-
const indexPath =
|
|
37390
|
+
const indexPath = path87.join(FRONTEND_DIR, "index.html");
|
|
36489
37391
|
try {
|
|
36490
|
-
const html =
|
|
37392
|
+
const html = fs79.readFileSync(indexPath, "utf-8");
|
|
36491
37393
|
return c.html(html);
|
|
36492
37394
|
} catch {
|
|
36493
37395
|
return c.text("Frontend not built. Run: cd packages/admin && npx next build", 503);
|
|
@@ -36506,7 +37408,7 @@ var init_dist2 = __esm({
|
|
|
36506
37408
|
for (const line of AGENTCORE ? lines.slice(0, 1) : lines)
|
|
36507
37409
|
logAlways(line);
|
|
36508
37410
|
});
|
|
36509
|
-
wss = new
|
|
37411
|
+
wss = new WebSocketServer4({
|
|
36510
37412
|
noServer: true,
|
|
36511
37413
|
verifyClient: (info, callback) => {
|
|
36512
37414
|
const token = getTokenFromCookieHeader(info.req.headers.cookie);
|
|
@@ -36522,12 +37424,15 @@ var init_dist2 = __esm({
|
|
|
36522
37424
|
setBroadcastWss(wss);
|
|
36523
37425
|
}
|
|
36524
37426
|
transcribe = AGENTCORE ? null : createTranscribeProxy();
|
|
37427
|
+
stt = AGENTCORE ? null : createSttProxy();
|
|
36525
37428
|
jupyterChannels = jupyter ? createJupyterChannels(jupyter) : null;
|
|
36526
37429
|
upgradeRoutes = new Map(AGENTCORE ? [] : [
|
|
36527
37430
|
["/ws", (req, socket, head) => wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req))]
|
|
36528
37431
|
]);
|
|
36529
37432
|
if (transcribe)
|
|
36530
37433
|
upgradeRoutes.set(TRANSCRIBE_PATH, transcribe.handleUpgrade);
|
|
37434
|
+
if (stt)
|
|
37435
|
+
upgradeRoutes.set(STT_PATH, stt.handleUpgrade);
|
|
36531
37436
|
if (jupyterChannels)
|
|
36532
37437
|
upgradeRoutes.set(JUPYTER_CHANNELS_PATH, jupyterChannels.handleUpgrade);
|
|
36533
37438
|
server.on("upgrade", createUpgradeRouter(upgradeRoutes));
|
|
@@ -36695,8 +37600,8 @@ var init_halo_client = __esm({
|
|
|
36695
37600
|
}
|
|
36696
37601
|
return await res.json();
|
|
36697
37602
|
}
|
|
36698
|
-
sessionUrl(
|
|
36699
|
-
const url = new URL(`${this.opts.baseUrl}${
|
|
37603
|
+
sessionUrl(path90, workspace, sessionId) {
|
|
37604
|
+
const url = new URL(`${this.opts.baseUrl}${path90}`);
|
|
36700
37605
|
url.searchParams.set("workspace", workspace);
|
|
36701
37606
|
url.searchParams.set("sessionId", sessionId);
|
|
36702
37607
|
return url;
|
|
@@ -36844,7 +37749,7 @@ var init_version = __esm({
|
|
|
36844
37749
|
});
|
|
36845
37750
|
|
|
36846
37751
|
// ../acp-adapter/dist/adapter.js
|
|
36847
|
-
import
|
|
37752
|
+
import path88 from "node:path";
|
|
36848
37753
|
function composePrompt(blocks) {
|
|
36849
37754
|
const segments = [];
|
|
36850
37755
|
let run = "";
|
|
@@ -37037,7 +37942,7 @@ var init_adapter = __esm({
|
|
|
37037
37942
|
if (cursor !== void 0 && !/^\d+$/.test(cursor))
|
|
37038
37943
|
throw newError(`invalid cursor: ${cursor}`, -32602);
|
|
37039
37944
|
const page = await this.client.listSessions(this.config.workspace, cursor === void 0 ? void 0 : Number(cursor));
|
|
37040
|
-
if (p.cwd !== void 0 &&
|
|
37945
|
+
if (p.cwd !== void 0 && path88.resolve(p.cwd) !== path88.resolve(page.workspace))
|
|
37041
37946
|
return { sessions: [] };
|
|
37042
37947
|
return {
|
|
37043
37948
|
sessions: page.sessions.map((s) => ({
|
|
@@ -37651,8 +38556,8 @@ process.emitWarning = (warning, ...args) => {
|
|
|
37651
38556
|
|
|
37652
38557
|
// src/index.ts
|
|
37653
38558
|
init_server_supervisor();
|
|
37654
|
-
import
|
|
37655
|
-
import
|
|
38559
|
+
import path89 from "node:path";
|
|
38560
|
+
import fs80 from "node:fs";
|
|
37656
38561
|
import { homedir as homedir26, userInfo } from "node:os";
|
|
37657
38562
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
37658
38563
|
|
|
@@ -37943,7 +38848,7 @@ function installUserUnit(opts, run = runCommand) {
|
|
|
37943
38848
|
}
|
|
37944
38849
|
|
|
37945
38850
|
// src/index.ts
|
|
37946
|
-
var VERSION = "1.6.
|
|
38851
|
+
var VERSION = "1.6.6";
|
|
37947
38852
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
37948
38853
|
|
|
37949
38854
|
Commands:
|
|
@@ -38285,8 +39190,8 @@ async function cmdSetup(argv = []) {
|
|
|
38285
39190
|
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
39191
|
const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
|
|
38287
39192
|
const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
|
|
38288
|
-
const haloHome2 =
|
|
38289
|
-
const globalDir2 =
|
|
39193
|
+
const haloHome2 = path89.join(homedir26(), ".halo");
|
|
39194
|
+
const globalDir2 = path89.join(haloHome2, "global");
|
|
38290
39195
|
ensureHaloHome2(haloHome2);
|
|
38291
39196
|
process.stderr.write(`[setup] ${haloHome2}/ ready
|
|
38292
39197
|
`);
|
|
@@ -38639,7 +39544,7 @@ async function cmdUpgrade() {
|
|
|
38639
39544
|
`);
|
|
38640
39545
|
process.exit(1);
|
|
38641
39546
|
}
|
|
38642
|
-
const broken = verifyNativeModules2(
|
|
39547
|
+
const broken = verifyNativeModules2(path89.join(rootDir, PKG));
|
|
38643
39548
|
if (broken.length > 0) {
|
|
38644
39549
|
process.stderr.write(
|
|
38645
39550
|
`
|
|
@@ -38670,7 +39575,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
|
|
|
38670
39575
|
async function cmdAgents(flags) {
|
|
38671
39576
|
const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
38672
39577
|
await initRuntime2();
|
|
38673
|
-
const workspace =
|
|
39578
|
+
const workspace = path89.resolve(flags.workspace ?? process.cwd());
|
|
38674
39579
|
const agents = await listAgents2(workspace);
|
|
38675
39580
|
if (agents.length === 0) {
|
|
38676
39581
|
process.stderr.write("No agents found.\n");
|
|
@@ -38686,7 +39591,7 @@ async function cmdAgents(flags) {
|
|
|
38686
39591
|
async function cmdSessions(flags) {
|
|
38687
39592
|
const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
38688
39593
|
await initRuntime2();
|
|
38689
|
-
const workspace =
|
|
39594
|
+
const workspace = path89.resolve(flags.workspace ?? process.cwd());
|
|
38690
39595
|
const sessions = listSessions2(workspace);
|
|
38691
39596
|
if (sessions.length === 0) {
|
|
38692
39597
|
process.stderr.write("No sessions found.\n");
|
|
@@ -38717,7 +39622,7 @@ async function cmdModels(argv) {
|
|
|
38717
39622
|
process.exitCode = 1;
|
|
38718
39623
|
return;
|
|
38719
39624
|
}
|
|
38720
|
-
const plan = mod.planModelsInstall(
|
|
39625
|
+
const plan = mod.planModelsInstall(path89.resolve(dir));
|
|
38721
39626
|
for (const line of mod.formatPlan(plan)) process.stdout.write(`${line}
|
|
38722
39627
|
`);
|
|
38723
39628
|
if (plan.needsConfirm && !values.yes) {
|
|
@@ -38826,11 +39731,11 @@ ${stdinContent}` : stdinContent;
|
|
|
38826
39731
|
harness.destroy();
|
|
38827
39732
|
}
|
|
38828
39733
|
}
|
|
38829
|
-
var SERVER_LOCK_PATH =
|
|
38830
|
-
var SERVER_LOG_PATH =
|
|
39734
|
+
var SERVER_LOCK_PATH = path89.join(homedir26(), ".halo", "global", "server.lock");
|
|
39735
|
+
var SERVER_LOG_PATH = path89.join(homedir26(), ".halo", "logs", "server.log");
|
|
38831
39736
|
function readServerPid() {
|
|
38832
39737
|
try {
|
|
38833
|
-
const txt =
|
|
39738
|
+
const txt = fs80.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
|
|
38834
39739
|
const n = parseInt(txt, 10);
|
|
38835
39740
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
38836
39741
|
} catch {
|
|
@@ -38937,8 +39842,8 @@ async function cmdServerStart(argv, restart) {
|
|
|
38937
39842
|
}
|
|
38938
39843
|
if (plan.note) writeDim(plan.note);
|
|
38939
39844
|
const { spawn: spawn5 } = await import("node:child_process");
|
|
38940
|
-
|
|
38941
|
-
const out =
|
|
39845
|
+
fs80.mkdirSync(path89.dirname(SERVER_LOG_PATH), { recursive: true });
|
|
39846
|
+
const out = fs80.openSync(SERVER_LOG_PATH, "a");
|
|
38942
39847
|
const child = spawn5(process.argv[0], [process.argv[1], "server", "start"], {
|
|
38943
39848
|
detached: true,
|
|
38944
39849
|
stdio: ["ignore", out, out],
|
|
@@ -39018,8 +39923,8 @@ ${HELP_AGENTCORE}`);
|
|
|
39018
39923
|
process.exitCode = 1;
|
|
39019
39924
|
return;
|
|
39020
39925
|
}
|
|
39021
|
-
const workspace =
|
|
39022
|
-
if (!
|
|
39926
|
+
const workspace = path89.resolve(raw);
|
|
39927
|
+
if (!fs80.statSync(workspace, { throwIfNoEntry: false })?.isDirectory()) {
|
|
39023
39928
|
process.stderr.write(`Workspace is not a directory: ${workspace}
|
|
39024
39929
|
`);
|
|
39025
39930
|
process.exitCode = 1;
|
|
@@ -39125,7 +40030,7 @@ async function cmdServerLogs(argv) {
|
|
|
39125
40030
|
lines: { type: "string", short: "n" }
|
|
39126
40031
|
}
|
|
39127
40032
|
});
|
|
39128
|
-
if (!
|
|
40033
|
+
if (!fs80.existsSync(SERVER_LOG_PATH)) {
|
|
39129
40034
|
process.stderr.write(`No log file at ${SERVER_LOG_PATH}
|
|
39130
40035
|
`);
|
|
39131
40036
|
return;
|
|
@@ -39143,7 +40048,7 @@ function exitMissingSetup() {
|
|
|
39143
40048
|
process.exit(1);
|
|
39144
40049
|
}
|
|
39145
40050
|
function isHaloHomeReady() {
|
|
39146
|
-
return
|
|
40051
|
+
return fs80.existsSync(path89.join(homedir26(), ".halo", "global", ".template-version"));
|
|
39147
40052
|
}
|
|
39148
40053
|
async function main2() {
|
|
39149
40054
|
const argv = process.argv.slice(2);
|