@turmind/halo 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/212-c5f046048be9c367.js +1 -0
- package/admin-out/_next/static/chunks/4-e7ba0b1ced30190b.js +1 -0
- package/admin-out/_next/static/chunks/app/page-5a6cf2b73a12b849.js +1 -0
- package/admin-out/_next/static/css/62b8bf49e6404ffc.css +3 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/agent.md +2 -1
- package/bundled-docs/design/cron.md +9 -3
- package/bundled-docs/design/session.md +3 -1
- package/bundled-docs/design/storage.md +2 -1
- package/bundled-docs/design/ws.md +30 -8
- package/bundled-docs/dev/add-model-provider.md +1 -1
- package/bundled-docs/dev/api.md +37 -3
- package/bundled-docs/dev/env.md +2 -0
- package/bundled-docs/dev/tools.md +13 -4
- package/bundled-docs/guide/chat.md +1 -1
- package/bundled-docs/guide/workspace.md +2 -2
- package/bundled-docs/requirements/chat.md +1 -1
- package/bundled-docs/requirements/settings.md +8 -0
- package/dist/index.js +440 -263
- package/package.json +1 -1
- package/templates/agents/__apply_agent__/agent.yaml +1 -1
- package/templates/agents/__evo_agent__/agent.yaml +1 -1
- package/templates/agents/__score__/agent.yaml +1 -1
- package/templates/agents/deep-executor/agent.yaml +1 -1
- package/templates/agents/default/agent.yaml +1 -1
- package/templates/agents/goal/agent.yaml +1 -1
- package/templates/models/aws-bedrock-claude-invoke.yaml +27 -0
- package/templates/models/aws-bedrock-mantle.yaml +13 -2
- package/templates/skills/agent/SKILL.md +2 -2
- package/admin-out/_next/static/chunks/212-e217e00ce6cc2bdc.js +0 -1
- package/admin-out/_next/static/chunks/397-fc5a406d80d46ca7.js +0 -1
- package/admin-out/_next/static/chunks/app/page-19d5f2a0a3412b4a.js +0 -1
- package/admin-out/_next/static/css/9e7dadb24830255a.css +0 -3
- /package/admin-out/_next/static/{aWThs2cmYntOEy-YdkDPD → CWrUwTypkebkhwZ5i82_G}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{aWThs2cmYntOEy-YdkDPD → CWrUwTypkebkhwZ5i82_G}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -479,6 +479,17 @@ function modelsRegistry() {
|
|
|
479
479
|
_modelsRegistryCache = loadModelsRegistry();
|
|
480
480
|
return _modelsRegistryCache;
|
|
481
481
|
}
|
|
482
|
+
function getSystemConfig() {
|
|
483
|
+
let mtimeMs = 0;
|
|
484
|
+
try {
|
|
485
|
+
mtimeMs = fs3.statSync(SYSTEM_CONFIG_PATH).mtimeMs;
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
if (!_systemConfigCache || _systemConfigCache.mtimeMs !== mtimeMs) {
|
|
489
|
+
_systemConfigCache = { mtimeMs, data: loadYamlFile(SYSTEM_CONFIG_PATH) };
|
|
490
|
+
}
|
|
491
|
+
return _systemConfigCache.data;
|
|
492
|
+
}
|
|
482
493
|
function getSettings() {
|
|
483
494
|
let mtimeMs = 0;
|
|
484
495
|
try {
|
|
@@ -514,7 +525,7 @@ function settingsValue(dotPath) {
|
|
|
514
525
|
return v == null ? void 0 : typeof v === "string" ? v : String(v);
|
|
515
526
|
}
|
|
516
527
|
function configValue(dotPath) {
|
|
517
|
-
return readYamlValue(
|
|
528
|
+
return readYamlValue(getSystemConfig(), dotPath);
|
|
518
529
|
}
|
|
519
530
|
function envInt(key, fallback) {
|
|
520
531
|
const v = process.env[key];
|
|
@@ -711,7 +722,7 @@ function modelSupportsAudio(modelId, override) {
|
|
|
711
722
|
function getModelsRegistry() {
|
|
712
723
|
return modelsRegistry();
|
|
713
724
|
}
|
|
714
|
-
var HALO_HOME2, HALO_GLOBAL_DIR, HALO_SECRETS_DIR, _modelsRegistryCache,
|
|
725
|
+
var HALO_HOME2, HALO_GLOBAL_DIR, HALO_SECRETS_DIR, _modelsRegistryCache, SYSTEM_CONFIG_PATH, _systemConfigCache, SETTINGS_PATH, _settingsCache, _schemaDefaultsCache, ENV_PATTERN, _wsSettingsCache, config, HIDDEN_DIRS_DEFAULT, HIDDEN_FILES_DEFAULT;
|
|
715
726
|
var init_config = __esm({
|
|
716
727
|
"packages/server/dist/config.js"() {
|
|
717
728
|
"use strict";
|
|
@@ -720,7 +731,8 @@ var init_config = __esm({
|
|
|
720
731
|
HALO_GLOBAL_DIR = path3.join(HALO_HOME2, "global");
|
|
721
732
|
HALO_SECRETS_DIR = path3.join(HALO_HOME2, "secrets");
|
|
722
733
|
_modelsRegistryCache = null;
|
|
723
|
-
|
|
734
|
+
SYSTEM_CONFIG_PATH = path3.join(HALO_SECRETS_DIR, "config.yaml");
|
|
735
|
+
_systemConfigCache = null;
|
|
724
736
|
SETTINGS_PATH = path3.join(HALO_SECRETS_DIR, "settings.yaml");
|
|
725
737
|
_settingsCache = null;
|
|
726
738
|
_schemaDefaultsCache = null;
|
|
@@ -2206,7 +2218,7 @@ var init_init = __esm({
|
|
|
2206
2218
|
path6.resolve(__dirname, "..", "bundled-docs"),
|
|
2207
2219
|
path6.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
2208
2220
|
];
|
|
2209
|
-
TEMPLATE_VERSION =
|
|
2221
|
+
TEMPLATE_VERSION = 49;
|
|
2210
2222
|
VERSION_FILE = ".template-version";
|
|
2211
2223
|
SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
|
|
2212
2224
|
BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
@@ -4126,7 +4138,7 @@ function applyEvent(state, event) {
|
|
|
4126
4138
|
break;
|
|
4127
4139
|
case "tool_call": {
|
|
4128
4140
|
const inputStr = formatToolInput(event.toolInput);
|
|
4129
|
-
const entry = { name: event.toolName ?? "", input: inputStr };
|
|
4141
|
+
const entry = { name: event.toolName ?? "", input: inputStr, toolUseId: event.toolUseId };
|
|
4130
4142
|
const msg = {
|
|
4131
4143
|
id: genId(),
|
|
4132
4144
|
type: "tool_call",
|
|
@@ -6276,7 +6288,8 @@ var init_mantle_agent = __esm({
|
|
|
6276
6288
|
};
|
|
6277
6289
|
if (this.config.thinking?.enabled && this.config.thinking.effort) {
|
|
6278
6290
|
const e = this.config.thinking.effort;
|
|
6279
|
-
|
|
6291
|
+
const valid = ["low", "medium", "high", "xhigh", "max"];
|
|
6292
|
+
body.reasoning = { effort: valid.includes(e) ? e : "high" };
|
|
6280
6293
|
}
|
|
6281
6294
|
const payload = JSON.stringify(body);
|
|
6282
6295
|
const response = this.config.apiKey ? await fetch(url, {
|
|
@@ -6934,6 +6947,16 @@ function buildBwrapArgs(opts) {
|
|
|
6934
6947
|
args.push("--bind", dir, dir);
|
|
6935
6948
|
}
|
|
6936
6949
|
}
|
|
6950
|
+
for (const rel of WORKSPACE_HIDDEN_DIRS) {
|
|
6951
|
+
const dir = path15.join(opts.workspaceRoot, rel);
|
|
6952
|
+
if (existsSync(dir))
|
|
6953
|
+
args.push("--tmpfs", dir);
|
|
6954
|
+
}
|
|
6955
|
+
for (const rel of WORKSPACE_HIDDEN_FILES) {
|
|
6956
|
+
const file = path15.join(opts.workspaceRoot, rel);
|
|
6957
|
+
if (existsSync(file))
|
|
6958
|
+
args.push("--ro-bind", "/dev/null", file);
|
|
6959
|
+
}
|
|
6937
6960
|
args.push("--proc", "/proc");
|
|
6938
6961
|
args.push("--dev", "/dev");
|
|
6939
6962
|
args.push("--clearenv");
|
|
@@ -6968,6 +6991,9 @@ function assertPathAllowed(filePath, opts, write = false) {
|
|
|
6968
6991
|
const resolved = realpathBounded(filePath);
|
|
6969
6992
|
const wsRoot = realpathBounded(opts.workspaceRoot);
|
|
6970
6993
|
if (resolved === wsRoot || resolved.startsWith(wsRoot + "/")) {
|
|
6994
|
+
if (isHiddenWorkspacePath(resolved, wsRoot)) {
|
|
6995
|
+
throw new Error(`Access denied: "${filePath}" is outside the allowed sandbox paths`);
|
|
6996
|
+
}
|
|
6971
6997
|
if (write && opts.accessLevel === "readonly") {
|
|
6972
6998
|
throw new Error(`Access denied: readonly session cannot write to "${filePath}"`);
|
|
6973
6999
|
}
|
|
@@ -6990,6 +7016,18 @@ function isHiddenGlobalPath(resolved) {
|
|
|
6990
7016
|
}
|
|
6991
7017
|
return false;
|
|
6992
7018
|
}
|
|
7019
|
+
function isHiddenWorkspacePath(resolved, wsRoot) {
|
|
7020
|
+
for (const rel of WORKSPACE_HIDDEN_FILES) {
|
|
7021
|
+
if (resolved === path15.join(wsRoot, rel))
|
|
7022
|
+
return true;
|
|
7023
|
+
}
|
|
7024
|
+
for (const rel of WORKSPACE_HIDDEN_DIRS) {
|
|
7025
|
+
const dir = path15.join(wsRoot, rel);
|
|
7026
|
+
if (resolved === dir || resolved.startsWith(dir + "/"))
|
|
7027
|
+
return true;
|
|
7028
|
+
}
|
|
7029
|
+
return false;
|
|
7030
|
+
}
|
|
6993
7031
|
function buildExecScriptArgs(workspaceRoot, command) {
|
|
6994
7032
|
return ["bash", "-c", `cd "$1" && shift && ${command}`, "bash", workspaceRoot];
|
|
6995
7033
|
}
|
|
@@ -7193,7 +7231,7 @@ async function sandboxReaddir(dirPath, opts) {
|
|
|
7193
7231
|
return { name: isDir ? name.slice(0, -1) : name, isDirectory: isDir };
|
|
7194
7232
|
});
|
|
7195
7233
|
}
|
|
7196
|
-
var execAsync, execFileAsync, HOME, HALO_HOME3, GLOBAL_DIR, KILL_GRACE_MS, _bwrapAvailable, DEFAULT_HIDDEN_DIRS, DEFAULT_HIDDEN_FILES, _hiddenDirs, _hiddenFiles, _writableDirs;
|
|
7234
|
+
var execAsync, execFileAsync, HOME, HALO_HOME3, GLOBAL_DIR, KILL_GRACE_MS, _bwrapAvailable, DEFAULT_HIDDEN_DIRS, DEFAULT_HIDDEN_FILES, WORKSPACE_HIDDEN_DIRS, WORKSPACE_HIDDEN_FILES, _hiddenDirs, _hiddenFiles, _writableDirs;
|
|
7197
7235
|
var init_sandbox = __esm({
|
|
7198
7236
|
"packages/server/dist/tools/sandbox.js"() {
|
|
7199
7237
|
"use strict";
|
|
@@ -7236,6 +7274,8 @@ var init_sandbox = __esm({
|
|
|
7236
7274
|
"~/.halo/global/cron.db-wal",
|
|
7237
7275
|
"~/.halo/global/cron.db-shm"
|
|
7238
7276
|
];
|
|
7277
|
+
WORKSPACE_HIDDEN_DIRS = [".halo/sessions", ".halo/logs", ".halo/evo"];
|
|
7278
|
+
WORKSPACE_HIDDEN_FILES = [".halo/halo.db", ".halo/halo.db-wal", ".halo/halo.db-shm"];
|
|
7239
7279
|
_hiddenDirs = DEFAULT_HIDDEN_DIRS;
|
|
7240
7280
|
_hiddenFiles = DEFAULT_HIDDEN_FILES;
|
|
7241
7281
|
_writableDirs = [];
|
|
@@ -8703,8 +8743,15 @@ var init_i18n = __esm({
|
|
|
8703
8743
|
"cmd.unknown": { zh: "\u672A\u77E5\u547D\u4EE4: {cmd}\n\u53D1 /help \u67E5\u770B\u53EF\u7528\u547D\u4EE4", en: "Unknown command: {cmd}\nSend /help to see available commands" },
|
|
8704
8744
|
// ── Channel handler messages ──
|
|
8705
8745
|
"handler.workspace_missing": { zh: "\u26A0\uFE0F \u8FD9\u4E2A bot \u7ED1\u5B9A\u7684 workspace \u5DF2\u4E0D\u5B58\u5728\uFF1A\n{path}\n\n\u8BF7\u5230 web \u7AEF\u7684 Channels \u8BBE\u7F6E\u91CC\u66F4\u65B0\u7ED1\u5B9A\u76EE\u5F55\u3002", en: "\u26A0\uFE0F The workspace bound to this bot no longer exists:\n{path}\n\nPlease update the binding in web Channels settings." },
|
|
8706
|
-
|
|
8746
|
+
// Both hints must read as "received, will be answered" — the message IS
|
|
8747
|
+
// queued (sendUserMessage queues compacting/busy sessions). Telling the user
|
|
8748
|
+
// to re-send would make them duplicate a message that was never lost.
|
|
8749
|
+
"handler.compacting": { zh: "\u23F3 \u6B63\u5728\u6574\u7406\u4E0A\u4E0B\u6587\uFF0C\u6D88\u606F\u5DF2\u6536\u5230\uFF0C\u6574\u7406\u5B8C\u6210\u540E\u7ACB\u5373\u5904\u7406\uFF08\u901A\u5E38 30 \u79D2\u5185\uFF09", en: "\u23F3 Compacting context \u2014 got your message, will process as soon as it finishes (usually under 30s)" },
|
|
8707
8750
|
"handler.queued": { zh: "\u{1F504} \u521A\u624D\u90A3\u6761\u8FD8\u5728\u5904\u7406\u4E2D\uFF0C\u6D88\u606F\u5DF2\u6392\u961F\uFF0C\u8BF7\u7A0D\u5019", en: "\u{1F504} Previous message still processing, queued" },
|
|
8751
|
+
// Outbound media upload failed — tells the user why no attachment showed up.
|
|
8752
|
+
// Plain text (no backticks): slack/feishu render markdown differently, and the
|
|
8753
|
+
// filename is already delimited by the surrounding punctuation.
|
|
8754
|
+
"handler.upload_failed": { zh: "\u26A0\uFE0F \u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF1A{name} \u2014 {error}", en: "\u26A0\uFE0F Failed to upload {name}: {error}" },
|
|
8708
8755
|
"handler.workspace_gone": { zh: "\u26A0\uFE0F workspace \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5230 web \u7AEF\u66F4\u65B0\u7ED1\u5B9A", en: "\u26A0\uFE0F Workspace does not exist, please update binding in web" },
|
|
8709
8756
|
"handler.not_allowed": { zh: "\u26A0\uFE0F \u4F60\u4E0D\u5728\u8FD9\u4E2A bot \u7684\u5141\u8BB8\u5217\u8868\u4E2D", en: "\u26A0\uFE0F You are not in this bot's allowed list" },
|
|
8710
8757
|
"handler.start_greeting": { zh: "\u{1F44B} Halo bot \u5DF2\u5C31\u7EEA\u3002\u76F4\u63A5\u53D1\u6D88\u606F\u5F00\u59CB\u5BF9\u8BDD\u3002\u53D1 /help \u67E5\u770B\u53EF\u7528\u547D\u4EE4\u3002", en: "\u{1F44B} Halo bot ready. Send a message to start. /help for commands." },
|
|
@@ -9765,12 +9812,12 @@ var init_session_manager = __esm({
|
|
|
9765
9812
|
if (loopStatus === "warn") {
|
|
9766
9813
|
this.emitEvent(session.id, { type: "system", text: `\u26A0\uFE0F Tool "${event.toolName}" called repeatedly with identical input. Consider a different approach.` });
|
|
9767
9814
|
}
|
|
9768
|
-
this.emitEvent(session.id, { type: "tool_call", toolName: event.toolName, toolInput: event.toolInput, agentName, agentId, taskId });
|
|
9815
|
+
this.emitEvent(session.id, { type: "tool_call", toolName: event.toolName, toolUseId: event.toolUseId, toolInput: event.toolInput, agentName, agentId, taskId });
|
|
9769
9816
|
break;
|
|
9770
9817
|
}
|
|
9771
9818
|
case "tool_result": {
|
|
9772
9819
|
const resultStr = event.toolResultFull ?? event.toolResult ?? "";
|
|
9773
|
-
this.emitEvent(session.id, { type: "tool_result", toolName: event.toolName, toolResult: resultStr, durationMs: event.durationMs, agentName, agentId, taskId });
|
|
9820
|
+
this.emitEvent(session.id, { type: "tool_result", toolName: event.toolName, toolUseId: event.toolUseId, toolResult: resultStr, durationMs: event.durationMs, agentName, agentId, taskId });
|
|
9774
9821
|
break;
|
|
9775
9822
|
}
|
|
9776
9823
|
case "usage": {
|
|
@@ -10136,6 +10183,7 @@ ${q.text}` : q.text).join("\n\n");
|
|
|
10136
10183
|
this.emitEvent(sessionId, {
|
|
10137
10184
|
type: "tool_result",
|
|
10138
10185
|
toolName: tc.name,
|
|
10186
|
+
toolUseId: tc.toolUseId,
|
|
10139
10187
|
toolResult: "[interrupted by user]",
|
|
10140
10188
|
agentName: session?.agentName,
|
|
10141
10189
|
agentId: session?.agentId,
|
|
@@ -10469,9 +10517,11 @@ ${cleaned}` : cleaned;
|
|
|
10469
10517
|
const overThreshold = session.lastContextTokens > session.contextConfig.maxTokens * session.contextConfig.compressAt;
|
|
10470
10518
|
if (!overThreshold)
|
|
10471
10519
|
return;
|
|
10520
|
+
if (this.compactCut(session.agent.messages) === 0)
|
|
10521
|
+
return;
|
|
10522
|
+
const taskId = session.parentId ? session.id : void 0;
|
|
10472
10523
|
try {
|
|
10473
10524
|
session.isCompacting = true;
|
|
10474
|
-
const taskId = session.parentId ? session.id : void 0;
|
|
10475
10525
|
const preflightText = `Compacting context (${Math.round(session.lastContextTokens / 1e3)}K tokens)\u2026`;
|
|
10476
10526
|
this.emitEvent(session.id, { type: "system", text: preflightText, taskId });
|
|
10477
10527
|
const result = await this.selfCompactSession(session.id);
|
|
@@ -10482,9 +10532,12 @@ ${cleaned}` : cleaned;
|
|
|
10482
10532
|
${result.summary}`, agentName: "user", report: true, taskId });
|
|
10483
10533
|
this.emitEvent(session.id, { type: "compacted", totalTokens: result.estimatedTokens, taskId });
|
|
10484
10534
|
console.debug(`[SessionManager] Auto-compact ${session.id}: ${result.olderCount} messages compacted`);
|
|
10535
|
+
} else {
|
|
10536
|
+
this.emitEvent(session.id, { type: "system", text: "Compaction skipped \u2014 no summary produced", taskId });
|
|
10485
10537
|
}
|
|
10486
10538
|
} catch (err) {
|
|
10487
10539
|
console.debug(`[SessionManager] Auto-compact ${session.id} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10540
|
+
this.emitEvent(session.id, { type: "system", text: "Compaction failed \u2014 context unchanged", taskId });
|
|
10488
10541
|
} finally {
|
|
10489
10542
|
session.isCompacting = false;
|
|
10490
10543
|
}
|
|
@@ -10576,19 +10629,25 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
10576
10629
|
}
|
|
10577
10630
|
}
|
|
10578
10631
|
/**
|
|
10579
|
-
*
|
|
10580
|
-
*
|
|
10581
|
-
*
|
|
10632
|
+
* Compute the compaction cut point: messages[0..cut) get summarized,
|
|
10633
|
+
* messages[cut..] are kept. Returns 0 when there is nothing to compact
|
|
10634
|
+
* (too few messages) — the "compaction is feasible" predicate.
|
|
10635
|
+
*
|
|
10636
|
+
* Single source of truth shared by selfCompactSession AND the
|
|
10637
|
+
* pre-preflight feasibility gates in maybeAutoCompact / compactSession:
|
|
10638
|
+
* gate and actual compact must run the same computation on the same
|
|
10639
|
+
* `session.agent.messages` reference or they drift ("gate said yes,
|
|
10640
|
+
* compact said no" re-creates the orphan-preflight bug).
|
|
10641
|
+
*
|
|
10642
|
+
* Note the tail loop only ever moves `cut` UP (a tail of user tool_result
|
|
10643
|
+
* messages extends the summarized region — they're protocol continuations
|
|
10644
|
+
* of an assistant turn, not standalone turns), so cut === 0 is exactly
|
|
10645
|
+
* the `messages.length <= keepCount` case.
|
|
10582
10646
|
*/
|
|
10583
|
-
|
|
10584
|
-
const session = this.sessions.get(sessionId);
|
|
10585
|
-
if (!session)
|
|
10586
|
-
return null;
|
|
10587
|
-
const messages2 = session.agent.messages;
|
|
10647
|
+
compactCut(messages2) {
|
|
10588
10648
|
const keepCount = config.compact.keep_messages;
|
|
10589
10649
|
if (messages2.length <= keepCount)
|
|
10590
|
-
return
|
|
10591
|
-
session.compactedThisTurn = true;
|
|
10650
|
+
return 0;
|
|
10592
10651
|
let cut = Math.max(0, messages2.length - keepCount);
|
|
10593
10652
|
while (cut < messages2.length) {
|
|
10594
10653
|
const m = messages2[cut];
|
|
@@ -10599,8 +10658,22 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
10599
10658
|
}
|
|
10600
10659
|
break;
|
|
10601
10660
|
}
|
|
10661
|
+
return cut;
|
|
10662
|
+
}
|
|
10663
|
+
/**
|
|
10664
|
+
* Self-compact: let the current agent summarize its own conversation.
|
|
10665
|
+
* The agent already has full context cached, so this is fast and lossless.
|
|
10666
|
+
* Returns the summary text, or null if compaction was not needed/failed.
|
|
10667
|
+
*/
|
|
10668
|
+
async selfCompactSession(sessionId, signal) {
|
|
10669
|
+
const session = this.sessions.get(sessionId);
|
|
10670
|
+
if (!session)
|
|
10671
|
+
return null;
|
|
10672
|
+
const messages2 = session.agent.messages;
|
|
10673
|
+
const cut = this.compactCut(messages2);
|
|
10602
10674
|
if (cut === 0)
|
|
10603
10675
|
return null;
|
|
10676
|
+
session.compactedThisTurn = true;
|
|
10604
10677
|
const olderCount = cut;
|
|
10605
10678
|
const compactInstruction = [
|
|
10606
10679
|
"Summarize the conversation so far into a concise summary that preserves:",
|
|
@@ -10684,9 +10757,8 @@ ${summaryText}` }]
|
|
|
10684
10757
|
return "running";
|
|
10685
10758
|
if (session.isCompacting)
|
|
10686
10759
|
return "already";
|
|
10687
|
-
const keepCount = config.compact.keep_messages;
|
|
10688
10760
|
const rawCount = session.agent.messages.length;
|
|
10689
|
-
if (
|
|
10761
|
+
if (this.compactCut(session.agent.messages) === 0)
|
|
10690
10762
|
return "nothing";
|
|
10691
10763
|
const ac = await this.beginCompact(sessionId);
|
|
10692
10764
|
if (!ac)
|
|
@@ -10698,8 +10770,10 @@ ${summaryText}` }]
|
|
|
10698
10770
|
this.emitEvent(sessionId, { type: "system", text: preflightText, taskId });
|
|
10699
10771
|
try {
|
|
10700
10772
|
const result = await this.selfCompactSession(sessionId, ac.signal);
|
|
10701
|
-
if (!result)
|
|
10773
|
+
if (!result) {
|
|
10774
|
+
this.emitEvent(sessionId, { type: "system", text: "Compaction skipped \u2014 no summary produced", taskId });
|
|
10702
10775
|
return "nothing";
|
|
10776
|
+
}
|
|
10703
10777
|
session.lastContextTokens = result.estimatedTokens;
|
|
10704
10778
|
const noticeText = `Context compacted: ${result.olderCount} older messages summarized`;
|
|
10705
10779
|
this.emitEvent(sessionId, { type: "system", text: noticeText, taskId });
|
|
@@ -10715,6 +10789,7 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
10715
10789
|
this.emitEvent(sessionId, { type: "system", text: "Compact cancelled" });
|
|
10716
10790
|
return "nothing";
|
|
10717
10791
|
}
|
|
10792
|
+
this.emitEvent(sessionId, { type: "system", text: "Compaction failed \u2014 context unchanged", taskId });
|
|
10718
10793
|
throw err;
|
|
10719
10794
|
} finally {
|
|
10720
10795
|
this.endCompact(sessionId);
|
|
@@ -13572,8 +13647,8 @@ function applyPathPick(value, ref, pick) {
|
|
|
13572
13647
|
const before = value.slice(0, markerStart);
|
|
13573
13648
|
const kind = ref.kind === "scope" ? "scope" : ref.kind === "image" ? "image" : pick.isImage ? "image" : "file";
|
|
13574
13649
|
const needsQuote = pick.insertPath.includes(" ");
|
|
13575
|
-
const
|
|
13576
|
-
return `${before}@${kind} ${
|
|
13650
|
+
const path63 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
|
|
13651
|
+
return `${before}@${kind} ${path63}`;
|
|
13577
13652
|
}
|
|
13578
13653
|
var IMAGE_EXTS2;
|
|
13579
13654
|
var init_path_suggest_engine = __esm({
|
|
@@ -15093,12 +15168,12 @@ None yet.
|
|
|
15093
15168
|
}
|
|
15094
15169
|
console.log(`[Workspace] Initialized workspace: ${this.projectRoot}`);
|
|
15095
15170
|
}
|
|
15096
|
-
async readFile(
|
|
15097
|
-
const fullPath = this.validatePath(
|
|
15171
|
+
async readFile(path63) {
|
|
15172
|
+
const fullPath = this.validatePath(path63);
|
|
15098
15173
|
return readFile(fullPath, "utf-8");
|
|
15099
15174
|
}
|
|
15100
|
-
async writeFile(
|
|
15101
|
-
const fullPath = this.validatePath(
|
|
15175
|
+
async writeFile(path63, content) {
|
|
15176
|
+
const fullPath = this.validatePath(path63);
|
|
15102
15177
|
const dir = resolve(fullPath, "..");
|
|
15103
15178
|
await mkdir(dir, { recursive: true });
|
|
15104
15179
|
await writeFile(fullPath, content, "utf-8");
|
|
@@ -15118,26 +15193,26 @@ None yet.
|
|
|
15118
15193
|
}
|
|
15119
15194
|
return results;
|
|
15120
15195
|
}
|
|
15121
|
-
async fileExists(
|
|
15196
|
+
async fileExists(path63) {
|
|
15122
15197
|
try {
|
|
15123
|
-
const fullPath = this.validatePath(
|
|
15198
|
+
const fullPath = this.validatePath(path63);
|
|
15124
15199
|
await access(fullPath);
|
|
15125
15200
|
return true;
|
|
15126
15201
|
} catch {
|
|
15127
15202
|
return false;
|
|
15128
15203
|
}
|
|
15129
15204
|
}
|
|
15130
|
-
validatePath(
|
|
15131
|
-
const fullPath = isAbsolute(
|
|
15205
|
+
validatePath(path63) {
|
|
15206
|
+
const fullPath = isAbsolute(path63) ? resolve(path63) : resolve(this.projectRoot, path63);
|
|
15132
15207
|
const root = resolve(this.projectRoot);
|
|
15133
15208
|
if (fullPath !== root && !fullPath.startsWith(root + sep)) {
|
|
15134
|
-
throw new WorkspaceError(`Path "${
|
|
15209
|
+
throw new WorkspaceError(`Path "${path63}" is outside the workspace directory "${this.projectRoot}"`);
|
|
15135
15210
|
}
|
|
15136
15211
|
try {
|
|
15137
15212
|
const real = realpathSync2(fullPath);
|
|
15138
15213
|
const realRoot = realpathSync2(root);
|
|
15139
15214
|
if (real !== realRoot && !real.startsWith(realRoot + sep)) {
|
|
15140
|
-
throw new WorkspaceError(`Path "${
|
|
15215
|
+
throw new WorkspaceError(`Path "${path63}" is outside the workspace directory "${this.projectRoot}"`);
|
|
15141
15216
|
}
|
|
15142
15217
|
} catch (err) {
|
|
15143
15218
|
if (err instanceof WorkspaceError)
|
|
@@ -15250,10 +15325,10 @@ var init_git_manager = __esm({
|
|
|
15250
15325
|
throw new GitManagerError(`Failed to commit: ${message2}`);
|
|
15251
15326
|
}
|
|
15252
15327
|
}
|
|
15253
|
-
async getDiff(
|
|
15328
|
+
async getDiff(path63) {
|
|
15254
15329
|
try {
|
|
15255
|
-
if (
|
|
15256
|
-
return await this.git.diff(["HEAD", "--",
|
|
15330
|
+
if (path63) {
|
|
15331
|
+
return await this.git.diff(["HEAD", "--", path63]);
|
|
15257
15332
|
}
|
|
15258
15333
|
return await this.git.diff(["HEAD"]);
|
|
15259
15334
|
} catch {
|
|
@@ -15342,12 +15417,12 @@ var init_git_manager = __esm({
|
|
|
15342
15417
|
const code = parts[0];
|
|
15343
15418
|
const status = code[0];
|
|
15344
15419
|
const isRenameOrCopy = status === "R" || status === "C";
|
|
15345
|
-
const
|
|
15346
|
-
if (!
|
|
15420
|
+
const path63 = isRenameOrCopy ? parts[2] : parts[1];
|
|
15421
|
+
if (!path63 || seen.has(path63))
|
|
15347
15422
|
continue;
|
|
15348
|
-
seen.add(
|
|
15423
|
+
seen.add(path63);
|
|
15349
15424
|
files.push({
|
|
15350
|
-
path:
|
|
15425
|
+
path: path63,
|
|
15351
15426
|
status,
|
|
15352
15427
|
from: isRenameOrCopy ? parts[1] : void 0
|
|
15353
15428
|
});
|
|
@@ -15495,9 +15570,9 @@ var init_git_manager = __esm({
|
|
|
15495
15570
|
* empty for an added file or the root commit) and `modified` is the version
|
|
15496
15571
|
* at `commit` (empty for a deletion). The working-tree/staged path above is
|
|
15497
15572
|
* left entirely untouched (commit === undefined preserves existing behavior). */
|
|
15498
|
-
async getFileDiff(
|
|
15573
|
+
async getFileDiff(path63, staged, from, commit) {
|
|
15499
15574
|
if (commit) {
|
|
15500
|
-
const parentPath = from ||
|
|
15575
|
+
const parentPath = from || path63;
|
|
15501
15576
|
let original2 = "";
|
|
15502
15577
|
try {
|
|
15503
15578
|
original2 = await this.git.show([`${commit}^:${parentPath}`]);
|
|
@@ -15506,13 +15581,13 @@ var init_git_manager = __esm({
|
|
|
15506
15581
|
}
|
|
15507
15582
|
let modified2 = "";
|
|
15508
15583
|
try {
|
|
15509
|
-
modified2 = await this.git.show([`${commit}:${
|
|
15584
|
+
modified2 = await this.git.show([`${commit}:${path63}`]);
|
|
15510
15585
|
} catch {
|
|
15511
15586
|
modified2 = "";
|
|
15512
15587
|
}
|
|
15513
15588
|
return { original: original2, modified: modified2 };
|
|
15514
15589
|
}
|
|
15515
|
-
const headPath = from ||
|
|
15590
|
+
const headPath = from || path63;
|
|
15516
15591
|
let original = "";
|
|
15517
15592
|
try {
|
|
15518
15593
|
original = await this.git.show([`HEAD:${headPath}`]);
|
|
@@ -15522,13 +15597,13 @@ var init_git_manager = __esm({
|
|
|
15522
15597
|
let modified = "";
|
|
15523
15598
|
if (staged) {
|
|
15524
15599
|
try {
|
|
15525
|
-
modified = await this.git.show([`:${
|
|
15600
|
+
modified = await this.git.show([`:${path63}`]);
|
|
15526
15601
|
} catch {
|
|
15527
15602
|
modified = "";
|
|
15528
15603
|
}
|
|
15529
15604
|
} else {
|
|
15530
15605
|
try {
|
|
15531
|
-
modified = await this.workspace.readFile(
|
|
15606
|
+
modified = await this.workspace.readFile(path63);
|
|
15532
15607
|
} catch {
|
|
15533
15608
|
modified = "";
|
|
15534
15609
|
}
|
|
@@ -15559,6 +15634,24 @@ function isInTempDir(filePath) {
|
|
|
15559
15634
|
const tmp = tempDir();
|
|
15560
15635
|
return resolved === tmp || resolved.startsWith(tmp + path32.sep);
|
|
15561
15636
|
}
|
|
15637
|
+
function extractMediaPaths(text5) {
|
|
15638
|
+
const mediaPaths = [];
|
|
15639
|
+
const stripped = text5.replace(MEDIA_MARKER_RE, (_m, p) => {
|
|
15640
|
+
if (p)
|
|
15641
|
+
mediaPaths.push(p);
|
|
15642
|
+
return "";
|
|
15643
|
+
});
|
|
15644
|
+
return { text: stripped, mediaPaths };
|
|
15645
|
+
}
|
|
15646
|
+
function extractMediaMessage(text5) {
|
|
15647
|
+
const { text: stripped, mediaPaths } = extractMediaPaths(text5);
|
|
15648
|
+
return { text: stripped.replace(/\n{3,}/g, "\n\n").trim(), mediaPaths };
|
|
15649
|
+
}
|
|
15650
|
+
function isMediaPathAllowed(filePath, workspacePath) {
|
|
15651
|
+
const resolved = path32.resolve(filePath);
|
|
15652
|
+
const ws = path32.resolve(workspacePath);
|
|
15653
|
+
return resolved === ws || resolved.startsWith(ws + path32.sep) || isInTempDir(resolved);
|
|
15654
|
+
}
|
|
15562
15655
|
function classifyMedia(filePath) {
|
|
15563
15656
|
const ext = path32.extname(filePath).toLowerCase();
|
|
15564
15657
|
if (IMAGE_EXTS3.has(ext))
|
|
@@ -15569,10 +15662,11 @@ function classifyMedia(filePath) {
|
|
|
15569
15662
|
return "voice";
|
|
15570
15663
|
return "other";
|
|
15571
15664
|
}
|
|
15572
|
-
var IMAGE_EXTS3, VIDEO_EXTS, VOICE_EXTS;
|
|
15665
|
+
var MEDIA_MARKER_RE, IMAGE_EXTS3, VIDEO_EXTS, VOICE_EXTS;
|
|
15573
15666
|
var init_media = __esm({
|
|
15574
15667
|
"packages/server/dist/channels/shared/media.js"() {
|
|
15575
15668
|
"use strict";
|
|
15669
|
+
MEDIA_MARKER_RE = /^MEDIA:\s*(\S.*?)\s*$/gm;
|
|
15576
15670
|
IMAGE_EXTS3 = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"]);
|
|
15577
15671
|
VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".avi"]);
|
|
15578
15672
|
VOICE_EXTS = /* @__PURE__ */ new Set([".ogg", ".oga", ".opus"]);
|
|
@@ -16484,12 +16578,20 @@ function createGitRoutes() {
|
|
|
16484
16578
|
const git = new GitManager(new Workspace(projectPath));
|
|
16485
16579
|
return { git, projectPath };
|
|
16486
16580
|
}
|
|
16581
|
+
async function getGitForRead(projectId) {
|
|
16582
|
+
const res = await getGit(projectId);
|
|
16583
|
+
if ("error" in res)
|
|
16584
|
+
return res;
|
|
16585
|
+
if (!await res.git.isRepoRoot())
|
|
16586
|
+
return { notRepo: true };
|
|
16587
|
+
return res;
|
|
16588
|
+
}
|
|
16487
16589
|
app2.get("/git/status", async (c) => {
|
|
16488
16590
|
try {
|
|
16489
|
-
const res = await
|
|
16591
|
+
const res = await getGitForRead(c.req.query("projectId"));
|
|
16490
16592
|
if ("error" in res)
|
|
16491
16593
|
return c.json({ error: res.error }, res.status);
|
|
16492
|
-
if (
|
|
16594
|
+
if ("notRepo" in res)
|
|
16493
16595
|
return c.json({ isRepo: false });
|
|
16494
16596
|
const status = await res.git.getStatus();
|
|
16495
16597
|
return c.json({ isRepo: true, ...status });
|
|
@@ -16501,9 +16603,11 @@ function createGitRoutes() {
|
|
|
16501
16603
|
});
|
|
16502
16604
|
app2.get("/git/ignored", async (c) => {
|
|
16503
16605
|
try {
|
|
16504
|
-
const res = await
|
|
16606
|
+
const res = await getGitForRead(c.req.query("projectId"));
|
|
16505
16607
|
if ("error" in res)
|
|
16506
16608
|
return c.json({ error: res.error }, res.status);
|
|
16609
|
+
if ("notRepo" in res)
|
|
16610
|
+
return c.json({ isRepo: false, ignored: [] });
|
|
16507
16611
|
const ignored = await res.git.getIgnoredPaths();
|
|
16508
16612
|
return c.json({ ignored });
|
|
16509
16613
|
} catch (err) {
|
|
@@ -16518,11 +16622,13 @@ function createGitRoutes() {
|
|
|
16518
16622
|
const staged = c.req.query("staged") === "1";
|
|
16519
16623
|
const from = c.req.query("from") || void 0;
|
|
16520
16624
|
const commit = c.req.query("commit") || void 0;
|
|
16521
|
-
const res = await
|
|
16625
|
+
const res = await getGitForRead(c.req.query("projectId"));
|
|
16522
16626
|
if ("error" in res)
|
|
16523
16627
|
return c.json({ error: res.error }, res.status);
|
|
16524
16628
|
if (!filePath)
|
|
16525
16629
|
return c.json({ error: "path is required" }, 400);
|
|
16630
|
+
if ("notRepo" in res)
|
|
16631
|
+
return c.json({ path: filePath, original: "", modified: "" });
|
|
16526
16632
|
if (!validatePath(filePath, res.projectPath)) {
|
|
16527
16633
|
return c.json({ error: "Path traversal not allowed" }, 403);
|
|
16528
16634
|
}
|
|
@@ -16536,9 +16642,11 @@ function createGitRoutes() {
|
|
|
16536
16642
|
});
|
|
16537
16643
|
app2.get("/git/log", async (c) => {
|
|
16538
16644
|
try {
|
|
16539
|
-
const res = await
|
|
16645
|
+
const res = await getGitForRead(c.req.query("projectId"));
|
|
16540
16646
|
if ("error" in res)
|
|
16541
16647
|
return c.json({ error: res.error }, res.status);
|
|
16648
|
+
if ("notRepo" in res)
|
|
16649
|
+
return c.json({ commits: [] });
|
|
16542
16650
|
const limitRaw = Number(c.req.query("limit"));
|
|
16543
16651
|
const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 2e3) : 50;
|
|
16544
16652
|
const commits = await res.git.getLog(limit);
|
|
@@ -16552,11 +16660,13 @@ function createGitRoutes() {
|
|
|
16552
16660
|
app2.get("/git/commit-files", async (c) => {
|
|
16553
16661
|
try {
|
|
16554
16662
|
const hash = c.req.query("hash");
|
|
16555
|
-
const res = await
|
|
16663
|
+
const res = await getGitForRead(c.req.query("projectId"));
|
|
16556
16664
|
if ("error" in res)
|
|
16557
16665
|
return c.json({ error: res.error }, res.status);
|
|
16558
16666
|
if (!hash)
|
|
16559
16667
|
return c.json({ error: "hash is required" }, 400);
|
|
16668
|
+
if ("notRepo" in res)
|
|
16669
|
+
return c.json({ files: [] });
|
|
16560
16670
|
const files = await res.git.getCommitFiles(hash);
|
|
16561
16671
|
return c.json({ files });
|
|
16562
16672
|
} catch (err) {
|
|
@@ -16665,9 +16775,11 @@ function createGitRoutes() {
|
|
|
16665
16775
|
});
|
|
16666
16776
|
app2.get("/git/remotes", async (c) => {
|
|
16667
16777
|
try {
|
|
16668
|
-
const res = await
|
|
16778
|
+
const res = await getGitForRead(c.req.query("projectId"));
|
|
16669
16779
|
if ("error" in res)
|
|
16670
16780
|
return c.json({ error: res.error }, res.status);
|
|
16781
|
+
if ("notRepo" in res)
|
|
16782
|
+
return c.json({ remotes: [] });
|
|
16671
16783
|
const remotes = await res.git.getRemotes();
|
|
16672
16784
|
return c.json({ remotes });
|
|
16673
16785
|
} catch (err) {
|
|
@@ -16790,9 +16902,11 @@ function createGitRoutes() {
|
|
|
16790
16902
|
});
|
|
16791
16903
|
app2.get("/git/remote/protocol", async (c) => {
|
|
16792
16904
|
try {
|
|
16793
|
-
const res = await
|
|
16905
|
+
const res = await getGitForRead(c.req.query("projectId"));
|
|
16794
16906
|
if ("error" in res)
|
|
16795
16907
|
return c.json({ error: res.error }, res.status);
|
|
16908
|
+
if ("notRepo" in res)
|
|
16909
|
+
return c.json({ url: "", protocol: "other" });
|
|
16796
16910
|
return c.json(getRemoteProtocol(res.projectPath));
|
|
16797
16911
|
} catch (err) {
|
|
16798
16912
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -17655,8 +17769,8 @@ function detectOrphans(tree, declared) {
|
|
|
17655
17769
|
if (!block || typeof block !== "object")
|
|
17656
17770
|
continue;
|
|
17657
17771
|
for (const k of Object.keys(block)) {
|
|
17658
|
-
const
|
|
17659
|
-
if (!declaredMap.has(
|
|
17772
|
+
const path63 = `${namespaceId}.${kindWord}.${k}`;
|
|
17773
|
+
if (!declaredMap.has(path63)) {
|
|
17660
17774
|
out.push({ namespaceId, kind: kindWord === "params" ? "param" : "secret", key: k });
|
|
17661
17775
|
}
|
|
17662
17776
|
}
|
|
@@ -20074,10 +20188,10 @@ function sendWsNotification(event, state, turnId, ctx) {
|
|
|
20074
20188
|
sendJson(ctx.ws, { type: "agent:done", agentName, taskId });
|
|
20075
20189
|
break;
|
|
20076
20190
|
case "tool_call":
|
|
20077
|
-
sendJson(ctx.ws, { type: "agent:tool_call", tool: event.toolName, input: event.toolInput, agentName, taskId, turnId });
|
|
20191
|
+
sendJson(ctx.ws, { type: "agent:tool_call", tool: event.toolName, toolUseId: event.toolUseId, input: event.toolInput, agentName, taskId, turnId });
|
|
20078
20192
|
break;
|
|
20079
20193
|
case "tool_result":
|
|
20080
|
-
sendJson(ctx.ws, { type: "agent:tool_result", result: event.toolResult, agentName, taskId, durationMs: event.durationMs });
|
|
20194
|
+
sendJson(ctx.ws, { type: "agent:tool_result", result: event.toolResult, toolUseId: event.toolUseId, agentName, taskId, durationMs: event.durationMs });
|
|
20081
20195
|
break;
|
|
20082
20196
|
case "followup_start":
|
|
20083
20197
|
case "queued_message":
|
|
@@ -20457,6 +20571,17 @@ function setupWebSocketHandler(deps) {
|
|
|
20457
20571
|
});
|
|
20458
20572
|
};
|
|
20459
20573
|
}
|
|
20574
|
+
function reclaimIfAbandoned(client) {
|
|
20575
|
+
if (!client.unsubscribeEvents)
|
|
20576
|
+
return;
|
|
20577
|
+
const closed = client.ws.readyState === client.ws.CLOSED;
|
|
20578
|
+
if (!closed && Date.now() - client.lastClientPingAt <= CLIENT_SILENCE_LIMIT_MS)
|
|
20579
|
+
return;
|
|
20580
|
+
client.unsubscribeEvents();
|
|
20581
|
+
client.unsubscribeEvents = null;
|
|
20582
|
+
console.debug(`[WS] Released listener for ${closed ? "closed" : "abandoned"} connection session=${client.sessionId}`);
|
|
20583
|
+
sendJson(client.ws, { type: "listener:released", sessionId: client.sessionId });
|
|
20584
|
+
}
|
|
20460
20585
|
wss2.on("connection", (ws) => {
|
|
20461
20586
|
const fileWatcher = new WorkspaceWatcher();
|
|
20462
20587
|
const gitDirWatcher = new GitDirWatcher();
|
|
@@ -20471,7 +20596,8 @@ function setupWebSocketHandler(deps) {
|
|
|
20471
20596
|
gitDirWatcher,
|
|
20472
20597
|
terminalManager,
|
|
20473
20598
|
backgroundSaves: /* @__PURE__ */ new Map(),
|
|
20474
|
-
unsubscribeEvents: null
|
|
20599
|
+
unsubscribeEvents: null,
|
|
20600
|
+
lastClientPingAt: Date.now()
|
|
20475
20601
|
};
|
|
20476
20602
|
fileWatcher.setCallback((evt) => {
|
|
20477
20603
|
sendJson(ws, { type: "file:changed", path: evt.path, action: evt.action });
|
|
@@ -20486,6 +20612,7 @@ function setupWebSocketHandler(deps) {
|
|
|
20486
20612
|
missedPongs = 0;
|
|
20487
20613
|
});
|
|
20488
20614
|
const keepaliveTimer = setInterval(() => {
|
|
20615
|
+
reclaimIfAbandoned(client);
|
|
20489
20616
|
if (missedPongs >= 2) {
|
|
20490
20617
|
ws.terminate();
|
|
20491
20618
|
return;
|
|
@@ -20499,6 +20626,7 @@ function setupWebSocketHandler(deps) {
|
|
|
20499
20626
|
sendJson(ws, { type: "state:snapshot", snapshot: { activePlan: null, agents: [], recentMessages: [], maxContextTokens: config.model.maxContextTokens } });
|
|
20500
20627
|
let messageQueue = Promise.resolve();
|
|
20501
20628
|
ws.on("message", (raw) => {
|
|
20629
|
+
client.lastClientPingAt = Date.now();
|
|
20502
20630
|
let msg;
|
|
20503
20631
|
try {
|
|
20504
20632
|
const text5 = typeof raw === "string" ? raw : raw.toString("utf-8");
|
|
@@ -20676,7 +20804,7 @@ function setupWebSocketHandler(deps) {
|
|
|
20676
20804
|
client2.unsubscribeEvents = sm.registerEventListener(client2.sessionId, createEventListener(client2));
|
|
20677
20805
|
const ctxConfig = await sm.getContextConfig(client2.sessionId);
|
|
20678
20806
|
sendJson(ws, { type: "state:snapshot", snapshot: { activePlan: null, agents: [], recentMessages: [], sessionId: client2.sessionId, maxContextTokens: ctxConfig.maxTokens, agentId } });
|
|
20679
|
-
} else if (client2.sessionId !== msg.sessionId) {
|
|
20807
|
+
} else if (client2.sessionId !== msg.sessionId || !client2.unsubscribeEvents) {
|
|
20680
20808
|
client2.unsubscribeEvents?.();
|
|
20681
20809
|
client2.sessionId = msg.sessionId;
|
|
20682
20810
|
client2.unsubscribeEvents = sm.registerEventListener(client2.sessionId, createEventListener(client2));
|
|
@@ -20800,7 +20928,7 @@ ${msg.message}` : markers.join("\n");
|
|
|
20800
20928
|
if (client2.sessionId && client2.sessionId !== msg.sessionId) {
|
|
20801
20929
|
saveSession(client2);
|
|
20802
20930
|
}
|
|
20803
|
-
if (client2.sessionId
|
|
20931
|
+
if (client2.sessionId) {
|
|
20804
20932
|
client2.unsubscribeEvents?.();
|
|
20805
20933
|
client2.unsubscribeEvents = null;
|
|
20806
20934
|
client2.sessionId = null;
|
|
@@ -20819,7 +20947,8 @@ ${msg.message}` : markers.join("\n");
|
|
|
20819
20947
|
client2.projectId = detached.projectId;
|
|
20820
20948
|
const state2 = getState(client2);
|
|
20821
20949
|
const ctxConfig = await client2.sessionManager.getContextConfig(client2.sessionId);
|
|
20822
|
-
const
|
|
20950
|
+
const running = client2.sessionManager.isSessionRunning(client2.sessionId);
|
|
20951
|
+
const messages3 = state2 ? running ? [...state2.messageLog] : [...createSaveSnapshot(state2)] : [];
|
|
20823
20952
|
const detachedSession = client2.sessionManager.getSessionById(client2.sessionId);
|
|
20824
20953
|
sendJson(ws, { type: "state:snapshot", snapshot: { activePlan: null, agents: [], recentMessages: messages3, sessionId: msg.sessionId, maxContextTokens: ctxConfig.maxTokens, agentId: detachedSession?.agentId } });
|
|
20825
20954
|
if (state2 && state2.contextTokens > 0) {
|
|
@@ -20832,16 +20961,21 @@ ${msg.message}` : markers.join("\n");
|
|
|
20832
20961
|
}
|
|
20833
20962
|
detached.unsubscribe();
|
|
20834
20963
|
client2.unsubscribeEvents = client2.sessionManager.registerEventListener(client2.sessionId, createEventListener(client2));
|
|
20835
|
-
if (state2 &&
|
|
20964
|
+
if (state2 && running) {
|
|
20836
20965
|
console.debug(`[WS] Session still running \u2014 synthesizing in-progress state`);
|
|
20837
|
-
|
|
20838
|
-
|
|
20839
|
-
|
|
20840
|
-
|
|
20841
|
-
|
|
20842
|
-
|
|
20843
|
-
|
|
20844
|
-
|
|
20966
|
+
const agentName = detachedSession?.agentName ?? (state2.streamingAgent || "default");
|
|
20967
|
+
sendJson(ws, { type: "chat:followup", agentName, replay: true });
|
|
20968
|
+
for (const block of state2.turnContentBlocks) {
|
|
20969
|
+
if (block.type === "thinking") {
|
|
20970
|
+
sendJson(ws, { type: "chat:thinking", text: block.text, agentName, turnId: block.turnId, replay: true });
|
|
20971
|
+
} else if (block.type === "text") {
|
|
20972
|
+
sendJson(ws, { type: "chat:stream", text: block.text, agentName, turnId: block.turnId, replay: true });
|
|
20973
|
+
} else {
|
|
20974
|
+
const tc = block.toolCall;
|
|
20975
|
+
sendJson(ws, { type: "agent:tool_call", tool: tc.name, toolUseId: tc.toolUseId, input: tc.input, agentName, turnId: block.turnId, replay: true });
|
|
20976
|
+
if (tc.output) {
|
|
20977
|
+
sendJson(ws, { type: "agent:tool_result", result: tc.output, toolUseId: tc.toolUseId, agentName, durationMs: tc.durationMs, replay: true });
|
|
20978
|
+
}
|
|
20845
20979
|
}
|
|
20846
20980
|
}
|
|
20847
20981
|
}
|
|
@@ -20903,7 +21037,7 @@ ${msg.message}` : markers.join("\n");
|
|
|
20903
21037
|
});
|
|
20904
21038
|
console.log("[WS] WebSocket handler initialized");
|
|
20905
21039
|
}
|
|
20906
|
-
var detachedSessions;
|
|
21040
|
+
var CLIENT_SILENCE_LIMIT_MS, detachedSessions;
|
|
20907
21041
|
var init_handler = __esm({
|
|
20908
21042
|
"packages/server/dist/ws/handler.js"() {
|
|
20909
21043
|
"use strict";
|
|
@@ -20916,6 +21050,7 @@ var init_handler = __esm({
|
|
|
20916
21050
|
init_terminal_manager();
|
|
20917
21051
|
init_commands2();
|
|
20918
21052
|
init_goal_mode();
|
|
21053
|
+
CLIENT_SILENCE_LIMIT_MS = 3 * 6e4;
|
|
20919
21054
|
detachedSessions = /* @__PURE__ */ new Map();
|
|
20920
21055
|
}
|
|
20921
21056
|
});
|
|
@@ -21070,7 +21205,9 @@ function listAllCronTargets() {
|
|
|
21070
21205
|
}
|
|
21071
21206
|
return out;
|
|
21072
21207
|
}
|
|
21073
|
-
async function dispatchToTargets(
|
|
21208
|
+
async function dispatchToTargets(rawText, targets, workspacePath) {
|
|
21209
|
+
const { text: strippedText, mediaPaths } = extractMediaMessage(rawText);
|
|
21210
|
+
const media = mediaPaths.length > 0 ? { paths: mediaPaths, workspacePath } : void 0;
|
|
21074
21211
|
const out = [];
|
|
21075
21212
|
for (const t2 of targets) {
|
|
21076
21213
|
const handler = _registry.get(t2.channelType);
|
|
@@ -21085,7 +21222,7 @@ async function dispatchToTargets(text5, targets) {
|
|
|
21085
21222
|
continue;
|
|
21086
21223
|
}
|
|
21087
21224
|
try {
|
|
21088
|
-
const results = await handler.dispatch(t2.accountId,
|
|
21225
|
+
const results = handler.supportsMedia ? await handler.dispatch(t2.accountId, strippedText, t2.chatId, media) : await handler.dispatch(t2.accountId, rawText, t2.chatId);
|
|
21089
21226
|
for (const r of results)
|
|
21090
21227
|
out.push(r);
|
|
21091
21228
|
} catch (err) {
|
|
@@ -21104,6 +21241,7 @@ var _registry;
|
|
|
21104
21241
|
var init_dispatcher = __esm({
|
|
21105
21242
|
"packages/server/dist/cron/dispatcher.js"() {
|
|
21106
21243
|
"use strict";
|
|
21244
|
+
init_media();
|
|
21107
21245
|
_registry = /* @__PURE__ */ new Map();
|
|
21108
21246
|
}
|
|
21109
21247
|
});
|
|
@@ -21534,7 +21672,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
21534
21672
|
}
|
|
21535
21673
|
let dispatchResults = [];
|
|
21536
21674
|
if (status === "succeeded" && targets.length > 0) {
|
|
21537
|
-
dispatchResults = await dispatchToTargets(trimmedOut, targets);
|
|
21675
|
+
dispatchResults = await dispatchToTargets(trimmedOut, targets, job.workspacePath);
|
|
21538
21676
|
if (dispatchResults.length > 0 && dispatchResults.every((r) => !r.ok)) {
|
|
21539
21677
|
status = "failed";
|
|
21540
21678
|
failureReason = `all ${dispatchResults.length} target(s) failed: ${dispatchResults.map((r) => `${r.channelType}:${r.error ?? "?"}`).join("; ")}`;
|
|
@@ -22269,12 +22407,12 @@ var init_accounts3 = __esm({
|
|
|
22269
22407
|
});
|
|
22270
22408
|
|
|
22271
22409
|
// packages/server/dist/channels/telegram/event-adapter.js
|
|
22272
|
-
var HARD_CHARS,
|
|
22410
|
+
var HARD_CHARS, TelegramResponder;
|
|
22273
22411
|
var init_event_adapter = __esm({
|
|
22274
22412
|
"packages/server/dist/channels/telegram/event-adapter.js"() {
|
|
22275
22413
|
"use strict";
|
|
22414
|
+
init_media();
|
|
22276
22415
|
HARD_CHARS = 4e3;
|
|
22277
|
-
MEDIA_MARKER_RE = /^MEDIA:\s*(\S.*?)\s*$/gm;
|
|
22278
22416
|
TelegramResponder = class {
|
|
22279
22417
|
buffer = "";
|
|
22280
22418
|
deps;
|
|
@@ -22347,12 +22485,7 @@ var init_event_adapter = __esm({
|
|
|
22347
22485
|
return limit;
|
|
22348
22486
|
}
|
|
22349
22487
|
async dispatchChunk(chunk) {
|
|
22350
|
-
const mediaPaths =
|
|
22351
|
-
const text5 = chunk.replace(MEDIA_MARKER_RE, (_m, p) => {
|
|
22352
|
-
if (p)
|
|
22353
|
-
mediaPaths.push(p.trim());
|
|
22354
|
-
return "";
|
|
22355
|
-
}).replace(/\n{3,}/g, "\n\n").trim();
|
|
22488
|
+
const { text: text5, mediaPaths } = extractMediaMessage(chunk);
|
|
22356
22489
|
if (text5) {
|
|
22357
22490
|
try {
|
|
22358
22491
|
await this.deps.sendText(text5);
|
|
@@ -22372,6 +22505,21 @@ var init_event_adapter = __esm({
|
|
|
22372
22505
|
}
|
|
22373
22506
|
});
|
|
22374
22507
|
|
|
22508
|
+
// packages/server/dist/channels/shared/busy-hint.js
|
|
22509
|
+
function busyHint(sm, sessionId, lang) {
|
|
22510
|
+
if (sm.isSessionCompacting(sessionId))
|
|
22511
|
+
return t("handler.compacting", lang);
|
|
22512
|
+
if (sm.isSessionRunning(sessionId))
|
|
22513
|
+
return t("handler.queued", lang);
|
|
22514
|
+
return null;
|
|
22515
|
+
}
|
|
22516
|
+
var init_busy_hint = __esm({
|
|
22517
|
+
"packages/server/dist/channels/shared/busy-hint.js"() {
|
|
22518
|
+
"use strict";
|
|
22519
|
+
init_i18n();
|
|
22520
|
+
}
|
|
22521
|
+
});
|
|
22522
|
+
|
|
22375
22523
|
// packages/server/dist/channels/shared/session-prefix.js
|
|
22376
22524
|
function sessionPrefix(channel, userId) {
|
|
22377
22525
|
return `${channel}_${userId}_`;
|
|
@@ -22384,7 +22532,6 @@ var init_session_prefix = __esm({
|
|
|
22384
22532
|
|
|
22385
22533
|
// packages/server/dist/channels/telegram/handler.js
|
|
22386
22534
|
import { Bot, InputFile } from "grammy";
|
|
22387
|
-
import path52 from "node:path";
|
|
22388
22535
|
function inferMediaKind(filePath) {
|
|
22389
22536
|
const cls = classifyMedia(filePath);
|
|
22390
22537
|
if (cls === "image")
|
|
@@ -22660,23 +22807,16 @@ async function handleUserMessage(args) {
|
|
|
22660
22807
|
const sm = registry2.getOrCreate(workspace);
|
|
22661
22808
|
const accessLevel = account.accessLevel === "full" ? null : account.accessLevel === "workspace" ? "workspace" : "readonly";
|
|
22662
22809
|
const sessionId = resolveGoalRoute(sm.getDb(), await getOrCreateActiveSession(sm, userId, activeOverrides2, accessLevel, workspace));
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
}
|
|
22667
|
-
if (sm.isSessionRunning(sessionId)) {
|
|
22668
|
-
await ctx.reply(t("handler.queued", lang));
|
|
22669
|
-
}
|
|
22810
|
+
const hint = busyHint(sm, sessionId, lang);
|
|
22811
|
+
if (hint)
|
|
22812
|
+
await ctx.reply(hint);
|
|
22670
22813
|
if (!unsubscribers.has(sessionId)) {
|
|
22671
22814
|
const responder = new TelegramResponder({
|
|
22672
22815
|
sendText: async (chunk) => {
|
|
22673
22816
|
await bot.api.sendMessage(chatId, chunk, { parse_mode: void 0 });
|
|
22674
22817
|
},
|
|
22675
22818
|
sendMedia: async (filePath) => {
|
|
22676
|
-
|
|
22677
|
-
const ws = path52.resolve(workspace);
|
|
22678
|
-
const inWorkspace = resolved === ws || resolved.startsWith(ws + path52.sep);
|
|
22679
|
-
if (!inWorkspace && !isInTempDir(resolved)) {
|
|
22819
|
+
if (!isMediaPathAllowed(filePath, workspace)) {
|
|
22680
22820
|
console.log(`[telegram] sendMedia blocked: ${filePath} not under workspace`);
|
|
22681
22821
|
return;
|
|
22682
22822
|
}
|
|
@@ -22734,6 +22874,7 @@ var init_handler2 = __esm({
|
|
|
22734
22874
|
init_commands2();
|
|
22735
22875
|
init_goal_mode();
|
|
22736
22876
|
init_i18n();
|
|
22877
|
+
init_busy_hint();
|
|
22737
22878
|
init_commands();
|
|
22738
22879
|
init_media();
|
|
22739
22880
|
init_session_prefix();
|
|
@@ -22742,7 +22883,7 @@ var init_handler2 = __esm({
|
|
|
22742
22883
|
|
|
22743
22884
|
// packages/server/dist/routes/telegram.js
|
|
22744
22885
|
import { Hono as Hono13 } from "hono";
|
|
22745
|
-
import
|
|
22886
|
+
import path52 from "node:path";
|
|
22746
22887
|
import { Bot as Bot2 } from "grammy";
|
|
22747
22888
|
import fs47 from "node:fs";
|
|
22748
22889
|
function createTelegramRoutes(deps) {
|
|
@@ -22772,7 +22913,7 @@ function createTelegramRoutes(deps) {
|
|
|
22772
22913
|
return c.json({ error: "botToken required" }, 400);
|
|
22773
22914
|
if (!body.workspacePath)
|
|
22774
22915
|
return c.json({ error: "workspacePath required" }, 400);
|
|
22775
|
-
if (!
|
|
22916
|
+
if (!path52.isAbsolute(body.workspacePath))
|
|
22776
22917
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
22777
22918
|
if (!fs47.existsSync(body.workspacePath))
|
|
22778
22919
|
return c.json({ error: "workspace path not found" }, 400);
|
|
@@ -23261,12 +23402,12 @@ var init_accounts4 = __esm({
|
|
|
23261
23402
|
});
|
|
23262
23403
|
|
|
23263
23404
|
// packages/server/dist/channels/wechat/event-adapter.js
|
|
23264
|
-
var HARD_CHARS2,
|
|
23405
|
+
var HARD_CHARS2, WeixinResponder;
|
|
23265
23406
|
var init_event_adapter2 = __esm({
|
|
23266
23407
|
"packages/server/dist/channels/wechat/event-adapter.js"() {
|
|
23267
23408
|
"use strict";
|
|
23409
|
+
init_media();
|
|
23268
23410
|
HARD_CHARS2 = 3500;
|
|
23269
|
-
MEDIA_MARKER_RE2 = /^MEDIA:\s*(\S.*?)\s*$/gm;
|
|
23270
23411
|
WeixinResponder = class {
|
|
23271
23412
|
buffer = "";
|
|
23272
23413
|
deps;
|
|
@@ -23287,13 +23428,13 @@ var init_event_adapter2 = __esm({
|
|
|
23287
23428
|
case "error":
|
|
23288
23429
|
if (event.error) {
|
|
23289
23430
|
this.flushAll();
|
|
23290
|
-
void this.dispatchChunk(
|
|
23431
|
+
void this.dispatchChunk(`\u274C ${event.error}`);
|
|
23291
23432
|
}
|
|
23292
23433
|
break;
|
|
23293
23434
|
case "system":
|
|
23294
23435
|
if (event.text) {
|
|
23295
23436
|
this.flushAll();
|
|
23296
|
-
void this.dispatchChunk(
|
|
23437
|
+
void this.dispatchChunk(`\u2139\uFE0F ${event.text}`);
|
|
23297
23438
|
}
|
|
23298
23439
|
break;
|
|
23299
23440
|
case "complete":
|
|
@@ -23347,12 +23488,7 @@ var init_event_adapter2 = __esm({
|
|
|
23347
23488
|
* goes out as a WeChat message (if non-empty after trim).
|
|
23348
23489
|
*/
|
|
23349
23490
|
async dispatchChunk(chunk) {
|
|
23350
|
-
const mediaPaths =
|
|
23351
|
-
const text5 = chunk.replace(MEDIA_MARKER_RE2, (_m, p) => {
|
|
23352
|
-
if (p)
|
|
23353
|
-
mediaPaths.push(p.trim());
|
|
23354
|
-
return "";
|
|
23355
|
-
}).replace(/\n{3,}/g, "\n\n").trim();
|
|
23491
|
+
const { text: text5, mediaPaths } = extractMediaMessage(chunk);
|
|
23356
23492
|
if (text5) {
|
|
23357
23493
|
try {
|
|
23358
23494
|
await this.deps.sendText(text5);
|
|
@@ -23480,7 +23616,7 @@ var init_cdn = __esm({
|
|
|
23480
23616
|
|
|
23481
23617
|
// packages/server/dist/channels/wechat/send-media.js
|
|
23482
23618
|
import fs48 from "node:fs/promises";
|
|
23483
|
-
import
|
|
23619
|
+
import path53 from "node:path";
|
|
23484
23620
|
function inferKind(filePath) {
|
|
23485
23621
|
const cls = classifyMedia(filePath);
|
|
23486
23622
|
if (cls === "image")
|
|
@@ -23530,7 +23666,7 @@ async function sendMediaFile(params) {
|
|
|
23530
23666
|
aesKeyBase64,
|
|
23531
23667
|
fileSizeCiphertext: prep.filesize,
|
|
23532
23668
|
fileSize: prep.rawsize,
|
|
23533
|
-
fileName:
|
|
23669
|
+
fileName: path53.basename(filePath)
|
|
23534
23670
|
});
|
|
23535
23671
|
const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
23536
23672
|
const body = {
|
|
@@ -23694,12 +23830,8 @@ var init_login = __esm({
|
|
|
23694
23830
|
|
|
23695
23831
|
// packages/server/dist/channels/wechat/handler.js
|
|
23696
23832
|
import fs49 from "node:fs";
|
|
23697
|
-
import
|
|
23833
|
+
import path54 from "node:path";
|
|
23698
23834
|
import QRCode from "qrcode";
|
|
23699
|
-
function isPathAllowed(filePath, workspacePath) {
|
|
23700
|
-
const resolved = path55.resolve(filePath);
|
|
23701
|
-
return resolved.startsWith(path55.resolve(workspacePath)) || isInTempDir(resolved);
|
|
23702
|
-
}
|
|
23703
23835
|
function startWeixinChannel(deps) {
|
|
23704
23836
|
const { registry: registry2, db } = deps;
|
|
23705
23837
|
const runners = /* @__PURE__ */ new Map();
|
|
@@ -23983,21 +24115,13 @@ async function handleInbound(args) {
|
|
|
23983
24115
|
const sm = registry2.getOrCreate(account.workspacePath);
|
|
23984
24116
|
const sessionAccessLevel = account.accessLevel === "full" ? null : account.accessLevel === "workspace" ? "workspace" : "readonly";
|
|
23985
24117
|
const sessionId = resolveGoalRoute(sm.getDb(), await getOrCreateActiveSession2(sm, fromUserId, activeOverrides2, sessionAccessLevel, account.workspacePath));
|
|
23986
|
-
|
|
24118
|
+
const hint = busyHint(sm, sessionId, lang);
|
|
24119
|
+
if (hint) {
|
|
23987
24120
|
await sendToUser({
|
|
23988
24121
|
account,
|
|
23989
24122
|
toUserId: fromUserId,
|
|
23990
24123
|
contextToken: msg.context_token,
|
|
23991
|
-
text:
|
|
23992
|
-
});
|
|
23993
|
-
return;
|
|
23994
|
-
}
|
|
23995
|
-
if (sm.isSessionRunning(sessionId)) {
|
|
23996
|
-
await sendToUser({
|
|
23997
|
-
account,
|
|
23998
|
-
toUserId: fromUserId,
|
|
23999
|
-
contextToken: msg.context_token,
|
|
24000
|
-
text: t("handler.queued", lang)
|
|
24124
|
+
text: hint
|
|
24001
24125
|
});
|
|
24002
24126
|
}
|
|
24003
24127
|
if (msg.context_token)
|
|
@@ -24011,7 +24135,7 @@ async function handleInbound(args) {
|
|
|
24011
24135
|
contextToken: sessionContextTokens.get(sessionId)
|
|
24012
24136
|
}),
|
|
24013
24137
|
sendMedia: async (filePath) => {
|
|
24014
|
-
if (!
|
|
24138
|
+
if (!isMediaPathAllowed(filePath, account.workspacePath)) {
|
|
24015
24139
|
console.log(`[weixin] sendMedia blocked: ${filePath} not under workspace`);
|
|
24016
24140
|
return;
|
|
24017
24141
|
}
|
|
@@ -24053,7 +24177,7 @@ async function handleSlashCommand(args) {
|
|
|
24053
24177
|
contextToken: sessionContextTokens.get(sessionId)
|
|
24054
24178
|
}),
|
|
24055
24179
|
sendMedia: async (filePath) => {
|
|
24056
|
-
if (!
|
|
24180
|
+
if (!isMediaPathAllowed(filePath, account.workspacePath)) {
|
|
24057
24181
|
console.log(`[weixin] sendMedia blocked: ${filePath} not under workspace`);
|
|
24058
24182
|
return;
|
|
24059
24183
|
}
|
|
@@ -24135,7 +24259,7 @@ async function handleSlashCommand(args) {
|
|
|
24135
24259
|
await reply(login.message);
|
|
24136
24260
|
return true;
|
|
24137
24261
|
}
|
|
24138
|
-
const tmpPath =
|
|
24262
|
+
const tmpPath = path54.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
|
|
24139
24263
|
await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
|
|
24140
24264
|
await sendMediaFile({
|
|
24141
24265
|
baseUrl: account.baseUrl,
|
|
@@ -24215,6 +24339,7 @@ var init_handler3 = __esm({
|
|
|
24215
24339
|
init_commands2();
|
|
24216
24340
|
init_goal_mode();
|
|
24217
24341
|
init_i18n();
|
|
24342
|
+
init_busy_hint();
|
|
24218
24343
|
init_session_prefix();
|
|
24219
24344
|
MAX_CONSECUTIVE_FAILURES = 3;
|
|
24220
24345
|
BACKOFF_DELAY_MS = 3e4;
|
|
@@ -24224,7 +24349,7 @@ var init_handler3 = __esm({
|
|
|
24224
24349
|
|
|
24225
24350
|
// packages/server/dist/routes/weixin.js
|
|
24226
24351
|
import { Hono as Hono14 } from "hono";
|
|
24227
|
-
import
|
|
24352
|
+
import path55 from "node:path";
|
|
24228
24353
|
import fs50 from "node:fs";
|
|
24229
24354
|
function createWeixinRoutes(deps) {
|
|
24230
24355
|
const { db, channel } = deps;
|
|
@@ -24250,7 +24375,7 @@ function createWeixinRoutes(deps) {
|
|
|
24250
24375
|
return c.json({ error: "sessionKey required" }, 400);
|
|
24251
24376
|
if (!body.workspacePath)
|
|
24252
24377
|
return c.json({ error: "workspacePath required" }, 400);
|
|
24253
|
-
if (!
|
|
24378
|
+
if (!path55.isAbsolute(body.workspacePath))
|
|
24254
24379
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
24255
24380
|
const accessLevel = body.accessLevel === "full" ? "full" : body.accessLevel === "workspace" ? "workspace" : "readonly";
|
|
24256
24381
|
const result = await waitLogin({ sessionKey: body.sessionKey, timeoutMs: body.timeoutMs });
|
|
@@ -24295,7 +24420,7 @@ function createWeixinRoutes(deps) {
|
|
|
24295
24420
|
if (!existing)
|
|
24296
24421
|
return c.json({ error: "not found" }, 404);
|
|
24297
24422
|
const body = await c.req.json().catch(() => ({}));
|
|
24298
|
-
if (body.workspacePath !== void 0 && !
|
|
24423
|
+
if (body.workspacePath !== void 0 && !path55.isAbsolute(body.workspacePath)) {
|
|
24299
24424
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
24300
24425
|
}
|
|
24301
24426
|
if (body.accessLevel !== void 0 && body.accessLevel !== "full" && body.accessLevel !== "workspace" && body.accessLevel !== "readonly") {
|
|
@@ -24353,7 +24478,7 @@ function readLastActiveChatId(accountId) {
|
|
|
24353
24478
|
const v = acct.config?.lastActiveChatId;
|
|
24354
24479
|
return typeof v === "string" && v.length > 0 ? v : null;
|
|
24355
24480
|
}
|
|
24356
|
-
async function dispatch2(accountId, text5, explicitChatId) {
|
|
24481
|
+
async function dispatch2(accountId, text5, explicitChatId, media) {
|
|
24357
24482
|
const acct = getAccount4(getChannelDb(), accountId);
|
|
24358
24483
|
if (!acct)
|
|
24359
24484
|
throw new Error(`wechat account ${accountId} not found`);
|
|
@@ -24363,8 +24488,32 @@ async function dispatch2(accountId, text5, explicitChatId) {
|
|
|
24363
24488
|
if (!chatId) {
|
|
24364
24489
|
throw new Error("no wechat target \u2014 bind the account first (the QR-login owner becomes the default cron recipient)");
|
|
24365
24490
|
}
|
|
24366
|
-
|
|
24367
|
-
|
|
24491
|
+
const out = [];
|
|
24492
|
+
if (text5) {
|
|
24493
|
+
await sendToUser({ account: acct, toUserId: chatId, text: text5 });
|
|
24494
|
+
out.push({ channelType: "wechat", accountId, chatId, ok: true });
|
|
24495
|
+
}
|
|
24496
|
+
for (const filePath of media?.paths ?? []) {
|
|
24497
|
+
if (!isMediaPathAllowed(filePath, media.workspacePath)) {
|
|
24498
|
+
console.log(`[weixin] cron sendMediaFile blocked: ${filePath} not under ${media.workspacePath}`);
|
|
24499
|
+
out.push({ channelType: "wechat", accountId, chatId, ok: false, error: `media path not under job workspace: ${filePath}` });
|
|
24500
|
+
continue;
|
|
24501
|
+
}
|
|
24502
|
+
try {
|
|
24503
|
+
await sendMediaFile({
|
|
24504
|
+
baseUrl: acct.baseUrl,
|
|
24505
|
+
token: acct.botToken,
|
|
24506
|
+
toUserId: chatId,
|
|
24507
|
+
filePath
|
|
24508
|
+
});
|
|
24509
|
+
out.push({ channelType: "wechat", accountId, chatId, ok: true });
|
|
24510
|
+
} catch (err) {
|
|
24511
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
24512
|
+
console.log(`[weixin] cron sendMediaFile ${filePath} failed: ${msg}`);
|
|
24513
|
+
out.push({ channelType: "wechat", accountId, chatId, ok: false, error: `media ${filePath}: ${msg}` });
|
|
24514
|
+
}
|
|
24515
|
+
}
|
|
24516
|
+
return out;
|
|
24368
24517
|
}
|
|
24369
24518
|
function listTargets2() {
|
|
24370
24519
|
const cdb = getChannelDb();
|
|
@@ -24385,7 +24534,7 @@ function listTargets2() {
|
|
|
24385
24534
|
return out;
|
|
24386
24535
|
}
|
|
24387
24536
|
function registerWechatCronDispatcher() {
|
|
24388
|
-
registerCronDispatcher({ channelType: "wechat", dispatch: dispatch2, listTargets: listTargets2 });
|
|
24537
|
+
registerCronDispatcher({ channelType: "wechat", supportsMedia: true, dispatch: dispatch2, listTargets: listTargets2 });
|
|
24389
24538
|
}
|
|
24390
24539
|
var init_cron_dispatcher2 = __esm({
|
|
24391
24540
|
"packages/server/dist/channels/wechat/cron-dispatcher.js"() {
|
|
@@ -24394,6 +24543,8 @@ var init_cron_dispatcher2 = __esm({
|
|
|
24394
24543
|
init_accounts();
|
|
24395
24544
|
init_accounts4();
|
|
24396
24545
|
init_handler3();
|
|
24546
|
+
init_send_media();
|
|
24547
|
+
init_media();
|
|
24397
24548
|
init_dispatcher();
|
|
24398
24549
|
}
|
|
24399
24550
|
});
|
|
@@ -24428,14 +24579,14 @@ function createWebChannel(deps) {
|
|
|
24428
24579
|
return findActiveSessionId(sm, accountId, prefix, activeOverrides2, "full") ?? void 0;
|
|
24429
24580
|
}
|
|
24430
24581
|
function resolveWorkspace(account, override) {
|
|
24431
|
-
let
|
|
24582
|
+
let path63 = account.workspacePath;
|
|
24432
24583
|
if (override && override !== account.workspacePath) {
|
|
24433
24584
|
if (account.accessLevel !== "full") {
|
|
24434
24585
|
return { ok: false, error: "workspace override requires a full-access token" };
|
|
24435
24586
|
}
|
|
24436
|
-
|
|
24587
|
+
path63 = override;
|
|
24437
24588
|
}
|
|
24438
|
-
const resolved = resolveAccountWorkspace({ ...account, workspacePath:
|
|
24589
|
+
const resolved = resolveAccountWorkspace({ ...account, workspacePath: path63 });
|
|
24439
24590
|
if (!resolved)
|
|
24440
24591
|
return { ok: false, error: "workspace not found" };
|
|
24441
24592
|
return { ok: true, path: resolved };
|
|
@@ -24736,15 +24887,11 @@ function sseData(obj) {
|
|
|
24736
24887
|
`;
|
|
24737
24888
|
}
|
|
24738
24889
|
function flushText(text5) {
|
|
24739
|
-
const
|
|
24740
|
-
const cleaned = text5.replace(MEDIA_MARKER_RE3, (_match, p) => {
|
|
24741
|
-
files.push(p);
|
|
24742
|
-
return "";
|
|
24743
|
-
});
|
|
24890
|
+
const { text: cleaned, mediaPaths } = extractMediaPaths(text5);
|
|
24744
24891
|
let out = "";
|
|
24745
24892
|
if (cleaned)
|
|
24746
24893
|
out += sseData({ type: "stream", text: cleaned });
|
|
24747
|
-
for (const filePath of
|
|
24894
|
+
for (const filePath of mediaPaths)
|
|
24748
24895
|
out += sseData({ type: "file", path: filePath });
|
|
24749
24896
|
return out;
|
|
24750
24897
|
}
|
|
@@ -24802,7 +24949,6 @@ function createMediaBuffer() {
|
|
|
24802
24949
|
}
|
|
24803
24950
|
};
|
|
24804
24951
|
}
|
|
24805
|
-
var MEDIA_MARKER_RE3;
|
|
24806
24952
|
var init_handler4 = __esm({
|
|
24807
24953
|
"packages/server/dist/channels/web/handler.js"() {
|
|
24808
24954
|
"use strict";
|
|
@@ -24810,12 +24956,12 @@ var init_handler4 = __esm({
|
|
|
24810
24956
|
init_accounts2();
|
|
24811
24957
|
init_accounts2();
|
|
24812
24958
|
init_media_store();
|
|
24959
|
+
init_media();
|
|
24813
24960
|
init_accounts();
|
|
24814
24961
|
init_commands2();
|
|
24815
24962
|
init_goal_mode();
|
|
24816
24963
|
init_i18n();
|
|
24817
24964
|
init_session_prefix();
|
|
24818
|
-
MEDIA_MARKER_RE3 = /^MEDIA:\s*(\S.*?)\s*$/gm;
|
|
24819
24965
|
}
|
|
24820
24966
|
});
|
|
24821
24967
|
|
|
@@ -24823,7 +24969,7 @@ var init_handler4 = __esm({
|
|
|
24823
24969
|
import { Hono as Hono15 } from "hono";
|
|
24824
24970
|
import { streamSSE } from "hono/streaming";
|
|
24825
24971
|
import crypto4 from "node:crypto";
|
|
24826
|
-
import
|
|
24972
|
+
import path56 from "node:path";
|
|
24827
24973
|
import fs51 from "node:fs";
|
|
24828
24974
|
function createWebRoutes(deps) {
|
|
24829
24975
|
const { db, channel } = deps;
|
|
@@ -24849,7 +24995,7 @@ function createWebRoutes(deps) {
|
|
|
24849
24995
|
const body = await c.req.json().catch(() => ({}));
|
|
24850
24996
|
if (!body.workspacePath)
|
|
24851
24997
|
return c.json({ error: "workspacePath required" }, 400);
|
|
24852
|
-
if (!
|
|
24998
|
+
if (!path56.isAbsolute(body.workspacePath))
|
|
24853
24999
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
24854
25000
|
if (!fs51.existsSync(body.workspacePath))
|
|
24855
25001
|
return c.json({ error: "workspace path not found" }, 400);
|
|
@@ -24987,10 +25133,10 @@ function createWebRoutes(deps) {
|
|
|
24987
25133
|
const filePath = c.req.query("path");
|
|
24988
25134
|
if (!filePath)
|
|
24989
25135
|
return c.json({ error: "path required" }, 400);
|
|
24990
|
-
const
|
|
24991
|
-
const resolved =
|
|
24992
|
-
const root =
|
|
24993
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
25136
|
+
const path63 = await import("node:path");
|
|
25137
|
+
const resolved = path63.default.resolve(account.workspacePath, filePath);
|
|
25138
|
+
const root = path63.default.resolve(account.workspacePath);
|
|
25139
|
+
if (resolved !== root && !resolved.startsWith(root + path63.default.sep)) {
|
|
24994
25140
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
24995
25141
|
}
|
|
24996
25142
|
let real;
|
|
@@ -25001,13 +25147,13 @@ function createWebRoutes(deps) {
|
|
|
25001
25147
|
} catch {
|
|
25002
25148
|
return c.json({ error: "file not found" }, 404);
|
|
25003
25149
|
}
|
|
25004
|
-
if (real !== realRoot && !real.startsWith(realRoot +
|
|
25150
|
+
if (real !== realRoot && !real.startsWith(realRoot + path63.default.sep)) {
|
|
25005
25151
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
25006
25152
|
}
|
|
25007
25153
|
const stat = fs51.statSync(real);
|
|
25008
25154
|
if (stat.isDirectory())
|
|
25009
25155
|
return c.json({ error: "cannot serve directory" }, 400);
|
|
25010
|
-
const ext =
|
|
25156
|
+
const ext = path63.default.extname(real).toLowerCase();
|
|
25011
25157
|
const mimeMap = {
|
|
25012
25158
|
".png": "image/png",
|
|
25013
25159
|
".jpg": "image/jpeg",
|
|
@@ -25021,7 +25167,7 @@ function createWebRoutes(deps) {
|
|
|
25021
25167
|
".txt": "text/plain"
|
|
25022
25168
|
};
|
|
25023
25169
|
const contentType = mimeMap[ext] || "application/octet-stream";
|
|
25024
|
-
const fileName =
|
|
25170
|
+
const fileName = path63.default.basename(resolved);
|
|
25025
25171
|
const data = fs51.readFileSync(real);
|
|
25026
25172
|
c.header("content-type", contentType);
|
|
25027
25173
|
c.header("content-disposition", `inline; filename="${fileName}"`);
|
|
@@ -25227,13 +25373,13 @@ var init_markdown = __esm({
|
|
|
25227
25373
|
});
|
|
25228
25374
|
|
|
25229
25375
|
// packages/server/dist/channels/slack/event-adapter.js
|
|
25230
|
-
var HARD_CHARS3,
|
|
25376
|
+
var HARD_CHARS3, SlackResponder;
|
|
25231
25377
|
var init_event_adapter3 = __esm({
|
|
25232
25378
|
"packages/server/dist/channels/slack/event-adapter.js"() {
|
|
25233
25379
|
"use strict";
|
|
25234
25380
|
init_markdown();
|
|
25381
|
+
init_media();
|
|
25235
25382
|
HARD_CHARS3 = 35e3;
|
|
25236
|
-
MEDIA_MARKER_RE4 = /^MEDIA:\s*(\S.*?)\s*$/gm;
|
|
25237
25383
|
SlackResponder = class {
|
|
25238
25384
|
buffer = "";
|
|
25239
25385
|
deps;
|
|
@@ -25297,12 +25443,7 @@ var init_event_adapter3 = __esm({
|
|
|
25297
25443
|
return limit;
|
|
25298
25444
|
}
|
|
25299
25445
|
async dispatchChunk(chunk) {
|
|
25300
|
-
const mediaPaths =
|
|
25301
|
-
const stripped = chunk.replace(MEDIA_MARKER_RE4, (_m, p) => {
|
|
25302
|
-
if (p)
|
|
25303
|
-
mediaPaths.push(p.trim());
|
|
25304
|
-
return "";
|
|
25305
|
-
}).replace(/\n{3,}/g, "\n\n").trim();
|
|
25446
|
+
const { text: stripped, mediaPaths } = extractMediaMessage(chunk);
|
|
25306
25447
|
const text5 = formatForSlack(stripped);
|
|
25307
25448
|
if (text5) {
|
|
25308
25449
|
try {
|
|
@@ -25353,8 +25494,8 @@ async function authTest(botToken) {
|
|
|
25353
25494
|
}
|
|
25354
25495
|
async function uploadFile(args) {
|
|
25355
25496
|
const fs56 = await import("node:fs");
|
|
25356
|
-
const
|
|
25357
|
-
const filename = args.filename ??
|
|
25497
|
+
const path63 = await import("node:path");
|
|
25498
|
+
const filename = args.filename ?? path63.basename(args.filePath);
|
|
25358
25499
|
const stat = fs56.statSync(args.filePath);
|
|
25359
25500
|
if (!stat.isFile())
|
|
25360
25501
|
throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
|
|
@@ -25518,7 +25659,7 @@ var init_api2 = __esm({
|
|
|
25518
25659
|
});
|
|
25519
25660
|
|
|
25520
25661
|
// packages/server/dist/channels/slack/handler.js
|
|
25521
|
-
import
|
|
25662
|
+
import path57 from "node:path";
|
|
25522
25663
|
import { WebSocket } from "ws";
|
|
25523
25664
|
function slashToBang(text5, commandNames) {
|
|
25524
25665
|
if (commandNames.length === 0)
|
|
@@ -25527,11 +25668,6 @@ function slashToBang(text5, commandNames) {
|
|
|
25527
25668
|
const re = new RegExp(`(^|[\\s(\\[,\uFF0C\u3001])\\/(${alternation})\\b`, "g");
|
|
25528
25669
|
return text5.replace(re, "$1!$2");
|
|
25529
25670
|
}
|
|
25530
|
-
function isPathAllowed2(filePath, workspacePath) {
|
|
25531
|
-
const resolved = path58.resolve(filePath);
|
|
25532
|
-
const wsResolved = path58.resolve(workspacePath);
|
|
25533
|
-
return resolved.startsWith(wsResolved + path58.sep) || resolved === wsResolved || isInTempDir(resolved);
|
|
25534
|
-
}
|
|
25535
25671
|
function rememberEventId(id) {
|
|
25536
25672
|
if (SEEN_EVENT_IDS.has(id))
|
|
25537
25673
|
return true;
|
|
@@ -25789,12 +25925,9 @@ async function handleInbound2(args) {
|
|
|
25789
25925
|
const sm = registry2.getOrCreate(workspace);
|
|
25790
25926
|
const accessLevel = account.accessLevel === "full" ? null : account.accessLevel === "workspace" ? "workspace" : "readonly";
|
|
25791
25927
|
const sessionId = resolveGoalRoute(sm.getDb(), await getOrCreateSessionForThread({ sm, channelId, rootTs, userId, accessLevel, state, workspacePath: workspace }));
|
|
25792
|
-
|
|
25793
|
-
|
|
25794
|
-
|
|
25795
|
-
}
|
|
25796
|
-
if (sm.isSessionRunning(sessionId)) {
|
|
25797
|
-
await postMessage({ botToken: account.botToken, channel: channelId, threadTs: replyTs, text: "\u5DF2\u6536\u5230\uFF0C\u4F1A\u5728\u5F53\u524D\u8F6E\u7ED3\u675F\u540E\u5904\u7406\u3002" });
|
|
25928
|
+
const hint = busyHint(sm, sessionId, getLang(account));
|
|
25929
|
+
if (hint) {
|
|
25930
|
+
await postMessage({ botToken: account.botToken, channel: channelId, threadTs: replyTs, text: hint });
|
|
25798
25931
|
}
|
|
25799
25932
|
if (!state.unsubscribers.has(sessionId)) {
|
|
25800
25933
|
const responder = new SlackResponder({
|
|
@@ -25802,8 +25935,8 @@ async function handleInbound2(args) {
|
|
|
25802
25935
|
await postMessage({ botToken: account.botToken, channel: channelId, threadTs: replyTs, text: chunk });
|
|
25803
25936
|
},
|
|
25804
25937
|
sendMedia: async (filePath) => {
|
|
25805
|
-
const resolved =
|
|
25806
|
-
if (!
|
|
25938
|
+
const resolved = path57.resolve(filePath);
|
|
25939
|
+
if (!isMediaPathAllowed(resolved, workspace)) {
|
|
25807
25940
|
console.log(`[slack] sendMedia blocked: ${filePath} not under workspace`);
|
|
25808
25941
|
return;
|
|
25809
25942
|
}
|
|
@@ -25820,7 +25953,10 @@ async function handleInbound2(args) {
|
|
|
25820
25953
|
botToken: account.botToken,
|
|
25821
25954
|
channel: channelId,
|
|
25822
25955
|
threadTs: replyTs,
|
|
25823
|
-
text:
|
|
25956
|
+
text: t("handler.upload_failed", getLang(account), {
|
|
25957
|
+
name: path57.basename(filePath),
|
|
25958
|
+
error: err instanceof Error ? err.message : String(err)
|
|
25959
|
+
})
|
|
25824
25960
|
}).catch(() => {
|
|
25825
25961
|
});
|
|
25826
25962
|
}
|
|
@@ -25890,6 +26026,7 @@ var init_handler5 = __esm({
|
|
|
25890
26026
|
init_session_prefix();
|
|
25891
26027
|
init_commands();
|
|
25892
26028
|
init_i18n();
|
|
26029
|
+
init_busy_hint();
|
|
25893
26030
|
SLACK_EXTRA_COMMANDS = ["qr"];
|
|
25894
26031
|
SEEN_EVENT_IDS = /* @__PURE__ */ new Set();
|
|
25895
26032
|
SEEN_EVENT_IDS_MAX = 2e3;
|
|
@@ -25899,7 +26036,7 @@ var init_handler5 = __esm({
|
|
|
25899
26036
|
// packages/server/dist/routes/slack.js
|
|
25900
26037
|
import { Hono as Hono16 } from "hono";
|
|
25901
26038
|
import fs52 from "node:fs";
|
|
25902
|
-
import
|
|
26039
|
+
import path58 from "node:path";
|
|
25903
26040
|
function createSlackRoutes(deps) {
|
|
25904
26041
|
const { db, channel } = deps;
|
|
25905
26042
|
const app2 = new Hono16();
|
|
@@ -25942,7 +26079,7 @@ function createSlackRoutes(deps) {
|
|
|
25942
26079
|
return c.json({ error: "appToken must start with xapp-" }, 400);
|
|
25943
26080
|
if (!body.workspacePath)
|
|
25944
26081
|
return c.json({ error: "workspacePath required" }, 400);
|
|
25945
|
-
if (!
|
|
26082
|
+
if (!path58.isAbsolute(body.workspacePath))
|
|
25946
26083
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
25947
26084
|
if (!fs52.existsSync(body.workspacePath))
|
|
25948
26085
|
return c.json({ error: "workspace path not found" }, 400);
|
|
@@ -26042,7 +26179,7 @@ function parseChatId(raw) {
|
|
|
26042
26179
|
return { channel: raw };
|
|
26043
26180
|
return { channel: raw.slice(0, idx), threadTs: raw.slice(idx + 1) || void 0 };
|
|
26044
26181
|
}
|
|
26045
|
-
async function dispatch3(accountId, text5, explicitChatId) {
|
|
26182
|
+
async function dispatch3(accountId, text5, explicitChatId, media) {
|
|
26046
26183
|
const acct = getAccount5(getChannelDb(), accountId);
|
|
26047
26184
|
if (!acct)
|
|
26048
26185
|
throw new Error(`slack account ${accountId} not found`);
|
|
@@ -26052,18 +26189,37 @@ async function dispatch3(accountId, text5, explicitChatId) {
|
|
|
26052
26189
|
throw new Error('slack cron target requires an explicit chatId (e.g. "D01234567" for a DM, or "C0123:1700.0" for a thread). Create the cron from inside a Slack chat to auto-pin, or pass --targets slack:<accountId>:<chatId>.');
|
|
26053
26190
|
}
|
|
26054
26191
|
const { channel, threadTs } = parseChatId(explicitChatId);
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
|
|
26059
|
-
|
|
26060
|
-
|
|
26061
|
-
|
|
26062
|
-
|
|
26063
|
-
|
|
26064
|
-
|
|
26065
|
-
|
|
26192
|
+
const out = [];
|
|
26193
|
+
if (text5) {
|
|
26194
|
+
try {
|
|
26195
|
+
await postMessage({ botToken: acct.botToken, channel, threadTs, text: text5 });
|
|
26196
|
+
out.push({ channelType: "slack", accountId, chatId: explicitChatId, ok: true });
|
|
26197
|
+
} catch (err) {
|
|
26198
|
+
out.push({
|
|
26199
|
+
channelType: "slack",
|
|
26200
|
+
accountId,
|
|
26201
|
+
chatId: explicitChatId,
|
|
26202
|
+
ok: false,
|
|
26203
|
+
error: err instanceof Error ? err.message : String(err)
|
|
26204
|
+
});
|
|
26205
|
+
}
|
|
26066
26206
|
}
|
|
26207
|
+
for (const filePath of media?.paths ?? []) {
|
|
26208
|
+
if (!isMediaPathAllowed(filePath, media.workspacePath)) {
|
|
26209
|
+
console.log(`[slack] cron uploadFile blocked: ${filePath} not under ${media.workspacePath}`);
|
|
26210
|
+
out.push({ channelType: "slack", accountId, chatId: explicitChatId, ok: false, error: `media path not under job workspace: ${filePath}` });
|
|
26211
|
+
continue;
|
|
26212
|
+
}
|
|
26213
|
+
try {
|
|
26214
|
+
await uploadFile({ botToken: acct.botToken, channel, threadTs, filePath });
|
|
26215
|
+
out.push({ channelType: "slack", accountId, chatId: explicitChatId, ok: true });
|
|
26216
|
+
} catch (err) {
|
|
26217
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26218
|
+
console.log(`[slack] cron uploadFile ${filePath} failed: ${msg}`);
|
|
26219
|
+
out.push({ channelType: "slack", accountId, chatId: explicitChatId, ok: false, error: `media ${filePath}: ${msg}` });
|
|
26220
|
+
}
|
|
26221
|
+
}
|
|
26222
|
+
return out;
|
|
26067
26223
|
}
|
|
26068
26224
|
function listTargets3() {
|
|
26069
26225
|
const cdb = getChannelDb();
|
|
@@ -26077,7 +26233,7 @@ function listTargets3() {
|
|
|
26077
26233
|
}));
|
|
26078
26234
|
}
|
|
26079
26235
|
function registerSlackCronDispatcher() {
|
|
26080
|
-
registerCronDispatcher({ channelType: "slack", dispatch: dispatch3, listTargets: listTargets3 });
|
|
26236
|
+
registerCronDispatcher({ channelType: "slack", supportsMedia: true, dispatch: dispatch3, listTargets: listTargets3 });
|
|
26081
26237
|
}
|
|
26082
26238
|
var init_cron_dispatcher3 = __esm({
|
|
26083
26239
|
"packages/server/dist/channels/slack/cron-dispatcher.js"() {
|
|
@@ -26085,6 +26241,7 @@ var init_cron_dispatcher3 = __esm({
|
|
|
26085
26241
|
init_channel_db();
|
|
26086
26242
|
init_accounts5();
|
|
26087
26243
|
init_api2();
|
|
26244
|
+
init_media();
|
|
26088
26245
|
init_dispatcher();
|
|
26089
26246
|
}
|
|
26090
26247
|
});
|
|
@@ -26193,13 +26350,13 @@ var init_accounts6 = __esm({
|
|
|
26193
26350
|
});
|
|
26194
26351
|
|
|
26195
26352
|
// packages/server/dist/channels/feishu/event-adapter.js
|
|
26196
|
-
var HARD_CHARS4,
|
|
26353
|
+
var HARD_CHARS4, FeishuResponder;
|
|
26197
26354
|
var init_event_adapter4 = __esm({
|
|
26198
26355
|
"packages/server/dist/channels/feishu/event-adapter.js"() {
|
|
26199
26356
|
"use strict";
|
|
26200
26357
|
init_markdown();
|
|
26358
|
+
init_media();
|
|
26201
26359
|
HARD_CHARS4 = 4500;
|
|
26202
|
-
MEDIA_MARKER_RE5 = /^MEDIA:\s*(\S.*?)\s*$/gm;
|
|
26203
26360
|
FeishuResponder = class {
|
|
26204
26361
|
buffer = "";
|
|
26205
26362
|
deps;
|
|
@@ -26263,12 +26420,7 @@ var init_event_adapter4 = __esm({
|
|
|
26263
26420
|
return limit;
|
|
26264
26421
|
}
|
|
26265
26422
|
async dispatchChunk(chunk) {
|
|
26266
|
-
const mediaPaths =
|
|
26267
|
-
const stripped = chunk.replace(MEDIA_MARKER_RE5, (_m, p) => {
|
|
26268
|
-
if (p)
|
|
26269
|
-
mediaPaths.push(p.trim());
|
|
26270
|
-
return "";
|
|
26271
|
-
}).replace(/\n{3,}/g, "\n\n").trim();
|
|
26423
|
+
const { text: stripped, mediaPaths } = extractMediaMessage(chunk);
|
|
26272
26424
|
const text5 = formatForFeishu(stripped);
|
|
26273
26425
|
if (text5) {
|
|
26274
26426
|
try {
|
|
@@ -26419,9 +26571,9 @@ async function replyMessage(args) {
|
|
|
26419
26571
|
}
|
|
26420
26572
|
async function uploadImage(args) {
|
|
26421
26573
|
const fs56 = await import("node:fs");
|
|
26422
|
-
const
|
|
26574
|
+
const path63 = await import("node:path");
|
|
26423
26575
|
const buf = fs56.readFileSync(args.filePath);
|
|
26424
|
-
const filename =
|
|
26576
|
+
const filename = path63.basename(args.filePath);
|
|
26425
26577
|
const send = async (token2) => {
|
|
26426
26578
|
const form = new FormData();
|
|
26427
26579
|
form.append("image_type", "message");
|
|
@@ -26453,9 +26605,9 @@ async function uploadImage(args) {
|
|
|
26453
26605
|
}
|
|
26454
26606
|
async function uploadFile2(args) {
|
|
26455
26607
|
const fs56 = await import("node:fs");
|
|
26456
|
-
const
|
|
26608
|
+
const path63 = await import("node:path");
|
|
26457
26609
|
const buf = fs56.readFileSync(args.filePath);
|
|
26458
|
-
const filename = args.filename ??
|
|
26610
|
+
const filename = args.filename ?? path63.basename(args.filePath);
|
|
26459
26611
|
const send = async (token2) => {
|
|
26460
26612
|
const form = new FormData();
|
|
26461
26613
|
form.append("file_type", args.fileType);
|
|
@@ -26561,13 +26713,8 @@ var init_api3 = __esm({
|
|
|
26561
26713
|
});
|
|
26562
26714
|
|
|
26563
26715
|
// packages/server/dist/channels/feishu/handler.js
|
|
26564
|
-
import
|
|
26716
|
+
import path59 from "node:path";
|
|
26565
26717
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
26566
|
-
function isPathAllowed3(filePath, workspacePath) {
|
|
26567
|
-
const resolved = path60.resolve(filePath);
|
|
26568
|
-
const wsResolved = path60.resolve(workspacePath);
|
|
26569
|
-
return resolved.startsWith(wsResolved + path60.sep) || resolved === wsResolved || isInTempDir(resolved);
|
|
26570
|
-
}
|
|
26571
26718
|
function newAccountState2() {
|
|
26572
26719
|
return {
|
|
26573
26720
|
wsClient: null,
|
|
@@ -26795,12 +26942,9 @@ async function handleInbound3(args) {
|
|
|
26795
26942
|
const sm = registry2.getOrCreate(workspace);
|
|
26796
26943
|
const accessLevel = account.accessLevel === "full" ? null : account.accessLevel === "workspace" ? "workspace" : "readonly";
|
|
26797
26944
|
const sessionId = resolveGoalRoute(sm.getDb(), await getOrCreateSessionForThread2({ sm, chatId, rootId, userId, accessLevel, state, workspacePath: workspace }));
|
|
26798
|
-
|
|
26799
|
-
|
|
26800
|
-
|
|
26801
|
-
}
|
|
26802
|
-
if (sm.isSessionRunning(sessionId)) {
|
|
26803
|
-
await replyToInbound({ account, inboundMessageId, isP2P, chatId, text: "\u5DF2\u6536\u5230\uFF0C\u4F1A\u5728\u5F53\u524D\u8F6E\u7ED3\u675F\u540E\u5904\u7406\u3002" });
|
|
26945
|
+
const hint = busyHint(sm, sessionId, getLang(account));
|
|
26946
|
+
if (hint) {
|
|
26947
|
+
await replyToInbound({ account, inboundMessageId, isP2P, chatId, text: hint });
|
|
26804
26948
|
}
|
|
26805
26949
|
if (!state.unsubscribers.has(sessionId)) {
|
|
26806
26950
|
const responder = new FeishuResponder({
|
|
@@ -26808,8 +26952,8 @@ async function handleInbound3(args) {
|
|
|
26808
26952
|
await replyToInbound({ account, inboundMessageId, isP2P, chatId, text: chunk });
|
|
26809
26953
|
},
|
|
26810
26954
|
sendMedia: async (filePath) => {
|
|
26811
|
-
const resolved =
|
|
26812
|
-
if (!
|
|
26955
|
+
const resolved = path59.resolve(filePath);
|
|
26956
|
+
if (!isMediaPathAllowed(resolved, workspace)) {
|
|
26813
26957
|
console.log(`[feishu] sendMedia blocked: ${filePath} not under workspace`);
|
|
26814
26958
|
return;
|
|
26815
26959
|
}
|
|
@@ -26828,7 +26972,10 @@ async function handleInbound3(args) {
|
|
|
26828
26972
|
inboundMessageId,
|
|
26829
26973
|
isP2P,
|
|
26830
26974
|
chatId,
|
|
26831
|
-
text:
|
|
26975
|
+
text: t("handler.upload_failed", getLang(account), {
|
|
26976
|
+
name: path59.basename(filePath),
|
|
26977
|
+
error: err instanceof Error ? err.message : String(err)
|
|
26978
|
+
})
|
|
26832
26979
|
}).catch(() => {
|
|
26833
26980
|
});
|
|
26834
26981
|
}
|
|
@@ -26858,7 +27005,7 @@ async function replyToInbound(args) {
|
|
|
26858
27005
|
async function sendFeishuMedia(args) {
|
|
26859
27006
|
const { account, inboundMessageId, isP2P, chatId, filePath } = args;
|
|
26860
27007
|
const cls = classifyMedia(filePath);
|
|
26861
|
-
const ext =
|
|
27008
|
+
const ext = path59.extname(filePath).toLowerCase();
|
|
26862
27009
|
if (cls === "image") {
|
|
26863
27010
|
const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
|
|
26864
27011
|
await replyToInboundRaw({
|
|
@@ -26993,13 +27140,14 @@ var init_handler6 = __esm({
|
|
|
26993
27140
|
init_goal_mode();
|
|
26994
27141
|
init_session_prefix();
|
|
26995
27142
|
init_i18n();
|
|
27143
|
+
init_busy_hint();
|
|
26996
27144
|
}
|
|
26997
27145
|
});
|
|
26998
27146
|
|
|
26999
27147
|
// packages/server/dist/routes/feishu.js
|
|
27000
27148
|
import { Hono as Hono17 } from "hono";
|
|
27001
27149
|
import fs53 from "node:fs";
|
|
27002
|
-
import
|
|
27150
|
+
import path60 from "node:path";
|
|
27003
27151
|
function createFeishuRoutes(deps) {
|
|
27004
27152
|
const { db, channel } = deps;
|
|
27005
27153
|
const app2 = new Hono17();
|
|
@@ -27041,7 +27189,7 @@ function createFeishuRoutes(deps) {
|
|
|
27041
27189
|
return c.json({ error: "appSecret required" }, 400);
|
|
27042
27190
|
if (!body.workspacePath)
|
|
27043
27191
|
return c.json({ error: "workspacePath required" }, 400);
|
|
27044
|
-
if (!
|
|
27192
|
+
if (!path60.isAbsolute(body.workspacePath))
|
|
27045
27193
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
27046
27194
|
if (!fs53.existsSync(body.workspacePath))
|
|
27047
27195
|
return c.json({ error: "workspace path not found" }, 400);
|
|
@@ -27375,18 +27523,46 @@ function createAuthRoutes() {
|
|
|
27375
27523
|
setCookie(c, COOKIE_NAME, "", { path: "/", maxAge: 0 });
|
|
27376
27524
|
return c.json({ ok: true });
|
|
27377
27525
|
});
|
|
27526
|
+
app2.post("/auth/change-password", async (c) => {
|
|
27527
|
+
const body = await c.req.json().catch(() => ({}));
|
|
27528
|
+
const oldPassword = typeof body.oldPassword === "string" ? body.oldPassword : "";
|
|
27529
|
+
const newPassword = typeof body.newPassword === "string" ? body.newPassword : "";
|
|
27530
|
+
if (passwordEnvPlaintext() != null) {
|
|
27531
|
+
return c.json({ error: "Password is managed by the HALO_PASSWORD environment variable" }, 400);
|
|
27532
|
+
}
|
|
27533
|
+
if (!await verifyPassword(oldPassword, passwordHash())) {
|
|
27534
|
+
return c.json({ error: "Incorrect current password" }, 401);
|
|
27535
|
+
}
|
|
27536
|
+
const weak = passwordStrengthError(newPassword);
|
|
27537
|
+
if (weak)
|
|
27538
|
+
return c.json({ error: weak }, 400);
|
|
27539
|
+
if (newPassword === oldPassword) {
|
|
27540
|
+
return c.json({ error: "New password must differ from the current password" }, 400);
|
|
27541
|
+
}
|
|
27542
|
+
updateConfigLeaves({ "server.password": await hashPassword(newPassword) });
|
|
27543
|
+
return c.json({ ok: true });
|
|
27544
|
+
});
|
|
27378
27545
|
return app2;
|
|
27379
27546
|
}
|
|
27547
|
+
function passwordStrengthError(pw) {
|
|
27548
|
+
if (pw.length < 8)
|
|
27549
|
+
return "Password must be at least 8 characters";
|
|
27550
|
+
if (!/[A-Za-z]/.test(pw))
|
|
27551
|
+
return "Password must contain at least one letter";
|
|
27552
|
+
if (!/[0-9]/.test(pw))
|
|
27553
|
+
return "Password must contain at least one digit";
|
|
27554
|
+
return null;
|
|
27555
|
+
}
|
|
27380
27556
|
function authMiddleware() {
|
|
27381
27557
|
return async (c, next) => {
|
|
27382
|
-
const
|
|
27383
|
-
if (PUBLIC_PATHS.some((p) =>
|
|
27558
|
+
const path63 = c.req.path;
|
|
27559
|
+
if (PUBLIC_PATHS.some((p) => path63 === p || path63.startsWith(p + "?"))) {
|
|
27384
27560
|
return next();
|
|
27385
27561
|
}
|
|
27386
27562
|
const token = getCookie(c, COOKIE_NAME);
|
|
27387
27563
|
const payload = validateToken(token);
|
|
27388
27564
|
if (!payload) {
|
|
27389
|
-
if (!
|
|
27565
|
+
if (!path63.startsWith("/api/")) {
|
|
27390
27566
|
return next();
|
|
27391
27567
|
}
|
|
27392
27568
|
return c.json({ error: "Unauthorized" }, 401);
|
|
@@ -27404,6 +27580,7 @@ var init_auth = __esm({
|
|
|
27404
27580
|
"use strict";
|
|
27405
27581
|
init_config();
|
|
27406
27582
|
init_password_hash();
|
|
27583
|
+
init_setup_config();
|
|
27407
27584
|
init_brute_force();
|
|
27408
27585
|
TOKEN_MAX_AGE = config.auth.tokenMaxAge;
|
|
27409
27586
|
REFRESH_AFTER = config.auth.refreshAfter;
|
|
@@ -27423,7 +27600,7 @@ import { cors } from "hono/cors";
|
|
|
27423
27600
|
import { serve } from "@hono/node-server";
|
|
27424
27601
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
27425
27602
|
import { WebSocketServer } from "ws";
|
|
27426
|
-
import
|
|
27603
|
+
import path61 from "node:path";
|
|
27427
27604
|
import fs54 from "node:fs";
|
|
27428
27605
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
27429
27606
|
import { homedir as homedir24 } from "node:os";
|
|
@@ -27431,9 +27608,9 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
27431
27608
|
function findProjectRoot() {
|
|
27432
27609
|
let dir = process.cwd();
|
|
27433
27610
|
for (let i = 0; i < 32; i++) {
|
|
27434
|
-
if (fs54.existsSync(
|
|
27611
|
+
if (fs54.existsSync(path61.join(dir, "pnpm-workspace.yaml")))
|
|
27435
27612
|
return dir;
|
|
27436
|
-
const parent =
|
|
27613
|
+
const parent = path61.dirname(dir);
|
|
27437
27614
|
if (parent === dir)
|
|
27438
27615
|
break;
|
|
27439
27616
|
dir = parent;
|
|
@@ -27442,7 +27619,7 @@ function findProjectRoot() {
|
|
|
27442
27619
|
return process.cwd();
|
|
27443
27620
|
}
|
|
27444
27621
|
function acquireSingleInstanceLock(lockFile) {
|
|
27445
|
-
fs54.mkdirSync(
|
|
27622
|
+
fs54.mkdirSync(path61.dirname(lockFile), { recursive: true });
|
|
27446
27623
|
const fd = fs54.openSync(lockFile, fs54.constants.O_RDWR | fs54.constants.O_CREAT, 420);
|
|
27447
27624
|
const result = spawnSync4("flock", ["-n", "3"], {
|
|
27448
27625
|
stdio: ["ignore", "ignore", "ignore", fd]
|
|
@@ -27499,7 +27676,7 @@ function isProcessAlive(pid) {
|
|
|
27499
27676
|
}
|
|
27500
27677
|
}
|
|
27501
27678
|
function readGitSha() {
|
|
27502
|
-
if (!fs54.existsSync(
|
|
27679
|
+
if (!fs54.existsSync(path61.join(PROJECT_ROOT, "pnpm-workspace.yaml")) || !fs54.existsSync(path61.join(PROJECT_ROOT, ".git")))
|
|
27503
27680
|
return null;
|
|
27504
27681
|
try {
|
|
27505
27682
|
const rev = spawnSync4("git", ["rev-parse", "--short", "HEAD"], { cwd: PROJECT_ROOT, encoding: "utf-8" });
|
|
@@ -27516,14 +27693,14 @@ function readGitSha() {
|
|
|
27516
27693
|
}
|
|
27517
27694
|
function resolveFrontendDir() {
|
|
27518
27695
|
if (process.env.HALO_FRONTEND_DIR)
|
|
27519
|
-
return
|
|
27520
|
-
const bundled =
|
|
27521
|
-
if (fs54.existsSync(
|
|
27696
|
+
return path61.resolve(process.env.HALO_FRONTEND_DIR);
|
|
27697
|
+
const bundled = path61.resolve(__dirname3, "..", "admin-out");
|
|
27698
|
+
if (fs54.existsSync(path61.join(bundled, "index.html")))
|
|
27522
27699
|
return bundled;
|
|
27523
|
-
const sibling =
|
|
27524
|
-
if (fs54.existsSync(
|
|
27700
|
+
const sibling = path61.resolve(__dirname3, "..", "..", "admin", "out");
|
|
27701
|
+
if (fs54.existsSync(path61.join(sibling, "index.html")))
|
|
27525
27702
|
return sibling;
|
|
27526
|
-
return
|
|
27703
|
+
return path61.resolve(PROJECT_ROOT, "packages", "admin", "out");
|
|
27527
27704
|
}
|
|
27528
27705
|
async function gracefulShutdown(signal) {
|
|
27529
27706
|
console.log(`[Server] Received ${signal}, shutting down gracefully...`);
|
|
@@ -27576,13 +27753,13 @@ var init_dist2 = __esm({
|
|
|
27576
27753
|
init_sandbox();
|
|
27577
27754
|
init_init();
|
|
27578
27755
|
init_git_ssh();
|
|
27579
|
-
__dirname3 =
|
|
27756
|
+
__dirname3 = path61.dirname(fileURLToPath3(import.meta.url));
|
|
27580
27757
|
PORT = config.server.port;
|
|
27581
|
-
HALO_VERSION = "1.0.
|
|
27758
|
+
HALO_VERSION = "1.0.3";
|
|
27582
27759
|
PROJECT_ROOT = findProjectRoot();
|
|
27583
|
-
HALO_HOME4 =
|
|
27760
|
+
HALO_HOME4 = path61.join(homedir24(), ".halo");
|
|
27584
27761
|
GIT_SHA = readGitSha();
|
|
27585
|
-
if (!fs54.existsSync(
|
|
27762
|
+
if (!fs54.existsSync(path61.join(HALO_HOME4, "global", ".template-version"))) {
|
|
27586
27763
|
process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
|
|
27587
27764
|
process.exit(1);
|
|
27588
27765
|
}
|
|
@@ -27603,7 +27780,7 @@ var init_dist2 = __esm({
|
|
|
27603
27780
|
process.exit(1);
|
|
27604
27781
|
}
|
|
27605
27782
|
if (!AGENTCORE)
|
|
27606
|
-
acquireSingleInstanceLock(
|
|
27783
|
+
acquireSingleInstanceLock(path61.join(HALO_HOME4, "global", "server.lock"));
|
|
27607
27784
|
{
|
|
27608
27785
|
const declared = new Set(commandRegistry.listDescriptors().filter((d) => d.source === "builtin").map((d) => d.slashName));
|
|
27609
27786
|
const dispatched = new Set(DISPATCH_COMMANDS);
|
|
@@ -27669,10 +27846,10 @@ var init_dist2 = __esm({
|
|
|
27669
27846
|
app.route("/api", evolutionRoutes);
|
|
27670
27847
|
cronRoutes = createCronRoutes();
|
|
27671
27848
|
app.route("/api", cronRoutes);
|
|
27672
|
-
channelDb = createChannelDb(
|
|
27849
|
+
channelDb = createChannelDb(path61.join(HALO_HOME4, "secrets"));
|
|
27673
27850
|
setChannelDb(channelDb);
|
|
27674
|
-
setEvoDb(createEvoDb(
|
|
27675
|
-
setCronDb(createCronDb(
|
|
27851
|
+
setEvoDb(createEvoDb(path61.join(HALO_HOME4, "global")));
|
|
27852
|
+
setCronDb(createCronDb(path61.join(HALO_HOME4, "global")));
|
|
27676
27853
|
setEvoSpawner(realEvoSpawner);
|
|
27677
27854
|
if (!AGENTCORE) {
|
|
27678
27855
|
startEvoTicker();
|
|
@@ -27696,9 +27873,9 @@ var init_dist2 = __esm({
|
|
|
27696
27873
|
console.log(`[Server] AgentCore runtime mode \u2014 workspace: ${config.server.agentcoreWorkspace}`);
|
|
27697
27874
|
}
|
|
27698
27875
|
FRONTEND_DIR = resolveFrontendDir();
|
|
27699
|
-
app.use("/*", serveStatic({ root:
|
|
27876
|
+
app.use("/*", serveStatic({ root: path61.relative(process.cwd(), FRONTEND_DIR) }));
|
|
27700
27877
|
app.get("/*", (c) => {
|
|
27701
|
-
const indexPath =
|
|
27878
|
+
const indexPath = path61.join(FRONTEND_DIR, "index.html");
|
|
27702
27879
|
try {
|
|
27703
27880
|
const html = fs54.readFileSync(indexPath, "utf-8");
|
|
27704
27881
|
return c.html(html);
|
|
@@ -28469,11 +28646,11 @@ process.emitWarning = (warning, ...args) => {
|
|
|
28469
28646
|
};
|
|
28470
28647
|
|
|
28471
28648
|
// packages/cli/src/index.ts
|
|
28472
|
-
import
|
|
28649
|
+
import path62 from "node:path";
|
|
28473
28650
|
import fs55 from "node:fs";
|
|
28474
28651
|
import { homedir as homedir25 } from "node:os";
|
|
28475
28652
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
28476
|
-
var VERSION = "1.0.
|
|
28653
|
+
var VERSION = "1.0.3";
|
|
28477
28654
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
28478
28655
|
|
|
28479
28656
|
Commands:
|
|
@@ -28726,8 +28903,8 @@ async function cmdSetup(argv = []) {
|
|
|
28726
28903
|
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));
|
|
28727
28904
|
const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
|
|
28728
28905
|
const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
|
|
28729
|
-
const haloHome2 =
|
|
28730
|
-
const globalDir2 =
|
|
28906
|
+
const haloHome2 = path62.join(homedir25(), ".halo");
|
|
28907
|
+
const globalDir2 = path62.join(haloHome2, "global");
|
|
28731
28908
|
ensureHaloHome2(haloHome2);
|
|
28732
28909
|
process.stderr.write(`[setup] ${haloHome2}/ ready
|
|
28733
28910
|
`);
|
|
@@ -29071,7 +29248,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
|
|
|
29071
29248
|
async function cmdAgents(flags) {
|
|
29072
29249
|
const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
29073
29250
|
await initRuntime2();
|
|
29074
|
-
const workspace =
|
|
29251
|
+
const workspace = path62.resolve(flags.workspace ?? process.cwd());
|
|
29075
29252
|
const agents = await listAgents2(workspace);
|
|
29076
29253
|
if (agents.length === 0) {
|
|
29077
29254
|
process.stderr.write("No agents found.\n");
|
|
@@ -29087,7 +29264,7 @@ async function cmdAgents(flags) {
|
|
|
29087
29264
|
async function cmdSessions(flags) {
|
|
29088
29265
|
const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
29089
29266
|
await initRuntime2();
|
|
29090
|
-
const workspace =
|
|
29267
|
+
const workspace = path62.resolve(flags.workspace ?? process.cwd());
|
|
29091
29268
|
const sessions2 = listSessions2(workspace);
|
|
29092
29269
|
if (sessions2.length === 0) {
|
|
29093
29270
|
process.stderr.write("No sessions found.\n");
|
|
@@ -29189,8 +29366,8 @@ ${stdinContent}` : stdinContent;
|
|
|
29189
29366
|
harness.destroy();
|
|
29190
29367
|
}
|
|
29191
29368
|
}
|
|
29192
|
-
var SERVER_LOCK_PATH =
|
|
29193
|
-
var SERVER_LOG_PATH =
|
|
29369
|
+
var SERVER_LOCK_PATH = path62.join(homedir25(), ".halo", "global", "server.lock");
|
|
29370
|
+
var SERVER_LOG_PATH = path62.join(homedir25(), ".halo", "logs", "server.log");
|
|
29194
29371
|
function readServerPid() {
|
|
29195
29372
|
try {
|
|
29196
29373
|
const txt = fs55.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
|
|
@@ -29275,7 +29452,7 @@ async function cmdServerStart(argv, _unused) {
|
|
|
29275
29452
|
return;
|
|
29276
29453
|
}
|
|
29277
29454
|
const { spawn: spawn4 } = await import("node:child_process");
|
|
29278
|
-
fs55.mkdirSync(
|
|
29455
|
+
fs55.mkdirSync(path62.dirname(SERVER_LOG_PATH), { recursive: true });
|
|
29279
29456
|
const out = fs55.openSync(SERVER_LOG_PATH, "a");
|
|
29280
29457
|
const child = spawn4(process.argv[0], [process.argv[1], "server", "start"], {
|
|
29281
29458
|
detached: true,
|
|
@@ -29394,7 +29571,7 @@ function exitMissingSetup() {
|
|
|
29394
29571
|
process.exit(1);
|
|
29395
29572
|
}
|
|
29396
29573
|
function isHaloHomeReady() {
|
|
29397
|
-
return fs55.existsSync(
|
|
29574
|
+
return fs55.existsSync(path62.join(homedir25(), ".halo", "global", ".template-version"));
|
|
29398
29575
|
}
|
|
29399
29576
|
async function main2() {
|
|
29400
29577
|
const argv = process.argv.slice(2);
|