@turmind/halo 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/989-71c38d29c6672b27.js +1 -0
- package/admin-out/_next/static/chunks/app/page-67334fcd2175c7ad.js +1 -0
- package/admin-out/_next/static/css/3467b854242306b8.css +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/agentcore.md +1 -1
- package/bundled-docs/design/architecture.md +2 -2
- package/bundled-docs/design/storage.md +1 -1
- package/bundled-docs/design/ws.md +7 -7
- package/bundled-docs/dev/env.md +1 -2
- package/bundled-docs/guide/workspace.md +6 -3
- package/bundled-docs/requirements/terminal.md +76 -44
- package/bundled-docs/requirements/workspace.md +10 -1
- package/dist/evo-wrapper.js +4 -5
- package/dist/index.js +176 -118
- package/package.json +3 -1
- package/templates/config.yaml +0 -6
- package/admin-out/_next/static/chunks/989-04766caa08c5f506.js +0 -1
- package/admin-out/_next/static/chunks/app/page-ee1d0dae346ba9d2.js +0 -1
- package/admin-out/_next/static/css/e395d2ae2e0636a0.css +0 -1
- /package/admin-out/_next/static/{a5z_n1eIm6T8T3Ty_MMHF → 0cRHmPZfENhrU7Hb0w3n_}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{a5z_n1eIm6T8T3Ty_MMHF → 0cRHmPZfENhrU7Hb0w3n_}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -1924,7 +1924,7 @@ var init_init = __esm({
|
|
|
1924
1924
|
path8.resolve(__dirname, "..", "bundled-docs"),
|
|
1925
1925
|
path8.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
1926
1926
|
];
|
|
1927
|
-
TEMPLATE_VERSION =
|
|
1927
|
+
TEMPLATE_VERSION = 112;
|
|
1928
1928
|
VERSION_FILE = ".template-version";
|
|
1929
1929
|
SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
|
|
1930
1930
|
BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
@@ -2653,7 +2653,7 @@ function generalSection() {
|
|
|
2653
2653
|
{ 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" },
|
|
2654
2654
|
{ 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) },
|
|
2655
2655
|
{ 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" },
|
|
2656
|
-
{ key: "limits.
|
|
2656
|
+
{ key: "limits.terminal_scrollback_lines", type: "int", globalOnly: true, description: "Scrollback lines the server keeps per terminal, restored to every page that attaches (reconnect, another browser or device). Applies to terminals opened after the change.", description_zh: "\u670D\u52A1\u7AEF\u4E3A\u6BCF\u4E2A\u7EC8\u7AEF\u4FDD\u7559\u7684\u56DE\u6EDA\u884C\u6570\uFF0C\u4EFB\u4F55\u9875\u9762\u63A5\u5165\uFF08\u91CD\u8FDE\u3001\u6362\u6D4F\u89C8\u5668\u6216\u8BBE\u5907\uFF09\u65F6\u6062\u590D\u3002\u4FEE\u6539\u540E\u5BF9\u65B0\u5F00\u7684\u7EC8\u7AEF\u751F\u6548\u3002", default: "5000" }
|
|
2657
2657
|
].map((f) => ({
|
|
2658
2658
|
...f,
|
|
2659
2659
|
kind: "param",
|
|
@@ -3308,8 +3308,7 @@ var init_config = __esm({
|
|
|
3308
3308
|
// fragments, SSE comments included), so for them it is an idle timeout — a
|
|
3309
3309
|
// long but live stream is never cut off.
|
|
3310
3310
|
modelRequest: systemInt("HALO_MODEL_TIMEOUT", "timeout.model_request", 10 * 6e4),
|
|
3311
|
-
sessionGrace: systemInt("HALO_SESSION_GRACE", "timeout.session_grace", 5 * 6e4)
|
|
3312
|
-
terminalGrace: systemInt("HALO_TERMINAL_GRACE", "timeout.terminal_grace", 5 * 6e4)
|
|
3311
|
+
sessionGrace: systemInt("HALO_SESSION_GRACE", "timeout.session_grace", 5 * 6e4)
|
|
3313
3312
|
},
|
|
3314
3313
|
// Runtime caps — read live from settings.yaml on every access. Getters
|
|
3315
3314
|
// (not literals) so a settings reload takes effect on the next tool
|
|
@@ -3334,8 +3333,8 @@ var init_config = __esm({
|
|
|
3334
3333
|
get autoReportMax() {
|
|
3335
3334
|
return settingsInt("general.limits.auto_report_chars", 8192);
|
|
3336
3335
|
},
|
|
3337
|
-
get
|
|
3338
|
-
return settingsInt("general.limits.
|
|
3336
|
+
get terminalScrollbackLines() {
|
|
3337
|
+
return settingsInt("general.limits.terminal_scrollback_lines", 5e3);
|
|
3339
3338
|
}
|
|
3340
3339
|
},
|
|
3341
3340
|
compact: {
|
|
@@ -9979,10 +9978,10 @@ function completeTask(taskId, s) {
|
|
|
9979
9978
|
if (!row || row.state !== "working")
|
|
9980
9979
|
return;
|
|
9981
9980
|
const level = s.accessLevel === null ? "full" : s.accessLevel ?? "readonly";
|
|
9982
|
-
const
|
|
9981
|
+
const attach2 = (text6) => attachResultFiles(text6, row.workspace, level);
|
|
9983
9982
|
if (s.turnError) {
|
|
9984
9983
|
const next = s.turnErrorKind === "account" ? "This is a model account / credential / balance / permission problem on the remote: re-sending will fail the same way until its model configuration is fixed." : "Send again on the same context to let it resume.";
|
|
9985
|
-
const partial = s.output ?
|
|
9984
|
+
const partial = s.output ? attach2(s.output) : null;
|
|
9986
9985
|
transition(taskId, "failed", {
|
|
9987
9986
|
statusText: `The last turn was terminated by an unrecoverable error, NOT completed. Error: ${s.turnError}. ${next}`,
|
|
9988
9987
|
result: partial?.text || null,
|
|
@@ -9991,7 +9990,7 @@ function completeTask(taskId, s) {
|
|
|
9991
9990
|
});
|
|
9992
9991
|
return;
|
|
9993
9992
|
}
|
|
9994
|
-
const done =
|
|
9993
|
+
const done = attach2(s.finalOutput || s.output || "");
|
|
9995
9994
|
transition(taskId, "completed", { result: done.text, resultFiles: done.files });
|
|
9996
9995
|
}
|
|
9997
9996
|
function pushConfigJson(r) {
|
|
@@ -16446,7 +16445,7 @@ async function initObservability(haloProcess = "server") {
|
|
|
16446
16445
|
return true;
|
|
16447
16446
|
}
|
|
16448
16447
|
const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
|
|
16449
|
-
providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "2.1.
|
|
16448
|
+
providers = await registerSdk2(process.env.OTEL_SERVICE_NAME, "2.1.1", haloProcess);
|
|
16450
16449
|
console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, halo.process=${haloProcess}, capture_content=${config.observability.captureContent})`);
|
|
16451
16450
|
return true;
|
|
16452
16451
|
}
|
|
@@ -23217,7 +23216,7 @@ function isUnwatchablePath(absPath) {
|
|
|
23217
23216
|
}
|
|
23218
23217
|
return null;
|
|
23219
23218
|
}
|
|
23220
|
-
var IGNORED_SEGMENTS, IS_WIN32, IGNORED_SEGMENT_SET, NATIVE_IGNORE, nativeOpChain, WorkspaceWatcher;
|
|
23219
|
+
var IGNORED_SEGMENTS, IS_WIN32, IGNORED_SEGMENT_SET, NATIVE_IGNORE, NATIVE_BACKEND, nativeOpChain, WorkspaceWatcher;
|
|
23221
23220
|
var init_file_watcher = __esm({
|
|
23222
23221
|
"../server/dist/ws/file-watcher.js"() {
|
|
23223
23222
|
"use strict";
|
|
@@ -23284,6 +23283,7 @@ var init_file_watcher = __esm({
|
|
|
23284
23283
|
IS_WIN32 = process.platform === "win32";
|
|
23285
23284
|
IGNORED_SEGMENT_SET = new Set(IGNORED_SEGMENTS);
|
|
23286
23285
|
NATIVE_IGNORE = IS_WIN32 ? IGNORED_SEGMENTS : [...IGNORED_SEGMENTS, `**/{${IGNORED_SEGMENTS.join(",")}}/**`];
|
|
23286
|
+
NATIVE_BACKEND = process.platform === "linux" ? "inotify" : IS_WIN32 ? "windows" : void 0;
|
|
23287
23287
|
nativeOpChain = Promise.resolve();
|
|
23288
23288
|
WorkspaceWatcher = class {
|
|
23289
23289
|
subscription = null;
|
|
@@ -23325,7 +23325,7 @@ var init_file_watcher = __esm({
|
|
|
23325
23325
|
}
|
|
23326
23326
|
for (const e of events)
|
|
23327
23327
|
this.handleEvent(e.type, e.path);
|
|
23328
|
-
}, { ignore: NATIVE_IGNORE }));
|
|
23328
|
+
}, { ignore: NATIVE_IGNORE, backend: NATIVE_BACKEND }));
|
|
23329
23329
|
if (this.startEpoch !== epoch) {
|
|
23330
23330
|
void enqueueNativeOp(() => sub.unsubscribe()).catch((err) => {
|
|
23331
23331
|
console.warn(`[FileWatcher] failed to unsubscribe superseded watch on ${root}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -30486,27 +30486,77 @@ function getPty() {
|
|
|
30486
30486
|
ptyModule = requireFromHere("node-pty");
|
|
30487
30487
|
return ptyModule;
|
|
30488
30488
|
}
|
|
30489
|
+
function getHeadless() {
|
|
30490
|
+
if (!headlessModule) {
|
|
30491
|
+
headlessModule = {
|
|
30492
|
+
Terminal: requireFromHere("@xterm/headless").Terminal,
|
|
30493
|
+
SerializeAddon: requireFromHere("@xterm/addon-serialize").SerializeAddon
|
|
30494
|
+
};
|
|
30495
|
+
}
|
|
30496
|
+
return headlessModule;
|
|
30497
|
+
}
|
|
30498
|
+
function isOnline(t2, m) {
|
|
30499
|
+
return m !== null && (t2.attached.has(m) || t2.pending.has(m));
|
|
30500
|
+
}
|
|
30501
|
+
function applySize(t2, cols, rows) {
|
|
30502
|
+
if (t2.pty.cols === cols && t2.pty.rows === rows)
|
|
30503
|
+
return;
|
|
30504
|
+
try {
|
|
30505
|
+
t2.pty.resize(cols, rows);
|
|
30506
|
+
} catch {
|
|
30507
|
+
return;
|
|
30508
|
+
}
|
|
30509
|
+
t2.headless.resize(cols, rows);
|
|
30510
|
+
}
|
|
30511
|
+
function extraModes(term) {
|
|
30512
|
+
const core = term._core;
|
|
30513
|
+
let out = core?.coreService?.isCursorHidden ? "\x1B[?25l" : "";
|
|
30514
|
+
const encoding = core?.coreMouseService?.activeEncoding;
|
|
30515
|
+
if (encoding === "SGR")
|
|
30516
|
+
out += "\x1B[?1006h";
|
|
30517
|
+
else if (encoding === "SGR_PIXELS")
|
|
30518
|
+
out += "\x1B[?1016h";
|
|
30519
|
+
return out;
|
|
30520
|
+
}
|
|
30521
|
+
function attach(t2, m) {
|
|
30522
|
+
if (t2.attached.has(m) || t2.pending.has(m))
|
|
30523
|
+
return;
|
|
30524
|
+
t2.pending.set(m, []);
|
|
30525
|
+
t2.headless.write("", () => {
|
|
30526
|
+
const queued = t2.pending.get(m);
|
|
30527
|
+
if (!queued || terminals.get(t2.id) !== t2)
|
|
30528
|
+
return;
|
|
30529
|
+
t2.pending.delete(m);
|
|
30530
|
+
m.send({ type: "terminal:snapshot", terminalId: t2.id, data: t2.serializer.serialize() + extraModes(t2.headless), cols: t2.headless.cols, rows: t2.headless.rows });
|
|
30531
|
+
for (const data of queued)
|
|
30532
|
+
m.send({ type: "terminal:output", data, terminalId: t2.id });
|
|
30533
|
+
t2.attached.add(m);
|
|
30534
|
+
});
|
|
30535
|
+
}
|
|
30536
|
+
function endTerminal(t2, exitCode) {
|
|
30537
|
+
if (terminals.get(t2.id) !== t2)
|
|
30538
|
+
return;
|
|
30539
|
+
terminals.delete(t2.id);
|
|
30540
|
+
for (const m of [...t2.attached, ...t2.pending.keys()]) {
|
|
30541
|
+
m.send({ type: "terminal:exit", exitCode, terminalId: t2.id });
|
|
30542
|
+
}
|
|
30543
|
+
t2.attached.clear();
|
|
30544
|
+
t2.pending.clear();
|
|
30545
|
+
t2.sizes.clear();
|
|
30546
|
+
t2.driver = null;
|
|
30547
|
+
t2.headless.dispose();
|
|
30548
|
+
}
|
|
30489
30549
|
function attachListenersOnce(t2) {
|
|
30490
30550
|
t2.pty.onData((data) => {
|
|
30491
|
-
|
|
30492
|
-
|
|
30493
|
-
|
|
30494
|
-
|
|
30495
|
-
|
|
30496
|
-
t2.buffer = t2.buffer.slice(-config.limits.terminalOutputBuffer);
|
|
30497
|
-
}
|
|
30498
|
-
}
|
|
30499
|
-
});
|
|
30500
|
-
t2.pty.onExit(({ exitCode }) => {
|
|
30501
|
-
if (t2.currentWs && t2.currentWs.readyState === t2.currentWs.OPEN) {
|
|
30502
|
-
sendJson(t2.currentWs, { type: "terminal:exit", exitCode, terminalId: t2.id });
|
|
30503
|
-
}
|
|
30504
|
-
if (t2.graceTimer)
|
|
30505
|
-
clearTimeout(t2.graceTimer);
|
|
30506
|
-
terminals.delete(t2.id);
|
|
30551
|
+
t2.headless.write(data);
|
|
30552
|
+
for (const queued of t2.pending.values())
|
|
30553
|
+
queued.push(data);
|
|
30554
|
+
for (const m of t2.attached)
|
|
30555
|
+
m.send({ type: "terminal:output", data, terminalId: t2.id });
|
|
30507
30556
|
});
|
|
30557
|
+
t2.pty.onExit(({ exitCode }) => endTerminal(t2, exitCode));
|
|
30508
30558
|
}
|
|
30509
|
-
var requireFromHere, ptyModule,
|
|
30559
|
+
var requireFromHere, ptyModule, headlessModule, terminals, subscribers, TerminalManager;
|
|
30510
30560
|
var init_terminal_manager = __esm({
|
|
30511
30561
|
"../server/dist/ws/terminal-manager.js"() {
|
|
30512
30562
|
"use strict";
|
|
@@ -30515,14 +30565,19 @@ var init_terminal_manager = __esm({
|
|
|
30515
30565
|
init_event_processor();
|
|
30516
30566
|
requireFromHere = createRequire(import.meta.url);
|
|
30517
30567
|
ptyModule = null;
|
|
30518
|
-
|
|
30568
|
+
headlessModule = null;
|
|
30519
30569
|
terminals = /* @__PURE__ */ new Map();
|
|
30570
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
30520
30571
|
TerminalManager = class {
|
|
30521
|
-
ownedIds = /* @__PURE__ */ new Set();
|
|
30522
30572
|
ws;
|
|
30573
|
+
/** Workspace this connection subscribed to via terminal:reattach. */
|
|
30574
|
+
workspace = null;
|
|
30523
30575
|
constructor(ws) {
|
|
30524
30576
|
this.ws = ws;
|
|
30525
30577
|
}
|
|
30578
|
+
send(frame) {
|
|
30579
|
+
sendJson(this.ws, frame);
|
|
30580
|
+
}
|
|
30526
30581
|
start(opts) {
|
|
30527
30582
|
const terminalId = opts.terminalId ?? `term_${Date.now().toString(36)}`;
|
|
30528
30583
|
const existing = terminals.get(terminalId);
|
|
@@ -30531,9 +30586,7 @@ var init_terminal_manager = __esm({
|
|
|
30531
30586
|
existing.pty.kill();
|
|
30532
30587
|
} catch {
|
|
30533
30588
|
}
|
|
30534
|
-
|
|
30535
|
-
clearTimeout(existing.graceTimer);
|
|
30536
|
-
terminals.delete(terminalId);
|
|
30589
|
+
endTerminal(existing, 0);
|
|
30537
30590
|
}
|
|
30538
30591
|
const cwd = opts.cwd === "~" || !opts.cwd ? homedir23() : opts.cwd;
|
|
30539
30592
|
const cols = opts.cols ?? 80;
|
|
@@ -30544,113 +30597,119 @@ var init_terminal_manager = __esm({
|
|
|
30544
30597
|
const shellName = basename(shell).toLowerCase();
|
|
30545
30598
|
const args = ["bash", "zsh", "sh", "fish"].some((s) => shellName.startsWith(s)) ? ["-l"] : [];
|
|
30546
30599
|
const child = getPty().spawn(shell, args, { name: "xterm-256color", cols, rows, cwd, env: termEnv });
|
|
30600
|
+
const { Terminal, SerializeAddon } = getHeadless();
|
|
30601
|
+
const headless = new Terminal({ cols: child.cols, rows: child.rows, scrollback: config.limits.terminalScrollbackLines, allowProposedApi: true, logLevel: "off" });
|
|
30602
|
+
const serializer = new SerializeAddon();
|
|
30603
|
+
headless.loadAddon(serializer);
|
|
30547
30604
|
const t2 = {
|
|
30548
30605
|
pty: child,
|
|
30549
30606
|
id: terminalId,
|
|
30550
|
-
|
|
30551
|
-
|
|
30552
|
-
|
|
30553
|
-
|
|
30554
|
-
|
|
30607
|
+
name: opts.name || basename(shell),
|
|
30608
|
+
workspacePath: opts.workspacePath ?? "",
|
|
30609
|
+
headless,
|
|
30610
|
+
serializer,
|
|
30611
|
+
attached: /* @__PURE__ */ new Set([this]),
|
|
30612
|
+
pending: /* @__PURE__ */ new Map(),
|
|
30613
|
+
sizes: /* @__PURE__ */ new Map([[this, { cols: child.cols, rows: child.rows }]]),
|
|
30614
|
+
driver: this
|
|
30555
30615
|
};
|
|
30556
30616
|
attachListenersOnce(t2);
|
|
30557
30617
|
terminals.set(terminalId, t2);
|
|
30558
|
-
this.
|
|
30559
|
-
|
|
30618
|
+
this.send({ type: "terminal:ready", terminalId });
|
|
30619
|
+
for (const m of subscribers.get(t2.workspacePath) ?? []) {
|
|
30620
|
+
if (m === this)
|
|
30621
|
+
continue;
|
|
30622
|
+
m.send({ type: "terminal:opened", terminalId, name: t2.name });
|
|
30623
|
+
attach(t2, m);
|
|
30624
|
+
}
|
|
30560
30625
|
console.log(`[WS] Terminal started (id=${terminalId}, pid=${child.pid}, cwd=${cwd})`);
|
|
30561
30626
|
return terminalId;
|
|
30562
30627
|
}
|
|
30563
30628
|
// No terminalId → reject (audit A-M3). The old fallback took the FIRST entry
|
|
30564
|
-
// of the global `terminals` map
|
|
30565
|
-
//
|
|
30566
|
-
//
|
|
30567
|
-
//
|
|
30568
|
-
// hits this path.
|
|
30629
|
+
// of the global `terminals` map, so with several admin connections an
|
|
30630
|
+
// id-less input could write into a PTY of another workspace. Every admin
|
|
30631
|
+
// send site always includes terminalId (verified against the panel and the
|
|
30632
|
+
// compiled desktop bundle), so nothing legitimate hits this path.
|
|
30569
30633
|
writeInput(terminalId, data) {
|
|
30570
30634
|
if (!terminalId) {
|
|
30571
30635
|
console.warn("[WS] terminal:input without terminalId rejected");
|
|
30572
30636
|
return;
|
|
30573
30637
|
}
|
|
30574
30638
|
const t2 = terminals.get(terminalId);
|
|
30575
|
-
if (t2)
|
|
30576
|
-
|
|
30577
|
-
|
|
30639
|
+
if (!t2)
|
|
30640
|
+
return;
|
|
30641
|
+
t2.driver = this;
|
|
30642
|
+
const size = t2.sizes.get(this);
|
|
30643
|
+
if (size)
|
|
30644
|
+
applySize(t2, size.cols, size.rows);
|
|
30645
|
+
t2.pty.write(data);
|
|
30646
|
+
}
|
|
30647
|
+
/** Records this page's size; applies it only when this page is the driver
|
|
30648
|
+
* or no driver is attached — otherwise a background page's resize would
|
|
30649
|
+
* reflow the screen under whoever is typing. */
|
|
30578
30650
|
resize(terminalId, cols, rows) {
|
|
30579
30651
|
if (!terminalId) {
|
|
30580
30652
|
console.warn("[WS] terminal:resize without terminalId rejected");
|
|
30581
30653
|
return;
|
|
30582
30654
|
}
|
|
30655
|
+
if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols < 1 || rows < 1)
|
|
30656
|
+
return;
|
|
30583
30657
|
const t2 = terminals.get(terminalId);
|
|
30584
|
-
if (t2)
|
|
30585
|
-
|
|
30658
|
+
if (!t2)
|
|
30659
|
+
return;
|
|
30660
|
+
t2.sizes.set(this, { cols, rows });
|
|
30661
|
+
if (t2.driver === this || !isOnline(t2, t2.driver)) {
|
|
30662
|
+
t2.driver = this;
|
|
30663
|
+
applySize(t2, cols, rows);
|
|
30664
|
+
}
|
|
30586
30665
|
}
|
|
30587
30666
|
close(terminalId) {
|
|
30588
30667
|
const t2 = terminals.get(terminalId);
|
|
30589
30668
|
if (!t2)
|
|
30590
30669
|
return;
|
|
30591
|
-
if (t2.graceTimer)
|
|
30592
|
-
clearTimeout(t2.graceTimer);
|
|
30593
30670
|
try {
|
|
30594
30671
|
t2.pty.kill();
|
|
30595
30672
|
} catch {
|
|
30596
30673
|
}
|
|
30597
|
-
|
|
30598
|
-
sendJson(t2.currentWs, { type: "terminal:exit", terminalId, exitCode: 0 });
|
|
30599
|
-
}
|
|
30600
|
-
terminals.delete(terminalId);
|
|
30601
|
-
this.ownedIds.delete(terminalId);
|
|
30674
|
+
endTerminal(t2, 0);
|
|
30602
30675
|
}
|
|
30603
|
-
/** WS closed:
|
|
30676
|
+
/** WS closed: leave every PTY's attached set and the workspace
|
|
30677
|
+
* subscription. The PTYs keep running — no timer. */
|
|
30604
30678
|
detachAll() {
|
|
30605
|
-
for (const
|
|
30606
|
-
|
|
30607
|
-
|
|
30608
|
-
|
|
30609
|
-
if (t2.
|
|
30610
|
-
|
|
30611
|
-
|
|
30612
|
-
|
|
30613
|
-
|
|
30614
|
-
|
|
30615
|
-
|
|
30616
|
-
|
|
30617
|
-
|
|
30618
|
-
|
|
30619
|
-
|
|
30620
|
-
|
|
30621
|
-
|
|
30622
|
-
|
|
30623
|
-
|
|
30624
|
-
|
|
30625
|
-
|
|
30626
|
-
|
|
30627
|
-
|
|
30628
|
-
|
|
30629
|
-
|
|
30630
|
-
|
|
30631
|
-
|
|
30632
|
-
|
|
30633
|
-
|
|
30634
|
-
|
|
30635
|
-
|
|
30636
|
-
|
|
30637
|
-
|
|
30638
|
-
|
|
30639
|
-
if (t2.graceTimer) {
|
|
30640
|
-
clearTimeout(t2.graceTimer);
|
|
30641
|
-
t2.graceTimer = null;
|
|
30642
|
-
}
|
|
30643
|
-
t2.currentWs = this.ws;
|
|
30644
|
-
this.ownedIds.add(id);
|
|
30645
|
-
if (t2.buffer) {
|
|
30646
|
-
sendJson(this.ws, { type: "terminal:output", data: t2.buffer, terminalId: id });
|
|
30647
|
-
t2.buffer = "";
|
|
30648
|
-
}
|
|
30649
|
-
reattached.push(id);
|
|
30650
|
-
}
|
|
30651
|
-
sendJson(this.ws, { type: "terminal:reattached", terminalIds: reattached });
|
|
30652
|
-
console.log(`[WS] Reattached ${reattached.length} terminals for browser=${browserId.slice(0, 8) || "(none)"} workspace=${workspacePath || "(none)"}`);
|
|
30653
|
-
return reattached;
|
|
30679
|
+
for (const t2 of terminals.values()) {
|
|
30680
|
+
t2.attached.delete(this);
|
|
30681
|
+
t2.pending.delete(this);
|
|
30682
|
+
t2.sizes.delete(this);
|
|
30683
|
+
if (t2.driver === this)
|
|
30684
|
+
t2.driver = null;
|
|
30685
|
+
}
|
|
30686
|
+
this.unsubscribe();
|
|
30687
|
+
}
|
|
30688
|
+
/** Attach to every PTY of `workspacePath` (snapshot each) and subscribe to
|
|
30689
|
+
* the workspace, so PTYs started there later are pushed here too. Any
|
|
30690
|
+
* browser / device may attach — the admin has one login, terminals have
|
|
30691
|
+
* no per-browser owner. Re-sending is harmless: attached PTYs are skipped. */
|
|
30692
|
+
reattachAll(workspacePath) {
|
|
30693
|
+
this.unsubscribe();
|
|
30694
|
+
this.workspace = workspacePath;
|
|
30695
|
+
let subs = subscribers.get(workspacePath);
|
|
30696
|
+
if (!subs)
|
|
30697
|
+
subscribers.set(workspacePath, subs = /* @__PURE__ */ new Set());
|
|
30698
|
+
subs.add(this);
|
|
30699
|
+
const list = [...terminals.values()].filter((t2) => t2.workspacePath === workspacePath);
|
|
30700
|
+
this.send({ type: "terminal:reattached", terminals: list.map((t2) => ({ id: t2.id, name: t2.name })) });
|
|
30701
|
+
for (const t2 of list)
|
|
30702
|
+
attach(t2, this);
|
|
30703
|
+
console.log(`[WS] Reattached ${list.length} terminals for workspace=${workspacePath || "(none)"}`);
|
|
30704
|
+
}
|
|
30705
|
+
unsubscribe() {
|
|
30706
|
+
if (this.workspace === null)
|
|
30707
|
+
return;
|
|
30708
|
+
const subs = subscribers.get(this.workspace);
|
|
30709
|
+
subs?.delete(this);
|
|
30710
|
+
if (subs?.size === 0)
|
|
30711
|
+
subscribers.delete(this.workspace);
|
|
30712
|
+
this.workspace = null;
|
|
30654
30713
|
}
|
|
30655
30714
|
};
|
|
30656
30715
|
}
|
|
@@ -30909,7 +30968,7 @@ function setupWebSocketHandler(deps) {
|
|
|
30909
30968
|
terminalManager.close(msg.terminalId);
|
|
30910
30969
|
break;
|
|
30911
30970
|
case "terminal:reattach":
|
|
30912
|
-
terminalManager.reattachAll(msg.
|
|
30971
|
+
terminalManager.reattachAll(msg.workspacePath ?? "");
|
|
30913
30972
|
break;
|
|
30914
30973
|
case "session:delete":
|
|
30915
30974
|
await handleSessionDelete(client, ws, msg);
|
|
@@ -31237,8 +31296,8 @@ ${msg.message}` : markers.join("\n");
|
|
|
31237
31296
|
cwd,
|
|
31238
31297
|
cols: msg.cols,
|
|
31239
31298
|
rows: msg.rows,
|
|
31240
|
-
|
|
31241
|
-
|
|
31299
|
+
workspacePath: msg.workspacePath ?? "",
|
|
31300
|
+
name: msg.name
|
|
31242
31301
|
});
|
|
31243
31302
|
} catch (err) {
|
|
31244
31303
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -31632,7 +31691,7 @@ function buildCard(workspace, interfaceUrl, caps) {
|
|
|
31632
31691
|
return {
|
|
31633
31692
|
name: raw.name,
|
|
31634
31693
|
description: raw.description,
|
|
31635
|
-
version: typeof raw.version === "string" ? raw.version : "2.1.
|
|
31694
|
+
version: typeof raw.version === "string" ? raw.version : "2.1.1",
|
|
31636
31695
|
supportedInterfaces: [{ url: interfaceUrl, protocolBinding: "JSONRPC", protocolVersion: A2A_VERSION }],
|
|
31637
31696
|
capabilities: { streaming: caps.streaming, pushNotifications: true, extendedAgentCard: false },
|
|
31638
31697
|
...caps.tokenAuth === false ? {} : {
|
|
@@ -34393,7 +34452,7 @@ function createCoreChecker(opts) {
|
|
|
34393
34452
|
}
|
|
34394
34453
|
function getCoreStatus(force) {
|
|
34395
34454
|
checker ??= createCoreChecker({
|
|
34396
|
-
haloVersion: "2.1.
|
|
34455
|
+
haloVersion: "2.1.1",
|
|
34397
34456
|
fetchLatest: fetchNpmLatest,
|
|
34398
34457
|
onChange: (status) => broadcast({ type: "updates:changed", scope: "core", status })
|
|
34399
34458
|
});
|
|
@@ -34561,7 +34620,7 @@ function hubBase() {
|
|
|
34561
34620
|
}
|
|
34562
34621
|
function serverChecker() {
|
|
34563
34622
|
checker2 ??= createExtensionsChecker({
|
|
34564
|
-
haloVersion: "2.1.
|
|
34623
|
+
haloVersion: "2.1.1",
|
|
34565
34624
|
installed: () => {
|
|
34566
34625
|
const s = getSnapshot();
|
|
34567
34626
|
return [...s.extensions, ...s.disabled];
|
|
@@ -40850,7 +40909,7 @@ var init_dist2 = __esm({
|
|
|
40850
40909
|
init_git_ssh();
|
|
40851
40910
|
__dirname3 = path90.dirname(fileURLToPath3(import.meta.url));
|
|
40852
40911
|
PORT = config.server.port;
|
|
40853
|
-
HALO_VERSION = "2.1.
|
|
40912
|
+
HALO_VERSION = "2.1.1";
|
|
40854
40913
|
PROJECT_ROOT = findProjectRoot();
|
|
40855
40914
|
HALO_HOME4 = path90.join(homedir25(), ".halo");
|
|
40856
40915
|
GIT_SHA = readGitSha();
|
|
@@ -41624,7 +41683,6 @@ var init_agentcore_front = __esm({
|
|
|
41624
41683
|
"HALO_WEB_FETCH_TIMEOUT",
|
|
41625
41684
|
"HALO_MODEL_TIMEOUT",
|
|
41626
41685
|
"HALO_SESSION_GRACE",
|
|
41627
|
-
"HALO_TERMINAL_GRACE",
|
|
41628
41686
|
"HALO_MAX_CACHED_SESSIONS",
|
|
41629
41687
|
"HALO_LOG_MAX_SIZE",
|
|
41630
41688
|
"HALO_LOG_MAX_FILES"
|
|
@@ -42910,7 +42968,7 @@ function runNode(opts) {
|
|
|
42910
42968
|
shellTimeoutMs: config.timeout.shellExec,
|
|
42911
42969
|
os: process.platform,
|
|
42912
42970
|
hostname: os13.hostname(),
|
|
42913
|
-
version: "2.1.
|
|
42971
|
+
version: "2.1.1"
|
|
42914
42972
|
};
|
|
42915
42973
|
const exec2 = createNodeExecutor({ root, base, allowShell }, log2);
|
|
42916
42974
|
const instance = crypto16.randomUUID();
|
|
@@ -43426,7 +43484,7 @@ function installUserUnit(opts, run = runCommand) {
|
|
|
43426
43484
|
}
|
|
43427
43485
|
|
|
43428
43486
|
// src/index.ts
|
|
43429
|
-
var VERSION = "2.1.
|
|
43487
|
+
var VERSION = "2.1.1";
|
|
43430
43488
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
43431
43489
|
|
|
43432
43490
|
Commands:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turmind/halo",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Halo — multi-agent collaborative workspace. CLI + TUI + HTTP/WS server in one package.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"@parcel/watcher": "^2.5.6",
|
|
38
38
|
"@smithy/signature-v4": "^5.3.13",
|
|
39
39
|
"@wecom/aibot-node-sdk": "^1.0.7",
|
|
40
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
41
|
+
"@xterm/headless": "^6.0.0",
|
|
40
42
|
"better-sqlite3": "^11.7.0",
|
|
41
43
|
"croner": "^10.0.1",
|
|
42
44
|
"drizzle-orm": "^0.45.2",
|
package/templates/config.yaml
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
# timeout.web_fetch → HALO_WEB_FETCH_TIMEOUT
|
|
10
10
|
# timeout.model_request → HALO_MODEL_TIMEOUT
|
|
11
11
|
# timeout.session_grace → HALO_SESSION_GRACE
|
|
12
|
-
# timeout.terminal_grace → HALO_TERMINAL_GRACE
|
|
13
12
|
# logging.max_file_size → HALO_LOG_MAX_SIZE
|
|
14
13
|
# logging.max_files → HALO_LOG_MAX_FILES
|
|
15
14
|
|
|
@@ -56,11 +55,6 @@ timeout:
|
|
|
56
55
|
default: 300000
|
|
57
56
|
description: "Idle session grace period before unloading from memory (ms)."
|
|
58
57
|
description_zh: "空闲会话保留在内存中的时长(毫秒)。"
|
|
59
|
-
terminal_grace:
|
|
60
|
-
value: 300000
|
|
61
|
-
default: 300000
|
|
62
|
-
description: "Terminal grace period after disconnect before killing the pty (ms)."
|
|
63
|
-
description_zh: "终端断连后保留 pty 的时长(毫秒)。"
|
|
64
58
|
|
|
65
59
|
logging:
|
|
66
60
|
max_file_size:
|