@turmind/halo 1.1.0 → 1.1.2
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-8ac9dfe9c24e512c.js +1 -0
- package/admin-out/_next/static/chunks/438-866a1a20e9efa78a.js +1 -0
- package/admin-out/_next/static/chunks/635-d7fd95bff8674116.js +1 -0
- package/admin-out/_next/static/chunks/app/page-2076ec04359bb395.js +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +4 -4
- package/bundled-docs/design/session.md +12 -0
- package/bundled-docs/dev/api.md +2 -0
- package/bundled-docs/requirements/chat.md +7 -5
- package/bundled-docs/requirements/session.md +1 -0
- package/dist/index.js +651 -475
- package/package.json +1 -1
- package/admin-out/_next/static/chunks/212-394c4fd40216e633.js +0 -1
- package/admin-out/_next/static/chunks/4-e7ba0b1ced30190b.js +0 -1
- package/admin-out/_next/static/chunks/635-3dd0043ed0520b9d.js +0 -1
- package/admin-out/_next/static/chunks/app/page-86cb00582129ae76.js +0 -1
- /package/admin-out/_next/static/{lOrGs0dR-jmXz9PgabRBc → bZcoAqHBQyOvzwS5c84EJ}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{lOrGs0dR-jmXz9PgabRBc → bZcoAqHBQyOvzwS5c84EJ}/_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);
|
|
@@ -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
|
}
|
|
@@ -5062,7 +5094,7 @@ var init_ui_log_builder = __esm({
|
|
|
5062
5094
|
import fsSync5 from "node:fs";
|
|
5063
5095
|
import path19 from "node:path";
|
|
5064
5096
|
import { eq as eq3 } from "drizzle-orm";
|
|
5065
|
-
var SessionUIStore;
|
|
5097
|
+
var UI_STATE_IDLE_TTL_MS, UI_STATE_SWEEP_PERIOD_MS, SessionUIStore;
|
|
5066
5098
|
var init_session_ui_store = __esm({
|
|
5067
5099
|
"packages/server/dist/agents/session-ui-store.js"() {
|
|
5068
5100
|
"use strict";
|
|
@@ -5071,6 +5103,8 @@ var init_session_ui_store = __esm({
|
|
|
5071
5103
|
init_session_store();
|
|
5072
5104
|
init_session_archive();
|
|
5073
5105
|
init_ui_log_builder();
|
|
5106
|
+
UI_STATE_IDLE_TTL_MS = 10 * 6e4;
|
|
5107
|
+
UI_STATE_SWEEP_PERIOD_MS = 6e4;
|
|
5074
5108
|
SessionUIStore = class {
|
|
5075
5109
|
host;
|
|
5076
5110
|
db;
|
|
@@ -5090,9 +5124,13 @@ var init_session_ui_store = __esm({
|
|
|
5090
5124
|
uiStateProjectPaths = /* @__PURE__ */ new Map();
|
|
5091
5125
|
/** Debounced persist timers — prevents flooding disk with writes during rapid tool loops */
|
|
5092
5126
|
persistTimers = /* @__PURE__ */ new Map();
|
|
5127
|
+
/** Last activity (event reduced / view built) per uiStates key — the idle
|
|
5128
|
+
* sweep's eviction clock. Invariant: every uiStates key has an entry. */
|
|
5129
|
+
uiStateTouched = /* @__PURE__ */ new Map();
|
|
5093
5130
|
constructor(host) {
|
|
5094
5131
|
this.host = host;
|
|
5095
5132
|
this.db = host.getDb();
|
|
5133
|
+
setInterval(() => this.sweepIdleUIStates(), UI_STATE_SWEEP_PERIOD_MS).unref();
|
|
5096
5134
|
}
|
|
5097
5135
|
// ── Event routing ──────────────────────────────────────────────────
|
|
5098
5136
|
/** Store global event handler (backward compat — Phase 2 only) */
|
|
@@ -5229,6 +5267,7 @@ var init_session_ui_store = __esm({
|
|
|
5229
5267
|
* from the on-disk messages so the view is complete.
|
|
5230
5268
|
*/
|
|
5231
5269
|
ensureUIState(rootId) {
|
|
5270
|
+
this.uiStateTouched.set(rootId, Date.now());
|
|
5232
5271
|
const existing = this.uiStates.get(rootId);
|
|
5233
5272
|
if (existing)
|
|
5234
5273
|
return existing;
|
|
@@ -5449,10 +5488,48 @@ var init_session_ui_store = __esm({
|
|
|
5449
5488
|
/**
|
|
5450
5489
|
* Drop the in-memory UIState for a session (e.g. after stopSession / clear).
|
|
5451
5490
|
* The disk file stays. This is a no-op if not loaded.
|
|
5491
|
+
*
|
|
5492
|
+
* A pending debounced persist is flushed BEFORE eviction: dropping first
|
|
5493
|
+
* would let a later ensureUIState rehydrate from a disk file that's missing
|
|
5494
|
+
* the last batch, and the next persist of that shorter log would overwrite
|
|
5495
|
+
* the orphaned timer's eventual write — messages lost. Must not be called
|
|
5496
|
+
* while the session tree is mid-turn (callers gate on that): flushing then
|
|
5497
|
+
* would snapshot a temp in-flight assistant message that the turn's own
|
|
5498
|
+
* flush would later duplicate.
|
|
5452
5499
|
*/
|
|
5453
5500
|
dropUIState(sessionId) {
|
|
5501
|
+
const state = this.uiStates.get(sessionId);
|
|
5502
|
+
if (state && this.persistTimers.has(sessionId))
|
|
5503
|
+
this.flushPersist(sessionId, state);
|
|
5454
5504
|
this.uiStates.delete(sessionId);
|
|
5455
5505
|
this.uiStateProjectPaths.delete(sessionId);
|
|
5506
|
+
this.uiStateTouched.delete(sessionId);
|
|
5507
|
+
}
|
|
5508
|
+
/**
|
|
5509
|
+
* Evict UIStates whose tree has been idle past the TTL. This is THE eviction
|
|
5510
|
+
* point for the common leak (channel / cron / admin-viewed sessions that just
|
|
5511
|
+
* stop getting traffic) — archive and delete only cover explicit user action.
|
|
5512
|
+
*
|
|
5513
|
+
* Safety gates, in order:
|
|
5514
|
+
* - tree activity: an in-flight turn/compact anywhere in the tree keeps the
|
|
5515
|
+
* root's state (it holds the live sub buffers + streaming state);
|
|
5516
|
+
* - pending debounced persist counts as activity-in-progress? No — drop
|
|
5517
|
+
* flushes it first (see dropUIState), so a just-debounced write is safe.
|
|
5518
|
+
*
|
|
5519
|
+
* Re-entry cost after eviction: one loadSessionMessages disk read on the next
|
|
5520
|
+
* event/view — turn-start cadence at worst (ensureSession already does a
|
|
5521
|
+
* full-file read there anyway), never per-event.
|
|
5522
|
+
*/
|
|
5523
|
+
sweepIdleUIStates() {
|
|
5524
|
+
const cutoff = Date.now() - UI_STATE_IDLE_TTL_MS;
|
|
5525
|
+
for (const [rootId, touched] of this.uiStateTouched) {
|
|
5526
|
+
if (touched > cutoff)
|
|
5527
|
+
continue;
|
|
5528
|
+
if (this.host.hasActiveWorkInTree(rootId))
|
|
5529
|
+
continue;
|
|
5530
|
+
this.dropUIState(rootId);
|
|
5531
|
+
console.debug(`[SessionUIStore] evicted idle UIState ${rootId}`);
|
|
5532
|
+
}
|
|
5456
5533
|
}
|
|
5457
5534
|
// ── Operations the manager's session-lifecycle methods delegate here ──
|
|
5458
5535
|
// These exist so the manager never reaches into the maps above directly.
|
|
@@ -5493,6 +5570,7 @@ var init_session_ui_store = __esm({
|
|
|
5493
5570
|
purge(id) {
|
|
5494
5571
|
this.uiStates.delete(id);
|
|
5495
5572
|
this.uiStateProjectPaths.delete(id);
|
|
5573
|
+
this.uiStateTouched.delete(id);
|
|
5496
5574
|
const timer = this.persistTimers.get(id);
|
|
5497
5575
|
if (timer) {
|
|
5498
5576
|
clearTimeout(timer);
|
|
@@ -9075,13 +9153,20 @@ var init_session_agent_builder = __esm({
|
|
|
9075
9153
|
});
|
|
9076
9154
|
return { agent, yamlConfig, contextConfig, modelId, systemPrompt, thinkingEffort, meta, draftReset };
|
|
9077
9155
|
}
|
|
9078
|
-
/** 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. */
|
|
9079
9160
|
validateAgentModelConfig(agentId, yamlConfig) {
|
|
9080
|
-
const
|
|
9081
|
-
|
|
9082
|
-
|
|
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;
|
|
9083
9168
|
if (!modelId || !endpoint || !providerId) {
|
|
9084
|
-
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.`);
|
|
9085
9170
|
}
|
|
9086
9171
|
return { modelId, endpoint, providerId };
|
|
9087
9172
|
}
|
|
@@ -11416,6 +11501,19 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
11416
11501
|
}
|
|
11417
11502
|
return false;
|
|
11418
11503
|
}
|
|
11504
|
+
/** Any in-flight turn or compact anywhere in a root's session tree (root or
|
|
11505
|
+
* `root>…` descendants). SessionUIStoreHost member — the UI store's idle
|
|
11506
|
+
* sweep gates eviction on this, because a root's UIState carries its running
|
|
11507
|
+
* subs' live buffers even while the root itself is between turns. */
|
|
11508
|
+
hasActiveWorkInTree(rootId) {
|
|
11509
|
+
for (const [id, session] of this.sessions) {
|
|
11510
|
+
if (id !== rootId && !id.startsWith(`${rootId}>`))
|
|
11511
|
+
continue;
|
|
11512
|
+
if (session.promise !== null || session.isCompacting)
|
|
11513
|
+
return true;
|
|
11514
|
+
}
|
|
11515
|
+
return false;
|
|
11516
|
+
}
|
|
11419
11517
|
/** Get context config for a session */
|
|
11420
11518
|
async getContextConfig(sessionId) {
|
|
11421
11519
|
const session = this.sessions.get(sessionId);
|
|
@@ -13985,6 +14083,41 @@ var init_setup_prompts = __esm({
|
|
|
13985
14083
|
}
|
|
13986
14084
|
});
|
|
13987
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
|
+
|
|
13988
14121
|
// packages/cli/src/render-md.ts
|
|
13989
14122
|
import { marked } from "marked";
|
|
13990
14123
|
import { markedTerminal } from "marked-terminal";
|
|
@@ -14001,8 +14134,8 @@ var init_render_md = __esm({
|
|
|
14001
14134
|
});
|
|
14002
14135
|
|
|
14003
14136
|
// packages/cli/src/resolve-refs.ts
|
|
14004
|
-
import
|
|
14005
|
-
import
|
|
14137
|
+
import fs28 from "node:fs";
|
|
14138
|
+
import path34 from "node:path";
|
|
14006
14139
|
function resolveRefs(input, workspace) {
|
|
14007
14140
|
const images = [];
|
|
14008
14141
|
const attachments = [];
|
|
@@ -14015,11 +14148,11 @@ function resolveRefs(input, workspace) {
|
|
|
14015
14148
|
lastIdx = match.index + match[0].length;
|
|
14016
14149
|
const kind = match[1];
|
|
14017
14150
|
const rawPath = match[2] ?? match[3];
|
|
14018
|
-
const resolved =
|
|
14019
|
-
const ext =
|
|
14151
|
+
const resolved = path34.isAbsolute(rawPath) ? rawPath : path34.resolve(workspace, rawPath);
|
|
14152
|
+
const ext = path34.extname(resolved).toLowerCase();
|
|
14020
14153
|
let stat = null;
|
|
14021
14154
|
try {
|
|
14022
|
-
stat =
|
|
14155
|
+
stat = fs28.statSync(resolved);
|
|
14023
14156
|
} catch {
|
|
14024
14157
|
}
|
|
14025
14158
|
if (!stat) {
|
|
@@ -14037,18 +14170,18 @@ function resolveRefs(input, workspace) {
|
|
|
14037
14170
|
warnings.push(`${rawPath}: image too large (${(stat.size / 1024 / 1024).toFixed(1)}MB, max ${MAX_IMAGE_BYTES / 1024 / 1024}MB), skipped`);
|
|
14038
14171
|
continue;
|
|
14039
14172
|
}
|
|
14040
|
-
const data =
|
|
14173
|
+
const data = fs28.readFileSync(resolved).toString("base64");
|
|
14041
14174
|
images.push({ data, mimeType: imageMimeFromExt(ext) ?? "application/octet-stream" });
|
|
14042
14175
|
} else {
|
|
14043
|
-
const relPath =
|
|
14176
|
+
const relPath = path34.relative(workspace, resolved);
|
|
14044
14177
|
if (stat.size > MAX_FILE_BYTES) {
|
|
14045
14178
|
const buf = Buffer.alloc(MAX_FILE_BYTES);
|
|
14046
|
-
const fd =
|
|
14179
|
+
const fd = fs28.openSync(resolved, "r");
|
|
14047
14180
|
let n = 0;
|
|
14048
14181
|
try {
|
|
14049
|
-
n =
|
|
14182
|
+
n = fs28.readSync(fd, buf, 0, MAX_FILE_BYTES, 0);
|
|
14050
14183
|
} finally {
|
|
14051
|
-
|
|
14184
|
+
fs28.closeSync(fd);
|
|
14052
14185
|
}
|
|
14053
14186
|
const content = buf.subarray(0, n).toString("utf-8").replace(/\uFFFD+$/, "");
|
|
14054
14187
|
parts.push(`
|
|
@@ -14059,7 +14192,7 @@ ${content}
|
|
|
14059
14192
|
`);
|
|
14060
14193
|
warnings.push(`${rawPath}: truncated (${(stat.size / 1024).toFixed(0)}KB, max ${MAX_FILE_BYTES / 1024}KB)`);
|
|
14061
14194
|
} else {
|
|
14062
|
-
const content =
|
|
14195
|
+
const content = fs28.readFileSync(resolved, "utf-8");
|
|
14063
14196
|
parts.push(`
|
|
14064
14197
|
<file path="${relPath}">
|
|
14065
14198
|
${content}
|
|
@@ -14331,12 +14464,12 @@ var init_streaming = __esm({
|
|
|
14331
14464
|
});
|
|
14332
14465
|
|
|
14333
14466
|
// packages/cli/src/tui/history.ts
|
|
14334
|
-
import
|
|
14335
|
-
import
|
|
14467
|
+
import fs29 from "node:fs";
|
|
14468
|
+
import path35 from "node:path";
|
|
14336
14469
|
import os2 from "node:os";
|
|
14337
14470
|
function loadHistory() {
|
|
14338
14471
|
try {
|
|
14339
|
-
const arr = JSON.parse(
|
|
14472
|
+
const arr = JSON.parse(fs29.readFileSync(HISTORY_FILE, "utf8"));
|
|
14340
14473
|
if (Array.isArray(arr)) {
|
|
14341
14474
|
return arr.filter((x) => typeof x === "string").slice(-MAX_ENTRIES);
|
|
14342
14475
|
}
|
|
@@ -14349,8 +14482,8 @@ function appendHistory(entry) {
|
|
|
14349
14482
|
const arr = loadHistory();
|
|
14350
14483
|
if (arr[arr.length - 1] === entry) return;
|
|
14351
14484
|
arr.push(entry);
|
|
14352
|
-
|
|
14353
|
-
|
|
14485
|
+
fs29.mkdirSync(path35.dirname(HISTORY_FILE), { recursive: true });
|
|
14486
|
+
fs29.writeFileSync(HISTORY_FILE, JSON.stringify(arr.slice(-MAX_ENTRIES)));
|
|
14354
14487
|
} catch {
|
|
14355
14488
|
}
|
|
14356
14489
|
}
|
|
@@ -14358,7 +14491,7 @@ var HISTORY_FILE, MAX_ENTRIES;
|
|
|
14358
14491
|
var init_history = __esm({
|
|
14359
14492
|
"packages/cli/src/tui/history.ts"() {
|
|
14360
14493
|
"use strict";
|
|
14361
|
-
HISTORY_FILE =
|
|
14494
|
+
HISTORY_FILE = path35.join(os2.homedir(), ".halo", "global", "tui-history.json");
|
|
14362
14495
|
MAX_ENTRIES = 100;
|
|
14363
14496
|
}
|
|
14364
14497
|
});
|
|
@@ -14469,8 +14602,8 @@ var init_path_suggest = __esm({
|
|
|
14469
14602
|
});
|
|
14470
14603
|
|
|
14471
14604
|
// packages/cli/src/tui/path-suggest-engine.ts
|
|
14472
|
-
import
|
|
14473
|
-
import
|
|
14605
|
+
import fs30 from "node:fs";
|
|
14606
|
+
import path36 from "node:path";
|
|
14474
14607
|
function detectActiveRef(value) {
|
|
14475
14608
|
const reExplicit = /@(file|image|scope)\s+(?:"([^"]*)|(\S*))$/;
|
|
14476
14609
|
const mExplicit = value.match(reExplicit);
|
|
@@ -14499,20 +14632,20 @@ function resolveDirAndPrefix(partial, workspace) {
|
|
|
14499
14632
|
if (partial === "") {
|
|
14500
14633
|
return { absDir: workspace, displayDir: ".", prefix: "" };
|
|
14501
14634
|
}
|
|
14502
|
-
const absBase =
|
|
14635
|
+
const absBase = path36.isAbsolute(partial) ? partial : path36.join(workspace, partial);
|
|
14503
14636
|
if (partial.endsWith("/")) {
|
|
14504
14637
|
return { absDir: absBase, displayDir: partial.replace(/\/$/, "") || "/", prefix: "" };
|
|
14505
14638
|
}
|
|
14506
|
-
const dirAbs =
|
|
14507
|
-
const prefix =
|
|
14508
|
-
const dirRel =
|
|
14639
|
+
const dirAbs = path36.dirname(absBase);
|
|
14640
|
+
const prefix = path36.basename(absBase);
|
|
14641
|
+
const dirRel = path36.dirname(partial);
|
|
14509
14642
|
return { absDir: dirAbs, displayDir: dirRel === "." ? "." : dirRel, prefix };
|
|
14510
14643
|
}
|
|
14511
14644
|
function scanCandidates(partial, workspace, kind) {
|
|
14512
14645
|
const { absDir, displayDir, prefix } = resolveDirAndPrefix(partial, workspace);
|
|
14513
14646
|
let entries;
|
|
14514
14647
|
try {
|
|
14515
|
-
entries =
|
|
14648
|
+
entries = fs30.readdirSync(absDir, { withFileTypes: true });
|
|
14516
14649
|
} catch {
|
|
14517
14650
|
return { items: [], cwdLabel: displayDir };
|
|
14518
14651
|
}
|
|
@@ -14521,17 +14654,17 @@ function scanCandidates(partial, workspace, kind) {
|
|
|
14521
14654
|
if (e.name.startsWith(".")) continue;
|
|
14522
14655
|
if (prefix && !e.name.toLowerCase().startsWith(prefix.toLowerCase())) continue;
|
|
14523
14656
|
const isDir = e.isDirectory();
|
|
14524
|
-
const ext =
|
|
14657
|
+
const ext = path36.extname(e.name).toLowerCase();
|
|
14525
14658
|
const isImage = IMAGE_EXTS.has(ext);
|
|
14526
14659
|
if (kind === "image" && !isDir && !isImage) continue;
|
|
14527
14660
|
if (kind === "scope" && !isDir) continue;
|
|
14528
14661
|
let insertPath;
|
|
14529
|
-
if (
|
|
14530
|
-
insertPath =
|
|
14662
|
+
if (path36.isAbsolute(partial)) {
|
|
14663
|
+
insertPath = path36.join(absDir, e.name);
|
|
14531
14664
|
} else if (partial.endsWith("/") || partial === "") {
|
|
14532
14665
|
insertPath = partial + e.name;
|
|
14533
14666
|
} else {
|
|
14534
|
-
const dirRel =
|
|
14667
|
+
const dirRel = path36.dirname(partial);
|
|
14535
14668
|
insertPath = (dirRel === "." ? "" : dirRel + "/") + e.name;
|
|
14536
14669
|
}
|
|
14537
14670
|
items.push({ name: e.name, insertPath, isDir, isImage });
|
|
@@ -14550,8 +14683,8 @@ function applyPathPick(value, ref, pick) {
|
|
|
14550
14683
|
const before = value.slice(0, markerStart);
|
|
14551
14684
|
const kind = ref.kind === "scope" ? "scope" : ref.kind === "image" ? "image" : pick.isImage ? "image" : "file";
|
|
14552
14685
|
const needsQuote = pick.insertPath.includes(" ");
|
|
14553
|
-
const
|
|
14554
|
-
return `${before}@${kind} ${
|
|
14686
|
+
const path66 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
|
|
14687
|
+
return `${before}@${kind} ${path66}`;
|
|
14555
14688
|
}
|
|
14556
14689
|
var init_path_suggest_engine = __esm({
|
|
14557
14690
|
"packages/cli/src/tui/path-suggest-engine.ts"() {
|
|
@@ -16024,15 +16157,15 @@ var init_cli = __esm({
|
|
|
16024
16157
|
});
|
|
16025
16158
|
|
|
16026
16159
|
// packages/server/dist/channels/shared/media.js
|
|
16027
|
-
import
|
|
16160
|
+
import path37 from "node:path";
|
|
16028
16161
|
import os3 from "node:os";
|
|
16029
16162
|
function tempDir() {
|
|
16030
|
-
return
|
|
16163
|
+
return path37.resolve(os3.tmpdir());
|
|
16031
16164
|
}
|
|
16032
16165
|
function isInTempDir(filePath) {
|
|
16033
|
-
const resolved =
|
|
16166
|
+
const resolved = path37.resolve(filePath);
|
|
16034
16167
|
const tmp = tempDir();
|
|
16035
|
-
return resolved === tmp || resolved.startsWith(tmp +
|
|
16168
|
+
return resolved === tmp || resolved.startsWith(tmp + path37.sep);
|
|
16036
16169
|
}
|
|
16037
16170
|
function extractMediaPaths(text5) {
|
|
16038
16171
|
const mediaPaths = [];
|
|
@@ -16048,12 +16181,12 @@ function extractMediaMessage(text5) {
|
|
|
16048
16181
|
return { text: stripped.replace(/\n{3,}/g, "\n\n").trim(), mediaPaths };
|
|
16049
16182
|
}
|
|
16050
16183
|
function isMediaPathAllowed(filePath, workspacePath) {
|
|
16051
|
-
const resolved =
|
|
16052
|
-
const ws =
|
|
16053
|
-
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);
|
|
16054
16187
|
}
|
|
16055
16188
|
function classifyMedia(filePath) {
|
|
16056
|
-
const ext =
|
|
16189
|
+
const ext = path37.extname(filePath).toLowerCase();
|
|
16057
16190
|
if (IMAGE_EXTS.has(ext))
|
|
16058
16191
|
return "image";
|
|
16059
16192
|
if (VIDEO_EXTS.has(ext))
|
|
@@ -16074,13 +16207,13 @@ var init_media = __esm({
|
|
|
16074
16207
|
});
|
|
16075
16208
|
|
|
16076
16209
|
// packages/server/dist/routes/workspace-path.js
|
|
16077
|
-
import
|
|
16210
|
+
import fs31 from "node:fs/promises";
|
|
16078
16211
|
import { existsSync as existsSync2, realpathSync as realpathSync3 } from "node:fs";
|
|
16079
|
-
import
|
|
16212
|
+
import path38 from "node:path";
|
|
16080
16213
|
async function resolveProjectPath(projectId) {
|
|
16081
|
-
if (
|
|
16214
|
+
if (path38.isAbsolute(projectId)) {
|
|
16082
16215
|
try {
|
|
16083
|
-
await
|
|
16216
|
+
await fs31.access(projectId);
|
|
16084
16217
|
return projectId;
|
|
16085
16218
|
} catch {
|
|
16086
16219
|
return null;
|
|
@@ -16089,13 +16222,13 @@ async function resolveProjectPath(projectId) {
|
|
|
16089
16222
|
return null;
|
|
16090
16223
|
}
|
|
16091
16224
|
function realpathBounded2(filePath) {
|
|
16092
|
-
let prefix =
|
|
16225
|
+
let prefix = path38.resolve(filePath);
|
|
16093
16226
|
const tail = [];
|
|
16094
16227
|
while (!existsSync2(prefix)) {
|
|
16095
|
-
const parent =
|
|
16228
|
+
const parent = path38.dirname(prefix);
|
|
16096
16229
|
if (parent === prefix)
|
|
16097
16230
|
break;
|
|
16098
|
-
tail.unshift(
|
|
16231
|
+
tail.unshift(path38.basename(prefix));
|
|
16099
16232
|
prefix = parent;
|
|
16100
16233
|
}
|
|
16101
16234
|
let realPrefix;
|
|
@@ -16104,12 +16237,12 @@ function realpathBounded2(filePath) {
|
|
|
16104
16237
|
} catch {
|
|
16105
16238
|
realPrefix = prefix;
|
|
16106
16239
|
}
|
|
16107
|
-
return tail.length > 0 ?
|
|
16240
|
+
return tail.length > 0 ? path38.join(realPrefix, ...tail) : realPrefix;
|
|
16108
16241
|
}
|
|
16109
16242
|
function validatePath(filePath, projectPath) {
|
|
16110
|
-
const resolved = realpathBounded2(
|
|
16243
|
+
const resolved = realpathBounded2(path38.resolve(projectPath, filePath));
|
|
16111
16244
|
const proj = realpathBounded2(projectPath);
|
|
16112
|
-
return resolved === proj || resolved.startsWith(proj +
|
|
16245
|
+
return resolved === proj || resolved.startsWith(proj + path38.sep);
|
|
16113
16246
|
}
|
|
16114
16247
|
function isSafeIdSegment(id) {
|
|
16115
16248
|
if (id === "." || id === "..")
|
|
@@ -16124,10 +16257,10 @@ var init_workspace_path = __esm({
|
|
|
16124
16257
|
|
|
16125
16258
|
// packages/server/dist/routes/files.js
|
|
16126
16259
|
import { Hono } from "hono";
|
|
16127
|
-
import
|
|
16260
|
+
import fs32 from "node:fs/promises";
|
|
16128
16261
|
import { createReadStream } from "node:fs";
|
|
16129
16262
|
import { Readable } from "node:stream";
|
|
16130
|
-
import
|
|
16263
|
+
import path39 from "node:path";
|
|
16131
16264
|
import { homedir as homedir17 } from "node:os";
|
|
16132
16265
|
function createFileRoutes() {
|
|
16133
16266
|
const app2 = new Hono();
|
|
@@ -16144,7 +16277,7 @@ function createFileRoutes() {
|
|
|
16144
16277
|
}
|
|
16145
16278
|
async function dirHasChildren(dirPath) {
|
|
16146
16279
|
try {
|
|
16147
|
-
const entries = await
|
|
16280
|
+
const entries = await fs32.readdir(dirPath, { withFileTypes: true });
|
|
16148
16281
|
for (const entry of entries) {
|
|
16149
16282
|
if (!isSkippedName(entry.name))
|
|
16150
16283
|
return true;
|
|
@@ -16157,7 +16290,7 @@ function createFileRoutes() {
|
|
|
16157
16290
|
async function listDir(dirPath, basePath) {
|
|
16158
16291
|
let entries;
|
|
16159
16292
|
try {
|
|
16160
|
-
entries = await
|
|
16293
|
+
entries = await fs32.readdir(dirPath, { withFileTypes: true });
|
|
16161
16294
|
} catch {
|
|
16162
16295
|
return [];
|
|
16163
16296
|
}
|
|
@@ -16172,8 +16305,8 @@ function createFileRoutes() {
|
|
|
16172
16305
|
for (const entry of sorted) {
|
|
16173
16306
|
if (isSkippedName(entry.name))
|
|
16174
16307
|
continue;
|
|
16175
|
-
const fullPath =
|
|
16176
|
-
const relativePath =
|
|
16308
|
+
const fullPath = path39.join(dirPath, entry.name);
|
|
16309
|
+
const relativePath = path39.relative(basePath, fullPath).split(path39.sep).join("/");
|
|
16177
16310
|
if (entry.isDirectory()) {
|
|
16178
16311
|
nodes.push({
|
|
16179
16312
|
name: entry.name,
|
|
@@ -16205,9 +16338,9 @@ function createFileRoutes() {
|
|
|
16205
16338
|
if (relPath && !validatePath(relPath, projectPath)) {
|
|
16206
16339
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16207
16340
|
}
|
|
16208
|
-
const targetPath = relPath ?
|
|
16341
|
+
const targetPath = relPath ? path39.resolve(projectPath, relPath) : projectPath;
|
|
16209
16342
|
try {
|
|
16210
|
-
const stat = await
|
|
16343
|
+
const stat = await fs32.stat(targetPath);
|
|
16211
16344
|
if (!stat.isDirectory()) {
|
|
16212
16345
|
return c.json({ error: "Path is not a directory" }, 400);
|
|
16213
16346
|
}
|
|
@@ -16217,7 +16350,7 @@ function createFileRoutes() {
|
|
|
16217
16350
|
const tree = await listDir(targetPath, projectPath);
|
|
16218
16351
|
return c.json({
|
|
16219
16352
|
projectId,
|
|
16220
|
-
root:
|
|
16353
|
+
root: path39.basename(projectPath),
|
|
16221
16354
|
path: relPath,
|
|
16222
16355
|
tree
|
|
16223
16356
|
});
|
|
@@ -16250,7 +16383,7 @@ function createFileRoutes() {
|
|
|
16250
16383
|
return;
|
|
16251
16384
|
let entries;
|
|
16252
16385
|
try {
|
|
16253
|
-
entries = await
|
|
16386
|
+
entries = await fs32.readdir(dirPath, { withFileTypes: true });
|
|
16254
16387
|
} catch {
|
|
16255
16388
|
return;
|
|
16256
16389
|
}
|
|
@@ -16260,11 +16393,11 @@ function createFileRoutes() {
|
|
|
16260
16393
|
if (isSkippedName(entry.name))
|
|
16261
16394
|
continue;
|
|
16262
16395
|
scanned++;
|
|
16263
|
-
const fullPath =
|
|
16396
|
+
const fullPath = path39.join(dirPath, entry.name);
|
|
16264
16397
|
if (entry.isDirectory()) {
|
|
16265
16398
|
if (dirsOnly) {
|
|
16266
16399
|
const name = entry.name.toLowerCase();
|
|
16267
|
-
const rel =
|
|
16400
|
+
const rel = path39.relative(projectRoot, fullPath);
|
|
16268
16401
|
if (!q || name.includes(q) || rel.toLowerCase().includes(q)) {
|
|
16269
16402
|
matches.push({ name: entry.name, path: rel });
|
|
16270
16403
|
}
|
|
@@ -16272,7 +16405,7 @@ function createFileRoutes() {
|
|
|
16272
16405
|
await walk(fullPath);
|
|
16273
16406
|
} else if (entry.isFile() && !dirsOnly) {
|
|
16274
16407
|
const name = entry.name.toLowerCase();
|
|
16275
|
-
const rel =
|
|
16408
|
+
const rel = path39.relative(projectRoot, fullPath);
|
|
16276
16409
|
if (!q || name.includes(q) || rel.toLowerCase().includes(q)) {
|
|
16277
16410
|
matches.push({ name: entry.name, path: rel });
|
|
16278
16411
|
}
|
|
@@ -16316,15 +16449,15 @@ function createFileRoutes() {
|
|
|
16316
16449
|
}
|
|
16317
16450
|
const uploaded = [];
|
|
16318
16451
|
for (const file of files) {
|
|
16319
|
-
const relPath = targetDir ?
|
|
16452
|
+
const relPath = targetDir ? path39.join(targetDir, file.name) : file.name;
|
|
16320
16453
|
if (!validatePath(relPath, projectPath)) {
|
|
16321
16454
|
continue;
|
|
16322
16455
|
}
|
|
16323
|
-
const destPath =
|
|
16324
|
-
const destDir =
|
|
16325
|
-
await
|
|
16456
|
+
const destPath = path39.resolve(projectPath, relPath);
|
|
16457
|
+
const destDir = path39.dirname(destPath);
|
|
16458
|
+
await fs32.mkdir(destDir, { recursive: true });
|
|
16326
16459
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
16327
|
-
await
|
|
16460
|
+
await fs32.writeFile(destPath, buffer);
|
|
16328
16461
|
uploaded.push({
|
|
16329
16462
|
name: file.name,
|
|
16330
16463
|
path: relPath,
|
|
@@ -16352,9 +16485,9 @@ function createFileRoutes() {
|
|
|
16352
16485
|
if (!validatePath(filePath, projectPath)) {
|
|
16353
16486
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16354
16487
|
}
|
|
16355
|
-
const absolutePath =
|
|
16356
|
-
await
|
|
16357
|
-
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);
|
|
16358
16491
|
return c.json({ ok: true, path: filePath, modifiedAt: stat.mtimeMs });
|
|
16359
16492
|
} catch (err) {
|
|
16360
16493
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16376,14 +16509,14 @@ function createFileRoutes() {
|
|
|
16376
16509
|
if (!validatePath(filePath, projectPath)) {
|
|
16377
16510
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16378
16511
|
}
|
|
16379
|
-
const absolutePath =
|
|
16380
|
-
await
|
|
16512
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16513
|
+
await fs32.mkdir(path39.dirname(absolutePath), { recursive: true });
|
|
16381
16514
|
try {
|
|
16382
|
-
await
|
|
16515
|
+
await fs32.access(absolutePath);
|
|
16383
16516
|
return c.json({ error: "File already exists" }, 409);
|
|
16384
16517
|
} catch {
|
|
16385
16518
|
}
|
|
16386
|
-
await
|
|
16519
|
+
await fs32.writeFile(absolutePath, "", "utf-8");
|
|
16387
16520
|
return c.json({ ok: true, path: filePath });
|
|
16388
16521
|
} catch (err) {
|
|
16389
16522
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16405,8 +16538,8 @@ function createFileRoutes() {
|
|
|
16405
16538
|
if (!validatePath(dirPath, projectPath)) {
|
|
16406
16539
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16407
16540
|
}
|
|
16408
|
-
const absolutePath =
|
|
16409
|
-
await
|
|
16541
|
+
const absolutePath = path39.resolve(projectPath, dirPath);
|
|
16542
|
+
await fs32.mkdir(absolutePath, { recursive: true });
|
|
16410
16543
|
return c.json({ ok: true, path: dirPath });
|
|
16411
16544
|
} catch (err) {
|
|
16412
16545
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16428,10 +16561,10 @@ function createFileRoutes() {
|
|
|
16428
16561
|
if (!validatePath(oldPath, projectPath) || !validatePath(newPath, projectPath)) {
|
|
16429
16562
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16430
16563
|
}
|
|
16431
|
-
const absOld =
|
|
16432
|
-
const absNew =
|
|
16433
|
-
await
|
|
16434
|
-
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);
|
|
16435
16568
|
return c.json({ ok: true, oldPath, newPath });
|
|
16436
16569
|
} catch (err) {
|
|
16437
16570
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16453,11 +16586,11 @@ function createFileRoutes() {
|
|
|
16453
16586
|
if (!validatePath(filePath, projectPath)) {
|
|
16454
16587
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16455
16588
|
}
|
|
16456
|
-
const absolutePath =
|
|
16457
|
-
if (absolutePath ===
|
|
16589
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16590
|
+
if (absolutePath === path39.resolve(projectPath)) {
|
|
16458
16591
|
return c.json({ error: "Cannot delete project root" }, 403);
|
|
16459
16592
|
}
|
|
16460
|
-
await
|
|
16593
|
+
await fs32.rm(absolutePath, { recursive: true, force: true });
|
|
16461
16594
|
return c.json({ ok: true, path: filePath });
|
|
16462
16595
|
} catch (err) {
|
|
16463
16596
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -16491,16 +16624,16 @@ function createFileRoutes() {
|
|
|
16491
16624
|
if (!projectPath) {
|
|
16492
16625
|
return c.json({ error: "Project not found" }, 404);
|
|
16493
16626
|
}
|
|
16494
|
-
const isTmp =
|
|
16627
|
+
const isTmp = path39.isAbsolute(filePath) && isInTempDir(filePath);
|
|
16495
16628
|
if (!isTmp && !validatePath(filePath, projectPath)) {
|
|
16496
16629
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16497
16630
|
}
|
|
16498
|
-
const absolutePath = isTmp ?
|
|
16499
|
-
const stat = await
|
|
16631
|
+
const absolutePath = isTmp ? path39.resolve(filePath) : path39.resolve(projectPath, filePath);
|
|
16632
|
+
const stat = await fs32.stat(absolutePath);
|
|
16500
16633
|
if (stat.isDirectory()) {
|
|
16501
16634
|
return c.json({ error: "Cannot download a directory" }, 400);
|
|
16502
16635
|
}
|
|
16503
|
-
const fileName =
|
|
16636
|
+
const fileName = path39.basename(filePath);
|
|
16504
16637
|
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
16505
16638
|
const mime = inline ? imageMimeFromExt(ext) ?? MIME_MAP[ext] ?? "application/octet-stream" : "application/octet-stream";
|
|
16506
16639
|
const disposition = inline ? `inline; filename="${encodeURIComponent(fileName)}"` : `attachment; filename="${encodeURIComponent(fileName)}"`;
|
|
@@ -16559,8 +16692,8 @@ function createFileRoutes() {
|
|
|
16559
16692
|
return c.json({ error: "Project not found" }, 404);
|
|
16560
16693
|
if (!validatePath(filePath, projectPath))
|
|
16561
16694
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16562
|
-
const absolutePath =
|
|
16563
|
-
const stat = await
|
|
16695
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16696
|
+
const stat = await fs32.stat(absolutePath);
|
|
16564
16697
|
return c.json({ path: filePath, modifiedAt: stat.mtimeMs, createdAt: stat.birthtimeMs, size: stat.size });
|
|
16565
16698
|
} catch (err) {
|
|
16566
16699
|
if (err.code === "ENOENT")
|
|
@@ -16582,9 +16715,9 @@ function createFileRoutes() {
|
|
|
16582
16715
|
if (!validatePath(filePath, projectPath)) {
|
|
16583
16716
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16584
16717
|
}
|
|
16585
|
-
const absolutePath =
|
|
16718
|
+
const absolutePath = path39.resolve(projectPath, filePath);
|
|
16586
16719
|
try {
|
|
16587
|
-
const stat = await
|
|
16720
|
+
const stat = await fs32.stat(absolutePath);
|
|
16588
16721
|
if (stat.isDirectory()) {
|
|
16589
16722
|
return c.json({ error: "Path is a directory, use /files/tree instead" }, 400);
|
|
16590
16723
|
}
|
|
@@ -16592,7 +16725,7 @@ function createFileRoutes() {
|
|
|
16592
16725
|
if (stat.size > maxSize) {
|
|
16593
16726
|
return c.json({ error: "File too large (max 10MB)" }, 413);
|
|
16594
16727
|
}
|
|
16595
|
-
const content = await
|
|
16728
|
+
const content = await fs32.readFile(absolutePath, "utf-8");
|
|
16596
16729
|
return c.json({
|
|
16597
16730
|
path: filePath,
|
|
16598
16731
|
content,
|
|
@@ -16619,16 +16752,16 @@ function createFileRoutes() {
|
|
|
16619
16752
|
const body = await c.req.json();
|
|
16620
16753
|
if (!body.path)
|
|
16621
16754
|
return c.json({ error: "path required" }, 400);
|
|
16622
|
-
const resolved =
|
|
16755
|
+
const resolved = path39.resolve(body.path);
|
|
16623
16756
|
let stat;
|
|
16624
16757
|
try {
|
|
16625
|
-
stat = await
|
|
16758
|
+
stat = await fs32.stat(resolved);
|
|
16626
16759
|
} catch {
|
|
16627
16760
|
return c.json({ error: "path not found" }, 404);
|
|
16628
16761
|
}
|
|
16629
16762
|
if (!stat.isDirectory())
|
|
16630
16763
|
return c.json({ error: "not a directory" }, 400);
|
|
16631
|
-
if (resolved ===
|
|
16764
|
+
if (resolved === path39.parse(resolved).root) {
|
|
16632
16765
|
return c.json({ error: "filesystem root cannot be a workspace" }, 400);
|
|
16633
16766
|
}
|
|
16634
16767
|
const { ensureWorkspaceHalo: ensureWorkspaceHalo2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
@@ -16637,10 +16770,10 @@ function createFileRoutes() {
|
|
|
16637
16770
|
});
|
|
16638
16771
|
app2.get("/fs/exists", async (c) => {
|
|
16639
16772
|
const target = c.req.query("path");
|
|
16640
|
-
if (!target || !
|
|
16773
|
+
if (!target || !path39.isAbsolute(target))
|
|
16641
16774
|
return c.json({ exists: false, reason: "absolute path required" });
|
|
16642
16775
|
try {
|
|
16643
|
-
const stat = await
|
|
16776
|
+
const stat = await fs32.stat(target);
|
|
16644
16777
|
return c.json({ exists: true, isDirectory: stat.isDirectory() });
|
|
16645
16778
|
} catch {
|
|
16646
16779
|
return c.json({ exists: false });
|
|
@@ -16648,13 +16781,13 @@ function createFileRoutes() {
|
|
|
16648
16781
|
});
|
|
16649
16782
|
app2.get("/fs/browse", async (c) => {
|
|
16650
16783
|
const target = c.req.query("path") ?? homedir17();
|
|
16651
|
-
if (!
|
|
16784
|
+
if (!path39.isAbsolute(target))
|
|
16652
16785
|
return c.json({ error: "absolute path required" }, 400);
|
|
16653
16786
|
try {
|
|
16654
|
-
const resolved =
|
|
16655
|
-
const entries = await
|
|
16656
|
-
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
16657
|
-
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 });
|
|
16658
16791
|
} catch (err) {
|
|
16659
16792
|
return c.json({ error: err.message }, 404);
|
|
16660
16793
|
}
|
|
@@ -16672,9 +16805,9 @@ var init_files = __esm({
|
|
|
16672
16805
|
|
|
16673
16806
|
// packages/server/dist/routes/data-preview.js
|
|
16674
16807
|
import { Hono as Hono2 } from "hono";
|
|
16675
|
-
import
|
|
16808
|
+
import fs33 from "node:fs/promises";
|
|
16676
16809
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
16677
|
-
import
|
|
16810
|
+
import path40 from "node:path";
|
|
16678
16811
|
import Database3 from "better-sqlite3";
|
|
16679
16812
|
import { asyncBufferFromFile, parquetMetadataAsync, parquetSchema, parquetReadObjects } from "hyparquet";
|
|
16680
16813
|
import { compressors } from "hyparquet-compressors";
|
|
@@ -16705,7 +16838,7 @@ function quoteIdent(name) {
|
|
|
16705
16838
|
return `"${name.replaceAll('"', '""')}"`;
|
|
16706
16839
|
}
|
|
16707
16840
|
async function sniffDelimiter(absolutePath) {
|
|
16708
|
-
const fh = await
|
|
16841
|
+
const fh = await fs33.open(absolutePath, "r");
|
|
16709
16842
|
let head;
|
|
16710
16843
|
try {
|
|
16711
16844
|
const buf = Buffer.alloc(65536);
|
|
@@ -16789,9 +16922,9 @@ function createDataPreviewRoutes() {
|
|
|
16789
16922
|
return { error: "Project not found", status: 404 };
|
|
16790
16923
|
if (!validatePath(filePath, projectPath))
|
|
16791
16924
|
return { error: "Path traversal not allowed", status: 403 };
|
|
16792
|
-
const absolutePath =
|
|
16925
|
+
const absolutePath = path40.resolve(projectPath, filePath);
|
|
16793
16926
|
try {
|
|
16794
|
-
const stat = await
|
|
16927
|
+
const stat = await fs33.stat(absolutePath);
|
|
16795
16928
|
if (stat.isDirectory())
|
|
16796
16929
|
return { error: "Path is a directory", status: 400 };
|
|
16797
16930
|
} catch {
|
|
@@ -16997,15 +17130,15 @@ var init_data_preview = __esm({
|
|
|
16997
17130
|
});
|
|
16998
17131
|
|
|
16999
17132
|
// packages/server/dist/git-credentials.js
|
|
17000
|
-
import
|
|
17001
|
-
import
|
|
17133
|
+
import fs34 from "node:fs";
|
|
17134
|
+
import path41 from "node:path";
|
|
17002
17135
|
import { homedir as homedir18 } from "node:os";
|
|
17003
|
-
import { spawnSync } from "node:child_process";
|
|
17136
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
17004
17137
|
function writeGitCredentialsFile(host, username, token) {
|
|
17005
17138
|
const line = `https://${encodeURIComponent(username)}:${encodeURIComponent(token)}@${host}`;
|
|
17006
17139
|
let lines = [];
|
|
17007
|
-
if (
|
|
17008
|
-
lines =
|
|
17140
|
+
if (fs34.existsSync(GIT_CREDENTIALS_PATH)) {
|
|
17141
|
+
lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
|
|
17009
17142
|
}
|
|
17010
17143
|
const kept = lines.filter((l) => {
|
|
17011
17144
|
try {
|
|
@@ -17015,17 +17148,17 @@ function writeGitCredentialsFile(host, username, token) {
|
|
|
17015
17148
|
}
|
|
17016
17149
|
});
|
|
17017
17150
|
kept.push(line);
|
|
17018
|
-
|
|
17151
|
+
fs34.writeFileSync(GIT_CREDENTIALS_PATH, kept.join("\n") + "\n", { encoding: "utf-8", mode: 384 });
|
|
17019
17152
|
try {
|
|
17020
|
-
|
|
17153
|
+
fs34.chmodSync(GIT_CREDENTIALS_PATH, 384);
|
|
17021
17154
|
} catch {
|
|
17022
17155
|
}
|
|
17023
17156
|
}
|
|
17024
17157
|
function ensureCredentialHelper() {
|
|
17025
|
-
const current =
|
|
17158
|
+
const current = spawnSync2("git", ["config", "--global", "--get", "credential.helper"], { encoding: "utf-8" });
|
|
17026
17159
|
const helper = (current.stdout ?? "").trim();
|
|
17027
17160
|
if (!helper) {
|
|
17028
|
-
|
|
17161
|
+
spawnSync2("git", ["config", "--global", "credential.helper", "store"], { encoding: "utf-8" });
|
|
17029
17162
|
}
|
|
17030
17163
|
}
|
|
17031
17164
|
function saveGitCredentials({ host, username, token }) {
|
|
@@ -17034,9 +17167,9 @@ function saveGitCredentials({ host, username, token }) {
|
|
|
17034
17167
|
console.log(`[GitCredentials] saved for host ${host}`);
|
|
17035
17168
|
}
|
|
17036
17169
|
function listGitCredentials() {
|
|
17037
|
-
if (!
|
|
17170
|
+
if (!fs34.existsSync(GIT_CREDENTIALS_PATH))
|
|
17038
17171
|
return [];
|
|
17039
|
-
const lines =
|
|
17172
|
+
const lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
|
|
17040
17173
|
const byHost = /* @__PURE__ */ new Map();
|
|
17041
17174
|
for (const line of lines) {
|
|
17042
17175
|
try {
|
|
@@ -17050,9 +17183,9 @@ function listGitCredentials() {
|
|
|
17050
17183
|
return [...byHost.values()];
|
|
17051
17184
|
}
|
|
17052
17185
|
function deleteGitCredential(host) {
|
|
17053
|
-
if (!
|
|
17186
|
+
if (!fs34.existsSync(GIT_CREDENTIALS_PATH))
|
|
17054
17187
|
return;
|
|
17055
|
-
const lines =
|
|
17188
|
+
const lines = fs34.readFileSync(GIT_CREDENTIALS_PATH, "utf-8").split("\n").filter((l) => l.trim() !== "");
|
|
17056
17189
|
const kept = lines.filter((l) => {
|
|
17057
17190
|
try {
|
|
17058
17191
|
return new URL(l).host !== host;
|
|
@@ -17062,9 +17195,9 @@ function deleteGitCredential(host) {
|
|
|
17062
17195
|
});
|
|
17063
17196
|
if (kept.length === lines.length)
|
|
17064
17197
|
return;
|
|
17065
|
-
|
|
17198
|
+
fs34.writeFileSync(GIT_CREDENTIALS_PATH, kept.length ? kept.join("\n") + "\n" : "", { encoding: "utf-8", mode: 384 });
|
|
17066
17199
|
try {
|
|
17067
|
-
|
|
17200
|
+
fs34.chmodSync(GIT_CREDENTIALS_PATH, 384);
|
|
17068
17201
|
} catch {
|
|
17069
17202
|
}
|
|
17070
17203
|
console.log(`[GitCredentials] removed for host ${host}`);
|
|
@@ -17073,15 +17206,15 @@ var GIT_CREDENTIALS_PATH;
|
|
|
17073
17206
|
var init_git_credentials = __esm({
|
|
17074
17207
|
"packages/server/dist/git-credentials.js"() {
|
|
17075
17208
|
"use strict";
|
|
17076
|
-
GIT_CREDENTIALS_PATH =
|
|
17209
|
+
GIT_CREDENTIALS_PATH = path41.join(homedir18(), ".git-credentials");
|
|
17077
17210
|
}
|
|
17078
17211
|
});
|
|
17079
17212
|
|
|
17080
17213
|
// packages/server/dist/git-ssh.js
|
|
17081
|
-
import
|
|
17214
|
+
import fs35 from "node:fs";
|
|
17082
17215
|
import os4 from "node:os";
|
|
17083
|
-
import
|
|
17084
|
-
import { spawnSync as
|
|
17216
|
+
import path42 from "node:path";
|
|
17217
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
17085
17218
|
function looksLikePrivateKey(name, abs) {
|
|
17086
17219
|
if (name.endsWith(".pub"))
|
|
17087
17220
|
return false;
|
|
@@ -17089,13 +17222,13 @@ function looksLikePrivateKey(name, abs) {
|
|
|
17089
17222
|
return false;
|
|
17090
17223
|
let firstLine = "";
|
|
17091
17224
|
try {
|
|
17092
|
-
const fd =
|
|
17225
|
+
const fd = fs35.openSync(abs, "r");
|
|
17093
17226
|
try {
|
|
17094
17227
|
const buf = Buffer.alloc(64);
|
|
17095
|
-
const n =
|
|
17228
|
+
const n = fs35.readSync(fd, buf, 0, 64, 0);
|
|
17096
17229
|
firstLine = buf.toString("utf-8", 0, n).split("\n")[0];
|
|
17097
17230
|
} finally {
|
|
17098
|
-
|
|
17231
|
+
fs35.closeSync(fd);
|
|
17099
17232
|
}
|
|
17100
17233
|
} catch {
|
|
17101
17234
|
return false;
|
|
@@ -17103,21 +17236,21 @@ function looksLikePrivateKey(name, abs) {
|
|
|
17103
17236
|
return firstLine.includes("PRIVATE KEY-----");
|
|
17104
17237
|
}
|
|
17105
17238
|
function isKeyEncrypted(abs) {
|
|
17106
|
-
const r =
|
|
17239
|
+
const r = spawnSync3("ssh-keygen", ["-y", "-P", "", "-f", abs], { encoding: "utf-8" });
|
|
17107
17240
|
return r.status !== 0;
|
|
17108
17241
|
}
|
|
17109
17242
|
function listSshKeys() {
|
|
17110
17243
|
let entries = [];
|
|
17111
17244
|
try {
|
|
17112
|
-
entries =
|
|
17245
|
+
entries = fs35.readdirSync(SSH_DIR);
|
|
17113
17246
|
} catch {
|
|
17114
17247
|
return [];
|
|
17115
17248
|
}
|
|
17116
17249
|
const keys = [];
|
|
17117
17250
|
for (const name of entries) {
|
|
17118
|
-
const abs =
|
|
17251
|
+
const abs = path42.join(SSH_DIR, name);
|
|
17119
17252
|
try {
|
|
17120
|
-
if (!
|
|
17253
|
+
if (!fs35.statSync(abs).isFile())
|
|
17121
17254
|
continue;
|
|
17122
17255
|
} catch {
|
|
17123
17256
|
continue;
|
|
@@ -17131,7 +17264,7 @@ function listSshKeys() {
|
|
|
17131
17264
|
function getSshAgentStatus() {
|
|
17132
17265
|
if (!process.env.SSH_AUTH_SOCK)
|
|
17133
17266
|
return { agentRunning: false, loadedKeys: [] };
|
|
17134
|
-
const r =
|
|
17267
|
+
const r = spawnSync3("ssh-add", ["-l"], { encoding: "utf-8" });
|
|
17135
17268
|
if (r.status === 2)
|
|
17136
17269
|
return { agentRunning: false, loadedKeys: [] };
|
|
17137
17270
|
const loadedKeys = (r.stdout ?? "").split("\n").map((l) => l.trim()).filter(Boolean).filter((l) => l !== "The agent has no identities.");
|
|
@@ -17142,10 +17275,10 @@ function unlockSshKey(keyPath, passphrase) {
|
|
|
17142
17275
|
return { ok: false, error: "ssh-agent is not running" };
|
|
17143
17276
|
let dir = "";
|
|
17144
17277
|
try {
|
|
17145
|
-
dir =
|
|
17146
|
-
const askpass =
|
|
17147
|
-
|
|
17148
|
-
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], {
|
|
17149
17282
|
encoding: "utf-8",
|
|
17150
17283
|
// stdin from /dev/null so ssh-add can never fall back to a tty prompt and block.
|
|
17151
17284
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -17160,10 +17293,10 @@ function unlockSshKey(keyPath, passphrase) {
|
|
|
17160
17293
|
}
|
|
17161
17294
|
});
|
|
17162
17295
|
if (r.status === 0) {
|
|
17163
|
-
console.log(`[GitSsh] unlocked key ${
|
|
17296
|
+
console.log(`[GitSsh] unlocked key ${path42.basename(keyPath)}`);
|
|
17164
17297
|
return { ok: true };
|
|
17165
17298
|
}
|
|
17166
|
-
console.log(`[GitSsh] unlock failed for ${
|
|
17299
|
+
console.log(`[GitSsh] unlock failed for ${path42.basename(keyPath)} (exit ${r.status ?? "killed"})`);
|
|
17167
17300
|
if (r.status === 2)
|
|
17168
17301
|
return { ok: false, error: "ssh-agent is not reachable" };
|
|
17169
17302
|
return { ok: false, error: "Incorrect passphrase, or the key could not be unlocked" };
|
|
@@ -17173,19 +17306,19 @@ function unlockSshKey(keyPath, passphrase) {
|
|
|
17173
17306
|
return { ok: false, error: "Failed to unlock key" };
|
|
17174
17307
|
} finally {
|
|
17175
17308
|
if (dir)
|
|
17176
|
-
|
|
17309
|
+
fs35.rmSync(dir, { recursive: true, force: true });
|
|
17177
17310
|
}
|
|
17178
17311
|
}
|
|
17179
17312
|
function ensureSshAgent() {
|
|
17180
17313
|
try {
|
|
17181
17314
|
if (process.env.SSH_AUTH_SOCK) {
|
|
17182
|
-
const probe =
|
|
17315
|
+
const probe = spawnSync3("ssh-add", ["-l"], { encoding: "utf-8" });
|
|
17183
17316
|
if (probe.status !== 2) {
|
|
17184
17317
|
console.log(`[GitSsh] using existing agent at ${process.env.SSH_AUTH_SOCK}`);
|
|
17185
17318
|
return;
|
|
17186
17319
|
}
|
|
17187
17320
|
}
|
|
17188
|
-
const r =
|
|
17321
|
+
const r = spawnSync3("ssh-agent", ["-s"], { encoding: "utf-8" });
|
|
17189
17322
|
if (r.status !== 0) {
|
|
17190
17323
|
console.log("[GitSsh] ssh-agent unavailable; SSH push/pull will need a manually-started agent");
|
|
17191
17324
|
return;
|
|
@@ -17230,19 +17363,19 @@ function parseRemote(url) {
|
|
|
17230
17363
|
}
|
|
17231
17364
|
}
|
|
17232
17365
|
function switchRemoteProtocol(projectRoot, to) {
|
|
17233
|
-
const cur =
|
|
17366
|
+
const cur = spawnSync3("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf-8" });
|
|
17234
17367
|
if (cur.status !== 0)
|
|
17235
17368
|
throw new Error((cur.stderr ?? "no origin remote").trim());
|
|
17236
17369
|
const { host, repoPath } = parseRemote((cur.stdout ?? "").trim());
|
|
17237
17370
|
const next = to === "ssh" ? `git@${host}:${repoPath}` : `https://${host}/${repoPath}`;
|
|
17238
|
-
const set =
|
|
17371
|
+
const set = spawnSync3("git", ["-C", projectRoot, "remote", "set-url", "origin", next], { encoding: "utf-8" });
|
|
17239
17372
|
if (set.status !== 0)
|
|
17240
17373
|
throw new Error((set.stderr ?? "failed to set remote url").trim());
|
|
17241
17374
|
console.log(`[GitSsh] switched origin to ${to}`);
|
|
17242
17375
|
return next;
|
|
17243
17376
|
}
|
|
17244
17377
|
function getRemoteProtocol(projectRoot) {
|
|
17245
|
-
const cur =
|
|
17378
|
+
const cur = spawnSync3("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf-8" });
|
|
17246
17379
|
if (cur.status !== 0)
|
|
17247
17380
|
return { url: "", protocol: "other" };
|
|
17248
17381
|
const url = (cur.stdout ?? "").trim();
|
|
@@ -17256,7 +17389,7 @@ var SSH_DIR, NON_KEY_NAMES, ownedAgentPid, ASKPASS_SCRIPT;
|
|
|
17256
17389
|
var init_git_ssh = __esm({
|
|
17257
17390
|
"packages/server/dist/git-ssh.js"() {
|
|
17258
17391
|
"use strict";
|
|
17259
|
-
SSH_DIR =
|
|
17392
|
+
SSH_DIR = path42.join(os4.homedir(), ".ssh");
|
|
17260
17393
|
NON_KEY_NAMES = /* @__PURE__ */ new Set(["known_hosts", "known_hosts2", "authorized_keys", "config"]);
|
|
17261
17394
|
ownedAgentPid = null;
|
|
17262
17395
|
ASKPASS_SCRIPT = `#!/bin/sh
|
|
@@ -17270,7 +17403,7 @@ printf '%s\\n' "$HALO_SSH_PASSPHRASE"
|
|
|
17270
17403
|
// packages/server/dist/routes/git.js
|
|
17271
17404
|
import { Hono as Hono3 } from "hono";
|
|
17272
17405
|
import os5 from "node:os";
|
|
17273
|
-
import
|
|
17406
|
+
import path43 from "node:path";
|
|
17274
17407
|
function notifyGitChanged(workspacePath) {
|
|
17275
17408
|
broadcastToWorkspace(workspacePath, { type: "file:changed", path: ".git", action: "change" });
|
|
17276
17409
|
}
|
|
@@ -17587,9 +17720,9 @@ function createGitRoutes() {
|
|
|
17587
17720
|
if (!keyPath || typeof passphrase !== "string") {
|
|
17588
17721
|
return c.json({ error: "keyPath and passphrase are required" }, 400);
|
|
17589
17722
|
}
|
|
17590
|
-
const sshDir =
|
|
17591
|
-
const resolved =
|
|
17592
|
-
if (
|
|
17723
|
+
const sshDir = path43.join(os5.homedir(), ".ssh");
|
|
17724
|
+
const resolved = path43.resolve(keyPath);
|
|
17725
|
+
if (path43.dirname(resolved) !== path43.resolve(sshDir)) {
|
|
17593
17726
|
return c.json({ error: "keyPath must be inside ~/.ssh" }, 400);
|
|
17594
17727
|
}
|
|
17595
17728
|
return c.json(unlockSshKey(resolved, passphrase));
|
|
@@ -17665,8 +17798,8 @@ var init_mtime_cache = __esm({
|
|
|
17665
17798
|
|
|
17666
17799
|
// packages/server/dist/routes/agent-configs.js
|
|
17667
17800
|
import { Hono as Hono4 } from "hono";
|
|
17668
|
-
import
|
|
17669
|
-
import
|
|
17801
|
+
import fs36 from "node:fs/promises";
|
|
17802
|
+
import path44 from "node:path";
|
|
17670
17803
|
import { homedir as homedir19 } from "node:os";
|
|
17671
17804
|
import YAML10 from "yaml";
|
|
17672
17805
|
function getAvailableTools() {
|
|
@@ -17677,6 +17810,17 @@ function getAvailableTools() {
|
|
|
17677
17810
|
_cachedTools = [...tools, draft].map((t2) => ({ name: t2.name, description: t2.description }));
|
|
17678
17811
|
return _cachedTools;
|
|
17679
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
|
+
}
|
|
17680
17824
|
function parseAgentYaml(content) {
|
|
17681
17825
|
try {
|
|
17682
17826
|
const data = YAML10.parse(content);
|
|
@@ -17762,29 +17906,29 @@ function defaultAgentYamlTemplate() {
|
|
|
17762
17906
|
}, { lineWidth: 120 });
|
|
17763
17907
|
}
|
|
17764
17908
|
async function ensureDir2(dir) {
|
|
17765
|
-
await
|
|
17909
|
+
await fs36.mkdir(dir, { recursive: true });
|
|
17766
17910
|
}
|
|
17767
17911
|
async function scanAgentsDir(dir, scope) {
|
|
17768
17912
|
const agents = [];
|
|
17769
17913
|
let names;
|
|
17770
17914
|
try {
|
|
17771
|
-
names = await
|
|
17915
|
+
names = await fs36.readdir(dir);
|
|
17772
17916
|
} catch {
|
|
17773
17917
|
return agents;
|
|
17774
17918
|
}
|
|
17775
17919
|
for (const entryName of names) {
|
|
17776
|
-
const agentDir =
|
|
17920
|
+
const agentDir = path44.join(dir, entryName);
|
|
17777
17921
|
try {
|
|
17778
|
-
const stat = await
|
|
17922
|
+
const stat = await fs36.stat(agentDir);
|
|
17779
17923
|
if (!stat.isDirectory())
|
|
17780
17924
|
continue;
|
|
17781
17925
|
} catch {
|
|
17782
17926
|
continue;
|
|
17783
17927
|
}
|
|
17784
|
-
const yamlPath =
|
|
17928
|
+
const yamlPath = path44.join(agentDir, "agent.yaml");
|
|
17785
17929
|
try {
|
|
17786
|
-
const yamlStat = await
|
|
17787
|
-
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")));
|
|
17788
17932
|
const { name, description, model, priority, tools, skills, internal } = parsed;
|
|
17789
17933
|
agents.push({
|
|
17790
17934
|
id: entryName,
|
|
@@ -17805,13 +17949,13 @@ async function scanAgentsDir(dir, scope) {
|
|
|
17805
17949
|
return agents;
|
|
17806
17950
|
}
|
|
17807
17951
|
async function ensureDefaultAgent(dir) {
|
|
17808
|
-
const defaultDir =
|
|
17809
|
-
const yamlPath =
|
|
17952
|
+
const defaultDir = path44.join(dir, "default");
|
|
17953
|
+
const yamlPath = path44.join(defaultDir, "agent.yaml");
|
|
17810
17954
|
try {
|
|
17811
|
-
await
|
|
17955
|
+
await fs36.access(yamlPath);
|
|
17812
17956
|
} catch {
|
|
17813
17957
|
await ensureDir2(defaultDir);
|
|
17814
|
-
await
|
|
17958
|
+
await fs36.writeFile(yamlPath, defaultAgentYamlTemplate(), "utf-8");
|
|
17815
17959
|
}
|
|
17816
17960
|
}
|
|
17817
17961
|
function createAgentConfigRoutes() {
|
|
@@ -17829,7 +17973,7 @@ function createAgentConfigRoutes() {
|
|
|
17829
17973
|
const projectId = c.req.query("projectId");
|
|
17830
17974
|
let workspaceAgents = [];
|
|
17831
17975
|
if (projectId) {
|
|
17832
|
-
const wsAgentsDir =
|
|
17976
|
+
const wsAgentsDir = path44.join(projectId, ".halo", "agents");
|
|
17833
17977
|
workspaceAgents = await scanAgentsDir(wsAgentsDir, "workspace");
|
|
17834
17978
|
}
|
|
17835
17979
|
const wsIds = new Set(workspaceAgents.map((a) => a.id));
|
|
@@ -17861,33 +18005,33 @@ function createAgentConfigRoutes() {
|
|
|
17861
18005
|
if (scope === "workspace") {
|
|
17862
18006
|
if (!body.projectId)
|
|
17863
18007
|
return c.json({ error: "projectId required for workspace agents" }, 400);
|
|
17864
|
-
baseDir =
|
|
18008
|
+
baseDir = path44.join(body.projectId, ".halo", "agents");
|
|
17865
18009
|
} else {
|
|
17866
18010
|
baseDir = GLOBAL_AGENTS_DIR4;
|
|
17867
18011
|
}
|
|
17868
|
-
const agentDir =
|
|
18012
|
+
const agentDir = path44.join(baseDir, id);
|
|
17869
18013
|
try {
|
|
17870
|
-
await
|
|
18014
|
+
await fs36.access(agentDir);
|
|
17871
18015
|
return c.json({ error: "Agent already exists" }, 409);
|
|
17872
18016
|
} catch {
|
|
17873
18017
|
}
|
|
17874
18018
|
let conflictScope = null;
|
|
17875
18019
|
if (scope === "workspace") {
|
|
17876
18020
|
try {
|
|
17877
|
-
await
|
|
18021
|
+
await fs36.access(path44.join(GLOBAL_AGENTS_DIR4, id));
|
|
17878
18022
|
conflictScope = "global";
|
|
17879
18023
|
} catch {
|
|
17880
18024
|
}
|
|
17881
18025
|
} else if (body.projectId) {
|
|
17882
18026
|
try {
|
|
17883
|
-
await
|
|
18027
|
+
await fs36.access(path44.join(body.projectId, ".halo", "agents", id));
|
|
17884
18028
|
conflictScope = "workspace";
|
|
17885
18029
|
} catch {
|
|
17886
18030
|
}
|
|
17887
18031
|
}
|
|
17888
18032
|
await ensureDir2(agentDir);
|
|
17889
|
-
await
|
|
17890
|
-
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}
|
|
17891
18035
|
|
|
17892
18036
|
${body.description ?? ""}
|
|
17893
18037
|
`, "utf-8");
|
|
@@ -17901,13 +18045,13 @@ ${body.description ?? ""}
|
|
|
17901
18045
|
const projectId = c.req.query("projectId");
|
|
17902
18046
|
let agentDir;
|
|
17903
18047
|
if (scope === "workspace" && projectId) {
|
|
17904
|
-
agentDir =
|
|
18048
|
+
agentDir = path44.join(projectId, ".halo", "agents", id);
|
|
17905
18049
|
} else {
|
|
17906
|
-
agentDir =
|
|
18050
|
+
agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
|
|
17907
18051
|
}
|
|
17908
|
-
const yamlPath =
|
|
18052
|
+
const yamlPath = path44.join(agentDir, "agent.yaml");
|
|
17909
18053
|
try {
|
|
17910
|
-
const content = await
|
|
18054
|
+
const content = await fs36.readFile(yamlPath, "utf-8");
|
|
17911
18055
|
return c.json({ yaml: content });
|
|
17912
18056
|
} catch {
|
|
17913
18057
|
return c.json({ error: "Agent not found" }, 404);
|
|
@@ -17924,17 +18068,17 @@ ${body.description ?? ""}
|
|
|
17924
18068
|
const scope = body.scope ?? "global";
|
|
17925
18069
|
let agentDir;
|
|
17926
18070
|
if (scope === "workspace" && body.projectId) {
|
|
17927
|
-
agentDir =
|
|
18071
|
+
agentDir = path44.join(body.projectId, ".halo", "agents", id);
|
|
17928
18072
|
} else {
|
|
17929
|
-
agentDir =
|
|
18073
|
+
agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
|
|
17930
18074
|
}
|
|
17931
|
-
const yamlPath =
|
|
18075
|
+
const yamlPath = path44.join(agentDir, "agent.yaml");
|
|
17932
18076
|
try {
|
|
17933
|
-
await
|
|
18077
|
+
await fs36.access(agentDir);
|
|
17934
18078
|
} catch {
|
|
17935
18079
|
return c.json({ error: "Agent not found" }, 404);
|
|
17936
18080
|
}
|
|
17937
|
-
await
|
|
18081
|
+
await writeFileAtomic2(yamlPath, body.yaml);
|
|
17938
18082
|
const { name, description, model } = parseAgentYaml(body.yaml);
|
|
17939
18083
|
return c.json({ agent: { id, name, description, model, path: agentDir, scope } });
|
|
17940
18084
|
});
|
|
@@ -17946,12 +18090,12 @@ ${body.description ?? ""}
|
|
|
17946
18090
|
const projectId = c.req.query("projectId");
|
|
17947
18091
|
let agentDir;
|
|
17948
18092
|
if (scope === "workspace" && projectId) {
|
|
17949
|
-
agentDir =
|
|
18093
|
+
agentDir = path44.join(projectId, ".halo", "agents", id);
|
|
17950
18094
|
} else {
|
|
17951
|
-
agentDir =
|
|
18095
|
+
agentDir = path44.join(GLOBAL_AGENTS_DIR4, id);
|
|
17952
18096
|
}
|
|
17953
18097
|
try {
|
|
17954
|
-
await
|
|
18098
|
+
await fs36.access(agentDir);
|
|
17955
18099
|
} catch {
|
|
17956
18100
|
return c.json({ error: "Agent not found" }, 404);
|
|
17957
18101
|
}
|
|
@@ -17961,7 +18105,7 @@ ${body.description ?? ""}
|
|
|
17961
18105
|
return c.json({ error: "Cannot delete the last global agent. At least one global agent must remain." }, 400);
|
|
17962
18106
|
}
|
|
17963
18107
|
}
|
|
17964
|
-
await
|
|
18108
|
+
await fs36.rm(agentDir, { recursive: true, force: true });
|
|
17965
18109
|
return c.json({ ok: true });
|
|
17966
18110
|
});
|
|
17967
18111
|
app2.patch("/agent-configs/:id/toggle", async (c) => {
|
|
@@ -17993,7 +18137,7 @@ ${body.description ?? ""}
|
|
|
17993
18137
|
if (!filePath)
|
|
17994
18138
|
return c.json({ content: "", exists: false, readOnly: !isMdWritable(fileType) });
|
|
17995
18139
|
try {
|
|
17996
|
-
const content = await
|
|
18140
|
+
const content = await fs36.readFile(filePath, "utf-8");
|
|
17997
18141
|
return c.json({ content, exists: true, path: filePath, readOnly: !isMdWritable(fileType) });
|
|
17998
18142
|
} catch {
|
|
17999
18143
|
return c.json({ content: "", exists: false, path: filePath, readOnly: !isMdWritable(fileType) });
|
|
@@ -18026,7 +18170,7 @@ ${body.description ?? ""}
|
|
|
18026
18170
|
continue;
|
|
18027
18171
|
}
|
|
18028
18172
|
try {
|
|
18029
|
-
const content = await
|
|
18173
|
+
const content = await fs36.readFile(filePath, "utf-8");
|
|
18030
18174
|
result[ft] = { content, exists: true, path: filePath, readOnly: !isMdWritable(ft) };
|
|
18031
18175
|
} catch {
|
|
18032
18176
|
result[ft] = { content: "", exists: false, path: filePath, readOnly: !isMdWritable(ft) };
|
|
@@ -18036,10 +18180,10 @@ ${body.description ?? ""}
|
|
|
18036
18180
|
});
|
|
18037
18181
|
function getSessionsDir(agentId, _source, projectId) {
|
|
18038
18182
|
if (agentId.startsWith("__") && agentId.endsWith("__")) {
|
|
18039
|
-
return
|
|
18183
|
+
return path44.join(homedir19(), ".halo", "global", "internal-sessions", agentId);
|
|
18040
18184
|
}
|
|
18041
|
-
const base = projectId ?
|
|
18042
|
-
return
|
|
18185
|
+
const base = projectId ? path44.join(projectId, ".halo") : path44.join(homedir19(), ".halo");
|
|
18186
|
+
return path44.join(base, "sessions", agentId);
|
|
18043
18187
|
}
|
|
18044
18188
|
app2.get("/agent-configs/:id/sessions", async (c) => {
|
|
18045
18189
|
const agentId = c.req.param("id");
|
|
@@ -18050,7 +18194,7 @@ ${body.description ?? ""}
|
|
|
18050
18194
|
const dir = getSessionsDir(agentId, source, projectId || void 0);
|
|
18051
18195
|
let files;
|
|
18052
18196
|
try {
|
|
18053
|
-
files = await
|
|
18197
|
+
files = await fs36.readdir(dir);
|
|
18054
18198
|
} catch {
|
|
18055
18199
|
return c.json({ sessions: [] });
|
|
18056
18200
|
}
|
|
@@ -18059,7 +18203,7 @@ ${body.description ?? ""}
|
|
|
18059
18203
|
if (!f.endsWith(".json"))
|
|
18060
18204
|
continue;
|
|
18061
18205
|
try {
|
|
18062
|
-
const raw = await
|
|
18206
|
+
const raw = await fs36.readFile(path44.join(dir, f), "utf-8");
|
|
18063
18207
|
const session = JSON.parse(raw);
|
|
18064
18208
|
sessions.push({
|
|
18065
18209
|
id: session.id,
|
|
@@ -18088,9 +18232,9 @@ ${body.description ?? ""}
|
|
|
18088
18232
|
const projectId = c.req.query("projectId");
|
|
18089
18233
|
const source = c.req.query("source") || "test-chat";
|
|
18090
18234
|
const dir = getSessionsDir(agentId, source, projectId || void 0);
|
|
18091
|
-
const filePath =
|
|
18235
|
+
const filePath = path44.join(dir, `${sessionId}.json`);
|
|
18092
18236
|
try {
|
|
18093
|
-
const raw = await
|
|
18237
|
+
const raw = await fs36.readFile(filePath, "utf-8");
|
|
18094
18238
|
return c.json({ session: JSON.parse(raw) });
|
|
18095
18239
|
} catch {
|
|
18096
18240
|
return c.json({ error: "Session not found" }, 404);
|
|
@@ -18124,7 +18268,7 @@ ${body.description ?? ""}
|
|
|
18124
18268
|
agentSnapshot: body.agentSnapshot ?? {},
|
|
18125
18269
|
messages: messages2
|
|
18126
18270
|
};
|
|
18127
|
-
await
|
|
18271
|
+
await fs36.writeFile(path44.join(dir, `${sessionId}.json`), JSON.stringify(session, null, 2), "utf-8");
|
|
18128
18272
|
return c.json({ id: sessionId, session: { ...session, messages: void 0 } });
|
|
18129
18273
|
});
|
|
18130
18274
|
app2.delete("/agent-configs/:id/sessions", async (c) => {
|
|
@@ -18136,11 +18280,11 @@ ${body.description ?? ""}
|
|
|
18136
18280
|
const dir = getSessionsDir(agentId, source, projectId || void 0);
|
|
18137
18281
|
let deleted = 0;
|
|
18138
18282
|
try {
|
|
18139
|
-
const files = await
|
|
18283
|
+
const files = await fs36.readdir(dir);
|
|
18140
18284
|
for (const f of files) {
|
|
18141
18285
|
if (!f.endsWith(".json"))
|
|
18142
18286
|
continue;
|
|
18143
|
-
await
|
|
18287
|
+
await fs36.rm(path44.join(dir, f));
|
|
18144
18288
|
deleted++;
|
|
18145
18289
|
}
|
|
18146
18290
|
} catch {
|
|
@@ -18157,7 +18301,7 @@ ${body.description ?? ""}
|
|
|
18157
18301
|
const source = c.req.query("source") || "test-chat";
|
|
18158
18302
|
const dir = getSessionsDir(agentId, source, projectId || void 0);
|
|
18159
18303
|
try {
|
|
18160
|
-
await
|
|
18304
|
+
await fs36.rm(path44.join(dir, `${sessionId}.json`));
|
|
18161
18305
|
return c.json({ ok: true });
|
|
18162
18306
|
} catch {
|
|
18163
18307
|
return c.json({ error: "Session not found" }, 404);
|
|
@@ -18177,7 +18321,7 @@ var init_agent_configs = __esm({
|
|
|
18177
18321
|
init_workspace_path();
|
|
18178
18322
|
init_mtime_cache();
|
|
18179
18323
|
_cachedTools = null;
|
|
18180
|
-
GLOBAL_AGENTS_DIR4 =
|
|
18324
|
+
GLOBAL_AGENTS_DIR4 = path44.join(homedir19(), ".halo", "global", "agents");
|
|
18181
18325
|
FALLBACK_SCAFFOLD_PROVIDER = "aws-bedrock-claude-invoke";
|
|
18182
18326
|
_yamlCache = createMtimeCache();
|
|
18183
18327
|
}
|
|
@@ -18185,8 +18329,8 @@ var init_agent_configs = __esm({
|
|
|
18185
18329
|
|
|
18186
18330
|
// packages/server/dist/routes/skills.js
|
|
18187
18331
|
import { Hono as Hono5 } from "hono";
|
|
18188
|
-
import
|
|
18189
|
-
import
|
|
18332
|
+
import fs37 from "node:fs/promises";
|
|
18333
|
+
import path45 from "node:path";
|
|
18190
18334
|
import { homedir as homedir20 } from "node:os";
|
|
18191
18335
|
import YAML11 from "yaml";
|
|
18192
18336
|
function defaultSkillSettings() {
|
|
@@ -18205,20 +18349,20 @@ function defaultSkillSettings() {
|
|
|
18205
18349
|
}
|
|
18206
18350
|
async function readYamlFile3(filePath) {
|
|
18207
18351
|
try {
|
|
18208
|
-
const content = await
|
|
18352
|
+
const content = await fs37.readFile(filePath, "utf-8");
|
|
18209
18353
|
return YAML11.parse(content) ?? {};
|
|
18210
18354
|
} catch {
|
|
18211
18355
|
try {
|
|
18212
18356
|
const jsonPath = filePath.replace(/\.yaml$/, ".json");
|
|
18213
|
-
return JSON.parse(await
|
|
18357
|
+
return JSON.parse(await fs37.readFile(jsonPath, "utf-8"));
|
|
18214
18358
|
} catch {
|
|
18215
18359
|
return {};
|
|
18216
18360
|
}
|
|
18217
18361
|
}
|
|
18218
18362
|
}
|
|
18219
18363
|
async function writeYamlFile(filePath, data) {
|
|
18220
|
-
await
|
|
18221
|
-
await
|
|
18364
|
+
await fs37.mkdir(path45.dirname(filePath), { recursive: true });
|
|
18365
|
+
await fs37.writeFile(filePath, YAML11.stringify(data, { lineWidth: 120 }), "utf-8");
|
|
18222
18366
|
}
|
|
18223
18367
|
async function addSkillSettings(settingsPath, skillId) {
|
|
18224
18368
|
const settings = await readYamlFile3(settingsPath);
|
|
@@ -18241,7 +18385,7 @@ async function removeSkillSettings(settingsPath, skillId) {
|
|
|
18241
18385
|
}
|
|
18242
18386
|
}
|
|
18243
18387
|
async function ensureDir3(dir) {
|
|
18244
|
-
await
|
|
18388
|
+
await fs37.mkdir(dir, { recursive: true });
|
|
18245
18389
|
}
|
|
18246
18390
|
function toSkillMd(name, description) {
|
|
18247
18391
|
return `---
|
|
@@ -18258,32 +18402,32 @@ async function scanSkillsDir(dir, scope) {
|
|
|
18258
18402
|
const skills = [];
|
|
18259
18403
|
let names;
|
|
18260
18404
|
try {
|
|
18261
|
-
names = await
|
|
18405
|
+
names = await fs37.readdir(dir);
|
|
18262
18406
|
} catch {
|
|
18263
18407
|
return skills;
|
|
18264
18408
|
}
|
|
18265
18409
|
for (const entryName of names) {
|
|
18266
|
-
const skillDir =
|
|
18410
|
+
const skillDir = path45.join(dir, entryName);
|
|
18267
18411
|
try {
|
|
18268
|
-
const stat = await
|
|
18412
|
+
const stat = await fs37.stat(skillDir);
|
|
18269
18413
|
if (!stat.isDirectory())
|
|
18270
18414
|
continue;
|
|
18271
18415
|
} catch {
|
|
18272
18416
|
continue;
|
|
18273
18417
|
}
|
|
18274
|
-
const skillMdPath =
|
|
18418
|
+
const skillMdPath = path45.join(skillDir, "SKILL.md");
|
|
18275
18419
|
try {
|
|
18276
|
-
const mdStat = await
|
|
18420
|
+
const mdStat = await fs37.stat(skillMdPath);
|
|
18277
18421
|
let meta = _skillMdCache.get(skillMdPath, mdStat.mtimeMs);
|
|
18278
18422
|
if (!meta) {
|
|
18279
|
-
const { name, description, command } = parseSkillFrontmatter(await
|
|
18423
|
+
const { name, description, command } = parseSkillFrontmatter(await fs37.readFile(skillMdPath, "utf-8"));
|
|
18280
18424
|
meta = _skillMdCache.set(skillMdPath, mdStat.mtimeMs, { name: name || entryName, description: description ?? "", command });
|
|
18281
18425
|
}
|
|
18282
18426
|
skills.push({ id: entryName, name: meta.name, description: meta.description, path: skillDir, scope, command: meta.command });
|
|
18283
18427
|
} catch {
|
|
18284
|
-
const yamlPath =
|
|
18428
|
+
const yamlPath = path45.join(skillDir, "skill.yaml");
|
|
18285
18429
|
try {
|
|
18286
|
-
const yamlContent = await
|
|
18430
|
+
const yamlContent = await fs37.readFile(yamlPath, "utf-8");
|
|
18287
18431
|
const m = yamlContent.match(/^name:\s*(.+)$/m);
|
|
18288
18432
|
const d = yamlContent.match(/^description:\s*(.+)$/m);
|
|
18289
18433
|
skills.push({ id: entryName, name: m?.[1]?.trim() || entryName, description: d?.[1]?.trim() || "", path: skillDir, scope });
|
|
@@ -18302,7 +18446,7 @@ function createSkillRoutes() {
|
|
|
18302
18446
|
const projectId = c.req.query("projectId");
|
|
18303
18447
|
let workspaceSkills = [];
|
|
18304
18448
|
if (projectId) {
|
|
18305
|
-
const wsSkillsDir =
|
|
18449
|
+
const wsSkillsDir = path45.join(projectId, ".halo", "skills");
|
|
18306
18450
|
workspaceSkills = await scanSkillsDir(wsSkillsDir, "workspace");
|
|
18307
18451
|
}
|
|
18308
18452
|
const wsIds = new Set(workspaceSkills.map((s) => s.id));
|
|
@@ -18325,19 +18469,19 @@ function createSkillRoutes() {
|
|
|
18325
18469
|
if (scope === "workspace") {
|
|
18326
18470
|
if (!body.projectId)
|
|
18327
18471
|
return c.json({ error: "projectId required for workspace skills" }, 400);
|
|
18328
|
-
baseDir =
|
|
18472
|
+
baseDir = path45.join(body.projectId, ".halo", "skills");
|
|
18329
18473
|
} else {
|
|
18330
18474
|
baseDir = GLOBAL_SKILLS_DIR2;
|
|
18331
18475
|
}
|
|
18332
|
-
const skillDir =
|
|
18476
|
+
const skillDir = path45.join(baseDir, id);
|
|
18333
18477
|
try {
|
|
18334
|
-
await
|
|
18478
|
+
await fs37.access(skillDir);
|
|
18335
18479
|
return c.json({ error: "Skill already exists" }, 409);
|
|
18336
18480
|
} catch {
|
|
18337
18481
|
}
|
|
18338
18482
|
await ensureDir3(skillDir);
|
|
18339
|
-
await
|
|
18340
|
-
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;
|
|
18341
18485
|
await addSkillSettings(settingsPath, id);
|
|
18342
18486
|
const skill = { id, name: body.name, description: body.description ?? "", path: skillDir, scope };
|
|
18343
18487
|
return c.json({ skill }, 201);
|
|
@@ -18350,17 +18494,17 @@ function createSkillRoutes() {
|
|
|
18350
18494
|
const projectId = c.req.query("projectId");
|
|
18351
18495
|
let skillDir;
|
|
18352
18496
|
if (scope === "workspace" && projectId) {
|
|
18353
|
-
skillDir =
|
|
18497
|
+
skillDir = path45.join(projectId, ".halo", "skills", id);
|
|
18354
18498
|
} else {
|
|
18355
|
-
skillDir =
|
|
18499
|
+
skillDir = path45.join(GLOBAL_SKILLS_DIR2, id);
|
|
18356
18500
|
}
|
|
18357
18501
|
try {
|
|
18358
|
-
await
|
|
18502
|
+
await fs37.access(skillDir);
|
|
18359
18503
|
} catch {
|
|
18360
18504
|
return c.json({ error: "Skill not found" }, 404);
|
|
18361
18505
|
}
|
|
18362
|
-
await
|
|
18363
|
-
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;
|
|
18364
18508
|
await removeSkillSettings(settingsPath, id);
|
|
18365
18509
|
return c.json({ ok: true });
|
|
18366
18510
|
});
|
|
@@ -18384,30 +18528,30 @@ var init_skills = __esm({
|
|
|
18384
18528
|
init_db();
|
|
18385
18529
|
init_workspace_path();
|
|
18386
18530
|
init_mtime_cache();
|
|
18387
|
-
GLOBAL_SETTINGS_PATH =
|
|
18531
|
+
GLOBAL_SETTINGS_PATH = path45.join(homedir20(), ".halo", "secrets", "settings.yaml");
|
|
18388
18532
|
_skillMdCache = createMtimeCache();
|
|
18389
18533
|
}
|
|
18390
18534
|
});
|
|
18391
18535
|
|
|
18392
18536
|
// packages/server/dist/routes/settings.js
|
|
18393
18537
|
import { Hono as Hono6 } from "hono";
|
|
18394
|
-
import
|
|
18395
|
-
import
|
|
18538
|
+
import fs38 from "node:fs/promises";
|
|
18539
|
+
import path46 from "node:path";
|
|
18396
18540
|
import { homedir as homedir21 } from "node:os";
|
|
18397
18541
|
import YAML12 from "yaml";
|
|
18398
18542
|
async function readSettingsFile(filePath) {
|
|
18399
18543
|
try {
|
|
18400
|
-
const content = await
|
|
18544
|
+
const content = await fs38.readFile(filePath, "utf-8");
|
|
18401
18545
|
return YAML12.parse(content) ?? {};
|
|
18402
18546
|
} catch {
|
|
18403
18547
|
return {};
|
|
18404
18548
|
}
|
|
18405
18549
|
}
|
|
18406
18550
|
async function writeSettingsFile(filePath, data) {
|
|
18407
|
-
await
|
|
18408
|
-
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 });
|
|
18409
18553
|
try {
|
|
18410
|
-
await
|
|
18554
|
+
await fs38.chmod(filePath, 384);
|
|
18411
18555
|
} catch {
|
|
18412
18556
|
}
|
|
18413
18557
|
}
|
|
@@ -18521,8 +18665,8 @@ function detectOrphans(tree, declared) {
|
|
|
18521
18665
|
if (!block || typeof block !== "object")
|
|
18522
18666
|
continue;
|
|
18523
18667
|
for (const k of Object.keys(block)) {
|
|
18524
|
-
const
|
|
18525
|
-
if (!declaredMap.has(
|
|
18668
|
+
const path66 = `${namespaceId}.${kindWord}.${k}`;
|
|
18669
|
+
if (!declaredMap.has(path66)) {
|
|
18526
18670
|
out.push({ namespaceId, kind: kindWord === "params" ? "param" : "secret", key: k });
|
|
18527
18671
|
}
|
|
18528
18672
|
}
|
|
@@ -18537,7 +18681,7 @@ function createSettingsRoutes() {
|
|
|
18537
18681
|
const globalTree = await readSettingsFile(GLOBAL_SETTINGS_PATH2);
|
|
18538
18682
|
let workspaceTree = null;
|
|
18539
18683
|
if (projectId) {
|
|
18540
|
-
workspaceTree = await readSettingsFile(
|
|
18684
|
+
workspaceTree = await readSettingsFile(path46.join(projectId, ".halo", "settings.yaml"));
|
|
18541
18685
|
}
|
|
18542
18686
|
const declared = loadSettingsSchema(projectId ?? void 0);
|
|
18543
18687
|
const sections = resolveSections(declared, globalTree, workspaceTree);
|
|
@@ -18587,7 +18731,7 @@ function createSettingsRoutes() {
|
|
|
18587
18731
|
const offending = rejectGlobalOnlyAtWorkspace("workspace", flattenDotted(body.settings));
|
|
18588
18732
|
if (offending)
|
|
18589
18733
|
return c.json({ error: `${offending} is global-only and cannot be set per workspace` }, 400);
|
|
18590
|
-
const wsPath =
|
|
18734
|
+
const wsPath = path46.join(projectPath, ".halo", "settings.yaml");
|
|
18591
18735
|
await writeSettingsFile(wsPath, body.settings);
|
|
18592
18736
|
} else {
|
|
18593
18737
|
await writeSettingsFile(GLOBAL_SETTINGS_PATH2, body.settings);
|
|
@@ -18607,7 +18751,7 @@ function createSettingsRoutes() {
|
|
|
18607
18751
|
const offending = rejectGlobalOnlyAtWorkspace("workspace", [body.key]);
|
|
18608
18752
|
if (offending)
|
|
18609
18753
|
return c.json({ error: `${offending} is global-only and cannot be set per workspace` }, 400);
|
|
18610
|
-
filePath =
|
|
18754
|
+
filePath = path46.join(projectPath, ".halo", "settings.yaml");
|
|
18611
18755
|
} else {
|
|
18612
18756
|
filePath = GLOBAL_SETTINGS_PATH2;
|
|
18613
18757
|
}
|
|
@@ -18636,7 +18780,7 @@ function createSettingsRoutes() {
|
|
|
18636
18780
|
const projectPath = await resolveProjectPath(body.projectId);
|
|
18637
18781
|
if (!projectPath)
|
|
18638
18782
|
return c.json({ error: "Project not found" }, 404);
|
|
18639
|
-
filePath =
|
|
18783
|
+
filePath = path46.join(projectPath, ".halo", "settings.yaml");
|
|
18640
18784
|
} else {
|
|
18641
18785
|
filePath = GLOBAL_SETTINGS_PATH2;
|
|
18642
18786
|
}
|
|
@@ -18662,39 +18806,39 @@ var init_settings = __esm({
|
|
|
18662
18806
|
"use strict";
|
|
18663
18807
|
init_settings_schema();
|
|
18664
18808
|
init_workspace_path();
|
|
18665
|
-
GLOBAL_SETTINGS_PATH2 =
|
|
18809
|
+
GLOBAL_SETTINGS_PATH2 = path46.join(homedir21(), ".halo", "secrets", "settings.yaml");
|
|
18666
18810
|
_changeListeners = [];
|
|
18667
18811
|
}
|
|
18668
18812
|
});
|
|
18669
18813
|
|
|
18670
18814
|
// packages/server/dist/evolution/archive.js
|
|
18671
|
-
import
|
|
18672
|
-
import
|
|
18815
|
+
import fs39 from "node:fs";
|
|
18816
|
+
import path47 from "node:path";
|
|
18673
18817
|
import { pipeline } from "node:stream/promises";
|
|
18674
18818
|
import JSZip from "jszip";
|
|
18675
18819
|
import { eq as eq7, and as and5, isNull as isNull4, lte, isNotNull as isNotNull3, lt as lt2 } from "drizzle-orm";
|
|
18676
18820
|
async function zipDir(srcDir, outZip) {
|
|
18677
|
-
if (!
|
|
18821
|
+
if (!fs39.existsSync(srcDir))
|
|
18678
18822
|
return false;
|
|
18679
|
-
|
|
18823
|
+
fs39.mkdirSync(path47.dirname(outZip), { recursive: true });
|
|
18680
18824
|
const zip = new JSZip();
|
|
18681
18825
|
const walk = (dir, rel) => {
|
|
18682
|
-
const entries =
|
|
18826
|
+
const entries = fs39.readdirSync(dir, { withFileTypes: true });
|
|
18683
18827
|
if (entries.length === 0 && rel) {
|
|
18684
18828
|
zip.folder(rel);
|
|
18685
18829
|
return;
|
|
18686
18830
|
}
|
|
18687
18831
|
for (const e of entries) {
|
|
18688
|
-
const abs =
|
|
18832
|
+
const abs = path47.join(dir, e.name);
|
|
18689
18833
|
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
18690
18834
|
if (e.isDirectory()) {
|
|
18691
18835
|
walk(abs, childRel);
|
|
18692
18836
|
} else if (e.isFile()) {
|
|
18693
|
-
zip.file(childRel,
|
|
18837
|
+
zip.file(childRel, fs39.readFileSync(abs), { date: fs39.statSync(abs).mtime });
|
|
18694
18838
|
} else if (e.isSymbolicLink()) {
|
|
18695
18839
|
try {
|
|
18696
|
-
if (
|
|
18697
|
-
zip.file(childRel,
|
|
18840
|
+
if (fs39.statSync(abs).isFile())
|
|
18841
|
+
zip.file(childRel, fs39.readFileSync(abs));
|
|
18698
18842
|
} catch {
|
|
18699
18843
|
}
|
|
18700
18844
|
}
|
|
@@ -18702,7 +18846,7 @@ async function zipDir(srcDir, outZip) {
|
|
|
18702
18846
|
};
|
|
18703
18847
|
try {
|
|
18704
18848
|
walk(srcDir, "");
|
|
18705
|
-
await pipeline(zip.generateNodeStream({ type: "nodebuffer", streamFiles: true, compression: "DEFLATE" }),
|
|
18849
|
+
await pipeline(zip.generateNodeStream({ type: "nodebuffer", streamFiles: true, compression: "DEFLATE" }), fs39.createWriteStream(outZip));
|
|
18706
18850
|
return true;
|
|
18707
18851
|
} catch (err) {
|
|
18708
18852
|
rmFile(outZip);
|
|
@@ -18712,20 +18856,20 @@ async function zipDir(srcDir, outZip) {
|
|
|
18712
18856
|
}
|
|
18713
18857
|
function rmDir(dir) {
|
|
18714
18858
|
try {
|
|
18715
|
-
|
|
18859
|
+
fs39.rmSync(dir, { recursive: true, force: true });
|
|
18716
18860
|
} catch {
|
|
18717
18861
|
}
|
|
18718
18862
|
}
|
|
18719
18863
|
function rmFile(p) {
|
|
18720
18864
|
try {
|
|
18721
|
-
|
|
18865
|
+
fs39.rmSync(p, { force: true });
|
|
18722
18866
|
} catch {
|
|
18723
18867
|
}
|
|
18724
18868
|
}
|
|
18725
18869
|
async function archiveRun(workspacePath, runId, errors) {
|
|
18726
18870
|
const runDir = wsEvoRunDir(workspacePath, runId);
|
|
18727
18871
|
const outZip = wsEvoArchivedRunZip(workspacePath, runId);
|
|
18728
|
-
if (!
|
|
18872
|
+
if (!fs39.existsSync(runDir)) {
|
|
18729
18873
|
return true;
|
|
18730
18874
|
}
|
|
18731
18875
|
if (!await zipDir(runDir, outZip)) {
|
|
@@ -18738,7 +18882,7 @@ async function archiveRun(workspacePath, runId, errors) {
|
|
|
18738
18882
|
async function archiveApply(workspacePath, applyId, errors) {
|
|
18739
18883
|
const applyDir = wsEvoApplyDir(workspacePath, applyId);
|
|
18740
18884
|
const outZip = wsEvoArchivedApplyZip(workspacePath, applyId);
|
|
18741
|
-
if (!
|
|
18885
|
+
if (!fs39.existsSync(applyDir))
|
|
18742
18886
|
return true;
|
|
18743
18887
|
if (!await zipDir(applyDir, outZip)) {
|
|
18744
18888
|
errors.push(`zip failed for apply ${applyId}`);
|
|
@@ -18865,8 +19009,8 @@ var init_raw_sqlite = __esm({
|
|
|
18865
19009
|
// packages/server/dist/routes/evolution.js
|
|
18866
19010
|
import { Hono as Hono7 } from "hono";
|
|
18867
19011
|
import { eq as eq8, desc as desc2, lt as lt3, and as and6, or, sql } from "drizzle-orm";
|
|
18868
|
-
import
|
|
18869
|
-
import
|
|
19012
|
+
import fs40 from "node:fs/promises";
|
|
19013
|
+
import path48 from "node:path";
|
|
18870
19014
|
function createEvolutionRoutes() {
|
|
18871
19015
|
const router = new Hono7();
|
|
18872
19016
|
router.get("/evolution/runs", async (c) => {
|
|
@@ -18931,33 +19075,33 @@ function createEvolutionRoutes() {
|
|
|
18931
19075
|
const dir = wsEvoRunDir(row.workspacePath, id);
|
|
18932
19076
|
let patchMd = null;
|
|
18933
19077
|
try {
|
|
18934
|
-
patchMd = await
|
|
19078
|
+
patchMd = await fs40.readFile(path48.join(dir, "patch.md"), "utf-8");
|
|
18935
19079
|
} catch {
|
|
18936
19080
|
}
|
|
18937
19081
|
let scoreJson = null;
|
|
18938
19082
|
try {
|
|
18939
|
-
const raw = await
|
|
19083
|
+
const raw = await fs40.readFile(path48.join(dir, "score.json"), "utf-8");
|
|
18940
19084
|
scoreJson = JSON.parse(raw);
|
|
18941
19085
|
} catch {
|
|
18942
19086
|
}
|
|
18943
19087
|
let wrapperLog = null;
|
|
18944
19088
|
try {
|
|
18945
|
-
wrapperLog = await
|
|
19089
|
+
wrapperLog = await fs40.readFile(evoWrapperLogFile(id), "utf-8");
|
|
18946
19090
|
} catch {
|
|
18947
19091
|
}
|
|
18948
19092
|
let subCliLog = null;
|
|
18949
19093
|
try {
|
|
18950
|
-
subCliLog = await
|
|
19094
|
+
subCliLog = await fs40.readFile(path48.join(dir, "sub-cli.log"), "utf-8");
|
|
18951
19095
|
} catch {
|
|
18952
19096
|
}
|
|
18953
19097
|
let skipReasonMd = null;
|
|
18954
19098
|
try {
|
|
18955
|
-
skipReasonMd = await
|
|
19099
|
+
skipReasonMd = await fs40.readFile(path48.join(dir, ".skip.md"), "utf-8");
|
|
18956
19100
|
} catch {
|
|
18957
19101
|
}
|
|
18958
19102
|
let snapshotSummary = null;
|
|
18959
19103
|
try {
|
|
18960
|
-
const raw = await
|
|
19104
|
+
const raw = await fs40.readFile(path48.join(dir, "source-snapshot.json"), "utf-8");
|
|
18961
19105
|
const parsed = JSON.parse(raw);
|
|
18962
19106
|
const msgs = parsed.rawMessages ?? [];
|
|
18963
19107
|
const firstUser = msgs.find((m) => m.role === "user");
|
|
@@ -19445,12 +19589,12 @@ var init_session_archive2 = __esm({
|
|
|
19445
19589
|
import Database4 from "better-sqlite3";
|
|
19446
19590
|
import { drizzle as drizzle3 } from "drizzle-orm/better-sqlite3";
|
|
19447
19591
|
import { sqliteTable as sqliteTable3, text as text3, integer as integer3 } from "drizzle-orm/sqlite-core";
|
|
19448
|
-
import
|
|
19449
|
-
import
|
|
19592
|
+
import path49 from "node:path";
|
|
19593
|
+
import fs41 from "node:fs";
|
|
19450
19594
|
function createChannelDb(secretsDir) {
|
|
19451
|
-
const channelsDir =
|
|
19452
|
-
|
|
19453
|
-
const dbPath =
|
|
19595
|
+
const channelsDir = path49.join(secretsDir, "channels");
|
|
19596
|
+
fs41.mkdirSync(channelsDir, { recursive: true });
|
|
19597
|
+
const dbPath = path49.join(channelsDir, "channels.db");
|
|
19454
19598
|
const sqlite = new Database4(dbPath);
|
|
19455
19599
|
sqlite.pragma("journal_mode = WAL");
|
|
19456
19600
|
sqlite.exec(CREATE_SQL2);
|
|
@@ -19500,11 +19644,11 @@ CREATE INDEX IF NOT EXISTS idx_channel_accounts_type ON channel_accounts(channel
|
|
|
19500
19644
|
});
|
|
19501
19645
|
|
|
19502
19646
|
// packages/server/dist/channels/shared/accounts.js
|
|
19503
|
-
import
|
|
19504
|
-
import
|
|
19647
|
+
import fs42 from "node:fs";
|
|
19648
|
+
import path50 from "node:path";
|
|
19505
19649
|
import { eq as eq10, and as and7 } from "drizzle-orm";
|
|
19506
19650
|
function resolveAccountWorkspace(account) {
|
|
19507
|
-
if (!
|
|
19651
|
+
if (!fs42.existsSync(account.workspacePath))
|
|
19508
19652
|
return null;
|
|
19509
19653
|
ensureWorkspaceHalo(account.workspacePath);
|
|
19510
19654
|
return account.workspacePath;
|
|
@@ -19517,9 +19661,9 @@ function accessLevelError(value, allowed) {
|
|
|
19517
19661
|
return `accessLevel must be one of: ${allowed.join(", ")}`;
|
|
19518
19662
|
}
|
|
19519
19663
|
function validateWorkspaceBody(workspacePath) {
|
|
19520
|
-
if (!
|
|
19664
|
+
if (!path50.isAbsolute(workspacePath))
|
|
19521
19665
|
return "workspacePath must be absolute";
|
|
19522
|
-
if (!
|
|
19666
|
+
if (!fs42.existsSync(workspacePath))
|
|
19523
19667
|
return "workspace path not found";
|
|
19524
19668
|
ensureWorkspaceHalo(workspacePath);
|
|
19525
19669
|
return null;
|
|
@@ -19819,22 +19963,22 @@ var init_session_types = __esm({
|
|
|
19819
19963
|
|
|
19820
19964
|
// packages/server/dist/routes/halo-city.js
|
|
19821
19965
|
import { Hono as Hono10 } from "hono";
|
|
19822
|
-
import
|
|
19823
|
-
import
|
|
19966
|
+
import fs43 from "node:fs";
|
|
19967
|
+
import path51 from "node:path";
|
|
19824
19968
|
import Database5 from "better-sqlite3";
|
|
19825
19969
|
function scanSkills2(dir) {
|
|
19826
19970
|
let names;
|
|
19827
19971
|
try {
|
|
19828
|
-
names =
|
|
19972
|
+
names = fs43.readdirSync(dir);
|
|
19829
19973
|
} catch {
|
|
19830
19974
|
return [];
|
|
19831
19975
|
}
|
|
19832
19976
|
const out = [];
|
|
19833
19977
|
for (const entry of names) {
|
|
19834
|
-
const skillMd =
|
|
19978
|
+
const skillMd = path51.join(dir, entry, "SKILL.md");
|
|
19835
19979
|
let stat;
|
|
19836
19980
|
try {
|
|
19837
|
-
stat =
|
|
19981
|
+
stat = fs43.statSync(skillMd);
|
|
19838
19982
|
if (!stat.isFile())
|
|
19839
19983
|
continue;
|
|
19840
19984
|
} catch {
|
|
@@ -19843,7 +19987,7 @@ function scanSkills2(dir) {
|
|
|
19843
19987
|
let meta = _skillCache.get(skillMd, stat.mtimeMs);
|
|
19844
19988
|
if (!meta) {
|
|
19845
19989
|
try {
|
|
19846
|
-
const { name, description, command } = parseSkillFrontmatter(
|
|
19990
|
+
const { name, description, command } = parseSkillFrontmatter(fs43.readFileSync(skillMd, "utf-8"));
|
|
19847
19991
|
meta = _skillCache.set(skillMd, stat.mtimeMs, { id: entry, name: name || entry, description: description ?? "", command });
|
|
19848
19992
|
} catch {
|
|
19849
19993
|
continue;
|
|
@@ -19929,10 +20073,10 @@ function snapshotWorkspace(sm, wsPath, label) {
|
|
|
19929
20073
|
messageCount: meta.messageCount
|
|
19930
20074
|
};
|
|
19931
20075
|
});
|
|
19932
|
-
const wsSkills = scanSkills2(
|
|
20076
|
+
const wsSkills = scanSkills2(path51.join(wsPath, ".halo", "skills"));
|
|
19933
20077
|
return {
|
|
19934
20078
|
path: wsPath,
|
|
19935
|
-
key:
|
|
20079
|
+
key: path51.basename(wsPath) || wsPath,
|
|
19936
20080
|
label,
|
|
19937
20081
|
sessions,
|
|
19938
20082
|
counts,
|
|
@@ -19943,15 +20087,15 @@ function snapshotWorkspace(sm, wsPath, label) {
|
|
|
19943
20087
|
function roReader(rawPath) {
|
|
19944
20088
|
let wsPath;
|
|
19945
20089
|
try {
|
|
19946
|
-
wsPath =
|
|
20090
|
+
wsPath = fs43.realpathSync(rawPath);
|
|
19947
20091
|
} catch {
|
|
19948
20092
|
return null;
|
|
19949
20093
|
}
|
|
19950
20094
|
const cached = _roReaders.get(wsPath);
|
|
19951
20095
|
if (cached)
|
|
19952
20096
|
return cached;
|
|
19953
|
-
const dbPath =
|
|
19954
|
-
if (!
|
|
20097
|
+
const dbPath = path51.join(wsPath, ".halo", "halo.db");
|
|
20098
|
+
if (!fs43.existsSync(dbPath))
|
|
19955
20099
|
return null;
|
|
19956
20100
|
try {
|
|
19957
20101
|
const db = new Database5(dbPath, { readonly: true, fileMustExist: true });
|
|
@@ -19979,7 +20123,7 @@ function dropRoReader(rawPath) {
|
|
|
19979
20123
|
return;
|
|
19980
20124
|
let wsPath = rawPath;
|
|
19981
20125
|
try {
|
|
19982
|
-
wsPath =
|
|
20126
|
+
wsPath = fs43.realpathSync(rawPath);
|
|
19983
20127
|
} catch {
|
|
19984
20128
|
}
|
|
19985
20129
|
const r = _roReaders.get(wsPath);
|
|
@@ -20027,12 +20171,12 @@ function snapshotWorkspaceReadonly(wsPath, label) {
|
|
|
20027
20171
|
});
|
|
20028
20172
|
return {
|
|
20029
20173
|
path: wsPath,
|
|
20030
|
-
key:
|
|
20174
|
+
key: path51.basename(wsPath) || wsPath,
|
|
20031
20175
|
label,
|
|
20032
20176
|
sessions,
|
|
20033
20177
|
counts,
|
|
20034
20178
|
totalSessions: counts.total,
|
|
20035
|
-
skills: scanSkills2(
|
|
20179
|
+
skills: scanSkills2(path51.join(wsPath, ".halo", "skills"))
|
|
20036
20180
|
};
|
|
20037
20181
|
}
|
|
20038
20182
|
function readonlySessionCounts(wsPath, limit) {
|
|
@@ -20076,7 +20220,7 @@ function workspaceBirth(wsPath) {
|
|
|
20076
20220
|
let b = _birthCache.get(wsPath);
|
|
20077
20221
|
if (b === void 0) {
|
|
20078
20222
|
try {
|
|
20079
|
-
b =
|
|
20223
|
+
b = fs43.statSync(wsPath).birthtimeMs || fs43.statSync(wsPath).ctimeMs;
|
|
20080
20224
|
} catch {
|
|
20081
20225
|
b = 0;
|
|
20082
20226
|
}
|
|
@@ -20088,18 +20232,18 @@ function discoverWorkspaces(registry2) {
|
|
|
20088
20232
|
const out = /* @__PURE__ */ new Map();
|
|
20089
20233
|
const add = (p, label) => {
|
|
20090
20234
|
try {
|
|
20091
|
-
const resolved =
|
|
20235
|
+
const resolved = fs43.realpathSync(p);
|
|
20092
20236
|
if (!out.has(resolved))
|
|
20093
20237
|
out.set(resolved, label);
|
|
20094
20238
|
} catch {
|
|
20095
20239
|
}
|
|
20096
20240
|
};
|
|
20097
20241
|
for (const { workspacePath } of registry2.list())
|
|
20098
|
-
add(workspacePath,
|
|
20242
|
+
add(workspacePath, path51.basename(workspacePath));
|
|
20099
20243
|
try {
|
|
20100
20244
|
const rows = getChannelDb().select().from(channelAccounts).all();
|
|
20101
20245
|
for (const row of rows)
|
|
20102
|
-
add(row.workspacePath,
|
|
20246
|
+
add(row.workspacePath, path51.basename(row.workspacePath));
|
|
20103
20247
|
} catch {
|
|
20104
20248
|
}
|
|
20105
20249
|
return out;
|
|
@@ -20118,7 +20262,7 @@ function createShowRoutes(registry2) {
|
|
|
20118
20262
|
return a.response;
|
|
20119
20263
|
const { account } = a;
|
|
20120
20264
|
const globalView = account.accessLevel === "full" || account.accessLevel === "observer";
|
|
20121
|
-
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)]]);
|
|
20122
20266
|
const workspaces = [];
|
|
20123
20267
|
for (const [wsPath, label] of targets) {
|
|
20124
20268
|
try {
|
|
@@ -20152,7 +20296,7 @@ function createShowRoutes(registry2) {
|
|
|
20152
20296
|
if (!globalView) {
|
|
20153
20297
|
let allowed = false;
|
|
20154
20298
|
try {
|
|
20155
|
-
allowed =
|
|
20299
|
+
allowed = fs43.realpathSync(wsPath) === fs43.realpathSync(account.workspacePath);
|
|
20156
20300
|
} catch {
|
|
20157
20301
|
}
|
|
20158
20302
|
if (!allowed)
|
|
@@ -20346,14 +20490,14 @@ var init_commands3 = __esm({
|
|
|
20346
20490
|
|
|
20347
20491
|
// packages/server/dist/routes/agentcore.js
|
|
20348
20492
|
import { Hono as Hono13 } from "hono";
|
|
20349
|
-
import
|
|
20350
|
-
import
|
|
20493
|
+
import path52 from "node:path";
|
|
20494
|
+
import fs44 from "node:fs";
|
|
20351
20495
|
function haloSessionId(runtimeSessionId) {
|
|
20352
20496
|
return `agentcore_${runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80)}`;
|
|
20353
20497
|
}
|
|
20354
20498
|
function userWorkspace(base, runtimeSessionId) {
|
|
20355
|
-
const dir =
|
|
20356
|
-
|
|
20499
|
+
const dir = path52.join(base, "users", runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80));
|
|
20500
|
+
fs44.mkdirSync(dir, { recursive: true });
|
|
20357
20501
|
return dir;
|
|
20358
20502
|
}
|
|
20359
20503
|
async function ensureSession(sm, workspace, sessionId) {
|
|
@@ -20610,8 +20754,8 @@ var init_agentcore = __esm({
|
|
|
20610
20754
|
});
|
|
20611
20755
|
|
|
20612
20756
|
// packages/server/dist/ws/file-watcher.js
|
|
20613
|
-
import
|
|
20614
|
-
import
|
|
20757
|
+
import fs45 from "node:fs";
|
|
20758
|
+
import path53 from "node:path";
|
|
20615
20759
|
function enqueueNativeOp(op) {
|
|
20616
20760
|
const result = nativeOpChain.then(op);
|
|
20617
20761
|
nativeOpChain = result.catch(() => {
|
|
@@ -20619,12 +20763,12 @@ function enqueueNativeOp(op) {
|
|
|
20619
20763
|
return result;
|
|
20620
20764
|
}
|
|
20621
20765
|
function isUnwatchablePath(absPath) {
|
|
20622
|
-
const norm =
|
|
20623
|
-
if (norm === "/" || norm ===
|
|
20766
|
+
const norm = path53.resolve(absPath);
|
|
20767
|
+
if (norm === "/" || norm === path53.parse(norm).root)
|
|
20624
20768
|
return "filesystem root";
|
|
20625
20769
|
const banned = ["/System", "/Library", "/private", "/usr", "/bin", "/sbin", "/etc"];
|
|
20626
20770
|
for (const b of banned) {
|
|
20627
|
-
if (norm === b || norm.startsWith(b +
|
|
20771
|
+
if (norm === b || norm.startsWith(b + path53.sep))
|
|
20628
20772
|
return `system path ${b}`;
|
|
20629
20773
|
}
|
|
20630
20774
|
return null;
|
|
@@ -20720,7 +20864,7 @@ var init_file_watcher = __esm({
|
|
|
20720
20864
|
}
|
|
20721
20865
|
let root = workspaceRoot;
|
|
20722
20866
|
try {
|
|
20723
|
-
root =
|
|
20867
|
+
root = fs45.realpathSync(workspaceRoot);
|
|
20724
20868
|
} catch {
|
|
20725
20869
|
}
|
|
20726
20870
|
if (this.subscription && this.workspaceRoot === root)
|
|
@@ -20764,10 +20908,10 @@ var init_file_watcher = __esm({
|
|
|
20764
20908
|
const root = this.workspaceRoot;
|
|
20765
20909
|
if (!root)
|
|
20766
20910
|
return;
|
|
20767
|
-
const raw =
|
|
20911
|
+
const raw = path53.relative(root, absPath);
|
|
20768
20912
|
if (!raw || raw.startsWith(".."))
|
|
20769
20913
|
return;
|
|
20770
|
-
const rel = raw.split(
|
|
20914
|
+
const rel = raw.split(path53.sep).join("/");
|
|
20771
20915
|
if (IS_WIN32 && rel.split("/").some((seg) => IGNORED_SEGMENT_SET.has(seg)))
|
|
20772
20916
|
return;
|
|
20773
20917
|
let action;
|
|
@@ -20778,7 +20922,7 @@ var init_file_watcher = __esm({
|
|
|
20778
20922
|
} else {
|
|
20779
20923
|
let isDir = false;
|
|
20780
20924
|
try {
|
|
20781
|
-
isDir =
|
|
20925
|
+
isDir = fs45.statSync(absPath).isDirectory();
|
|
20782
20926
|
} catch {
|
|
20783
20927
|
}
|
|
20784
20928
|
action = isDir ? "addDir" : "add";
|
|
@@ -20854,8 +20998,8 @@ var init_file_watcher = __esm({
|
|
|
20854
20998
|
});
|
|
20855
20999
|
|
|
20856
21000
|
// packages/server/dist/ws/git-dir-watcher.js
|
|
20857
|
-
import
|
|
20858
|
-
import
|
|
21001
|
+
import fs46 from "node:fs";
|
|
21002
|
+
import path54 from "node:path";
|
|
20859
21003
|
var GitDirWatcher;
|
|
20860
21004
|
var init_git_dir_watcher = __esm({
|
|
20861
21005
|
"packages/server/dist/ws/git-dir-watcher.js"() {
|
|
@@ -20879,8 +21023,8 @@ var init_git_dir_watcher = __esm({
|
|
|
20879
21023
|
return;
|
|
20880
21024
|
this.stop();
|
|
20881
21025
|
this.workspaceRoot = workspaceRoot;
|
|
20882
|
-
const gitDir =
|
|
20883
|
-
if (
|
|
21026
|
+
const gitDir = path54.join(workspaceRoot, ".git");
|
|
21027
|
+
if (fs46.existsSync(gitDir)) {
|
|
20884
21028
|
this.watchGitDir(gitDir);
|
|
20885
21029
|
} else {
|
|
20886
21030
|
this.watchParentForGit(workspaceRoot);
|
|
@@ -20889,7 +21033,7 @@ var init_git_dir_watcher = __esm({
|
|
|
20889
21033
|
/** Watch the `.git` dir for index/HEAD changes (steady state). */
|
|
20890
21034
|
watchGitDir(gitDir) {
|
|
20891
21035
|
try {
|
|
20892
|
-
this.watcher =
|
|
21036
|
+
this.watcher = fs46.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
20893
21037
|
this.handleGitEvent(filename);
|
|
20894
21038
|
});
|
|
20895
21039
|
this.watcher.on("error", (err) => {
|
|
@@ -20908,7 +21052,7 @@ var init_git_dir_watcher = __esm({
|
|
|
20908
21052
|
* Exists solely during the non-repo window; upgraded away on first `.git`. */
|
|
20909
21053
|
watchParentForGit(workspaceRoot) {
|
|
20910
21054
|
try {
|
|
20911
|
-
this.watcher =
|
|
21055
|
+
this.watcher = fs46.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
20912
21056
|
this.handleParentEvent(filename);
|
|
20913
21057
|
});
|
|
20914
21058
|
this.watcher.on("error", (err) => {
|
|
@@ -20944,8 +21088,8 @@ var init_git_dir_watcher = __esm({
|
|
|
20944
21088
|
const root = this.workspaceRoot;
|
|
20945
21089
|
if (!root)
|
|
20946
21090
|
return;
|
|
20947
|
-
const gitDir =
|
|
20948
|
-
if (!
|
|
21091
|
+
const gitDir = path54.join(root, ".git");
|
|
21092
|
+
if (!fs46.existsSync(gitDir))
|
|
20949
21093
|
return;
|
|
20950
21094
|
this.closeWatcher();
|
|
20951
21095
|
this.watchGitDir(gitDir);
|
|
@@ -21261,7 +21405,7 @@ var init_terminal_manager = __esm({
|
|
|
21261
21405
|
});
|
|
21262
21406
|
|
|
21263
21407
|
// packages/server/dist/ws/handler.js
|
|
21264
|
-
import
|
|
21408
|
+
import path55 from "node:path";
|
|
21265
21409
|
function setupWebSocketHandler(deps) {
|
|
21266
21410
|
const { wss: wss2, registry: registry2 } = deps;
|
|
21267
21411
|
const clients = /* @__PURE__ */ new Set();
|
|
@@ -21283,7 +21427,7 @@ function setupWebSocketHandler(deps) {
|
|
|
21283
21427
|
ackedChatIds.add(id);
|
|
21284
21428
|
}
|
|
21285
21429
|
function resolveProjectPath2(projectId) {
|
|
21286
|
-
if (
|
|
21430
|
+
if (path55.isAbsolute(projectId))
|
|
21287
21431
|
return projectId;
|
|
21288
21432
|
return null;
|
|
21289
21433
|
}
|
|
@@ -21909,11 +22053,11 @@ var init_handler = __esm({
|
|
|
21909
22053
|
import Database6 from "better-sqlite3";
|
|
21910
22054
|
import { drizzle as drizzle4 } from "drizzle-orm/better-sqlite3";
|
|
21911
22055
|
import { sqliteTable as sqliteTable4, text as text4, integer as integer4 } from "drizzle-orm/sqlite-core";
|
|
21912
|
-
import
|
|
21913
|
-
import
|
|
22056
|
+
import path56 from "node:path";
|
|
22057
|
+
import fs47 from "node:fs";
|
|
21914
22058
|
function createCronDb(globalDir2) {
|
|
21915
|
-
|
|
21916
|
-
const dbPath =
|
|
22059
|
+
fs47.mkdirSync(globalDir2, { recursive: true });
|
|
22060
|
+
const dbPath = path56.join(globalDir2, "cron.db");
|
|
21917
22061
|
const sqlite = new Database6(dbPath);
|
|
21918
22062
|
sqlite.pragma("journal_mode = WAL");
|
|
21919
22063
|
sqlite.exec(CREATE_SQL3);
|
|
@@ -22111,8 +22255,8 @@ var init_child_env = __esm({
|
|
|
22111
22255
|
|
|
22112
22256
|
// packages/server/dist/cron/runner.js
|
|
22113
22257
|
import { spawn as spawn3, execFileSync } from "node:child_process";
|
|
22114
|
-
import
|
|
22115
|
-
import
|
|
22258
|
+
import fs48 from "node:fs";
|
|
22259
|
+
import path57 from "node:path";
|
|
22116
22260
|
import { homedir as homedir23 } from "node:os";
|
|
22117
22261
|
import { Cron } from "croner";
|
|
22118
22262
|
import { eq as eq11, lt as lt4 } from "drizzle-orm";
|
|
@@ -22183,10 +22327,10 @@ function isCronCliProcess(pid, jobId) {
|
|
|
22183
22327
|
}
|
|
22184
22328
|
}
|
|
22185
22329
|
function logsDir() {
|
|
22186
|
-
return
|
|
22330
|
+
return path57.join(homedir23(), ".halo", "global", "logs", "cron");
|
|
22187
22331
|
}
|
|
22188
22332
|
function logPathFor(runId) {
|
|
22189
|
-
return
|
|
22333
|
+
return path57.join(logsDir(), `${runId}.log`);
|
|
22190
22334
|
}
|
|
22191
22335
|
function newRunId() {
|
|
22192
22336
|
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -22237,7 +22381,7 @@ function sweepOrphanRuns(opts) {
|
|
|
22237
22381
|
}
|
|
22238
22382
|
}
|
|
22239
22383
|
function startCronDaemon() {
|
|
22240
|
-
|
|
22384
|
+
fs48.mkdirSync(logsDir(), { recursive: true });
|
|
22241
22385
|
sweepOrphanRuns();
|
|
22242
22386
|
reloadAll();
|
|
22243
22387
|
_daemonTimers.push(setInterval(pruneOldLogs, DAY_MS2));
|
|
@@ -22394,7 +22538,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
22394
22538
|
const runId = newRunId();
|
|
22395
22539
|
const startedAt = Date.now();
|
|
22396
22540
|
const logPath = logPathFor(runId);
|
|
22397
|
-
|
|
22541
|
+
fs48.mkdirSync(logsDir(), { recursive: true });
|
|
22398
22542
|
if (_inflight.has(jobId)) {
|
|
22399
22543
|
db.insert(cronRuns).values({
|
|
22400
22544
|
id: runId,
|
|
@@ -22429,7 +22573,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
22429
22573
|
}).run();
|
|
22430
22574
|
broadcast({ type: "cron:run_changed", jobId, runId, status: "running" });
|
|
22431
22575
|
const targets = parseTargets(job.targets);
|
|
22432
|
-
if (!
|
|
22576
|
+
if (!fs48.existsSync(job.workspacePath)) {
|
|
22433
22577
|
finalize(runId, jobId, {
|
|
22434
22578
|
status: "failed",
|
|
22435
22579
|
output: null,
|
|
@@ -22443,7 +22587,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
22443
22587
|
const sessionId = `cron-${job.id}`;
|
|
22444
22588
|
const args = ["cli", "-a", job.agentId, "-s", sessionId, "-w", job.workspacePath];
|
|
22445
22589
|
const [spawnBin, spawnArgs] = process.platform === "win32" && cli.endsWith(".cmd") ? ["cmd.exe", ["/c", cli, ...args]] : [cli, args];
|
|
22446
|
-
const teeStream =
|
|
22590
|
+
const teeStream = fs48.createWriteStream(logPath, { flags: "a" });
|
|
22447
22591
|
teeStream.write(`=== ${(/* @__PURE__ */ new Date()).toISOString()} cron run ${runId} job=${jobId} trigger=${triggerKind} ===
|
|
22448
22592
|
`);
|
|
22449
22593
|
teeStream.write(`agent=${job.agentId} workspace=${job.workspacePath} schedule=${job.schedule}
|
|
@@ -22615,15 +22759,15 @@ function pruneOldRuns(jobId) {
|
|
|
22615
22759
|
}
|
|
22616
22760
|
function pruneOldLogs() {
|
|
22617
22761
|
const dir = logsDir();
|
|
22618
|
-
if (!
|
|
22762
|
+
if (!fs48.existsSync(dir))
|
|
22619
22763
|
return;
|
|
22620
22764
|
const cutoff = Date.now() - LOG_RETENTION_DAYS * DAY_MS2;
|
|
22621
|
-
for (const name of
|
|
22622
|
-
const p =
|
|
22765
|
+
for (const name of fs48.readdirSync(dir)) {
|
|
22766
|
+
const p = path57.join(dir, name);
|
|
22623
22767
|
try {
|
|
22624
|
-
const stat =
|
|
22768
|
+
const stat = fs48.statSync(p);
|
|
22625
22769
|
if (stat.mtimeMs < cutoff)
|
|
22626
|
-
|
|
22770
|
+
fs48.rmSync(p, { force: true });
|
|
22627
22771
|
} catch {
|
|
22628
22772
|
}
|
|
22629
22773
|
}
|
|
@@ -22656,7 +22800,7 @@ var init_runner = __esm({
|
|
|
22656
22800
|
// packages/server/dist/routes/cron.js
|
|
22657
22801
|
import { Hono as Hono14 } from "hono";
|
|
22658
22802
|
import { eq as eq12, desc as desc3, lt as lt5, and as and8 } from "drizzle-orm";
|
|
22659
|
-
import
|
|
22803
|
+
import fs49 from "node:fs/promises";
|
|
22660
22804
|
import { Cron as Cron2 } from "croner";
|
|
22661
22805
|
function newJobId() {
|
|
22662
22806
|
return `cron-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -22852,7 +22996,7 @@ function createCronRoutes() {
|
|
|
22852
22996
|
return c.json({ log: null });
|
|
22853
22997
|
let log2 = null;
|
|
22854
22998
|
try {
|
|
22855
|
-
log2 = await
|
|
22999
|
+
log2 = await fs49.readFile(row.logPath, "utf-8");
|
|
22856
23000
|
} catch {
|
|
22857
23001
|
}
|
|
22858
23002
|
return c.json({ log: log2 });
|
|
@@ -23154,7 +23298,7 @@ var init_ticker = __esm({
|
|
|
23154
23298
|
});
|
|
23155
23299
|
|
|
23156
23300
|
// packages/server/dist/evolution/spawn.js
|
|
23157
|
-
import
|
|
23301
|
+
import path58 from "node:path";
|
|
23158
23302
|
import { spawn as spawn4 } from "node:child_process";
|
|
23159
23303
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
23160
23304
|
var __dirname2, WRAPPER_PATH, realEvoSpawner;
|
|
@@ -23162,8 +23306,8 @@ var init_spawn = __esm({
|
|
|
23162
23306
|
"packages/server/dist/evolution/spawn.js"() {
|
|
23163
23307
|
"use strict";
|
|
23164
23308
|
init_child_env();
|
|
23165
|
-
__dirname2 =
|
|
23166
|
-
WRAPPER_PATH =
|
|
23309
|
+
__dirname2 = path58.dirname(fileURLToPath2(import.meta.url));
|
|
23310
|
+
WRAPPER_PATH = path58.resolve(__dirname2, "evo-wrapper.js");
|
|
23167
23311
|
realEvoSpawner = (mode, id) => {
|
|
23168
23312
|
const child = spawn4(process.execPath, [WRAPPER_PATH, `--mode=${mode}`, `--id=${id}`], {
|
|
23169
23313
|
detached: true,
|
|
@@ -23950,7 +24094,7 @@ var init_handler2 = __esm({
|
|
|
23950
24094
|
// packages/server/dist/routes/telegram.js
|
|
23951
24095
|
import { Hono as Hono15 } from "hono";
|
|
23952
24096
|
import { Bot as Bot2 } from "grammy";
|
|
23953
|
-
import
|
|
24097
|
+
import fs50 from "node:fs";
|
|
23954
24098
|
function createTelegramRoutes(deps) {
|
|
23955
24099
|
const { db, channel } = deps;
|
|
23956
24100
|
const app2 = new Hono15();
|
|
@@ -23960,7 +24104,7 @@ function createTelegramRoutes(deps) {
|
|
|
23960
24104
|
accountId: acc.accountId,
|
|
23961
24105
|
botUsername: acc.botUsername,
|
|
23962
24106
|
workspacePath: acc.workspacePath,
|
|
23963
|
-
workspaceMissing: !
|
|
24107
|
+
workspaceMissing: !fs50.existsSync(acc.workspacePath),
|
|
23964
24108
|
label: acc.label,
|
|
23965
24109
|
enabled: acc.enabled,
|
|
23966
24110
|
accessLevel: acc.accessLevel,
|
|
@@ -24684,8 +24828,8 @@ var init_cdn = __esm({
|
|
|
24684
24828
|
});
|
|
24685
24829
|
|
|
24686
24830
|
// packages/server/dist/channels/wechat/send-media.js
|
|
24687
|
-
import
|
|
24688
|
-
import
|
|
24831
|
+
import fs51 from "node:fs/promises";
|
|
24832
|
+
import path59 from "node:path";
|
|
24689
24833
|
function inferKind(filePath) {
|
|
24690
24834
|
const cls = classifyMedia(filePath);
|
|
24691
24835
|
if (cls === "image")
|
|
@@ -24697,7 +24841,7 @@ function inferKind(filePath) {
|
|
|
24697
24841
|
async function sendMediaFile(params) {
|
|
24698
24842
|
const { baseUrl, token, toUserId, contextToken, filePath } = params;
|
|
24699
24843
|
const kind = params.kind ?? inferKind(filePath);
|
|
24700
|
-
const plaintext = await
|
|
24844
|
+
const plaintext = await fs51.readFile(filePath);
|
|
24701
24845
|
const prep = prepareUpload(plaintext);
|
|
24702
24846
|
console.log(`[wechat] sendMediaFile: path=${filePath} kind=${kind} rawsize=${prep.rawsize} filesize=${prep.filesize}`);
|
|
24703
24847
|
const mediaType = kind === "image" ? UploadMediaType.IMAGE : kind === "video" ? UploadMediaType.VIDEO : UploadMediaType.FILE;
|
|
@@ -24735,7 +24879,7 @@ async function sendMediaFile(params) {
|
|
|
24735
24879
|
aesKeyBase64,
|
|
24736
24880
|
fileSizeCiphertext: prep.filesize,
|
|
24737
24881
|
fileSize: prep.rawsize,
|
|
24738
|
-
fileName:
|
|
24882
|
+
fileName: path59.basename(filePath)
|
|
24739
24883
|
});
|
|
24740
24884
|
const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
24741
24885
|
const body = {
|
|
@@ -24898,8 +25042,8 @@ var init_login = __esm({
|
|
|
24898
25042
|
});
|
|
24899
25043
|
|
|
24900
25044
|
// packages/server/dist/channels/wechat/handler.js
|
|
24901
|
-
import
|
|
24902
|
-
import
|
|
25045
|
+
import fs52 from "node:fs";
|
|
25046
|
+
import path60 from "node:path";
|
|
24903
25047
|
import QRCode from "qrcode";
|
|
24904
25048
|
function wxRoute(fromUserId, contextToken) {
|
|
24905
25049
|
return (prev) => ({
|
|
@@ -25288,7 +25432,7 @@ async function handleSlashCommand(args) {
|
|
|
25288
25432
|
await reply(login.message);
|
|
25289
25433
|
return true;
|
|
25290
25434
|
}
|
|
25291
|
-
const tmpPath =
|
|
25435
|
+
const tmpPath = path60.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
|
|
25292
25436
|
await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
|
|
25293
25437
|
await sendMediaFile({
|
|
25294
25438
|
baseUrl: account.baseUrl,
|
|
@@ -25297,7 +25441,7 @@ async function handleSlashCommand(args) {
|
|
|
25297
25441
|
contextToken,
|
|
25298
25442
|
filePath: tmpPath
|
|
25299
25443
|
});
|
|
25300
|
-
await
|
|
25444
|
+
await fs52.promises.unlink(tmpPath).catch(() => {
|
|
25301
25445
|
});
|
|
25302
25446
|
await reply(t("wechat.qr_sent", lang, { level, path: account.workspacePath }));
|
|
25303
25447
|
waitLogin({ sessionKey: login.sessionKey }).then(async (result2) => {
|
|
@@ -25377,8 +25521,8 @@ var init_handler3 = __esm({
|
|
|
25377
25521
|
|
|
25378
25522
|
// packages/server/dist/routes/wechat.js
|
|
25379
25523
|
import { Hono as Hono16 } from "hono";
|
|
25380
|
-
import
|
|
25381
|
-
import
|
|
25524
|
+
import path61 from "node:path";
|
|
25525
|
+
import fs53 from "node:fs";
|
|
25382
25526
|
function createWechatRoutes(deps) {
|
|
25383
25527
|
const { db, channel } = deps;
|
|
25384
25528
|
const app2 = new Hono16();
|
|
@@ -25387,7 +25531,7 @@ function createWechatRoutes(deps) {
|
|
|
25387
25531
|
return {
|
|
25388
25532
|
...stripSecrets(acc),
|
|
25389
25533
|
workspacePath: acc.workspacePath,
|
|
25390
|
-
workspaceMissing: !
|
|
25534
|
+
workspaceMissing: !fs53.existsSync(acc.workspacePath)
|
|
25391
25535
|
};
|
|
25392
25536
|
});
|
|
25393
25537
|
return c.json({ accounts });
|
|
@@ -25403,7 +25547,7 @@ function createWechatRoutes(deps) {
|
|
|
25403
25547
|
return c.json({ error: "sessionKey required" }, 400);
|
|
25404
25548
|
if (!body.workspacePath)
|
|
25405
25549
|
return c.json({ error: "workspacePath required" }, 400);
|
|
25406
|
-
if (!
|
|
25550
|
+
if (!path61.isAbsolute(body.workspacePath))
|
|
25407
25551
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
25408
25552
|
const levelError = accessLevelError(body.accessLevel, CHAT_ACCESS_LEVELS);
|
|
25409
25553
|
if (levelError)
|
|
@@ -25607,14 +25751,14 @@ function createWebChannel(deps) {
|
|
|
25607
25751
|
return findActiveSessionId(sm, accountId, prefix, activeOverrides2, "full") ?? void 0;
|
|
25608
25752
|
}
|
|
25609
25753
|
function resolveWorkspace(account, override) {
|
|
25610
|
-
let
|
|
25754
|
+
let path66 = account.workspacePath;
|
|
25611
25755
|
if (override && override !== account.workspacePath) {
|
|
25612
25756
|
if (account.accessLevel !== "full") {
|
|
25613
25757
|
return { ok: false, error: "workspace override requires a full-access token" };
|
|
25614
25758
|
}
|
|
25615
|
-
|
|
25759
|
+
path66 = override;
|
|
25616
25760
|
}
|
|
25617
|
-
const resolved = resolveAccountWorkspace({ ...account, workspacePath:
|
|
25761
|
+
const resolved = resolveAccountWorkspace({ ...account, workspacePath: path66 });
|
|
25618
25762
|
if (!resolved)
|
|
25619
25763
|
return { ok: false, error: "workspace not found" };
|
|
25620
25764
|
return { ok: true, path: resolved };
|
|
@@ -25997,7 +26141,7 @@ var init_handler4 = __esm({
|
|
|
25997
26141
|
import { Hono as Hono17 } from "hono";
|
|
25998
26142
|
import { streamSSE } from "hono/streaming";
|
|
25999
26143
|
import crypto4 from "node:crypto";
|
|
26000
|
-
import
|
|
26144
|
+
import fs54 from "node:fs";
|
|
26001
26145
|
function createWebRoutes(deps) {
|
|
26002
26146
|
const { db, channel } = deps;
|
|
26003
26147
|
const app2 = new Hono17();
|
|
@@ -26007,7 +26151,7 @@ function createWebRoutes(deps) {
|
|
|
26007
26151
|
accountId: acc.accountId,
|
|
26008
26152
|
token: acc.token,
|
|
26009
26153
|
workspacePath: acc.workspacePath,
|
|
26010
|
-
workspaceMissing: !
|
|
26154
|
+
workspaceMissing: !fs54.existsSync(acc.workspacePath),
|
|
26011
26155
|
label: acc.label,
|
|
26012
26156
|
enabled: acc.enabled,
|
|
26013
26157
|
accessLevel: acc.accessLevel,
|
|
@@ -26153,27 +26297,27 @@ function createWebRoutes(deps) {
|
|
|
26153
26297
|
const filePath = c.req.query("path");
|
|
26154
26298
|
if (!filePath)
|
|
26155
26299
|
return c.json({ error: "path required" }, 400);
|
|
26156
|
-
const
|
|
26157
|
-
const resolved =
|
|
26158
|
-
const root =
|
|
26159
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
26300
|
+
const path66 = await import("node:path");
|
|
26301
|
+
const resolved = path66.default.resolve(account.workspacePath, filePath);
|
|
26302
|
+
const root = path66.default.resolve(account.workspacePath);
|
|
26303
|
+
if (resolved !== root && !resolved.startsWith(root + path66.default.sep)) {
|
|
26160
26304
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
26161
26305
|
}
|
|
26162
26306
|
let real;
|
|
26163
26307
|
let realRoot;
|
|
26164
26308
|
try {
|
|
26165
|
-
real =
|
|
26166
|
-
realRoot =
|
|
26309
|
+
real = fs54.realpathSync(resolved);
|
|
26310
|
+
realRoot = fs54.realpathSync(root);
|
|
26167
26311
|
} catch {
|
|
26168
26312
|
return c.json({ error: "file not found" }, 404);
|
|
26169
26313
|
}
|
|
26170
|
-
if (real !== realRoot && !real.startsWith(realRoot +
|
|
26314
|
+
if (real !== realRoot && !real.startsWith(realRoot + path66.default.sep)) {
|
|
26171
26315
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
26172
26316
|
}
|
|
26173
|
-
const stat =
|
|
26317
|
+
const stat = fs54.statSync(real);
|
|
26174
26318
|
if (stat.isDirectory())
|
|
26175
26319
|
return c.json({ error: "cannot serve directory" }, 400);
|
|
26176
|
-
const ext =
|
|
26320
|
+
const ext = path66.default.extname(real).toLowerCase();
|
|
26177
26321
|
const nonImageMimes = {
|
|
26178
26322
|
".mp4": "video/mp4",
|
|
26179
26323
|
".webm": "video/webm",
|
|
@@ -26181,8 +26325,8 @@ function createWebRoutes(deps) {
|
|
|
26181
26325
|
".txt": "text/plain"
|
|
26182
26326
|
};
|
|
26183
26327
|
const contentType = imageMimeFromExt(ext) || nonImageMimes[ext] || "application/octet-stream";
|
|
26184
|
-
const fileName =
|
|
26185
|
-
const data =
|
|
26328
|
+
const fileName = path66.default.basename(resolved);
|
|
26329
|
+
const data = fs54.readFileSync(real);
|
|
26186
26330
|
c.header("content-type", contentType);
|
|
26187
26331
|
c.header("content-disposition", `inline; filename="${fileName}"`);
|
|
26188
26332
|
return c.body(data);
|
|
@@ -26523,10 +26667,10 @@ async function authTest(botToken) {
|
|
|
26523
26667
|
return slackPost("auth.test", botToken, {});
|
|
26524
26668
|
}
|
|
26525
26669
|
async function uploadFile(args) {
|
|
26526
|
-
const
|
|
26527
|
-
const
|
|
26528
|
-
const filename = args.filename ??
|
|
26529
|
-
const stat =
|
|
26670
|
+
const fs59 = await import("node:fs");
|
|
26671
|
+
const path66 = await import("node:path");
|
|
26672
|
+
const filename = args.filename ?? path66.basename(args.filePath);
|
|
26673
|
+
const stat = fs59.statSync(args.filePath);
|
|
26530
26674
|
if (!stat.isFile())
|
|
26531
26675
|
throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
|
|
26532
26676
|
const length = stat.size;
|
|
@@ -26544,7 +26688,7 @@ async function uploadFile(args) {
|
|
|
26544
26688
|
if (step1Parsed.ok === false || !step1Parsed.upload_url || !step1Parsed.file_id) {
|
|
26545
26689
|
throw new Error(`[slack:files.getUploadURLExternal] ${step1Parsed.error ?? "unknown"}`);
|
|
26546
26690
|
}
|
|
26547
|
-
const buf =
|
|
26691
|
+
const buf = fs59.readFileSync(args.filePath);
|
|
26548
26692
|
const step2 = await fetch(step1Parsed.upload_url, {
|
|
26549
26693
|
method: "POST",
|
|
26550
26694
|
body: buf
|
|
@@ -26689,7 +26833,7 @@ var init_api2 = __esm({
|
|
|
26689
26833
|
});
|
|
26690
26834
|
|
|
26691
26835
|
// packages/server/dist/channels/slack/handler.js
|
|
26692
|
-
import
|
|
26836
|
+
import path62 from "node:path";
|
|
26693
26837
|
import { WebSocket } from "ws";
|
|
26694
26838
|
function slashToBang(text5, commandNames) {
|
|
26695
26839
|
if (commandNames.length === 0)
|
|
@@ -26794,7 +26938,7 @@ function startSlackChannel(deps) {
|
|
|
26794
26938
|
const route = bridge.getRoute(sessionId);
|
|
26795
26939
|
if (!account || !route)
|
|
26796
26940
|
return;
|
|
26797
|
-
const resolved =
|
|
26941
|
+
const resolved = path62.resolve(filePath);
|
|
26798
26942
|
if (!isMediaPathAllowed(resolved, account.workspacePath)) {
|
|
26799
26943
|
console.log(`[slack] sendMedia blocked: ${filePath} not under workspace`);
|
|
26800
26944
|
return;
|
|
@@ -26813,7 +26957,7 @@ function startSlackChannel(deps) {
|
|
|
26813
26957
|
channel: route.channelId,
|
|
26814
26958
|
threadTs: route.replyTs,
|
|
26815
26959
|
text: t("handler.upload_failed", getLang(account), {
|
|
26816
|
-
name:
|
|
26960
|
+
name: path62.basename(filePath),
|
|
26817
26961
|
error: err instanceof Error ? err.message : String(err)
|
|
26818
26962
|
})
|
|
26819
26963
|
}).catch(() => {
|
|
@@ -27070,7 +27214,7 @@ var init_handler5 = __esm({
|
|
|
27070
27214
|
|
|
27071
27215
|
// packages/server/dist/routes/slack.js
|
|
27072
27216
|
import { Hono as Hono18 } from "hono";
|
|
27073
|
-
import
|
|
27217
|
+
import fs55 from "node:fs";
|
|
27074
27218
|
function createSlackRoutes(deps) {
|
|
27075
27219
|
const { db, channel } = deps;
|
|
27076
27220
|
const app2 = new Hono18();
|
|
@@ -27093,7 +27237,7 @@ function createSlackRoutes(deps) {
|
|
|
27093
27237
|
botUserId: a.botUserId,
|
|
27094
27238
|
teamId: a.teamId,
|
|
27095
27239
|
workspacePath: a.workspacePath,
|
|
27096
|
-
workspaceMissing: !
|
|
27240
|
+
workspaceMissing: !fs55.existsSync(a.workspacePath),
|
|
27097
27241
|
label: a.label,
|
|
27098
27242
|
enabled: a.enabled,
|
|
27099
27243
|
accessLevel: a.accessLevel,
|
|
@@ -27625,10 +27769,10 @@ async function replyMessage(args) {
|
|
|
27625
27769
|
return { message_id: parsed.data?.message_id ?? "" };
|
|
27626
27770
|
}
|
|
27627
27771
|
async function uploadImage(args) {
|
|
27628
|
-
const
|
|
27629
|
-
const
|
|
27630
|
-
const buf =
|
|
27631
|
-
const filename =
|
|
27772
|
+
const fs59 = await import("node:fs");
|
|
27773
|
+
const path66 = await import("node:path");
|
|
27774
|
+
const buf = fs59.readFileSync(args.filePath);
|
|
27775
|
+
const filename = path66.basename(args.filePath);
|
|
27632
27776
|
const send = async (token2) => {
|
|
27633
27777
|
const form = new FormData();
|
|
27634
27778
|
form.append("image_type", "message");
|
|
@@ -27659,10 +27803,10 @@ async function uploadImage(args) {
|
|
|
27659
27803
|
return { imageKey: parsed.data.image_key };
|
|
27660
27804
|
}
|
|
27661
27805
|
async function uploadFile2(args) {
|
|
27662
|
-
const
|
|
27663
|
-
const
|
|
27664
|
-
const buf =
|
|
27665
|
-
const filename = args.filename ??
|
|
27806
|
+
const fs59 = await import("node:fs");
|
|
27807
|
+
const path66 = await import("node:path");
|
|
27808
|
+
const buf = fs59.readFileSync(args.filePath);
|
|
27809
|
+
const filename = args.filename ?? path66.basename(args.filePath);
|
|
27666
27810
|
const send = async (token2) => {
|
|
27667
27811
|
const form = new FormData();
|
|
27668
27812
|
form.append("file_type", args.fileType);
|
|
@@ -27768,7 +27912,7 @@ var init_api3 = __esm({
|
|
|
27768
27912
|
});
|
|
27769
27913
|
|
|
27770
27914
|
// packages/server/dist/channels/feishu/handler.js
|
|
27771
|
-
import
|
|
27915
|
+
import path63 from "node:path";
|
|
27772
27916
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
27773
27917
|
function buildSessionPrefixForThread2(chatId, rootId) {
|
|
27774
27918
|
return sessionPrefix("feishu", `${chatId}:${rootId}`);
|
|
@@ -27891,7 +28035,7 @@ function startFeishuChannel(deps) {
|
|
|
27891
28035
|
const route = bridge.getRoute(sessionId);
|
|
27892
28036
|
if (!account || !route)
|
|
27893
28037
|
return;
|
|
27894
|
-
const resolved =
|
|
28038
|
+
const resolved = path63.resolve(filePath);
|
|
27895
28039
|
if (!isMediaPathAllowed(resolved, account.workspacePath)) {
|
|
27896
28040
|
console.log(`[feishu] sendMedia blocked: ${filePath} not under workspace`);
|
|
27897
28041
|
return;
|
|
@@ -27904,7 +28048,7 @@ function startFeishuChannel(deps) {
|
|
|
27904
28048
|
account,
|
|
27905
28049
|
...route,
|
|
27906
28050
|
text: t("handler.upload_failed", getLang(account), {
|
|
27907
|
-
name:
|
|
28051
|
+
name: path63.basename(filePath),
|
|
27908
28052
|
error: err instanceof Error ? err.message : String(err)
|
|
27909
28053
|
})
|
|
27910
28054
|
}).catch(() => {
|
|
@@ -28068,7 +28212,7 @@ async function replyToInbound(args) {
|
|
|
28068
28212
|
async function sendFeishuMedia(args) {
|
|
28069
28213
|
const { account, inboundMessageId, isP2P, chatId, filePath } = args;
|
|
28070
28214
|
const cls = classifyMedia(filePath);
|
|
28071
|
-
const ext =
|
|
28215
|
+
const ext = path63.extname(filePath).toLowerCase();
|
|
28072
28216
|
if (cls === "image") {
|
|
28073
28217
|
const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
|
|
28074
28218
|
await replyToInboundRaw({
|
|
@@ -28194,7 +28338,7 @@ var init_handler6 = __esm({
|
|
|
28194
28338
|
|
|
28195
28339
|
// packages/server/dist/routes/feishu.js
|
|
28196
28340
|
import { Hono as Hono19 } from "hono";
|
|
28197
|
-
import
|
|
28341
|
+
import fs56 from "node:fs";
|
|
28198
28342
|
function createFeishuRoutes(deps) {
|
|
28199
28343
|
const { db, channel } = deps;
|
|
28200
28344
|
const app2 = new Hono19();
|
|
@@ -28218,7 +28362,7 @@ function createFeishuRoutes(deps) {
|
|
|
28218
28362
|
botOpenId: a.botOpenId,
|
|
28219
28363
|
hasEncryptKey: !!a.encryptKey,
|
|
28220
28364
|
workspacePath: a.workspacePath,
|
|
28221
|
-
workspaceMissing: !
|
|
28365
|
+
workspaceMissing: !fs56.existsSync(a.workspacePath),
|
|
28222
28366
|
label: a.label,
|
|
28223
28367
|
enabled: a.enabled,
|
|
28224
28368
|
accessLevel: a.accessLevel,
|
|
@@ -28606,14 +28750,14 @@ function passwordStrengthError(pw) {
|
|
|
28606
28750
|
}
|
|
28607
28751
|
function authMiddleware() {
|
|
28608
28752
|
return async (c, next) => {
|
|
28609
|
-
const
|
|
28610
|
-
if (PUBLIC_PATHS.some((p) =>
|
|
28753
|
+
const path66 = c.req.path;
|
|
28754
|
+
if (PUBLIC_PATHS.some((p) => path66 === p || path66.startsWith(p + "?"))) {
|
|
28611
28755
|
return next();
|
|
28612
28756
|
}
|
|
28613
28757
|
const token = getCookie(c, COOKIE_NAME);
|
|
28614
28758
|
const payload = validateToken(token);
|
|
28615
28759
|
if (!payload) {
|
|
28616
|
-
if (!
|
|
28760
|
+
if (!path66.startsWith("/api/")) {
|
|
28617
28761
|
return next();
|
|
28618
28762
|
}
|
|
28619
28763
|
return c.json({ error: "Unauthorized" }, 401);
|
|
@@ -28648,20 +28792,21 @@ var init_auth = __esm({
|
|
|
28648
28792
|
var dist_exports = {};
|
|
28649
28793
|
import { Hono as Hono21 } from "hono";
|
|
28650
28794
|
import { cors } from "hono/cors";
|
|
28795
|
+
import { compress } from "hono/compress";
|
|
28651
28796
|
import { serve } from "@hono/node-server";
|
|
28652
28797
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
28653
28798
|
import { WebSocketServer } from "ws";
|
|
28654
|
-
import
|
|
28655
|
-
import
|
|
28656
|
-
import { spawnSync as
|
|
28799
|
+
import path64 from "node:path";
|
|
28800
|
+
import fs57 from "node:fs";
|
|
28801
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
28657
28802
|
import { homedir as homedir24 } from "node:os";
|
|
28658
28803
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
28659
28804
|
function findProjectRoot() {
|
|
28660
28805
|
let dir = process.cwd();
|
|
28661
28806
|
for (let i = 0; i < 32; i++) {
|
|
28662
|
-
if (
|
|
28807
|
+
if (fs57.existsSync(path64.join(dir, "pnpm-workspace.yaml")))
|
|
28663
28808
|
return dir;
|
|
28664
|
-
const parent =
|
|
28809
|
+
const parent = path64.dirname(dir);
|
|
28665
28810
|
if (parent === dir)
|
|
28666
28811
|
break;
|
|
28667
28812
|
dir = parent;
|
|
@@ -28670,15 +28815,15 @@ function findProjectRoot() {
|
|
|
28670
28815
|
return process.cwd();
|
|
28671
28816
|
}
|
|
28672
28817
|
function acquireSingleInstanceLock(lockFile) {
|
|
28673
|
-
|
|
28674
|
-
const fd =
|
|
28675
|
-
const result =
|
|
28818
|
+
fs57.mkdirSync(path64.dirname(lockFile), { recursive: true });
|
|
28819
|
+
const fd = fs57.openSync(lockFile, fs57.constants.O_RDWR | fs57.constants.O_CREAT, 420);
|
|
28820
|
+
const result = spawnSync4("flock", ["-n", "3"], {
|
|
28676
28821
|
stdio: ["ignore", "ignore", "ignore", fd]
|
|
28677
28822
|
});
|
|
28678
28823
|
if (result.error) {
|
|
28679
28824
|
const content = readLockPid(fd);
|
|
28680
28825
|
if (content > 0 && isProcessAlive(content)) {
|
|
28681
|
-
|
|
28826
|
+
fs57.closeSync(fd);
|
|
28682
28827
|
console.error(`[Server] another halo server is already running (pid ${content}). kill it first.`);
|
|
28683
28828
|
process.exit(1);
|
|
28684
28829
|
}
|
|
@@ -28687,19 +28832,19 @@ function acquireSingleInstanceLock(lockFile) {
|
|
|
28687
28832
|
if (content > 0 && !isProcessAlive(content)) {
|
|
28688
28833
|
console.warn(`[Server] removing stale server.lock (pid ${content} not running)`);
|
|
28689
28834
|
} else {
|
|
28690
|
-
|
|
28835
|
+
fs57.closeSync(fd);
|
|
28691
28836
|
console.error(`[Server] another halo server is already running${content > 0 ? ` (pid ${content})` : ""}. kill it first.`);
|
|
28692
28837
|
process.exit(1);
|
|
28693
28838
|
}
|
|
28694
28839
|
}
|
|
28695
|
-
|
|
28696
|
-
|
|
28697
|
-
|
|
28840
|
+
fs57.ftruncateSync(fd);
|
|
28841
|
+
fs57.writeSync(fd, String(process.pid));
|
|
28842
|
+
fs57.fsyncSync(fd);
|
|
28698
28843
|
}
|
|
28699
28844
|
function readLockPid(fd) {
|
|
28700
28845
|
try {
|
|
28701
28846
|
const buf = Buffer.alloc(32);
|
|
28702
|
-
const n =
|
|
28847
|
+
const n = fs57.readSync(fd, buf, 0, 32, 0);
|
|
28703
28848
|
return parseInt(buf.subarray(0, n).toString("utf-8").trim(), 10) || 0;
|
|
28704
28849
|
} catch {
|
|
28705
28850
|
return 0;
|
|
@@ -28708,7 +28853,7 @@ function readLockPid(fd) {
|
|
|
28708
28853
|
function isProcessAlive(pid) {
|
|
28709
28854
|
if (process.platform === "win32") {
|
|
28710
28855
|
try {
|
|
28711
|
-
const out =
|
|
28856
|
+
const out = spawnSync4("tasklist", ["/FI", `PID eq ${pid}`, "/NH", "/FO", "CSV"], {
|
|
28712
28857
|
encoding: "utf-8",
|
|
28713
28858
|
windowsHide: true
|
|
28714
28859
|
});
|
|
@@ -28727,16 +28872,16 @@ function isProcessAlive(pid) {
|
|
|
28727
28872
|
}
|
|
28728
28873
|
}
|
|
28729
28874
|
function readGitSha() {
|
|
28730
|
-
if (!
|
|
28875
|
+
if (!fs57.existsSync(path64.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs57.existsSync(path64.join(PROJECT_ROOT, ".git")))
|
|
28731
28876
|
return null;
|
|
28732
28877
|
try {
|
|
28733
|
-
const rev =
|
|
28878
|
+
const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
|
|
28734
28879
|
if (rev.status !== 0)
|
|
28735
28880
|
return null;
|
|
28736
28881
|
const sha = rev.stdout.trim();
|
|
28737
28882
|
if (!sha)
|
|
28738
28883
|
return null;
|
|
28739
|
-
const dirty =
|
|
28884
|
+
const dirty = spawnSync4("git", ["status", "--porcelain"], { cwd: PROJECT_ROOT, encoding: "utf-8" }).stdout.trim().length > 0;
|
|
28740
28885
|
return dirty ? `${sha}-dirty` : sha;
|
|
28741
28886
|
} catch {
|
|
28742
28887
|
return null;
|
|
@@ -28744,14 +28889,14 @@ function readGitSha() {
|
|
|
28744
28889
|
}
|
|
28745
28890
|
function resolveFrontendDir() {
|
|
28746
28891
|
if (process.env.HALO_FRONTEND_DIR)
|
|
28747
|
-
return
|
|
28748
|
-
const bundled =
|
|
28749
|
-
if (
|
|
28892
|
+
return path64.resolve(process.env.HALO_FRONTEND_DIR);
|
|
28893
|
+
const bundled = path64.resolve(__dirname3, "..", "admin-out");
|
|
28894
|
+
if (fs57.existsSync(path64.join(bundled, "index.html")))
|
|
28750
28895
|
return bundled;
|
|
28751
|
-
const sibling =
|
|
28752
|
-
if (
|
|
28896
|
+
const sibling = path64.resolve(__dirname3, "..", "..", "admin", "out");
|
|
28897
|
+
if (fs57.existsSync(path64.join(sibling, "index.html")))
|
|
28753
28898
|
return sibling;
|
|
28754
|
-
return
|
|
28899
|
+
return path64.resolve(PROJECT_ROOT, "packages", "admin", "out");
|
|
28755
28900
|
}
|
|
28756
28901
|
async function gracefulShutdown(signal) {
|
|
28757
28902
|
console.log(`[Server] Received ${signal}, shutting down gracefully...`);
|
|
@@ -28807,13 +28952,13 @@ var init_dist2 = __esm({
|
|
|
28807
28952
|
init_sandbox();
|
|
28808
28953
|
init_init();
|
|
28809
28954
|
init_git_ssh();
|
|
28810
|
-
__dirname3 =
|
|
28955
|
+
__dirname3 = path64.dirname(fileURLToPath3(import.meta.url));
|
|
28811
28956
|
PORT = config.server.port;
|
|
28812
|
-
HALO_VERSION = "1.1.
|
|
28957
|
+
HALO_VERSION = "1.1.2";
|
|
28813
28958
|
PROJECT_ROOT = findProjectRoot();
|
|
28814
|
-
HALO_HOME4 =
|
|
28959
|
+
HALO_HOME4 = path64.join(homedir24(), ".halo");
|
|
28815
28960
|
GIT_SHA = readGitSha();
|
|
28816
|
-
if (!
|
|
28961
|
+
if (!fs57.existsSync(path64.join(HALO_HOME4, "global", ".template-version"))) {
|
|
28817
28962
|
process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
|
|
28818
28963
|
process.exit(1);
|
|
28819
28964
|
}
|
|
@@ -28834,7 +28979,7 @@ var init_dist2 = __esm({
|
|
|
28834
28979
|
process.exit(1);
|
|
28835
28980
|
}
|
|
28836
28981
|
if (!AGENTCORE)
|
|
28837
|
-
acquireSingleInstanceLock(
|
|
28982
|
+
acquireSingleInstanceLock(path64.join(HALO_HOME4, "global", "server.lock"));
|
|
28838
28983
|
{
|
|
28839
28984
|
const declared = new Set(commandRegistry.listDescriptors().filter((d) => d.source === "builtin").map((d) => d.slashName));
|
|
28840
28985
|
const dispatched = new Set(DISPATCH_COMMANDS);
|
|
@@ -28873,6 +29018,7 @@ var init_dist2 = __esm({
|
|
|
28873
29018
|
maxAge: 86400,
|
|
28874
29019
|
credentials: allowlist.length > 0
|
|
28875
29020
|
}));
|
|
29021
|
+
app.use("/*", compress({ threshold: 1024 }));
|
|
28876
29022
|
app.use("/api/*", authMiddleware());
|
|
28877
29023
|
authRoutes = createAuthRoutes();
|
|
28878
29024
|
app.route("/api", authRoutes);
|
|
@@ -28902,10 +29048,10 @@ var init_dist2 = __esm({
|
|
|
28902
29048
|
app.route("/api", evolutionRoutes);
|
|
28903
29049
|
cronRoutes = createCronRoutes();
|
|
28904
29050
|
app.route("/api", cronRoutes);
|
|
28905
|
-
channelDb = createChannelDb(
|
|
29051
|
+
channelDb = createChannelDb(path64.join(HALO_HOME4, "secrets"));
|
|
28906
29052
|
setChannelDb(channelDb);
|
|
28907
|
-
setEvoDb(createEvoDb(
|
|
28908
|
-
setCronDb(createCronDb(
|
|
29053
|
+
setEvoDb(createEvoDb(path64.join(HALO_HOME4, "global")));
|
|
29054
|
+
setCronDb(createCronDb(path64.join(HALO_HOME4, "global")));
|
|
28909
29055
|
setEvoSpawner(realEvoSpawner);
|
|
28910
29056
|
if (!AGENTCORE) {
|
|
28911
29057
|
startEvoTicker();
|
|
@@ -28931,11 +29077,11 @@ var init_dist2 = __esm({
|
|
|
28931
29077
|
console.log(`[Server] AgentCore runtime mode \u2014 workspace: ${config.server.agentcoreWorkspace}`);
|
|
28932
29078
|
}
|
|
28933
29079
|
FRONTEND_DIR = resolveFrontendDir();
|
|
28934
|
-
app.use("/*", serveStatic({ root:
|
|
29080
|
+
app.use("/*", serveStatic({ root: path64.relative(process.cwd(), FRONTEND_DIR) }));
|
|
28935
29081
|
app.get("/*", (c) => {
|
|
28936
|
-
const indexPath =
|
|
29082
|
+
const indexPath = path64.join(FRONTEND_DIR, "index.html");
|
|
28937
29083
|
try {
|
|
28938
|
-
const html =
|
|
29084
|
+
const html = fs57.readFileSync(indexPath, "utf-8");
|
|
28939
29085
|
return c.html(html);
|
|
28940
29086
|
} catch {
|
|
28941
29087
|
return c.text("Frontend not built. Run: cd packages/admin && npx next build", 503);
|
|
@@ -29706,11 +29852,11 @@ process.emitWarning = (warning, ...args) => {
|
|
|
29706
29852
|
|
|
29707
29853
|
// packages/cli/src/index.ts
|
|
29708
29854
|
init_server_supervisor();
|
|
29709
|
-
import
|
|
29710
|
-
import
|
|
29855
|
+
import path65 from "node:path";
|
|
29856
|
+
import fs58 from "node:fs";
|
|
29711
29857
|
import { homedir as homedir25 } from "node:os";
|
|
29712
29858
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
29713
|
-
var VERSION = "1.1.
|
|
29859
|
+
var VERSION = "1.1.2";
|
|
29714
29860
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
29715
29861
|
|
|
29716
29862
|
Commands:
|
|
@@ -29967,8 +30113,8 @@ async function cmdSetup(argv = []) {
|
|
|
29967
30113
|
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));
|
|
29968
30114
|
const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
|
|
29969
30115
|
const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
|
|
29970
|
-
const haloHome2 =
|
|
29971
|
-
const globalDir2 =
|
|
30116
|
+
const haloHome2 = path65.join(homedir25(), ".halo");
|
|
30117
|
+
const globalDir2 = path65.join(haloHome2, "global");
|
|
29972
30118
|
ensureHaloHome2(haloHome2);
|
|
29973
30119
|
process.stderr.write(`[setup] ${haloHome2}/ ready
|
|
29974
30120
|
`);
|
|
@@ -30271,10 +30417,10 @@ async function cmdUpgrade() {
|
|
|
30271
30417
|
);
|
|
30272
30418
|
process.exit(1);
|
|
30273
30419
|
}
|
|
30274
|
-
const { spawnSync:
|
|
30420
|
+
const { spawnSync: spawnSync5 } = await import("node:child_process");
|
|
30275
30421
|
process.stderr.write(`Checking npm for the latest ${PKG}\u2026
|
|
30276
30422
|
`);
|
|
30277
|
-
const view =
|
|
30423
|
+
const view = spawnSync5("npm", ["view", PKG, "version"], { encoding: "utf-8" });
|
|
30278
30424
|
if (view.error || view.status !== 0) {
|
|
30279
30425
|
const err = view.error?.message ?? view.stderr?.trim() ?? `exit ${view.status}`;
|
|
30280
30426
|
process.stderr.write(`halo: \`npm view\` failed \u2014 ${err}
|
|
@@ -30292,10 +30438,40 @@ async function cmdUpgrade() {
|
|
|
30292
30438
|
`);
|
|
30293
30439
|
return;
|
|
30294
30440
|
}
|
|
30441
|
+
const { ALLOW_SCRIPTS: ALLOW_SCRIPTS2, verifyNativeModules: verifyNativeModules2 } = await Promise.resolve().then(() => (init_upgrade_verify(), upgrade_verify_exports));
|
|
30295
30442
|
process.stderr.write(`Upgrading ${installed} \u2192 ${latest}\u2026
|
|
30296
30443
|
`);
|
|
30297
|
-
const install =
|
|
30444
|
+
const install = spawnSync5(
|
|
30445
|
+
"npm",
|
|
30446
|
+
["install", "-g", `--allow-scripts=${ALLOW_SCRIPTS2}`, `${PKG}@latest`],
|
|
30447
|
+
{ stdio: "inherit" }
|
|
30448
|
+
);
|
|
30298
30449
|
if (install.status === 0) {
|
|
30450
|
+
const npmRoot = spawnSync5("npm", ["root", "-g"], {
|
|
30451
|
+
encoding: "utf-8",
|
|
30452
|
+
shell: process.platform === "win32"
|
|
30453
|
+
});
|
|
30454
|
+
const rootDir = npmRoot.status === 0 ? npmRoot.stdout.trim() : "";
|
|
30455
|
+
if (!rootDir) {
|
|
30456
|
+
process.stderr.write(`halo: \`npm root -g\` failed \u2014 cannot verify the new install
|
|
30457
|
+
`);
|
|
30458
|
+
process.exit(1);
|
|
30459
|
+
}
|
|
30460
|
+
const broken = verifyNativeModules2(path65.join(rootDir, PKG));
|
|
30461
|
+
if (broken.length > 0) {
|
|
30462
|
+
process.stderr.write(
|
|
30463
|
+
`
|
|
30464
|
+
halo: upgrade installed, but native module(s) failed to load: ${broken.join(", ")}
|
|
30465
|
+
This usually means npm blocked the module's install scripts (npm 12+ allowScripts policy \u2014 check \`npm -v\`).
|
|
30466
|
+
|
|
30467
|
+
Fix \u2014 reinstall with the scripts allowed:
|
|
30468
|
+
sudo npm install -g --allow-scripts=${ALLOW_SCRIPTS2} ${PKG}@latest
|
|
30469
|
+
Or persist the allowance first, then reinstall:
|
|
30470
|
+
npm config set allow-scripts=${ALLOW_SCRIPTS2} --location=user
|
|
30471
|
+
`
|
|
30472
|
+
);
|
|
30473
|
+
process.exit(1);
|
|
30474
|
+
}
|
|
30299
30475
|
process.stderr.write(
|
|
30300
30476
|
`
|
|
30301
30477
|
Upgraded to ${latest}. Run \`halo server restart\` (or your service manager's restart) to pick up the new templates.
|
|
@@ -30312,7 +30488,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
|
|
|
30312
30488
|
async function cmdAgents(flags) {
|
|
30313
30489
|
const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
30314
30490
|
await initRuntime2();
|
|
30315
|
-
const workspace =
|
|
30491
|
+
const workspace = path65.resolve(flags.workspace ?? process.cwd());
|
|
30316
30492
|
const agents = await listAgents2(workspace);
|
|
30317
30493
|
if (agents.length === 0) {
|
|
30318
30494
|
process.stderr.write("No agents found.\n");
|
|
@@ -30328,7 +30504,7 @@ async function cmdAgents(flags) {
|
|
|
30328
30504
|
async function cmdSessions(flags) {
|
|
30329
30505
|
const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
30330
30506
|
await initRuntime2();
|
|
30331
|
-
const workspace =
|
|
30507
|
+
const workspace = path65.resolve(flags.workspace ?? process.cwd());
|
|
30332
30508
|
const sessions = listSessions2(workspace);
|
|
30333
30509
|
if (sessions.length === 0) {
|
|
30334
30510
|
process.stderr.write("No sessions found.\n");
|
|
@@ -30441,11 +30617,11 @@ ${stdinContent}` : stdinContent;
|
|
|
30441
30617
|
harness.destroy();
|
|
30442
30618
|
}
|
|
30443
30619
|
}
|
|
30444
|
-
var SERVER_LOCK_PATH =
|
|
30445
|
-
var SERVER_LOG_PATH =
|
|
30620
|
+
var SERVER_LOCK_PATH = path65.join(homedir25(), ".halo", "global", "server.lock");
|
|
30621
|
+
var SERVER_LOG_PATH = path65.join(homedir25(), ".halo", "logs", "server.log");
|
|
30446
30622
|
function readServerPid() {
|
|
30447
30623
|
try {
|
|
30448
|
-
const txt =
|
|
30624
|
+
const txt = fs58.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
|
|
30449
30625
|
const n = parseInt(txt, 10);
|
|
30450
30626
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
30451
30627
|
} catch {
|
|
@@ -30532,8 +30708,8 @@ async function cmdServerStart(argv, _unused) {
|
|
|
30532
30708
|
return;
|
|
30533
30709
|
}
|
|
30534
30710
|
const { spawn: spawn5 } = await import("node:child_process");
|
|
30535
|
-
|
|
30536
|
-
const out =
|
|
30711
|
+
fs58.mkdirSync(path65.dirname(SERVER_LOG_PATH), { recursive: true });
|
|
30712
|
+
const out = fs58.openSync(SERVER_LOG_PATH, "a");
|
|
30537
30713
|
const child = spawn5(process.argv[0], [process.argv[1], "server", "start"], {
|
|
30538
30714
|
detached: true,
|
|
30539
30715
|
stdio: ["ignore", out, out],
|
|
@@ -30634,7 +30810,7 @@ async function cmdServerLogs(argv) {
|
|
|
30634
30810
|
lines: { type: "string", short: "n" }
|
|
30635
30811
|
}
|
|
30636
30812
|
});
|
|
30637
|
-
if (!
|
|
30813
|
+
if (!fs58.existsSync(SERVER_LOG_PATH)) {
|
|
30638
30814
|
process.stderr.write(`No log file at ${SERVER_LOG_PATH}
|
|
30639
30815
|
`);
|
|
30640
30816
|
return;
|
|
@@ -30652,7 +30828,7 @@ function exitMissingSetup() {
|
|
|
30652
30828
|
process.exit(1);
|
|
30653
30829
|
}
|
|
30654
30830
|
function isHaloHomeReady() {
|
|
30655
|
-
return
|
|
30831
|
+
return fs58.existsSync(path65.join(homedir25(), ".halo", "global", ".template-version"));
|
|
30656
30832
|
}
|
|
30657
30833
|
async function main2() {
|
|
30658
30834
|
const argv = process.argv.slice(2);
|