@turmind/halo 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/212-70bd2971392bde84.js +1 -0
- package/admin-out/_next/static/chunks/app/page-71c576815b4a30d9.js +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +4 -4
- package/bundled-docs/design/agentcore.md +35 -0
- package/bundled-docs/design/cron.md +2 -2
- package/bundled-docs/dev/api.md +2 -2
- package/dist/index.js +731 -483
- package/package.json +1 -1
- package/templates/skills/cron/SKILL.md +8 -0
- package/templates/skills/cron/manage-cron.py +27 -3
- package/admin-out/_next/static/chunks/212-8ac9dfe9c24e512c.js +0 -1
- package/admin-out/_next/static/chunks/app/page-686cd94fdf9df63b.js +0 -1
- /package/admin-out/_next/static/{Qre3-OObLUgfkv8BEU08q → oDvBPFdNAW2vHdHcxQ0lw}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{Qre3-OObLUgfkv8BEU08q → oDvBPFdNAW2vHdHcxQ0lw}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -1658,10 +1658,19 @@ function isAgentDisabled(agentId, workspaceRoot, disabledSet) {
|
|
|
1658
1658
|
return disabledSet.has(`${scope}:${agentId}`);
|
|
1659
1659
|
}
|
|
1660
1660
|
async function loadAgentYaml(agentId, workspaceRoot) {
|
|
1661
|
+
const yamlPath = path6.join(agentSourceDir(agentId, workspaceRoot), "agent.yaml");
|
|
1661
1662
|
try {
|
|
1662
|
-
const content = await fs6.readFile(
|
|
1663
|
-
|
|
1664
|
-
|
|
1663
|
+
const content = await fs6.readFile(yamlPath, "utf-8");
|
|
1664
|
+
const parsed = YAML4.parse(content);
|
|
1665
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
1666
|
+
console.warn(`[AgentLoader] Failed to parse ${yamlPath}: empty or non-object YAML`);
|
|
1667
|
+
return null;
|
|
1668
|
+
}
|
|
1669
|
+
return parsed;
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
if (err.code === "ENOENT")
|
|
1672
|
+
return null;
|
|
1673
|
+
console.warn(`[AgentLoader] Failed to parse ${yamlPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1665
1674
|
return null;
|
|
1666
1675
|
}
|
|
1667
1676
|
}
|
|
@@ -2036,6 +2045,7 @@ __export(init_exports, {
|
|
|
2036
2045
|
mergeAgentYaml: () => mergeAgentYaml,
|
|
2037
2046
|
readInstalledOptionalSkills: () => readInstalledOptionalSkills,
|
|
2038
2047
|
readSeedVersion: () => readSeedVersion,
|
|
2048
|
+
writeFileAtomic: () => writeFileAtomic,
|
|
2039
2049
|
writeInstalledOptionalSkills: () => writeInstalledOptionalSkills
|
|
2040
2050
|
});
|
|
2041
2051
|
import fs7 from "node:fs";
|
|
@@ -2057,10 +2067,28 @@ function readSeedVersion(haloHome2) {
|
|
|
2057
2067
|
return 0;
|
|
2058
2068
|
}
|
|
2059
2069
|
}
|
|
2070
|
+
function writeFileAtomic(filePath, content, mode) {
|
|
2071
|
+
fs7.mkdirSync(path8.dirname(filePath), { recursive: true });
|
|
2072
|
+
const tmpPath = `${filePath}.tmp`;
|
|
2073
|
+
fs7.writeFileSync(tmpPath, content, mode === void 0 ? { encoding: "utf-8" } : { encoding: "utf-8", mode });
|
|
2074
|
+
try {
|
|
2075
|
+
fs7.renameSync(tmpPath, filePath);
|
|
2076
|
+
} catch {
|
|
2077
|
+
fs7.writeFileSync(filePath, content, mode === void 0 ? { encoding: "utf-8" } : { encoding: "utf-8", mode });
|
|
2078
|
+
try {
|
|
2079
|
+
fs7.rmSync(tmpPath, { force: true });
|
|
2080
|
+
} catch {
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2060
2084
|
function copyTemplate(srcAbs, dstAbs) {
|
|
2061
|
-
fs7.mkdirSync(path8.dirname(dstAbs), { recursive: true });
|
|
2062
2085
|
const content = fs7.readFileSync(srcAbs, "utf-8");
|
|
2063
|
-
|
|
2086
|
+
try {
|
|
2087
|
+
if (fs7.readFileSync(dstAbs, "utf-8") === content)
|
|
2088
|
+
return;
|
|
2089
|
+
} catch {
|
|
2090
|
+
}
|
|
2091
|
+
writeFileAtomic(dstAbs, content);
|
|
2064
2092
|
}
|
|
2065
2093
|
function writeIfMissing(filePath, content, mode) {
|
|
2066
2094
|
if (fs7.existsSync(filePath))
|
|
@@ -2075,7 +2103,8 @@ function mergeAgentYaml(srcAbs, dstAbs) {
|
|
|
2075
2103
|
return;
|
|
2076
2104
|
}
|
|
2077
2105
|
try {
|
|
2078
|
-
const
|
|
2106
|
+
const userRaw = fs7.readFileSync(dstAbs, "utf-8");
|
|
2107
|
+
const userDoc = YAML5.parseDocument(userRaw);
|
|
2079
2108
|
const templateDoc = YAML5.parseDocument(fs7.readFileSync(srcAbs, "utf-8"));
|
|
2080
2109
|
const userModel = userDoc.get("model");
|
|
2081
2110
|
if (userModel != null) {
|
|
@@ -2085,7 +2114,10 @@ function mergeAgentYaml(srcAbs, dstAbs) {
|
|
|
2085
2114
|
if (userContext != null) {
|
|
2086
2115
|
templateDoc.set("context", userContext);
|
|
2087
2116
|
}
|
|
2088
|
-
|
|
2117
|
+
const merged = templateDoc.toString();
|
|
2118
|
+
if (merged === userRaw)
|
|
2119
|
+
return;
|
|
2120
|
+
writeFileAtomic(dstAbs, merged);
|
|
2089
2121
|
} catch (err) {
|
|
2090
2122
|
console.log(`[Init] mergeAgentYaml fallback (force-copy) for ${dstAbs}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2091
2123
|
copyTemplate(srcAbs, dstAbs);
|
|
@@ -2368,7 +2400,7 @@ var init_init = __esm({
|
|
|
2368
2400
|
path8.resolve(__dirname, "..", "bundled-docs"),
|
|
2369
2401
|
path8.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
2370
2402
|
];
|
|
2371
|
-
TEMPLATE_VERSION =
|
|
2403
|
+
TEMPLATE_VERSION = 50;
|
|
2372
2404
|
VERSION_FILE = ".template-version";
|
|
2373
2405
|
SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
|
|
2374
2406
|
BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
@@ -4128,27 +4160,27 @@ var init_workspace = __esm({
|
|
|
4128
4160
|
constructor(projectRoot) {
|
|
4129
4161
|
this.projectRoot = projectRoot;
|
|
4130
4162
|
}
|
|
4131
|
-
async readFile(
|
|
4132
|
-
const fullPath = this.validatePath(
|
|
4163
|
+
async readFile(path66) {
|
|
4164
|
+
const fullPath = this.validatePath(path66);
|
|
4133
4165
|
return readFile(fullPath, "utf-8");
|
|
4134
4166
|
}
|
|
4135
|
-
async writeFile(
|
|
4136
|
-
const fullPath = this.validatePath(
|
|
4167
|
+
async writeFile(path66, content) {
|
|
4168
|
+
const fullPath = this.validatePath(path66);
|
|
4137
4169
|
const dir = resolve(fullPath, "..");
|
|
4138
4170
|
await mkdir(dir, { recursive: true });
|
|
4139
4171
|
await writeFile(fullPath, content, "utf-8");
|
|
4140
4172
|
}
|
|
4141
|
-
validatePath(
|
|
4142
|
-
const fullPath = isAbsolute(
|
|
4173
|
+
validatePath(path66) {
|
|
4174
|
+
const fullPath = isAbsolute(path66) ? resolve(path66) : resolve(this.projectRoot, path66);
|
|
4143
4175
|
const root = resolve(this.projectRoot);
|
|
4144
4176
|
if (fullPath !== root && !fullPath.startsWith(root + sep)) {
|
|
4145
|
-
throw new WorkspaceError(`Path "${
|
|
4177
|
+
throw new WorkspaceError(`Path "${path66}" is outside the workspace directory "${this.projectRoot}"`);
|
|
4146
4178
|
}
|
|
4147
4179
|
try {
|
|
4148
4180
|
const real = realpathSync(fullPath);
|
|
4149
4181
|
const realRoot = realpathSync(root);
|
|
4150
4182
|
if (real !== realRoot && !real.startsWith(realRoot + sep)) {
|
|
4151
|
-
throw new WorkspaceError(`Path "${
|
|
4183
|
+
throw new WorkspaceError(`Path "${path66}" is outside the workspace directory "${this.projectRoot}"`);
|
|
4152
4184
|
}
|
|
4153
4185
|
} catch (err) {
|
|
4154
4186
|
if (err instanceof WorkspaceError)
|
|
@@ -4326,12 +4358,12 @@ var init_git_manager = __esm({
|
|
|
4326
4358
|
const code = parts[0];
|
|
4327
4359
|
const status = code[0];
|
|
4328
4360
|
const isRenameOrCopy = status === "R" || status === "C";
|
|
4329
|
-
const
|
|
4330
|
-
if (!
|
|
4361
|
+
const path66 = isRenameOrCopy ? parts[2] : parts[1];
|
|
4362
|
+
if (!path66 || seen.has(path66))
|
|
4331
4363
|
continue;
|
|
4332
|
-
seen.add(
|
|
4364
|
+
seen.add(path66);
|
|
4333
4365
|
files.push({
|
|
4334
|
-
path:
|
|
4366
|
+
path: path66,
|
|
4335
4367
|
status,
|
|
4336
4368
|
from: isRenameOrCopy ? parts[1] : void 0
|
|
4337
4369
|
});
|
|
@@ -4479,9 +4511,9 @@ var init_git_manager = __esm({
|
|
|
4479
4511
|
* empty for an added file or the root commit) and `modified` is the version
|
|
4480
4512
|
* at `commit` (empty for a deletion). The working-tree/staged path above is
|
|
4481
4513
|
* left entirely untouched (commit === undefined preserves existing behavior). */
|
|
4482
|
-
async getFileDiff(
|
|
4514
|
+
async getFileDiff(path66, staged, from, commit) {
|
|
4483
4515
|
if (commit) {
|
|
4484
|
-
const parentPath = from ||
|
|
4516
|
+
const parentPath = from || path66;
|
|
4485
4517
|
let original2 = "";
|
|
4486
4518
|
try {
|
|
4487
4519
|
original2 = await this.git.show([`${commit}^:${parentPath}`]);
|
|
@@ -4490,13 +4522,13 @@ var init_git_manager = __esm({
|
|
|
4490
4522
|
}
|
|
4491
4523
|
let modified2 = "";
|
|
4492
4524
|
try {
|
|
4493
|
-
modified2 = await this.git.show([`${commit}:${
|
|
4525
|
+
modified2 = await this.git.show([`${commit}:${path66}`]);
|
|
4494
4526
|
} catch {
|
|
4495
4527
|
modified2 = "";
|
|
4496
4528
|
}
|
|
4497
4529
|
return { original: original2, modified: modified2 };
|
|
4498
4530
|
}
|
|
4499
|
-
const headPath = from ||
|
|
4531
|
+
const headPath = from || path66;
|
|
4500
4532
|
let original = "";
|
|
4501
4533
|
try {
|
|
4502
4534
|
original = await this.git.show([`HEAD:${headPath}`]);
|
|
@@ -4506,13 +4538,13 @@ var init_git_manager = __esm({
|
|
|
4506
4538
|
let modified = "";
|
|
4507
4539
|
if (staged) {
|
|
4508
4540
|
try {
|
|
4509
|
-
modified = await this.git.show([`:${
|
|
4541
|
+
modified = await this.git.show([`:${path66}`]);
|
|
4510
4542
|
} catch {
|
|
4511
4543
|
modified = "";
|
|
4512
4544
|
}
|
|
4513
4545
|
} else {
|
|
4514
4546
|
try {
|
|
4515
|
-
modified = await this.workspace.readFile(
|
|
4547
|
+
modified = await this.workspace.readFile(path66);
|
|
4516
4548
|
} catch {
|
|
4517
4549
|
modified = "";
|
|
4518
4550
|
}
|
|
@@ -9121,13 +9153,20 @@ var init_session_agent_builder = __esm({
|
|
|
9121
9153
|
});
|
|
9122
9154
|
return { agent, yamlConfig, contextConfig, modelId, systemPrompt, thinkingEffort, meta, draftReset };
|
|
9123
9155
|
}
|
|
9124
|
-
/** Throw with a clear message if agent.yaml is missing the model triple.
|
|
9156
|
+
/** Throw with a clear message if agent.yaml is missing the model triple.
|
|
9157
|
+
* Includes the resolved yaml path and distinguishes "file unreadable"
|
|
9158
|
+
* (yamlConfig null — read/parse failed, see loadAgentYaml) from "config
|
|
9159
|
+
* genuinely incomplete", so the error is self-diagnosing. */
|
|
9125
9160
|
validateAgentModelConfig(agentId, yamlConfig) {
|
|
9126
|
-
const
|
|
9127
|
-
|
|
9128
|
-
|
|
9161
|
+
const yamlPath = path23.join(agentSourceDir(agentId, this.host.workspaceRoot), "agent.yaml");
|
|
9162
|
+
if (yamlConfig === null) {
|
|
9163
|
+
throw new Error(`[SessionManager] Agent "${agentId}": agent.yaml missing or unreadable at ${yamlPath}.`);
|
|
9164
|
+
}
|
|
9165
|
+
const modelId = yamlConfig.model?.id;
|
|
9166
|
+
const endpoint = yamlConfig.model?.endpoint;
|
|
9167
|
+
const providerId = yamlConfig.model?.provider;
|
|
9129
9168
|
if (!modelId || !endpoint || !providerId) {
|
|
9130
|
-
throw new Error(`[SessionManager] Agent "${agentId}" is missing model config. agent.yaml must specify model.provider, model.id and model.endpoint.`);
|
|
9169
|
+
throw new Error(`[SessionManager] Agent "${agentId}" is missing model config in ${yamlPath}. agent.yaml must specify model.provider, model.id and model.endpoint.`);
|
|
9131
9170
|
}
|
|
9132
9171
|
return { modelId, endpoint, providerId };
|
|
9133
9172
|
}
|
|
@@ -14044,6 +14083,41 @@ var init_setup_prompts = __esm({
|
|
|
14044
14083
|
}
|
|
14045
14084
|
});
|
|
14046
14085
|
|
|
14086
|
+
// packages/cli/src/upgrade-verify.ts
|
|
14087
|
+
var upgrade_verify_exports = {};
|
|
14088
|
+
__export(upgrade_verify_exports, {
|
|
14089
|
+
ALLOW_SCRIPTS: () => ALLOW_SCRIPTS,
|
|
14090
|
+
NATIVE_MODULE_SMOKES: () => NATIVE_MODULE_SMOKES,
|
|
14091
|
+
verifyNativeModules: () => verifyNativeModules
|
|
14092
|
+
});
|
|
14093
|
+
import path33 from "node:path";
|
|
14094
|
+
import fs27 from "node:fs";
|
|
14095
|
+
import { spawnSync } from "node:child_process";
|
|
14096
|
+
function verifyNativeModules(pkgRoot) {
|
|
14097
|
+
const broken = [];
|
|
14098
|
+
for (const { name, smoke } of NATIVE_MODULE_SMOKES) {
|
|
14099
|
+
const modDir = path33.join(pkgRoot, "node_modules", name);
|
|
14100
|
+
if (!fs27.existsSync(modDir)) {
|
|
14101
|
+
broken.push(name);
|
|
14102
|
+
continue;
|
|
14103
|
+
}
|
|
14104
|
+
const probe = spawnSync(process.execPath, ["-e", smoke, modDir], { encoding: "utf-8" });
|
|
14105
|
+
if (probe.error || probe.status !== 0) broken.push(name);
|
|
14106
|
+
}
|
|
14107
|
+
return broken;
|
|
14108
|
+
}
|
|
14109
|
+
var NATIVE_MODULE_SMOKES, ALLOW_SCRIPTS;
|
|
14110
|
+
var init_upgrade_verify = __esm({
|
|
14111
|
+
"packages/cli/src/upgrade-verify.ts"() {
|
|
14112
|
+
"use strict";
|
|
14113
|
+
NATIVE_MODULE_SMOKES = [
|
|
14114
|
+
{ name: "better-sqlite3", smoke: "new (require(process.argv[1]))(':memory:')" },
|
|
14115
|
+
{ name: "node-pty", smoke: "require(process.argv[1])" }
|
|
14116
|
+
];
|
|
14117
|
+
ALLOW_SCRIPTS = "better-sqlite3";
|
|
14118
|
+
}
|
|
14119
|
+
});
|
|
14120
|
+
|
|
14047
14121
|
// packages/cli/src/render-md.ts
|
|
14048
14122
|
import { marked } from "marked";
|
|
14049
14123
|
import { markedTerminal } from "marked-terminal";
|
|
@@ -14060,8 +14134,8 @@ var init_render_md = __esm({
|
|
|
14060
14134
|
});
|
|
14061
14135
|
|
|
14062
14136
|
// packages/cli/src/resolve-refs.ts
|
|
14063
|
-
import
|
|
14064
|
-
import
|
|
14137
|
+
import fs28 from "node:fs";
|
|
14138
|
+
import path34 from "node:path";
|
|
14065
14139
|
function resolveRefs(input, workspace) {
|
|
14066
14140
|
const images = [];
|
|
14067
14141
|
const attachments = [];
|
|
@@ -14074,11 +14148,11 @@ function resolveRefs(input, workspace) {
|
|
|
14074
14148
|
lastIdx = match.index + match[0].length;
|
|
14075
14149
|
const kind = match[1];
|
|
14076
14150
|
const rawPath = match[2] ?? match[3];
|
|
14077
|
-
const resolved =
|
|
14078
|
-
const ext =
|
|
14151
|
+
const resolved = path34.isAbsolute(rawPath) ? rawPath : path34.resolve(workspace, rawPath);
|
|
14152
|
+
const ext = path34.extname(resolved).toLowerCase();
|
|
14079
14153
|
let stat = null;
|
|
14080
14154
|
try {
|
|
14081
|
-
stat =
|
|
14155
|
+
stat = fs28.statSync(resolved);
|
|
14082
14156
|
} catch {
|
|
14083
14157
|
}
|
|
14084
14158
|
if (!stat) {
|
|
@@ -14096,18 +14170,18 @@ function resolveRefs(input, workspace) {
|
|
|
14096
14170
|
warnings.push(`${rawPath}: image too large (${(stat.size / 1024 / 1024).toFixed(1)}MB, max ${MAX_IMAGE_BYTES / 1024 / 1024}MB), skipped`);
|
|
14097
14171
|
continue;
|
|
14098
14172
|
}
|
|
14099
|
-
const data =
|
|
14173
|
+
const data = fs28.readFileSync(resolved).toString("base64");
|
|
14100
14174
|
images.push({ data, mimeType: imageMimeFromExt(ext) ?? "application/octet-stream" });
|
|
14101
14175
|
} else {
|
|
14102
|
-
const relPath =
|
|
14176
|
+
const relPath = path34.relative(workspace, resolved);
|
|
14103
14177
|
if (stat.size > MAX_FILE_BYTES) {
|
|
14104
14178
|
const buf = Buffer.alloc(MAX_FILE_BYTES);
|
|
14105
|
-
const fd =
|
|
14179
|
+
const fd = fs28.openSync(resolved, "r");
|
|
14106
14180
|
let n = 0;
|
|
14107
14181
|
try {
|
|
14108
|
-
n =
|
|
14182
|
+
n = fs28.readSync(fd, buf, 0, MAX_FILE_BYTES, 0);
|
|
14109
14183
|
} finally {
|
|
14110
|
-
|
|
14184
|
+
fs28.closeSync(fd);
|
|
14111
14185
|
}
|
|
14112
14186
|
const content = buf.subarray(0, n).toString("utf-8").replace(/\uFFFD+$/, "");
|
|
14113
14187
|
parts.push(`
|
|
@@ -14118,7 +14192,7 @@ ${content}
|
|
|
14118
14192
|
`);
|
|
14119
14193
|
warnings.push(`${rawPath}: truncated (${(stat.size / 1024).toFixed(0)}KB, max ${MAX_FILE_BYTES / 1024}KB)`);
|
|
14120
14194
|
} else {
|
|
14121
|
-
const content =
|
|
14195
|
+
const content = fs28.readFileSync(resolved, "utf-8");
|
|
14122
14196
|
parts.push(`
|
|
14123
14197
|
<file path="${relPath}">
|
|
14124
14198
|
${content}
|
|
@@ -14390,12 +14464,12 @@ var init_streaming = __esm({
|
|
|
14390
14464
|
});
|
|
14391
14465
|
|
|
14392
14466
|
// packages/cli/src/tui/history.ts
|
|
14393
|
-
import
|
|
14394
|
-
import
|
|
14467
|
+
import fs29 from "node:fs";
|
|
14468
|
+
import path35 from "node:path";
|
|
14395
14469
|
import os2 from "node:os";
|
|
14396
14470
|
function loadHistory() {
|
|
14397
14471
|
try {
|
|
14398
|
-
const arr = JSON.parse(
|
|
14472
|
+
const arr = JSON.parse(fs29.readFileSync(HISTORY_FILE, "utf8"));
|
|
14399
14473
|
if (Array.isArray(arr)) {
|
|
14400
14474
|
return arr.filter((x) => typeof x === "string").slice(-MAX_ENTRIES);
|
|
14401
14475
|
}
|
|
@@ -14408,8 +14482,8 @@ function appendHistory(entry) {
|
|
|
14408
14482
|
const arr = loadHistory();
|
|
14409
14483
|
if (arr[arr.length - 1] === entry) return;
|
|
14410
14484
|
arr.push(entry);
|
|
14411
|
-
|
|
14412
|
-
|
|
14485
|
+
fs29.mkdirSync(path35.dirname(HISTORY_FILE), { recursive: true });
|
|
14486
|
+
fs29.writeFileSync(HISTORY_FILE, JSON.stringify(arr.slice(-MAX_ENTRIES)));
|
|
14413
14487
|
} catch {
|
|
14414
14488
|
}
|
|
14415
14489
|
}
|
|
@@ -14417,7 +14491,7 @@ var HISTORY_FILE, MAX_ENTRIES;
|
|
|
14417
14491
|
var init_history = __esm({
|
|
14418
14492
|
"packages/cli/src/tui/history.ts"() {
|
|
14419
14493
|
"use strict";
|
|
14420
|
-
HISTORY_FILE =
|
|
14494
|
+
HISTORY_FILE = path35.join(os2.homedir(), ".halo", "global", "tui-history.json");
|
|
14421
14495
|
MAX_ENTRIES = 100;
|
|
14422
14496
|
}
|
|
14423
14497
|
});
|
|
@@ -14528,8 +14602,8 @@ var init_path_suggest = __esm({
|
|
|
14528
14602
|
});
|
|
14529
14603
|
|
|
14530
14604
|
// packages/cli/src/tui/path-suggest-engine.ts
|
|
14531
|
-
import
|
|
14532
|
-
import
|
|
14605
|
+
import fs30 from "node:fs";
|
|
14606
|
+
import path36 from "node:path";
|
|
14533
14607
|
function detectActiveRef(value) {
|
|
14534
14608
|
const reExplicit = /@(file|image|scope)\s+(?:"([^"]*)|(\S*))$/;
|
|
14535
14609
|
const mExplicit = value.match(reExplicit);
|
|
@@ -14558,20 +14632,20 @@ function resolveDirAndPrefix(partial, workspace) {
|
|
|
14558
14632
|
if (partial === "") {
|
|
14559
14633
|
return { absDir: workspace, displayDir: ".", prefix: "" };
|
|
14560
14634
|
}
|
|
14561
|
-
const absBase =
|
|
14635
|
+
const absBase = path36.isAbsolute(partial) ? partial : path36.join(workspace, partial);
|
|
14562
14636
|
if (partial.endsWith("/")) {
|
|
14563
14637
|
return { absDir: absBase, displayDir: partial.replace(/\/$/, "") || "/", prefix: "" };
|
|
14564
14638
|
}
|
|
14565
|
-
const dirAbs =
|
|
14566
|
-
const prefix =
|
|
14567
|
-
const dirRel =
|
|
14639
|
+
const dirAbs = path36.dirname(absBase);
|
|
14640
|
+
const prefix = path36.basename(absBase);
|
|
14641
|
+
const dirRel = path36.dirname(partial);
|
|
14568
14642
|
return { absDir: dirAbs, displayDir: dirRel === "." ? "." : dirRel, prefix };
|
|
14569
14643
|
}
|
|
14570
14644
|
function scanCandidates(partial, workspace, kind) {
|
|
14571
14645
|
const { absDir, displayDir, prefix } = resolveDirAndPrefix(partial, workspace);
|
|
14572
14646
|
let entries;
|
|
14573
14647
|
try {
|
|
14574
|
-
entries =
|
|
14648
|
+
entries = fs30.readdirSync(absDir, { withFileTypes: true });
|
|
14575
14649
|
} catch {
|
|
14576
14650
|
return { items: [], cwdLabel: displayDir };
|
|
14577
14651
|
}
|
|
@@ -14580,17 +14654,17 @@ function scanCandidates(partial, workspace, kind) {
|
|
|
14580
14654
|
if (e.name.startsWith(".")) continue;
|
|
14581
14655
|
if (prefix && !e.name.toLowerCase().startsWith(prefix.toLowerCase())) continue;
|
|
14582
14656
|
const isDir = e.isDirectory();
|
|
14583
|
-
const ext =
|
|
14657
|
+
const ext = path36.extname(e.name).toLowerCase();
|
|
14584
14658
|
const isImage = IMAGE_EXTS.has(ext);
|
|
14585
14659
|
if (kind === "image" && !isDir && !isImage) continue;
|
|
14586
14660
|
if (kind === "scope" && !isDir) continue;
|
|
14587
14661
|
let insertPath;
|
|
14588
|
-
if (
|
|
14589
|
-
insertPath =
|
|
14662
|
+
if (path36.isAbsolute(partial)) {
|
|
14663
|
+
insertPath = path36.join(absDir, e.name);
|
|
14590
14664
|
} else if (partial.endsWith("/") || partial === "") {
|
|
14591
14665
|
insertPath = partial + e.name;
|
|
14592
14666
|
} else {
|
|
14593
|
-
const dirRel =
|
|
14667
|
+
const dirRel = path36.dirname(partial);
|
|
14594
14668
|
insertPath = (dirRel === "." ? "" : dirRel + "/") + e.name;
|
|
14595
14669
|
}
|
|
14596
14670
|
items.push({ name: e.name, insertPath, isDir, isImage });
|
|
@@ -14609,8 +14683,8 @@ function applyPathPick(value, ref, pick) {
|
|
|
14609
14683
|
const before = value.slice(0, markerStart);
|
|
14610
14684
|
const kind = ref.kind === "scope" ? "scope" : ref.kind === "image" ? "image" : pick.isImage ? "image" : "file";
|
|
14611
14685
|
const needsQuote = pick.insertPath.includes(" ");
|
|
14612
|
-
const
|
|
14613
|
-
return `${before}@${kind} ${
|
|
14686
|
+
const path66 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
|
|
14687
|
+
return `${before}@${kind} ${path66}`;
|
|
14614
14688
|
}
|
|
14615
14689
|
var init_path_suggest_engine = __esm({
|
|
14616
14690
|
"packages/cli/src/tui/path-suggest-engine.ts"() {
|
|
@@ -16083,15 +16157,15 @@ var init_cli = __esm({
|
|
|
16083
16157
|
});
|
|
16084
16158
|
|
|
16085
16159
|
// packages/server/dist/channels/shared/media.js
|
|
16086
|
-
import
|
|
16160
|
+
import path37 from "node:path";
|
|
16087
16161
|
import os3 from "node:os";
|
|
16088
16162
|
function tempDir() {
|
|
16089
|
-
return
|
|
16163
|
+
return path37.resolve(os3.tmpdir());
|
|
16090
16164
|
}
|
|
16091
16165
|
function isInTempDir(filePath) {
|
|
16092
|
-
const resolved =
|
|
16166
|
+
const resolved = path37.resolve(filePath);
|
|
16093
16167
|
const tmp = tempDir();
|
|
16094
|
-
return resolved === tmp || resolved.startsWith(tmp +
|
|
16168
|
+
return resolved === tmp || resolved.startsWith(tmp + path37.sep);
|
|
16095
16169
|
}
|
|
16096
16170
|
function extractMediaPaths(text5) {
|
|
16097
16171
|
const mediaPaths = [];
|
|
@@ -16107,12 +16181,12 @@ function extractMediaMessage(text5) {
|
|
|
16107
16181
|
return { text: stripped.replace(/\n{3,}/g, "\n\n").trim(), mediaPaths };
|
|
16108
16182
|
}
|
|
16109
16183
|
function isMediaPathAllowed(filePath, workspacePath) {
|
|
16110
|
-
const resolved =
|
|
16111
|
-
const ws =
|
|
16112
|
-
return resolved === ws || resolved.startsWith(ws +
|
|
16184
|
+
const resolved = path37.resolve(filePath);
|
|
16185
|
+
const ws = path37.resolve(workspacePath);
|
|
16186
|
+
return resolved === ws || resolved.startsWith(ws + path37.sep) || isInTempDir(resolved);
|
|
16113
16187
|
}
|
|
16114
16188
|
function classifyMedia(filePath) {
|
|
16115
|
-
const ext =
|
|
16189
|
+
const ext = path37.extname(filePath).toLowerCase();
|
|
16116
16190
|
if (IMAGE_EXTS.has(ext))
|
|
16117
16191
|
return "image";
|
|
16118
16192
|
if (VIDEO_EXTS.has(ext))
|
|
@@ -16133,13 +16207,13 @@ var init_media = __esm({
|
|
|
16133
16207
|
});
|
|
16134
16208
|
|
|
16135
16209
|
// packages/server/dist/routes/workspace-path.js
|
|
16136
|
-
import
|
|
16210
|
+
import fs31 from "node:fs/promises";
|
|
16137
16211
|
import { existsSync as existsSync2, realpathSync as realpathSync3 } from "node:fs";
|
|
16138
|
-
import
|
|
16212
|
+
import path38 from "node:path";
|
|
16139
16213
|
async function resolveProjectPath(projectId) {
|
|
16140
|
-
if (
|
|
16214
|
+
if (path38.isAbsolute(projectId)) {
|
|
16141
16215
|
try {
|
|
16142
|
-
await
|
|
16216
|
+
await fs31.access(projectId);
|
|
16143
16217
|
return projectId;
|
|
16144
16218
|
} catch {
|
|
16145
16219
|
return null;
|
|
@@ -16148,13 +16222,13 @@ async function resolveProjectPath(projectId) {
|
|
|
16148
16222
|
return null;
|
|
16149
16223
|
}
|
|
16150
16224
|
function realpathBounded2(filePath) {
|
|
16151
|
-
let prefix =
|
|
16225
|
+
let prefix = path38.resolve(filePath);
|
|
16152
16226
|
const tail = [];
|
|
16153
16227
|
while (!existsSync2(prefix)) {
|
|
16154
|
-
const parent =
|
|
16228
|
+
const parent = path38.dirname(prefix);
|
|
16155
16229
|
if (parent === prefix)
|
|
16156
16230
|
break;
|
|
16157
|
-
tail.unshift(
|
|
16231
|
+
tail.unshift(path38.basename(prefix));
|
|
16158
16232
|
prefix = parent;
|
|
16159
16233
|
}
|
|
16160
16234
|
let realPrefix;
|
|
@@ -16163,12 +16237,12 @@ function realpathBounded2(filePath) {
|
|
|
16163
16237
|
} catch {
|
|
16164
16238
|
realPrefix = prefix;
|
|
16165
16239
|
}
|
|
16166
|
-
return tail.length > 0 ?
|
|
16240
|
+
return tail.length > 0 ? path38.join(realPrefix, ...tail) : realPrefix;
|
|
16167
16241
|
}
|
|
16168
16242
|
function validatePath(filePath, projectPath) {
|
|
16169
|
-
const resolved = realpathBounded2(
|
|
16243
|
+
const resolved = realpathBounded2(path38.resolve(projectPath, filePath));
|
|
16170
16244
|
const proj = realpathBounded2(projectPath);
|
|
16171
|
-
return resolved === proj || resolved.startsWith(proj +
|
|
16245
|
+
return resolved === proj || resolved.startsWith(proj + path38.sep);
|
|
16172
16246
|
}
|
|
16173
16247
|
function isSafeIdSegment(id) {
|
|
16174
16248
|
if (id === "." || id === "..")
|
|
@@ -16183,10 +16257,10 @@ var init_workspace_path = __esm({
|
|
|
16183
16257
|
|
|
16184
16258
|
// packages/server/dist/routes/files.js
|
|
16185
16259
|
import { Hono } from "hono";
|
|
16186
|
-
import
|
|
16260
|
+
import fs32 from "node:fs/promises";
|
|
16187
16261
|
import { createReadStream } from "node:fs";
|
|
16188
16262
|
import { Readable } from "node:stream";
|
|
16189
|
-
import
|
|
16263
|
+
import path39 from "node:path";
|
|
16190
16264
|
import { homedir as homedir17 } from "node:os";
|
|
16191
16265
|
function createFileRoutes() {
|
|
16192
16266
|
const app2 = new Hono();
|
|
@@ -16203,7 +16277,7 @@ function createFileRoutes() {
|
|
|
16203
16277
|
}
|
|
16204
16278
|
async function dirHasChildren(dirPath) {
|
|
16205
16279
|
try {
|
|
16206
|
-
const entries = await
|
|
16280
|
+
const entries = await fs32.readdir(dirPath, { withFileTypes: true });
|
|
16207
16281
|
for (const entry of entries) {
|
|
16208
16282
|
if (!isSkippedName(entry.name))
|
|
16209
16283
|
return true;
|
|
@@ -16216,7 +16290,7 @@ function createFileRoutes() {
|
|
|
16216
16290
|
async function listDir(dirPath, basePath) {
|
|
16217
16291
|
let entries;
|
|
16218
16292
|
try {
|
|
16219
|
-
entries = await
|
|
16293
|
+
entries = await fs32.readdir(dirPath, { withFileTypes: true });
|
|
16220
16294
|
} catch {
|
|
16221
16295
|
return [];
|
|
16222
16296
|
}
|
|
@@ -16231,8 +16305,8 @@ function createFileRoutes() {
|
|
|
16231
16305
|
for (const entry of sorted) {
|
|
16232
16306
|
if (isSkippedName(entry.name))
|
|
16233
16307
|
continue;
|
|
16234
|
-
const fullPath =
|
|
16235
|
-
const relativePath =
|
|
16308
|
+
const fullPath = path39.join(dirPath, entry.name);
|
|
16309
|
+
const relativePath = path39.relative(basePath, fullPath).split(path39.sep).join("/");
|
|
16236
16310
|
if (entry.isDirectory()) {
|
|
16237
16311
|
nodes.push({
|
|
16238
16312
|
name: entry.name,
|
|
@@ -16264,9 +16338,9 @@ function createFileRoutes() {
|
|
|
16264
16338
|
if (relPath && !validatePath(relPath, projectPath)) {
|
|
16265
16339
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16266
16340
|
}
|
|
16267
|
-
const targetPath = relPath ?
|
|
16341
|
+
const targetPath = relPath ? path39.resolve(projectPath, relPath) : projectPath;
|
|
16268
16342
|
try {
|
|
16269
|
-
const stat = await
|
|
16343
|
+
const stat = await fs32.stat(targetPath);
|
|
16270
16344
|
if (!stat.isDirectory()) {
|
|
16271
16345
|
return c.json({ error: "Path is not a directory" }, 400);
|
|
16272
16346
|
}
|
|
@@ -16276,7 +16350,7 @@ function createFileRoutes() {
|
|
|
16276
16350
|
const tree = await listDir(targetPath, projectPath);
|
|
16277
16351
|
return c.json({
|
|
16278
16352
|
projectId,
|
|
16279
|
-
root:
|
|
16353
|
+
root: path39.basename(projectPath),
|
|
16280
16354
|
path: relPath,
|
|
16281
16355
|
tree
|
|
16282
16356
|
});
|
|
@@ -16309,7 +16383,7 @@ function createFileRoutes() {
|
|
|
16309
16383
|
return;
|
|
16310
16384
|
let entries;
|
|
16311
16385
|
try {
|
|
16312
|
-
entries = await
|
|
16386
|
+
entries = await fs32.readdir(dirPath, { withFileTypes: true });
|
|
16313
16387
|
} catch {
|
|
16314
16388
|
return;
|
|
16315
16389
|
}
|
|
@@ -16319,11 +16393,11 @@ function createFileRoutes() {
|
|
|
16319
16393
|
if (isSkippedName(entry.name))
|
|
16320
16394
|
continue;
|
|
16321
16395
|
scanned++;
|
|
16322
|
-
const fullPath =
|
|
16396
|
+
const fullPath = path39.join(dirPath, entry.name);
|
|
16323
16397
|
if (entry.isDirectory()) {
|
|
16324
16398
|
if (dirsOnly) {
|
|
16325
16399
|
const name = entry.name.toLowerCase();
|
|
16326
|
-
const rel =
|
|
16400
|
+
const rel = path39.relative(projectRoot, fullPath);
|
|
16327
16401
|
if (!q || name.includes(q) || rel.toLowerCase().includes(q)) {
|
|
16328
16402
|
matches.push({ name: entry.name, path: rel });
|
|
16329
16403
|
}
|
|
@@ -16331,7 +16405,7 @@ function createFileRoutes() {
|
|
|
16331
16405
|
await walk(fullPath);
|
|
16332
16406
|
} else if (entry.isFile() && !dirsOnly) {
|
|
16333
16407
|
const name = entry.name.toLowerCase();
|
|
16334
|
-
const rel =
|
|
16408
|
+
const rel = path39.relative(projectRoot, fullPath);
|
|
16335
16409
|
if (!q || name.includes(q) || rel.toLowerCase().includes(q)) {
|
|
16336
16410
|
matches.push({ name: entry.name, path: rel });
|
|
16337
16411
|
}
|
|
@@ -16375,15 +16449,15 @@ function createFileRoutes() {
|
|
|
16375
16449
|
}
|
|
16376
16450
|
const uploaded = [];
|
|
16377
16451
|
for (const file of files) {
|
|
16378
|
-
const relPath = targetDir ?
|
|
16452
|
+
const relPath = targetDir ? path39.join(targetDir, file.name) : file.name;
|
|
16379
16453
|
if (!validatePath(relPath, projectPath)) {
|
|
16380
16454
|
continue;
|
|
16381
16455
|
}
|
|
16382
|
-
const destPath =
|
|
16383
|
-
const destDir =
|
|
16384
|
-
await
|
|
16456
|
+
const destPath = path39.resolve(projectPath, relPath);
|
|
16457
|
+
const destDir = path39.dirname(destPath);
|
|
16458
|
+
await fs32.mkdir(destDir, { recursive: true });
|
|
16385
16459
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
16386
|
-
await
|
|
16460
|
+
await fs32.writeFile(destPath, buffer);
|
|
16387
16461
|
uploaded.push({
|
|
16388
16462
|
name: file.name,
|
|
16389
16463
|
path: relPath,
|
|
@@ -16411,9 +16485,9 @@ function createFileRoutes() {
|
|
|
16411
16485
|
if (!validatePath(filePath, projectPath)) {
|
|
16412
16486
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16413
16487
|
}
|
|
16414
|
-
const absolutePath =
|
|
16415
|
-
await
|
|
16416
|
-
const stat = await
|
|
16488
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16489
|
+
await fs32.writeFile(absolutePath, content, "utf-8");
|
|
16490
|
+
const stat = await fs32.stat(absolutePath);
|
|
16417
16491
|
return c.json({ ok: true, path: filePath, modifiedAt: stat.mtimeMs });
|
|
16418
16492
|
} catch (err) {
|
|
16419
16493
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16435,14 +16509,14 @@ function createFileRoutes() {
|
|
|
16435
16509
|
if (!validatePath(filePath, projectPath)) {
|
|
16436
16510
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16437
16511
|
}
|
|
16438
|
-
const absolutePath =
|
|
16439
|
-
await
|
|
16512
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16513
|
+
await fs32.mkdir(path39.dirname(absolutePath), { recursive: true });
|
|
16440
16514
|
try {
|
|
16441
|
-
await
|
|
16515
|
+
await fs32.access(absolutePath);
|
|
16442
16516
|
return c.json({ error: "File already exists" }, 409);
|
|
16443
16517
|
} catch {
|
|
16444
16518
|
}
|
|
16445
|
-
await
|
|
16519
|
+
await fs32.writeFile(absolutePath, "", "utf-8");
|
|
16446
16520
|
return c.json({ ok: true, path: filePath });
|
|
16447
16521
|
} catch (err) {
|
|
16448
16522
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16464,8 +16538,8 @@ function createFileRoutes() {
|
|
|
16464
16538
|
if (!validatePath(dirPath, projectPath)) {
|
|
16465
16539
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16466
16540
|
}
|
|
16467
|
-
const absolutePath =
|
|
16468
|
-
await
|
|
16541
|
+
const absolutePath = path39.resolve(projectPath, dirPath);
|
|
16542
|
+
await fs32.mkdir(absolutePath, { recursive: true });
|
|
16469
16543
|
return c.json({ ok: true, path: dirPath });
|
|
16470
16544
|
} catch (err) {
|
|
16471
16545
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16487,10 +16561,10 @@ function createFileRoutes() {
|
|
|
16487
16561
|
if (!validatePath(oldPath, projectPath) || !validatePath(newPath, projectPath)) {
|
|
16488
16562
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16489
16563
|
}
|
|
16490
|
-
const absOld =
|
|
16491
|
-
const absNew =
|
|
16492
|
-
await
|
|
16493
|
-
await
|
|
16564
|
+
const absOld = path39.resolve(projectPath, oldPath);
|
|
16565
|
+
const absNew = path39.resolve(projectPath, newPath);
|
|
16566
|
+
await fs32.mkdir(path39.dirname(absNew), { recursive: true });
|
|
16567
|
+
await fs32.rename(absOld, absNew);
|
|
16494
16568
|
return c.json({ ok: true, oldPath, newPath });
|
|
16495
16569
|
} catch (err) {
|
|
16496
16570
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16512,11 +16586,11 @@ function createFileRoutes() {
|
|
|
16512
16586
|
if (!validatePath(filePath, projectPath)) {
|
|
16513
16587
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16514
16588
|
}
|
|
16515
|
-
const absolutePath =
|
|
16516
|
-
if (absolutePath ===
|
|
16589
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16590
|
+
if (absolutePath === path39.resolve(projectPath)) {
|
|
16517
16591
|
return c.json({ error: "Cannot delete project root" }, 403);
|
|
16518
16592
|
}
|
|
16519
|
-
await
|
|
16593
|
+
await fs32.rm(absolutePath, { recursive: true, force: true });
|
|
16520
16594
|
return c.json({ ok: true, path: filePath });
|
|
16521
16595
|
} catch (err) {
|
|
16522
16596
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16550,16 +16624,16 @@ function createFileRoutes() {
|
|
|
16550
16624
|
if (!projectPath) {
|
|
16551
16625
|
return c.json({ error: "Project not found" }, 404);
|
|
16552
16626
|
}
|
|
16553
|
-
const isTmp =
|
|
16627
|
+
const isTmp = path39.isAbsolute(filePath) && isInTempDir(filePath);
|
|
16554
16628
|
if (!isTmp && !validatePath(filePath, projectPath)) {
|
|
16555
16629
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16556
16630
|
}
|
|
16557
|
-
const absolutePath = isTmp ?
|
|
16558
|
-
const stat = await
|
|
16631
|
+
const absolutePath = isTmp ? path39.resolve(filePath) : path39.resolve(projectPath, filePath);
|
|
16632
|
+
const stat = await fs32.stat(absolutePath);
|
|
16559
16633
|
if (stat.isDirectory()) {
|
|
16560
16634
|
return c.json({ error: "Cannot download a directory" }, 400);
|
|
16561
16635
|
}
|
|
16562
|
-
const fileName =
|
|
16636
|
+
const fileName = path39.basename(filePath);
|
|
16563
16637
|
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
16564
16638
|
const mime = inline ? imageMimeFromExt(ext) ?? MIME_MAP[ext] ?? "application/octet-stream" : "application/octet-stream";
|
|
16565
16639
|
const disposition = inline ? `inline; filename="${encodeURIComponent(fileName)}"` : `attachment; filename="${encodeURIComponent(fileName)}"`;
|
|
@@ -16618,8 +16692,8 @@ function createFileRoutes() {
|
|
|
16618
16692
|
return c.json({ error: "Project not found" }, 404);
|
|
16619
16693
|
if (!validatePath(filePath, projectPath))
|
|
16620
16694
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16621
|
-
const absolutePath =
|
|
16622
|
-
const stat = await
|
|
16695
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16696
|
+
const stat = await fs32.stat(absolutePath);
|
|
16623
16697
|
return c.json({ path: filePath, modifiedAt: stat.mtimeMs, createdAt: stat.birthtimeMs, size: stat.size });
|
|
16624
16698
|
} catch (err) {
|
|
16625
16699
|
if (err.code === "ENOENT")
|
|
@@ -16641,9 +16715,9 @@ function createFileRoutes() {
|
|
|
16641
16715
|
if (!validatePath(filePath, projectPath)) {
|
|
16642
16716
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16643
16717
|
}
|
|
16644
|
-
const absolutePath =
|
|
16718
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16645
16719
|
try {
|
|
16646
|
-
const stat = await
|
|
16720
|
+
const stat = await fs32.stat(absolutePath);
|
|
16647
16721
|
if (stat.isDirectory()) {
|
|
16648
16722
|
return c.json({ error: "Path is a directory, use /files/tree instead" }, 400);
|
|
16649
16723
|
}
|
|
@@ -16651,7 +16725,7 @@ function createFileRoutes() {
|
|
|
16651
16725
|
if (stat.size > maxSize) {
|
|
16652
16726
|
return c.json({ error: "File too large (max 10MB)" }, 413);
|
|
16653
16727
|
}
|
|
16654
|
-
const content = await
|
|
16728
|
+
const content = await fs32.readFile(absolutePath, "utf-8");
|
|
16655
16729
|
return c.json({
|
|
16656
16730
|
path: filePath,
|
|
16657
16731
|
content,
|
|
@@ -16678,16 +16752,16 @@ function createFileRoutes() {
|
|
|
16678
16752
|
const body = await c.req.json();
|
|
16679
16753
|
if (!body.path)
|
|
16680
16754
|
return c.json({ error: "path required" }, 400);
|
|
16681
|
-
const resolved =
|
|
16755
|
+
const resolved = path39.resolve(body.path);
|
|
16682
16756
|
let stat;
|
|
16683
16757
|
try {
|
|
16684
|
-
stat = await
|
|
16758
|
+
stat = await fs32.stat(resolved);
|
|
16685
16759
|
} catch {
|
|
16686
16760
|
return c.json({ error: "path not found" }, 404);
|
|
16687
16761
|
}
|
|
16688
16762
|
if (!stat.isDirectory())
|
|
16689
16763
|
return c.json({ error: "not a directory" }, 400);
|
|
16690
|
-
if (resolved ===
|
|
16764
|
+
if (resolved === path39.parse(resolved).root) {
|
|
16691
16765
|
return c.json({ error: "filesystem root cannot be a workspace" }, 400);
|
|
16692
16766
|
}
|
|
16693
16767
|
const { ensureWorkspaceHalo: ensureWorkspaceHalo2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
@@ -16696,10 +16770,10 @@ function createFileRoutes() {
|
|
|
16696
16770
|
});
|
|
16697
16771
|
app2.get("/fs/exists", async (c) => {
|
|
16698
16772
|
const target = c.req.query("path");
|
|
16699
|
-
if (!target || !
|
|
16773
|
+
if (!target || !path39.isAbsolute(target))
|
|
16700
16774
|
return c.json({ exists: false, reason: "absolute path required" });
|
|
16701
16775
|
try {
|
|
16702
|
-
const stat = await
|
|
16776
|
+
const stat = await fs32.stat(target);
|
|
16703
16777
|
return c.json({ exists: true, isDirectory: stat.isDirectory() });
|
|
16704
16778
|
} catch {
|
|
16705
16779
|
return c.json({ exists: false });
|
|
@@ -16707,13 +16781,13 @@ function createFileRoutes() {
|
|
|
16707
16781
|
});
|
|
16708
16782
|
app2.get("/fs/browse", async (c) => {
|
|
16709
16783
|
const target = c.req.query("path") ?? homedir17();
|
|
16710
|
-
if (!
|
|
16784
|
+
if (!path39.isAbsolute(target))
|
|
16711
16785
|
return c.json({ error: "absolute path required" }, 400);
|
|
16712
16786
|
try {
|
|
16713
|
-
const resolved =
|
|
16714
|
-
const entries = await
|
|
16715
|
-
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
16716
|
-
return c.json({ path: resolved, parent:
|
|
16787
|
+
const resolved = path39.resolve(target);
|
|
16788
|
+
const entries = await fs32.readdir(resolved, { withFileTypes: true });
|
|
16789
|
+
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: path39.join(resolved, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
16790
|
+
return c.json({ path: resolved, parent: path39.dirname(resolved), entries: dirs });
|
|
16717
16791
|
} catch (err) {
|
|
16718
16792
|
return c.json({ error: err.message }, 404);
|
|
16719
16793
|
}
|
|
@@ -16731,9 +16805,9 @@ var init_files = __esm({
|
|
|
16731
16805
|
|
|
16732
16806
|
// packages/server/dist/routes/data-preview.js
|
|
16733
16807
|
import { Hono as Hono2 } from "hono";
|
|
16734
|
-
import
|
|
16808
|
+
import fs33 from "node:fs/promises";
|
|
16735
16809
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
16736
|
-
import
|
|
16810
|
+
import path40 from "node:path";
|
|
16737
16811
|
import Database3 from "better-sqlite3";
|
|
16738
16812
|
import { asyncBufferFromFile, parquetMetadataAsync, parquetSchema, parquetReadObjects } from "hyparquet";
|
|
16739
16813
|
import { compressors } from "hyparquet-compressors";
|
|
@@ -16764,7 +16838,7 @@ function quoteIdent(name) {
|
|
|
16764
16838
|
return `"${name.replaceAll('"', '""')}"`;
|
|
16765
16839
|
}
|
|
16766
16840
|
async function sniffDelimiter(absolutePath) {
|
|
16767
|
-
const fh = await
|
|
16841
|
+
const fh = await fs33.open(absolutePath, "r");
|
|
16768
16842
|
let head;
|
|
16769
16843
|
try {
|
|
16770
16844
|
const buf = Buffer.alloc(65536);
|
|
@@ -16848,9 +16922,9 @@ function createDataPreviewRoutes() {
|
|
|
16848
16922
|
return { error: "Project not found", status: 404 };
|
|
16849
16923
|
if (!validatePath(filePath, projectPath))
|
|
16850
16924
|
return { error: "Path traversal not allowed", status: 403 };
|
|
16851
|
-
const absolutePath =
|
|
16925
|
+
const absolutePath = path40.resolve(projectPath, filePath);
|
|
16852
16926
|
try {
|
|
16853
|
-
const stat = await
|
|
16927
|
+
const stat = await fs33.stat(absolutePath);
|
|
16854
16928
|
if (stat.isDirectory())
|
|
16855
16929
|
return { error: "Path is a directory", status: 400 };
|
|
16856
16930
|
} catch {
|
|
@@ -17056,15 +17130,15 @@ var init_data_preview = __esm({
|
|
|
17056
17130
|
});
|
|
17057
17131
|
|
|
17058
17132
|
// packages/server/dist/git-credentials.js
|
|
17059
|
-
import
|
|
17060
|
-
import
|
|
17133
|
+
import fs34 from "node:fs";
|
|
17134
|
+
import path41 from "node:path";
|
|
17061
17135
|
import { homedir as homedir18 } from "node:os";
|
|
17062
|
-
import { spawnSync } from "node:child_process";
|
|
17136
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
17063
17137
|
function writeGitCredentialsFile(host, username, token) {
|
|
17064
17138
|
const line = `https://${encodeURIComponent(username)}:${encodeURIComponent(token)}@${host}`;
|
|
17065
17139
|
let lines = [];
|
|
17066
|
-
if (
|
|
17067
|
-
lines =
|
|
17140
|
+
if (fs34.existsSync(GIT_CREDENTIALS_PATH)) {
|
|
17141
|
+
lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
|
|
17068
17142
|
}
|
|
17069
17143
|
const kept = lines.filter((l) => {
|
|
17070
17144
|
try {
|
|
@@ -17074,17 +17148,17 @@ function writeGitCredentialsFile(host, username, token) {
|
|
|
17074
17148
|
}
|
|
17075
17149
|
});
|
|
17076
17150
|
kept.push(line);
|
|
17077
|
-
|
|
17151
|
+
fs34.writeFileSync(GIT_CREDENTIALS_PATH, kept.join("\n") + "\n", { encoding: "utf-8", mode: 384 });
|
|
17078
17152
|
try {
|
|
17079
|
-
|
|
17153
|
+
fs34.chmodSync(GIT_CREDENTIALS_PATH, 384);
|
|
17080
17154
|
} catch {
|
|
17081
17155
|
}
|
|
17082
17156
|
}
|
|
17083
17157
|
function ensureCredentialHelper() {
|
|
17084
|
-
const current =
|
|
17158
|
+
const current = spawnSync2("git", ["config", "--global", "--get", "credential.helper"], { encoding: "utf-8" });
|
|
17085
17159
|
const helper = (current.stdout ?? "").trim();
|
|
17086
17160
|
if (!helper) {
|
|
17087
|
-
|
|
17161
|
+
spawnSync2("git", ["config", "--global", "credential.helper", "store"], { encoding: "utf-8" });
|
|
17088
17162
|
}
|
|
17089
17163
|
}
|
|
17090
17164
|
function saveGitCredentials({ host, username, token }) {
|
|
@@ -17093,9 +17167,9 @@ function saveGitCredentials({ host, username, token }) {
|
|
|
17093
17167
|
console.log(`[GitCredentials] saved for host ${host}`);
|
|
17094
17168
|
}
|
|
17095
17169
|
function listGitCredentials() {
|
|
17096
|
-
if (!
|
|
17170
|
+
if (!fs34.existsSync(GIT_CREDENTIALS_PATH))
|
|
17097
17171
|
return [];
|
|
17098
|
-
const lines =
|
|
17172
|
+
const lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
|
|
17099
17173
|
const byHost = /* @__PURE__ */ new Map();
|
|
17100
17174
|
for (const line of lines) {
|
|
17101
17175
|
try {
|
|
@@ -17109,9 +17183,9 @@ function listGitCredentials() {
|
|
|
17109
17183
|
return [...byHost.values()];
|
|
17110
17184
|
}
|
|
17111
17185
|
function deleteGitCredential(host) {
|
|
17112
|
-
if (!
|
|
17186
|
+
if (!fs34.existsSync(GIT_CREDENTIALS_PATH))
|
|
17113
17187
|
return;
|
|
17114
|
-
const lines =
|
|
17188
|
+
const lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
|
|
17115
17189
|
const kept = lines.filter((l) => {
|
|
17116
17190
|
try {
|
|
17117
17191
|
return new URL(l).host !== host;
|
|
@@ -17121,9 +17195,9 @@ function deleteGitCredential(host) {
|
|
|
17121
17195
|
});
|
|
17122
17196
|
if (kept.length === lines.length)
|
|
17123
17197
|
return;
|
|
17124
|
-
|
|
17198
|
+
fs34.writeFileSync(GIT_CREDENTIALS_PATH, kept.length ? kept.join("\n") + "\n" : "", { encoding: "utf-8", mode: 384 });
|
|
17125
17199
|
try {
|
|
17126
|
-
|
|
17200
|
+
fs34.chmodSync(GIT_CREDENTIALS_PATH, 384);
|
|
17127
17201
|
} catch {
|
|
17128
17202
|
}
|
|
17129
17203
|
console.log(`[GitCredentials] removed for host ${host}`);
|
|
@@ -17132,15 +17206,15 @@ var GIT_CREDENTIALS_PATH;
|
|
|
17132
17206
|
var init_git_credentials = __esm({
|
|
17133
17207
|
"packages/server/dist/git-credentials.js"() {
|
|
17134
17208
|
"use strict";
|
|
17135
|
-
GIT_CREDENTIALS_PATH =
|
|
17209
|
+
GIT_CREDENTIALS_PATH = path41.join(homedir18(), ".git-credentials");
|
|
17136
17210
|
}
|
|
17137
17211
|
});
|
|
17138
17212
|
|
|
17139
17213
|
// packages/server/dist/git-ssh.js
|
|
17140
|
-
import
|
|
17214
|
+
import fs35 from "node:fs";
|
|
17141
17215
|
import os4 from "node:os";
|
|
17142
|
-
import
|
|
17143
|
-
import { spawnSync as
|
|
17216
|
+
import path42 from "node:path";
|
|
17217
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
17144
17218
|
function looksLikePrivateKey(name, abs) {
|
|
17145
17219
|
if (name.endsWith(".pub"))
|
|
17146
17220
|
return false;
|
|
@@ -17148,13 +17222,13 @@ function looksLikePrivateKey(name, abs) {
|
|
|
17148
17222
|
return false;
|
|
17149
17223
|
let firstLine = "";
|
|
17150
17224
|
try {
|
|
17151
|
-
const fd =
|
|
17225
|
+
const fd = fs35.openSync(abs, "r");
|
|
17152
17226
|
try {
|
|
17153
17227
|
const buf = Buffer.alloc(64);
|
|
17154
|
-
const n =
|
|
17228
|
+
const n = fs35.readSync(fd, buf, 0, 64, 0);
|
|
17155
17229
|
firstLine = buf.toString("utf-8", 0, n).split("\n")[0];
|
|
17156
17230
|
} finally {
|
|
17157
|
-
|
|
17231
|
+
fs35.closeSync(fd);
|
|
17158
17232
|
}
|
|
17159
17233
|
} catch {
|
|
17160
17234
|
return false;
|
|
@@ -17162,21 +17236,21 @@ function looksLikePrivateKey(name, abs) {
|
|
|
17162
17236
|
return firstLine.includes("PRIVATE KEY-----");
|
|
17163
17237
|
}
|
|
17164
17238
|
function isKeyEncrypted(abs) {
|
|
17165
|
-
const r =
|
|
17239
|
+
const r = spawnSync3("ssh-keygen", ["-y", "-P", "", "-f", abs], { encoding: "utf-8" });
|
|
17166
17240
|
return r.status !== 0;
|
|
17167
17241
|
}
|
|
17168
17242
|
function listSshKeys() {
|
|
17169
17243
|
let entries = [];
|
|
17170
17244
|
try {
|
|
17171
|
-
entries =
|
|
17245
|
+
entries = fs35.readdirSync(SSH_DIR);
|
|
17172
17246
|
} catch {
|
|
17173
17247
|
return [];
|
|
17174
17248
|
}
|
|
17175
17249
|
const keys = [];
|
|
17176
17250
|
for (const name of entries) {
|
|
17177
|
-
const abs =
|
|
17251
|
+
const abs = path42.join(SSH_DIR, name);
|
|
17178
17252
|
try {
|
|
17179
|
-
if (!
|
|
17253
|
+
if (!fs35.statSync(abs).isFile())
|
|
17180
17254
|
continue;
|
|
17181
17255
|
} catch {
|
|
17182
17256
|
continue;
|
|
@@ -17190,7 +17264,7 @@ function listSshKeys() {
|
|
|
17190
17264
|
function getSshAgentStatus() {
|
|
17191
17265
|
if (!process.env.SSH_AUTH_SOCK)
|
|
17192
17266
|
return { agentRunning: false, loadedKeys: [] };
|
|
17193
|
-
const r =
|
|
17267
|
+
const r = spawnSync3("ssh-add", ["-l"], { encoding: "utf-8" });
|
|
17194
17268
|
if (r.status === 2)
|
|
17195
17269
|
return { agentRunning: false, loadedKeys: [] };
|
|
17196
17270
|
const loadedKeys = (r.stdout ?? "").split("\n").map((l) => l.trim()).filter(Boolean).filter((l) => l !== "The agent has no identities.");
|
|
@@ -17201,10 +17275,10 @@ function unlockSshKey(keyPath, passphrase) {
|
|
|
17201
17275
|
return { ok: false, error: "ssh-agent is not running" };
|
|
17202
17276
|
let dir = "";
|
|
17203
17277
|
try {
|
|
17204
|
-
dir =
|
|
17205
|
-
const askpass =
|
|
17206
|
-
|
|
17207
|
-
const r =
|
|
17278
|
+
dir = fs35.mkdtempSync(path42.join(os4.tmpdir(), "halo-askpass-"));
|
|
17279
|
+
const askpass = path42.join(dir, "askpass.sh");
|
|
17280
|
+
fs35.writeFileSync(askpass, ASKPASS_SCRIPT, { mode: 448 });
|
|
17281
|
+
const r = spawnSync3("ssh-add", [keyPath], {
|
|
17208
17282
|
encoding: "utf-8",
|
|
17209
17283
|
// stdin from /dev/null so ssh-add can never fall back to a tty prompt and block.
|
|
17210
17284
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -17219,10 +17293,10 @@ function unlockSshKey(keyPath, passphrase) {
|
|
|
17219
17293
|
}
|
|
17220
17294
|
});
|
|
17221
17295
|
if (r.status === 0) {
|
|
17222
|
-
console.log(`[GitSsh] unlocked key ${
|
|
17296
|
+
console.log(`[GitSsh] unlocked key ${path42.basename(keyPath)}`);
|
|
17223
17297
|
return { ok: true };
|
|
17224
17298
|
}
|
|
17225
|
-
console.log(`[GitSsh] unlock failed for ${
|
|
17299
|
+
console.log(`[GitSsh] unlock failed for ${path42.basename(keyPath)} (exit ${r.status ?? "killed"})`);
|
|
17226
17300
|
if (r.status === 2)
|
|
17227
17301
|
return { ok: false, error: "ssh-agent is not reachable" };
|
|
17228
17302
|
return { ok: false, error: "Incorrect passphrase, or the key could not be unlocked" };
|
|
@@ -17232,19 +17306,19 @@ function unlockSshKey(keyPath, passphrase) {
|
|
|
17232
17306
|
return { ok: false, error: "Failed to unlock key" };
|
|
17233
17307
|
} finally {
|
|
17234
17308
|
if (dir)
|
|
17235
|
-
|
|
17309
|
+
fs35.rmSync(dir, { recursive: true, force: true });
|
|
17236
17310
|
}
|
|
17237
17311
|
}
|
|
17238
17312
|
function ensureSshAgent() {
|
|
17239
17313
|
try {
|
|
17240
17314
|
if (process.env.SSH_AUTH_SOCK) {
|
|
17241
|
-
const probe =
|
|
17315
|
+
const probe = spawnSync3("ssh-add", ["-l"], { encoding: "utf-8" });
|
|
17242
17316
|
if (probe.status !== 2) {
|
|
17243
17317
|
console.log(`[GitSsh] using existing agent at ${process.env.SSH_AUTH_SOCK}`);
|
|
17244
17318
|
return;
|
|
17245
17319
|
}
|
|
17246
17320
|
}
|
|
17247
|
-
const r =
|
|
17321
|
+
const r = spawnSync3("ssh-agent", ["-s"], { encoding: "utf-8" });
|
|
17248
17322
|
if (r.status !== 0) {
|
|
17249
17323
|
console.log("[GitSsh] ssh-agent unavailable; SSH push/pull will need a manually-started agent");
|
|
17250
17324
|
return;
|
|
@@ -17289,19 +17363,19 @@ function parseRemote(url) {
|
|
|
17289
17363
|
}
|
|
17290
17364
|
}
|
|
17291
17365
|
function switchRemoteProtocol(projectRoot, to) {
|
|
17292
|
-
const cur =
|
|
17366
|
+
const cur = spawnSync3("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf-8" });
|
|
17293
17367
|
if (cur.status !== 0)
|
|
17294
17368
|
throw new Error((cur.stderr ?? "no origin remote").trim());
|
|
17295
17369
|
const { host, repoPath } = parseRemote((cur.stdout ?? "").trim());
|
|
17296
17370
|
const next = to === "ssh" ? `git@${host}:${repoPath}` : `https://${host}/${repoPath}`;
|
|
17297
|
-
const set =
|
|
17371
|
+
const set = spawnSync3("git", ["-C", projectRoot, "remote", "set-url", "origin", next], { encoding: "utf-8" });
|
|
17298
17372
|
if (set.status !== 0)
|
|
17299
17373
|
throw new Error((set.stderr ?? "failed to set remote url").trim());
|
|
17300
17374
|
console.log(`[GitSsh] switched origin to ${to}`);
|
|
17301
17375
|
return next;
|
|
17302
17376
|
}
|
|
17303
17377
|
function getRemoteProtocol(projectRoot) {
|
|
17304
|
-
const cur =
|
|
17378
|
+
const cur = spawnSync3("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf-8" });
|
|
17305
17379
|
if (cur.status !== 0)
|
|
17306
17380
|
return { url: "", protocol: "other" };
|
|
17307
17381
|
const url = (cur.stdout ?? "").trim();
|
|
@@ -17315,7 +17389,7 @@ var SSH_DIR, NON_KEY_NAMES, ownedAgentPid, ASKPASS_SCRIPT;
|
|
|
17315
17389
|
var init_git_ssh = __esm({
|
|
17316
17390
|
"packages/server/dist/git-ssh.js"() {
|
|
17317
17391
|
"use strict";
|
|
17318
|
-
SSH_DIR =
|
|
17392
|
+
SSH_DIR = path42.join(os4.homedir(), ".ssh");
|
|
17319
17393
|
NON_KEY_NAMES = /* @__PURE__ */ new Set(["known_hosts", "known_hosts2", "authorized_keys", "config"]);
|
|
17320
17394
|
ownedAgentPid = null;
|
|
17321
17395
|
ASKPASS_SCRIPT = `#!/bin/sh
|
|
@@ -17329,7 +17403,7 @@ printf '%s\\n' "$HALO_SSH_PASSPHRASE"
|
|
|
17329
17403
|
// packages/server/dist/routes/git.js
|
|
17330
17404
|
import { Hono as Hono3 } from "hono";
|
|
17331
17405
|
import os5 from "node:os";
|
|
17332
|
-
import
|
|
17406
|
+
import path43 from "node:path";
|
|
17333
17407
|
function notifyGitChanged(workspacePath) {
|
|
17334
17408
|
broadcastToWorkspace(workspacePath, { type: "file:changed", path: ".git", action: "change" });
|
|
17335
17409
|
}
|
|
@@ -17646,9 +17720,9 @@ function createGitRoutes() {
|
|
|
17646
17720
|
if (!keyPath || typeof passphrase !== "string") {
|
|
17647
17721
|
return c.json({ error: "keyPath and passphrase are required" }, 400);
|
|
17648
17722
|
}
|
|
17649
|
-
const sshDir =
|
|
17650
|
-
const resolved =
|
|
17651
|
-
if (
|
|
17723
|
+
const sshDir = path43.join(os5.homedir(), ".ssh");
|
|
17724
|
+
const resolved = path43.resolve(keyPath);
|
|
17725
|
+
if (path43.dirname(resolved) !== path43.resolve(sshDir)) {
|
|
17652
17726
|
return c.json({ error: "keyPath must be inside ~/.ssh" }, 400);
|
|
17653
17727
|
}
|
|
17654
17728
|
return c.json(unlockSshKey(resolved, passphrase));
|
|
@@ -17724,8 +17798,8 @@ var init_mtime_cache = __esm({
|
|
|
17724
17798
|
|
|
17725
17799
|
// packages/server/dist/routes/agent-configs.js
|
|
17726
17800
|
import { Hono as Hono4 } from "hono";
|
|
17727
|
-
import
|
|
17728
|
-
import
|
|
17801
|
+
import fs36 from "node:fs/promises";
|
|
17802
|
+
import path44 from "node:path";
|
|
17729
17803
|
import { homedir as homedir19 } from "node:os";
|
|
17730
17804
|
import YAML10 from "yaml";
|
|
17731
17805
|
function getAvailableTools() {
|
|
@@ -17736,6 +17810,17 @@ function getAvailableTools() {
|
|
|
17736
17810
|
_cachedTools = [...tools, draft].map((t2) => ({ name: t2.name, description: t2.description }));
|
|
17737
17811
|
return _cachedTools;
|
|
17738
17812
|
}
|
|
17813
|
+
async function writeFileAtomic2(filePath, content) {
|
|
17814
|
+
const tmpPath = `${filePath}.tmp`;
|
|
17815
|
+
await fs36.writeFile(tmpPath, content, "utf-8");
|
|
17816
|
+
try {
|
|
17817
|
+
await fs36.rename(tmpPath, filePath);
|
|
17818
|
+
} catch {
|
|
17819
|
+
await fs36.writeFile(filePath, content, "utf-8");
|
|
17820
|
+
await fs36.rm(tmpPath, { force: true }).catch(() => {
|
|
17821
|
+
});
|
|
17822
|
+
}
|
|
17823
|
+
}
|
|
17739
17824
|
function parseAgentYaml(content) {
|
|
17740
17825
|
try {
|
|
17741
17826
|
const data = YAML10.parse(content);
|
|
@@ -17821,29 +17906,29 @@ function defaultAgentYamlTemplate() {
|
|
|
17821
17906
|
}, { lineWidth: 120 });
|
|
17822
17907
|
}
|
|
17823
17908
|
async function ensureDir2(dir) {
|
|
17824
|
-
await
|
|
17909
|
+
await fs36.mkdir(dir, { recursive: true });
|
|
17825
17910
|
}
|
|
17826
17911
|
async function scanAgentsDir(dir, scope) {
|
|
17827
17912
|
const agents = [];
|
|
17828
17913
|
let names;
|
|
17829
17914
|
try {
|
|
17830
|
-
names = await
|
|
17915
|
+
names = await fs36.readdir(dir);
|
|
17831
17916
|
} catch {
|
|
17832
17917
|
return agents;
|
|
17833
17918
|
}
|
|
17834
17919
|
for (const entryName of names) {
|
|
17835
|
-
const agentDir =
|
|
17920
|
+
const agentDir = path44.join(dir, entryName);
|
|
17836
17921
|
try {
|
|
17837
|
-
const stat = await
|
|
17922
|
+
const stat = await fs36.stat(agentDir);
|
|
17838
17923
|
if (!stat.isDirectory())
|
|
17839
17924
|
continue;
|
|
17840
17925
|
} catch {
|
|
17841
17926
|
continue;
|
|
17842
17927
|
}
|
|
17843
|
-
const yamlPath =
|
|
17928
|
+
const yamlPath = path44.join(agentDir, "agent.yaml");
|
|
17844
17929
|
try {
|
|
17845
|
-
const yamlStat = await
|
|
17846
|
-
const parsed = _yamlCache.get(yamlPath, yamlStat.mtimeMs) ?? _yamlCache.set(yamlPath, yamlStat.mtimeMs, parseAgentYaml(await
|
|
17930
|
+
const yamlStat = await fs36.stat(yamlPath);
|
|
17931
|
+
const parsed = _yamlCache.get(yamlPath, yamlStat.mtimeMs) ?? _yamlCache.set(yamlPath, yamlStat.mtimeMs, parseAgentYaml(await fs36.readFile(yamlPath, "utf-8")));
|
|
17847
17932
|
const { name, description, model, priority, tools, skills, internal } = parsed;
|
|
17848
17933
|
agents.push({
|
|
17849
17934
|
id: entryName,
|
|
@@ -17864,13 +17949,13 @@ async function scanAgentsDir(dir, scope) {
|
|
|
17864
17949
|
return agents;
|
|
17865
17950
|
}
|
|
17866
17951
|
async function ensureDefaultAgent(dir) {
|
|
17867
|
-
const defaultDir =
|
|
17868
|
-
const yamlPath =
|
|
17952
|
+
const defaultDir = path44.join(dir, "default");
|
|
17953
|
+
const yamlPath = path44.join(defaultDir, "agent.yaml");
|
|
17869
17954
|
try {
|
|
17870
|
-
await
|
|
17955
|
+
await fs36.access(yamlPath);
|
|
17871
17956
|
} catch {
|
|
17872
17957
|
await ensureDir2(defaultDir);
|
|
17873
|
-
await
|
|
17958
|
+
await fs36.writeFile(yamlPath, defaultAgentYamlTemplate(), "utf-8");
|
|
17874
17959
|
}
|
|
17875
17960
|
}
|
|
17876
17961
|
function createAgentConfigRoutes() {
|
|
@@ -17888,7 +17973,7 @@ function createAgentConfigRoutes() {
|
|
|
17888
17973
|
const projectId = c.req.query("projectId");
|
|
17889
17974
|
let workspaceAgents = [];
|
|
17890
17975
|
if (projectId) {
|
|
17891
|
-
const wsAgentsDir =
|
|
17976
|
+
const wsAgentsDir = path44.join(projectId, ".halo", "agents");
|
|
17892
17977
|
workspaceAgents = await scanAgentsDir(wsAgentsDir, "workspace");
|
|
17893
17978
|
}
|
|
17894
17979
|
const wsIds = new Set(workspaceAgents.map((a) => a.id));
|
|
@@ -17920,33 +18005,33 @@ function createAgentConfigRoutes() {
|
|
|
17920
18005
|
if (scope === "workspace") {
|
|
17921
18006
|
if (!body.projectId)
|
|
17922
18007
|
return c.json({ error: "projectId required for workspace agents" }, 400);
|
|
17923
|
-
baseDir =
|
|
18008
|
+
baseDir = path44.join(body.projectId, ".halo", "agents");
|
|
17924
18009
|
} else {
|
|
17925
18010
|
baseDir = GLOBAL_AGENTS_DIR4;
|
|
17926
18011
|
}
|
|
17927
|
-
const agentDir =
|
|
18012
|
+
const agentDir = path44.join(baseDir, id);
|
|
17928
18013
|
try {
|
|
17929
|
-
await
|
|
18014
|
+
await fs36.access(agentDir);
|
|
17930
18015
|
return c.json({ error: "Agent already exists" }, 409);
|
|
17931
18016
|
} catch {
|
|
17932
18017
|
}
|
|
17933
18018
|
let conflictScope = null;
|
|
17934
18019
|
if (scope === "workspace") {
|
|
17935
18020
|
try {
|
|
17936
|
-
await
|
|
18021
|
+
await fs36.access(path44.join(GLOBAL_AGENTS_DIR4, id));
|
|
17937
18022
|
conflictScope = "global";
|
|
17938
18023
|
} catch {
|
|
17939
18024
|
}
|
|
17940
18025
|
} else if (body.projectId) {
|
|
17941
18026
|
try {
|
|
17942
|
-
await
|
|
18027
|
+
await fs36.access(path44.join(body.projectId, ".halo", "agents", id));
|
|
17943
18028
|
conflictScope = "workspace";
|
|
17944
18029
|
} catch {
|
|
17945
18030
|
}
|
|
17946
18031
|
}
|
|
17947
18032
|
await ensureDir2(agentDir);
|
|
17948
|
-
await
|
|
17949
|
-
await
|
|
18033
|
+
await fs36.writeFile(path44.join(agentDir, "agent.yaml"), defaultAgentYaml(body.name, body.description ?? ""), "utf-8");
|
|
18034
|
+
await fs36.writeFile(path44.join(agentDir, "AGENT.md"), `# ${body.name}
|
|
17950
18035
|
|
|
17951
18036
|
${body.description ?? ""}
|
|
17952
18037
|
`, "utf-8");
|
|
@@ -17960,13 +18045,13 @@ ${body.description ?? ""}
|
|
|
17960
18045
|
const projectId = c.req.query("projectId");
|
|
17961
18046
|
let agentDir;
|
|
17962
18047
|
if (scope === "workspace" && projectId) {
|
|
17963
|
-
agentDir =
|
|
18048
|
+
agentDir = path44.join(projectId, ".halo", "agents", id);
|
|
17964
18049
|
} else {
|
|
17965
|
-
agentDir =
|
|
18050
|
+
agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
|
|
17966
18051
|
}
|
|
17967
|
-
const yamlPath =
|
|
18052
|
+
const yamlPath = path44.join(agentDir, "agent.yaml");
|
|
17968
18053
|
try {
|
|
17969
|
-
const content = await
|
|
18054
|
+
const content = await fs36.readFile(yamlPath, "utf-8");
|
|
17970
18055
|
return c.json({ yaml: content });
|
|
17971
18056
|
} catch {
|
|
17972
18057
|
return c.json({ error: "Agent not found" }, 404);
|
|
@@ -17983,17 +18068,17 @@ ${body.description ?? ""}
|
|
|
17983
18068
|
const scope = body.scope ?? "global";
|
|
17984
18069
|
let agentDir;
|
|
17985
18070
|
if (scope === "workspace" && body.projectId) {
|
|
17986
|
-
agentDir =
|
|
18071
|
+
agentDir = path44.join(body.projectId, ".halo", "agents", id);
|
|
17987
18072
|
} else {
|
|
17988
|
-
agentDir =
|
|
18073
|
+
agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
|
|
17989
18074
|
}
|
|
17990
|
-
const yamlPath =
|
|
18075
|
+
const yamlPath = path44.join(agentDir, "agent.yaml");
|
|
17991
18076
|
try {
|
|
17992
|
-
await
|
|
18077
|
+
await fs36.access(agentDir);
|
|
17993
18078
|
} catch {
|
|
17994
18079
|
return c.json({ error: "Agent not found" }, 404);
|
|
17995
18080
|
}
|
|
17996
|
-
await
|
|
18081
|
+
await writeFileAtomic2(yamlPath, body.yaml);
|
|
17997
18082
|
const { name, description, model } = parseAgentYaml(body.yaml);
|
|
17998
18083
|
return c.json({ agent: { id, name, description, model, path: agentDir, scope } });
|
|
17999
18084
|
});
|
|
@@ -18005,12 +18090,12 @@ ${body.description ?? ""}
|
|
|
18005
18090
|
const projectId = c.req.query("projectId");
|
|
18006
18091
|
let agentDir;
|
|
18007
18092
|
if (scope === "workspace" && projectId) {
|
|
18008
|
-
agentDir =
|
|
18093
|
+
agentDir = path44.join(projectId, ".halo", "agents", id);
|
|
18009
18094
|
} else {
|
|
18010
|
-
agentDir =
|
|
18095
|
+
agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
|
|
18011
18096
|
}
|
|
18012
18097
|
try {
|
|
18013
|
-
await
|
|
18098
|
+
await fs36.access(agentDir);
|
|
18014
18099
|
} catch {
|
|
18015
18100
|
return c.json({ error: "Agent not found" }, 404);
|
|
18016
18101
|
}
|
|
@@ -18020,7 +18105,7 @@ ${body.description ?? ""}
|
|
|
18020
18105
|
return c.json({ error: "Cannot delete the last global agent. At least one global agent must remain." }, 400);
|
|
18021
18106
|
}
|
|
18022
18107
|
}
|
|
18023
|
-
await
|
|
18108
|
+
await fs36.rm(agentDir, { recursive: true, force: true });
|
|
18024
18109
|
return c.json({ ok: true });
|
|
18025
18110
|
});
|
|
18026
18111
|
app2.patch("/agent-configs/:id/toggle", async (c) => {
|
|
@@ -18052,7 +18137,7 @@ ${body.description ?? ""}
|
|
|
18052
18137
|
if (!filePath)
|
|
18053
18138
|
return c.json({ content: "", exists: false, readOnly: !isMdWritable(fileType) });
|
|
18054
18139
|
try {
|
|
18055
|
-
const content = await
|
|
18140
|
+
const content = await fs36.readFile(filePath, "utf-8");
|
|
18056
18141
|
return c.json({ content, exists: true, path: filePath, readOnly: !isMdWritable(fileType) });
|
|
18057
18142
|
} catch {
|
|
18058
18143
|
return c.json({ content: "", exists: false, path: filePath, readOnly: !isMdWritable(fileType) });
|
|
@@ -18085,7 +18170,7 @@ ${body.description ?? ""}
|
|
|
18085
18170
|
continue;
|
|
18086
18171
|
}
|
|
18087
18172
|
try {
|
|
18088
|
-
const content = await
|
|
18173
|
+
const content = await fs36.readFile(filePath, "utf-8");
|
|
18089
18174
|
result[ft] = { content, exists: true, path: filePath, readOnly: !isMdWritable(ft) };
|
|
18090
18175
|
} catch {
|
|
18091
18176
|
result[ft] = { content: "", exists: false, path: filePath, readOnly: !isMdWritable(ft) };
|
|
@@ -18095,10 +18180,10 @@ ${body.description ?? ""}
|
|
|
18095
18180
|
});
|
|
18096
18181
|
function getSessionsDir(agentId, _source, projectId) {
|
|
18097
18182
|
if (agentId.startsWith("__") && agentId.endsWith("__")) {
|
|
18098
|
-
return
|
|
18183
|
+
return path44.join(homedir19(), ".halo", "global", "internal-sessions", agentId);
|
|
18099
18184
|
}
|
|
18100
|
-
const base = projectId ?
|
|
18101
|
-
return
|
|
18185
|
+
const base = projectId ? path44.join(projectId, ".halo") : path44.join(homedir19(), ".halo");
|
|
18186
|
+
return path44.join(base, "sessions", agentId);
|
|
18102
18187
|
}
|
|
18103
18188
|
app2.get("/agent-configs/:id/sessions", async (c) => {
|
|
18104
18189
|
const agentId = c.req.param("id");
|
|
@@ -18109,7 +18194,7 @@ ${body.description ?? ""}
|
|
|
18109
18194
|
const dir = getSessionsDir(agentId, source, projectId || void 0);
|
|
18110
18195
|
let files;
|
|
18111
18196
|
try {
|
|
18112
|
-
files = await
|
|
18197
|
+
files = await fs36.readdir(dir);
|
|
18113
18198
|
} catch {
|
|
18114
18199
|
return c.json({ sessions: [] });
|
|
18115
18200
|
}
|
|
@@ -18118,7 +18203,7 @@ ${body.description ?? ""}
|
|
|
18118
18203
|
if (!f.endsWith(".json"))
|
|
18119
18204
|
continue;
|
|
18120
18205
|
try {
|
|
18121
|
-
const raw = await
|
|
18206
|
+
const raw = await fs36.readFile(path44.join(dir, f), "utf-8");
|
|
18122
18207
|
const session = JSON.parse(raw);
|
|
18123
18208
|
sessions.push({
|
|
18124
18209
|
id: session.id,
|
|
@@ -18147,9 +18232,9 @@ ${body.description ?? ""}
|
|
|
18147
18232
|
const projectId = c.req.query("projectId");
|
|
18148
18233
|
const source = c.req.query("source") || "test-chat";
|
|
18149
18234
|
const dir = getSessionsDir(agentId, source, projectId || void 0);
|
|
18150
|
-
const filePath =
|
|
18235
|
+
const filePath = path44.join(dir, `${sessionId}.json`);
|
|
18151
18236
|
try {
|
|
18152
|
-
const raw = await
|
|
18237
|
+
const raw = await fs36.readFile(filePath, "utf-8");
|
|
18153
18238
|
return c.json({ session: JSON.parse(raw) });
|
|
18154
18239
|
} catch {
|
|
18155
18240
|
return c.json({ error: "Session not found" }, 404);
|
|
@@ -18183,7 +18268,7 @@ ${body.description ?? ""}
|
|
|
18183
18268
|
agentSnapshot: body.agentSnapshot ?? {},
|
|
18184
18269
|
messages: messages2
|
|
18185
18270
|
};
|
|
18186
|
-
await
|
|
18271
|
+
await fs36.writeFile(path44.join(dir, `${sessionId}.json`), JSON.stringify(session, null, 2), "utf-8");
|
|
18187
18272
|
return c.json({ id: sessionId, session: { ...session, messages: void 0 } });
|
|
18188
18273
|
});
|
|
18189
18274
|
app2.delete("/agent-configs/:id/sessions", async (c) => {
|
|
@@ -18195,11 +18280,11 @@ ${body.description ?? ""}
|
|
|
18195
18280
|
const dir = getSessionsDir(agentId, source, projectId || void 0);
|
|
18196
18281
|
let deleted = 0;
|
|
18197
18282
|
try {
|
|
18198
|
-
const files = await
|
|
18283
|
+
const files = await fs36.readdir(dir);
|
|
18199
18284
|
for (const f of files) {
|
|
18200
18285
|
if (!f.endsWith(".json"))
|
|
18201
18286
|
continue;
|
|
18202
|
-
await
|
|
18287
|
+
await fs36.rm(path44.join(dir, f));
|
|
18203
18288
|
deleted++;
|
|
18204
18289
|
}
|
|
18205
18290
|
} catch {
|
|
@@ -18216,7 +18301,7 @@ ${body.description ?? ""}
|
|
|
18216
18301
|
const source = c.req.query("source") || "test-chat";
|
|
18217
18302
|
const dir = getSessionsDir(agentId, source, projectId || void 0);
|
|
18218
18303
|
try {
|
|
18219
|
-
await
|
|
18304
|
+
await fs36.rm(path44.join(dir, `${sessionId}.json`));
|
|
18220
18305
|
return c.json({ ok: true });
|
|
18221
18306
|
} catch {
|
|
18222
18307
|
return c.json({ error: "Session not found" }, 404);
|
|
@@ -18236,7 +18321,7 @@ var init_agent_configs = __esm({
|
|
|
18236
18321
|
init_workspace_path();
|
|
18237
18322
|
init_mtime_cache();
|
|
18238
18323
|
_cachedTools = null;
|
|
18239
|
-
GLOBAL_AGENTS_DIR4 =
|
|
18324
|
+
GLOBAL_AGENTS_DIR4 = path44.join(homedir19(), ".halo", "global", "agents");
|
|
18240
18325
|
FALLBACK_SCAFFOLD_PROVIDER = "aws-bedrock-claude-invoke";
|
|
18241
18326
|
_yamlCache = createMtimeCache();
|
|
18242
18327
|
}
|
|
@@ -18244,8 +18329,8 @@ var init_agent_configs = __esm({
|
|
|
18244
18329
|
|
|
18245
18330
|
// packages/server/dist/routes/skills.js
|
|
18246
18331
|
import { Hono as Hono5 } from "hono";
|
|
18247
|
-
import
|
|
18248
|
-
import
|
|
18332
|
+
import fs37 from "node:fs/promises";
|
|
18333
|
+
import path45 from "node:path";
|
|
18249
18334
|
import { homedir as homedir20 } from "node:os";
|
|
18250
18335
|
import YAML11 from "yaml";
|
|
18251
18336
|
function defaultSkillSettings() {
|
|
@@ -18264,20 +18349,20 @@ function defaultSkillSettings() {
|
|
|
18264
18349
|
}
|
|
18265
18350
|
async function readYamlFile3(filePath) {
|
|
18266
18351
|
try {
|
|
18267
|
-
const content = await
|
|
18352
|
+
const content = await fs37.readFile(filePath, "utf-8");
|
|
18268
18353
|
return YAML11.parse(content) ?? {};
|
|
18269
18354
|
} catch {
|
|
18270
18355
|
try {
|
|
18271
18356
|
const jsonPath = filePath.replace(/\.yaml$/, ".json");
|
|
18272
|
-
return JSON.parse(await
|
|
18357
|
+
return JSON.parse(await fs37.readFile(jsonPath, "utf-8"));
|
|
18273
18358
|
} catch {
|
|
18274
18359
|
return {};
|
|
18275
18360
|
}
|
|
18276
18361
|
}
|
|
18277
18362
|
}
|
|
18278
18363
|
async function writeYamlFile(filePath, data) {
|
|
18279
|
-
await
|
|
18280
|
-
await
|
|
18364
|
+
await fs37.mkdir(path45.dirname(filePath), { recursive: true });
|
|
18365
|
+
await fs37.writeFile(filePath, YAML11.stringify(data, { lineWidth: 120 }), "utf-8");
|
|
18281
18366
|
}
|
|
18282
18367
|
async function addSkillSettings(settingsPath, skillId) {
|
|
18283
18368
|
const settings = await readYamlFile3(settingsPath);
|
|
@@ -18300,7 +18385,7 @@ async function removeSkillSettings(settingsPath, skillId) {
|
|
|
18300
18385
|
}
|
|
18301
18386
|
}
|
|
18302
18387
|
async function ensureDir3(dir) {
|
|
18303
|
-
await
|
|
18388
|
+
await fs37.mkdir(dir, { recursive: true });
|
|
18304
18389
|
}
|
|
18305
18390
|
function toSkillMd(name, description) {
|
|
18306
18391
|
return `---
|
|
@@ -18317,32 +18402,32 @@ async function scanSkillsDir(dir, scope) {
|
|
|
18317
18402
|
const skills = [];
|
|
18318
18403
|
let names;
|
|
18319
18404
|
try {
|
|
18320
|
-
names = await
|
|
18405
|
+
names = await fs37.readdir(dir);
|
|
18321
18406
|
} catch {
|
|
18322
18407
|
return skills;
|
|
18323
18408
|
}
|
|
18324
18409
|
for (const entryName of names) {
|
|
18325
|
-
const skillDir =
|
|
18410
|
+
const skillDir = path45.join(dir, entryName);
|
|
18326
18411
|
try {
|
|
18327
|
-
const stat = await
|
|
18412
|
+
const stat = await fs37.stat(skillDir);
|
|
18328
18413
|
if (!stat.isDirectory())
|
|
18329
18414
|
continue;
|
|
18330
18415
|
} catch {
|
|
18331
18416
|
continue;
|
|
18332
18417
|
}
|
|
18333
|
-
const skillMdPath =
|
|
18418
|
+
const skillMdPath = path45.join(skillDir, "SKILL.md");
|
|
18334
18419
|
try {
|
|
18335
|
-
const mdStat = await
|
|
18420
|
+
const mdStat = await fs37.stat(skillMdPath);
|
|
18336
18421
|
let meta = _skillMdCache.get(skillMdPath, mdStat.mtimeMs);
|
|
18337
18422
|
if (!meta) {
|
|
18338
|
-
const { name, description, command } = parseSkillFrontmatter(await
|
|
18423
|
+
const { name, description, command } = parseSkillFrontmatter(await fs37.readFile(skillMdPath, "utf-8"));
|
|
18339
18424
|
meta = _skillMdCache.set(skillMdPath, mdStat.mtimeMs, { name: name || entryName, description: description ?? "", command });
|
|
18340
18425
|
}
|
|
18341
18426
|
skills.push({ id: entryName, name: meta.name, description: meta.description, path: skillDir, scope, command: meta.command });
|
|
18342
18427
|
} catch {
|
|
18343
|
-
const yamlPath =
|
|
18428
|
+
const yamlPath = path45.join(skillDir, "skill.yaml");
|
|
18344
18429
|
try {
|
|
18345
|
-
const yamlContent = await
|
|
18430
|
+
const yamlContent = await fs37.readFile(yamlPath, "utf-8");
|
|
18346
18431
|
const m = yamlContent.match(/^name:\s*(.+)$/m);
|
|
18347
18432
|
const d = yamlContent.match(/^description:\s*(.+)$/m);
|
|
18348
18433
|
skills.push({ id: entryName, name: m?.[1]?.trim() || entryName, description: d?.[1]?.trim() || "", path: skillDir, scope });
|
|
@@ -18361,7 +18446,7 @@ function createSkillRoutes() {
|
|
|
18361
18446
|
const projectId = c.req.query("projectId");
|
|
18362
18447
|
let workspaceSkills = [];
|
|
18363
18448
|
if (projectId) {
|
|
18364
|
-
const wsSkillsDir =
|
|
18449
|
+
const wsSkillsDir = path45.join(projectId, ".halo", "skills");
|
|
18365
18450
|
workspaceSkills = await scanSkillsDir(wsSkillsDir, "workspace");
|
|
18366
18451
|
}
|
|
18367
18452
|
const wsIds = new Set(workspaceSkills.map((s) => s.id));
|
|
@@ -18384,19 +18469,19 @@ function createSkillRoutes() {
|
|
|
18384
18469
|
if (scope === "workspace") {
|
|
18385
18470
|
if (!body.projectId)
|
|
18386
18471
|
return c.json({ error: "projectId required for workspace skills" }, 400);
|
|
18387
|
-
baseDir =
|
|
18472
|
+
baseDir = path45.join(body.projectId, ".halo", "skills");
|
|
18388
18473
|
} else {
|
|
18389
18474
|
baseDir = GLOBAL_SKILLS_DIR2;
|
|
18390
18475
|
}
|
|
18391
|
-
const skillDir =
|
|
18476
|
+
const skillDir = path45.join(baseDir, id);
|
|
18392
18477
|
try {
|
|
18393
|
-
await
|
|
18478
|
+
await fs37.access(skillDir);
|
|
18394
18479
|
return c.json({ error: "Skill already exists" }, 409);
|
|
18395
18480
|
} catch {
|
|
18396
18481
|
}
|
|
18397
18482
|
await ensureDir3(skillDir);
|
|
18398
|
-
await
|
|
18399
|
-
const settingsPath = scope === "workspace" && body.projectId ?
|
|
18483
|
+
await fs37.writeFile(path45.join(skillDir, "SKILL.md"), toSkillMd(body.name, body.description ?? ""), "utf-8");
|
|
18484
|
+
const settingsPath = scope === "workspace" && body.projectId ? path45.join(body.projectId, ".halo", "settings.yaml") : GLOBAL_SETTINGS_PATH;
|
|
18400
18485
|
await addSkillSettings(settingsPath, id);
|
|
18401
18486
|
const skill = { id, name: body.name, description: body.description ?? "", path: skillDir, scope };
|
|
18402
18487
|
return c.json({ skill }, 201);
|
|
@@ -18409,17 +18494,17 @@ function createSkillRoutes() {
|
|
|
18409
18494
|
const projectId = c.req.query("projectId");
|
|
18410
18495
|
let skillDir;
|
|
18411
18496
|
if (scope === "workspace" && projectId) {
|
|
18412
|
-
skillDir =
|
|
18497
|
+
skillDir = path45.join(projectId, ".halo", "skills", id);
|
|
18413
18498
|
} else {
|
|
18414
|
-
skillDir =
|
|
18499
|
+
skillDir = path45.join(GLOBAL_SKILLS_DIR2, id);
|
|
18415
18500
|
}
|
|
18416
18501
|
try {
|
|
18417
|
-
await
|
|
18502
|
+
await fs37.access(skillDir);
|
|
18418
18503
|
} catch {
|
|
18419
18504
|
return c.json({ error: "Skill not found" }, 404);
|
|
18420
18505
|
}
|
|
18421
|
-
await
|
|
18422
|
-
const settingsPath = scope === "workspace" && projectId ?
|
|
18506
|
+
await fs37.rm(skillDir, { recursive: true, force: true });
|
|
18507
|
+
const settingsPath = scope === "workspace" && projectId ? path45.join(projectId, ".halo", "settings.yaml") : GLOBAL_SETTINGS_PATH;
|
|
18423
18508
|
await removeSkillSettings(settingsPath, id);
|
|
18424
18509
|
return c.json({ ok: true });
|
|
18425
18510
|
});
|
|
@@ -18443,30 +18528,30 @@ var init_skills = __esm({
|
|
|
18443
18528
|
init_db();
|
|
18444
18529
|
init_workspace_path();
|
|
18445
18530
|
init_mtime_cache();
|
|
18446
|
-
GLOBAL_SETTINGS_PATH =
|
|
18531
|
+
GLOBAL_SETTINGS_PATH = path45.join(homedir20(), ".halo", "secrets", "settings.yaml");
|
|
18447
18532
|
_skillMdCache = createMtimeCache();
|
|
18448
18533
|
}
|
|
18449
18534
|
});
|
|
18450
18535
|
|
|
18451
18536
|
// packages/server/dist/routes/settings.js
|
|
18452
18537
|
import { Hono as Hono6 } from "hono";
|
|
18453
|
-
import
|
|
18454
|
-
import
|
|
18538
|
+
import fs38 from "node:fs/promises";
|
|
18539
|
+
import path46 from "node:path";
|
|
18455
18540
|
import { homedir as homedir21 } from "node:os";
|
|
18456
18541
|
import YAML12 from "yaml";
|
|
18457
18542
|
async function readSettingsFile(filePath) {
|
|
18458
18543
|
try {
|
|
18459
|
-
const content = await
|
|
18544
|
+
const content = await fs38.readFile(filePath, "utf-8");
|
|
18460
18545
|
return YAML12.parse(content) ?? {};
|
|
18461
18546
|
} catch {
|
|
18462
18547
|
return {};
|
|
18463
18548
|
}
|
|
18464
18549
|
}
|
|
18465
18550
|
async function writeSettingsFile(filePath, data) {
|
|
18466
|
-
await
|
|
18467
|
-
await
|
|
18551
|
+
await fs38.mkdir(path46.dirname(filePath), { recursive: true });
|
|
18552
|
+
await fs38.writeFile(filePath, YAML12.stringify(data, { lineWidth: 120 }), { encoding: "utf-8", mode: 384 });
|
|
18468
18553
|
try {
|
|
18469
|
-
await
|
|
18554
|
+
await fs38.chmod(filePath, 384);
|
|
18470
18555
|
} catch {
|
|
18471
18556
|
}
|
|
18472
18557
|
}
|
|
@@ -18580,8 +18665,8 @@ function detectOrphans(tree, declared) {
|
|
|
18580
18665
|
if (!block || typeof block !== "object")
|
|
18581
18666
|
continue;
|
|
18582
18667
|
for (const k of Object.keys(block)) {
|
|
18583
|
-
const
|
|
18584
|
-
if (!declaredMap.has(
|
|
18668
|
+
const path66 = `${namespaceId}.${kindWord}.${k}`;
|
|
18669
|
+
if (!declaredMap.has(path66)) {
|
|
18585
18670
|
out.push({ namespaceId, kind: kindWord === "params" ? "param" : "secret", key: k });
|
|
18586
18671
|
}
|
|
18587
18672
|
}
|
|
@@ -18596,7 +18681,7 @@ function createSettingsRoutes() {
|
|
|
18596
18681
|
const globalTree = await readSettingsFile(GLOBAL_SETTINGS_PATH2);
|
|
18597
18682
|
let workspaceTree = null;
|
|
18598
18683
|
if (projectId) {
|
|
18599
|
-
workspaceTree = await readSettingsFile(
|
|
18684
|
+
workspaceTree = await readSettingsFile(path46.join(projectId, ".halo", "settings.yaml"));
|
|
18600
18685
|
}
|
|
18601
18686
|
const declared = loadSettingsSchema(projectId ?? void 0);
|
|
18602
18687
|
const sections = resolveSections(declared, globalTree, workspaceTree);
|
|
@@ -18646,7 +18731,7 @@ function createSettingsRoutes() {
|
|
|
18646
18731
|
const offending = rejectGlobalOnlyAtWorkspace("workspace", flattenDotted(body.settings));
|
|
18647
18732
|
if (offending)
|
|
18648
18733
|
return c.json({ error: `${offending} is global-only and cannot be set per workspace` }, 400);
|
|
18649
|
-
const wsPath =
|
|
18734
|
+
const wsPath = path46.join(projectPath, ".halo", "settings.yaml");
|
|
18650
18735
|
await writeSettingsFile(wsPath, body.settings);
|
|
18651
18736
|
} else {
|
|
18652
18737
|
await writeSettingsFile(GLOBAL_SETTINGS_PATH2, body.settings);
|
|
@@ -18666,7 +18751,7 @@ function createSettingsRoutes() {
|
|
|
18666
18751
|
const offending = rejectGlobalOnlyAtWorkspace("workspace", [body.key]);
|
|
18667
18752
|
if (offending)
|
|
18668
18753
|
return c.json({ error: `${offending} is global-only and cannot be set per workspace` }, 400);
|
|
18669
|
-
filePath =
|
|
18754
|
+
filePath = path46.join(projectPath, ".halo", "settings.yaml");
|
|
18670
18755
|
} else {
|
|
18671
18756
|
filePath = GLOBAL_SETTINGS_PATH2;
|
|
18672
18757
|
}
|
|
@@ -18695,7 +18780,7 @@ function createSettingsRoutes() {
|
|
|
18695
18780
|
const projectPath = await resolveProjectPath(body.projectId);
|
|
18696
18781
|
if (!projectPath)
|
|
18697
18782
|
return c.json({ error: "Project not found" }, 404);
|
|
18698
|
-
filePath =
|
|
18783
|
+
filePath = path46.join(projectPath, ".halo", "settings.yaml");
|
|
18699
18784
|
} else {
|
|
18700
18785
|
filePath = GLOBAL_SETTINGS_PATH2;
|
|
18701
18786
|
}
|
|
@@ -18721,39 +18806,39 @@ var init_settings = __esm({
|
|
|
18721
18806
|
"use strict";
|
|
18722
18807
|
init_settings_schema();
|
|
18723
18808
|
init_workspace_path();
|
|
18724
|
-
GLOBAL_SETTINGS_PATH2 =
|
|
18809
|
+
GLOBAL_SETTINGS_PATH2 = path46.join(homedir21(), ".halo", "secrets", "settings.yaml");
|
|
18725
18810
|
_changeListeners = [];
|
|
18726
18811
|
}
|
|
18727
18812
|
});
|
|
18728
18813
|
|
|
18729
18814
|
// packages/server/dist/evolution/archive.js
|
|
18730
|
-
import
|
|
18731
|
-
import
|
|
18815
|
+
import fs39 from "node:fs";
|
|
18816
|
+
import path47 from "node:path";
|
|
18732
18817
|
import { pipeline } from "node:stream/promises";
|
|
18733
18818
|
import JSZip from "jszip";
|
|
18734
18819
|
import { eq as eq7, and as and5, isNull as isNull4, lte, isNotNull as isNotNull3, lt as lt2 } from "drizzle-orm";
|
|
18735
18820
|
async function zipDir(srcDir, outZip) {
|
|
18736
|
-
if (!
|
|
18821
|
+
if (!fs39.existsSync(srcDir))
|
|
18737
18822
|
return false;
|
|
18738
|
-
|
|
18823
|
+
fs39.mkdirSync(path47.dirname(outZip), { recursive: true });
|
|
18739
18824
|
const zip = new JSZip();
|
|
18740
18825
|
const walk = (dir, rel) => {
|
|
18741
|
-
const entries =
|
|
18826
|
+
const entries = fs39.readdirSync(dir, { withFileTypes: true });
|
|
18742
18827
|
if (entries.length === 0 && rel) {
|
|
18743
18828
|
zip.folder(rel);
|
|
18744
18829
|
return;
|
|
18745
18830
|
}
|
|
18746
18831
|
for (const e of entries) {
|
|
18747
|
-
const abs =
|
|
18832
|
+
const abs = path47.join(dir, e.name);
|
|
18748
18833
|
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
18749
18834
|
if (e.isDirectory()) {
|
|
18750
18835
|
walk(abs, childRel);
|
|
18751
18836
|
} else if (e.isFile()) {
|
|
18752
|
-
zip.file(childRel,
|
|
18837
|
+
zip.file(childRel, fs39.readFileSync(abs), { date: fs39.statSync(abs).mtime });
|
|
18753
18838
|
} else if (e.isSymbolicLink()) {
|
|
18754
18839
|
try {
|
|
18755
|
-
if (
|
|
18756
|
-
zip.file(childRel,
|
|
18840
|
+
if (fs39.statSync(abs).isFile())
|
|
18841
|
+
zip.file(childRel, fs39.readFileSync(abs));
|
|
18757
18842
|
} catch {
|
|
18758
18843
|
}
|
|
18759
18844
|
}
|
|
@@ -18761,7 +18846,7 @@ async function zipDir(srcDir, outZip) {
|
|
|
18761
18846
|
};
|
|
18762
18847
|
try {
|
|
18763
18848
|
walk(srcDir, "");
|
|
18764
|
-
await pipeline(zip.generateNodeStream({ type: "nodebuffer", streamFiles: true, compression: "DEFLATE" }),
|
|
18849
|
+
await pipeline(zip.generateNodeStream({ type: "nodebuffer", streamFiles: true, compression: "DEFLATE" }), fs39.createWriteStream(outZip));
|
|
18765
18850
|
return true;
|
|
18766
18851
|
} catch (err) {
|
|
18767
18852
|
rmFile(outZip);
|
|
@@ -18771,20 +18856,20 @@ async function zipDir(srcDir, outZip) {
|
|
|
18771
18856
|
}
|
|
18772
18857
|
function rmDir(dir) {
|
|
18773
18858
|
try {
|
|
18774
|
-
|
|
18859
|
+
fs39.rmSync(dir, { recursive: true, force: true });
|
|
18775
18860
|
} catch {
|
|
18776
18861
|
}
|
|
18777
18862
|
}
|
|
18778
18863
|
function rmFile(p) {
|
|
18779
18864
|
try {
|
|
18780
|
-
|
|
18865
|
+
fs39.rmSync(p, { force: true });
|
|
18781
18866
|
} catch {
|
|
18782
18867
|
}
|
|
18783
18868
|
}
|
|
18784
18869
|
async function archiveRun(workspacePath, runId, errors) {
|
|
18785
18870
|
const runDir = wsEvoRunDir(workspacePath, runId);
|
|
18786
18871
|
const outZip = wsEvoArchivedRunZip(workspacePath, runId);
|
|
18787
|
-
if (!
|
|
18872
|
+
if (!fs39.existsSync(runDir)) {
|
|
18788
18873
|
return true;
|
|
18789
18874
|
}
|
|
18790
18875
|
if (!await zipDir(runDir, outZip)) {
|
|
@@ -18797,7 +18882,7 @@ async function archiveRun(workspacePath, runId, errors) {
|
|
|
18797
18882
|
async function archiveApply(workspacePath, applyId, errors) {
|
|
18798
18883
|
const applyDir = wsEvoApplyDir(workspacePath, applyId);
|
|
18799
18884
|
const outZip = wsEvoArchivedApplyZip(workspacePath, applyId);
|
|
18800
|
-
if (!
|
|
18885
|
+
if (!fs39.existsSync(applyDir))
|
|
18801
18886
|
return true;
|
|
18802
18887
|
if (!await zipDir(applyDir, outZip)) {
|
|
18803
18888
|
errors.push(`zip failed for apply ${applyId}`);
|
|
@@ -18924,8 +19009,8 @@ var init_raw_sqlite = __esm({
|
|
|
18924
19009
|
// packages/server/dist/routes/evolution.js
|
|
18925
19010
|
import { Hono as Hono7 } from "hono";
|
|
18926
19011
|
import { eq as eq8, desc as desc2, lt as lt3, and as and6, or, sql } from "drizzle-orm";
|
|
18927
|
-
import
|
|
18928
|
-
import
|
|
19012
|
+
import fs40 from "node:fs/promises";
|
|
19013
|
+
import path48 from "node:path";
|
|
18929
19014
|
function createEvolutionRoutes() {
|
|
18930
19015
|
const router = new Hono7();
|
|
18931
19016
|
router.get("/evolution/runs", async (c) => {
|
|
@@ -18990,33 +19075,33 @@ function createEvolutionRoutes() {
|
|
|
18990
19075
|
const dir = wsEvoRunDir(row.workspacePath, id);
|
|
18991
19076
|
let patchMd = null;
|
|
18992
19077
|
try {
|
|
18993
|
-
patchMd = await
|
|
19078
|
+
patchMd = await fs40.readFile(path48.join(dir, "patch.md"), "utf-8");
|
|
18994
19079
|
} catch {
|
|
18995
19080
|
}
|
|
18996
19081
|
let scoreJson = null;
|
|
18997
19082
|
try {
|
|
18998
|
-
const raw = await
|
|
19083
|
+
const raw = await fs40.readFile(path48.join(dir, "score.json"), "utf-8");
|
|
18999
19084
|
scoreJson = JSON.parse(raw);
|
|
19000
19085
|
} catch {
|
|
19001
19086
|
}
|
|
19002
19087
|
let wrapperLog = null;
|
|
19003
19088
|
try {
|
|
19004
|
-
wrapperLog = await
|
|
19089
|
+
wrapperLog = await fs40.readFile(evoWrapperLogFile(id), "utf-8");
|
|
19005
19090
|
} catch {
|
|
19006
19091
|
}
|
|
19007
19092
|
let subCliLog = null;
|
|
19008
19093
|
try {
|
|
19009
|
-
subCliLog = await
|
|
19094
|
+
subCliLog = await fs40.readFile(path48.join(dir, "sub-cli.log"), "utf-8");
|
|
19010
19095
|
} catch {
|
|
19011
19096
|
}
|
|
19012
19097
|
let skipReasonMd = null;
|
|
19013
19098
|
try {
|
|
19014
|
-
skipReasonMd = await
|
|
19099
|
+
skipReasonMd = await fs40.readFile(path48.join(dir, ".skip.md"), "utf-8");
|
|
19015
19100
|
} catch {
|
|
19016
19101
|
}
|
|
19017
19102
|
let snapshotSummary = null;
|
|
19018
19103
|
try {
|
|
19019
|
-
const raw = await
|
|
19104
|
+
const raw = await fs40.readFile(path48.join(dir, "source-snapshot.json"), "utf-8");
|
|
19020
19105
|
const parsed = JSON.parse(raw);
|
|
19021
19106
|
const msgs = parsed.rawMessages ?? [];
|
|
19022
19107
|
const firstUser = msgs.find((m) => m.role === "user");
|
|
@@ -19504,12 +19589,12 @@ var init_session_archive2 = __esm({
|
|
|
19504
19589
|
import Database4 from "better-sqlite3";
|
|
19505
19590
|
import { drizzle as drizzle3 } from "drizzle-orm/better-sqlite3";
|
|
19506
19591
|
import { sqliteTable as sqliteTable3, text as text3, integer as integer3 } from "drizzle-orm/sqlite-core";
|
|
19507
|
-
import
|
|
19508
|
-
import
|
|
19592
|
+
import path49 from "node:path";
|
|
19593
|
+
import fs41 from "node:fs";
|
|
19509
19594
|
function createChannelDb(secretsDir) {
|
|
19510
|
-
const channelsDir =
|
|
19511
|
-
|
|
19512
|
-
const dbPath =
|
|
19595
|
+
const channelsDir = path49.join(secretsDir, "channels");
|
|
19596
|
+
fs41.mkdirSync(channelsDir, { recursive: true });
|
|
19597
|
+
const dbPath = path49.join(channelsDir, "channels.db");
|
|
19513
19598
|
const sqlite = new Database4(dbPath);
|
|
19514
19599
|
sqlite.pragma("journal_mode = WAL");
|
|
19515
19600
|
sqlite.exec(CREATE_SQL2);
|
|
@@ -19559,11 +19644,11 @@ CREATE INDEX IF NOT EXISTS idx_channel_accounts_type ON channel_accounts(channel
|
|
|
19559
19644
|
});
|
|
19560
19645
|
|
|
19561
19646
|
// packages/server/dist/channels/shared/accounts.js
|
|
19562
|
-
import
|
|
19563
|
-
import
|
|
19647
|
+
import fs42 from "node:fs";
|
|
19648
|
+
import path50 from "node:path";
|
|
19564
19649
|
import { eq as eq10, and as and7 } from "drizzle-orm";
|
|
19565
19650
|
function resolveAccountWorkspace(account) {
|
|
19566
|
-
if (!
|
|
19651
|
+
if (!fs42.existsSync(account.workspacePath))
|
|
19567
19652
|
return null;
|
|
19568
19653
|
ensureWorkspaceHalo(account.workspacePath);
|
|
19569
19654
|
return account.workspacePath;
|
|
@@ -19576,9 +19661,9 @@ function accessLevelError(value, allowed) {
|
|
|
19576
19661
|
return `accessLevel must be one of: ${allowed.join(", ")}`;
|
|
19577
19662
|
}
|
|
19578
19663
|
function validateWorkspaceBody(workspacePath) {
|
|
19579
|
-
if (!
|
|
19664
|
+
if (!path50.isAbsolute(workspacePath))
|
|
19580
19665
|
return "workspacePath must be absolute";
|
|
19581
|
-
if (!
|
|
19666
|
+
if (!fs42.existsSync(workspacePath))
|
|
19582
19667
|
return "workspace path not found";
|
|
19583
19668
|
ensureWorkspaceHalo(workspacePath);
|
|
19584
19669
|
return null;
|
|
@@ -19878,22 +19963,22 @@ var init_session_types = __esm({
|
|
|
19878
19963
|
|
|
19879
19964
|
// packages/server/dist/routes/halo-city.js
|
|
19880
19965
|
import { Hono as Hono10 } from "hono";
|
|
19881
|
-
import
|
|
19882
|
-
import
|
|
19966
|
+
import fs43 from "node:fs";
|
|
19967
|
+
import path51 from "node:path";
|
|
19883
19968
|
import Database5 from "better-sqlite3";
|
|
19884
19969
|
function scanSkills2(dir) {
|
|
19885
19970
|
let names;
|
|
19886
19971
|
try {
|
|
19887
|
-
names =
|
|
19972
|
+
names = fs43.readdirSync(dir);
|
|
19888
19973
|
} catch {
|
|
19889
19974
|
return [];
|
|
19890
19975
|
}
|
|
19891
19976
|
const out = [];
|
|
19892
19977
|
for (const entry of names) {
|
|
19893
|
-
const skillMd =
|
|
19978
|
+
const skillMd = path51.join(dir, entry, "SKILL.md");
|
|
19894
19979
|
let stat;
|
|
19895
19980
|
try {
|
|
19896
|
-
stat =
|
|
19981
|
+
stat = fs43.statSync(skillMd);
|
|
19897
19982
|
if (!stat.isFile())
|
|
19898
19983
|
continue;
|
|
19899
19984
|
} catch {
|
|
@@ -19902,7 +19987,7 @@ function scanSkills2(dir) {
|
|
|
19902
19987
|
let meta = _skillCache.get(skillMd, stat.mtimeMs);
|
|
19903
19988
|
if (!meta) {
|
|
19904
19989
|
try {
|
|
19905
|
-
const { name, description, command } = parseSkillFrontmatter(
|
|
19990
|
+
const { name, description, command } = parseSkillFrontmatter(fs43.readFileSync(skillMd, "utf-8"));
|
|
19906
19991
|
meta = _skillCache.set(skillMd, stat.mtimeMs, { id: entry, name: name || entry, description: description ?? "", command });
|
|
19907
19992
|
} catch {
|
|
19908
19993
|
continue;
|
|
@@ -19988,10 +20073,10 @@ function snapshotWorkspace(sm, wsPath, label) {
|
|
|
19988
20073
|
messageCount: meta.messageCount
|
|
19989
20074
|
};
|
|
19990
20075
|
});
|
|
19991
|
-
const wsSkills = scanSkills2(
|
|
20076
|
+
const wsSkills = scanSkills2(path51.join(wsPath, ".halo", "skills"));
|
|
19992
20077
|
return {
|
|
19993
20078
|
path: wsPath,
|
|
19994
|
-
key:
|
|
20079
|
+
key: path51.basename(wsPath) || wsPath,
|
|
19995
20080
|
label,
|
|
19996
20081
|
sessions,
|
|
19997
20082
|
counts,
|
|
@@ -20002,15 +20087,15 @@ function snapshotWorkspace(sm, wsPath, label) {
|
|
|
20002
20087
|
function roReader(rawPath) {
|
|
20003
20088
|
let wsPath;
|
|
20004
20089
|
try {
|
|
20005
|
-
wsPath =
|
|
20090
|
+
wsPath = fs43.realpathSync(rawPath);
|
|
20006
20091
|
} catch {
|
|
20007
20092
|
return null;
|
|
20008
20093
|
}
|
|
20009
20094
|
const cached = _roReaders.get(wsPath);
|
|
20010
20095
|
if (cached)
|
|
20011
20096
|
return cached;
|
|
20012
|
-
const dbPath =
|
|
20013
|
-
if (!
|
|
20097
|
+
const dbPath = path51.join(wsPath, ".halo", "halo.db");
|
|
20098
|
+
if (!fs43.existsSync(dbPath))
|
|
20014
20099
|
return null;
|
|
20015
20100
|
try {
|
|
20016
20101
|
const db = new Database5(dbPath, { readonly: true, fileMustExist: true });
|
|
@@ -20038,7 +20123,7 @@ function dropRoReader(rawPath) {
|
|
|
20038
20123
|
return;
|
|
20039
20124
|
let wsPath = rawPath;
|
|
20040
20125
|
try {
|
|
20041
|
-
wsPath =
|
|
20126
|
+
wsPath = fs43.realpathSync(rawPath);
|
|
20042
20127
|
} catch {
|
|
20043
20128
|
}
|
|
20044
20129
|
const r = _roReaders.get(wsPath);
|
|
@@ -20086,12 +20171,12 @@ function snapshotWorkspaceReadonly(wsPath, label) {
|
|
|
20086
20171
|
});
|
|
20087
20172
|
return {
|
|
20088
20173
|
path: wsPath,
|
|
20089
|
-
key:
|
|
20174
|
+
key: path51.basename(wsPath) || wsPath,
|
|
20090
20175
|
label,
|
|
20091
20176
|
sessions,
|
|
20092
20177
|
counts,
|
|
20093
20178
|
totalSessions: counts.total,
|
|
20094
|
-
skills: scanSkills2(
|
|
20179
|
+
skills: scanSkills2(path51.join(wsPath, ".halo", "skills"))
|
|
20095
20180
|
};
|
|
20096
20181
|
}
|
|
20097
20182
|
function readonlySessionCounts(wsPath, limit) {
|
|
@@ -20135,7 +20220,7 @@ function workspaceBirth(wsPath) {
|
|
|
20135
20220
|
let b = _birthCache.get(wsPath);
|
|
20136
20221
|
if (b === void 0) {
|
|
20137
20222
|
try {
|
|
20138
|
-
b =
|
|
20223
|
+
b = fs43.statSync(wsPath).birthtimeMs || fs43.statSync(wsPath).ctimeMs;
|
|
20139
20224
|
} catch {
|
|
20140
20225
|
b = 0;
|
|
20141
20226
|
}
|
|
@@ -20147,18 +20232,18 @@ function discoverWorkspaces(registry2) {
|
|
|
20147
20232
|
const out = /* @__PURE__ */ new Map();
|
|
20148
20233
|
const add = (p, label) => {
|
|
20149
20234
|
try {
|
|
20150
|
-
const resolved =
|
|
20235
|
+
const resolved = fs43.realpathSync(p);
|
|
20151
20236
|
if (!out.has(resolved))
|
|
20152
20237
|
out.set(resolved, label);
|
|
20153
20238
|
} catch {
|
|
20154
20239
|
}
|
|
20155
20240
|
};
|
|
20156
20241
|
for (const { workspacePath } of registry2.list())
|
|
20157
|
-
add(workspacePath,
|
|
20242
|
+
add(workspacePath, path51.basename(workspacePath));
|
|
20158
20243
|
try {
|
|
20159
20244
|
const rows = getChannelDb().select().from(channelAccounts).all();
|
|
20160
20245
|
for (const row of rows)
|
|
20161
|
-
add(row.workspacePath,
|
|
20246
|
+
add(row.workspacePath, path51.basename(row.workspacePath));
|
|
20162
20247
|
} catch {
|
|
20163
20248
|
}
|
|
20164
20249
|
return out;
|
|
@@ -20177,7 +20262,7 @@ function createShowRoutes(registry2) {
|
|
|
20177
20262
|
return a.response;
|
|
20178
20263
|
const { account } = a;
|
|
20179
20264
|
const globalView = account.accessLevel === "full" || account.accessLevel === "observer";
|
|
20180
|
-
const targets = globalView ? discoverWorkspaces(registry2) : /* @__PURE__ */ new Map([[account.workspacePath,
|
|
20265
|
+
const targets = globalView ? discoverWorkspaces(registry2) : /* @__PURE__ */ new Map([[account.workspacePath, path51.basename(account.workspacePath)]]);
|
|
20181
20266
|
const workspaces = [];
|
|
20182
20267
|
for (const [wsPath, label] of targets) {
|
|
20183
20268
|
try {
|
|
@@ -20211,7 +20296,7 @@ function createShowRoutes(registry2) {
|
|
|
20211
20296
|
if (!globalView) {
|
|
20212
20297
|
let allowed = false;
|
|
20213
20298
|
try {
|
|
20214
|
-
allowed =
|
|
20299
|
+
allowed = fs43.realpathSync(wsPath) === fs43.realpathSync(account.workspacePath);
|
|
20215
20300
|
} catch {
|
|
20216
20301
|
}
|
|
20217
20302
|
if (!allowed)
|
|
@@ -20405,16 +20490,77 @@ var init_commands3 = __esm({
|
|
|
20405
20490
|
|
|
20406
20491
|
// packages/server/dist/routes/agentcore.js
|
|
20407
20492
|
import { Hono as Hono13 } from "hono";
|
|
20408
|
-
import
|
|
20409
|
-
import
|
|
20493
|
+
import path52 from "node:path";
|
|
20494
|
+
import fs44 from "node:fs";
|
|
20495
|
+
function sanitizeRuntimeId(runtimeSessionId) {
|
|
20496
|
+
return runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80);
|
|
20497
|
+
}
|
|
20410
20498
|
function haloSessionId(runtimeSessionId) {
|
|
20411
|
-
return `agentcore_${runtimeSessionId
|
|
20499
|
+
return `agentcore_${sanitizeRuntimeId(runtimeSessionId)}`;
|
|
20412
20500
|
}
|
|
20413
20501
|
function userWorkspace(base, runtimeSessionId) {
|
|
20414
|
-
const dir =
|
|
20415
|
-
|
|
20502
|
+
const dir = path52.join(base, "users", sanitizeRuntimeId(runtimeSessionId));
|
|
20503
|
+
fs44.mkdirSync(dir, { recursive: true });
|
|
20416
20504
|
return dir;
|
|
20417
20505
|
}
|
|
20506
|
+
function acceptChunk(uploads, msg) {
|
|
20507
|
+
const uploadId = msg.uploadId;
|
|
20508
|
+
const seq = msg.seq;
|
|
20509
|
+
const total = msg.total;
|
|
20510
|
+
const mimeType = msg.mimeType;
|
|
20511
|
+
const data = msg.data;
|
|
20512
|
+
if (typeof uploadId !== "string" || !uploadId || uploadId.length > 64)
|
|
20513
|
+
return "image_chunk: uploadId must be a non-empty string (\u226464 chars)";
|
|
20514
|
+
if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 0)
|
|
20515
|
+
return "image_chunk: seq must be a non-negative integer";
|
|
20516
|
+
if (typeof total !== "number" || !Number.isInteger(total) || total < 1 || total > MAX_CHUNKS_PER_UPLOAD)
|
|
20517
|
+
return `image_chunk: total must be 1..${MAX_CHUNKS_PER_UPLOAD}`;
|
|
20518
|
+
if (typeof mimeType !== "string" || typeof data !== "string" || !data)
|
|
20519
|
+
return "image_chunk: string mimeType and non-empty string data required";
|
|
20520
|
+
if (seq >= total)
|
|
20521
|
+
return "image_chunk: seq out of range";
|
|
20522
|
+
let up = uploads.get(uploadId);
|
|
20523
|
+
if (!up) {
|
|
20524
|
+
if (uploads.size >= MAX_PENDING_UPLOADS)
|
|
20525
|
+
return `too many pending uploads (max ${MAX_PENDING_UPLOADS} per connection)`;
|
|
20526
|
+
up = { chunks: new Array(total), received: 0, mimeType, size: 0 };
|
|
20527
|
+
uploads.set(uploadId, up);
|
|
20528
|
+
}
|
|
20529
|
+
if (up.chunks.length !== total)
|
|
20530
|
+
return "image_chunk: total mismatch across chunks of one uploadId";
|
|
20531
|
+
if (up.chunks[seq] !== void 0)
|
|
20532
|
+
return null;
|
|
20533
|
+
if (up.size + data.length > MAX_IMAGE_BASE64_LENGTH) {
|
|
20534
|
+
uploads.delete(uploadId);
|
|
20535
|
+
return "image too large (max 8MB base64 per image)";
|
|
20536
|
+
}
|
|
20537
|
+
up.chunks[seq] = data;
|
|
20538
|
+
up.received++;
|
|
20539
|
+
up.size += data.length;
|
|
20540
|
+
return null;
|
|
20541
|
+
}
|
|
20542
|
+
function claimImageRefs(uploads, raw) {
|
|
20543
|
+
if (raw === void 0)
|
|
20544
|
+
return [];
|
|
20545
|
+
if (!Array.isArray(raw))
|
|
20546
|
+
return { error: "imageRefs must be an array" };
|
|
20547
|
+
if (raw.length > MAX_IMAGES_PER_MESSAGE)
|
|
20548
|
+
return { error: `too many images (max ${MAX_IMAGES_PER_MESSAGE} per message)` };
|
|
20549
|
+
const images = [];
|
|
20550
|
+
for (const ref of raw) {
|
|
20551
|
+
if (typeof ref !== "string")
|
|
20552
|
+
return { error: "imageRefs entries must be strings" };
|
|
20553
|
+
const up = uploads.get(ref);
|
|
20554
|
+
if (!up)
|
|
20555
|
+
return { error: `unknown uploadId: ${ref}` };
|
|
20556
|
+
if (up.received !== up.chunks.length)
|
|
20557
|
+
return { error: `upload ${ref} incomplete (${up.received}/${up.chunks.length} chunks)` };
|
|
20558
|
+
images.push({ data: up.chunks.join(""), mimeType: up.mimeType });
|
|
20559
|
+
}
|
|
20560
|
+
for (const ref of raw)
|
|
20561
|
+
uploads.delete(ref);
|
|
20562
|
+
return images;
|
|
20563
|
+
}
|
|
20418
20564
|
async function ensureSession(sm, workspace, sessionId) {
|
|
20419
20565
|
if (sm.getSessionById(sessionId))
|
|
20420
20566
|
return;
|
|
@@ -20553,6 +20699,7 @@ function setupAgentCoreWebSocket(deps) {
|
|
|
20553
20699
|
ready ??= ensureSession(sm, userWs, sessionId).then(() => attachListener());
|
|
20554
20700
|
return ready;
|
|
20555
20701
|
};
|
|
20702
|
+
const uploads = /* @__PURE__ */ new Map();
|
|
20556
20703
|
ws.on("message", (raw) => {
|
|
20557
20704
|
let msg;
|
|
20558
20705
|
try {
|
|
@@ -20578,8 +20725,20 @@ function setupAgentCoreWebSocket(deps) {
|
|
|
20578
20725
|
});
|
|
20579
20726
|
return;
|
|
20580
20727
|
}
|
|
20728
|
+
if (msg.type === "image_chunk") {
|
|
20729
|
+
const err = acceptChunk(uploads, msg);
|
|
20730
|
+
if (err)
|
|
20731
|
+
send({ type: "error", error: err });
|
|
20732
|
+
return;
|
|
20733
|
+
}
|
|
20581
20734
|
const text5 = typeof msg.inputText === "string" ? msg.inputText.trim() : "";
|
|
20582
|
-
|
|
20735
|
+
const claimed = claimImageRefs(uploads, msg.imageRefs);
|
|
20736
|
+
if (!Array.isArray(claimed)) {
|
|
20737
|
+
send({ type: "error", error: claimed.error });
|
|
20738
|
+
return;
|
|
20739
|
+
}
|
|
20740
|
+
const images = claimed;
|
|
20741
|
+
if (!text5 && images.length === 0) {
|
|
20583
20742
|
return;
|
|
20584
20743
|
}
|
|
20585
20744
|
if (text5.startsWith("/")) {
|
|
@@ -20637,10 +20796,29 @@ function setupAgentCoreWebSocket(deps) {
|
|
|
20637
20796
|
void (async () => {
|
|
20638
20797
|
try {
|
|
20639
20798
|
await ensureOnce();
|
|
20640
|
-
|
|
20799
|
+
const notes = [];
|
|
20800
|
+
for (const img of images) {
|
|
20801
|
+
try {
|
|
20802
|
+
const savedPath = await saveInboundMedia({
|
|
20803
|
+
workspacePath: userWs,
|
|
20804
|
+
accountId: sanitizeRuntimeId(runtimeSessionId),
|
|
20805
|
+
channel: "agentcore",
|
|
20806
|
+
buffer: Buffer.from(img.data, "base64"),
|
|
20807
|
+
kind: "image",
|
|
20808
|
+
mimeType: img.mimeType
|
|
20809
|
+
});
|
|
20810
|
+
notes.push(`[\u56FE\u7247\u5DF2\u4FDD\u5B58: ${savedPath}]`);
|
|
20811
|
+
} catch (err) {
|
|
20812
|
+
console.log(`[AgentCore] image save failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
20813
|
+
notes.push("[\u56FE\u7247]");
|
|
20814
|
+
}
|
|
20815
|
+
}
|
|
20816
|
+
const fullText = notes.length > 0 ? text5 ? `${text5}
|
|
20817
|
+
${notes.join("\n")}` : notes.join("\n") : text5;
|
|
20818
|
+
sm.appendUserMessage(sessionId, fullText);
|
|
20641
20819
|
const result = await sm.sendUserMessage(sessionId, `[channel: agentcore]
|
|
20642
20820
|
|
|
20643
|
-
${
|
|
20821
|
+
${fullText}`, images.length > 0 ? images : void 0);
|
|
20644
20822
|
if (result === "queued")
|
|
20645
20823
|
send({ type: "queued" });
|
|
20646
20824
|
} catch (err) {
|
|
@@ -20651,26 +20829,32 @@ ${text5}`);
|
|
|
20651
20829
|
ws.on("close", () => {
|
|
20652
20830
|
unsubscribe?.();
|
|
20653
20831
|
unsubscribe = null;
|
|
20832
|
+
uploads.clear();
|
|
20654
20833
|
activeOverrides.delete(runtimeSessionId);
|
|
20655
20834
|
});
|
|
20656
20835
|
console.log(`[AgentCore] ws connected \u2014 session ${sessionId}`);
|
|
20657
20836
|
});
|
|
20658
20837
|
}
|
|
20659
|
-
var SESSION_HEADER, activeOverrides;
|
|
20838
|
+
var SESSION_HEADER, MAX_IMAGES_PER_MESSAGE, MAX_IMAGE_BASE64_LENGTH, MAX_PENDING_UPLOADS, MAX_CHUNKS_PER_UPLOAD, activeOverrides;
|
|
20660
20839
|
var init_agentcore = __esm({
|
|
20661
20840
|
"packages/server/dist/routes/agentcore.js"() {
|
|
20662
20841
|
"use strict";
|
|
20663
20842
|
init_commands2();
|
|
20664
20843
|
init_ui_log_builder();
|
|
20665
20844
|
init_session_types();
|
|
20845
|
+
init_media_store();
|
|
20666
20846
|
SESSION_HEADER = "x-amzn-bedrock-agentcore-runtime-session-id";
|
|
20847
|
+
MAX_IMAGES_PER_MESSAGE = 4;
|
|
20848
|
+
MAX_IMAGE_BASE64_LENGTH = 8 * 1024 * 1024;
|
|
20849
|
+
MAX_PENDING_UPLOADS = 4;
|
|
20850
|
+
MAX_CHUNKS_PER_UPLOAD = 256;
|
|
20667
20851
|
activeOverrides = /* @__PURE__ */ new Map();
|
|
20668
20852
|
}
|
|
20669
20853
|
});
|
|
20670
20854
|
|
|
20671
20855
|
// packages/server/dist/ws/file-watcher.js
|
|
20672
|
-
import
|
|
20673
|
-
import
|
|
20856
|
+
import fs45 from "node:fs";
|
|
20857
|
+
import path53 from "node:path";
|
|
20674
20858
|
function enqueueNativeOp(op) {
|
|
20675
20859
|
const result = nativeOpChain.then(op);
|
|
20676
20860
|
nativeOpChain = result.catch(() => {
|
|
@@ -20678,12 +20862,12 @@ function enqueueNativeOp(op) {
|
|
|
20678
20862
|
return result;
|
|
20679
20863
|
}
|
|
20680
20864
|
function isUnwatchablePath(absPath) {
|
|
20681
|
-
const norm =
|
|
20682
|
-
if (norm === "/" || norm ===
|
|
20865
|
+
const norm = path53.resolve(absPath);
|
|
20866
|
+
if (norm === "/" || norm === path53.parse(norm).root)
|
|
20683
20867
|
return "filesystem root";
|
|
20684
20868
|
const banned = ["/System", "/Library", "/private", "/usr", "/bin", "/sbin", "/etc"];
|
|
20685
20869
|
for (const b of banned) {
|
|
20686
|
-
if (norm === b || norm.startsWith(b +
|
|
20870
|
+
if (norm === b || norm.startsWith(b + path53.sep))
|
|
20687
20871
|
return `system path ${b}`;
|
|
20688
20872
|
}
|
|
20689
20873
|
return null;
|
|
@@ -20779,7 +20963,7 @@ var init_file_watcher = __esm({
|
|
|
20779
20963
|
}
|
|
20780
20964
|
let root = workspaceRoot;
|
|
20781
20965
|
try {
|
|
20782
|
-
root =
|
|
20966
|
+
root = fs45.realpathSync(workspaceRoot);
|
|
20783
20967
|
} catch {
|
|
20784
20968
|
}
|
|
20785
20969
|
if (this.subscription && this.workspaceRoot === root)
|
|
@@ -20823,10 +21007,10 @@ var init_file_watcher = __esm({
|
|
|
20823
21007
|
const root = this.workspaceRoot;
|
|
20824
21008
|
if (!root)
|
|
20825
21009
|
return;
|
|
20826
|
-
const raw =
|
|
21010
|
+
const raw = path53.relative(root, absPath);
|
|
20827
21011
|
if (!raw || raw.startsWith(".."))
|
|
20828
21012
|
return;
|
|
20829
|
-
const rel = raw.split(
|
|
21013
|
+
const rel = raw.split(path53.sep).join("/");
|
|
20830
21014
|
if (IS_WIN32 && rel.split("/").some((seg) => IGNORED_SEGMENT_SET.has(seg)))
|
|
20831
21015
|
return;
|
|
20832
21016
|
let action;
|
|
@@ -20837,7 +21021,7 @@ var init_file_watcher = __esm({
|
|
|
20837
21021
|
} else {
|
|
20838
21022
|
let isDir = false;
|
|
20839
21023
|
try {
|
|
20840
|
-
isDir =
|
|
21024
|
+
isDir = fs45.statSync(absPath).isDirectory();
|
|
20841
21025
|
} catch {
|
|
20842
21026
|
}
|
|
20843
21027
|
action = isDir ? "addDir" : "add";
|
|
@@ -20913,8 +21097,8 @@ var init_file_watcher = __esm({
|
|
|
20913
21097
|
});
|
|
20914
21098
|
|
|
20915
21099
|
// packages/server/dist/ws/git-dir-watcher.js
|
|
20916
|
-
import
|
|
20917
|
-
import
|
|
21100
|
+
import fs46 from "node:fs";
|
|
21101
|
+
import path54 from "node:path";
|
|
20918
21102
|
var GitDirWatcher;
|
|
20919
21103
|
var init_git_dir_watcher = __esm({
|
|
20920
21104
|
"packages/server/dist/ws/git-dir-watcher.js"() {
|
|
@@ -20938,8 +21122,8 @@ var init_git_dir_watcher = __esm({
|
|
|
20938
21122
|
return;
|
|
20939
21123
|
this.stop();
|
|
20940
21124
|
this.workspaceRoot = workspaceRoot;
|
|
20941
|
-
const gitDir =
|
|
20942
|
-
if (
|
|
21125
|
+
const gitDir = path54.join(workspaceRoot, ".git");
|
|
21126
|
+
if (fs46.existsSync(gitDir)) {
|
|
20943
21127
|
this.watchGitDir(gitDir);
|
|
20944
21128
|
} else {
|
|
20945
21129
|
this.watchParentForGit(workspaceRoot);
|
|
@@ -20948,7 +21132,7 @@ var init_git_dir_watcher = __esm({
|
|
|
20948
21132
|
/** Watch the `.git` dir for index/HEAD changes (steady state). */
|
|
20949
21133
|
watchGitDir(gitDir) {
|
|
20950
21134
|
try {
|
|
20951
|
-
this.watcher =
|
|
21135
|
+
this.watcher = fs46.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
20952
21136
|
this.handleGitEvent(filename);
|
|
20953
21137
|
});
|
|
20954
21138
|
this.watcher.on("error", (err) => {
|
|
@@ -20967,7 +21151,7 @@ var init_git_dir_watcher = __esm({
|
|
|
20967
21151
|
* Exists solely during the non-repo window; upgraded away on first `.git`. */
|
|
20968
21152
|
watchParentForGit(workspaceRoot) {
|
|
20969
21153
|
try {
|
|
20970
|
-
this.watcher =
|
|
21154
|
+
this.watcher = fs46.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
20971
21155
|
this.handleParentEvent(filename);
|
|
20972
21156
|
});
|
|
20973
21157
|
this.watcher.on("error", (err) => {
|
|
@@ -21003,8 +21187,8 @@ var init_git_dir_watcher = __esm({
|
|
|
21003
21187
|
const root = this.workspaceRoot;
|
|
21004
21188
|
if (!root)
|
|
21005
21189
|
return;
|
|
21006
|
-
const gitDir =
|
|
21007
|
-
if (!
|
|
21190
|
+
const gitDir = path54.join(root, ".git");
|
|
21191
|
+
if (!fs46.existsSync(gitDir))
|
|
21008
21192
|
return;
|
|
21009
21193
|
this.closeWatcher();
|
|
21010
21194
|
this.watchGitDir(gitDir);
|
|
@@ -21320,7 +21504,7 @@ var init_terminal_manager = __esm({
|
|
|
21320
21504
|
});
|
|
21321
21505
|
|
|
21322
21506
|
// packages/server/dist/ws/handler.js
|
|
21323
|
-
import
|
|
21507
|
+
import path55 from "node:path";
|
|
21324
21508
|
function setupWebSocketHandler(deps) {
|
|
21325
21509
|
const { wss: wss2, registry: registry2 } = deps;
|
|
21326
21510
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -21342,7 +21526,7 @@ function setupWebSocketHandler(deps) {
|
|
|
21342
21526
|
ackedChatIds.add(id);
|
|
21343
21527
|
}
|
|
21344
21528
|
function resolveProjectPath2(projectId) {
|
|
21345
|
-
if (
|
|
21529
|
+
if (path55.isAbsolute(projectId))
|
|
21346
21530
|
return projectId;
|
|
21347
21531
|
return null;
|
|
21348
21532
|
}
|
|
@@ -21968,11 +22152,11 @@ var init_handler = __esm({
|
|
|
21968
22152
|
import Database6 from "better-sqlite3";
|
|
21969
22153
|
import { drizzle as drizzle4 } from "drizzle-orm/better-sqlite3";
|
|
21970
22154
|
import { sqliteTable as sqliteTable4, text as text4, integer as integer4 } from "drizzle-orm/sqlite-core";
|
|
21971
|
-
import
|
|
21972
|
-
import
|
|
22155
|
+
import path56 from "node:path";
|
|
22156
|
+
import fs47 from "node:fs";
|
|
21973
22157
|
function createCronDb(globalDir2) {
|
|
21974
|
-
|
|
21975
|
-
const dbPath =
|
|
22158
|
+
fs47.mkdirSync(globalDir2, { recursive: true });
|
|
22159
|
+
const dbPath = path56.join(globalDir2, "cron.db");
|
|
21976
22160
|
const sqlite = new Database6(dbPath);
|
|
21977
22161
|
sqlite.pragma("journal_mode = WAL");
|
|
21978
22162
|
sqlite.exec(CREATE_SQL3);
|
|
@@ -21980,6 +22164,9 @@ function createCronDb(globalDir2) {
|
|
|
21980
22164
|
if (!cols.some((c) => c.name === "run_at")) {
|
|
21981
22165
|
sqlite.exec(`ALTER TABLE cron_jobs ADD COLUMN run_at INTEGER`);
|
|
21982
22166
|
}
|
|
22167
|
+
if (!cols.some((c) => c.name === "timeout_sec")) {
|
|
22168
|
+
sqlite.exec(`ALTER TABLE cron_jobs ADD COLUMN timeout_sec INTEGER`);
|
|
22169
|
+
}
|
|
21983
22170
|
const runCols = sqlite.prepare(`PRAGMA table_info(cron_runs)`).all();
|
|
21984
22171
|
if (!runCols.some((c) => c.name === "pid")) {
|
|
21985
22172
|
sqlite.exec(`ALTER TABLE cron_runs ADD COLUMN pid INTEGER`);
|
|
@@ -22020,6 +22207,10 @@ var init_cron_db = __esm({
|
|
|
22020
22207
|
runAt: integer4("run_at"),
|
|
22021
22208
|
/** IANA timezone name; null = host timezone. */
|
|
22022
22209
|
timezone: text4("timezone"),
|
|
22210
|
+
/** Max seconds a single cron-fired cli may run before the runner kills
|
|
22211
|
+
* it. NULL = default 3600 (runner.ts CLI_TIMEOUT_SEC). Range 60–21600,
|
|
22212
|
+
* enforced at the write points (REST routes / skill helper). */
|
|
22213
|
+
timeoutSec: integer4("timeout_sec"),
|
|
22023
22214
|
/** JSON array of `{channelType, accountId}` records. Cron output (final
|
|
22024
22215
|
* assistant text) is dispatched to each one. Empty array = log only. */
|
|
22025
22216
|
targets: text4("targets").notNull().default("[]"),
|
|
@@ -22069,6 +22260,7 @@ CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
|
22069
22260
|
schedule TEXT NOT NULL,
|
|
22070
22261
|
run_at INTEGER,
|
|
22071
22262
|
timezone TEXT,
|
|
22263
|
+
timeout_sec INTEGER,
|
|
22072
22264
|
targets TEXT NOT NULL DEFAULT '[]',
|
|
22073
22265
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
22074
22266
|
last_run_status TEXT,
|
|
@@ -22170,8 +22362,8 @@ var init_child_env = __esm({
|
|
|
22170
22362
|
|
|
22171
22363
|
// packages/server/dist/cron/runner.js
|
|
22172
22364
|
import { spawn as spawn3, execFileSync } from "node:child_process";
|
|
22173
|
-
import
|
|
22174
|
-
import
|
|
22365
|
+
import fs48 from "node:fs";
|
|
22366
|
+
import path57 from "node:path";
|
|
22175
22367
|
import { homedir as homedir23 } from "node:os";
|
|
22176
22368
|
import { Cron } from "croner";
|
|
22177
22369
|
import { eq as eq11, lt as lt4 } from "drizzle-orm";
|
|
@@ -22242,10 +22434,10 @@ function isCronCliProcess(pid, jobId) {
|
|
|
22242
22434
|
}
|
|
22243
22435
|
}
|
|
22244
22436
|
function logsDir() {
|
|
22245
|
-
return
|
|
22437
|
+
return path57.join(homedir23(), ".halo", "global", "logs", "cron");
|
|
22246
22438
|
}
|
|
22247
22439
|
function logPathFor(runId) {
|
|
22248
|
-
return
|
|
22440
|
+
return path57.join(logsDir(), `${runId}.log`);
|
|
22249
22441
|
}
|
|
22250
22442
|
function newRunId() {
|
|
22251
22443
|
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -22296,7 +22488,7 @@ function sweepOrphanRuns(opts) {
|
|
|
22296
22488
|
}
|
|
22297
22489
|
}
|
|
22298
22490
|
function startCronDaemon() {
|
|
22299
|
-
|
|
22491
|
+
fs48.mkdirSync(logsDir(), { recursive: true });
|
|
22300
22492
|
sweepOrphanRuns();
|
|
22301
22493
|
reloadAll();
|
|
22302
22494
|
_daemonTimers.push(setInterval(pruneOldLogs, DAY_MS2));
|
|
@@ -22453,7 +22645,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
22453
22645
|
const runId = newRunId();
|
|
22454
22646
|
const startedAt = Date.now();
|
|
22455
22647
|
const logPath = logPathFor(runId);
|
|
22456
|
-
|
|
22648
|
+
fs48.mkdirSync(logsDir(), { recursive: true });
|
|
22457
22649
|
if (_inflight.has(jobId)) {
|
|
22458
22650
|
db.insert(cronRuns).values({
|
|
22459
22651
|
id: runId,
|
|
@@ -22488,7 +22680,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
22488
22680
|
}).run();
|
|
22489
22681
|
broadcast({ type: "cron:run_changed", jobId, runId, status: "running" });
|
|
22490
22682
|
const targets = parseTargets(job.targets);
|
|
22491
|
-
if (!
|
|
22683
|
+
if (!fs48.existsSync(job.workspacePath)) {
|
|
22492
22684
|
finalize(runId, jobId, {
|
|
22493
22685
|
status: "failed",
|
|
22494
22686
|
output: null,
|
|
@@ -22502,7 +22694,8 @@ async function runJob(jobId, triggerKind) {
|
|
|
22502
22694
|
const sessionId = `cron-${job.id}`;
|
|
22503
22695
|
const args = ["cli", "-a", job.agentId, "-s", sessionId, "-w", job.workspacePath];
|
|
22504
22696
|
const [spawnBin, spawnArgs] = process.platform === "win32" && cli.endsWith(".cmd") ? ["cmd.exe", ["/c", cli, ...args]] : [cli, args];
|
|
22505
|
-
const
|
|
22697
|
+
const timeoutSec = job.timeoutSec ?? CLI_TIMEOUT_SEC;
|
|
22698
|
+
const teeStream = fs48.createWriteStream(logPath, { flags: "a" });
|
|
22506
22699
|
teeStream.write(`=== ${(/* @__PURE__ */ new Date()).toISOString()} cron run ${runId} job=${jobId} trigger=${triggerKind} ===
|
|
22507
22700
|
`);
|
|
22508
22701
|
teeStream.write(`agent=${job.agentId} workspace=${job.workspacePath} schedule=${job.schedule}
|
|
@@ -22532,7 +22725,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
22532
22725
|
const killTimer = setTimeout(() => {
|
|
22533
22726
|
timedOut = true;
|
|
22534
22727
|
teeStream.write(`
|
|
22535
|
-
[runner] timeout ${
|
|
22728
|
+
[runner] timeout ${timeoutSec}s \u2014 SIGTERM, grace ${KILL_GRACE_SEC}s
|
|
22536
22729
|
`);
|
|
22537
22730
|
try {
|
|
22538
22731
|
child.kill("SIGTERM");
|
|
@@ -22544,7 +22737,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
22544
22737
|
if (child.pid)
|
|
22545
22738
|
killTreeHard(child.pid);
|
|
22546
22739
|
}, KILL_GRACE_SEC * 1e3);
|
|
22547
|
-
},
|
|
22740
|
+
}, timeoutSec * 1e3);
|
|
22548
22741
|
child.stdout?.on("data", (chunk) => {
|
|
22549
22742
|
stdout += chunk.toString("utf-8");
|
|
22550
22743
|
teeStream.write(chunk);
|
|
@@ -22580,7 +22773,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
22580
22773
|
let failureReason = null;
|
|
22581
22774
|
if (result.exitCode === 124) {
|
|
22582
22775
|
status = "timeout";
|
|
22583
|
-
failureReason = `cli exceeded ${
|
|
22776
|
+
failureReason = `cli exceeded ${timeoutSec}s timeout`;
|
|
22584
22777
|
} else if (result.exitCode !== 0) {
|
|
22585
22778
|
status = "failed";
|
|
22586
22779
|
failureReason = stderr.slice(-500).trim() || `cli exited ${result.exitCode}`;
|
|
@@ -22674,15 +22867,15 @@ function pruneOldRuns(jobId) {
|
|
|
22674
22867
|
}
|
|
22675
22868
|
function pruneOldLogs() {
|
|
22676
22869
|
const dir = logsDir();
|
|
22677
|
-
if (!
|
|
22870
|
+
if (!fs48.existsSync(dir))
|
|
22678
22871
|
return;
|
|
22679
22872
|
const cutoff = Date.now() - LOG_RETENTION_DAYS * DAY_MS2;
|
|
22680
|
-
for (const name of
|
|
22681
|
-
const p =
|
|
22873
|
+
for (const name of fs48.readdirSync(dir)) {
|
|
22874
|
+
const p = path57.join(dir, name);
|
|
22682
22875
|
try {
|
|
22683
|
-
const stat =
|
|
22876
|
+
const stat = fs48.statSync(p);
|
|
22684
22877
|
if (stat.mtimeMs < cutoff)
|
|
22685
|
-
|
|
22878
|
+
fs48.rmSync(p, { force: true });
|
|
22686
22879
|
} catch {
|
|
22687
22880
|
}
|
|
22688
22881
|
}
|
|
@@ -22715,7 +22908,7 @@ var init_runner = __esm({
|
|
|
22715
22908
|
// packages/server/dist/routes/cron.js
|
|
22716
22909
|
import { Hono as Hono14 } from "hono";
|
|
22717
22910
|
import { eq as eq12, desc as desc3, lt as lt5, and as and8 } from "drizzle-orm";
|
|
22718
|
-
import
|
|
22911
|
+
import fs49 from "node:fs/promises";
|
|
22719
22912
|
import { Cron as Cron2 } from "croner";
|
|
22720
22913
|
function newJobId() {
|
|
22721
22914
|
return `cron-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -22728,6 +22921,15 @@ function validateSchedule(expr) {
|
|
|
22728
22921
|
return err instanceof Error ? err.message : String(err);
|
|
22729
22922
|
}
|
|
22730
22923
|
}
|
|
22924
|
+
function validateTimeoutSec(v) {
|
|
22925
|
+
if (typeof v !== "number" || !Number.isInteger(v)) {
|
|
22926
|
+
return "timeoutSec must be an integer (seconds)";
|
|
22927
|
+
}
|
|
22928
|
+
if (v < TIMEOUT_SEC_MIN || v > TIMEOUT_SEC_MAX) {
|
|
22929
|
+
return `timeoutSec must be between ${TIMEOUT_SEC_MIN} and ${TIMEOUT_SEC_MAX}`;
|
|
22930
|
+
}
|
|
22931
|
+
return null;
|
|
22932
|
+
}
|
|
22731
22933
|
function createCronRoutes() {
|
|
22732
22934
|
const router = new Hono14();
|
|
22733
22935
|
router.get("/cron/jobs", async (c) => {
|
|
@@ -22771,6 +22973,11 @@ function createCronRoutes() {
|
|
|
22771
22973
|
if (hasRunAt && body.runAt <= Date.now()) {
|
|
22772
22974
|
return c.json({ error: "runAt must be in the future" }, 400);
|
|
22773
22975
|
}
|
|
22976
|
+
if (body.timeoutSec !== void 0 && body.timeoutSec !== null) {
|
|
22977
|
+
const err = validateTimeoutSec(body.timeoutSec);
|
|
22978
|
+
if (err)
|
|
22979
|
+
return c.json({ error: err }, 400);
|
|
22980
|
+
}
|
|
22774
22981
|
const id = newJobId();
|
|
22775
22982
|
const now = Date.now();
|
|
22776
22983
|
getCronDb().insert(cronJobs).values({
|
|
@@ -22782,6 +22989,7 @@ function createCronRoutes() {
|
|
|
22782
22989
|
schedule: hasSchedule ? body.schedule : "",
|
|
22783
22990
|
runAt: hasRunAt ? body.runAt : null,
|
|
22784
22991
|
timezone: body.timezone ?? null,
|
|
22992
|
+
timeoutSec: body.timeoutSec ?? null,
|
|
22785
22993
|
targets: JSON.stringify(body.targets ?? []),
|
|
22786
22994
|
enabled: body.enabled === false ? 0 : 1,
|
|
22787
22995
|
lastRunStatus: null,
|
|
@@ -22818,6 +23026,11 @@ function createCronRoutes() {
|
|
|
22818
23026
|
if (body.runAt <= Date.now())
|
|
22819
23027
|
return c.json({ error: "runAt must be in the future" }, 400);
|
|
22820
23028
|
}
|
|
23029
|
+
if (body.timeoutSec !== void 0 && body.timeoutSec !== null) {
|
|
23030
|
+
const err = validateTimeoutSec(body.timeoutSec);
|
|
23031
|
+
if (err)
|
|
23032
|
+
return c.json({ error: err }, 400);
|
|
23033
|
+
}
|
|
22821
23034
|
const hasSchedule = typeof body.schedule === "string" && body.schedule.trim().length > 0;
|
|
22822
23035
|
const hasRunAt = typeof body.runAt === "number";
|
|
22823
23036
|
if (hasSchedule && hasRunAt) {
|
|
@@ -22845,6 +23058,8 @@ function createCronRoutes() {
|
|
|
22845
23058
|
patch.runAt = nextRunAt;
|
|
22846
23059
|
if (body.timezone !== void 0)
|
|
22847
23060
|
patch.timezone = body.timezone || null;
|
|
23061
|
+
if (body.timeoutSec !== void 0)
|
|
23062
|
+
patch.timeoutSec = body.timeoutSec;
|
|
22848
23063
|
if (body.targets !== void 0)
|
|
22849
23064
|
patch.targets = JSON.stringify(body.targets);
|
|
22850
23065
|
if (body.enabled !== void 0)
|
|
@@ -22911,7 +23126,7 @@ function createCronRoutes() {
|
|
|
22911
23126
|
return c.json({ log: null });
|
|
22912
23127
|
let log2 = null;
|
|
22913
23128
|
try {
|
|
22914
|
-
log2 = await
|
|
23129
|
+
log2 = await fs49.readFile(row.logPath, "utf-8");
|
|
22915
23130
|
} catch {
|
|
22916
23131
|
}
|
|
22917
23132
|
return c.json({ log: log2 });
|
|
@@ -22960,6 +23175,7 @@ function computeNextRun(j) {
|
|
|
22960
23175
|
return null;
|
|
22961
23176
|
}
|
|
22962
23177
|
}
|
|
23178
|
+
var TIMEOUT_SEC_MIN, TIMEOUT_SEC_MAX;
|
|
22963
23179
|
var init_cron = __esm({
|
|
22964
23180
|
"packages/server/dist/routes/cron.js"() {
|
|
22965
23181
|
"use strict";
|
|
@@ -22967,6 +23183,8 @@ var init_cron = __esm({
|
|
|
22967
23183
|
init_runner();
|
|
22968
23184
|
init_dispatcher();
|
|
22969
23185
|
init_broadcast();
|
|
23186
|
+
TIMEOUT_SEC_MIN = 60;
|
|
23187
|
+
TIMEOUT_SEC_MAX = 21600;
|
|
22970
23188
|
}
|
|
22971
23189
|
});
|
|
22972
23190
|
|
|
@@ -23213,7 +23431,7 @@ var init_ticker = __esm({
|
|
|
23213
23431
|
});
|
|
23214
23432
|
|
|
23215
23433
|
// packages/server/dist/evolution/spawn.js
|
|
23216
|
-
import
|
|
23434
|
+
import path58 from "node:path";
|
|
23217
23435
|
import { spawn as spawn4 } from "node:child_process";
|
|
23218
23436
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
23219
23437
|
var __dirname2, WRAPPER_PATH, realEvoSpawner;
|
|
@@ -23221,8 +23439,8 @@ var init_spawn = __esm({
|
|
|
23221
23439
|
"packages/server/dist/evolution/spawn.js"() {
|
|
23222
23440
|
"use strict";
|
|
23223
23441
|
init_child_env();
|
|
23224
|
-
__dirname2 =
|
|
23225
|
-
WRAPPER_PATH =
|
|
23442
|
+
__dirname2 = path58.dirname(fileURLToPath2(import.meta.url));
|
|
23443
|
+
WRAPPER_PATH = path58.resolve(__dirname2, "evo-wrapper.js");
|
|
23226
23444
|
realEvoSpawner = (mode, id) => {
|
|
23227
23445
|
const child = spawn4(process.execPath, [WRAPPER_PATH, `--mode=${mode}`, `--id=${id}`], {
|
|
23228
23446
|
detached: true,
|
|
@@ -24009,7 +24227,7 @@ var init_handler2 = __esm({
|
|
|
24009
24227
|
// packages/server/dist/routes/telegram.js
|
|
24010
24228
|
import { Hono as Hono15 } from "hono";
|
|
24011
24229
|
import { Bot as Bot2 } from "grammy";
|
|
24012
|
-
import
|
|
24230
|
+
import fs50 from "node:fs";
|
|
24013
24231
|
function createTelegramRoutes(deps) {
|
|
24014
24232
|
const { db, channel } = deps;
|
|
24015
24233
|
const app2 = new Hono15();
|
|
@@ -24019,7 +24237,7 @@ function createTelegramRoutes(deps) {
|
|
|
24019
24237
|
accountId: acc.accountId,
|
|
24020
24238
|
botUsername: acc.botUsername,
|
|
24021
24239
|
workspacePath: acc.workspacePath,
|
|
24022
|
-
workspaceMissing: !
|
|
24240
|
+
workspaceMissing: !fs50.existsSync(acc.workspacePath),
|
|
24023
24241
|
label: acc.label,
|
|
24024
24242
|
enabled: acc.enabled,
|
|
24025
24243
|
accessLevel: acc.accessLevel,
|
|
@@ -24743,8 +24961,8 @@ var init_cdn = __esm({
|
|
|
24743
24961
|
});
|
|
24744
24962
|
|
|
24745
24963
|
// packages/server/dist/channels/wechat/send-media.js
|
|
24746
|
-
import
|
|
24747
|
-
import
|
|
24964
|
+
import fs51 from "node:fs/promises";
|
|
24965
|
+
import path59 from "node:path";
|
|
24748
24966
|
function inferKind(filePath) {
|
|
24749
24967
|
const cls = classifyMedia(filePath);
|
|
24750
24968
|
if (cls === "image")
|
|
@@ -24756,7 +24974,7 @@ function inferKind(filePath) {
|
|
|
24756
24974
|
async function sendMediaFile(params) {
|
|
24757
24975
|
const { baseUrl, token, toUserId, contextToken, filePath } = params;
|
|
24758
24976
|
const kind = params.kind ?? inferKind(filePath);
|
|
24759
|
-
const plaintext = await
|
|
24977
|
+
const plaintext = await fs51.readFile(filePath);
|
|
24760
24978
|
const prep = prepareUpload(plaintext);
|
|
24761
24979
|
console.log(`[wechat] sendMediaFile: path=${filePath} kind=${kind} rawsize=${prep.rawsize} filesize=${prep.filesize}`);
|
|
24762
24980
|
const mediaType = kind === "image" ? UploadMediaType.IMAGE : kind === "video" ? UploadMediaType.VIDEO : UploadMediaType.FILE;
|
|
@@ -24794,7 +25012,7 @@ async function sendMediaFile(params) {
|
|
|
24794
25012
|
aesKeyBase64,
|
|
24795
25013
|
fileSizeCiphertext: prep.filesize,
|
|
24796
25014
|
fileSize: prep.rawsize,
|
|
24797
|
-
fileName:
|
|
25015
|
+
fileName: path59.basename(filePath)
|
|
24798
25016
|
});
|
|
24799
25017
|
const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
24800
25018
|
const body = {
|
|
@@ -24957,8 +25175,8 @@ var init_login = __esm({
|
|
|
24957
25175
|
});
|
|
24958
25176
|
|
|
24959
25177
|
// packages/server/dist/channels/wechat/handler.js
|
|
24960
|
-
import
|
|
24961
|
-
import
|
|
25178
|
+
import fs52 from "node:fs";
|
|
25179
|
+
import path60 from "node:path";
|
|
24962
25180
|
import QRCode from "qrcode";
|
|
24963
25181
|
function wxRoute(fromUserId, contextToken) {
|
|
24964
25182
|
return (prev) => ({
|
|
@@ -25347,7 +25565,7 @@ async function handleSlashCommand(args) {
|
|
|
25347
25565
|
await reply(login.message);
|
|
25348
25566
|
return true;
|
|
25349
25567
|
}
|
|
25350
|
-
const tmpPath =
|
|
25568
|
+
const tmpPath = path60.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
|
|
25351
25569
|
await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
|
|
25352
25570
|
await sendMediaFile({
|
|
25353
25571
|
baseUrl: account.baseUrl,
|
|
@@ -25356,7 +25574,7 @@ async function handleSlashCommand(args) {
|
|
|
25356
25574
|
contextToken,
|
|
25357
25575
|
filePath: tmpPath
|
|
25358
25576
|
});
|
|
25359
|
-
await
|
|
25577
|
+
await fs52.promises.unlink(tmpPath).catch(() => {
|
|
25360
25578
|
});
|
|
25361
25579
|
await reply(t("wechat.qr_sent", lang, { level, path: account.workspacePath }));
|
|
25362
25580
|
waitLogin({ sessionKey: login.sessionKey }).then(async (result2) => {
|
|
@@ -25436,8 +25654,8 @@ var init_handler3 = __esm({
|
|
|
25436
25654
|
|
|
25437
25655
|
// packages/server/dist/routes/wechat.js
|
|
25438
25656
|
import { Hono as Hono16 } from "hono";
|
|
25439
|
-
import
|
|
25440
|
-
import
|
|
25657
|
+
import path61 from "node:path";
|
|
25658
|
+
import fs53 from "node:fs";
|
|
25441
25659
|
function createWechatRoutes(deps) {
|
|
25442
25660
|
const { db, channel } = deps;
|
|
25443
25661
|
const app2 = new Hono16();
|
|
@@ -25446,7 +25664,7 @@ function createWechatRoutes(deps) {
|
|
|
25446
25664
|
return {
|
|
25447
25665
|
...stripSecrets(acc),
|
|
25448
25666
|
workspacePath: acc.workspacePath,
|
|
25449
|
-
workspaceMissing: !
|
|
25667
|
+
workspaceMissing: !fs53.existsSync(acc.workspacePath)
|
|
25450
25668
|
};
|
|
25451
25669
|
});
|
|
25452
25670
|
return c.json({ accounts });
|
|
@@ -25462,7 +25680,7 @@ function createWechatRoutes(deps) {
|
|
|
25462
25680
|
return c.json({ error: "sessionKey required" }, 400);
|
|
25463
25681
|
if (!body.workspacePath)
|
|
25464
25682
|
return c.json({ error: "workspacePath required" }, 400);
|
|
25465
|
-
if (!
|
|
25683
|
+
if (!path61.isAbsolute(body.workspacePath))
|
|
25466
25684
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
25467
25685
|
const levelError = accessLevelError(body.accessLevel, CHAT_ACCESS_LEVELS);
|
|
25468
25686
|
if (levelError)
|
|
@@ -25666,14 +25884,14 @@ function createWebChannel(deps) {
|
|
|
25666
25884
|
return findActiveSessionId(sm, accountId, prefix, activeOverrides2, "full") ?? void 0;
|
|
25667
25885
|
}
|
|
25668
25886
|
function resolveWorkspace(account, override) {
|
|
25669
|
-
let
|
|
25887
|
+
let path66 = account.workspacePath;
|
|
25670
25888
|
if (override && override !== account.workspacePath) {
|
|
25671
25889
|
if (account.accessLevel !== "full") {
|
|
25672
25890
|
return { ok: false, error: "workspace override requires a full-access token" };
|
|
25673
25891
|
}
|
|
25674
|
-
|
|
25892
|
+
path66 = override;
|
|
25675
25893
|
}
|
|
25676
|
-
const resolved = resolveAccountWorkspace({ ...account, workspacePath:
|
|
25894
|
+
const resolved = resolveAccountWorkspace({ ...account, workspacePath: path66 });
|
|
25677
25895
|
if (!resolved)
|
|
25678
25896
|
return { ok: false, error: "workspace not found" };
|
|
25679
25897
|
return { ok: true, path: resolved };
|
|
@@ -26056,7 +26274,7 @@ var init_handler4 = __esm({
|
|
|
26056
26274
|
import { Hono as Hono17 } from "hono";
|
|
26057
26275
|
import { streamSSE } from "hono/streaming";
|
|
26058
26276
|
import crypto4 from "node:crypto";
|
|
26059
|
-
import
|
|
26277
|
+
import fs54 from "node:fs";
|
|
26060
26278
|
function createWebRoutes(deps) {
|
|
26061
26279
|
const { db, channel } = deps;
|
|
26062
26280
|
const app2 = new Hono17();
|
|
@@ -26066,7 +26284,7 @@ function createWebRoutes(deps) {
|
|
|
26066
26284
|
accountId: acc.accountId,
|
|
26067
26285
|
token: acc.token,
|
|
26068
26286
|
workspacePath: acc.workspacePath,
|
|
26069
|
-
workspaceMissing: !
|
|
26287
|
+
workspaceMissing: !fs54.existsSync(acc.workspacePath),
|
|
26070
26288
|
label: acc.label,
|
|
26071
26289
|
enabled: acc.enabled,
|
|
26072
26290
|
accessLevel: acc.accessLevel,
|
|
@@ -26212,27 +26430,27 @@ function createWebRoutes(deps) {
|
|
|
26212
26430
|
const filePath = c.req.query("path");
|
|
26213
26431
|
if (!filePath)
|
|
26214
26432
|
return c.json({ error: "path required" }, 400);
|
|
26215
|
-
const
|
|
26216
|
-
const resolved =
|
|
26217
|
-
const root =
|
|
26218
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
26433
|
+
const path66 = await import("node:path");
|
|
26434
|
+
const resolved = path66.default.resolve(account.workspacePath, filePath);
|
|
26435
|
+
const root = path66.default.resolve(account.workspacePath);
|
|
26436
|
+
if (resolved !== root && !resolved.startsWith(root + path66.default.sep)) {
|
|
26219
26437
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
26220
26438
|
}
|
|
26221
26439
|
let real;
|
|
26222
26440
|
let realRoot;
|
|
26223
26441
|
try {
|
|
26224
|
-
real =
|
|
26225
|
-
realRoot =
|
|
26442
|
+
real = fs54.realpathSync(resolved);
|
|
26443
|
+
realRoot = fs54.realpathSync(root);
|
|
26226
26444
|
} catch {
|
|
26227
26445
|
return c.json({ error: "file not found" }, 404);
|
|
26228
26446
|
}
|
|
26229
|
-
if (real !== realRoot && !real.startsWith(realRoot +
|
|
26447
|
+
if (real !== realRoot && !real.startsWith(realRoot + path66.default.sep)) {
|
|
26230
26448
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
26231
26449
|
}
|
|
26232
|
-
const stat =
|
|
26450
|
+
const stat = fs54.statSync(real);
|
|
26233
26451
|
if (stat.isDirectory())
|
|
26234
26452
|
return c.json({ error: "cannot serve directory" }, 400);
|
|
26235
|
-
const ext =
|
|
26453
|
+
const ext = path66.default.extname(real).toLowerCase();
|
|
26236
26454
|
const nonImageMimes = {
|
|
26237
26455
|
".mp4": "video/mp4",
|
|
26238
26456
|
".webm": "video/webm",
|
|
@@ -26240,8 +26458,8 @@ function createWebRoutes(deps) {
|
|
|
26240
26458
|
".txt": "text/plain"
|
|
26241
26459
|
};
|
|
26242
26460
|
const contentType = imageMimeFromExt(ext) || nonImageMimes[ext] || "application/octet-stream";
|
|
26243
|
-
const fileName =
|
|
26244
|
-
const data =
|
|
26461
|
+
const fileName = path66.default.basename(resolved);
|
|
26462
|
+
const data = fs54.readFileSync(real);
|
|
26245
26463
|
c.header("content-type", contentType);
|
|
26246
26464
|
c.header("content-disposition", `inline; filename="${fileName}"`);
|
|
26247
26465
|
return c.body(data);
|
|
@@ -26582,10 +26800,10 @@ async function authTest(botToken) {
|
|
|
26582
26800
|
return slackPost("auth.test", botToken, {});
|
|
26583
26801
|
}
|
|
26584
26802
|
async function uploadFile(args) {
|
|
26585
|
-
const
|
|
26586
|
-
const
|
|
26587
|
-
const filename = args.filename ??
|
|
26588
|
-
const stat =
|
|
26803
|
+
const fs59 = await import("node:fs");
|
|
26804
|
+
const path66 = await import("node:path");
|
|
26805
|
+
const filename = args.filename ?? path66.basename(args.filePath);
|
|
26806
|
+
const stat = fs59.statSync(args.filePath);
|
|
26589
26807
|
if (!stat.isFile())
|
|
26590
26808
|
throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
|
|
26591
26809
|
const length = stat.size;
|
|
@@ -26603,7 +26821,7 @@ async function uploadFile(args) {
|
|
|
26603
26821
|
if (step1Parsed.ok === false || !step1Parsed.upload_url || !step1Parsed.file_id) {
|
|
26604
26822
|
throw new Error(`[slack:files.getUploadURLExternal] ${step1Parsed.error ?? "unknown"}`);
|
|
26605
26823
|
}
|
|
26606
|
-
const buf =
|
|
26824
|
+
const buf = fs59.readFileSync(args.filePath);
|
|
26607
26825
|
const step2 = await fetch(step1Parsed.upload_url, {
|
|
26608
26826
|
method: "POST",
|
|
26609
26827
|
body: buf
|
|
@@ -26748,7 +26966,7 @@ var init_api2 = __esm({
|
|
|
26748
26966
|
});
|
|
26749
26967
|
|
|
26750
26968
|
// packages/server/dist/channels/slack/handler.js
|
|
26751
|
-
import
|
|
26969
|
+
import path62 from "node:path";
|
|
26752
26970
|
import { WebSocket } from "ws";
|
|
26753
26971
|
function slashToBang(text5, commandNames) {
|
|
26754
26972
|
if (commandNames.length === 0)
|
|
@@ -26853,7 +27071,7 @@ function startSlackChannel(deps) {
|
|
|
26853
27071
|
const route = bridge.getRoute(sessionId);
|
|
26854
27072
|
if (!account || !route)
|
|
26855
27073
|
return;
|
|
26856
|
-
const resolved =
|
|
27074
|
+
const resolved = path62.resolve(filePath);
|
|
26857
27075
|
if (!isMediaPathAllowed(resolved, account.workspacePath)) {
|
|
26858
27076
|
console.log(`[slack] sendMedia blocked: ${filePath} not under workspace`);
|
|
26859
27077
|
return;
|
|
@@ -26872,7 +27090,7 @@ function startSlackChannel(deps) {
|
|
|
26872
27090
|
channel: route.channelId,
|
|
26873
27091
|
threadTs: route.replyTs,
|
|
26874
27092
|
text: t("handler.upload_failed", getLang(account), {
|
|
26875
|
-
name:
|
|
27093
|
+
name: path62.basename(filePath),
|
|
26876
27094
|
error: err instanceof Error ? err.message : String(err)
|
|
26877
27095
|
})
|
|
26878
27096
|
}).catch(() => {
|
|
@@ -27129,7 +27347,7 @@ var init_handler5 = __esm({
|
|
|
27129
27347
|
|
|
27130
27348
|
// packages/server/dist/routes/slack.js
|
|
27131
27349
|
import { Hono as Hono18 } from "hono";
|
|
27132
|
-
import
|
|
27350
|
+
import fs55 from "node:fs";
|
|
27133
27351
|
function createSlackRoutes(deps) {
|
|
27134
27352
|
const { db, channel } = deps;
|
|
27135
27353
|
const app2 = new Hono18();
|
|
@@ -27152,7 +27370,7 @@ function createSlackRoutes(deps) {
|
|
|
27152
27370
|
botUserId: a.botUserId,
|
|
27153
27371
|
teamId: a.teamId,
|
|
27154
27372
|
workspacePath: a.workspacePath,
|
|
27155
|
-
workspaceMissing: !
|
|
27373
|
+
workspaceMissing: !fs55.existsSync(a.workspacePath),
|
|
27156
27374
|
label: a.label,
|
|
27157
27375
|
enabled: a.enabled,
|
|
27158
27376
|
accessLevel: a.accessLevel,
|
|
@@ -27684,10 +27902,10 @@ async function replyMessage(args) {
|
|
|
27684
27902
|
return { message_id: parsed.data?.message_id ?? "" };
|
|
27685
27903
|
}
|
|
27686
27904
|
async function uploadImage(args) {
|
|
27687
|
-
const
|
|
27688
|
-
const
|
|
27689
|
-
const buf =
|
|
27690
|
-
const filename =
|
|
27905
|
+
const fs59 = await import("node:fs");
|
|
27906
|
+
const path66 = await import("node:path");
|
|
27907
|
+
const buf = fs59.readFileSync(args.filePath);
|
|
27908
|
+
const filename = path66.basename(args.filePath);
|
|
27691
27909
|
const send = async (token2) => {
|
|
27692
27910
|
const form = new FormData();
|
|
27693
27911
|
form.append("image_type", "message");
|
|
@@ -27718,10 +27936,10 @@ async function uploadImage(args) {
|
|
|
27718
27936
|
return { imageKey: parsed.data.image_key };
|
|
27719
27937
|
}
|
|
27720
27938
|
async function uploadFile2(args) {
|
|
27721
|
-
const
|
|
27722
|
-
const
|
|
27723
|
-
const buf =
|
|
27724
|
-
const filename = args.filename ??
|
|
27939
|
+
const fs59 = await import("node:fs");
|
|
27940
|
+
const path66 = await import("node:path");
|
|
27941
|
+
const buf = fs59.readFileSync(args.filePath);
|
|
27942
|
+
const filename = args.filename ?? path66.basename(args.filePath);
|
|
27725
27943
|
const send = async (token2) => {
|
|
27726
27944
|
const form = new FormData();
|
|
27727
27945
|
form.append("file_type", args.fileType);
|
|
@@ -27827,7 +28045,7 @@ var init_api3 = __esm({
|
|
|
27827
28045
|
});
|
|
27828
28046
|
|
|
27829
28047
|
// packages/server/dist/channels/feishu/handler.js
|
|
27830
|
-
import
|
|
28048
|
+
import path63 from "node:path";
|
|
27831
28049
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
27832
28050
|
function buildSessionPrefixForThread2(chatId, rootId) {
|
|
27833
28051
|
return sessionPrefix("feishu", `${chatId}:${rootId}`);
|
|
@@ -27950,7 +28168,7 @@ function startFeishuChannel(deps) {
|
|
|
27950
28168
|
const route = bridge.getRoute(sessionId);
|
|
27951
28169
|
if (!account || !route)
|
|
27952
28170
|
return;
|
|
27953
|
-
const resolved =
|
|
28171
|
+
const resolved = path63.resolve(filePath);
|
|
27954
28172
|
if (!isMediaPathAllowed(resolved, account.workspacePath)) {
|
|
27955
28173
|
console.log(`[feishu] sendMedia blocked: ${filePath} not under workspace`);
|
|
27956
28174
|
return;
|
|
@@ -27963,7 +28181,7 @@ function startFeishuChannel(deps) {
|
|
|
27963
28181
|
account,
|
|
27964
28182
|
...route,
|
|
27965
28183
|
text: t("handler.upload_failed", getLang(account), {
|
|
27966
|
-
name:
|
|
28184
|
+
name: path63.basename(filePath),
|
|
27967
28185
|
error: err instanceof Error ? err.message : String(err)
|
|
27968
28186
|
})
|
|
27969
28187
|
}).catch(() => {
|
|
@@ -28127,7 +28345,7 @@ async function replyToInbound(args) {
|
|
|
28127
28345
|
async function sendFeishuMedia(args) {
|
|
28128
28346
|
const { account, inboundMessageId, isP2P, chatId, filePath } = args;
|
|
28129
28347
|
const cls = classifyMedia(filePath);
|
|
28130
|
-
const ext =
|
|
28348
|
+
const ext = path63.extname(filePath).toLowerCase();
|
|
28131
28349
|
if (cls === "image") {
|
|
28132
28350
|
const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
|
|
28133
28351
|
await replyToInboundRaw({
|
|
@@ -28253,7 +28471,7 @@ var init_handler6 = __esm({
|
|
|
28253
28471
|
|
|
28254
28472
|
// packages/server/dist/routes/feishu.js
|
|
28255
28473
|
import { Hono as Hono19 } from "hono";
|
|
28256
|
-
import
|
|
28474
|
+
import fs56 from "node:fs";
|
|
28257
28475
|
function createFeishuRoutes(deps) {
|
|
28258
28476
|
const { db, channel } = deps;
|
|
28259
28477
|
const app2 = new Hono19();
|
|
@@ -28277,7 +28495,7 @@ function createFeishuRoutes(deps) {
|
|
|
28277
28495
|
botOpenId: a.botOpenId,
|
|
28278
28496
|
hasEncryptKey: !!a.encryptKey,
|
|
28279
28497
|
workspacePath: a.workspacePath,
|
|
28280
|
-
workspaceMissing: !
|
|
28498
|
+
workspaceMissing: !fs56.existsSync(a.workspacePath),
|
|
28281
28499
|
label: a.label,
|
|
28282
28500
|
enabled: a.enabled,
|
|
28283
28501
|
accessLevel: a.accessLevel,
|
|
@@ -28665,14 +28883,14 @@ function passwordStrengthError(pw) {
|
|
|
28665
28883
|
}
|
|
28666
28884
|
function authMiddleware() {
|
|
28667
28885
|
return async (c, next) => {
|
|
28668
|
-
const
|
|
28669
|
-
if (PUBLIC_PATHS.some((p) =>
|
|
28886
|
+
const path66 = c.req.path;
|
|
28887
|
+
if (PUBLIC_PATHS.some((p) => path66 === p || path66.startsWith(p + "?"))) {
|
|
28670
28888
|
return next();
|
|
28671
28889
|
}
|
|
28672
28890
|
const token = getCookie(c, COOKIE_NAME);
|
|
28673
28891
|
const payload = validateToken(token);
|
|
28674
28892
|
if (!payload) {
|
|
28675
|
-
if (!
|
|
28893
|
+
if (!path66.startsWith("/api/")) {
|
|
28676
28894
|
return next();
|
|
28677
28895
|
}
|
|
28678
28896
|
return c.json({ error: "Unauthorized" }, 401);
|
|
@@ -28711,17 +28929,17 @@ import { compress } from "hono/compress";
|
|
|
28711
28929
|
import { serve } from "@hono/node-server";
|
|
28712
28930
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
28713
28931
|
import { WebSocketServer } from "ws";
|
|
28714
|
-
import
|
|
28715
|
-
import
|
|
28716
|
-
import { spawnSync as
|
|
28932
|
+
import path64 from "node:path";
|
|
28933
|
+
import fs57 from "node:fs";
|
|
28934
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
28717
28935
|
import { homedir as homedir24 } from "node:os";
|
|
28718
28936
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
28719
28937
|
function findProjectRoot() {
|
|
28720
28938
|
let dir = process.cwd();
|
|
28721
28939
|
for (let i = 0; i < 32; i++) {
|
|
28722
|
-
if (
|
|
28940
|
+
if (fs57.existsSync(path64.join(dir, "pnpm-workspace.yaml")))
|
|
28723
28941
|
return dir;
|
|
28724
|
-
const parent =
|
|
28942
|
+
const parent = path64.dirname(dir);
|
|
28725
28943
|
if (parent === dir)
|
|
28726
28944
|
break;
|
|
28727
28945
|
dir = parent;
|
|
@@ -28730,15 +28948,15 @@ function findProjectRoot() {
|
|
|
28730
28948
|
return process.cwd();
|
|
28731
28949
|
}
|
|
28732
28950
|
function acquireSingleInstanceLock(lockFile) {
|
|
28733
|
-
|
|
28734
|
-
const fd =
|
|
28735
|
-
const result =
|
|
28951
|
+
fs57.mkdirSync(path64.dirname(lockFile), { recursive: true });
|
|
28952
|
+
const fd = fs57.openSync(lockFile, fs57.constants.O_RDWR | fs57.constants.O_CREAT, 420);
|
|
28953
|
+
const result = spawnSync4("flock", ["-n", "3"], {
|
|
28736
28954
|
stdio: ["ignore", "ignore", "ignore", fd]
|
|
28737
28955
|
});
|
|
28738
28956
|
if (result.error) {
|
|
28739
28957
|
const content = readLockPid(fd);
|
|
28740
28958
|
if (content > 0 && isProcessAlive(content)) {
|
|
28741
|
-
|
|
28959
|
+
fs57.closeSync(fd);
|
|
28742
28960
|
console.error(`[Server] another halo server is already running (pid ${content}). kill it first.`);
|
|
28743
28961
|
process.exit(1);
|
|
28744
28962
|
}
|
|
@@ -28747,19 +28965,19 @@ function acquireSingleInstanceLock(lockFile) {
|
|
|
28747
28965
|
if (content > 0 && !isProcessAlive(content)) {
|
|
28748
28966
|
console.warn(`[Server] removing stale server.lock (pid ${content} not running)`);
|
|
28749
28967
|
} else {
|
|
28750
|
-
|
|
28968
|
+
fs57.closeSync(fd);
|
|
28751
28969
|
console.error(`[Server] another halo server is already running${content > 0 ? ` (pid ${content})` : ""}. kill it first.`);
|
|
28752
28970
|
process.exit(1);
|
|
28753
28971
|
}
|
|
28754
28972
|
}
|
|
28755
|
-
|
|
28756
|
-
|
|
28757
|
-
|
|
28973
|
+
fs57.ftruncateSync(fd);
|
|
28974
|
+
fs57.writeSync(fd, String(process.pid));
|
|
28975
|
+
fs57.fsyncSync(fd);
|
|
28758
28976
|
}
|
|
28759
28977
|
function readLockPid(fd) {
|
|
28760
28978
|
try {
|
|
28761
28979
|
const buf = Buffer.alloc(32);
|
|
28762
|
-
const n =
|
|
28980
|
+
const n = fs57.readSync(fd, buf, 0, 32, 0);
|
|
28763
28981
|
return parseInt(buf.subarray(0, n).toString("utf-8").trim(), 10) || 0;
|
|
28764
28982
|
} catch {
|
|
28765
28983
|
return 0;
|
|
@@ -28768,7 +28986,7 @@ function readLockPid(fd) {
|
|
|
28768
28986
|
function isProcessAlive(pid) {
|
|
28769
28987
|
if (process.platform === "win32") {
|
|
28770
28988
|
try {
|
|
28771
|
-
const out =
|
|
28989
|
+
const out = spawnSync4("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
28772
28990
|
encoding: "utf-8",
|
|
28773
28991
|
windowsHide: true
|
|
28774
28992
|
});
|
|
@@ -28787,16 +29005,16 @@ function isProcessAlive(pid) {
|
|
|
28787
29005
|
}
|
|
28788
29006
|
}
|
|
28789
29007
|
function readGitSha() {
|
|
28790
|
-
if (!
|
|
29008
|
+
if (!fs57.existsSync(path64.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs57.existsSync(path64.join(PROJECT_ROOT, ".git")))
|
|
28791
29009
|
return null;
|
|
28792
29010
|
try {
|
|
28793
|
-
const rev =
|
|
29011
|
+
const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
|
|
28794
29012
|
if (rev.status !== 0)
|
|
28795
29013
|
return null;
|
|
28796
29014
|
const sha = rev.stdout.trim();
|
|
28797
29015
|
if (!sha)
|
|
28798
29016
|
return null;
|
|
28799
|
-
const dirty =
|
|
29017
|
+
const dirty = spawnSync4("git", ["status", "--porcelain"], { cwd: PROJECT_ROOT, encoding: "utf-8" }).stdout.trim().length > 0;
|
|
28800
29018
|
return dirty ? `${sha}-dirty` : sha;
|
|
28801
29019
|
} catch {
|
|
28802
29020
|
return null;
|
|
@@ -28804,14 +29022,14 @@ function readGitSha() {
|
|
|
28804
29022
|
}
|
|
28805
29023
|
function resolveFrontendDir() {
|
|
28806
29024
|
if (process.env.HALO_FRONTEND_DIR)
|
|
28807
|
-
return
|
|
28808
|
-
const bundled =
|
|
28809
|
-
if (
|
|
29025
|
+
return path64.resolve(process.env.HALO_FRONTEND_DIR);
|
|
29026
|
+
const bundled = path64.resolve(__dirname3, "..", "admin-out");
|
|
29027
|
+
if (fs57.existsSync(path64.join(bundled, "index.html")))
|
|
28810
29028
|
return bundled;
|
|
28811
|
-
const sibling =
|
|
28812
|
-
if (
|
|
29029
|
+
const sibling = path64.resolve(__dirname3, "..", "..", "admin", "out");
|
|
29030
|
+
if (fs57.existsSync(path64.join(sibling, "index.html")))
|
|
28813
29031
|
return sibling;
|
|
28814
|
-
return
|
|
29032
|
+
return path64.resolve(PROJECT_ROOT, "packages", "admin", "out");
|
|
28815
29033
|
}
|
|
28816
29034
|
async function gracefulShutdown(signal) {
|
|
28817
29035
|
console.log(`[Server] Received ${signal}, shutting down gracefully...`);
|
|
@@ -28867,13 +29085,13 @@ var init_dist2 = __esm({
|
|
|
28867
29085
|
init_sandbox();
|
|
28868
29086
|
init_init();
|
|
28869
29087
|
init_git_ssh();
|
|
28870
|
-
__dirname3 =
|
|
29088
|
+
__dirname3 = path64.dirname(fileURLToPath3(import.meta.url));
|
|
28871
29089
|
PORT = config.server.port;
|
|
28872
|
-
HALO_VERSION = "1.1.
|
|
29090
|
+
HALO_VERSION = "1.1.3";
|
|
28873
29091
|
PROJECT_ROOT = findProjectRoot();
|
|
28874
|
-
HALO_HOME4 =
|
|
29092
|
+
HALO_HOME4 = path64.join(homedir24(), ".halo");
|
|
28875
29093
|
GIT_SHA = readGitSha();
|
|
28876
|
-
if (!
|
|
29094
|
+
if (!fs57.existsSync(path64.join(HALO_HOME4, "global", ".template-version"))) {
|
|
28877
29095
|
process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
|
|
28878
29096
|
process.exit(1);
|
|
28879
29097
|
}
|
|
@@ -28894,7 +29112,7 @@ var init_dist2 = __esm({
|
|
|
28894
29112
|
process.exit(1);
|
|
28895
29113
|
}
|
|
28896
29114
|
if (!AGENTCORE)
|
|
28897
|
-
acquireSingleInstanceLock(
|
|
29115
|
+
acquireSingleInstanceLock(path64.join(HALO_HOME4, "global", "server.lock"));
|
|
28898
29116
|
{
|
|
28899
29117
|
const declared = new Set(commandRegistry.listDescriptors().filter((d) => d.source === "builtin").map((d) => d.slashName));
|
|
28900
29118
|
const dispatched = new Set(DISPATCH_COMMANDS);
|
|
@@ -28963,10 +29181,10 @@ var init_dist2 = __esm({
|
|
|
28963
29181
|
app.route("/api", evolutionRoutes);
|
|
28964
29182
|
cronRoutes = createCronRoutes();
|
|
28965
29183
|
app.route("/api", cronRoutes);
|
|
28966
|
-
channelDb = createChannelDb(
|
|
29184
|
+
channelDb = createChannelDb(path64.join(HALO_HOME4, "secrets"));
|
|
28967
29185
|
setChannelDb(channelDb);
|
|
28968
|
-
setEvoDb(createEvoDb(
|
|
28969
|
-
setCronDb(createCronDb(
|
|
29186
|
+
setEvoDb(createEvoDb(path64.join(HALO_HOME4, "global")));
|
|
29187
|
+
setCronDb(createCronDb(path64.join(HALO_HOME4, "global")));
|
|
28970
29188
|
setEvoSpawner(realEvoSpawner);
|
|
28971
29189
|
if (!AGENTCORE) {
|
|
28972
29190
|
startEvoTicker();
|
|
@@ -28992,11 +29210,11 @@ var init_dist2 = __esm({
|
|
|
28992
29210
|
console.log(`[Server] AgentCore runtime mode \u2014 workspace: ${config.server.agentcoreWorkspace}`);
|
|
28993
29211
|
}
|
|
28994
29212
|
FRONTEND_DIR = resolveFrontendDir();
|
|
28995
|
-
app.use("/*", serveStatic({ root:
|
|
29213
|
+
app.use("/*", serveStatic({ root: path64.relative(process.cwd(), FRONTEND_DIR) }));
|
|
28996
29214
|
app.get("/*", (c) => {
|
|
28997
|
-
const indexPath =
|
|
29215
|
+
const indexPath = path64.join(FRONTEND_DIR, "index.html");
|
|
28998
29216
|
try {
|
|
28999
|
-
const html =
|
|
29217
|
+
const html = fs57.readFileSync(indexPath, "utf-8");
|
|
29000
29218
|
return c.html(html);
|
|
29001
29219
|
} catch {
|
|
29002
29220
|
return c.text("Frontend not built. Run: cd packages/admin && npx next build", 503);
|
|
@@ -29767,11 +29985,11 @@ process.emitWarning = (warning, ...args) => {
|
|
|
29767
29985
|
|
|
29768
29986
|
// packages/cli/src/index.ts
|
|
29769
29987
|
init_server_supervisor();
|
|
29770
|
-
import
|
|
29771
|
-
import
|
|
29988
|
+
import path65 from "node:path";
|
|
29989
|
+
import fs58 from "node:fs";
|
|
29772
29990
|
import { homedir as homedir25 } from "node:os";
|
|
29773
29991
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
29774
|
-
var VERSION = "1.1.
|
|
29992
|
+
var VERSION = "1.1.3";
|
|
29775
29993
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
29776
29994
|
|
|
29777
29995
|
Commands:
|
|
@@ -30028,8 +30246,8 @@ async function cmdSetup(argv = []) {
|
|
|
30028
30246
|
const { buildBindProviderPlan: buildBindProviderPlan2, KEEP_CURRENT: KEEP_CURRENT2, AWS_CHAIN_PROVIDER: AWS_CHAIN_PROVIDER2 } = await Promise.resolve().then(() => (init_setup_bind_provider(), setup_bind_provider_exports));
|
|
30029
30247
|
const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
|
|
30030
30248
|
const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
|
|
30031
|
-
const haloHome2 =
|
|
30032
|
-
const globalDir2 =
|
|
30249
|
+
const haloHome2 = path65.join(homedir25(), ".halo");
|
|
30250
|
+
const globalDir2 = path65.join(haloHome2, "global");
|
|
30033
30251
|
ensureHaloHome2(haloHome2);
|
|
30034
30252
|
process.stderr.write(`[setup] ${haloHome2}/ ready
|
|
30035
30253
|
`);
|
|
@@ -30332,10 +30550,10 @@ async function cmdUpgrade() {
|
|
|
30332
30550
|
);
|
|
30333
30551
|
process.exit(1);
|
|
30334
30552
|
}
|
|
30335
|
-
const { spawnSync:
|
|
30553
|
+
const { spawnSync: spawnSync5 } = await import("node:child_process");
|
|
30336
30554
|
process.stderr.write(`Checking npm for the latest ${PKG}\u2026
|
|
30337
30555
|
`);
|
|
30338
|
-
const view =
|
|
30556
|
+
const view = spawnSync5("npm", ["view", PKG, "version"], { encoding: "utf-8" });
|
|
30339
30557
|
if (view.error || view.status !== 0) {
|
|
30340
30558
|
const err = view.error?.message ?? view.stderr?.trim() ?? `exit ${view.status}`;
|
|
30341
30559
|
process.stderr.write(`halo: \`npm view\` failed \u2014 ${err}
|
|
@@ -30353,10 +30571,40 @@ async function cmdUpgrade() {
|
|
|
30353
30571
|
`);
|
|
30354
30572
|
return;
|
|
30355
30573
|
}
|
|
30574
|
+
const { ALLOW_SCRIPTS: ALLOW_SCRIPTS2, verifyNativeModules: verifyNativeModules2 } = await Promise.resolve().then(() => (init_upgrade_verify(), upgrade_verify_exports));
|
|
30356
30575
|
process.stderr.write(`Upgrading ${installed} \u2192 ${latest}\u2026
|
|
30357
30576
|
`);
|
|
30358
|
-
const install =
|
|
30577
|
+
const install = spawnSync5(
|
|
30578
|
+
"npm",
|
|
30579
|
+
["install", "-g", `--allow-scripts=${ALLOW_SCRIPTS2}`, `${PKG}@latest`],
|
|
30580
|
+
{ stdio: "inherit" }
|
|
30581
|
+
);
|
|
30359
30582
|
if (install.status === 0) {
|
|
30583
|
+
const npmRoot = spawnSync5("npm", ["root", "-g"], {
|
|
30584
|
+
encoding: "utf-8",
|
|
30585
|
+
shell: process.platform === "win32"
|
|
30586
|
+
});
|
|
30587
|
+
const rootDir = npmRoot.status === 0 ? npmRoot.stdout.trim() : "";
|
|
30588
|
+
if (!rootDir) {
|
|
30589
|
+
process.stderr.write(`halo: \`npm root -g\` failed \u2014 cannot verify the new install
|
|
30590
|
+
`);
|
|
30591
|
+
process.exit(1);
|
|
30592
|
+
}
|
|
30593
|
+
const broken = verifyNativeModules2(path65.join(rootDir, PKG));
|
|
30594
|
+
if (broken.length > 0) {
|
|
30595
|
+
process.stderr.write(
|
|
30596
|
+
`
|
|
30597
|
+
halo: upgrade installed, but native module(s) failed to load: ${broken.join(", ")}
|
|
30598
|
+
This usually means npm blocked the module's install scripts (npm 12+ allowScripts policy \u2014 check \`npm -v\`).
|
|
30599
|
+
|
|
30600
|
+
Fix \u2014 reinstall with the scripts allowed:
|
|
30601
|
+
sudo npm install -g --allow-scripts=${ALLOW_SCRIPTS2} ${PKG}@latest
|
|
30602
|
+
Or persist the allowance first, then reinstall:
|
|
30603
|
+
npm config set allow-scripts=${ALLOW_SCRIPTS2} --location=user
|
|
30604
|
+
`
|
|
30605
|
+
);
|
|
30606
|
+
process.exit(1);
|
|
30607
|
+
}
|
|
30360
30608
|
process.stderr.write(
|
|
30361
30609
|
`
|
|
30362
30610
|
Upgraded to ${latest}. Run \`halo server restart\` (or your service manager's restart) to pick up the new templates.
|
|
@@ -30373,7 +30621,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
|
|
|
30373
30621
|
async function cmdAgents(flags) {
|
|
30374
30622
|
const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
30375
30623
|
await initRuntime2();
|
|
30376
|
-
const workspace =
|
|
30624
|
+
const workspace = path65.resolve(flags.workspace ?? process.cwd());
|
|
30377
30625
|
const agents = await listAgents2(workspace);
|
|
30378
30626
|
if (agents.length === 0) {
|
|
30379
30627
|
process.stderr.write("No agents found.\n");
|
|
@@ -30389,7 +30637,7 @@ async function cmdAgents(flags) {
|
|
|
30389
30637
|
async function cmdSessions(flags) {
|
|
30390
30638
|
const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
30391
30639
|
await initRuntime2();
|
|
30392
|
-
const workspace =
|
|
30640
|
+
const workspace = path65.resolve(flags.workspace ?? process.cwd());
|
|
30393
30641
|
const sessions = listSessions2(workspace);
|
|
30394
30642
|
if (sessions.length === 0) {
|
|
30395
30643
|
process.stderr.write("No sessions found.\n");
|
|
@@ -30502,11 +30750,11 @@ ${stdinContent}` : stdinContent;
|
|
|
30502
30750
|
harness.destroy();
|
|
30503
30751
|
}
|
|
30504
30752
|
}
|
|
30505
|
-
var SERVER_LOCK_PATH =
|
|
30506
|
-
var SERVER_LOG_PATH =
|
|
30753
|
+
var SERVER_LOCK_PATH = path65.join(homedir25(), ".halo", "global", "server.lock");
|
|
30754
|
+
var SERVER_LOG_PATH = path65.join(homedir25(), ".halo", "logs", "server.log");
|
|
30507
30755
|
function readServerPid() {
|
|
30508
30756
|
try {
|
|
30509
|
-
const txt =
|
|
30757
|
+
const txt = fs58.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
|
|
30510
30758
|
const n = parseInt(txt, 10);
|
|
30511
30759
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
30512
30760
|
} catch {
|
|
@@ -30593,8 +30841,8 @@ async function cmdServerStart(argv, _unused) {
|
|
|
30593
30841
|
return;
|
|
30594
30842
|
}
|
|
30595
30843
|
const { spawn: spawn5 } = await import("node:child_process");
|
|
30596
|
-
|
|
30597
|
-
const out =
|
|
30844
|
+
fs58.mkdirSync(path65.dirname(SERVER_LOG_PATH), { recursive: true });
|
|
30845
|
+
const out = fs58.openSync(SERVER_LOG_PATH, "a");
|
|
30598
30846
|
const child = spawn5(process.argv[0], [process.argv[1], "server", "start"], {
|
|
30599
30847
|
detached: true,
|
|
30600
30848
|
stdio: ["ignore", out, out],
|
|
@@ -30695,7 +30943,7 @@ async function cmdServerLogs(argv) {
|
|
|
30695
30943
|
lines: { type: "string", short: "n" }
|
|
30696
30944
|
}
|
|
30697
30945
|
});
|
|
30698
|
-
if (!
|
|
30946
|
+
if (!fs58.existsSync(SERVER_LOG_PATH)) {
|
|
30699
30947
|
process.stderr.write(`No log file at ${SERVER_LOG_PATH}
|
|
30700
30948
|
`);
|
|
30701
30949
|
return;
|
|
@@ -30713,7 +30961,7 @@ function exitMissingSetup() {
|
|
|
30713
30961
|
process.exit(1);
|
|
30714
30962
|
}
|
|
30715
30963
|
function isHaloHomeReady() {
|
|
30716
|
-
return
|
|
30964
|
+
return fs58.existsSync(path65.join(homedir25(), ".halo", "global", ".template-version"));
|
|
30717
30965
|
}
|
|
30718
30966
|
async function main2() {
|
|
30719
30967
|
const argv = process.argv.slice(2);
|