@turmind/halo 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -46
- package/admin-out/404.html +1 -1
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +1 -1
- package/bundled-docs/design/agentcore.md +97 -0
- package/bundled-docs/design/evolution.md +2 -2
- package/bundled-docs/design/halo-city.md +18 -5
- package/bundled-docs/design/storage.md +4 -4
- package/bundled-docs/design/web.md +10 -8
- package/bundled-docs/dev/api.md +3 -1
- package/bundled-docs/dev/deploy.md +10 -0
- package/bundled-docs/dev/env.md +3 -2
- package/bundled-docs/dev/tools.md +4 -3
- package/bundled-docs/guide/channels/web.md +2 -0
- package/bundled-docs/requirements/settings.md +10 -3
- package/dist/index.js +986 -341
- package/package.json +1 -1
- /package/admin-out/_next/static/{iSrc6429pyTB0lK-EE5oH → vHdygDsxwoyxkXg5umpZ1}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{iSrc6429pyTB0lK-EE5oH → vHdygDsxwoyxkXg5umpZ1}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -198,6 +198,10 @@ function generalSection() {
|
|
|
198
198
|
{ key: "theme", type: "enum", options: ["dark", "light", "midnight", "warm"], optionLabels: ["Dark", "Light", "Midnight", "Warm"], globalOnly: true, description: "Admin UI color theme.", description_zh: "Admin \u754C\u9762\u914D\u8272\u4E3B\u9898\u3002", default: "dark" },
|
|
199
199
|
// agent scaffold
|
|
200
200
|
{ key: "agent.default_provider", type: "enum", options: providerIds, description: "Provider used when scaffolding a new agent. Model id, endpoint, prompt-caching TTL, and thinking defaults are read from that provider's YAML.", description_zh: "\u65B0\u5EFA agent \u65F6\u4F7F\u7528\u7684\u4F9B\u5E94\u5546\u3002\u6A21\u578B id\u3001endpoint\u3001\u63D0\u793A\u7F13\u5B58 TTL\u3001Thinking \u9ED8\u8BA4\u503C\u90FD\u4ECE\u8BE5\u4F9B\u5E94\u5546\u7684 YAML \u8BFB\u53D6\u3002", default: defaultProvider2 },
|
|
201
|
+
{ key: "agent.max_retries", type: "int", description: "Max attempts per model call on transient errors (rate limit, 5xx, network). Backoff grows between attempts.", description_zh: "\u5355\u6B21\u6A21\u578B\u8C03\u7528\u9047\u5230\u77AC\u6001\u9519\u8BEF\uFF08\u9650\u6D41\u30015xx\u3001\u7F51\u7EDC\uFF09\u65F6\u7684\u6700\u5927\u5C1D\u8BD5\u6B21\u6570\uFF0C\u91CD\u8BD5\u95F4\u9694\u9012\u589E\u3002", default: "5" },
|
|
202
|
+
// server — network-facing knobs; global-only since they alter how the
|
|
203
|
+
// server itself authenticates clients.
|
|
204
|
+
{ key: "server.trust_proxy", type: "boolean", globalOnly: true, description: "Trust the x-forwarded-for header for client IP resolution (brute-force rate limiting). Enable ONLY when a reverse proxy you control sits in front and rewrites the header \u2014 otherwise clients can forge it to bypass lockouts.", description_zh: "\u4FE1\u4EFB x-forwarded-for \u8BF7\u6C42\u5934\u89E3\u6790\u5BA2\u6237\u7AEF IP\uFF08\u66B4\u529B\u7834\u89E3\u9650\u901F\u7528\uFF09\u3002\u4EC5\u5F53\u524D\u9762\u6709\u4F60\u63A7\u5236\u7684\u53CD\u5411\u4EE3\u7406\u5E76\u4F1A\u91CD\u5199\u8BE5\u5934\u65F6\u624D\u5F00\u542F\u2014\u2014\u5426\u5219\u5BA2\u6237\u7AEF\u53EF\u4F2A\u9020\u7ED5\u8FC7\u9501\u5B9A\u3002", default: "false" },
|
|
201
205
|
// session
|
|
202
206
|
{ key: "session.max_queue_size", type: "int", description: "Maximum queued messages per session", description_zh: "\u6BCF\u4E2A\u4F1A\u8BDD\u6700\u5927\u6392\u961F\u6D88\u606F\u6570", default: "256" },
|
|
203
207
|
{ key: "session.max_nesting_depth", type: "int", description: "Maximum session nesting depth for agent delegation", description_zh: "Agent \u59D4\u6D3E\u7684\u6700\u5927\u4F1A\u8BDD\u5D4C\u5957\u6DF1\u5EA6", default: "16" },
|
|
@@ -207,22 +211,24 @@ function generalSection() {
|
|
|
207
211
|
{ key: "compact.max_summary_input", type: "int", description: "Max chars fed into local truncation fallback", description_zh: "\u672C\u5730\u622A\u65AD\u515C\u5E95\u65F6\u7684\u603B\u8F93\u5165\u5B57\u7B26\u4E0A\u9650", default: "15000" },
|
|
208
212
|
{ key: "compact.max_message_slice", type: "int", description: "Max chars kept per old message during local truncation", description_zh: "\u672C\u5730\u622A\u65AD\u515C\u5E95\u65F6\u6BCF\u6761\u65E7\u6D88\u606F\u4FDD\u7559\u7684\u6700\u5927\u5B57\u7B26\u6570", default: "800" },
|
|
209
213
|
{ key: "compact.summarize_timeout_sec", type: "int", description: "LLM summarization timeout (seconds)", description_zh: "LLM \u6458\u8981\u8D85\u65F6\u65F6\u95F4\uFF08\u79D2\uFF09", default: "300" },
|
|
210
|
-
// sandbox (Linux bwrap only)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
{ key: "sandbox.
|
|
214
|
+
// sandbox (Linux bwrap only) — global-only: these define the security
|
|
215
|
+
// boundary agents run inside; a workspace overriding them could lift
|
|
216
|
+
// its own sandbox constraints.
|
|
217
|
+
{ key: "sandbox.hidden_dirs", globalOnly: true, description: "Comma-separated dirs hidden by bwrap (Linux only)", description_zh: "\u901A\u8FC7 bwrap tmpfs \u9690\u85CF\u7684\u76EE\u5F55\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u4EC5 Linux\uFF09", default: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs" },
|
|
218
|
+
{ key: "sandbox.writable_dirs", globalOnly: true, description: "Comma-separated dirs bind-mounted read-write inside the bwrap sandbox (Linux only) \u2014 for external CLIs that keep local state, e.g. ~/.kiro,~/.local/share/kiro-cli. Not applied to readonly sessions.", description_zh: "\u5728 bwrap \u6C99\u7BB1\u5185\u4EE5\u53EF\u5199\u65B9\u5F0F\u6302\u8F7D\u7684\u76EE\u5F55\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u4EC5 Linux\uFF09\u2014\u2014\u7ED9\u9700\u8981\u672C\u5730\u72B6\u6001\u7684\u5916\u90E8 CLI \u7528\uFF0C\u5982 ~/.kiro,~/.local/share/kiro-cli\u3002readonly \u4F1A\u8BDD\u4E0D\u751F\u6548\u3002", default: "" },
|
|
219
|
+
{ key: "sandbox.hidden_files", globalOnly: true, description: "Comma-separated files hidden by bwrap (Linux only)", description_zh: "\u901A\u8FC7 /dev/null bind \u9690\u85CF\u7684\u6587\u4EF6\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u4EC5 Linux\uFF09", default: "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/evo.db,~/.halo/global/evo.db-wal,~/.halo/global/evo.db-shm,~/.halo/global/cron.db,~/.halo/global/cron.db-wal,~/.halo/global/cron.db-shm" },
|
|
214
220
|
// logging
|
|
215
221
|
{ key: "logging.level", type: "enum", options: ["debug", "info", "warn", "error"], description: "Log level", description_zh: "\u65E5\u5FD7\u7EA7\u522B", default: "warn" },
|
|
216
222
|
// self-evolution (see plans/self-evolution.md). All evo settings are
|
|
217
223
|
// global-only — the worker / ticker live in the server process and
|
|
218
224
|
// would have to reload mid-flight if a workspace could override them.
|
|
219
|
-
{ key: "evolution.level", type: "enum", options: ["L0", "L1"], globalOnly: true, description: "Self-evolution capability ladder. L0: manual only \u2014 drafts via /
|
|
225
|
+
{ key: "evolution.level", type: "enum", options: ["L0", "L1"], globalOnly: true, description: "Self-evolution capability ladder. L0: manual only \u2014 drafts via /evo, manual approve/reject. L1: L0 plus automatic drafting on pre-compact. (Queued runs always execute regardless of level; the level only gates how they get enqueued.)", description_zh: "\u81EA\u6211\u8FDB\u5316\u7B49\u7EA7\u3002L0\uFF1A\u4EC5\u624B\u52A8\u2014\u2014\u901A\u8FC7 /evo \u8D77\u8349\uFF0C\u624B\u52A8 approve/reject\u3002L1\uFF1A\u5728 L0 \u57FA\u7840\u4E0A\uFF0C\u538B\u7F29\u524D\uFF08pre-compact\uFF09\u81EA\u52A8\u8D77\u8349\u3002\uFF08\u5DF2\u6392\u961F\u7684 run \u59CB\u7EC8\u6267\u884C\uFF0Clevel \u53EA\u51B3\u5B9A\u5982\u4F55\u5165\u961F\u3002\uFF09", default: "L0" },
|
|
220
226
|
{ key: "evolution.max_concurrent_run", type: "int", globalOnly: true, description: "Max concurrent evo run wrappers (drafting patches).", description_zh: "\u540C\u65F6\u8FD0\u884C\u7684 evo run wrapper \u4E0A\u9650\uFF08\u8D77\u8349 patch\uFF09\u3002", default: "1" },
|
|
221
227
|
{ key: "evolution.max_concurrent_apply", type: "int", globalOnly: true, description: "Max concurrent evo apply wrappers (merging patches).", description_zh: "\u540C\u65F6\u8FD0\u884C\u7684 evo apply wrapper \u4E0A\u9650\uFF08\u5408\u5E76 patch\uFF09\u3002", default: "1" },
|
|
222
|
-
{ key: "evolution.run_timeout_minutes", type: "int", globalOnly: true, description: "Heartbeat timeout for run wrappers
|
|
228
|
+
{ key: "evolution.run_timeout_minutes", type: "int", globalOnly: true, description: "Heartbeat timeout for run wrappers. The wrapper writes a heartbeat every 60s for as long as it lives, so exceeding this means the wrapper process died \u2014 the row is requeued (or marked timeout past max_attempts). This is dead-wrapper detection latency, NOT a budget for slow models; the per-phase model budget is the wrapper-internal 30min cap.", description_zh: "run wrapper \u5FC3\u8DF3\u8D85\u65F6\uFF08\u5206\u949F\uFF09\u3002wrapper \u5B58\u6D3B\u671F\u95F4\u6BCF 60 \u79D2\u5199\u4E00\u6B21\u5FC3\u8DF3\uFF0C\u8D85\u65F6\u5373\u89C6\u4E3A wrapper \u8FDB\u7A0B\u5DF2\u6B7B\uFF0C\u4EFB\u52A1\u91CD\u6392\uFF08\u8D85\u8FC7 max_attempts \u5219\u6807 timeout\uFF09\u3002\u8FD9\u662F\u6B7B wrapper \u7684\u68C0\u6D4B\u5EF6\u8FDF\uFF0C\u4E0D\u662F\u6162\u6A21\u578B\u7684\u8FD0\u884C\u9884\u7B97\u2014\u2014\u5355\u9636\u6BB5\u6A21\u578B\u9884\u7B97\u662F wrapper \u5185\u90E8\u7684 30 \u5206\u949F\u4E0A\u9650\u3002", default: "12" },
|
|
223
229
|
{ key: "evolution.apply_timeout_minutes", type: "int", globalOnly: true, description: "Heartbeat timeout for apply wrappers.", description_zh: "apply wrapper \u5FC3\u8DF3\u8D85\u65F6\uFF08\u5206\u949F\uFF09\u3002", default: "5" },
|
|
224
230
|
{ key: "evolution.max_attempts", type: "int", globalOnly: true, description: "How many times the ticker may try to spawn a wrapper for a single row before giving up. Stops infinite retry loops on permanently broken tasks.", description_zh: "ticker \u4E3A\u5355\u6761\u4EFB\u52A1\u5C1D\u8BD5\u62C9\u8D77 wrapper \u7684\u6700\u5927\u6B21\u6570\uFF0C\u8D85\u8FC7\u540E\u653E\u5F03\u3002\u907F\u514D\u574F\u4EFB\u52A1\u65E0\u9650\u91CD\u8BD5\u3002", default: "3" },
|
|
225
|
-
{ key: "evolution.triggers.pre_compact", type: "boolean", globalOnly: true, description: "Snapshot the session right before compaction so evo can analyze it. /
|
|
231
|
+
{ key: "evolution.triggers.pre_compact", type: "boolean", globalOnly: true, description: "Snapshot the session right before compaction so evo can analyze it. /evo is always available regardless of this setting.", description_zh: "\u5728 compact \u524D\u5FEB\u7167\u4F1A\u8BDD\u4F9B evo \u5206\u6790\u3002/evo \u603B\u662F\u53EF\u7528\uFF0C\u4E0E\u6B64\u5F00\u5173\u65E0\u5173\u3002", default: "true" },
|
|
226
232
|
// limits — runtime caps that protect against runaway tool output / UI
|
|
227
233
|
// freezes. All read live from settings.yaml so changes take effect on
|
|
228
234
|
// the next tool call without a server restart.
|
|
@@ -231,6 +237,7 @@ function generalSection() {
|
|
|
231
237
|
{ key: "limits.grep_default_matches", type: "int", globalOnly: true, description: "Default match cap for the grep tool when the call site does not pass an explicit `max`.", description_zh: "grep \u5DE5\u5177\u672A\u663E\u5F0F\u4F20 `max` \u53C2\u6570\u65F6\u7684\u9ED8\u8BA4\u5339\u914D\u4E0A\u9650\u3002", default: "50" },
|
|
232
238
|
{ key: "limits.tool_result_render_chars", type: "int", globalOnly: true, description: 'Per-tool-result cap on the content fed back to the LLM. Larger results are truncated (with a "[Content truncated]" marker telling the model to re-run with narrower scope) to protect the context window and prompt cache. The UI/admin chat panel shows a far larger slice \u2014 see tool_result_ui_chars.', description_zh: "\u6BCF\u6761 tool result \u56DE\u4F20\u7ED9 LLM \u7684\u5185\u5BB9\u957F\u5EA6\u4E0A\u9650\u3002\u8D85\u51FA\u90E8\u5206\u4F1A\u88AB\u622A\u65AD\uFF08\u5E76\u9644 [Content truncated] \u6807\u8BB0\u63D0\u793A\u6A21\u578B\u7F29\u5C0F\u8303\u56F4\u91CD\u67E5\uFF09\uFF0C\u4EE5\u4FDD\u62A4\u4E0A\u4E0B\u6587\u7A97\u53E3\u548C\u63D0\u793A\u8BCD\u7F13\u5B58\u3002UI/admin \u804A\u5929\u9762\u677F\u5C55\u793A\u7684\u5185\u5BB9\u4E0A\u9650\u8981\u5927\u5F97\u591A\uFF0C\u89C1 tool_result_ui_chars\u3002", default: "8000" },
|
|
233
239
|
{ key: "limits.tool_result_ui_chars", type: "int", globalOnly: true, description: "Per-tool-result cap on the content stored for UI display (admin/web chat panel). Far larger than the LLM cap so the full output of a normal command stays visible, but bounded so a multi-MB `cat` does not bloat the session file, the WS payload, or the browser render. Output past this is truncated with a marker pointing at file_read for the complete content.", description_zh: "\u6BCF\u6761 tool result \u5B58\u7ED9 UI \u5C55\u793A\uFF08admin/web \u804A\u5929\u9762\u677F\uFF09\u7684\u5185\u5BB9\u957F\u5EA6\u4E0A\u9650\u3002\u8FDC\u5927\u4E8E LLM \u4E0A\u9650\uFF0C\u4FDD\u8BC1\u666E\u901A\u547D\u4EE4\u7684\u5B8C\u6574\u8F93\u51FA\u90FD\u80FD\u770B\u5230\uFF1B\u4F46\u4ECD\u6709\u754C\uFF0C\u907F\u514D cat \u4E2A\u51E0 MB \u7684\u6587\u4EF6\u6491\u7206\u4F1A\u8BDD\u6587\u4EF6\u3001WS \u4F20\u8F93\u548C\u6D4F\u89C8\u5668\u6E32\u67D3\u3002\u8D85\u51FA\u90E8\u5206\u622A\u65AD\u5E76\u9644\u6807\u8BB0\uFF0C\u63D0\u793A\u7528 file_read \u770B\u5B8C\u6574\u5185\u5BB9\u3002", default: String(64 * 1024) },
|
|
240
|
+
{ key: "limits.auto_report_chars", type: "int", globalOnly: true, description: "Cap on the auto-report a finished sub-agent delivers to its parent. Longer reports are truncated with a pointer to get_session_output for the full text.", description_zh: "\u5B50\u4F1A\u8BDD\u5B8C\u6210\u540E\u81EA\u52A8\u4E0A\u62A5\u7ED9\u7236\u4F1A\u8BDD\u7684\u5185\u5BB9\u957F\u5EA6\u4E0A\u9650\u3002\u8D85\u51FA\u622A\u65AD\uFF0C\u5E76\u63D0\u793A\u7528 get_session_output \u53D6\u5168\u6587\u3002", default: "8192" },
|
|
234
241
|
{ key: "limits.ws_event_buffer", type: "int", globalOnly: true, description: "Events buffered per detached WebSocket session before the oldest are dropped on reattach.", description_zh: "WebSocket \u4F1A\u8BDD\u5728\u65AD\u5F00\u671F\u95F4\u6700\u591A\u7F13\u5B58\u591A\u5C11\u4E8B\u4EF6\uFF0C\u8D85\u51FA\u540E\u4ECE\u6700\u65E9\u7684\u5F00\u59CB\u4E22\u3002", default: "5000" },
|
|
235
242
|
{ key: "limits.terminal_scrollback_bytes", type: "int", globalOnly: true, description: "Off-screen scrollback bytes retained per persistent terminal while detached.", description_zh: "\u7EC8\u7AEF\u65AD\u5F00\u671F\u95F4\u4FDD\u7559\u7684\u56DE\u6EDA\u7F13\u51B2\u5B57\u8282\u6570\u3002", default: "50000" }
|
|
236
243
|
].map((f) => ({ ...f, kind: "param" }))
|
|
@@ -716,7 +723,21 @@ var init_config = __esm({
|
|
|
716
723
|
// Random base64 JWT signing key, set by `halo setup`. Empty = not set up.
|
|
717
724
|
jwtSecret: systemString("HALO_JWT_SECRET", "server.jwt_secret", null),
|
|
718
725
|
// Empty allowlist means "reflect any origin" (see CORS setup in index.ts).
|
|
719
|
-
corsOrigins: systemStringArray("HALO_CORS_ORIGINS", "server.cors_origins", [])
|
|
726
|
+
corsOrigins: systemStringArray("HALO_CORS_ORIGINS", "server.cors_origins", []),
|
|
727
|
+
/** Trust `x-forwarded-for` for client-IP resolution (brute-force
|
|
728
|
+
* buckets). Off by default — the header is client-forgeable unless a
|
|
729
|
+
* reverse proxy the operator controls rewrites it. */
|
|
730
|
+
get trustProxy() {
|
|
731
|
+
return settingsBool("general.server.trust_proxy", false);
|
|
732
|
+
},
|
|
733
|
+
/** `HALO_RUNTIME_MODE=agentcore` runs the server as an Amazon Bedrock
|
|
734
|
+
* AgentCore Runtime container: auth is terminated upstream by AgentCore,
|
|
735
|
+
* so password/JWT checks, channels, cron/evolution and the instance lock
|
|
736
|
+
* are skipped, and the /ping + /invocations + WS /ws adapter is mounted
|
|
737
|
+
* (see routes/agentcore.ts). Unset / any other value → normal server. */
|
|
738
|
+
runtimeMode: process.env.HALO_RUNTIME_MODE ?? null,
|
|
739
|
+
/** Workspace for agentcore mode. Env `HALO_WORKSPACE`, fallback cwd. */
|
|
740
|
+
agentcoreWorkspace: process.env.HALO_WORKSPACE ?? process.cwd()
|
|
720
741
|
},
|
|
721
742
|
// `model.compressAt` is intentionally a getter — it reads from
|
|
722
743
|
// settings.yaml (`general.compact.compress_at`) on every access so a
|
|
@@ -820,8 +841,8 @@ var init_config = __esm({
|
|
|
820
841
|
maxNestingDepth: settingsInt("general.session.max_nesting_depth", 16)
|
|
821
842
|
},
|
|
822
843
|
sandbox: {
|
|
823
|
-
hiddenDirs: settingsStr("general.sandbox.hidden_dirs", "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker").split(",").map((s) => s.trim()).filter(Boolean),
|
|
824
|
-
hiddenFiles: settingsStr("general.sandbox.hidden_files", "~/.npmrc,~/.bash_history,~/.gitconfig").split(",").map((s) => s.trim()).filter(Boolean),
|
|
844
|
+
hiddenDirs: settingsStr("general.sandbox.hidden_dirs", "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs").split(",").map((s) => s.trim()).filter(Boolean),
|
|
845
|
+
hiddenFiles: settingsStr("general.sandbox.hidden_files", "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/evo.db,~/.halo/global/evo.db-wal,~/.halo/global/evo.db-shm,~/.halo/global/cron.db,~/.halo/global/cron.db-wal,~/.halo/global/cron.db-shm").split(",").map((s) => s.trim()).filter(Boolean),
|
|
825
846
|
writableDirs: settingsStr("general.sandbox.writable_dirs", "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
826
847
|
},
|
|
827
848
|
logging: {
|
|
@@ -870,8 +891,8 @@ var init_config = __esm({
|
|
|
870
891
|
}
|
|
871
892
|
}
|
|
872
893
|
};
|
|
873
|
-
HIDDEN_DIRS_DEFAULT = "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker";
|
|
874
|
-
HIDDEN_FILES_DEFAULT = "~/.npmrc,~/.bash_history,~/.gitconfig";
|
|
894
|
+
HIDDEN_DIRS_DEFAULT = "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs";
|
|
895
|
+
HIDDEN_FILES_DEFAULT = "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/evo.db,~/.halo/global/evo.db-wal,~/.halo/global/evo.db-shm,~/.halo/global/cron.db,~/.halo/global/cron.db-wal,~/.halo/global/cron.db-shm";
|
|
875
896
|
}
|
|
876
897
|
});
|
|
877
898
|
|
|
@@ -1848,11 +1869,11 @@ function copyTemplate(srcAbs, dstAbs) {
|
|
|
1848
1869
|
const content = fs6.readFileSync(srcAbs, "utf-8");
|
|
1849
1870
|
fs6.writeFileSync(dstAbs, content, "utf-8");
|
|
1850
1871
|
}
|
|
1851
|
-
function writeIfMissing(filePath, content) {
|
|
1872
|
+
function writeIfMissing(filePath, content, mode) {
|
|
1852
1873
|
if (fs6.existsSync(filePath))
|
|
1853
1874
|
return;
|
|
1854
1875
|
fs6.mkdirSync(path6.dirname(filePath), { recursive: true });
|
|
1855
|
-
fs6.writeFileSync(filePath, content, "utf-8");
|
|
1876
|
+
fs6.writeFileSync(filePath, content, mode === void 0 ? { encoding: "utf-8" } : { encoding: "utf-8", mode });
|
|
1856
1877
|
console.log(`[Init] Created ${filePath}`);
|
|
1857
1878
|
}
|
|
1858
1879
|
function mergeAgentYaml(srcAbs, dstAbs) {
|
|
@@ -1969,7 +1990,7 @@ function forceCopyDir(srcDir, dstDir) {
|
|
|
1969
1990
|
function migrateSecrets(haloHome2) {
|
|
1970
1991
|
const globalDir2 = path6.join(haloHome2, "global");
|
|
1971
1992
|
const secretsDir = path6.join(haloHome2, "secrets");
|
|
1972
|
-
fs6.mkdirSync(secretsDir, { recursive: true });
|
|
1993
|
+
fs6.mkdirSync(secretsDir, { recursive: true, mode: 448 });
|
|
1973
1994
|
const fileMoves = [
|
|
1974
1995
|
[path6.join(globalDir2, "settings.yaml"), path6.join(secretsDir, "settings.yaml")],
|
|
1975
1996
|
[path6.join(globalDir2, "config.yaml"), path6.join(secretsDir, "config.yaml")]
|
|
@@ -2045,7 +2066,11 @@ function ensureHaloHome(haloHome2) {
|
|
|
2045
2066
|
const globalDir2 = path6.join(haloHome2, "global");
|
|
2046
2067
|
const secretsDir = path6.join(haloHome2, "secrets");
|
|
2047
2068
|
fs6.mkdirSync(globalDir2, { recursive: true });
|
|
2048
|
-
fs6.mkdirSync(secretsDir, { recursive: true });
|
|
2069
|
+
fs6.mkdirSync(secretsDir, { recursive: true, mode: 448 });
|
|
2070
|
+
try {
|
|
2071
|
+
fs6.chmodSync(secretsDir, 448);
|
|
2072
|
+
} catch {
|
|
2073
|
+
}
|
|
2049
2074
|
migrateSecrets(haloHome2);
|
|
2050
2075
|
const versionPath = path6.join(globalDir2, VERSION_FILE);
|
|
2051
2076
|
let existingVersion = 0;
|
|
@@ -2098,7 +2123,7 @@ function ensureHaloHome(haloHome2) {
|
|
|
2098
2123
|
}
|
|
2099
2124
|
}
|
|
2100
2125
|
mergeConfigYaml(path6.join(TEMPLATES_DIR, "config.yaml"), path6.join(secretsDir, "config.yaml"));
|
|
2101
|
-
writeIfMissing(path6.join(secretsDir, "settings.yaml"), "");
|
|
2126
|
+
writeIfMissing(path6.join(secretsDir, "settings.yaml"), "", 384);
|
|
2102
2127
|
const aliasesSrc = path6.join(TEMPLATES_DIR, "aliases.yaml");
|
|
2103
2128
|
if (fs6.existsSync(aliasesSrc)) {
|
|
2104
2129
|
writeIfMissing(path6.join(globalDir2, "aliases.yaml"), fs6.readFileSync(aliasesSrc, "utf-8"));
|
|
@@ -2511,7 +2536,8 @@ function buildSessionTools(sm, sessionId) {
|
|
|
2511
2536
|
agent_id: { type: "string", description: "The agent ID \u2014 must be one of the agents listed in your system prompt. Do not invent ids." },
|
|
2512
2537
|
message: { type: "string", description: "Task description / initial message for the agent" },
|
|
2513
2538
|
system_prompt_context: { type: "string", description: "Optional background material prepended to the opening message BEFORE your task text \u2014 despite the name, it is NOT injected into the system prompt. Put here what frames the whole task: relevant file paths, prior findings, user-stated constraints, role framing. Keep one-shot task instructions in `message` itself so it stays a clean task statement. Note the cost: this text lives in the child's conversation history and is re-sent on every one of its model calls until compaction \u2014 include what the child genuinely needs throughout, not bulk material it could read from files instead." },
|
|
2514
|
-
working_dir: { type: "string", description: "Optional focus directory for the sub-agent (workspace-relative or absolute; must be inside the workspace). The platform bakes the directory-scoped INSTRUCTIONS.md found along the path from the workspace root down to this directory into the sub-agent's system prompt (present every turn), and tags its prompt with this focus. Does NOT change where tools run (shell/file tools still operate from the project root). Omit for project-root scope." }
|
|
2539
|
+
working_dir: { type: "string", description: "Optional focus directory for the sub-agent (workspace-relative or absolute; must be inside the workspace). The platform bakes the directory-scoped INSTRUCTIONS.md found along the path from the workspace root down to this directory into the sub-agent's system prompt (present every turn), and tags its prompt with this focus. Does NOT change where tools run (shell/file tools still operate from the project root). Omit for project-root scope." },
|
|
2540
|
+
title: { type: "string", description: "Optional session title shown in the admin sidebar. When omitted, auto-generated from the task message." }
|
|
2515
2541
|
},
|
|
2516
2542
|
required: ["agent_id", "message"]
|
|
2517
2543
|
},
|
|
@@ -2548,7 +2574,7 @@ function buildSessionTools(sm, sessionId) {
|
|
|
2548
2574
|
const displayName = agentYaml.name ?? params.agent_id;
|
|
2549
2575
|
const parentSession = sm.sessions.get(sessionId);
|
|
2550
2576
|
const inheritedAccessLevel = parentSession?.accessLevel ?? null;
|
|
2551
|
-
const childSessionId = await sm.createSession(params.agent_id, sessionId, params.message.slice(0, 200), displayName, void 0, resolvedWorkingDir, inheritedAccessLevel);
|
|
2577
|
+
const childSessionId = await sm.createSession(params.agent_id, sessionId, params.message.slice(0, 200), displayName, void 0, resolvedWorkingDir, inheritedAccessLevel, params.title);
|
|
2552
2578
|
let initialMessage = `[Session ${childSessionId}]
|
|
2553
2579
|
|
|
2554
2580
|
`;
|
|
@@ -6330,11 +6356,23 @@ function assertPathAllowed(filePath, opts, write = false) {
|
|
|
6330
6356
|
}
|
|
6331
6357
|
return resolved;
|
|
6332
6358
|
}
|
|
6333
|
-
if (!write && (resolved === GLOBAL_DIR || resolved.startsWith(GLOBAL_DIR + "/"))) {
|
|
6359
|
+
if (!write && (resolved === GLOBAL_DIR || resolved.startsWith(GLOBAL_DIR + "/")) && !isHiddenGlobalPath(resolved)) {
|
|
6334
6360
|
return resolved;
|
|
6335
6361
|
}
|
|
6336
6362
|
throw new Error(`Access denied: "${filePath}" is outside the allowed sandbox paths`);
|
|
6337
6363
|
}
|
|
6364
|
+
function isHiddenGlobalPath(resolved) {
|
|
6365
|
+
for (const raw of _hiddenFiles) {
|
|
6366
|
+
if (resolved === expandTilde(raw))
|
|
6367
|
+
return true;
|
|
6368
|
+
}
|
|
6369
|
+
for (const raw of _hiddenDirs) {
|
|
6370
|
+
const dir = expandTilde(raw);
|
|
6371
|
+
if (resolved === dir || resolved.startsWith(dir + "/"))
|
|
6372
|
+
return true;
|
|
6373
|
+
}
|
|
6374
|
+
return false;
|
|
6375
|
+
}
|
|
6338
6376
|
function buildExecScriptArgs(workspaceRoot, command) {
|
|
6339
6377
|
return ["bash", "-c", `cd "$1" && shift && ${command}`, "bash", workspaceRoot];
|
|
6340
6378
|
}
|
|
@@ -6554,12 +6592,32 @@ var init_sandbox = __esm({
|
|
|
6554
6592
|
"~/.aws",
|
|
6555
6593
|
"~/.ssh",
|
|
6556
6594
|
"~/.gnupg",
|
|
6557
|
-
"~/.docker"
|
|
6595
|
+
"~/.docker",
|
|
6596
|
+
"~/.config/gh",
|
|
6597
|
+
// Cross-workspace state under ~/.halo/global — internal-agent session
|
|
6598
|
+
// transcripts and server/cron logs would leak other workspaces' activity
|
|
6599
|
+
// to a workspace/readonly session (global/ is readable by design for
|
|
6600
|
+
// skills/agents/prompts, so the sensitive subtrees are hidden explicitly).
|
|
6601
|
+
"~/.halo/global/internal-sessions",
|
|
6602
|
+
"~/.halo/global/logs"
|
|
6558
6603
|
];
|
|
6559
6604
|
DEFAULT_HIDDEN_FILES = [
|
|
6560
6605
|
"~/.npmrc",
|
|
6561
6606
|
"~/.bash_history",
|
|
6562
|
-
"~/.gitconfig"
|
|
6607
|
+
"~/.gitconfig",
|
|
6608
|
+
// Halo's own git-credential store (git-credentials.ts writes tokens here
|
|
6609
|
+
// in plaintext) — must be hidden like ~/.aws & co.
|
|
6610
|
+
"~/.git-credentials",
|
|
6611
|
+
"~/.netrc",
|
|
6612
|
+
// Global evolution / cron databases (plus sqlite WAL/SHM sidecars) carry
|
|
6613
|
+
// cross-workspace prompts, run history and channel targets — hidden from
|
|
6614
|
+
// workspace/readonly sessions like the dirs above.
|
|
6615
|
+
"~/.halo/global/evo.db",
|
|
6616
|
+
"~/.halo/global/evo.db-wal",
|
|
6617
|
+
"~/.halo/global/evo.db-shm",
|
|
6618
|
+
"~/.halo/global/cron.db",
|
|
6619
|
+
"~/.halo/global/cron.db-wal",
|
|
6620
|
+
"~/.halo/global/cron.db-shm"
|
|
6563
6621
|
];
|
|
6564
6622
|
_hiddenDirs = DEFAULT_HIDDEN_DIRS;
|
|
6565
6623
|
_hiddenFiles = DEFAULT_HIDDEN_FILES;
|
|
@@ -6762,6 +6820,13 @@ async function substituteSecrets(cmd, workspaceRoot, allowedNamespaces) {
|
|
|
6762
6820
|
return { cmd, secrets: [] };
|
|
6763
6821
|
const secrets = [];
|
|
6764
6822
|
const settings = await loadMergedSettings(workspaceRoot);
|
|
6823
|
+
const declaredSecretParams = /* @__PURE__ */ new Set();
|
|
6824
|
+
for (const section of loadSettingsSchema(workspaceRoot)) {
|
|
6825
|
+
for (const f of section.fields) {
|
|
6826
|
+
if (f.kind === "param" && f.secret === true)
|
|
6827
|
+
declaredSecretParams.add(`${section.namespaceId}.${f.key}`);
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6765
6830
|
const result = cmd.replace(PARAMS_PATTERN, (_match, namespace, dotted) => {
|
|
6766
6831
|
if (allowedNamespaces && !allowedNamespaces.has(namespace)) {
|
|
6767
6832
|
console.log(`[workspace-tools] {{${namespace}.params.${dotted}}} rejected \u2014 namespace not in allowed list`);
|
|
@@ -6795,7 +6860,7 @@ async function substituteSecrets(cmd, workspaceRoot, allowedNamespaces) {
|
|
|
6795
6860
|
envInjected = true;
|
|
6796
6861
|
return val;
|
|
6797
6862
|
});
|
|
6798
|
-
if (envInjected && expanded !== "")
|
|
6863
|
+
if ((envInjected || declaredSecretParams.has(`${namespace}.${dotted}`)) && expanded !== "")
|
|
6799
6864
|
secrets.push(expanded);
|
|
6800
6865
|
return expanded;
|
|
6801
6866
|
});
|
|
@@ -7344,6 +7409,7 @@ var init_workspace_tools = __esm({
|
|
|
7344
7409
|
init_config();
|
|
7345
7410
|
init_sandbox();
|
|
7346
7411
|
init_md_vars();
|
|
7412
|
+
init_settings_schema();
|
|
7347
7413
|
init_media_store();
|
|
7348
7414
|
init_agent_loop();
|
|
7349
7415
|
SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", ".halo"]);
|
|
@@ -8810,7 +8876,7 @@ var init_session_manager = __esm({
|
|
|
8810
8876
|
* Create a new session. Inserts metadata into SQLite, builds agent, stores in-memory.
|
|
8811
8877
|
* @param explicitId - Optional explicit session ID (for unifying frontend sessionId with agent sessionId)
|
|
8812
8878
|
*/
|
|
8813
|
-
async createSession(agentId, parentId, description, agentName, explicitId, workingDir, accessLevel = null) {
|
|
8879
|
+
async createSession(agentId, parentId, description, agentName, explicitId, workingDir, accessLevel = null, title) {
|
|
8814
8880
|
const segment = generateSessionId();
|
|
8815
8881
|
const sessionId = explicitId ?? (parentId ? `${parentId}>${segment}` : segment);
|
|
8816
8882
|
const now = Date.now();
|
|
@@ -8827,6 +8893,15 @@ var init_session_manager = __esm({
|
|
|
8827
8893
|
createdAt: now,
|
|
8828
8894
|
updatedAt: now
|
|
8829
8895
|
}).run();
|
|
8896
|
+
if (title) {
|
|
8897
|
+
try {
|
|
8898
|
+
const dir = this.sessionDir(agentId);
|
|
8899
|
+
fsSync6.mkdirSync(dir, { recursive: true });
|
|
8900
|
+
atomicWriteSessionFile(path20.join(dir, `${fileSegment(sessionId)}.json`), JSON.stringify({ id: sessionId, agentId, title, createdAt: new Date(now).toISOString() }, null, 2));
|
|
8901
|
+
} catch (err) {
|
|
8902
|
+
console.warn(`[SessionManager] Failed to pre-seed title for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8903
|
+
}
|
|
8904
|
+
}
|
|
8830
8905
|
const createdImageOverride = createdYaml?.model?.image;
|
|
8831
8906
|
const session = this.createAgentSession(sessionId, parentId, agentId, resolvedAgentName, agent, description, contextConfig, modelId, systemPrompt, thinkingEffort, workingDir ?? null, accessLevel, meta, createdImageOverride, draftReset);
|
|
8832
8907
|
this.sessions.set(sessionId, session);
|
|
@@ -10415,22 +10490,22 @@ function expandAlias(command, arg) {
|
|
|
10415
10490
|
}
|
|
10416
10491
|
return { command: cmd, arg: a };
|
|
10417
10492
|
}
|
|
10418
|
-
function findActiveSessionId(sm, userId, sessionPrefix2,
|
|
10419
|
-
const override =
|
|
10493
|
+
function findActiveSessionId(sm, userId, sessionPrefix2, activeOverrides2, accessLevel) {
|
|
10494
|
+
const override = activeOverrides2.get(userId);
|
|
10420
10495
|
if (override && accessLevel === "full") {
|
|
10421
10496
|
if (sm.getSessionById(override))
|
|
10422
10497
|
return override;
|
|
10423
|
-
|
|
10498
|
+
activeOverrides2.delete(userId);
|
|
10424
10499
|
return null;
|
|
10425
10500
|
}
|
|
10426
10501
|
if (override && override.startsWith(sessionPrefix2)) {
|
|
10427
10502
|
if (sm.getSessionById(override))
|
|
10428
10503
|
return override;
|
|
10429
|
-
|
|
10504
|
+
activeOverrides2.delete(userId);
|
|
10430
10505
|
}
|
|
10431
10506
|
const latest = sm.findLatestByPrefix(sessionPrefix2);
|
|
10432
10507
|
if (!latest) {
|
|
10433
|
-
|
|
10508
|
+
activeOverrides2.delete(userId);
|
|
10434
10509
|
return null;
|
|
10435
10510
|
}
|
|
10436
10511
|
return latest.id;
|
|
@@ -10542,7 +10617,7 @@ async function execNew(ctx) {
|
|
|
10542
10617
|
}
|
|
10543
10618
|
function visibleSessions(ctx) {
|
|
10544
10619
|
const { sessions: sessions2 } = ctx.sm.listSessions(ctx.accessLevel === "full" ? { rootOnly: true, limit: CHANNEL_LIST_LIMIT } : { rootOnly: true, prefix: ctx.sessionPrefix, limit: CHANNEL_LIST_LIMIT });
|
|
10545
|
-
return sessions2;
|
|
10620
|
+
return sessions2.sort((a, b) => b.createdAt - a.createdAt);
|
|
10546
10621
|
}
|
|
10547
10622
|
function execList(ctx) {
|
|
10548
10623
|
const sessions2 = visibleSessions(ctx);
|
|
@@ -11118,7 +11193,7 @@ async function createHarness(opts) {
|
|
|
11118
11193
|
const lang = opts.lang ?? "en";
|
|
11119
11194
|
const agentId = opts.agentId ?? "default";
|
|
11120
11195
|
const accessLevel = !opts.accessLevel || opts.accessLevel === "full" ? null : opts.accessLevel;
|
|
11121
|
-
const
|
|
11196
|
+
const activeOverrides2 = /* @__PURE__ */ new Map();
|
|
11122
11197
|
async function bindWorkspace(workspace, requestedSessionId, newSession = false) {
|
|
11123
11198
|
const sm = registry2.getOrCreate(workspace);
|
|
11124
11199
|
let sessionId;
|
|
@@ -11132,7 +11207,7 @@ async function createHarness(opts) {
|
|
|
11132
11207
|
await sm.createSession(agentId, null, "CLI", agentId, newId, void 0, accessLevel);
|
|
11133
11208
|
sessionId = newId;
|
|
11134
11209
|
} else {
|
|
11135
|
-
const existing = findActiveSessionId(sm, USER_ID, SESSION_PREFIX,
|
|
11210
|
+
const existing = findActiveSessionId(sm, USER_ID, SESSION_PREFIX, activeOverrides2, opts.accessLevel ?? "full");
|
|
11136
11211
|
if (existing) {
|
|
11137
11212
|
sessionId = existing;
|
|
11138
11213
|
} else {
|
|
@@ -11141,7 +11216,7 @@ async function createHarness(opts) {
|
|
|
11141
11216
|
sessionId = newId;
|
|
11142
11217
|
}
|
|
11143
11218
|
}
|
|
11144
|
-
|
|
11219
|
+
activeOverrides2.set(USER_ID, sessionId);
|
|
11145
11220
|
const disabledSet = getDisabledSet(sm.getDb(), "agent");
|
|
11146
11221
|
const agents = await scanAvailableAgents(workspace, disabledSet);
|
|
11147
11222
|
const agentEntry = agents.find((a) => a.id === agentId);
|
|
@@ -11169,7 +11244,7 @@ async function createHarness(opts) {
|
|
|
11169
11244
|
sessionPrefix: SESSION_PREFIX,
|
|
11170
11245
|
accessLevel: opts.accessLevel ?? "full",
|
|
11171
11246
|
channelLabel: "CLI",
|
|
11172
|
-
activeOverrides,
|
|
11247
|
+
activeOverrides: activeOverrides2,
|
|
11173
11248
|
workspacePath: state.workspace,
|
|
11174
11249
|
lang
|
|
11175
11250
|
};
|
|
@@ -11211,7 +11286,7 @@ async function createHarness(opts) {
|
|
|
11211
11286
|
async function command(cmd, arg) {
|
|
11212
11287
|
const result = await dispatchCommand(buildCommandContext(), cmd, arg, { channelName: "cli" });
|
|
11213
11288
|
if (result?.switchTo) {
|
|
11214
|
-
|
|
11289
|
+
activeOverrides2.set(USER_ID, result.switchTo);
|
|
11215
11290
|
state.unsubPersistent();
|
|
11216
11291
|
state = {
|
|
11217
11292
|
...state,
|
|
@@ -11287,7 +11362,7 @@ async function createHarness(opts) {
|
|
|
11287
11362
|
throw new Error(`Workspace does not exist: ${resolved}`);
|
|
11288
11363
|
}
|
|
11289
11364
|
state.unsubPersistent();
|
|
11290
|
-
|
|
11365
|
+
activeOverrides2.delete(USER_ID);
|
|
11291
11366
|
state = await bindWorkspace(resolved);
|
|
11292
11367
|
}
|
|
11293
11368
|
return {
|
|
@@ -11372,8 +11447,14 @@ async function verifyPassword(plain, stored) {
|
|
|
11372
11447
|
return false;
|
|
11373
11448
|
const salt = Buffer.from(saltB64, "base64");
|
|
11374
11449
|
const expected = Buffer.from(hashB64, "base64");
|
|
11375
|
-
|
|
11376
|
-
|
|
11450
|
+
if (expected.length !== KEY_LEN)
|
|
11451
|
+
return false;
|
|
11452
|
+
try {
|
|
11453
|
+
const actual = await scryptAsync(plain, salt, expected.length, { N, r, p });
|
|
11454
|
+
return crypto2.timingSafeEqual(actual, expected);
|
|
11455
|
+
} catch {
|
|
11456
|
+
return false;
|
|
11457
|
+
}
|
|
11377
11458
|
}
|
|
11378
11459
|
function generateJwtSecret() {
|
|
11379
11460
|
return crypto2.randomBytes(32).toString("base64");
|
|
@@ -11523,12 +11604,17 @@ var init_setup_settings = __esm({
|
|
|
11523
11604
|
// packages/server/dist/setup-providers.js
|
|
11524
11605
|
var setup_providers_exports = {};
|
|
11525
11606
|
__export(setup_providers_exports, {
|
|
11607
|
+
AWS_CREDENTIAL_CHAIN_PROVIDER: () => AWS_CREDENTIAL_CHAIN_PROVIDER,
|
|
11608
|
+
bindBuiltinAgentsToProvider: () => bindBuiltinAgentsToProvider,
|
|
11609
|
+
listConfiguredSwitchableProviders: () => listConfiguredSwitchableProviders,
|
|
11526
11610
|
listModelProviders: () => listModelProviders,
|
|
11527
11611
|
listOptionalSkills: () => listOptionalSkills,
|
|
11528
|
-
listRequiredSkillsWithSecrets: () => listRequiredSkillsWithSecrets
|
|
11612
|
+
listRequiredSkillsWithSecrets: () => listRequiredSkillsWithSecrets,
|
|
11613
|
+
readDefaultAgentProvider: () => readDefaultAgentProvider
|
|
11529
11614
|
});
|
|
11530
11615
|
import fs22 from "node:fs";
|
|
11531
11616
|
import path26 from "node:path";
|
|
11617
|
+
import { homedir as homedir14 } from "node:os";
|
|
11532
11618
|
import YAML9 from "yaml";
|
|
11533
11619
|
function readYamlFile2(filePath) {
|
|
11534
11620
|
if (!fs22.existsSync(filePath))
|
|
@@ -11617,6 +11703,69 @@ function listOptionalSkills() {
|
|
|
11617
11703
|
}
|
|
11618
11704
|
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
11619
11705
|
}
|
|
11706
|
+
function listConfiguredSwitchableProviders() {
|
|
11707
|
+
return listModelProviders().filter((p) => {
|
|
11708
|
+
if (p.id === AWS_CREDENTIAL_CHAIN_PROVIDER)
|
|
11709
|
+
return false;
|
|
11710
|
+
return p.fields.some((f) => {
|
|
11711
|
+
const v = readSetting(`${p.id}.secrets.${f.key}`);
|
|
11712
|
+
return v != null && v.length > 0;
|
|
11713
|
+
});
|
|
11714
|
+
});
|
|
11715
|
+
}
|
|
11716
|
+
function readDefaultAgentProvider() {
|
|
11717
|
+
const data = readYamlFile2(path26.join(GLOBAL_AGENTS_DIR3, "default", "agent.yaml"));
|
|
11718
|
+
const provider = data?.model && typeof data.model === "object" ? data.model.provider : void 0;
|
|
11719
|
+
return typeof provider === "string" && provider.length > 0 ? provider : void 0;
|
|
11720
|
+
}
|
|
11721
|
+
function buildTemplateModelBlock(providerId) {
|
|
11722
|
+
const data = readYamlFile2(path26.join(TEMPLATES_DIR, "models", `${providerId}.yaml`));
|
|
11723
|
+
if (!data || typeof data !== "object")
|
|
11724
|
+
return null;
|
|
11725
|
+
const models = Array.isArray(data.models) ? data.models : [];
|
|
11726
|
+
const modelId = (typeof data.defaultModelId === "string" ? data.defaultModelId : void 0) ?? (models[0] ? models[0].id : void 0);
|
|
11727
|
+
const endpoint = typeof data.defaultEndpoint === "string" ? data.defaultEndpoint : void 0;
|
|
11728
|
+
const model = models.find((m) => m.id === modelId);
|
|
11729
|
+
const caps = model?.capabilities ?? {};
|
|
11730
|
+
const promptCaching = caps.promptCaching?.default;
|
|
11731
|
+
const thinkingCap = caps.thinking;
|
|
11732
|
+
const block = { provider: providerId };
|
|
11733
|
+
if (modelId)
|
|
11734
|
+
block.id = modelId;
|
|
11735
|
+
if (endpoint)
|
|
11736
|
+
block.endpoint = endpoint;
|
|
11737
|
+
if (promptCaching)
|
|
11738
|
+
block.promptCaching = promptCaching;
|
|
11739
|
+
if (thinkingCap?.defaultEnabled) {
|
|
11740
|
+
const thinking = { enabled: true };
|
|
11741
|
+
if (thinkingCap.default)
|
|
11742
|
+
thinking.effort = thinkingCap.default;
|
|
11743
|
+
if (thinkingCap.defaultBudgetTokens != null)
|
|
11744
|
+
thinking.budget_tokens = thinkingCap.defaultBudgetTokens;
|
|
11745
|
+
block.thinking = thinking;
|
|
11746
|
+
}
|
|
11747
|
+
return block;
|
|
11748
|
+
}
|
|
11749
|
+
function bindBuiltinAgentsToProvider(providerId) {
|
|
11750
|
+
const block = buildTemplateModelBlock(providerId);
|
|
11751
|
+
if (!block)
|
|
11752
|
+
return null;
|
|
11753
|
+
const agents = [];
|
|
11754
|
+
for (const id of REBIND_BUILTIN_AGENT_IDS) {
|
|
11755
|
+
const yamlPath = path26.join(GLOBAL_AGENTS_DIR3, id, "agent.yaml");
|
|
11756
|
+
if (!fs22.existsSync(yamlPath))
|
|
11757
|
+
continue;
|
|
11758
|
+
try {
|
|
11759
|
+
const doc = YAML9.parseDocument(fs22.readFileSync(yamlPath, "utf-8"));
|
|
11760
|
+
doc.set("model", block);
|
|
11761
|
+
fs22.writeFileSync(yamlPath, doc.toString(), "utf-8");
|
|
11762
|
+
agents.push(id);
|
|
11763
|
+
} catch {
|
|
11764
|
+
}
|
|
11765
|
+
}
|
|
11766
|
+
writeSetting("general.agent.default_provider", providerId);
|
|
11767
|
+
return { modelId: typeof block.id === "string" ? block.id : void 0, agents };
|
|
11768
|
+
}
|
|
11620
11769
|
function listRequiredSkillsWithSecrets() {
|
|
11621
11770
|
const dir = path26.join(TEMPLATES_DIR, "skills");
|
|
11622
11771
|
if (!fs22.existsSync(dir))
|
|
@@ -11656,10 +11805,62 @@ function listRequiredSkillsWithSecrets() {
|
|
|
11656
11805
|
}
|
|
11657
11806
|
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
11658
11807
|
}
|
|
11808
|
+
var AWS_CREDENTIAL_CHAIN_PROVIDER, REBIND_BUILTIN_AGENT_IDS, GLOBAL_AGENTS_DIR3;
|
|
11659
11809
|
var init_setup_providers = __esm({
|
|
11660
11810
|
"packages/server/dist/setup-providers.js"() {
|
|
11661
11811
|
"use strict";
|
|
11662
11812
|
init_init();
|
|
11813
|
+
init_setup_settings();
|
|
11814
|
+
AWS_CREDENTIAL_CHAIN_PROVIDER = "aws-bedrock-claude-invoke";
|
|
11815
|
+
REBIND_BUILTIN_AGENT_IDS = ["default", "executor", "deep-executor"];
|
|
11816
|
+
GLOBAL_AGENTS_DIR3 = path26.join(homedir14(), ".halo", "global", "agents");
|
|
11817
|
+
}
|
|
11818
|
+
});
|
|
11819
|
+
|
|
11820
|
+
// packages/cli/src/setup-bind-provider.ts
|
|
11821
|
+
var setup_bind_provider_exports = {};
|
|
11822
|
+
__export(setup_bind_provider_exports, {
|
|
11823
|
+
AWS_CHAIN_PROVIDER: () => AWS_CHAIN_PROVIDER,
|
|
11824
|
+
KEEP_CURRENT: () => KEEP_CURRENT,
|
|
11825
|
+
buildBindProviderPlan: () => buildBindProviderPlan
|
|
11826
|
+
});
|
|
11827
|
+
function buildBindProviderPlan(configured, currentProvider, currentProviderHasKeys) {
|
|
11828
|
+
if (configured.length === 0) return null;
|
|
11829
|
+
const current = currentProvider ?? AWS_CHAIN_PROVIDER;
|
|
11830
|
+
if (configured.some((p) => p.id === current)) return null;
|
|
11831
|
+
const currentIsAwsChain = current === AWS_CHAIN_PROVIDER;
|
|
11832
|
+
const intro = [
|
|
11833
|
+
`Default agent model provider: ${current}${currentIsAwsChain ? " (works with zero keys via the AWS credential chain: env / ~/.aws / EC2-ECS machine role)" : ""}`
|
|
11834
|
+
];
|
|
11835
|
+
if (currentIsAwsChain) {
|
|
11836
|
+
intro.push('If you rely on AWS credentials / a machine role for Bedrock access, choose "Keep" to stay on Bedrock.');
|
|
11837
|
+
}
|
|
11838
|
+
const options = [
|
|
11839
|
+
...configured.map((p) => ({
|
|
11840
|
+
value: p.id,
|
|
11841
|
+
label: `Switch default agent to ${p.displayName}`,
|
|
11842
|
+
hint: `(${p.id})`
|
|
11843
|
+
})),
|
|
11844
|
+
{
|
|
11845
|
+
value: KEEP_CURRENT,
|
|
11846
|
+
label: `Keep ${current}`,
|
|
11847
|
+
hint: currentIsAwsChain ? "(AWS credential chain / machine role)" : ""
|
|
11848
|
+
}
|
|
11849
|
+
];
|
|
11850
|
+
const initialIndex = configured.length === 1 && !currentProviderHasKeys ? 0 : options.length - 1;
|
|
11851
|
+
return {
|
|
11852
|
+
intro,
|
|
11853
|
+
question: "Bind the default agent to a provider you just configured?",
|
|
11854
|
+
options,
|
|
11855
|
+
initialIndex
|
|
11856
|
+
};
|
|
11857
|
+
}
|
|
11858
|
+
var AWS_CHAIN_PROVIDER, KEEP_CURRENT;
|
|
11859
|
+
var init_setup_bind_provider = __esm({
|
|
11860
|
+
"packages/cli/src/setup-bind-provider.ts"() {
|
|
11861
|
+
"use strict";
|
|
11862
|
+
AWS_CHAIN_PROVIDER = "aws-bedrock-claude-invoke";
|
|
11863
|
+
KEEP_CURRENT = "__keep__";
|
|
11663
11864
|
}
|
|
11664
11865
|
});
|
|
11665
11866
|
|
|
@@ -12472,7 +12673,7 @@ function scanCandidates(partial, workspace, kind) {
|
|
|
12472
12673
|
function applyPathPick(value, ref, pick) {
|
|
12473
12674
|
let markerStart = ref.pathStart - 1;
|
|
12474
12675
|
if (value[markerStart - 1] === '"') markerStart -= 1;
|
|
12475
|
-
const explicitMatch = value.slice(0, markerStart + 1).match(/@(file|image|scope)\s
|
|
12676
|
+
const explicitMatch = value.slice(0, markerStart + 1).match(/@(file|image|scope)\s+"?$/);
|
|
12476
12677
|
if (explicitMatch) markerStart = explicitMatch.index ?? markerStart;
|
|
12477
12678
|
else {
|
|
12478
12679
|
markerStart = ref.pathStart - 1 - (value[ref.pathStart - 1] === '"' ? 1 : 0);
|
|
@@ -12480,8 +12681,8 @@ function applyPathPick(value, ref, pick) {
|
|
|
12480
12681
|
const before = value.slice(0, markerStart);
|
|
12481
12682
|
const kind = ref.kind === "scope" ? "scope" : ref.kind === "image" ? "image" : pick.isImage ? "image" : "file";
|
|
12482
12683
|
const needsQuote = pick.insertPath.includes(" ");
|
|
12483
|
-
const
|
|
12484
|
-
return `${before}@${kind} ${
|
|
12684
|
+
const path63 = pick.isDir ? needsQuote ? `"${pick.insertPath}/` : `${pick.insertPath}/` : needsQuote ? `"${pick.insertPath}" ` : `${pick.insertPath} `;
|
|
12685
|
+
return `${before}@${kind} ${path63}`;
|
|
12485
12686
|
}
|
|
12486
12687
|
var IMAGE_EXTS2;
|
|
12487
12688
|
var init_path_suggest_engine = __esm({
|
|
@@ -13961,7 +14162,8 @@ var init_cli = __esm({
|
|
|
13961
14162
|
|
|
13962
14163
|
// packages/core/dist/workspace/workspace.js
|
|
13963
14164
|
import { readFile, writeFile, mkdir, readdir, access } from "node:fs/promises";
|
|
13964
|
-
import {
|
|
14165
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
14166
|
+
import { resolve, relative, join, isAbsolute, sep } from "node:path";
|
|
13965
14167
|
var WorkspaceError, SKIP_DIRS2, Workspace;
|
|
13966
14168
|
var init_workspace = __esm({
|
|
13967
14169
|
"packages/core/dist/workspace/workspace.js"() {
|
|
@@ -14000,12 +14202,12 @@ None yet.
|
|
|
14000
14202
|
}
|
|
14001
14203
|
console.log(`[Workspace] Initialized workspace: ${this.projectRoot}`);
|
|
14002
14204
|
}
|
|
14003
|
-
async readFile(
|
|
14004
|
-
const fullPath = this.validatePath(
|
|
14205
|
+
async readFile(path63) {
|
|
14206
|
+
const fullPath = this.validatePath(path63);
|
|
14005
14207
|
return readFile(fullPath, "utf-8");
|
|
14006
14208
|
}
|
|
14007
|
-
async writeFile(
|
|
14008
|
-
const fullPath = this.validatePath(
|
|
14209
|
+
async writeFile(path63, content) {
|
|
14210
|
+
const fullPath = this.validatePath(path63);
|
|
14009
14211
|
const dir = resolve(fullPath, "..");
|
|
14010
14212
|
await mkdir(dir, { recursive: true });
|
|
14011
14213
|
await writeFile(fullPath, content, "utf-8");
|
|
@@ -14025,19 +14227,30 @@ None yet.
|
|
|
14025
14227
|
}
|
|
14026
14228
|
return results;
|
|
14027
14229
|
}
|
|
14028
|
-
async fileExists(
|
|
14230
|
+
async fileExists(path63) {
|
|
14029
14231
|
try {
|
|
14030
|
-
const fullPath = this.validatePath(
|
|
14232
|
+
const fullPath = this.validatePath(path63);
|
|
14031
14233
|
await access(fullPath);
|
|
14032
14234
|
return true;
|
|
14033
14235
|
} catch {
|
|
14034
14236
|
return false;
|
|
14035
14237
|
}
|
|
14036
14238
|
}
|
|
14037
|
-
validatePath(
|
|
14038
|
-
const fullPath = isAbsolute(
|
|
14039
|
-
|
|
14040
|
-
|
|
14239
|
+
validatePath(path63) {
|
|
14240
|
+
const fullPath = isAbsolute(path63) ? resolve(path63) : resolve(this.projectRoot, path63);
|
|
14241
|
+
const root = resolve(this.projectRoot);
|
|
14242
|
+
if (fullPath !== root && !fullPath.startsWith(root + sep)) {
|
|
14243
|
+
throw new WorkspaceError(`Path "${path63}" is outside the workspace directory "${this.projectRoot}"`);
|
|
14244
|
+
}
|
|
14245
|
+
try {
|
|
14246
|
+
const real = realpathSync2(fullPath);
|
|
14247
|
+
const realRoot = realpathSync2(root);
|
|
14248
|
+
if (real !== realRoot && !real.startsWith(realRoot + sep)) {
|
|
14249
|
+
throw new WorkspaceError(`Path "${path63}" is outside the workspace directory "${this.projectRoot}"`);
|
|
14250
|
+
}
|
|
14251
|
+
} catch (err) {
|
|
14252
|
+
if (err instanceof WorkspaceError)
|
|
14253
|
+
throw err;
|
|
14041
14254
|
}
|
|
14042
14255
|
return fullPath;
|
|
14043
14256
|
}
|
|
@@ -14146,10 +14359,10 @@ var init_git_manager = __esm({
|
|
|
14146
14359
|
throw new GitManagerError(`Failed to commit: ${message2}`);
|
|
14147
14360
|
}
|
|
14148
14361
|
}
|
|
14149
|
-
async getDiff(
|
|
14362
|
+
async getDiff(path63) {
|
|
14150
14363
|
try {
|
|
14151
|
-
if (
|
|
14152
|
-
return await this.git.diff(["HEAD", "--",
|
|
14364
|
+
if (path63) {
|
|
14365
|
+
return await this.git.diff(["HEAD", "--", path63]);
|
|
14153
14366
|
}
|
|
14154
14367
|
return await this.git.diff(["HEAD"]);
|
|
14155
14368
|
} catch {
|
|
@@ -14225,6 +14438,7 @@ var init_git_manager = __esm({
|
|
|
14225
14438
|
"--name-status",
|
|
14226
14439
|
"-r",
|
|
14227
14440
|
"-m",
|
|
14441
|
+
"-M",
|
|
14228
14442
|
"--root",
|
|
14229
14443
|
hash
|
|
14230
14444
|
]);
|
|
@@ -14237,12 +14451,12 @@ var init_git_manager = __esm({
|
|
|
14237
14451
|
const code = parts[0];
|
|
14238
14452
|
const status = code[0];
|
|
14239
14453
|
const isRenameOrCopy = status === "R" || status === "C";
|
|
14240
|
-
const
|
|
14241
|
-
if (!
|
|
14454
|
+
const path63 = isRenameOrCopy ? parts[2] : parts[1];
|
|
14455
|
+
if (!path63 || seen.has(path63))
|
|
14242
14456
|
continue;
|
|
14243
|
-
seen.add(
|
|
14457
|
+
seen.add(path63);
|
|
14244
14458
|
files.push({
|
|
14245
|
-
path:
|
|
14459
|
+
path: path63,
|
|
14246
14460
|
status,
|
|
14247
14461
|
from: isRenameOrCopy ? parts[1] : void 0
|
|
14248
14462
|
});
|
|
@@ -14390,9 +14604,9 @@ var init_git_manager = __esm({
|
|
|
14390
14604
|
* empty for an added file or the root commit) and `modified` is the version
|
|
14391
14605
|
* at `commit` (empty for a deletion). The working-tree/staged path above is
|
|
14392
14606
|
* left entirely untouched (commit === undefined preserves existing behavior). */
|
|
14393
|
-
async getFileDiff(
|
|
14607
|
+
async getFileDiff(path63, staged, from, commit) {
|
|
14394
14608
|
if (commit) {
|
|
14395
|
-
const parentPath = from ||
|
|
14609
|
+
const parentPath = from || path63;
|
|
14396
14610
|
let original2 = "";
|
|
14397
14611
|
try {
|
|
14398
14612
|
original2 = await this.git.show([`${commit}^:${parentPath}`]);
|
|
@@ -14401,13 +14615,13 @@ var init_git_manager = __esm({
|
|
|
14401
14615
|
}
|
|
14402
14616
|
let modified2 = "";
|
|
14403
14617
|
try {
|
|
14404
|
-
modified2 = await this.git.show([`${commit}:${
|
|
14618
|
+
modified2 = await this.git.show([`${commit}:${path63}`]);
|
|
14405
14619
|
} catch {
|
|
14406
14620
|
modified2 = "";
|
|
14407
14621
|
}
|
|
14408
14622
|
return { original: original2, modified: modified2 };
|
|
14409
14623
|
}
|
|
14410
|
-
const headPath = from ||
|
|
14624
|
+
const headPath = from || path63;
|
|
14411
14625
|
let original = "";
|
|
14412
14626
|
try {
|
|
14413
14627
|
original = await this.git.show([`HEAD:${headPath}`]);
|
|
@@ -14417,13 +14631,13 @@ var init_git_manager = __esm({
|
|
|
14417
14631
|
let modified = "";
|
|
14418
14632
|
if (staged) {
|
|
14419
14633
|
try {
|
|
14420
|
-
modified = await this.git.show([`:${
|
|
14634
|
+
modified = await this.git.show([`:${path63}`]);
|
|
14421
14635
|
} catch {
|
|
14422
14636
|
modified = "";
|
|
14423
14637
|
}
|
|
14424
14638
|
} else {
|
|
14425
14639
|
try {
|
|
14426
|
-
modified = await this.workspace.readFile(
|
|
14640
|
+
modified = await this.workspace.readFile(path63);
|
|
14427
14641
|
} catch {
|
|
14428
14642
|
modified = "";
|
|
14429
14643
|
}
|
|
@@ -14480,7 +14694,7 @@ import fs27 from "node:fs/promises";
|
|
|
14480
14694
|
import { createReadStream } from "node:fs";
|
|
14481
14695
|
import { Readable } from "node:stream";
|
|
14482
14696
|
import path32 from "node:path";
|
|
14483
|
-
import { homedir as
|
|
14697
|
+
import { homedir as homedir15 } from "node:os";
|
|
14484
14698
|
function createFileRoutes() {
|
|
14485
14699
|
const app2 = new Hono();
|
|
14486
14700
|
async function resolveProjectPath(projectId) {
|
|
@@ -15025,7 +15239,7 @@ function createFileRoutes() {
|
|
|
15025
15239
|
}
|
|
15026
15240
|
});
|
|
15027
15241
|
app2.get("/fs/home", (c) => {
|
|
15028
|
-
return c.json({ home:
|
|
15242
|
+
return c.json({ home: homedir15() });
|
|
15029
15243
|
});
|
|
15030
15244
|
app2.post("/fs/workspace/resolve", async (c) => {
|
|
15031
15245
|
const body = await c.req.json();
|
|
@@ -15051,7 +15265,7 @@ function createFileRoutes() {
|
|
|
15051
15265
|
}
|
|
15052
15266
|
});
|
|
15053
15267
|
app2.get("/fs/browse", async (c) => {
|
|
15054
|
-
const target = c.req.query("path") ??
|
|
15268
|
+
const target = c.req.query("path") ?? homedir15();
|
|
15055
15269
|
if (!path32.isAbsolute(target))
|
|
15056
15270
|
return c.json({ error: "absolute path required" }, 400);
|
|
15057
15271
|
try {
|
|
@@ -15076,7 +15290,7 @@ var init_files = __esm({
|
|
|
15076
15290
|
// packages/server/dist/git-credentials.js
|
|
15077
15291
|
import fs28 from "node:fs";
|
|
15078
15292
|
import path33 from "node:path";
|
|
15079
|
-
import { homedir as
|
|
15293
|
+
import { homedir as homedir16 } from "node:os";
|
|
15080
15294
|
import { spawnSync } from "node:child_process";
|
|
15081
15295
|
function writeGitCredentialsFile(host, username, token) {
|
|
15082
15296
|
const line = `https://${encodeURIComponent(username)}:${encodeURIComponent(token)}@${host}`;
|
|
@@ -15150,7 +15364,7 @@ var GIT_CREDENTIALS_PATH;
|
|
|
15150
15364
|
var init_git_credentials = __esm({
|
|
15151
15365
|
"packages/server/dist/git-credentials.js"() {
|
|
15152
15366
|
"use strict";
|
|
15153
|
-
GIT_CREDENTIALS_PATH = path33.join(
|
|
15367
|
+
GIT_CREDENTIALS_PATH = path33.join(homedir16(), ".git-credentials");
|
|
15154
15368
|
}
|
|
15155
15369
|
});
|
|
15156
15370
|
|
|
@@ -15711,7 +15925,7 @@ var init_git = __esm({
|
|
|
15711
15925
|
import { Hono as Hono3 } from "hono";
|
|
15712
15926
|
import fs31 from "node:fs/promises";
|
|
15713
15927
|
import path36 from "node:path";
|
|
15714
|
-
import { homedir as
|
|
15928
|
+
import { homedir as homedir17 } from "node:os";
|
|
15715
15929
|
import YAML10 from "yaml";
|
|
15716
15930
|
function getAvailableTools() {
|
|
15717
15931
|
if (_cachedTools)
|
|
@@ -15872,9 +16086,9 @@ function createAgentConfigRoutes() {
|
|
|
15872
16086
|
return c.json(getModelsRegistry());
|
|
15873
16087
|
});
|
|
15874
16088
|
app2.get("/agent-configs", async (c) => {
|
|
15875
|
-
await ensureDir2(
|
|
15876
|
-
await ensureDefaultAgent(
|
|
15877
|
-
const globalAgents = await scanAgentsDir(
|
|
16089
|
+
await ensureDir2(GLOBAL_AGENTS_DIR4);
|
|
16090
|
+
await ensureDefaultAgent(GLOBAL_AGENTS_DIR4);
|
|
16091
|
+
const globalAgents = await scanAgentsDir(GLOBAL_AGENTS_DIR4, "global");
|
|
15878
16092
|
const projectId = c.req.query("projectId");
|
|
15879
16093
|
let workspaceAgents = [];
|
|
15880
16094
|
if (projectId) {
|
|
@@ -15912,7 +16126,7 @@ function createAgentConfigRoutes() {
|
|
|
15912
16126
|
return c.json({ error: "projectId required for workspace agents" }, 400);
|
|
15913
16127
|
baseDir = path36.join(body.projectId, ".halo", "agents");
|
|
15914
16128
|
} else {
|
|
15915
|
-
baseDir =
|
|
16129
|
+
baseDir = GLOBAL_AGENTS_DIR4;
|
|
15916
16130
|
}
|
|
15917
16131
|
const agentDir = path36.join(baseDir, id);
|
|
15918
16132
|
try {
|
|
@@ -15923,7 +16137,7 @@ function createAgentConfigRoutes() {
|
|
|
15923
16137
|
let conflictScope = null;
|
|
15924
16138
|
if (scope === "workspace") {
|
|
15925
16139
|
try {
|
|
15926
|
-
await fs31.access(path36.join(
|
|
16140
|
+
await fs31.access(path36.join(GLOBAL_AGENTS_DIR4, id));
|
|
15927
16141
|
conflictScope = "global";
|
|
15928
16142
|
} catch {
|
|
15929
16143
|
}
|
|
@@ -15952,7 +16166,7 @@ ${body.description ?? ""}
|
|
|
15952
16166
|
if (scope === "workspace" && projectId) {
|
|
15953
16167
|
agentDir = path36.join(projectId, ".halo", "agents", id);
|
|
15954
16168
|
} else {
|
|
15955
|
-
agentDir = path36.join(
|
|
16169
|
+
agentDir = path36.join(GLOBAL_AGENTS_DIR4, id);
|
|
15956
16170
|
}
|
|
15957
16171
|
const yamlPath = path36.join(agentDir, "agent.yaml");
|
|
15958
16172
|
try {
|
|
@@ -15975,7 +16189,7 @@ ${body.description ?? ""}
|
|
|
15975
16189
|
if (scope === "workspace" && body.projectId) {
|
|
15976
16190
|
agentDir = path36.join(body.projectId, ".halo", "agents", id);
|
|
15977
16191
|
} else {
|
|
15978
|
-
agentDir = path36.join(
|
|
16192
|
+
agentDir = path36.join(GLOBAL_AGENTS_DIR4, id);
|
|
15979
16193
|
}
|
|
15980
16194
|
const yamlPath = path36.join(agentDir, "agent.yaml");
|
|
15981
16195
|
try {
|
|
@@ -15995,7 +16209,7 @@ ${body.description ?? ""}
|
|
|
15995
16209
|
if (scope === "workspace" && projectId) {
|
|
15996
16210
|
agentDir = path36.join(projectId, ".halo", "agents", id);
|
|
15997
16211
|
} else {
|
|
15998
|
-
agentDir = path36.join(
|
|
16212
|
+
agentDir = path36.join(GLOBAL_AGENTS_DIR4, id);
|
|
15999
16213
|
}
|
|
16000
16214
|
try {
|
|
16001
16215
|
await fs31.access(agentDir);
|
|
@@ -16003,7 +16217,7 @@ ${body.description ?? ""}
|
|
|
16003
16217
|
return c.json({ error: "Agent not found" }, 404);
|
|
16004
16218
|
}
|
|
16005
16219
|
if (scope === "global") {
|
|
16006
|
-
const globalAgents = await scanAgentsDir(
|
|
16220
|
+
const globalAgents = await scanAgentsDir(GLOBAL_AGENTS_DIR4, "global");
|
|
16007
16221
|
if (globalAgents.length <= 1) {
|
|
16008
16222
|
return c.json({ error: "Cannot delete the last global agent. At least one global agent must remain." }, 400);
|
|
16009
16223
|
}
|
|
@@ -16083,9 +16297,9 @@ ${body.description ?? ""}
|
|
|
16083
16297
|
});
|
|
16084
16298
|
function getSessionsDir(agentId, _source, projectId) {
|
|
16085
16299
|
if (agentId.startsWith("__") && agentId.endsWith("__")) {
|
|
16086
|
-
return path36.join(
|
|
16300
|
+
return path36.join(homedir17(), ".halo", "global", "internal-sessions", agentId);
|
|
16087
16301
|
}
|
|
16088
|
-
const base = projectId ? path36.join(projectId, ".halo") : path36.join(
|
|
16302
|
+
const base = projectId ? path36.join(projectId, ".halo") : path36.join(homedir17(), ".halo");
|
|
16089
16303
|
return path36.join(base, "sessions", agentId);
|
|
16090
16304
|
}
|
|
16091
16305
|
app2.get("/agent-configs/:id/sessions", async (c) => {
|
|
@@ -16198,7 +16412,7 @@ ${body.description ?? ""}
|
|
|
16198
16412
|
});
|
|
16199
16413
|
return app2;
|
|
16200
16414
|
}
|
|
16201
|
-
var _cachedTools,
|
|
16415
|
+
var _cachedTools, GLOBAL_AGENTS_DIR4, FALLBACK_SCAFFOLD_PROVIDER, _yamlCache;
|
|
16202
16416
|
var init_agent_configs = __esm({
|
|
16203
16417
|
"packages/server/dist/routes/agent-configs.js"() {
|
|
16204
16418
|
"use strict";
|
|
@@ -16208,7 +16422,7 @@ var init_agent_configs = __esm({
|
|
|
16208
16422
|
init_config();
|
|
16209
16423
|
init_db();
|
|
16210
16424
|
_cachedTools = null;
|
|
16211
|
-
|
|
16425
|
+
GLOBAL_AGENTS_DIR4 = path36.join(homedir17(), ".halo", "global", "agents");
|
|
16212
16426
|
FALLBACK_SCAFFOLD_PROVIDER = "aws-bedrock-claude-invoke";
|
|
16213
16427
|
_yamlCache = /* @__PURE__ */ new Map();
|
|
16214
16428
|
}
|
|
@@ -16218,7 +16432,7 @@ var init_agent_configs = __esm({
|
|
|
16218
16432
|
import { Hono as Hono4 } from "hono";
|
|
16219
16433
|
import fs32 from "node:fs/promises";
|
|
16220
16434
|
import path37 from "node:path";
|
|
16221
|
-
import { homedir as
|
|
16435
|
+
import { homedir as homedir18 } from "node:os";
|
|
16222
16436
|
import YAML11 from "yaml";
|
|
16223
16437
|
function defaultSkillSettings() {
|
|
16224
16438
|
return {
|
|
@@ -16416,7 +16630,7 @@ var init_skills = __esm({
|
|
|
16416
16630
|
"use strict";
|
|
16417
16631
|
init_agent_loader();
|
|
16418
16632
|
init_db();
|
|
16419
|
-
GLOBAL_SETTINGS_PATH = path37.join(
|
|
16633
|
+
GLOBAL_SETTINGS_PATH = path37.join(homedir18(), ".halo", "secrets", "settings.yaml");
|
|
16420
16634
|
_skillMdCache = /* @__PURE__ */ new Map();
|
|
16421
16635
|
}
|
|
16422
16636
|
});
|
|
@@ -16425,7 +16639,7 @@ var init_skills = __esm({
|
|
|
16425
16639
|
import { Hono as Hono5 } from "hono";
|
|
16426
16640
|
import fs33 from "node:fs/promises";
|
|
16427
16641
|
import path38 from "node:path";
|
|
16428
|
-
import { homedir as
|
|
16642
|
+
import { homedir as homedir19 } from "node:os";
|
|
16429
16643
|
import YAML12 from "yaml";
|
|
16430
16644
|
async function readSettingsFile(filePath) {
|
|
16431
16645
|
try {
|
|
@@ -16437,7 +16651,11 @@ async function readSettingsFile(filePath) {
|
|
|
16437
16651
|
}
|
|
16438
16652
|
async function writeSettingsFile(filePath, data) {
|
|
16439
16653
|
await fs33.mkdir(path38.dirname(filePath), { recursive: true });
|
|
16440
|
-
await fs33.writeFile(filePath, YAML12.stringify(data, { lineWidth: 120 }), "utf-8");
|
|
16654
|
+
await fs33.writeFile(filePath, YAML12.stringify(data, { lineWidth: 120 }), { encoding: "utf-8", mode: 384 });
|
|
16655
|
+
try {
|
|
16656
|
+
await fs33.chmod(filePath, 384);
|
|
16657
|
+
} catch {
|
|
16658
|
+
}
|
|
16441
16659
|
}
|
|
16442
16660
|
function deepMerge2(base, override) {
|
|
16443
16661
|
const result = { ...base };
|
|
@@ -16546,8 +16764,8 @@ function detectOrphans(tree, declared) {
|
|
|
16546
16764
|
if (!block || typeof block !== "object")
|
|
16547
16765
|
continue;
|
|
16548
16766
|
for (const k of Object.keys(block)) {
|
|
16549
|
-
const
|
|
16550
|
-
if (!declaredMap.has(
|
|
16767
|
+
const path63 = `${namespaceId}.${kindWord}.${k}`;
|
|
16768
|
+
if (!declaredMap.has(path63)) {
|
|
16551
16769
|
out.push({ namespaceId, kind: kindWord === "params" ? "param" : "secret", key: k });
|
|
16552
16770
|
}
|
|
16553
16771
|
}
|
|
@@ -16673,16 +16891,16 @@ var init_settings = __esm({
|
|
|
16673
16891
|
"packages/server/dist/routes/settings.js"() {
|
|
16674
16892
|
"use strict";
|
|
16675
16893
|
init_settings_schema();
|
|
16676
|
-
GLOBAL_SETTINGS_PATH2 = path38.join(
|
|
16894
|
+
GLOBAL_SETTINGS_PATH2 = path38.join(homedir19(), ".halo", "secrets", "settings.yaml");
|
|
16677
16895
|
_changeListeners = [];
|
|
16678
16896
|
}
|
|
16679
16897
|
});
|
|
16680
16898
|
|
|
16681
16899
|
// packages/server/dist/paths.js
|
|
16682
16900
|
import path39 from "node:path";
|
|
16683
|
-
import { homedir as
|
|
16901
|
+
import { homedir as homedir20 } from "node:os";
|
|
16684
16902
|
function haloHome() {
|
|
16685
|
-
return path39.join(
|
|
16903
|
+
return path39.join(homedir20(), ".halo");
|
|
16686
16904
|
}
|
|
16687
16905
|
function globalDir() {
|
|
16688
16906
|
return path39.join(haloHome(), "global");
|
|
@@ -16876,7 +17094,7 @@ import { Hono as Hono6 } from "hono";
|
|
|
16876
17094
|
import { eq as eq7, desc as desc2, asc, inArray as inArray2, lt as lt3, and as and5, or, sql } from "drizzle-orm";
|
|
16877
17095
|
import fs35 from "node:fs/promises";
|
|
16878
17096
|
import path41 from "node:path";
|
|
16879
|
-
import { homedir as
|
|
17097
|
+
import { homedir as homedir21 } from "node:os";
|
|
16880
17098
|
function runDirOf(workspacePath, runId) {
|
|
16881
17099
|
return path41.join(workspacePath, ".halo", "evo", "runs", runId);
|
|
16882
17100
|
}
|
|
@@ -16955,7 +17173,7 @@ function createEvolutionRoutes() {
|
|
|
16955
17173
|
}
|
|
16956
17174
|
let wrapperLog = null;
|
|
16957
17175
|
try {
|
|
16958
|
-
wrapperLog = await fs35.readFile(path41.join(
|
|
17176
|
+
wrapperLog = await fs35.readFile(path41.join(homedir21(), ".halo", "global", "logs", "evo", `run-${id}.log`), "utf-8");
|
|
16959
17177
|
} catch {
|
|
16960
17178
|
}
|
|
16961
17179
|
let subCliLog = null;
|
|
@@ -17644,6 +17862,7 @@ var init_accounts2 = __esm({
|
|
|
17644
17862
|
});
|
|
17645
17863
|
|
|
17646
17864
|
// packages/server/dist/middleware/brute-force.js
|
|
17865
|
+
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
17647
17866
|
function bucket(name) {
|
|
17648
17867
|
let b = buckets.get(name);
|
|
17649
17868
|
if (!b) {
|
|
@@ -17653,9 +17872,17 @@ function bucket(name) {
|
|
|
17653
17872
|
return b;
|
|
17654
17873
|
}
|
|
17655
17874
|
function getClientIp(c) {
|
|
17656
|
-
|
|
17657
|
-
|
|
17658
|
-
|
|
17875
|
+
if (config.server.trustProxy) {
|
|
17876
|
+
const forwarded = c.req.header("x-forwarded-for");
|
|
17877
|
+
if (forwarded)
|
|
17878
|
+
return forwarded.split(",")[0].trim();
|
|
17879
|
+
}
|
|
17880
|
+
try {
|
|
17881
|
+
const addr = getConnInfo(c).remote.address;
|
|
17882
|
+
if (addr)
|
|
17883
|
+
return addr;
|
|
17884
|
+
} catch {
|
|
17885
|
+
}
|
|
17659
17886
|
return "unknown";
|
|
17660
17887
|
}
|
|
17661
17888
|
function isLockedOut(bucketName, ip) {
|
|
@@ -17687,6 +17914,7 @@ var MAX_ATTEMPTS, LOCKOUT_MS, buckets;
|
|
|
17687
17914
|
var init_brute_force = __esm({
|
|
17688
17915
|
"packages/server/dist/middleware/brute-force.js"() {
|
|
17689
17916
|
"use strict";
|
|
17917
|
+
init_config();
|
|
17690
17918
|
MAX_ATTEMPTS = 5;
|
|
17691
17919
|
LOCKOUT_MS = 15 * 60 * 1e3;
|
|
17692
17920
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -17912,6 +18140,29 @@ function snapshotWorkspaceReadonly(wsPath, label) {
|
|
|
17912
18140
|
skills: scanSkills2(path43.join(wsPath, ".halo", "skills"))
|
|
17913
18141
|
};
|
|
17914
18142
|
}
|
|
18143
|
+
function readonlySessionCounts(wsPath, limit) {
|
|
18144
|
+
const counts = { running: 0, idle: 0, stopped: 0, total: 0 };
|
|
18145
|
+
const reader = roReader(wsPath);
|
|
18146
|
+
if (!reader)
|
|
18147
|
+
return counts;
|
|
18148
|
+
try {
|
|
18149
|
+
const rows = reader.listPage.all(limit);
|
|
18150
|
+
const liveParents = new Set(reader.liveParents.all().map((r) => r.parent_id));
|
|
18151
|
+
counts.total = rows.length;
|
|
18152
|
+
for (const r of rows) {
|
|
18153
|
+
if (r.stopped_at)
|
|
18154
|
+
counts.stopped++;
|
|
18155
|
+
else if (liveParents.has(r.id))
|
|
18156
|
+
counts.running++;
|
|
18157
|
+
else
|
|
18158
|
+
counts.idle++;
|
|
18159
|
+
}
|
|
18160
|
+
} catch (err) {
|
|
18161
|
+
console.log(`[Show] read-only query failed ${wsPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
18162
|
+
dropRoReader(wsPath);
|
|
18163
|
+
}
|
|
18164
|
+
return counts;
|
|
18165
|
+
}
|
|
17915
18166
|
function trimSessionMessages(messages2) {
|
|
17916
18167
|
return messages2.slice(-SESSION_LOG_MAX_MSGS).map((m) => ({
|
|
17917
18168
|
id: m.id,
|
|
@@ -18141,9 +18392,18 @@ function createMetricsRoutes(registry2) {
|
|
|
18141
18392
|
let workspaces = 0;
|
|
18142
18393
|
for (const wsPath of discoverWorkspaces2(registry2)) {
|
|
18143
18394
|
try {
|
|
18144
|
-
const sm = registry2.
|
|
18145
|
-
const { sessions: sessions2 } = sm.listSessions({ includeArchived: false, limit: SESSIONS_PER_WS2 });
|
|
18395
|
+
const sm = registry2.peek(wsPath);
|
|
18146
18396
|
workspaces++;
|
|
18397
|
+
if (!sm) {
|
|
18398
|
+
const counts = readonlySessionCounts(wsPath, SESSIONS_PER_WS2);
|
|
18399
|
+
running += counts.running;
|
|
18400
|
+
idle += counts.idle;
|
|
18401
|
+
stopped += counts.stopped;
|
|
18402
|
+
total += counts.total;
|
|
18403
|
+
continue;
|
|
18404
|
+
}
|
|
18405
|
+
dropRoReader(wsPath);
|
|
18406
|
+
const { sessions: sessions2 } = sm.listSessions({ includeArchived: false, limit: SESSIONS_PER_WS2 });
|
|
18147
18407
|
for (const r of sessions2) {
|
|
18148
18408
|
total++;
|
|
18149
18409
|
if (r.status === "running")
|
|
@@ -18186,6 +18446,7 @@ var init_metrics = __esm({
|
|
|
18186
18446
|
init_channel_db();
|
|
18187
18447
|
init_accounts2();
|
|
18188
18448
|
init_brute_force();
|
|
18449
|
+
init_show();
|
|
18189
18450
|
TOKEN_BUCKET2 = "web-token";
|
|
18190
18451
|
SESSIONS_PER_WS2 = 500;
|
|
18191
18452
|
}
|
|
@@ -18221,16 +18482,300 @@ var init_commands3 = __esm({
|
|
|
18221
18482
|
}
|
|
18222
18483
|
});
|
|
18223
18484
|
|
|
18224
|
-
// packages/server/dist/
|
|
18225
|
-
|
|
18485
|
+
// packages/server/dist/sessions/session-types.js
|
|
18486
|
+
function inferMessageType(msg) {
|
|
18487
|
+
if (msg.type)
|
|
18488
|
+
return msg.type;
|
|
18489
|
+
if (msg.role === "user")
|
|
18490
|
+
return "user";
|
|
18491
|
+
if (msg.role === "assistant")
|
|
18492
|
+
return "assistant";
|
|
18493
|
+
if (msg.toolName)
|
|
18494
|
+
return "tool_call";
|
|
18495
|
+
if (msg.toolOutput !== void 0 && !msg.toolName)
|
|
18496
|
+
return "tool_result";
|
|
18497
|
+
if (msg.usage)
|
|
18498
|
+
return "usage";
|
|
18499
|
+
if (msg.systemPrompt)
|
|
18500
|
+
return "context";
|
|
18501
|
+
return "notification";
|
|
18502
|
+
}
|
|
18503
|
+
var init_session_types = __esm({
|
|
18504
|
+
"packages/server/dist/sessions/session-types.js"() {
|
|
18505
|
+
"use strict";
|
|
18506
|
+
}
|
|
18507
|
+
});
|
|
18508
|
+
|
|
18509
|
+
// packages/server/dist/routes/agentcore.js
|
|
18510
|
+
import { Hono as Hono11 } from "hono";
|
|
18226
18511
|
import path44 from "node:path";
|
|
18512
|
+
import fs40 from "node:fs";
|
|
18513
|
+
function haloSessionId(runtimeSessionId) {
|
|
18514
|
+
return `agentcore_${runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80)}`;
|
|
18515
|
+
}
|
|
18516
|
+
function userWorkspace(base, runtimeSessionId) {
|
|
18517
|
+
const dir = path44.join(base, "users", runtimeSessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80));
|
|
18518
|
+
fs40.mkdirSync(dir, { recursive: true });
|
|
18519
|
+
return dir;
|
|
18520
|
+
}
|
|
18521
|
+
async function ensureSession(sm, workspace, sessionId) {
|
|
18522
|
+
if (sm.getSessionById(sessionId))
|
|
18523
|
+
return;
|
|
18524
|
+
const agentId = await resolveDefaultAgentId(sm, workspace);
|
|
18525
|
+
await sm.createSession(agentId, null, "AgentCore Runtime", void 0, sessionId);
|
|
18526
|
+
}
|
|
18527
|
+
function runTurnCollect(sm, sessionId, prompt) {
|
|
18528
|
+
return new Promise((resolve2, reject) => {
|
|
18529
|
+
let text5 = "";
|
|
18530
|
+
const unsubscribe = sm.registerEventListener(sessionId, (event) => {
|
|
18531
|
+
if (event.taskId)
|
|
18532
|
+
return;
|
|
18533
|
+
if (event.type === "stream") {
|
|
18534
|
+
text5 += event.text ?? "";
|
|
18535
|
+
} else if (event.type === "complete" && !event.batchBoundary) {
|
|
18536
|
+
unsubscribe();
|
|
18537
|
+
resolve2(text5);
|
|
18538
|
+
} else if (event.type === "error") {
|
|
18539
|
+
unsubscribe();
|
|
18540
|
+
resolve2(text5 ? `${text5}
|
|
18541
|
+
|
|
18542
|
+
[error] ${event.error}` : `[error] ${event.error ?? "unknown error"}`);
|
|
18543
|
+
}
|
|
18544
|
+
});
|
|
18545
|
+
sm.appendUserMessage(sessionId, prompt);
|
|
18546
|
+
sm.sendUserMessage(sessionId, `[channel: agentcore]
|
|
18547
|
+
|
|
18548
|
+
${prompt}`).then((result) => {
|
|
18549
|
+
if (result === "queued") {
|
|
18550
|
+
unsubscribe();
|
|
18551
|
+
resolve2("(agent busy \u2014 message queued and will be processed with the current turn)");
|
|
18552
|
+
}
|
|
18553
|
+
}).catch((err) => {
|
|
18554
|
+
unsubscribe();
|
|
18555
|
+
reject(err);
|
|
18556
|
+
});
|
|
18557
|
+
});
|
|
18558
|
+
}
|
|
18559
|
+
function createAgentCoreRoutes(deps) {
|
|
18560
|
+
const { registry: registry2, workspace } = deps;
|
|
18561
|
+
const app2 = new Hono11();
|
|
18562
|
+
app2.get("/ping", (c) => {
|
|
18563
|
+
const busy = registry2.list().some(({ sm }) => sm.hasRunningSessions());
|
|
18564
|
+
return c.json({ status: busy ? "HealthyBusy" : "Healthy" });
|
|
18565
|
+
});
|
|
18566
|
+
app2.post("/invocations", async (c) => {
|
|
18567
|
+
const body = await c.req.json().catch(() => ({}));
|
|
18568
|
+
const prompt = typeof body.input?.prompt === "string" ? body.input.prompt.trim() : "";
|
|
18569
|
+
if (!prompt)
|
|
18570
|
+
return c.json({ error: "input.prompt required" }, 400);
|
|
18571
|
+
const runtimeSessionId = c.req.header(SESSION_HEADER) || `inv-${Date.now().toString(36)}`;
|
|
18572
|
+
const sessionId = haloSessionId(runtimeSessionId);
|
|
18573
|
+
const userWs = userWorkspace(workspace, runtimeSessionId);
|
|
18574
|
+
const sm = registry2.getOrCreate(userWs);
|
|
18575
|
+
await ensureSession(sm, userWs, sessionId);
|
|
18576
|
+
const text5 = await runTurnCollect(sm, sessionId, prompt);
|
|
18577
|
+
return c.json({ output: { message: { role: "assistant", content: [{ text: text5 }] } } });
|
|
18578
|
+
});
|
|
18579
|
+
return app2;
|
|
18580
|
+
}
|
|
18581
|
+
function buildHistory(sm, sessionId) {
|
|
18582
|
+
const state = sm.getUIState(sessionId);
|
|
18583
|
+
if (!state)
|
|
18584
|
+
return { messages: [], running: false };
|
|
18585
|
+
const messages2 = [];
|
|
18586
|
+
for (const m of createSaveSnapshot(state)) {
|
|
18587
|
+
if (m.taskId)
|
|
18588
|
+
continue;
|
|
18589
|
+
const type = inferMessageType(m);
|
|
18590
|
+
if (type !== "user" && type !== "assistant")
|
|
18591
|
+
continue;
|
|
18592
|
+
const blocks = [];
|
|
18593
|
+
for (const b of m.contentBlocks ?? []) {
|
|
18594
|
+
if (b.type === "thinking") {
|
|
18595
|
+
blocks.push({ type: "thinking", text: b.text });
|
|
18596
|
+
} else if (b.type === "tool_call") {
|
|
18597
|
+
blocks.push({ type: "tool_call", toolCall: { name: b.toolCall.name, input: b.toolCall.input, output: b.toolCall.output?.slice(0, 500) } });
|
|
18598
|
+
}
|
|
18599
|
+
}
|
|
18600
|
+
messages2.push({
|
|
18601
|
+
role: m.role,
|
|
18602
|
+
content: m.content,
|
|
18603
|
+
ts: m.timestamp,
|
|
18604
|
+
...blocks.length > 0 ? { contentBlocks: blocks } : {}
|
|
18605
|
+
});
|
|
18606
|
+
}
|
|
18607
|
+
return { messages: messages2, running: sm.isSessionRunning(sessionId) };
|
|
18608
|
+
}
|
|
18609
|
+
function setupAgentCoreWebSocket(deps) {
|
|
18610
|
+
const { wss: wss2, registry: registry2, workspace } = deps;
|
|
18611
|
+
wss2.on("connection", (ws, req) => {
|
|
18612
|
+
const url = new URL(req.url ?? "/ws", "http://localhost");
|
|
18613
|
+
const runtimeSessionId = req.headers[SESSION_HEADER] || url.searchParams.get("sessionId") || `ws-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
18614
|
+
let sessionId = haloSessionId(runtimeSessionId);
|
|
18615
|
+
const userWs = userWorkspace(workspace, runtimeSessionId);
|
|
18616
|
+
const sm = registry2.getOrCreate(userWs);
|
|
18617
|
+
const send = (obj) => {
|
|
18618
|
+
if (ws.readyState !== ws.OPEN)
|
|
18619
|
+
return;
|
|
18620
|
+
try {
|
|
18621
|
+
ws.send(JSON.stringify(obj));
|
|
18622
|
+
} catch {
|
|
18623
|
+
}
|
|
18624
|
+
};
|
|
18625
|
+
let unsubscribe = null;
|
|
18626
|
+
const attachListener = () => {
|
|
18627
|
+
unsubscribe ??= sm.registerEventListener(sessionId, (event) => {
|
|
18628
|
+
if (event.taskId)
|
|
18629
|
+
return;
|
|
18630
|
+
switch (event.type) {
|
|
18631
|
+
case "stream":
|
|
18632
|
+
if (event.text)
|
|
18633
|
+
send({ type: "stream", text: event.text });
|
|
18634
|
+
break;
|
|
18635
|
+
case "thinking":
|
|
18636
|
+
send({ type: "thinking", text: event.text ?? "" });
|
|
18637
|
+
break;
|
|
18638
|
+
case "tool_call":
|
|
18639
|
+
send({ type: "tool_call", toolName: event.toolName, toolInput: event.toolInput });
|
|
18640
|
+
break;
|
|
18641
|
+
case "tool_result":
|
|
18642
|
+
send({ type: "tool_result", toolName: event.toolName, result: event.toolResult?.slice(0, 500) });
|
|
18643
|
+
break;
|
|
18644
|
+
case "complete":
|
|
18645
|
+
if (!event.batchBoundary)
|
|
18646
|
+
send({ type: "complete" });
|
|
18647
|
+
break;
|
|
18648
|
+
case "error":
|
|
18649
|
+
send({ type: "error", error: event.error ?? "unknown error" });
|
|
18650
|
+
break;
|
|
18651
|
+
}
|
|
18652
|
+
});
|
|
18653
|
+
};
|
|
18654
|
+
let ready = null;
|
|
18655
|
+
const ensureOnce = () => {
|
|
18656
|
+
ready ??= ensureSession(sm, userWs, sessionId).then(() => attachListener());
|
|
18657
|
+
return ready;
|
|
18658
|
+
};
|
|
18659
|
+
ws.on("message", (raw) => {
|
|
18660
|
+
let msg;
|
|
18661
|
+
try {
|
|
18662
|
+
msg = JSON.parse(String(raw));
|
|
18663
|
+
} catch {
|
|
18664
|
+
send({ type: "error", error: "invalid JSON" });
|
|
18665
|
+
return;
|
|
18666
|
+
}
|
|
18667
|
+
if (msg.type === "init" || typeof msg.inputText === "string" && msg.inputText.trim() === "/init") {
|
|
18668
|
+
if (sm.getSessionById(sessionId)) {
|
|
18669
|
+
attachListener();
|
|
18670
|
+
send({ type: "history", ...buildHistory(sm, sessionId) });
|
|
18671
|
+
} else {
|
|
18672
|
+
send({ type: "history", messages: [], running: false });
|
|
18673
|
+
}
|
|
18674
|
+
return;
|
|
18675
|
+
}
|
|
18676
|
+
if (msg.type === "stop") {
|
|
18677
|
+
if (!sm.getSessionById(sessionId))
|
|
18678
|
+
return;
|
|
18679
|
+
sm.stopSession(sessionId).catch((err) => {
|
|
18680
|
+
send({ type: "error", error: err instanceof Error ? err.message : String(err) });
|
|
18681
|
+
});
|
|
18682
|
+
return;
|
|
18683
|
+
}
|
|
18684
|
+
const text5 = typeof msg.inputText === "string" ? msg.inputText.trim() : "";
|
|
18685
|
+
if (!text5) {
|
|
18686
|
+
return;
|
|
18687
|
+
}
|
|
18688
|
+
if (text5.startsWith("/")) {
|
|
18689
|
+
void (async () => {
|
|
18690
|
+
try {
|
|
18691
|
+
await ensureOnce();
|
|
18692
|
+
const spaceIdx = text5.indexOf(" ");
|
|
18693
|
+
const command = spaceIdx === -1 ? text5 : text5.slice(0, spaceIdx);
|
|
18694
|
+
const arg = spaceIdx === -1 ? "" : text5.slice(spaceIdx + 1).trim();
|
|
18695
|
+
const ctx = {
|
|
18696
|
+
sm,
|
|
18697
|
+
userId: runtimeSessionId,
|
|
18698
|
+
sessionPrefix: "agentcore_",
|
|
18699
|
+
accessLevel: "full",
|
|
18700
|
+
channelLabel: "AgentCore Runtime",
|
|
18701
|
+
activeOverrides,
|
|
18702
|
+
workspacePath: userWs,
|
|
18703
|
+
lang: "en"
|
|
18704
|
+
};
|
|
18705
|
+
const cmdResult = await dispatchCommand(ctx, command, arg, { channelName: "agentcore" });
|
|
18706
|
+
if (cmdResult) {
|
|
18707
|
+
send({ type: "stream", text: cmdResult.text });
|
|
18708
|
+
if (cmdResult.switchTo) {
|
|
18709
|
+
unsubscribe?.();
|
|
18710
|
+
unsubscribe = null;
|
|
18711
|
+
sessionId = cmdResult.switchTo;
|
|
18712
|
+
attachListener();
|
|
18713
|
+
send({ type: "switch" });
|
|
18714
|
+
send({ type: "history", ...buildHistory(sm, sessionId) });
|
|
18715
|
+
}
|
|
18716
|
+
if (cmdResult.startedTurn && cmdResult.sessionId) {
|
|
18717
|
+
if (cmdResult.sessionId !== sessionId) {
|
|
18718
|
+
unsubscribe?.();
|
|
18719
|
+
unsubscribe = null;
|
|
18720
|
+
sessionId = cmdResult.sessionId;
|
|
18721
|
+
attachListener();
|
|
18722
|
+
}
|
|
18723
|
+
} else {
|
|
18724
|
+
send({ type: "complete" });
|
|
18725
|
+
}
|
|
18726
|
+
} else {
|
|
18727
|
+
send({ type: "stream", text: `Unknown command: ${command}` });
|
|
18728
|
+
send({ type: "complete" });
|
|
18729
|
+
}
|
|
18730
|
+
} catch (err) {
|
|
18731
|
+
send({ type: "error", error: err instanceof Error ? err.message : String(err) });
|
|
18732
|
+
}
|
|
18733
|
+
})();
|
|
18734
|
+
return;
|
|
18735
|
+
}
|
|
18736
|
+
void (async () => {
|
|
18737
|
+
try {
|
|
18738
|
+
await ensureOnce();
|
|
18739
|
+
sm.appendUserMessage(sessionId, text5);
|
|
18740
|
+
const result = await sm.sendUserMessage(sessionId, `[channel: agentcore]
|
|
18741
|
+
|
|
18742
|
+
${text5}`);
|
|
18743
|
+
if (result === "queued")
|
|
18744
|
+
send({ type: "queued" });
|
|
18745
|
+
} catch (err) {
|
|
18746
|
+
send({ type: "error", error: err instanceof Error ? err.message : String(err) });
|
|
18747
|
+
}
|
|
18748
|
+
})();
|
|
18749
|
+
});
|
|
18750
|
+
ws.on("close", () => {
|
|
18751
|
+
unsubscribe?.();
|
|
18752
|
+
unsubscribe = null;
|
|
18753
|
+
});
|
|
18754
|
+
console.log(`[AgentCore] ws connected \u2014 session ${sessionId}`);
|
|
18755
|
+
});
|
|
18756
|
+
}
|
|
18757
|
+
var SESSION_HEADER, activeOverrides;
|
|
18758
|
+
var init_agentcore = __esm({
|
|
18759
|
+
"packages/server/dist/routes/agentcore.js"() {
|
|
18760
|
+
"use strict";
|
|
18761
|
+
init_commands2();
|
|
18762
|
+
init_ui_log_builder();
|
|
18763
|
+
init_session_types();
|
|
18764
|
+
SESSION_HEADER = "x-amzn-bedrock-agentcore-runtime-session-id";
|
|
18765
|
+
activeOverrides = /* @__PURE__ */ new Map();
|
|
18766
|
+
}
|
|
18767
|
+
});
|
|
18768
|
+
|
|
18769
|
+
// packages/server/dist/ws/file-watcher.js
|
|
18770
|
+
import fs41 from "node:fs";
|
|
18771
|
+
import path45 from "node:path";
|
|
18227
18772
|
function isUnwatchablePath(absPath) {
|
|
18228
|
-
const norm =
|
|
18229
|
-
if (norm === "/" || norm ===
|
|
18773
|
+
const norm = path45.resolve(absPath);
|
|
18774
|
+
if (norm === "/" || norm === path45.parse(norm).root)
|
|
18230
18775
|
return "filesystem root";
|
|
18231
18776
|
const banned = ["/System", "/Library", "/private", "/usr", "/bin", "/sbin", "/etc"];
|
|
18232
18777
|
for (const b of banned) {
|
|
18233
|
-
if (norm === b || norm.startsWith(b +
|
|
18778
|
+
if (norm === b || norm.startsWith(b + path45.sep))
|
|
18234
18779
|
return `system path ${b}`;
|
|
18235
18780
|
}
|
|
18236
18781
|
return null;
|
|
@@ -18345,10 +18890,10 @@ var init_file_watcher = __esm({
|
|
|
18345
18890
|
const root = this.workspaceRoot;
|
|
18346
18891
|
if (!root)
|
|
18347
18892
|
return;
|
|
18348
|
-
const raw =
|
|
18893
|
+
const raw = path45.relative(root, absPath);
|
|
18349
18894
|
if (!raw || raw.startsWith(".."))
|
|
18350
18895
|
return;
|
|
18351
|
-
const rel = raw.split(
|
|
18896
|
+
const rel = raw.split(path45.sep).join("/");
|
|
18352
18897
|
let action;
|
|
18353
18898
|
if (type === "delete") {
|
|
18354
18899
|
action = "unlink";
|
|
@@ -18357,7 +18902,7 @@ var init_file_watcher = __esm({
|
|
|
18357
18902
|
} else {
|
|
18358
18903
|
let isDir = false;
|
|
18359
18904
|
try {
|
|
18360
|
-
isDir =
|
|
18905
|
+
isDir = fs41.statSync(absPath).isDirectory();
|
|
18361
18906
|
} catch {
|
|
18362
18907
|
}
|
|
18363
18908
|
action = isDir ? "addDir" : "add";
|
|
@@ -18427,8 +18972,8 @@ var init_file_watcher = __esm({
|
|
|
18427
18972
|
});
|
|
18428
18973
|
|
|
18429
18974
|
// packages/server/dist/ws/git-dir-watcher.js
|
|
18430
|
-
import
|
|
18431
|
-
import
|
|
18975
|
+
import fs42 from "node:fs";
|
|
18976
|
+
import path46 from "node:path";
|
|
18432
18977
|
var GitDirWatcher;
|
|
18433
18978
|
var init_git_dir_watcher = __esm({
|
|
18434
18979
|
"packages/server/dist/ws/git-dir-watcher.js"() {
|
|
@@ -18452,8 +18997,8 @@ var init_git_dir_watcher = __esm({
|
|
|
18452
18997
|
return;
|
|
18453
18998
|
this.stop();
|
|
18454
18999
|
this.workspaceRoot = workspaceRoot;
|
|
18455
|
-
const gitDir =
|
|
18456
|
-
if (
|
|
19000
|
+
const gitDir = path46.join(workspaceRoot, ".git");
|
|
19001
|
+
if (fs42.existsSync(gitDir)) {
|
|
18457
19002
|
this.watchGitDir(gitDir);
|
|
18458
19003
|
} else {
|
|
18459
19004
|
this.watchParentForGit(workspaceRoot);
|
|
@@ -18462,7 +19007,7 @@ var init_git_dir_watcher = __esm({
|
|
|
18462
19007
|
/** Watch the `.git` dir for index/HEAD changes (steady state). */
|
|
18463
19008
|
watchGitDir(gitDir) {
|
|
18464
19009
|
try {
|
|
18465
|
-
this.watcher =
|
|
19010
|
+
this.watcher = fs42.watch(gitDir, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
18466
19011
|
this.handleGitEvent(filename);
|
|
18467
19012
|
});
|
|
18468
19013
|
this.mode = "git";
|
|
@@ -18476,7 +19021,7 @@ var init_git_dir_watcher = __esm({
|
|
|
18476
19021
|
* Exists solely during the non-repo window; upgraded away on first `.git`. */
|
|
18477
19022
|
watchParentForGit(workspaceRoot) {
|
|
18478
19023
|
try {
|
|
18479
|
-
this.watcher =
|
|
19024
|
+
this.watcher = fs42.watch(workspaceRoot, { persistent: true, recursive: false }, (_eventType, filename) => {
|
|
18480
19025
|
this.handleParentEvent(filename);
|
|
18481
19026
|
});
|
|
18482
19027
|
this.mode = "parent";
|
|
@@ -18507,8 +19052,8 @@ var init_git_dir_watcher = __esm({
|
|
|
18507
19052
|
const root = this.workspaceRoot;
|
|
18508
19053
|
if (!root)
|
|
18509
19054
|
return;
|
|
18510
|
-
const gitDir =
|
|
18511
|
-
if (!
|
|
19055
|
+
const gitDir = path46.join(root, ".git");
|
|
19056
|
+
if (!fs42.existsSync(gitDir))
|
|
18512
19057
|
return;
|
|
18513
19058
|
this.closeWatcher();
|
|
18514
19059
|
this.watchGitDir(gitDir);
|
|
@@ -18646,7 +19191,7 @@ var init_event_processor = __esm({
|
|
|
18646
19191
|
});
|
|
18647
19192
|
|
|
18648
19193
|
// packages/server/dist/ws/terminal-manager.js
|
|
18649
|
-
import { homedir as
|
|
19194
|
+
import { homedir as homedir22 } from "node:os";
|
|
18650
19195
|
import { basename } from "node:path";
|
|
18651
19196
|
import { createRequire } from "node:module";
|
|
18652
19197
|
function getPty() {
|
|
@@ -18702,7 +19247,7 @@ var init_terminal_manager = __esm({
|
|
|
18702
19247
|
clearTimeout(existing.graceTimer);
|
|
18703
19248
|
terminals.delete(terminalId);
|
|
18704
19249
|
}
|
|
18705
|
-
const cwd = opts.cwd === "~" || !opts.cwd ?
|
|
19250
|
+
const cwd = opts.cwd === "~" || !opts.cwd ? homedir22() : opts.cwd;
|
|
18706
19251
|
const cols = opts.cols ?? 80;
|
|
18707
19252
|
const rows = opts.rows ?? 24;
|
|
18708
19253
|
const shell = process.env.SHELL ?? (process.platform === "win32" ? process.env.ComSpec || "powershell.exe" : "/bin/bash");
|
|
@@ -18810,12 +19355,12 @@ var init_terminal_manager = __esm({
|
|
|
18810
19355
|
});
|
|
18811
19356
|
|
|
18812
19357
|
// packages/server/dist/ws/handler.js
|
|
18813
|
-
import
|
|
19358
|
+
import path47 from "node:path";
|
|
18814
19359
|
function setupWebSocketHandler(deps) {
|
|
18815
19360
|
const { wss: wss2, registry: registry2 } = deps;
|
|
18816
19361
|
const clients = /* @__PURE__ */ new Set();
|
|
18817
19362
|
function resolveProjectPath(projectId) {
|
|
18818
|
-
if (
|
|
19363
|
+
if (path47.isAbsolute(projectId))
|
|
18819
19364
|
return projectId;
|
|
18820
19365
|
return null;
|
|
18821
19366
|
}
|
|
@@ -19337,11 +19882,11 @@ var init_handler = __esm({
|
|
|
19337
19882
|
import Database5 from "better-sqlite3";
|
|
19338
19883
|
import { drizzle as drizzle4 } from "drizzle-orm/better-sqlite3";
|
|
19339
19884
|
import { sqliteTable as sqliteTable4, text as text4, integer as integer4 } from "drizzle-orm/sqlite-core";
|
|
19340
|
-
import
|
|
19341
|
-
import
|
|
19885
|
+
import path48 from "node:path";
|
|
19886
|
+
import fs43 from "node:fs";
|
|
19342
19887
|
function createCronDb(globalDir2) {
|
|
19343
|
-
|
|
19344
|
-
const dbPath =
|
|
19888
|
+
fs43.mkdirSync(globalDir2, { recursive: true });
|
|
19889
|
+
const dbPath = path48.join(globalDir2, "cron.db");
|
|
19345
19890
|
const sqlite = new Database5(dbPath);
|
|
19346
19891
|
sqlite.pragma("journal_mode = WAL");
|
|
19347
19892
|
sqlite.exec(CREATE_SQL3);
|
|
@@ -19510,11 +20055,24 @@ var init_dispatcher = __esm({
|
|
|
19510
20055
|
}
|
|
19511
20056
|
});
|
|
19512
20057
|
|
|
20058
|
+
// packages/server/dist/child-env.js
|
|
20059
|
+
function cleanChildEnv() {
|
|
20060
|
+
const env = { ...process.env };
|
|
20061
|
+
delete env.HALO_PASSWORD;
|
|
20062
|
+
delete env.HALO_JWT_SECRET;
|
|
20063
|
+
return env;
|
|
20064
|
+
}
|
|
20065
|
+
var init_child_env = __esm({
|
|
20066
|
+
"packages/server/dist/child-env.js"() {
|
|
20067
|
+
"use strict";
|
|
20068
|
+
}
|
|
20069
|
+
});
|
|
20070
|
+
|
|
19513
20071
|
// packages/server/dist/cron/runner.js
|
|
19514
20072
|
import { spawn as spawn2 } from "node:child_process";
|
|
19515
|
-
import
|
|
19516
|
-
import
|
|
19517
|
-
import { homedir as
|
|
20073
|
+
import fs44 from "node:fs";
|
|
20074
|
+
import path49 from "node:path";
|
|
20075
|
+
import { homedir as homedir23 } from "node:os";
|
|
19518
20076
|
import { Cron } from "croner";
|
|
19519
20077
|
import { eq as eq10, lt as lt4 } from "drizzle-orm";
|
|
19520
20078
|
function resolveHaloCli() {
|
|
@@ -19523,16 +20081,16 @@ function resolveHaloCli() {
|
|
|
19523
20081
|
return process.platform === "win32" ? "halo.cmd" : "halo";
|
|
19524
20082
|
}
|
|
19525
20083
|
function logsDir() {
|
|
19526
|
-
return
|
|
20084
|
+
return path49.join(homedir23(), ".halo", "global", "logs", "cron");
|
|
19527
20085
|
}
|
|
19528
20086
|
function logPathFor(runId) {
|
|
19529
|
-
return
|
|
20087
|
+
return path49.join(logsDir(), `${runId}.log`);
|
|
19530
20088
|
}
|
|
19531
20089
|
function newRunId() {
|
|
19532
20090
|
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${Math.random().toString(36).slice(2, 8)}`;
|
|
19533
20091
|
}
|
|
19534
20092
|
function startCronDaemon() {
|
|
19535
|
-
|
|
20093
|
+
fs44.mkdirSync(logsDir(), { recursive: true });
|
|
19536
20094
|
reloadAll();
|
|
19537
20095
|
setInterval(pruneOldLogs, DAY_MS2);
|
|
19538
20096
|
setInterval(reconcileFromDb, 1e4);
|
|
@@ -19677,7 +20235,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
19677
20235
|
const runId = newRunId();
|
|
19678
20236
|
const startedAt = Date.now();
|
|
19679
20237
|
const logPath = logPathFor(runId);
|
|
19680
|
-
|
|
20238
|
+
fs44.mkdirSync(logsDir(), { recursive: true });
|
|
19681
20239
|
if (_inflight.has(jobId)) {
|
|
19682
20240
|
db.insert(cronRuns).values({
|
|
19683
20241
|
id: runId,
|
|
@@ -19712,7 +20270,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
19712
20270
|
}).run();
|
|
19713
20271
|
broadcast({ type: "cron:run_changed", jobId, runId, status: "running" });
|
|
19714
20272
|
const targets = parseTargets(job.targets);
|
|
19715
|
-
if (!
|
|
20273
|
+
if (!fs44.existsSync(job.workspacePath)) {
|
|
19716
20274
|
finalize(runId, jobId, {
|
|
19717
20275
|
status: "failed",
|
|
19718
20276
|
output: null,
|
|
@@ -19726,7 +20284,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
19726
20284
|
const sessionId = `cron-${job.id}`;
|
|
19727
20285
|
const args = ["cli", "-a", job.agentId, "-s", sessionId, "-w", job.workspacePath];
|
|
19728
20286
|
const [spawnBin, spawnArgs] = process.platform === "win32" && cli.endsWith(".cmd") ? ["cmd.exe", ["/c", cli, ...args]] : [cli, args];
|
|
19729
|
-
const teeStream =
|
|
20287
|
+
const teeStream = fs44.createWriteStream(logPath, { flags: "a" });
|
|
19730
20288
|
teeStream.write(`=== ${(/* @__PURE__ */ new Date()).toISOString()} cron run ${runId} job=${jobId} trigger=${triggerKind} ===
|
|
19731
20289
|
`);
|
|
19732
20290
|
teeStream.write(`agent=${job.agentId} workspace=${job.workspacePath} schedule=${job.schedule}
|
|
@@ -19739,7 +20297,7 @@ async function runJob(jobId, triggerKind) {
|
|
|
19739
20297
|
const result = await new Promise((resolve2) => {
|
|
19740
20298
|
const child = spawn2(spawnBin, spawnArgs, {
|
|
19741
20299
|
stdio: ["pipe", "pipe", "pipe"],
|
|
19742
|
-
env:
|
|
20300
|
+
env: cleanChildEnv(),
|
|
19743
20301
|
// Suppress the console window the `cmd.exe /c` wrapper would otherwise
|
|
19744
20302
|
// pop up on Windows (CREATE_NO_WINDOW). No-op on macOS/Linux.
|
|
19745
20303
|
windowsHide: true
|
|
@@ -19879,15 +20437,15 @@ function pruneOldRuns(jobId) {
|
|
|
19879
20437
|
}
|
|
19880
20438
|
function pruneOldLogs() {
|
|
19881
20439
|
const dir = logsDir();
|
|
19882
|
-
if (!
|
|
20440
|
+
if (!fs44.existsSync(dir))
|
|
19883
20441
|
return;
|
|
19884
20442
|
const cutoff = Date.now() - LOG_RETENTION_DAYS * DAY_MS2;
|
|
19885
|
-
for (const name of
|
|
19886
|
-
const p =
|
|
20443
|
+
for (const name of fs44.readdirSync(dir)) {
|
|
20444
|
+
const p = path49.join(dir, name);
|
|
19887
20445
|
try {
|
|
19888
|
-
const stat =
|
|
20446
|
+
const stat = fs44.statSync(p);
|
|
19889
20447
|
if (stat.mtimeMs < cutoff)
|
|
19890
|
-
|
|
20448
|
+
fs44.rmSync(p, { force: true });
|
|
19891
20449
|
} catch {
|
|
19892
20450
|
}
|
|
19893
20451
|
}
|
|
@@ -19902,6 +20460,7 @@ var init_runner = __esm({
|
|
|
19902
20460
|
init_cron_db();
|
|
19903
20461
|
init_dispatcher();
|
|
19904
20462
|
init_broadcast();
|
|
20463
|
+
init_child_env();
|
|
19905
20464
|
DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
19906
20465
|
CLI_TIMEOUT_SEC = 3600;
|
|
19907
20466
|
RUNS_PER_JOB_KEEP = 100;
|
|
@@ -19914,9 +20473,9 @@ var init_runner = __esm({
|
|
|
19914
20473
|
});
|
|
19915
20474
|
|
|
19916
20475
|
// packages/server/dist/routes/cron.js
|
|
19917
|
-
import { Hono as
|
|
20476
|
+
import { Hono as Hono12 } from "hono";
|
|
19918
20477
|
import { eq as eq11, desc as desc4, lt as lt5, and as and7 } from "drizzle-orm";
|
|
19919
|
-
import
|
|
20478
|
+
import fs45 from "node:fs/promises";
|
|
19920
20479
|
import { Cron as Cron2 } from "croner";
|
|
19921
20480
|
function newJobId() {
|
|
19922
20481
|
return `cron-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -19930,7 +20489,7 @@ function validateSchedule(expr) {
|
|
|
19930
20489
|
}
|
|
19931
20490
|
}
|
|
19932
20491
|
function createCronRoutes() {
|
|
19933
|
-
const router = new
|
|
20492
|
+
const router = new Hono12();
|
|
19934
20493
|
router.get("/cron/jobs", async (c) => {
|
|
19935
20494
|
const db = getCronDb();
|
|
19936
20495
|
const limit = Math.min(Math.max(parseInt(c.req.query("limit") ?? "20", 10) || 20, 1), 100);
|
|
@@ -20093,7 +20652,7 @@ function createCronRoutes() {
|
|
|
20093
20652
|
return c.json({ log: null });
|
|
20094
20653
|
let log = null;
|
|
20095
20654
|
try {
|
|
20096
|
-
log = await
|
|
20655
|
+
log = await fs45.readFile(row.logPath, "utf-8");
|
|
20097
20656
|
} catch {
|
|
20098
20657
|
}
|
|
20099
20658
|
return c.json({ log });
|
|
@@ -20389,20 +20948,21 @@ var init_ticker = __esm({
|
|
|
20389
20948
|
});
|
|
20390
20949
|
|
|
20391
20950
|
// packages/server/dist/evolution/spawn.js
|
|
20392
|
-
import
|
|
20951
|
+
import path50 from "node:path";
|
|
20393
20952
|
import { spawn as spawn3 } from "node:child_process";
|
|
20394
20953
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20395
20954
|
var __dirname2, WRAPPER_PATH, realEvoSpawner;
|
|
20396
20955
|
var init_spawn = __esm({
|
|
20397
20956
|
"packages/server/dist/evolution/spawn.js"() {
|
|
20398
20957
|
"use strict";
|
|
20399
|
-
|
|
20400
|
-
|
|
20958
|
+
init_child_env();
|
|
20959
|
+
__dirname2 = path50.dirname(fileURLToPath2(import.meta.url));
|
|
20960
|
+
WRAPPER_PATH = path50.resolve(__dirname2, "evo-wrapper.js");
|
|
20401
20961
|
realEvoSpawner = (mode, id) => {
|
|
20402
20962
|
const child = spawn3(process.execPath, [WRAPPER_PATH, `--mode=${mode}`, `--id=${id}`], {
|
|
20403
20963
|
detached: true,
|
|
20404
20964
|
stdio: "ignore",
|
|
20405
|
-
env:
|
|
20965
|
+
env: cleanChildEnv(),
|
|
20406
20966
|
// Without this, a detached console process on Windows gets a freshly
|
|
20407
20967
|
// allocated console window (a black box pops up for every evo/score/apply
|
|
20408
20968
|
// run). cron doesn't hit this — it spawns the cli inline, not via a
|
|
@@ -20643,7 +21203,7 @@ var init_session_prefix = __esm({
|
|
|
20643
21203
|
|
|
20644
21204
|
// packages/server/dist/channels/telegram/handler.js
|
|
20645
21205
|
import { Bot, InputFile } from "grammy";
|
|
20646
|
-
import
|
|
21206
|
+
import path51 from "node:path";
|
|
20647
21207
|
function inferMediaKind(filePath) {
|
|
20648
21208
|
const cls = classifyMedia(filePath);
|
|
20649
21209
|
if (cls === "image")
|
|
@@ -20694,7 +21254,7 @@ function startTelegramChannel(deps) {
|
|
|
20694
21254
|
}
|
|
20695
21255
|
const abort = new AbortController();
|
|
20696
21256
|
const unsubscribers = /* @__PURE__ */ new Map();
|
|
20697
|
-
const
|
|
21257
|
+
const activeOverrides2 = /* @__PURE__ */ new Map();
|
|
20698
21258
|
const restartSelf = () => {
|
|
20699
21259
|
queueMicrotask(() => {
|
|
20700
21260
|
void stopAccount(accountId).then(() => {
|
|
@@ -20703,8 +21263,8 @@ function startTelegramChannel(deps) {
|
|
|
20703
21263
|
});
|
|
20704
21264
|
};
|
|
20705
21265
|
const bot = new Bot(account.botToken);
|
|
20706
|
-
const promise = runBot({ registry: registry2, db, account, bot, abort, unsubscribers, activeOverrides, restartSelf }).catch((err) => console.log(`[telegram] account ${accountId} bot crashed: ${String(err)}`));
|
|
20707
|
-
runners.set(accountId, { accountId, bot, abort, promise, unsubscribers, activeOverrides });
|
|
21266
|
+
const promise = runBot({ registry: registry2, db, account, bot, abort, unsubscribers, activeOverrides: activeOverrides2, restartSelf }).catch((err) => console.log(`[telegram] account ${accountId} bot crashed: ${String(err)}`));
|
|
21267
|
+
runners.set(accountId, { accountId, bot, abort, promise, unsubscribers, activeOverrides: activeOverrides2 });
|
|
20708
21268
|
console.log(`[telegram] account ${accountId} started (@${account.botUsername}, workspace=${account.workspacePath})`);
|
|
20709
21269
|
}
|
|
20710
21270
|
async function stopAccount(accountId) {
|
|
@@ -20729,7 +21289,7 @@ function startTelegramChannel(deps) {
|
|
|
20729
21289
|
return { startAccount, stopAccount, stopAll };
|
|
20730
21290
|
}
|
|
20731
21291
|
async function runBot(args) {
|
|
20732
|
-
const { registry: registry2, db, account, bot, unsubscribers, activeOverrides, restartSelf } = args;
|
|
21292
|
+
const { registry: registry2, db, account, bot, unsubscribers, activeOverrides: activeOverrides2, restartSelf } = args;
|
|
20733
21293
|
bot.catch((err) => {
|
|
20734
21294
|
console.log(`[telegram] ${account.accountId} bot error: ${String(err)}`);
|
|
20735
21295
|
});
|
|
@@ -20744,7 +21304,7 @@ async function runBot(args) {
|
|
|
20744
21304
|
sessionPrefix: buildTgSessionPrefix(userId),
|
|
20745
21305
|
accessLevel: account.accessLevel,
|
|
20746
21306
|
channelLabel: `Telegram: ${userId}`,
|
|
20747
|
-
activeOverrides,
|
|
21307
|
+
activeOverrides: activeOverrides2,
|
|
20748
21308
|
workspacePath: account.workspacePath,
|
|
20749
21309
|
lang,
|
|
20750
21310
|
channel: {
|
|
@@ -20805,7 +21365,7 @@ async function runBot(args) {
|
|
|
20805
21365
|
}
|
|
20806
21366
|
}
|
|
20807
21367
|
}
|
|
20808
|
-
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: text5, lang, unsubscribers, activeOverrides });
|
|
21368
|
+
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: text5, lang, unsubscribers, activeOverrides: activeOverrides2 });
|
|
20809
21369
|
});
|
|
20810
21370
|
bot.on("message:photo", async (ctx) => {
|
|
20811
21371
|
const userId = ctx.from.id;
|
|
@@ -20841,7 +21401,7 @@ async function runBot(args) {
|
|
|
20841
21401
|
}
|
|
20842
21402
|
const text5 = caption ? `${caption}
|
|
20843
21403
|
${imageNote}` : imageNote;
|
|
20844
|
-
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: text5, images, lang, unsubscribers, activeOverrides });
|
|
21404
|
+
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: text5, images, lang, unsubscribers, activeOverrides: activeOverrides2 });
|
|
20845
21405
|
});
|
|
20846
21406
|
bot.on("message:document", async (ctx) => {
|
|
20847
21407
|
const userId = ctx.from.id;
|
|
@@ -20865,7 +21425,7 @@ ${imageNote}` : imageNote;
|
|
|
20865
21425
|
}
|
|
20866
21426
|
const text5 = caption ? `${caption}
|
|
20867
21427
|
${fileNote}` : fileNote;
|
|
20868
|
-
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: text5, lang, unsubscribers, activeOverrides });
|
|
21428
|
+
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: text5, lang, unsubscribers, activeOverrides: activeOverrides2 });
|
|
20869
21429
|
});
|
|
20870
21430
|
bot.on("message:voice", async (ctx) => {
|
|
20871
21431
|
const userId = ctx.from.id;
|
|
@@ -20885,7 +21445,7 @@ ${fileNote}` : fileNote;
|
|
|
20885
21445
|
}
|
|
20886
21446
|
} catch {
|
|
20887
21447
|
}
|
|
20888
|
-
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: voiceNote, lang, unsubscribers, activeOverrides });
|
|
21448
|
+
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: voiceNote, lang, unsubscribers, activeOverrides: activeOverrides2 });
|
|
20889
21449
|
});
|
|
20890
21450
|
bot.on("message:video_note", async (ctx) => {
|
|
20891
21451
|
const userId = ctx.from.id;
|
|
@@ -20904,7 +21464,7 @@ ${fileNote}` : fileNote;
|
|
|
20904
21464
|
}
|
|
20905
21465
|
} catch {
|
|
20906
21466
|
}
|
|
20907
|
-
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: vnNote, lang, unsubscribers, activeOverrides });
|
|
21467
|
+
await handleUserMessage({ registry: registry2, db, account, bot, ctx, userId, workspace, text: vnNote, lang, unsubscribers, activeOverrides: activeOverrides2 });
|
|
20908
21468
|
});
|
|
20909
21469
|
console.log(`[telegram] ${account.accountId} starting long-poll\u2026`);
|
|
20910
21470
|
await bot.start({
|
|
@@ -20913,12 +21473,12 @@ ${fileNote}` : fileNote;
|
|
|
20913
21473
|
});
|
|
20914
21474
|
}
|
|
20915
21475
|
async function handleUserMessage(args) {
|
|
20916
|
-
const { registry: registry2, db, account, bot, ctx, userId, workspace, text: text5, images, lang, unsubscribers, activeOverrides } = args;
|
|
21476
|
+
const { registry: registry2, db, account, bot, ctx, userId, workspace, text: text5, images, lang, unsubscribers, activeOverrides: activeOverrides2 } = args;
|
|
20917
21477
|
const chatId = ctx.chat.id;
|
|
20918
21478
|
rememberLastActiveChat(db, account.accountId, String(chatId));
|
|
20919
21479
|
const sm = registry2.getOrCreate(workspace);
|
|
20920
21480
|
const accessLevel = account.accessLevel === "full" ? null : account.accessLevel === "workspace" ? "workspace" : "readonly";
|
|
20921
|
-
const sessionId = await getOrCreateActiveSession(sm, userId,
|
|
21481
|
+
const sessionId = await getOrCreateActiveSession(sm, userId, activeOverrides2, accessLevel, workspace);
|
|
20922
21482
|
if (sm.isSessionCompacting(sessionId)) {
|
|
20923
21483
|
await ctx.reply(t("handler.compacting", lang));
|
|
20924
21484
|
return;
|
|
@@ -20932,9 +21492,9 @@ async function handleUserMessage(args) {
|
|
|
20932
21492
|
await bot.api.sendMessage(chatId, chunk, { parse_mode: void 0 });
|
|
20933
21493
|
},
|
|
20934
21494
|
sendMedia: async (filePath) => {
|
|
20935
|
-
const resolved =
|
|
20936
|
-
const ws =
|
|
20937
|
-
const inWorkspace = resolved === ws || resolved.startsWith(ws +
|
|
21495
|
+
const resolved = path51.resolve(filePath);
|
|
21496
|
+
const ws = path51.resolve(workspace);
|
|
21497
|
+
const inWorkspace = resolved === ws || resolved.startsWith(ws + path51.sep);
|
|
20938
21498
|
if (!inWorkspace && !isInTempDir(resolved)) {
|
|
20939
21499
|
console.log(`[telegram] sendMedia blocked: ${filePath} not under workspace`);
|
|
20940
21500
|
return;
|
|
@@ -20971,11 +21531,11 @@ ${text5}`;
|
|
|
20971
21531
|
console.log(`[telegram] sendUserMessage ${sessionId}: ${String(err)}`);
|
|
20972
21532
|
});
|
|
20973
21533
|
}
|
|
20974
|
-
function findActiveTgSession(sm, userId,
|
|
20975
|
-
return findActiveSessionId(sm, String(userId), buildTgSessionPrefix(userId),
|
|
21534
|
+
function findActiveTgSession(sm, userId, activeOverrides2, accessLevel) {
|
|
21535
|
+
return findActiveSessionId(sm, String(userId), buildTgSessionPrefix(userId), activeOverrides2, accessLevel);
|
|
20976
21536
|
}
|
|
20977
|
-
async function getOrCreateActiveSession(sm, userId,
|
|
20978
|
-
const existing = findActiveTgSession(sm, userId,
|
|
21537
|
+
async function getOrCreateActiveSession(sm, userId, activeOverrides2, accessLevel, workspacePath) {
|
|
21538
|
+
const existing = findActiveTgSession(sm, userId, activeOverrides2, accessLevel === null ? "full" : accessLevel);
|
|
20979
21539
|
if (existing)
|
|
20980
21540
|
return existing;
|
|
20981
21541
|
const newId = `${buildTgSessionPrefix(userId)}${Date.now().toString(36)}`;
|
|
@@ -20999,20 +21559,20 @@ var init_handler2 = __esm({
|
|
|
20999
21559
|
});
|
|
21000
21560
|
|
|
21001
21561
|
// packages/server/dist/routes/telegram.js
|
|
21002
|
-
import { Hono as
|
|
21003
|
-
import
|
|
21562
|
+
import { Hono as Hono13 } from "hono";
|
|
21563
|
+
import path52 from "node:path";
|
|
21004
21564
|
import { Bot as Bot2 } from "grammy";
|
|
21005
|
-
import
|
|
21565
|
+
import fs46 from "node:fs";
|
|
21006
21566
|
function createTelegramRoutes(deps) {
|
|
21007
21567
|
const { db, channel } = deps;
|
|
21008
|
-
const app2 = new
|
|
21568
|
+
const app2 = new Hono13();
|
|
21009
21569
|
app2.get("/telegram/accounts", (c) => {
|
|
21010
21570
|
const accounts = listAccounts3(db).map((acc) => {
|
|
21011
21571
|
return {
|
|
21012
21572
|
accountId: acc.accountId,
|
|
21013
21573
|
botUsername: acc.botUsername,
|
|
21014
21574
|
workspacePath: acc.workspacePath,
|
|
21015
|
-
workspaceMissing: !
|
|
21575
|
+
workspaceMissing: !fs46.existsSync(acc.workspacePath),
|
|
21016
21576
|
label: acc.label,
|
|
21017
21577
|
enabled: acc.enabled,
|
|
21018
21578
|
accessLevel: acc.accessLevel,
|
|
@@ -21030,9 +21590,9 @@ function createTelegramRoutes(deps) {
|
|
|
21030
21590
|
return c.json({ error: "botToken required" }, 400);
|
|
21031
21591
|
if (!body.workspacePath)
|
|
21032
21592
|
return c.json({ error: "workspacePath required" }, 400);
|
|
21033
|
-
if (!
|
|
21593
|
+
if (!path52.isAbsolute(body.workspacePath))
|
|
21034
21594
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
21035
|
-
if (!
|
|
21595
|
+
if (!fs46.existsSync(body.workspacePath))
|
|
21036
21596
|
return c.json({ error: "workspace path not found" }, 400);
|
|
21037
21597
|
ensureWorkspaceHalo(body.workspacePath);
|
|
21038
21598
|
let botUsername;
|
|
@@ -21091,7 +21651,7 @@ function createTelegramRoutes(deps) {
|
|
|
21091
21651
|
if (body.enabled !== void 0)
|
|
21092
21652
|
patch.enabled = body.enabled ? 1 : 0;
|
|
21093
21653
|
if (body.workspacePath) {
|
|
21094
|
-
if (!
|
|
21654
|
+
if (!fs46.existsSync(body.workspacePath))
|
|
21095
21655
|
return c.json({ error: "workspace path not found" }, 400);
|
|
21096
21656
|
ensureWorkspaceHalo(body.workspacePath);
|
|
21097
21657
|
patch.workspacePath = body.workspacePath;
|
|
@@ -21737,8 +22297,8 @@ var init_cdn = __esm({
|
|
|
21737
22297
|
});
|
|
21738
22298
|
|
|
21739
22299
|
// packages/server/dist/channels/wechat/send-media.js
|
|
21740
|
-
import
|
|
21741
|
-
import
|
|
22300
|
+
import fs47 from "node:fs/promises";
|
|
22301
|
+
import path53 from "node:path";
|
|
21742
22302
|
function inferKind(filePath) {
|
|
21743
22303
|
const cls = classifyMedia(filePath);
|
|
21744
22304
|
if (cls === "image")
|
|
@@ -21750,7 +22310,7 @@ function inferKind(filePath) {
|
|
|
21750
22310
|
async function sendMediaFile(params) {
|
|
21751
22311
|
const { baseUrl, token, toUserId, contextToken, filePath } = params;
|
|
21752
22312
|
const kind = params.kind ?? inferKind(filePath);
|
|
21753
|
-
const plaintext = await
|
|
22313
|
+
const plaintext = await fs47.readFile(filePath);
|
|
21754
22314
|
const prep = prepareUpload(plaintext);
|
|
21755
22315
|
console.log(`[weixin] sendMediaFile: path=${filePath} kind=${kind} rawsize=${prep.rawsize} filesize=${prep.filesize}`);
|
|
21756
22316
|
const mediaType = kind === "image" ? UploadMediaType.IMAGE : kind === "video" ? UploadMediaType.VIDEO : UploadMediaType.FILE;
|
|
@@ -21788,7 +22348,7 @@ async function sendMediaFile(params) {
|
|
|
21788
22348
|
aesKeyBase64,
|
|
21789
22349
|
fileSizeCiphertext: prep.filesize,
|
|
21790
22350
|
fileSize: prep.rawsize,
|
|
21791
|
-
fileName:
|
|
22351
|
+
fileName: path53.basename(filePath)
|
|
21792
22352
|
});
|
|
21793
22353
|
const clientId = `halo-media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
21794
22354
|
const body = {
|
|
@@ -21951,12 +22511,12 @@ var init_login = __esm({
|
|
|
21951
22511
|
});
|
|
21952
22512
|
|
|
21953
22513
|
// packages/server/dist/channels/wechat/handler.js
|
|
21954
|
-
import
|
|
21955
|
-
import
|
|
22514
|
+
import fs48 from "node:fs";
|
|
22515
|
+
import path54 from "node:path";
|
|
21956
22516
|
import QRCode from "qrcode";
|
|
21957
22517
|
function isPathAllowed(filePath, workspacePath) {
|
|
21958
|
-
const resolved =
|
|
21959
|
-
return resolved.startsWith(
|
|
22518
|
+
const resolved = path54.resolve(filePath);
|
|
22519
|
+
return resolved.startsWith(path54.resolve(workspacePath)) || isInTempDir(resolved);
|
|
21960
22520
|
}
|
|
21961
22521
|
function startWeixinChannel(deps) {
|
|
21962
22522
|
const { registry: registry2, db } = deps;
|
|
@@ -21977,7 +22537,7 @@ function startWeixinChannel(deps) {
|
|
|
21977
22537
|
}
|
|
21978
22538
|
const abort = new AbortController();
|
|
21979
22539
|
const unsubscribers = /* @__PURE__ */ new Map();
|
|
21980
|
-
const
|
|
22540
|
+
const activeOverrides2 = /* @__PURE__ */ new Map();
|
|
21981
22541
|
const sessionContextTokens = /* @__PURE__ */ new Map();
|
|
21982
22542
|
const restartSelf = () => {
|
|
21983
22543
|
queueMicrotask(() => {
|
|
@@ -21986,8 +22546,8 @@ function startWeixinChannel(deps) {
|
|
|
21986
22546
|
});
|
|
21987
22547
|
});
|
|
21988
22548
|
};
|
|
21989
|
-
const promise = runAccountLoop({ registry: registry2, db, account, abort: abort.signal, unsubscribers, activeOverrides, sessionContextTokens, restartSelf, startNewAccount: startAccount }).catch((err) => console.log(`[weixin] account ${accountId} loop crashed: ${String(err)}`));
|
|
21990
|
-
runners.set(accountId, { accountId, abort, promise, unsubscribers, activeOverrides });
|
|
22549
|
+
const promise = runAccountLoop({ registry: registry2, db, account, abort: abort.signal, unsubscribers, activeOverrides: activeOverrides2, sessionContextTokens, restartSelf, startNewAccount: startAccount }).catch((err) => console.log(`[weixin] account ${accountId} loop crashed: ${String(err)}`));
|
|
22550
|
+
runners.set(accountId, { accountId, abort, promise, unsubscribers, activeOverrides: activeOverrides2 });
|
|
21991
22551
|
console.log(`[weixin] account ${accountId} started (workspace=${account.workspacePath})`);
|
|
21992
22552
|
}
|
|
21993
22553
|
async function stopAccount(accountId) {
|
|
@@ -22019,7 +22579,7 @@ function startWeixinChannel(deps) {
|
|
|
22019
22579
|
return { startAccount, stopAccount, stopAll };
|
|
22020
22580
|
}
|
|
22021
22581
|
async function runAccountLoop(args) {
|
|
22022
|
-
const { registry: registry2, db, account, abort, unsubscribers, activeOverrides, sessionContextTokens, restartSelf, startNewAccount } = args;
|
|
22582
|
+
const { registry: registry2, db, account, abort, unsubscribers, activeOverrides: activeOverrides2, sessionContextTokens, restartSelf, startNewAccount } = args;
|
|
22023
22583
|
try {
|
|
22024
22584
|
await notifyStart({ baseUrl: account.baseUrl, token: account.botToken });
|
|
22025
22585
|
} catch (err) {
|
|
@@ -22058,7 +22618,7 @@ async function runAccountLoop(args) {
|
|
|
22058
22618
|
saveSyncBuf(db, account.accountId, getUpdatesBuf);
|
|
22059
22619
|
}
|
|
22060
22620
|
for (const msg of resp.msgs ?? []) {
|
|
22061
|
-
await handleInbound({ registry: registry2, db, account, msg, unsubscribers, activeOverrides, sessionContextTokens, restartSelf, startNewAccount });
|
|
22621
|
+
await handleInbound({ registry: registry2, db, account, msg, unsubscribers, activeOverrides: activeOverrides2, sessionContextTokens, restartSelf, startNewAccount });
|
|
22062
22622
|
}
|
|
22063
22623
|
} catch (err) {
|
|
22064
22624
|
if (abort.aborted)
|
|
@@ -22181,11 +22741,11 @@ async function processItems(args) {
|
|
|
22181
22741
|
function buildWxSessionPrefix(fromUserId) {
|
|
22182
22742
|
return sessionPrefix("wx", normalizeAccountId(fromUserId));
|
|
22183
22743
|
}
|
|
22184
|
-
function findActiveWxSession(sm, fromUserId,
|
|
22185
|
-
return findActiveSessionId(sm, fromUserId, buildWxSessionPrefix(fromUserId),
|
|
22744
|
+
function findActiveWxSession(sm, fromUserId, activeOverrides2, accessLevel) {
|
|
22745
|
+
return findActiveSessionId(sm, fromUserId, buildWxSessionPrefix(fromUserId), activeOverrides2, accessLevel);
|
|
22186
22746
|
}
|
|
22187
|
-
async function getOrCreateActiveSession2(sm, fromUserId,
|
|
22188
|
-
const existing = findActiveWxSession(sm, fromUserId,
|
|
22747
|
+
async function getOrCreateActiveSession2(sm, fromUserId, activeOverrides2, accessLevel, workspacePath) {
|
|
22748
|
+
const existing = findActiveWxSession(sm, fromUserId, activeOverrides2, accessLevel === null ? "full" : accessLevel);
|
|
22189
22749
|
if (existing)
|
|
22190
22750
|
return existing;
|
|
22191
22751
|
const newId = `${buildWxSessionPrefix(fromUserId)}${Date.now().toString(36)}`;
|
|
@@ -22194,7 +22754,7 @@ async function getOrCreateActiveSession2(sm, fromUserId, activeOverrides, access
|
|
|
22194
22754
|
return newId;
|
|
22195
22755
|
}
|
|
22196
22756
|
async function handleInbound(args) {
|
|
22197
|
-
const { registry: registry2, db, account: storedAccount, msg, unsubscribers, activeOverrides, sessionContextTokens, restartSelf, startNewAccount } = args;
|
|
22757
|
+
const { registry: registry2, db, account: storedAccount, msg, unsubscribers, activeOverrides: activeOverrides2, sessionContextTokens, restartSelf, startNewAccount } = args;
|
|
22198
22758
|
const fromUserId = msg.from_user_id ?? "";
|
|
22199
22759
|
if (!fromUserId)
|
|
22200
22760
|
return;
|
|
@@ -22228,7 +22788,7 @@ async function handleInbound(args) {
|
|
|
22228
22788
|
restartSelf,
|
|
22229
22789
|
startNewAccount,
|
|
22230
22790
|
registry: registry2,
|
|
22231
|
-
activeOverrides,
|
|
22791
|
+
activeOverrides: activeOverrides2,
|
|
22232
22792
|
unsubscribers,
|
|
22233
22793
|
sessionContextTokens,
|
|
22234
22794
|
lang
|
|
@@ -22240,7 +22800,7 @@ async function handleInbound(args) {
|
|
|
22240
22800
|
rememberLastActiveChat(db, account.accountId, fromUserId);
|
|
22241
22801
|
const sm = registry2.getOrCreate(account.workspacePath);
|
|
22242
22802
|
const sessionAccessLevel = account.accessLevel === "full" ? null : account.accessLevel === "workspace" ? "workspace" : "readonly";
|
|
22243
|
-
const sessionId = await getOrCreateActiveSession2(sm, fromUserId,
|
|
22803
|
+
const sessionId = await getOrCreateActiveSession2(sm, fromUserId, activeOverrides2, sessionAccessLevel, account.workspacePath);
|
|
22244
22804
|
if (sm.isSessionCompacting(sessionId)) {
|
|
22245
22805
|
await sendToUser({
|
|
22246
22806
|
account,
|
|
@@ -22298,7 +22858,7 @@ ${userText}`;
|
|
|
22298
22858
|
});
|
|
22299
22859
|
}
|
|
22300
22860
|
async function handleSlashCommand(args) {
|
|
22301
|
-
const { text: text5, account, db, fromUserId, contextToken, restartSelf, startNewAccount, registry: registry2, activeOverrides, unsubscribers, sessionContextTokens, lang } = args;
|
|
22861
|
+
const { text: text5, account, db, fromUserId, contextToken, restartSelf, startNewAccount, registry: registry2, activeOverrides: activeOverrides2, unsubscribers, sessionContextTokens, lang } = args;
|
|
22302
22862
|
const reply = (msg) => sendToUser({ account, toUserId: fromUserId, text: msg, contextToken });
|
|
22303
22863
|
function ensureListener(sm2, sessionId) {
|
|
22304
22864
|
if (unsubscribers.has(sessionId))
|
|
@@ -22340,7 +22900,7 @@ async function handleSlashCommand(args) {
|
|
|
22340
22900
|
sessionPrefix: buildWxSessionPrefix(fromUserId),
|
|
22341
22901
|
accessLevel: account.accessLevel,
|
|
22342
22902
|
channelLabel: `WeChat: ${fromUserId}`,
|
|
22343
|
-
activeOverrides,
|
|
22903
|
+
activeOverrides: activeOverrides2,
|
|
22344
22904
|
workspacePath: account.workspacePath,
|
|
22345
22905
|
lang,
|
|
22346
22906
|
// WeChat's "chat id" for cron purposes is the openId of the user
|
|
@@ -22366,7 +22926,7 @@ async function handleSlashCommand(args) {
|
|
|
22366
22926
|
restartSelf();
|
|
22367
22927
|
} else {
|
|
22368
22928
|
if (result.switchTo) {
|
|
22369
|
-
const oldSid = findActiveSessionId(sm, fromUserId, ctx.sessionPrefix,
|
|
22929
|
+
const oldSid = findActiveSessionId(sm, fromUserId, ctx.sessionPrefix, activeOverrides2, ctx.accessLevel);
|
|
22370
22930
|
if (oldSid && oldSid !== result.switchTo) {
|
|
22371
22931
|
unsubscribers.get(oldSid)?.();
|
|
22372
22932
|
unsubscribers.delete(oldSid);
|
|
@@ -22393,7 +22953,7 @@ async function handleSlashCommand(args) {
|
|
|
22393
22953
|
await reply(login.message);
|
|
22394
22954
|
return true;
|
|
22395
22955
|
}
|
|
22396
|
-
const tmpPath =
|
|
22956
|
+
const tmpPath = path54.join(tempDir(), `halo-qr-${login.sessionKey}.png`);
|
|
22397
22957
|
await QRCode.toFile(tmpPath, login.qrcodeUrl, { width: 256, margin: 2 });
|
|
22398
22958
|
await sendMediaFile({
|
|
22399
22959
|
baseUrl: account.baseUrl,
|
|
@@ -22402,7 +22962,7 @@ async function handleSlashCommand(args) {
|
|
|
22402
22962
|
contextToken,
|
|
22403
22963
|
filePath: tmpPath
|
|
22404
22964
|
});
|
|
22405
|
-
await
|
|
22965
|
+
await fs48.promises.unlink(tmpPath).catch(() => {
|
|
22406
22966
|
});
|
|
22407
22967
|
await reply(t("wechat.qr_sent", lang, { level, path: account.workspacePath }));
|
|
22408
22968
|
waitLogin({ sessionKey: login.sessionKey }).then(async (result2) => {
|
|
@@ -22480,18 +23040,18 @@ var init_handler3 = __esm({
|
|
|
22480
23040
|
});
|
|
22481
23041
|
|
|
22482
23042
|
// packages/server/dist/routes/weixin.js
|
|
22483
|
-
import { Hono as
|
|
22484
|
-
import
|
|
22485
|
-
import
|
|
23043
|
+
import { Hono as Hono14 } from "hono";
|
|
23044
|
+
import path55 from "node:path";
|
|
23045
|
+
import fs49 from "node:fs";
|
|
22486
23046
|
function createWeixinRoutes(deps) {
|
|
22487
23047
|
const { db, channel } = deps;
|
|
22488
|
-
const app2 = new
|
|
23048
|
+
const app2 = new Hono14();
|
|
22489
23049
|
app2.get("/weixin/accounts", (c) => {
|
|
22490
23050
|
const accounts = listAccounts4(db).map((acc) => {
|
|
22491
23051
|
return {
|
|
22492
23052
|
...stripSecrets(acc),
|
|
22493
23053
|
workspacePath: acc.workspacePath,
|
|
22494
|
-
workspaceMissing: !
|
|
23054
|
+
workspaceMissing: !fs49.existsSync(acc.workspacePath)
|
|
22495
23055
|
};
|
|
22496
23056
|
});
|
|
22497
23057
|
return c.json({ accounts });
|
|
@@ -22507,14 +23067,14 @@ function createWeixinRoutes(deps) {
|
|
|
22507
23067
|
return c.json({ error: "sessionKey required" }, 400);
|
|
22508
23068
|
if (!body.workspacePath)
|
|
22509
23069
|
return c.json({ error: "workspacePath required" }, 400);
|
|
22510
|
-
if (!
|
|
23070
|
+
if (!path55.isAbsolute(body.workspacePath))
|
|
22511
23071
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
22512
23072
|
const accessLevel = body.accessLevel === "full" ? "full" : body.accessLevel === "workspace" ? "workspace" : "readonly";
|
|
22513
23073
|
const result = await waitLogin({ sessionKey: body.sessionKey, timeoutMs: body.timeoutMs });
|
|
22514
23074
|
if (!result.connected || !result.accountId || !result.botToken || !result.baseUrl) {
|
|
22515
23075
|
return c.json({ connected: false, message: result.message }, 200);
|
|
22516
23076
|
}
|
|
22517
|
-
if (!
|
|
23077
|
+
if (!fs49.existsSync(body.workspacePath))
|
|
22518
23078
|
return c.json({ error: "workspace path not found" }, 400);
|
|
22519
23079
|
ensureWorkspaceHalo(body.workspacePath);
|
|
22520
23080
|
const normalized = normalizeAccountId(result.accountId);
|
|
@@ -22552,7 +23112,7 @@ function createWeixinRoutes(deps) {
|
|
|
22552
23112
|
if (!existing)
|
|
22553
23113
|
return c.json({ error: "not found" }, 404);
|
|
22554
23114
|
const body = await c.req.json().catch(() => ({}));
|
|
22555
|
-
if (body.workspacePath !== void 0 && !
|
|
23115
|
+
if (body.workspacePath !== void 0 && !path55.isAbsolute(body.workspacePath)) {
|
|
22556
23116
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
22557
23117
|
}
|
|
22558
23118
|
if (body.accessLevel !== void 0 && body.accessLevel !== "full" && body.accessLevel !== "workspace" && body.accessLevel !== "readonly") {
|
|
@@ -22560,7 +23120,7 @@ function createWeixinRoutes(deps) {
|
|
|
22560
23120
|
}
|
|
22561
23121
|
const patch = { ...body };
|
|
22562
23122
|
if (body.workspacePath !== void 0) {
|
|
22563
|
-
if (!
|
|
23123
|
+
if (!fs49.existsSync(body.workspacePath))
|
|
22564
23124
|
return c.json({ error: "workspace path not found" }, 400);
|
|
22565
23125
|
ensureWorkspaceHalo(body.workspacePath);
|
|
22566
23126
|
patch.workspacePath = body.workspacePath;
|
|
@@ -22679,20 +23239,20 @@ function buildWebSessionPrefix(accountId) {
|
|
|
22679
23239
|
}
|
|
22680
23240
|
function createWebChannel(deps) {
|
|
22681
23241
|
const { registry: registry2, db } = deps;
|
|
22682
|
-
const
|
|
23242
|
+
const activeOverrides2 = /* @__PURE__ */ new Map();
|
|
22683
23243
|
function getActiveSessionId(sm, accountId) {
|
|
22684
23244
|
const prefix = buildWebSessionPrefix(accountId);
|
|
22685
|
-
return findActiveSessionId(sm, accountId, prefix,
|
|
23245
|
+
return findActiveSessionId(sm, accountId, prefix, activeOverrides2, "full") ?? void 0;
|
|
22686
23246
|
}
|
|
22687
23247
|
function resolveWorkspace(account, override) {
|
|
22688
|
-
let
|
|
23248
|
+
let path63 = account.workspacePath;
|
|
22689
23249
|
if (override && override !== account.workspacePath) {
|
|
22690
23250
|
if (account.accessLevel !== "full") {
|
|
22691
23251
|
return { ok: false, error: "workspace override requires a full-access token" };
|
|
22692
23252
|
}
|
|
22693
|
-
|
|
23253
|
+
path63 = override;
|
|
22694
23254
|
}
|
|
22695
|
-
const resolved = resolveAccountWorkspace({ ...account, workspacePath:
|
|
23255
|
+
const resolved = resolveAccountWorkspace({ ...account, workspacePath: path63 });
|
|
22696
23256
|
if (!resolved)
|
|
22697
23257
|
return { ok: false, error: "workspace not found" };
|
|
22698
23258
|
return { ok: true, path: resolved };
|
|
@@ -22704,7 +23264,7 @@ function createWebChannel(deps) {
|
|
|
22704
23264
|
sessionPrefix: buildWebSessionPrefix(account.accountId),
|
|
22705
23265
|
accessLevel: account.accessLevel,
|
|
22706
23266
|
channelLabel: `Web: ${account.label || account.accountId}`,
|
|
22707
|
-
activeOverrides,
|
|
23267
|
+
activeOverrides: activeOverrides2,
|
|
22708
23268
|
workspacePath: account.workspacePath,
|
|
22709
23269
|
lang: getLang(account),
|
|
22710
23270
|
// Web is SSE-only, no per-conversation chat id — but the channel
|
|
@@ -22805,7 +23365,7 @@ function createWebChannel(deps) {
|
|
|
22805
23365
|
const agentId = opts?.agentId || await resolveDefaultAgentId(sm, workspace);
|
|
22806
23366
|
await sm.createSession(agentId, null, `Web: ${account.label || account.accountId}`, void 0, sessionId, void 0, accessLevel);
|
|
22807
23367
|
if (!opts?.sessionId)
|
|
22808
|
-
|
|
23368
|
+
activeOverrides2.set(account.accountId, sessionId);
|
|
22809
23369
|
}
|
|
22810
23370
|
yield sseData({ type: "session", sessionId });
|
|
22811
23371
|
const queue = [];
|
|
@@ -23075,21 +23635,21 @@ var init_handler4 = __esm({
|
|
|
23075
23635
|
});
|
|
23076
23636
|
|
|
23077
23637
|
// packages/server/dist/routes/web.js
|
|
23078
|
-
import { Hono as
|
|
23638
|
+
import { Hono as Hono15 } from "hono";
|
|
23079
23639
|
import { streamSSE } from "hono/streaming";
|
|
23080
23640
|
import crypto4 from "node:crypto";
|
|
23081
|
-
import
|
|
23082
|
-
import
|
|
23641
|
+
import path56 from "node:path";
|
|
23642
|
+
import fs50 from "node:fs";
|
|
23083
23643
|
function createWebRoutes(deps) {
|
|
23084
23644
|
const { db, channel } = deps;
|
|
23085
|
-
const app2 = new
|
|
23645
|
+
const app2 = new Hono15();
|
|
23086
23646
|
app2.get("/web/accounts", (c) => {
|
|
23087
23647
|
const accounts = listAccounts2(db).map((acc) => {
|
|
23088
23648
|
return {
|
|
23089
23649
|
accountId: acc.accountId,
|
|
23090
23650
|
token: acc.token,
|
|
23091
23651
|
workspacePath: acc.workspacePath,
|
|
23092
|
-
workspaceMissing: !
|
|
23652
|
+
workspaceMissing: !fs50.existsSync(acc.workspacePath),
|
|
23093
23653
|
label: acc.label,
|
|
23094
23654
|
enabled: acc.enabled,
|
|
23095
23655
|
accessLevel: acc.accessLevel,
|
|
@@ -23104,9 +23664,9 @@ function createWebRoutes(deps) {
|
|
|
23104
23664
|
const body = await c.req.json().catch(() => ({}));
|
|
23105
23665
|
if (!body.workspacePath)
|
|
23106
23666
|
return c.json({ error: "workspacePath required" }, 400);
|
|
23107
|
-
if (!
|
|
23667
|
+
if (!path56.isAbsolute(body.workspacePath))
|
|
23108
23668
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
23109
|
-
if (!
|
|
23669
|
+
if (!fs50.existsSync(body.workspacePath))
|
|
23110
23670
|
return c.json({ error: "workspace path not found" }, 400);
|
|
23111
23671
|
ensureWorkspaceHalo(body.workspacePath);
|
|
23112
23672
|
const accountId = crypto4.randomUUID().slice(0, 8);
|
|
@@ -23137,7 +23697,7 @@ function createWebRoutes(deps) {
|
|
|
23137
23697
|
if (body.enabled !== void 0)
|
|
23138
23698
|
patch.enabled = body.enabled ? 1 : 0;
|
|
23139
23699
|
if (body.workspacePath) {
|
|
23140
|
-
if (!
|
|
23700
|
+
if (!fs50.existsSync(body.workspacePath))
|
|
23141
23701
|
return c.json({ error: "workspace path not found" }, 400);
|
|
23142
23702
|
ensureWorkspaceHalo(body.workspacePath);
|
|
23143
23703
|
patch.workspacePath = body.workspacePath;
|
|
@@ -23242,18 +23802,27 @@ function createWebRoutes(deps) {
|
|
|
23242
23802
|
const filePath = c.req.query("path");
|
|
23243
23803
|
if (!filePath)
|
|
23244
23804
|
return c.json({ error: "path required" }, 400);
|
|
23245
|
-
const
|
|
23246
|
-
const resolved =
|
|
23247
|
-
const root =
|
|
23248
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
23805
|
+
const path63 = await import("node:path");
|
|
23806
|
+
const resolved = path63.default.resolve(account.workspacePath, filePath);
|
|
23807
|
+
const root = path63.default.resolve(account.workspacePath);
|
|
23808
|
+
if (resolved !== root && !resolved.startsWith(root + path63.default.sep)) {
|
|
23249
23809
|
return c.json({ error: "path traversal not allowed" }, 403);
|
|
23250
23810
|
}
|
|
23251
|
-
|
|
23811
|
+
let real;
|
|
23812
|
+
let realRoot;
|
|
23813
|
+
try {
|
|
23814
|
+
real = fs50.realpathSync(resolved);
|
|
23815
|
+
realRoot = fs50.realpathSync(root);
|
|
23816
|
+
} catch {
|
|
23252
23817
|
return c.json({ error: "file not found" }, 404);
|
|
23253
|
-
|
|
23818
|
+
}
|
|
23819
|
+
if (real !== realRoot && !real.startsWith(realRoot + path63.default.sep)) {
|
|
23820
|
+
return c.json({ error: "path traversal not allowed" }, 403);
|
|
23821
|
+
}
|
|
23822
|
+
const stat = fs50.statSync(real);
|
|
23254
23823
|
if (stat.isDirectory())
|
|
23255
23824
|
return c.json({ error: "cannot serve directory" }, 400);
|
|
23256
|
-
const ext =
|
|
23825
|
+
const ext = path63.default.extname(real).toLowerCase();
|
|
23257
23826
|
const mimeMap = {
|
|
23258
23827
|
".png": "image/png",
|
|
23259
23828
|
".jpg": "image/jpeg",
|
|
@@ -23267,8 +23836,8 @@ function createWebRoutes(deps) {
|
|
|
23267
23836
|
".txt": "text/plain"
|
|
23268
23837
|
};
|
|
23269
23838
|
const contentType = mimeMap[ext] || "application/octet-stream";
|
|
23270
|
-
const fileName =
|
|
23271
|
-
const data =
|
|
23839
|
+
const fileName = path63.default.basename(resolved);
|
|
23840
|
+
const data = fs50.readFileSync(real);
|
|
23272
23841
|
c.header("content-type", contentType);
|
|
23273
23842
|
c.header("content-disposition", `inline; filename="${fileName}"`);
|
|
23274
23843
|
return c.body(data);
|
|
@@ -23429,12 +23998,12 @@ function splitInlineCode(line) {
|
|
|
23429
23998
|
return segs.length > 0 ? segs : [{ text: line, code: false }];
|
|
23430
23999
|
}
|
|
23431
24000
|
function transformProse(text5) {
|
|
23432
|
-
text5 = text5.replace(/^(#{1,6})\s+(.*)$/, (_m, _h, body) => `*${body}*`);
|
|
23433
24001
|
text5 = text5.replace(/(?<!!)\[([^\]]+)\]\(([^)\s]+)\)/g, "<$2|$1>");
|
|
23434
|
-
text5 = text5.replace(/\*\*([^*\n]+?)\*\*/g, "*$1*");
|
|
23435
|
-
text5 = text5.replace(/__([^_\n]+?)__/g, "*$1*");
|
|
23436
24002
|
text5 = text5.replace(/(?<![*_])\*(?!\*)([^*\n]+?)(?<!\*)\*(?!\*)/g, "_$1_");
|
|
23437
24003
|
text5 = text5.replace(/(?<![_*])_(?!_)([^_\n]+?)(?<!_)_(?!_)/g, "_$1_");
|
|
24004
|
+
text5 = text5.replace(/\*\*([^*\n]+?)\*\*/g, "*$1*");
|
|
24005
|
+
text5 = text5.replace(/__([^_\n]+?)__/g, "*$1*");
|
|
24006
|
+
text5 = text5.replace(/^(#{1,6})\s+(.*)$/, (_m, _h, body) => `*${body}*`);
|
|
23438
24007
|
return text5;
|
|
23439
24008
|
}
|
|
23440
24009
|
function formatForFeishu(input) {
|
|
@@ -23598,10 +24167,10 @@ async function authTest(botToken) {
|
|
|
23598
24167
|
return slackPost("auth.test", botToken, {});
|
|
23599
24168
|
}
|
|
23600
24169
|
async function uploadFile(args) {
|
|
23601
|
-
const
|
|
23602
|
-
const
|
|
23603
|
-
const filename = args.filename ??
|
|
23604
|
-
const stat =
|
|
24170
|
+
const fs55 = await import("node:fs");
|
|
24171
|
+
const path63 = await import("node:path");
|
|
24172
|
+
const filename = args.filename ?? path63.basename(args.filePath);
|
|
24173
|
+
const stat = fs55.statSync(args.filePath);
|
|
23605
24174
|
if (!stat.isFile())
|
|
23606
24175
|
throw new Error(`[slack:uploadFile] not a regular file: ${args.filePath}`);
|
|
23607
24176
|
const length = stat.size;
|
|
@@ -23619,7 +24188,7 @@ async function uploadFile(args) {
|
|
|
23619
24188
|
if (step1Parsed.ok === false || !step1Parsed.upload_url || !step1Parsed.file_id) {
|
|
23620
24189
|
throw new Error(`[slack:files.getUploadURLExternal] ${step1Parsed.error ?? "unknown"}`);
|
|
23621
24190
|
}
|
|
23622
|
-
const buf =
|
|
24191
|
+
const buf = fs55.readFileSync(args.filePath);
|
|
23623
24192
|
const step2 = await fetch(step1Parsed.upload_url, {
|
|
23624
24193
|
method: "POST",
|
|
23625
24194
|
body: buf
|
|
@@ -23764,7 +24333,7 @@ var init_api2 = __esm({
|
|
|
23764
24333
|
});
|
|
23765
24334
|
|
|
23766
24335
|
// packages/server/dist/channels/slack/handler.js
|
|
23767
|
-
import
|
|
24336
|
+
import path57 from "node:path";
|
|
23768
24337
|
import { WebSocket } from "ws";
|
|
23769
24338
|
function slashToBang(text5, commandNames) {
|
|
23770
24339
|
if (commandNames.length === 0)
|
|
@@ -23774,9 +24343,9 @@ function slashToBang(text5, commandNames) {
|
|
|
23774
24343
|
return text5.replace(re, "$1!$2");
|
|
23775
24344
|
}
|
|
23776
24345
|
function isPathAllowed2(filePath, workspacePath) {
|
|
23777
|
-
const resolved =
|
|
23778
|
-
const wsResolved =
|
|
23779
|
-
return resolved.startsWith(wsResolved +
|
|
24346
|
+
const resolved = path57.resolve(filePath);
|
|
24347
|
+
const wsResolved = path57.resolve(workspacePath);
|
|
24348
|
+
return resolved.startsWith(wsResolved + path57.sep) || resolved === wsResolved || isInTempDir(resolved);
|
|
23780
24349
|
}
|
|
23781
24350
|
function rememberEventId(id) {
|
|
23782
24351
|
if (SEEN_EVENT_IDS.has(id))
|
|
@@ -24048,7 +24617,7 @@ async function handleInbound2(args) {
|
|
|
24048
24617
|
await postMessage({ botToken: account.botToken, channel: channelId, threadTs: replyTs, text: chunk });
|
|
24049
24618
|
},
|
|
24050
24619
|
sendMedia: async (filePath) => {
|
|
24051
|
-
const resolved =
|
|
24620
|
+
const resolved = path57.resolve(filePath);
|
|
24052
24621
|
if (!isPathAllowed2(resolved, workspace)) {
|
|
24053
24622
|
console.log(`[slack] sendMedia blocked: ${filePath} not under workspace`);
|
|
24054
24623
|
return;
|
|
@@ -24066,7 +24635,7 @@ async function handleInbound2(args) {
|
|
|
24066
24635
|
botToken: account.botToken,
|
|
24067
24636
|
channel: channelId,
|
|
24068
24637
|
threadTs: replyTs,
|
|
24069
|
-
text: `\u26A0\uFE0F Failed to upload \`${
|
|
24638
|
+
text: `\u26A0\uFE0F Failed to upload \`${path57.basename(filePath)}\`: ${err instanceof Error ? err.message : String(err)}`
|
|
24070
24639
|
}).catch(() => {
|
|
24071
24640
|
});
|
|
24072
24641
|
}
|
|
@@ -24142,12 +24711,12 @@ var init_handler5 = __esm({
|
|
|
24142
24711
|
});
|
|
24143
24712
|
|
|
24144
24713
|
// packages/server/dist/routes/slack.js
|
|
24145
|
-
import { Hono as
|
|
24146
|
-
import
|
|
24147
|
-
import
|
|
24714
|
+
import { Hono as Hono16 } from "hono";
|
|
24715
|
+
import fs51 from "node:fs";
|
|
24716
|
+
import path58 from "node:path";
|
|
24148
24717
|
function createSlackRoutes(deps) {
|
|
24149
24718
|
const { db, channel } = deps;
|
|
24150
|
-
const app2 = new
|
|
24719
|
+
const app2 = new Hono16();
|
|
24151
24720
|
app2.get("/slack/accounts/:id/search", async (c) => {
|
|
24152
24721
|
const id = c.req.param("id");
|
|
24153
24722
|
const q = c.req.query("q") ?? "";
|
|
@@ -24167,7 +24736,7 @@ function createSlackRoutes(deps) {
|
|
|
24167
24736
|
botUserId: a.botUserId,
|
|
24168
24737
|
teamId: a.teamId,
|
|
24169
24738
|
workspacePath: a.workspacePath,
|
|
24170
|
-
workspaceMissing: !
|
|
24739
|
+
workspaceMissing: !fs51.existsSync(a.workspacePath),
|
|
24171
24740
|
label: a.label,
|
|
24172
24741
|
enabled: a.enabled,
|
|
24173
24742
|
accessLevel: a.accessLevel,
|
|
@@ -24187,9 +24756,9 @@ function createSlackRoutes(deps) {
|
|
|
24187
24756
|
return c.json({ error: "appToken must start with xapp-" }, 400);
|
|
24188
24757
|
if (!body.workspacePath)
|
|
24189
24758
|
return c.json({ error: "workspacePath required" }, 400);
|
|
24190
|
-
if (!
|
|
24759
|
+
if (!path58.isAbsolute(body.workspacePath))
|
|
24191
24760
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
24192
|
-
if (!
|
|
24761
|
+
if (!fs51.existsSync(body.workspacePath))
|
|
24193
24762
|
return c.json({ error: "workspace path not found" }, 400);
|
|
24194
24763
|
ensureWorkspaceHalo(body.workspacePath);
|
|
24195
24764
|
let identity;
|
|
@@ -24249,7 +24818,7 @@ function createSlackRoutes(deps) {
|
|
|
24249
24818
|
if (body.enabled !== void 0)
|
|
24250
24819
|
patch.enabled = body.enabled ? 1 : 0;
|
|
24251
24820
|
if (body.workspacePath) {
|
|
24252
|
-
if (!
|
|
24821
|
+
if (!fs51.existsSync(body.workspacePath))
|
|
24253
24822
|
return c.json({ error: "workspace path not found" }, 400);
|
|
24254
24823
|
ensureWorkspaceHalo(body.workspacePath);
|
|
24255
24824
|
patch.workspacePath = body.workspacePath;
|
|
@@ -24663,10 +25232,10 @@ async function replyMessage(args) {
|
|
|
24663
25232
|
return { message_id: parsed.data?.message_id ?? "" };
|
|
24664
25233
|
}
|
|
24665
25234
|
async function uploadImage(args) {
|
|
24666
|
-
const
|
|
24667
|
-
const
|
|
24668
|
-
const buf =
|
|
24669
|
-
const filename =
|
|
25235
|
+
const fs55 = await import("node:fs");
|
|
25236
|
+
const path63 = await import("node:path");
|
|
25237
|
+
const buf = fs55.readFileSync(args.filePath);
|
|
25238
|
+
const filename = path63.basename(args.filePath);
|
|
24670
25239
|
const send = async (token2) => {
|
|
24671
25240
|
const form = new FormData();
|
|
24672
25241
|
form.append("image_type", "message");
|
|
@@ -24697,10 +25266,10 @@ async function uploadImage(args) {
|
|
|
24697
25266
|
return { imageKey: parsed.data.image_key };
|
|
24698
25267
|
}
|
|
24699
25268
|
async function uploadFile2(args) {
|
|
24700
|
-
const
|
|
24701
|
-
const
|
|
24702
|
-
const buf =
|
|
24703
|
-
const filename = args.filename ??
|
|
25269
|
+
const fs55 = await import("node:fs");
|
|
25270
|
+
const path63 = await import("node:path");
|
|
25271
|
+
const buf = fs55.readFileSync(args.filePath);
|
|
25272
|
+
const filename = args.filename ?? path63.basename(args.filePath);
|
|
24704
25273
|
const send = async (token2) => {
|
|
24705
25274
|
const form = new FormData();
|
|
24706
25275
|
form.append("file_type", args.fileType);
|
|
@@ -24806,12 +25375,12 @@ var init_api3 = __esm({
|
|
|
24806
25375
|
});
|
|
24807
25376
|
|
|
24808
25377
|
// packages/server/dist/channels/feishu/handler.js
|
|
24809
|
-
import
|
|
25378
|
+
import path59 from "node:path";
|
|
24810
25379
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
24811
25380
|
function isPathAllowed3(filePath, workspacePath) {
|
|
24812
|
-
const resolved =
|
|
24813
|
-
const wsResolved =
|
|
24814
|
-
return resolved.startsWith(wsResolved +
|
|
25381
|
+
const resolved = path59.resolve(filePath);
|
|
25382
|
+
const wsResolved = path59.resolve(workspacePath);
|
|
25383
|
+
return resolved.startsWith(wsResolved + path59.sep) || resolved === wsResolved || isInTempDir(resolved);
|
|
24815
25384
|
}
|
|
24816
25385
|
function newAccountState2() {
|
|
24817
25386
|
return {
|
|
@@ -25053,7 +25622,7 @@ async function handleInbound3(args) {
|
|
|
25053
25622
|
await replyToInbound({ account, inboundMessageId, isP2P, chatId, text: chunk });
|
|
25054
25623
|
},
|
|
25055
25624
|
sendMedia: async (filePath) => {
|
|
25056
|
-
const resolved =
|
|
25625
|
+
const resolved = path59.resolve(filePath);
|
|
25057
25626
|
if (!isPathAllowed3(resolved, workspace)) {
|
|
25058
25627
|
console.log(`[feishu] sendMedia blocked: ${filePath} not under workspace`);
|
|
25059
25628
|
return;
|
|
@@ -25073,7 +25642,7 @@ async function handleInbound3(args) {
|
|
|
25073
25642
|
inboundMessageId,
|
|
25074
25643
|
isP2P,
|
|
25075
25644
|
chatId,
|
|
25076
|
-
text: `\u26A0\uFE0F \u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF1A${
|
|
25645
|
+
text: `\u26A0\uFE0F \u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF1A${path59.basename(filePath)} \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
25077
25646
|
}).catch(() => {
|
|
25078
25647
|
});
|
|
25079
25648
|
}
|
|
@@ -25103,7 +25672,7 @@ async function replyToInbound(args) {
|
|
|
25103
25672
|
async function sendFeishuMedia(args) {
|
|
25104
25673
|
const { account, inboundMessageId, isP2P, chatId, filePath } = args;
|
|
25105
25674
|
const cls = classifyMedia(filePath);
|
|
25106
|
-
const ext =
|
|
25675
|
+
const ext = path59.extname(filePath).toLowerCase();
|
|
25107
25676
|
if (cls === "image") {
|
|
25108
25677
|
const { imageKey } = await uploadImage({ appId: account.appId, appSecret: account.appSecret, filePath });
|
|
25109
25678
|
await replyToInboundRaw({
|
|
@@ -25241,12 +25810,12 @@ var init_handler6 = __esm({
|
|
|
25241
25810
|
});
|
|
25242
25811
|
|
|
25243
25812
|
// packages/server/dist/routes/feishu.js
|
|
25244
|
-
import { Hono as
|
|
25245
|
-
import
|
|
25246
|
-
import
|
|
25813
|
+
import { Hono as Hono17 } from "hono";
|
|
25814
|
+
import fs52 from "node:fs";
|
|
25815
|
+
import path60 from "node:path";
|
|
25247
25816
|
function createFeishuRoutes(deps) {
|
|
25248
25817
|
const { db, channel } = deps;
|
|
25249
|
-
const app2 = new
|
|
25818
|
+
const app2 = new Hono17();
|
|
25250
25819
|
app2.get("/feishu/accounts/:id/search", async (c) => {
|
|
25251
25820
|
const id = c.req.param("id");
|
|
25252
25821
|
const q = c.req.query("q") ?? "";
|
|
@@ -25267,7 +25836,7 @@ function createFeishuRoutes(deps) {
|
|
|
25267
25836
|
botOpenId: a.botOpenId,
|
|
25268
25837
|
hasEncryptKey: !!a.encryptKey,
|
|
25269
25838
|
workspacePath: a.workspacePath,
|
|
25270
|
-
workspaceMissing: !
|
|
25839
|
+
workspaceMissing: !fs52.existsSync(a.workspacePath),
|
|
25271
25840
|
label: a.label,
|
|
25272
25841
|
enabled: a.enabled,
|
|
25273
25842
|
accessLevel: a.accessLevel,
|
|
@@ -25285,9 +25854,9 @@ function createFeishuRoutes(deps) {
|
|
|
25285
25854
|
return c.json({ error: "appSecret required" }, 400);
|
|
25286
25855
|
if (!body.workspacePath)
|
|
25287
25856
|
return c.json({ error: "workspacePath required" }, 400);
|
|
25288
|
-
if (!
|
|
25857
|
+
if (!path60.isAbsolute(body.workspacePath))
|
|
25289
25858
|
return c.json({ error: "workspacePath must be absolute" }, 400);
|
|
25290
|
-
if (!
|
|
25859
|
+
if (!fs52.existsSync(body.workspacePath))
|
|
25291
25860
|
return c.json({ error: "workspace path not found" }, 400);
|
|
25292
25861
|
ensureWorkspaceHalo(body.workspacePath);
|
|
25293
25862
|
let botOpenId;
|
|
@@ -25351,7 +25920,7 @@ function createFeishuRoutes(deps) {
|
|
|
25351
25920
|
if (body.enabled !== void 0)
|
|
25352
25921
|
patch.enabled = body.enabled ? 1 : 0;
|
|
25353
25922
|
if (body.workspacePath) {
|
|
25354
|
-
if (!
|
|
25923
|
+
if (!fs52.existsSync(body.workspacePath))
|
|
25355
25924
|
return c.json({ error: "workspace path not found" }, 400);
|
|
25356
25925
|
ensureWorkspaceHalo(body.workspacePath);
|
|
25357
25926
|
patch.workspacePath = body.workspacePath;
|
|
@@ -25480,7 +26049,7 @@ var init_descriptors = __esm({
|
|
|
25480
26049
|
});
|
|
25481
26050
|
|
|
25482
26051
|
// packages/server/dist/middleware/auth.js
|
|
25483
|
-
import { Hono as
|
|
26052
|
+
import { Hono as Hono18 } from "hono";
|
|
25484
26053
|
import { getCookie, setCookie } from "hono/cookie";
|
|
25485
26054
|
import crypto5 from "node:crypto";
|
|
25486
26055
|
function passwordHash() {
|
|
@@ -25581,7 +26150,7 @@ function setTokenCookie(c, token) {
|
|
|
25581
26150
|
});
|
|
25582
26151
|
}
|
|
25583
26152
|
function createAuthRoutes() {
|
|
25584
|
-
const app2 = new
|
|
26153
|
+
const app2 = new Hono18();
|
|
25585
26154
|
app2.post("/auth/login", async (c) => {
|
|
25586
26155
|
const ip = getClientIp(c);
|
|
25587
26156
|
if (isLockedOut2(ip)) {
|
|
@@ -25623,14 +26192,14 @@ function createAuthRoutes() {
|
|
|
25623
26192
|
}
|
|
25624
26193
|
function authMiddleware() {
|
|
25625
26194
|
return async (c, next) => {
|
|
25626
|
-
const
|
|
25627
|
-
if (PUBLIC_PATHS.some((p) =>
|
|
26195
|
+
const path63 = c.req.path;
|
|
26196
|
+
if (PUBLIC_PATHS.some((p) => path63 === p || path63.startsWith(p + "?"))) {
|
|
25628
26197
|
return next();
|
|
25629
26198
|
}
|
|
25630
26199
|
const token = getCookie(c, COOKIE_NAME);
|
|
25631
26200
|
const payload = validateToken(token);
|
|
25632
26201
|
if (!payload) {
|
|
25633
|
-
if (!
|
|
26202
|
+
if (!path63.startsWith("/api/")) {
|
|
25634
26203
|
return next();
|
|
25635
26204
|
}
|
|
25636
26205
|
return c.json({ error: "Unauthorized" }, 401);
|
|
@@ -25662,22 +26231,22 @@ var init_auth = __esm({
|
|
|
25662
26231
|
|
|
25663
26232
|
// packages/server/dist/index.js
|
|
25664
26233
|
var dist_exports = {};
|
|
25665
|
-
import { Hono as
|
|
26234
|
+
import { Hono as Hono19 } from "hono";
|
|
25666
26235
|
import { cors } from "hono/cors";
|
|
25667
26236
|
import { serve } from "@hono/node-server";
|
|
25668
26237
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
25669
26238
|
import { WebSocketServer } from "ws";
|
|
25670
|
-
import
|
|
25671
|
-
import
|
|
26239
|
+
import path61 from "node:path";
|
|
26240
|
+
import fs53 from "node:fs";
|
|
25672
26241
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
25673
|
-
import { homedir as
|
|
26242
|
+
import { homedir as homedir24 } from "node:os";
|
|
25674
26243
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
25675
26244
|
function findProjectRoot() {
|
|
25676
26245
|
let dir = process.cwd();
|
|
25677
26246
|
for (let i = 0; i < 32; i++) {
|
|
25678
|
-
if (
|
|
26247
|
+
if (fs53.existsSync(path61.join(dir, "pnpm-workspace.yaml")))
|
|
25679
26248
|
return dir;
|
|
25680
|
-
const parent =
|
|
26249
|
+
const parent = path61.dirname(dir);
|
|
25681
26250
|
if (parent === dir)
|
|
25682
26251
|
break;
|
|
25683
26252
|
dir = parent;
|
|
@@ -25686,15 +26255,15 @@ function findProjectRoot() {
|
|
|
25686
26255
|
return process.cwd();
|
|
25687
26256
|
}
|
|
25688
26257
|
function acquireSingleInstanceLock(lockFile) {
|
|
25689
|
-
|
|
25690
|
-
const fd =
|
|
26258
|
+
fs53.mkdirSync(path61.dirname(lockFile), { recursive: true });
|
|
26259
|
+
const fd = fs53.openSync(lockFile, fs53.constants.O_RDWR | fs53.constants.O_CREAT, 420);
|
|
25691
26260
|
const result = spawnSync4("flock", ["-n", "3"], {
|
|
25692
26261
|
stdio: ["ignore", "ignore", "ignore", fd]
|
|
25693
26262
|
});
|
|
25694
26263
|
if (result.error) {
|
|
25695
26264
|
const content = readLockPid(fd);
|
|
25696
26265
|
if (content > 0 && isProcessAlive(content)) {
|
|
25697
|
-
|
|
26266
|
+
fs53.closeSync(fd);
|
|
25698
26267
|
console.error(`[Server] another halo server is already running (pid ${content}). kill it first.`);
|
|
25699
26268
|
process.exit(1);
|
|
25700
26269
|
}
|
|
@@ -25703,19 +26272,19 @@ function acquireSingleInstanceLock(lockFile) {
|
|
|
25703
26272
|
if (content > 0 && !isProcessAlive(content)) {
|
|
25704
26273
|
console.warn(`[Server] removing stale server.lock (pid ${content} not running)`);
|
|
25705
26274
|
} else {
|
|
25706
|
-
|
|
26275
|
+
fs53.closeSync(fd);
|
|
25707
26276
|
console.error(`[Server] another halo server is already running${content > 0 ? ` (pid ${content})` : ""}. kill it first.`);
|
|
25708
26277
|
process.exit(1);
|
|
25709
26278
|
}
|
|
25710
26279
|
}
|
|
25711
|
-
|
|
25712
|
-
|
|
25713
|
-
|
|
26280
|
+
fs53.ftruncateSync(fd);
|
|
26281
|
+
fs53.writeSync(fd, String(process.pid));
|
|
26282
|
+
fs53.fsyncSync(fd);
|
|
25714
26283
|
}
|
|
25715
26284
|
function readLockPid(fd) {
|
|
25716
26285
|
try {
|
|
25717
26286
|
const buf = Buffer.alloc(32);
|
|
25718
|
-
const n =
|
|
26287
|
+
const n = fs53.readSync(fd, buf, 0, 32, 0);
|
|
25719
26288
|
return parseInt(buf.subarray(0, n).toString("utf-8").trim(), 10) || 0;
|
|
25720
26289
|
} catch {
|
|
25721
26290
|
return 0;
|
|
@@ -25744,14 +26313,14 @@ function isProcessAlive(pid) {
|
|
|
25744
26313
|
}
|
|
25745
26314
|
function resolveFrontendDir() {
|
|
25746
26315
|
if (process.env.HALO_FRONTEND_DIR)
|
|
25747
|
-
return
|
|
25748
|
-
const bundled =
|
|
25749
|
-
if (
|
|
26316
|
+
return path61.resolve(process.env.HALO_FRONTEND_DIR);
|
|
26317
|
+
const bundled = path61.resolve(__dirname3, "..", "admin-out");
|
|
26318
|
+
if (fs53.existsSync(path61.join(bundled, "index.html")))
|
|
25750
26319
|
return bundled;
|
|
25751
|
-
const sibling =
|
|
25752
|
-
if (
|
|
26320
|
+
const sibling = path61.resolve(__dirname3, "..", "..", "admin", "out");
|
|
26321
|
+
if (fs53.existsSync(path61.join(sibling, "index.html")))
|
|
25753
26322
|
return sibling;
|
|
25754
|
-
return
|
|
26323
|
+
return path61.resolve(PROJECT_ROOT, "packages", "admin", "out");
|
|
25755
26324
|
}
|
|
25756
26325
|
async function gracefulShutdown(signal) {
|
|
25757
26326
|
console.log(`[Server] Received ${signal}, shutting down gracefully...`);
|
|
@@ -25768,7 +26337,7 @@ async function gracefulShutdown(signal) {
|
|
|
25768
26337
|
console.log("[Server] Shutdown complete");
|
|
25769
26338
|
process.exit(0);
|
|
25770
26339
|
}
|
|
25771
|
-
var __dirname3, PORT, HALO_VERSION, PROJECT_ROOT, HALO_HOME4, app, allowlist, authRoutes, fileRoutes, gitRoutes, agentConfigRoutes, skillRoutes, settingsRoutes, evolutionRoutes, cronRoutes, channelDb, registry, sessionRoutes, showRoutes, metricsRoutes, commandRoutes, FRONTEND_DIR, server, wss;
|
|
26340
|
+
var __dirname3, PORT, HALO_VERSION, PROJECT_ROOT, HALO_HOME4, AGENTCORE, app, allowlist, authRoutes, fileRoutes, gitRoutes, agentConfigRoutes, skillRoutes, settingsRoutes, evolutionRoutes, cronRoutes, channelDb, registry, sessionRoutes, showRoutes, metricsRoutes, commandRoutes, FRONTEND_DIR, server, wss;
|
|
25772
26341
|
var init_dist2 = __esm({
|
|
25773
26342
|
async "packages/server/dist/index.js"() {
|
|
25774
26343
|
"use strict";
|
|
@@ -25782,6 +26351,7 @@ var init_dist2 = __esm({
|
|
|
25782
26351
|
init_show();
|
|
25783
26352
|
init_metrics();
|
|
25784
26353
|
init_commands3();
|
|
26354
|
+
init_agentcore();
|
|
25785
26355
|
init_commands();
|
|
25786
26356
|
init_commands2();
|
|
25787
26357
|
init_handler();
|
|
@@ -25803,12 +26373,12 @@ var init_dist2 = __esm({
|
|
|
25803
26373
|
init_sandbox();
|
|
25804
26374
|
init_init();
|
|
25805
26375
|
init_git_ssh();
|
|
25806
|
-
__dirname3 =
|
|
26376
|
+
__dirname3 = path61.dirname(fileURLToPath3(import.meta.url));
|
|
25807
26377
|
PORT = config.server.port;
|
|
25808
|
-
HALO_VERSION = "0.2.
|
|
26378
|
+
HALO_VERSION = "0.2.2";
|
|
25809
26379
|
PROJECT_ROOT = findProjectRoot();
|
|
25810
|
-
HALO_HOME4 =
|
|
25811
|
-
if (!
|
|
26380
|
+
HALO_HOME4 = path61.join(homedir24(), ".halo");
|
|
26381
|
+
if (!fs53.existsSync(path61.join(HALO_HOME4, "global", ".template-version"))) {
|
|
25812
26382
|
process.stderr.write("\x1B[31m[Server] ~/.halo/global/ not initialized. Run `halo setup` first.\x1B[0m\n");
|
|
25813
26383
|
process.exit(1);
|
|
25814
26384
|
}
|
|
@@ -25823,11 +26393,13 @@ var init_dist2 = __esm({
|
|
|
25823
26393
|
}
|
|
25824
26394
|
}
|
|
25825
26395
|
}
|
|
25826
|
-
|
|
26396
|
+
AGENTCORE = config.server.runtimeMode === "agentcore";
|
|
26397
|
+
if (!AGENTCORE && (!config.server.password && !config.server.passwordEnvPlaintext || !config.server.jwtSecret)) {
|
|
25827
26398
|
process.stderr.write("\x1B[31m[Server] admin password not configured. Run `halo setup` to set one, or set the HALO_PASSWORD env.\x1B[0m\n");
|
|
25828
26399
|
process.exit(1);
|
|
25829
26400
|
}
|
|
25830
|
-
|
|
26401
|
+
if (!AGENTCORE)
|
|
26402
|
+
acquireSingleInstanceLock(path61.join(HALO_HOME4, "global", "server.lock"));
|
|
25831
26403
|
{
|
|
25832
26404
|
const declared = new Set(commandRegistry.listDescriptors().filter((d) => d.source === "builtin").map((d) => d.slashName));
|
|
25833
26405
|
const dispatched = new Set(DISPATCH_COMMANDS);
|
|
@@ -25852,7 +26424,7 @@ var init_dist2 = __esm({
|
|
|
25852
26424
|
ensureSshAgent();
|
|
25853
26425
|
console.log(`[Server] Halo home: ${HALO_HOME4}`);
|
|
25854
26426
|
console.log("[Server] Services initialized (ModelRuntime)");
|
|
25855
|
-
app = new
|
|
26427
|
+
app = new Hono19();
|
|
25856
26428
|
allowlist = config.server.corsOrigins;
|
|
25857
26429
|
app.use("/*", cors({
|
|
25858
26430
|
origin: allowlist.length > 0 ? (origin) => origin && allowlist.includes(origin) ? origin : null : (origin) => origin ?? "*",
|
|
@@ -25892,13 +26464,15 @@ var init_dist2 = __esm({
|
|
|
25892
26464
|
app.route("/api", evolutionRoutes);
|
|
25893
26465
|
cronRoutes = createCronRoutes();
|
|
25894
26466
|
app.route("/api", cronRoutes);
|
|
25895
|
-
channelDb = createChannelDb(
|
|
26467
|
+
channelDb = createChannelDb(path61.join(HALO_HOME4, "secrets"));
|
|
25896
26468
|
setChannelDb(channelDb);
|
|
25897
|
-
setEvoDb(createEvoDb(
|
|
25898
|
-
setCronDb(createCronDb(
|
|
26469
|
+
setEvoDb(createEvoDb(path61.join(HALO_HOME4, "global")));
|
|
26470
|
+
setCronDb(createCronDb(path61.join(HALO_HOME4, "global")));
|
|
25899
26471
|
setEvoSpawner(realEvoSpawner);
|
|
25900
|
-
|
|
25901
|
-
|
|
26472
|
+
if (!AGENTCORE) {
|
|
26473
|
+
startEvoTicker();
|
|
26474
|
+
startArchiveDaemon();
|
|
26475
|
+
}
|
|
25902
26476
|
registry = new SessionManagerRegistry({ reconcileOrphansOnBoot: true });
|
|
25903
26477
|
sessionRoutes = createSessionRoutes(registry);
|
|
25904
26478
|
app.route("/api", sessionRoutes);
|
|
@@ -25908,14 +26482,20 @@ var init_dist2 = __esm({
|
|
|
25908
26482
|
app.route("/api", metricsRoutes);
|
|
25909
26483
|
commandRoutes = createCommandRoutes(commandRegistry, registry);
|
|
25910
26484
|
app.route("/api", commandRoutes);
|
|
25911
|
-
|
|
25912
|
-
|
|
26485
|
+
if (!AGENTCORE) {
|
|
26486
|
+
bootChannels(app, defaultChannelDescriptors, { registry, db: channelDb });
|
|
26487
|
+
startCronDaemon();
|
|
26488
|
+
} else {
|
|
26489
|
+
const agentcoreRoutes = createAgentCoreRoutes({ registry, workspace: config.server.agentcoreWorkspace });
|
|
26490
|
+
app.route("/", agentcoreRoutes);
|
|
26491
|
+
console.log(`[Server] AgentCore runtime mode \u2014 workspace: ${config.server.agentcoreWorkspace}`);
|
|
26492
|
+
}
|
|
25913
26493
|
FRONTEND_DIR = resolveFrontendDir();
|
|
25914
|
-
app.use("/*", serveStatic({ root:
|
|
26494
|
+
app.use("/*", serveStatic({ root: path61.relative(process.cwd(), FRONTEND_DIR) }));
|
|
25915
26495
|
app.get("/*", (c) => {
|
|
25916
|
-
const indexPath =
|
|
26496
|
+
const indexPath = path61.join(FRONTEND_DIR, "index.html");
|
|
25917
26497
|
try {
|
|
25918
|
-
const html =
|
|
26498
|
+
const html = fs53.readFileSync(indexPath, "utf-8");
|
|
25919
26499
|
return c.html(html);
|
|
25920
26500
|
} catch {
|
|
25921
26501
|
return c.text("Frontend not built. Run: cd packages/admin && npx next build", 503);
|
|
@@ -25934,7 +26514,9 @@ var init_dist2 = __esm({
|
|
|
25934
26514
|
wss = new WebSocketServer({
|
|
25935
26515
|
server,
|
|
25936
26516
|
path: "/ws",
|
|
25937
|
-
|
|
26517
|
+
// AgentCore terminates auth upstream (SigV4/OAuth) before the connection
|
|
26518
|
+
// reaches the container, so its /ws is open; normal mode keeps cookie auth.
|
|
26519
|
+
verifyClient: AGENTCORE ? void 0 : (info, callback) => {
|
|
25938
26520
|
const token = getTokenFromCookieHeader(info.req.headers.cookie);
|
|
25939
26521
|
if (isAuthenticated(token)) {
|
|
25940
26522
|
callback(true);
|
|
@@ -25943,8 +26525,12 @@ var init_dist2 = __esm({
|
|
|
25943
26525
|
}
|
|
25944
26526
|
}
|
|
25945
26527
|
});
|
|
25946
|
-
|
|
25947
|
-
|
|
26528
|
+
if (AGENTCORE) {
|
|
26529
|
+
setupAgentCoreWebSocket({ wss, registry, workspace: config.server.agentcoreWorkspace });
|
|
26530
|
+
} else {
|
|
26531
|
+
setupWebSocketHandler({ wss, registry });
|
|
26532
|
+
setBroadcastWss(wss);
|
|
26533
|
+
}
|
|
25948
26534
|
console.log(`[Server] WebSocket server ready on ws://localhost:${PORT}/ws`);
|
|
25949
26535
|
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
25950
26536
|
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
@@ -26678,11 +27264,11 @@ process.emitWarning = (warning, ...args) => {
|
|
|
26678
27264
|
};
|
|
26679
27265
|
|
|
26680
27266
|
// packages/cli/src/index.ts
|
|
26681
|
-
import
|
|
26682
|
-
import
|
|
26683
|
-
import { homedir as
|
|
27267
|
+
import path62 from "node:path";
|
|
27268
|
+
import fs54 from "node:fs";
|
|
27269
|
+
import { homedir as homedir25 } from "node:os";
|
|
26684
27270
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
26685
|
-
var VERSION = "0.2.
|
|
27271
|
+
var VERSION = "0.2.2";
|
|
26686
27272
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
26687
27273
|
|
|
26688
27274
|
Commands:
|
|
@@ -26918,11 +27504,19 @@ async function cmdSetup(argv = []) {
|
|
|
26918
27504
|
const { hashPassword: hashPassword2, generateJwtSecret: generateJwtSecret2 } = await Promise.resolve().then(() => (init_password_hash(), password_hash_exports));
|
|
26919
27505
|
const { updateConfigLeaves: updateConfigLeaves2, configLeafSet: configLeafSet2, readConfigLeaf: readConfigLeaf2 } = await Promise.resolve().then(() => (init_setup_config(), setup_config_exports));
|
|
26920
27506
|
const { readSetting: readSetting2, writeSetting: writeSetting2, maskSecret: maskSecret2 } = await Promise.resolve().then(() => (init_setup_settings(), setup_settings_exports));
|
|
26921
|
-
const {
|
|
27507
|
+
const {
|
|
27508
|
+
listModelProviders: listModelProviders2,
|
|
27509
|
+
listOptionalSkills: listOptionalSkills2,
|
|
27510
|
+
listRequiredSkillsWithSecrets: listRequiredSkillsWithSecrets2,
|
|
27511
|
+
listConfiguredSwitchableProviders: listConfiguredSwitchableProviders2,
|
|
27512
|
+
readDefaultAgentProvider: readDefaultAgentProvider2,
|
|
27513
|
+
bindBuiltinAgentsToProvider: bindBuiltinAgentsToProvider2
|
|
27514
|
+
} = await Promise.resolve().then(() => (init_setup_providers(), setup_providers_exports));
|
|
27515
|
+
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));
|
|
26922
27516
|
const { promptText: promptText2, promptPassword: promptPassword2, promptSelect: promptSelect2, promptMultiSelect: promptMultiSelect2 } = await Promise.resolve().then(() => (init_setup_prompts(), setup_prompts_exports));
|
|
26923
27517
|
const nonInteractive = argv.includes("--non-interactive") || argv.includes("-y");
|
|
26924
|
-
const haloHome2 =
|
|
26925
|
-
const globalDir2 =
|
|
27518
|
+
const haloHome2 = path62.join(homedir25(), ".halo");
|
|
27519
|
+
const globalDir2 = path62.join(haloHome2, "global");
|
|
26926
27520
|
ensureHaloHome2(haloHome2);
|
|
26927
27521
|
process.stderr.write(`[setup] ${haloHome2}/ ready
|
|
26928
27522
|
`);
|
|
@@ -26935,6 +27529,17 @@ async function cmdSetup(argv = []) {
|
|
|
26935
27529
|
if (!process.env.HALO_PASSWORD || process.env.HALO_PASSWORD.length === 0) {
|
|
26936
27530
|
if (!configLeafSet2("server.password")) {
|
|
26937
27531
|
process.stderr.write(`[setup] WARNING: no password set. Either set HALO_PASSWORD env, or re-run \`halo setup\` interactively.
|
|
27532
|
+
`);
|
|
27533
|
+
}
|
|
27534
|
+
}
|
|
27535
|
+
const envProvider = process.env.HALO_DEFAULT_PROVIDER;
|
|
27536
|
+
if (envProvider && envProvider.length > 0) {
|
|
27537
|
+
const bound = bindBuiltinAgentsToProvider2(envProvider);
|
|
27538
|
+
if (bound) {
|
|
27539
|
+
process.stderr.write(`[setup] built-in agents (${bound.agents.join(", ")}) bound to ${envProvider}${bound.modelId ? ` / ${bound.modelId}` : ""} (HALO_DEFAULT_PROVIDER)
|
|
27540
|
+
`);
|
|
27541
|
+
} else {
|
|
27542
|
+
process.stderr.write(`[setup] WARNING: HALO_DEFAULT_PROVIDER=${envProvider} is not a known provider \u2014 agents unchanged
|
|
26938
27543
|
`);
|
|
26939
27544
|
}
|
|
26940
27545
|
}
|
|
@@ -26946,12 +27551,22 @@ async function cmdSetup(argv = []) {
|
|
|
26946
27551
|
await stepPassword();
|
|
26947
27552
|
await stepPort();
|
|
26948
27553
|
await stepModelProviders();
|
|
27554
|
+
await stepBindDefaultAgent();
|
|
26949
27555
|
await stepOptionalSkills();
|
|
26950
27556
|
await stepRequiredSkillSecrets();
|
|
26951
27557
|
if (!configLeafSet2("server.jwt_secret")) {
|
|
26952
27558
|
updateConfigLeaves2({ "server.jwt_secret": generateJwtSecret2() });
|
|
26953
27559
|
}
|
|
26954
27560
|
process.stderr.write("\n[setup] done\n");
|
|
27561
|
+
{
|
|
27562
|
+
const portLeaf = readConfigLeaf2("server.port");
|
|
27563
|
+
const port = typeof portLeaf === "number" ? portLeaf : 9527;
|
|
27564
|
+
process.stderr.write("\nNext steps:\n");
|
|
27565
|
+
process.stderr.write(" 1. halo server start\n");
|
|
27566
|
+
process.stderr.write(` 2. Open http://localhost:${port} and log in with the password you just set
|
|
27567
|
+
`);
|
|
27568
|
+
process.stderr.write(" 3. Agents, model providers, and channels (Telegram / Slack / \u2026) are all configurable in the admin UI\n");
|
|
27569
|
+
}
|
|
26955
27570
|
async function stepPassword() {
|
|
26956
27571
|
const passwordSet = configLeafSet2("server.password");
|
|
26957
27572
|
const envSet = process.env.HALO_PASSWORD ? process.env.HALO_PASSWORD.length > 0 : false;
|
|
@@ -27050,6 +27665,36 @@ async function cmdSetup(argv = []) {
|
|
|
27050
27665
|
}
|
|
27051
27666
|
process.stderr.write("\n");
|
|
27052
27667
|
}
|
|
27668
|
+
async function stepBindDefaultAgent() {
|
|
27669
|
+
const configured = listConfiguredSwitchableProviders2();
|
|
27670
|
+
const current = readDefaultAgentProvider2();
|
|
27671
|
+
const currentHasKeys = current != null && listModelProviders2().some((p) => p.id === current && p.fields.some((f) => {
|
|
27672
|
+
const v = readSetting2(`${p.id}.secrets.${f.key}`);
|
|
27673
|
+
return v != null && v.length > 0;
|
|
27674
|
+
}));
|
|
27675
|
+
const plan = buildBindProviderPlan2(configured, current, currentHasKeys);
|
|
27676
|
+
if (!plan) return;
|
|
27677
|
+
for (const line of plan.intro) process.stderr.write(`${line}
|
|
27678
|
+
`);
|
|
27679
|
+
const pick = await promptSelect2(plan.question, plan.options, plan.initialIndex);
|
|
27680
|
+
if (pick == null) abort();
|
|
27681
|
+
if (pick === KEEP_CURRENT2) {
|
|
27682
|
+
process.stderr.write(`[setup] default agent unchanged (${current ?? AWS_CHAIN_PROVIDER2})
|
|
27683
|
+
|
|
27684
|
+
`);
|
|
27685
|
+
return;
|
|
27686
|
+
}
|
|
27687
|
+
const bound = bindBuiltinAgentsToProvider2(pick);
|
|
27688
|
+
if (bound) {
|
|
27689
|
+
process.stderr.write(`[setup] built-in agents (${bound.agents.join(", ")}) bound to ${pick}${bound.modelId ? ` / ${bound.modelId}` : ""}
|
|
27690
|
+
|
|
27691
|
+
`);
|
|
27692
|
+
} else {
|
|
27693
|
+
process.stderr.write(`[setup] could not bind agents to ${pick} \u2014 provider template missing; agents unchanged
|
|
27694
|
+
|
|
27695
|
+
`);
|
|
27696
|
+
}
|
|
27697
|
+
}
|
|
27053
27698
|
async function stepOptionalSkills() {
|
|
27054
27699
|
const skills = listOptionalSkills2();
|
|
27055
27700
|
if (skills.length === 0) return;
|
|
@@ -27215,7 +27860,7 @@ halo: upgrade failed (npm exited ${install.status}). ${hint}
|
|
|
27215
27860
|
async function cmdAgents(flags) {
|
|
27216
27861
|
const { initRuntime: initRuntime2, listAgents: listAgents2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
27217
27862
|
await initRuntime2();
|
|
27218
|
-
const workspace =
|
|
27863
|
+
const workspace = path62.resolve(flags.workspace ?? process.cwd());
|
|
27219
27864
|
const agents = await listAgents2(workspace);
|
|
27220
27865
|
if (agents.length === 0) {
|
|
27221
27866
|
process.stderr.write("No agents found.\n");
|
|
@@ -27231,7 +27876,7 @@ async function cmdAgents(flags) {
|
|
|
27231
27876
|
async function cmdSessions(flags) {
|
|
27232
27877
|
const { initRuntime: initRuntime2, listSessions: listSessions2 } = await Promise.resolve().then(() => (init_harness(), harness_exports));
|
|
27233
27878
|
await initRuntime2();
|
|
27234
|
-
const workspace =
|
|
27879
|
+
const workspace = path62.resolve(flags.workspace ?? process.cwd());
|
|
27235
27880
|
const sessions2 = listSessions2(workspace);
|
|
27236
27881
|
if (sessions2.length === 0) {
|
|
27237
27882
|
process.stderr.write("No sessions found.\n");
|
|
@@ -27333,11 +27978,11 @@ ${stdinContent}` : stdinContent;
|
|
|
27333
27978
|
harness.destroy();
|
|
27334
27979
|
}
|
|
27335
27980
|
}
|
|
27336
|
-
var SERVER_LOCK_PATH =
|
|
27337
|
-
var SERVER_LOG_PATH =
|
|
27981
|
+
var SERVER_LOCK_PATH = path62.join(homedir25(), ".halo", "global", "server.lock");
|
|
27982
|
+
var SERVER_LOG_PATH = path62.join(homedir25(), ".halo", "logs", "server.log");
|
|
27338
27983
|
function readServerPid() {
|
|
27339
27984
|
try {
|
|
27340
|
-
const txt =
|
|
27985
|
+
const txt = fs54.readFileSync(SERVER_LOCK_PATH, "utf-8").trim();
|
|
27341
27986
|
const n = parseInt(txt, 10);
|
|
27342
27987
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
27343
27988
|
} catch {
|
|
@@ -27419,8 +28064,8 @@ async function cmdServerStart(argv, _unused) {
|
|
|
27419
28064
|
return;
|
|
27420
28065
|
}
|
|
27421
28066
|
const { spawn: spawn4 } = await import("node:child_process");
|
|
27422
|
-
|
|
27423
|
-
const out =
|
|
28067
|
+
fs54.mkdirSync(path62.dirname(SERVER_LOG_PATH), { recursive: true });
|
|
28068
|
+
const out = fs54.openSync(SERVER_LOG_PATH, "a");
|
|
27424
28069
|
const child = spawn4(process.argv[0], [process.argv[1], "server", "start"], {
|
|
27425
28070
|
detached: true,
|
|
27426
28071
|
stdio: ["ignore", out, out],
|
|
@@ -27520,7 +28165,7 @@ async function cmdServerLogs(argv) {
|
|
|
27520
28165
|
lines: { type: "string", short: "n" }
|
|
27521
28166
|
}
|
|
27522
28167
|
});
|
|
27523
|
-
if (!
|
|
28168
|
+
if (!fs54.existsSync(SERVER_LOG_PATH)) {
|
|
27524
28169
|
process.stderr.write(`No log file at ${SERVER_LOG_PATH}
|
|
27525
28170
|
`);
|
|
27526
28171
|
return;
|
|
@@ -27538,7 +28183,7 @@ function exitMissingSetup() {
|
|
|
27538
28183
|
process.exit(1);
|
|
27539
28184
|
}
|
|
27540
28185
|
function isHaloHomeReady() {
|
|
27541
|
-
return
|
|
28186
|
+
return fs54.existsSync(path62.join(homedir25(), ".halo", "global", ".template-version"));
|
|
27542
28187
|
}
|
|
27543
28188
|
async function main2() {
|
|
27544
28189
|
const argv = process.argv.slice(2);
|