@turmind/halo 1.2.0 → 1.3.0
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/{SiIebna28lHobsJ4LTUOT → Z6KkY5u-XmAPBuUfiAvaG}/_buildManifest.js +1 -1
- package/admin-out/_next/static/chunks/132-29179dca2ef093ce.js +1 -0
- package/admin-out/_next/static/chunks/{330.3b784cf0dcd69163.js → 170.14d53d7f32df020c.js} +1 -1
- package/admin-out/_next/static/chunks/187.9e822f6f0d366d9a.js +1 -0
- package/admin-out/_next/static/chunks/188.0cc6998eb0ed3519.js +1 -0
- package/admin-out/_next/static/chunks/{1d2d5650-810a3ca1ae20cab5.js → 1d2d5650-f5c38b9df5ca7466.js} +1 -1
- package/admin-out/_next/static/chunks/{304eda7d.b1ee7f523653e027.js → 304eda7d.662c3a1a7cd50701.js} +1 -1
- package/admin-out/_next/static/chunks/311.3cfaf322ec2a9170.js +1 -0
- package/admin-out/_next/static/chunks/401.c5e64882d4305a08.js +1 -0
- package/admin-out/_next/static/chunks/{443.d1b289781793f8de.js → 444.c191bedee971e484.js} +1 -1
- package/admin-out/_next/static/chunks/496.7997fc6558c88c22.js +1 -0
- package/admin-out/_next/static/chunks/530.03c0a6ec971d3528.js +1 -0
- package/admin-out/_next/static/chunks/540.a663b46781b2c309.js +1 -0
- package/admin-out/_next/static/chunks/556.10e21b9f7b45d412.js +1 -0
- package/admin-out/_next/static/chunks/813-9056b8ef0984bee6.js +1 -0
- package/admin-out/_next/static/chunks/{533.5f2d4bfe3d076095.js → 841.48a39cec2ef962f3.js} +1 -1
- package/admin-out/_next/static/chunks/891.cfe754015339e975.js +1 -0
- package/admin-out/_next/static/chunks/915.c6ee9ab9bee0ce9b.js +1 -0
- package/admin-out/_next/static/chunks/957-48c734b74e5437a8.js +1 -0
- package/admin-out/_next/static/chunks/968.1676baa9449dbd9d.js +1 -0
- package/admin-out/_next/static/chunks/app/_not-found/page-2ea4ee5eada04f69.js +1 -0
- package/admin-out/_next/static/chunks/app/layout-1853d21b0644f5a1.js +1 -0
- package/admin-out/_next/static/chunks/app/page-2ab109c9a25f867e.js +1 -0
- package/admin-out/_next/static/chunks/db534642.a148101e95a7d317.js +1 -0
- package/admin-out/_next/static/chunks/e3080c2c-fbf81b7cad547976.js +1 -0
- package/admin-out/_next/static/chunks/framework-67e5f0af3ea64f2b.js +1 -0
- package/admin-out/_next/static/chunks/main-6b28974253a3bc7c.js +1 -0
- package/admin-out/_next/static/chunks/main-app-3367dd99b8642c55.js +1 -0
- package/admin-out/_next/static/chunks/pages/_app-51a53534a0a4a898.js +1 -0
- package/admin-out/_next/static/chunks/pages/_error-dfd19566fa6f79ad.js +1 -0
- package/admin-out/_next/static/chunks/webpack-bf41edfde9cdefec.js +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +13 -13
- package/bundled-docs/design/agentcore.md +9 -0
- package/bundled-docs/design/goal-mode.md +1 -1
- package/bundled-docs/design/observability.md +212 -0
- package/bundled-docs/design/relay.md +52 -0
- package/bundled-docs/design/session.md +12 -4
- package/bundled-docs/design/storage.md +8 -1
- package/bundled-docs/dev/api.md +9 -0
- package/bundled-docs/dev/deploy.md +23 -1
- package/bundled-docs/dev/env.md +4 -0
- package/bundled-docs/dev/tools.md +85 -6
- package/bundled-docs/guide/workspace.md +1 -0
- package/dist/index.js +520 -59
- package/package.json +12 -1
- package/admin-out/_next/static/chunks/133-e2df20fc6362a02b.js +0 -1
- package/admin-out/_next/static/chunks/200.dd28be896c7765de.js +0 -1
- package/admin-out/_next/static/chunks/220.e5a2532bda77e816.js +0 -1
- package/admin-out/_next/static/chunks/438-866a1a20e9efa78a.js +0 -1
- package/admin-out/_next/static/chunks/442.14eec8e615cc727b.js +0 -1
- package/admin-out/_next/static/chunks/515.b793a7455c5e8342.js +0 -1
- package/admin-out/_next/static/chunks/525.fe35ab8501121729.js +0 -1
- package/admin-out/_next/static/chunks/579.3ab0b2bb9244fc37.js +0 -1
- package/admin-out/_next/static/chunks/635-d7fd95bff8674116.js +0 -1
- package/admin-out/_next/static/chunks/65.3aac567c42cec99c.js +0 -1
- package/admin-out/_next/static/chunks/688.4e014ac6cfc7e152.js +0 -1
- package/admin-out/_next/static/chunks/868.0c95dfe283400a00.js +0 -1
- package/admin-out/_next/static/chunks/896.2069ab525ef81643.js +0 -1
- package/admin-out/_next/static/chunks/935.91ddb965b44041c9.js +0 -1
- package/admin-out/_next/static/chunks/app/_not-found/page-8e35399c8fd5016d.js +0 -1
- package/admin-out/_next/static/chunks/app/layout-e75df4cae90cf325.js +0 -1
- package/admin-out/_next/static/chunks/app/page-ca15438e8bddb403.js +0 -1
- package/admin-out/_next/static/chunks/db534642.09d3c650ff587175.js +0 -1
- package/admin-out/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js +0 -1
- package/admin-out/_next/static/chunks/framework-c3031ae54a63fe16.js +0 -1
- package/admin-out/_next/static/chunks/main-14b48e3c2588354f.js +0 -1
- package/admin-out/_next/static/chunks/main-app-483b436873410ddc.js +0 -1
- package/admin-out/_next/static/chunks/pages/_app-b2fc6e5f920d1b1a.js +0 -1
- package/admin-out/_next/static/chunks/pages/_error-f3696a8f3d67d150.js +0 -1
- package/admin-out/_next/static/chunks/webpack-f80c9b8cca4447a3.js +0 -1
- /package/admin-out/_next/static/{SiIebna28lHobsJ4LTUOT → Z6KkY5u-XmAPBuUfiAvaG}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -521,6 +521,13 @@ function generalSection() {
|
|
|
521
521
|
{ 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,~/.halo/global/runs.db,~/.halo/global/runs.db-wal,~/.halo/global/runs.db-shm" },
|
|
522
522
|
// logging
|
|
523
523
|
{ key: "logging.level", type: "enum", options: ["debug", "info", "warn", "error"], globalOnly: true, description: "Log level", description_zh: "\u65E5\u5FD7\u7EA7\u522B", default: "warn" },
|
|
524
|
+
// observability (OpenTelemetry). Read once at boot by observability/otel.ts
|
|
525
|
+
// and mapped onto the standard OTEL_* env vars — hence "takes effect on
|
|
526
|
+
// restart" everywhere below.
|
|
527
|
+
{ key: "observability.endpoint", type: "string", globalOnly: true, description: "OTLP base URL of an OpenTelemetry collector (e.g. http://localhost:4318). Traces, metrics and logs are all exported there over OTLP http/protobuf \u2014 http:// or https://, on the collector's OTLP/HTTP port (4318 by default); gRPC (4317) is not supported. Empty = observability off. Takes effect on server restart.", description_zh: "OpenTelemetry collector \u7684 OTLP \u57FA\u7840\u5730\u5740\uFF08\u5982 http://localhost:4318\uFF09\uFF0Ctrace / metrics / \u65E5\u5FD7\u4E09\u7C7B\u4FE1\u53F7\u90FD\u4EE5 OTLP http/protobuf \u53D1\u5230\u8FD9\u91CC\u3002http:// \u6216 https:// \u5747\u53EF\uFF0C\u7AEF\u53E3\u987B\u662F collector \u7684 OTLP/HTTP \u7AEF\u53E3\uFF08\u9ED8\u8BA4 4318\uFF09\uFF0C\u4E0D\u652F\u6301 gRPC\uFF084317\uFF09\u3002\u7559\u7A7A = \u5173\u95ED\u3002\u91CD\u542F\u670D\u52A1\u540E\u751F\u6548\u3002", default: "" },
|
|
528
|
+
{ key: "observability.service_name", type: "string", globalOnly: true, description: "OTel resource service.name reported for this server. Takes effect on restart.", description_zh: "\u672C\u670D\u52A1\u5728 OTel \u4E2D\u4E0A\u62A5\u7684 service.name\u3002\u91CD\u542F\u540E\u751F\u6548\u3002", default: "halo" },
|
|
529
|
+
{ key: "observability.headers", type: "string", secret: true, globalOnly: true, description: "Extra headers for every OTLP request, comma-separated k=v (e.g. authorization=Bearer \u2026). Takes effect on restart.", description_zh: "\u9644\u52A0\u5230\u6BCF\u4E2A OTLP \u8BF7\u6C42\u7684 header\uFF0C\u9017\u53F7\u5206\u9694\u7684 k=v\uFF08\u5982 authorization=Bearer \u2026\uFF09\u3002\u91CD\u542F\u540E\u751F\u6548\u3002", default: "" },
|
|
530
|
+
{ key: "observability.capture_content", type: "boolean", globalOnly: true, description: "Put prompt / completion / tool argument and result text on spans (gen_ai.*.messages etc.). Off = only metadata (model, tokens, latency, tool names). Takes effect on restart.", description_zh: "\u662F\u5426\u628A\u63D0\u793A\u8BCD / \u6A21\u578B\u8F93\u51FA / \u5DE5\u5177\u53C2\u6570\u4E0E\u7ED3\u679C\u6B63\u6587\u5199\u5230 span \u4E0A\uFF08gen_ai.*.messages \u7B49\uFF09\u3002\u5173 = \u53EA\u4E0A\u62A5\u5143\u6570\u636E\uFF08\u6A21\u578B\u3001token\u3001\u8017\u65F6\u3001\u5DE5\u5177\u540D\uFF09\u3002\u91CD\u542F\u540E\u751F\u6548\u3002", default: "false" },
|
|
524
531
|
// self-evolution (see plans/self-evolution.md). All evo settings are
|
|
525
532
|
// global-only — the worker / ticker live in the server process and
|
|
526
533
|
// would have to reload mid-flight if a workspace could override them.
|
|
@@ -753,7 +760,7 @@ function loadYamlFile(filePath) {
|
|
|
753
760
|
}
|
|
754
761
|
}
|
|
755
762
|
function loadModelsRegistry() {
|
|
756
|
-
const
|
|
763
|
+
const providers2 = [];
|
|
757
764
|
const modelsDir = path5.join(HALO_GLOBAL_DIR, "models");
|
|
758
765
|
try {
|
|
759
766
|
const entries = fs5.readdirSync(modelsDir, { withFileTypes: true });
|
|
@@ -764,7 +771,7 @@ function loadModelsRegistry() {
|
|
|
764
771
|
const raw = fs5.readFileSync(path5.join(modelsDir, entry.name), "utf-8");
|
|
765
772
|
const parsed = YAML3.parse(raw);
|
|
766
773
|
if (parsed && typeof parsed === "object" && typeof parsed.id === "string") {
|
|
767
|
-
|
|
774
|
+
providers2.push(parsed);
|
|
768
775
|
} else {
|
|
769
776
|
console.log(`[config] Skipping ${entry.name}: missing provider id`);
|
|
770
777
|
}
|
|
@@ -774,7 +781,7 @@ function loadModelsRegistry() {
|
|
|
774
781
|
}
|
|
775
782
|
} catch {
|
|
776
783
|
}
|
|
777
|
-
return { providers };
|
|
784
|
+
return { providers: providers2 };
|
|
778
785
|
}
|
|
779
786
|
function modelsRegistry() {
|
|
780
787
|
if (!_modelsRegistryCache)
|
|
@@ -953,9 +960,9 @@ function reloadSandboxConfig() {
|
|
|
953
960
|
}
|
|
954
961
|
function findModelEntry(modelId) {
|
|
955
962
|
try {
|
|
956
|
-
const
|
|
957
|
-
if (
|
|
958
|
-
for (const provider of
|
|
963
|
+
const providers2 = modelsRegistry().providers;
|
|
964
|
+
if (providers2) {
|
|
965
|
+
for (const provider of providers2) {
|
|
959
966
|
for (const model of provider.models ?? []) {
|
|
960
967
|
if (model.id === modelId)
|
|
961
968
|
return model;
|
|
@@ -1203,6 +1210,14 @@ var init_config = __esm({
|
|
|
1203
1210
|
// 10MB
|
|
1204
1211
|
maxFiles: systemInt("HALO_LOG_MAX_FILES", "logging.max_files", 5)
|
|
1205
1212
|
},
|
|
1213
|
+
// OpenTelemetry export. Read once at boot (observability/otel.ts maps these
|
|
1214
|
+
// onto OTEL_* env vars) — changes take effect on restart.
|
|
1215
|
+
observability: {
|
|
1216
|
+
endpoint: settingsStr("general.observability.endpoint", ""),
|
|
1217
|
+
serviceName: settingsStr("general.observability.service_name", "halo"),
|
|
1218
|
+
headers: settingsStr("general.observability.headers", ""),
|
|
1219
|
+
captureContent: settingsBool("general.observability.capture_content", false)
|
|
1220
|
+
},
|
|
1206
1221
|
// Self-evolution (see plans/self-evolution.md). Getter-based so user edits
|
|
1207
1222
|
// in settings.yaml take effect on the next ticker run without restart.
|
|
1208
1223
|
evolution: {
|
|
@@ -1600,20 +1615,20 @@ Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
|
1600
1615
|
const resultTextFull = resultText.length > uiCap ? resultText.slice(0, uiCap) + `
|
|
1601
1616
|
|
|
1602
1617
|
[Content truncated: ${resultText.length} chars total, showing first ${uiCap}. Use file_read for the complete content.]` : resultText;
|
|
1603
|
-
const
|
|
1618
|
+
const cap2 = tu.name === "activate_skill" ? Infinity : config.limits.toolResultMax;
|
|
1604
1619
|
const truncationNote = (origLen) => `
|
|
1605
1620
|
|
|
1606
|
-
[Content truncated: ${origLen} chars total, showing first ${
|
|
1607
|
-
if (typeof resultContent === "string" && resultContent.length >
|
|
1621
|
+
[Content truncated: ${origLen} chars total, showing first ${cap2}. Re-run with narrower scope (e.g. grep / file_read with offset+limit) to see specific sections.]`;
|
|
1622
|
+
if (typeof resultContent === "string" && resultContent.length > cap2) {
|
|
1608
1623
|
const orig = resultContent.length;
|
|
1609
|
-
resultContent = resultContent.slice(0,
|
|
1624
|
+
resultContent = resultContent.slice(0, cap2) + truncationNote(orig);
|
|
1610
1625
|
resultText = resultContent;
|
|
1611
1626
|
} else if (Array.isArray(resultContent)) {
|
|
1612
1627
|
let mutated = false;
|
|
1613
1628
|
resultContent = resultContent.map((b) => {
|
|
1614
|
-
if (b.type === "text" && b.text.length >
|
|
1629
|
+
if (b.type === "text" && b.text.length > cap2) {
|
|
1615
1630
|
mutated = true;
|
|
1616
|
-
return { type: "text", text: b.text.slice(0,
|
|
1631
|
+
return { type: "text", text: b.text.slice(0, cap2) + truncationNote(b.text.length) };
|
|
1617
1632
|
}
|
|
1618
1633
|
return b;
|
|
1619
1634
|
});
|
|
@@ -2427,7 +2442,7 @@ var init_init = __esm({
|
|
|
2427
2442
|
path8.resolve(__dirname, "..", "bundled-docs"),
|
|
2428
2443
|
path8.resolve(__dirname, "..", "..", "..", ".halo", "docs")
|
|
2429
2444
|
];
|
|
2430
|
-
TEMPLATE_VERSION =
|
|
2445
|
+
TEMPLATE_VERSION = 58;
|
|
2431
2446
|
VERSION_FILE = ".template-version";
|
|
2432
2447
|
SKIP_NAMES = /* @__PURE__ */ new Set([".DS_Store", "schema.sql", "__pycache__", ".pytest_cache"]);
|
|
2433
2448
|
BUILTIN_AGENT_IDS = /* @__PURE__ */ new Set([
|
|
@@ -3411,8 +3426,8 @@ ${report}`;
|
|
|
3411
3426
|
await deliver(host, gId, worker.id, header, report);
|
|
3412
3427
|
}
|
|
3413
3428
|
async function deliver(host, gId, workerId, header, report) {
|
|
3414
|
-
const
|
|
3415
|
-
const body = report.length >
|
|
3429
|
+
const cap2 = config.limits.autoReportMax;
|
|
3430
|
+
const body = report.length > cap2 ? report.slice(0, cap2) + `
|
|
3416
3431
|
|
|
3417
3432
|
[Report truncated: ${report.length} chars total. Use get_session_output("${workerId}") for the full text.]` : report;
|
|
3418
3433
|
await host.querySession(gId, workerId, `${header}
|
|
@@ -5387,8 +5402,8 @@ async function deliverRelayReport(host, session) {
|
|
|
5387
5402
|
|
|
5388
5403
|
${report}`;
|
|
5389
5404
|
}
|
|
5390
|
-
const
|
|
5391
|
-
const body = report.length >
|
|
5405
|
+
const cap2 = config.limits.autoReportMax;
|
|
5406
|
+
const body = report.length > cap2 ? report.slice(0, cap2) + `
|
|
5392
5407
|
|
|
5393
5408
|
[Report truncated: ${report.length} chars total. Use relay_read("${host.workspaceRoot}", "${session.id}") for the full text.]` : report;
|
|
5394
5409
|
const header = `[Relay report \xB7 workspace ${host.workspaceRoot} \xB7 session ${session.id}]`;
|
|
@@ -5424,7 +5439,7 @@ function resolveTarget(registry2, workspace) {
|
|
|
5424
5439
|
function buildRelayTools(host, callerSessionId) {
|
|
5425
5440
|
const relaySend = {
|
|
5426
5441
|
name: "relay_send",
|
|
5427
|
-
description: "Dispatch a message to a session in ANOTHER workspace on this server. Creates the session if `session_id` does not exist there (with `agent_id`, or the workspace's default agent). If the session is busy the message is queued and the current step is softly interrupted \u2014 use this for follow-ups and corrections too. Returns immediately; when the target's whole subtree finishes, its wrap-up is delivered to you as a `[Relay report \xB7 \u2026]` message. Do not poll \u2014 the report arrives on its own. Returns JSON with code 0 on success.",
|
|
5442
|
+
description: "Dispatch a message to a session in ANOTHER workspace on this server. Creates the session if `session_id` does not exist there (with `agent_id`, or the workspace's default agent). If the session is busy the message is queued and the current step is softly interrupted (finishes its current tool, then reads your message) \u2014 use this for follow-ups and corrections too. Returns immediately; when the target's whole subtree finishes, its wrap-up is delivered to you as a `[Relay report \xB7 \u2026]` message. Do not poll \u2014 the report arrives on its own. Returns JSON with code 0 on success.",
|
|
5428
5443
|
inputSchema: {
|
|
5429
5444
|
type: "object",
|
|
5430
5445
|
properties: {
|
|
@@ -5437,29 +5452,57 @@ function buildRelayTools(host, callerSessionId) {
|
|
|
5437
5452
|
callback: async (input) => {
|
|
5438
5453
|
const params = input;
|
|
5439
5454
|
try {
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5455
|
+
return await dispatch5(params, false);
|
|
5456
|
+
} catch (err) {
|
|
5457
|
+
return jsonErr2(err instanceof Error ? err.message : String(err));
|
|
5458
|
+
}
|
|
5459
|
+
}
|
|
5460
|
+
};
|
|
5461
|
+
const relayInterrupt = {
|
|
5462
|
+
name: "relay_interrupt",
|
|
5463
|
+
description: "Interrupt a running session in another workspace HARD \u2014 aborts whatever it is doing right now (including a command mid-execution) and re-runs it with your message. Use when the target is heading the wrong way and waiting for its current step is not acceptable; for ordinary follow-ups prefer relay_send, which lets the current step finish. Returns JSON with code 0 on success.",
|
|
5464
|
+
inputSchema: {
|
|
5465
|
+
type: "object",
|
|
5466
|
+
properties: {
|
|
5467
|
+
...WORKSPACE_SESSION_PROPS,
|
|
5468
|
+
message: { type: "string", description: "The message the target runs after the abort." }
|
|
5469
|
+
},
|
|
5470
|
+
required: ["workspace", "session_id", "message"]
|
|
5471
|
+
},
|
|
5472
|
+
callback: async (input) => {
|
|
5473
|
+
const params = input;
|
|
5474
|
+
try {
|
|
5475
|
+
return await dispatch5(params, true);
|
|
5458
5476
|
} catch (err) {
|
|
5459
5477
|
return jsonErr2(err instanceof Error ? err.message : String(err));
|
|
5460
5478
|
}
|
|
5461
5479
|
}
|
|
5462
5480
|
};
|
|
5481
|
+
async function dispatch5(params, hard) {
|
|
5482
|
+
const registry2 = getRelayRegistry();
|
|
5483
|
+
if (!registry2)
|
|
5484
|
+
return jsonErr2("relay is unavailable in this runtime (server only)");
|
|
5485
|
+
const resolved = resolveTarget(registry2, params.workspace);
|
|
5486
|
+
if (typeof resolved === "string")
|
|
5487
|
+
return resolved;
|
|
5488
|
+
const { wsPath, target } = resolved;
|
|
5489
|
+
if (!target.getSessionById(params.session_id)) {
|
|
5490
|
+
if (hard)
|
|
5491
|
+
return jsonErr2("session not found");
|
|
5492
|
+
const agentId = params.agent_id ?? await resolveDefaultAgentId(target, wsPath);
|
|
5493
|
+
await target.createSession(agentId, null, `Relay: ${params.message.slice(0, 60)}`, void 0, params.session_id);
|
|
5494
|
+
}
|
|
5495
|
+
writeReplyTo(target.getDb(), params.session_id, { workspace: host.workspaceRoot, sessionId: callerSessionId });
|
|
5496
|
+
const prefixed = `[channel: relay | from: ${host.workspaceRoot}]
|
|
5497
|
+
|
|
5498
|
+
${params.message}`;
|
|
5499
|
+
target.appendUserMessage(params.session_id, params.message);
|
|
5500
|
+
const state = await target.sendUserMessage(params.session_id, prefixed);
|
|
5501
|
+
const interrupted = hard && state === "queued";
|
|
5502
|
+
if (interrupted)
|
|
5503
|
+
target.interruptSession(params.session_id);
|
|
5504
|
+
return JSON.stringify({ code: 0, workspace: wsPath, session_id: params.session_id, state, ...hard ? { interrupted } : {} });
|
|
5505
|
+
}
|
|
5463
5506
|
const relayStop = {
|
|
5464
5507
|
name: "relay_stop",
|
|
5465
5508
|
description: "Stop a session in another workspace (and its sub-agents). If it was mid-turn you will still receive a relay report describing where it was cut off. Returns JSON with code 0 on success.",
|
|
@@ -5510,7 +5553,42 @@ ${params.message}`;
|
|
|
5510
5553
|
}
|
|
5511
5554
|
}
|
|
5512
5555
|
};
|
|
5513
|
-
|
|
5556
|
+
const relayList = {
|
|
5557
|
+
name: "relay_list",
|
|
5558
|
+
description: "List the root sessions of a workspace on this server (most recently active first, up to 100) with id / agent / title / status \u2014 to find an existing session to relay_send into, or to see what a department is working on. `workspace` defaults to your own.",
|
|
5559
|
+
inputSchema: {
|
|
5560
|
+
type: "object",
|
|
5561
|
+
properties: {
|
|
5562
|
+
workspace: { type: "string", description: "Absolute path of the workspace. Omit for the current workspace." }
|
|
5563
|
+
},
|
|
5564
|
+
required: []
|
|
5565
|
+
},
|
|
5566
|
+
callback: async (input) => {
|
|
5567
|
+
const params = input;
|
|
5568
|
+
try {
|
|
5569
|
+
const registry2 = getRelayRegistry();
|
|
5570
|
+
if (!registry2)
|
|
5571
|
+
return jsonErr2("relay is unavailable in this runtime (server only)");
|
|
5572
|
+
const resolved = resolveTarget(registry2, params.workspace ?? host.workspaceRoot);
|
|
5573
|
+
if (typeof resolved === "string")
|
|
5574
|
+
return resolved;
|
|
5575
|
+
const { sessions } = resolved.target.listSessions({ rootOnly: true, limit: 100 });
|
|
5576
|
+
const rows = sessions.map((s) => ({
|
|
5577
|
+
id: s.id,
|
|
5578
|
+
agentId: s.agentId,
|
|
5579
|
+
agentName: s.agentName,
|
|
5580
|
+
title: s.title || s.description,
|
|
5581
|
+
status: s.status,
|
|
5582
|
+
createdAt: s.createdAt,
|
|
5583
|
+
updatedAt: s.updatedAt
|
|
5584
|
+
}));
|
|
5585
|
+
return JSON.stringify({ code: 0, workspace: resolved.wsPath, sessions: rows, count: rows.length });
|
|
5586
|
+
} catch (err) {
|
|
5587
|
+
return jsonErr2(err instanceof Error ? err.message : String(err));
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
};
|
|
5591
|
+
return [relaySend, relayInterrupt, relayStop, relayRead, relayList];
|
|
5514
5592
|
}
|
|
5515
5593
|
var _registry, WORKSPACE_SESSION_PROPS;
|
|
5516
5594
|
var init_relay = __esm({
|
|
@@ -11210,6 +11288,342 @@ var init_session_state_store = __esm({
|
|
|
11210
11288
|
}
|
|
11211
11289
|
});
|
|
11212
11290
|
|
|
11291
|
+
// ../server/dist/observability/otel-sdk.js
|
|
11292
|
+
var otel_sdk_exports = {};
|
|
11293
|
+
__export(otel_sdk_exports, {
|
|
11294
|
+
registerSdk: () => registerSdk
|
|
11295
|
+
});
|
|
11296
|
+
import { metrics } from "@opentelemetry/api";
|
|
11297
|
+
import { logs } from "@opentelemetry/api-logs";
|
|
11298
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
11299
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
11300
|
+
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
11301
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
11302
|
+
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
11303
|
+
import { LoggerProvider, BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
11304
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
11305
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
|
|
11306
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
|
|
11307
|
+
function registerSdk(serviceName, serviceVersion) {
|
|
11308
|
+
const resource = resourceFromAttributes({
|
|
11309
|
+
[ATTR_SERVICE_NAME]: serviceName,
|
|
11310
|
+
[ATTR_SERVICE_VERSION]: serviceVersion
|
|
11311
|
+
});
|
|
11312
|
+
const tracerProvider = new NodeTracerProvider({
|
|
11313
|
+
resource,
|
|
11314
|
+
spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())]
|
|
11315
|
+
});
|
|
11316
|
+
tracerProvider.register();
|
|
11317
|
+
const meterProvider = new MeterProvider({
|
|
11318
|
+
resource,
|
|
11319
|
+
readers: [new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter(), exportIntervalMillis: 15e3 })]
|
|
11320
|
+
});
|
|
11321
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
11322
|
+
const loggerProvider = new LoggerProvider({
|
|
11323
|
+
resource,
|
|
11324
|
+
processors: [new BatchLogRecordProcessor({ exporter: new OTLPLogExporter() })]
|
|
11325
|
+
});
|
|
11326
|
+
logs.setGlobalLoggerProvider(loggerProvider);
|
|
11327
|
+
return [tracerProvider, meterProvider, loggerProvider];
|
|
11328
|
+
}
|
|
11329
|
+
var init_otel_sdk = __esm({
|
|
11330
|
+
"../server/dist/observability/otel-sdk.js"() {
|
|
11331
|
+
"use strict";
|
|
11332
|
+
}
|
|
11333
|
+
});
|
|
11334
|
+
|
|
11335
|
+
// ../server/dist/observability/otel.js
|
|
11336
|
+
import { trace, metrics as metrics2, ProxyTracerProvider } from "@opentelemetry/api";
|
|
11337
|
+
import { logs as logs2 } from "@opentelemetry/api-logs";
|
|
11338
|
+
function externalSdkRegistered() {
|
|
11339
|
+
const provider = trace.getTracerProvider();
|
|
11340
|
+
if (!(provider instanceof ProxyTracerProvider))
|
|
11341
|
+
return true;
|
|
11342
|
+
return provider.getDelegate() !== new ProxyTracerProvider().getDelegate();
|
|
11343
|
+
}
|
|
11344
|
+
async function initObservability() {
|
|
11345
|
+
const { endpoint, serviceName, headers } = config.observability;
|
|
11346
|
+
if (endpoint)
|
|
11347
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??= endpoint;
|
|
11348
|
+
if (headers)
|
|
11349
|
+
process.env.OTEL_EXPORTER_OTLP_HEADERS ??= headers;
|
|
11350
|
+
process.env.OTEL_SERVICE_NAME ??= serviceName;
|
|
11351
|
+
process.env.OTEL_EXPORTER_OTLP_PROTOCOL ??= "http/protobuf";
|
|
11352
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT)
|
|
11353
|
+
return false;
|
|
11354
|
+
enabled = true;
|
|
11355
|
+
if (externalSdkRegistered()) {
|
|
11356
|
+
console.log("[Observability] external OpenTelemetry SDK already registered \u2014 using it, skipping built-in exporters");
|
|
11357
|
+
return true;
|
|
11358
|
+
}
|
|
11359
|
+
const { registerSdk: registerSdk2 } = await Promise.resolve().then(() => (init_otel_sdk(), otel_sdk_exports));
|
|
11360
|
+
providers = registerSdk2(process.env.OTEL_SERVICE_NAME, "1.3.0");
|
|
11361
|
+
console.log(`[Observability] OTLP export \u2192 ${process.env.OTEL_EXPORTER_OTLP_ENDPOINT} (service.name=${process.env.OTEL_SERVICE_NAME}, capture_content=${config.observability.captureContent})`);
|
|
11362
|
+
return true;
|
|
11363
|
+
}
|
|
11364
|
+
async function shutdownObservability() {
|
|
11365
|
+
if (providers.length === 0)
|
|
11366
|
+
return;
|
|
11367
|
+
const all = Promise.all(providers.map(async (p) => {
|
|
11368
|
+
try {
|
|
11369
|
+
await p.forceFlush();
|
|
11370
|
+
} catch {
|
|
11371
|
+
}
|
|
11372
|
+
try {
|
|
11373
|
+
await p.shutdown();
|
|
11374
|
+
} catch {
|
|
11375
|
+
}
|
|
11376
|
+
}));
|
|
11377
|
+
await Promise.race([all, new Promise((resolve2) => setTimeout(resolve2, 3e3))]);
|
|
11378
|
+
}
|
|
11379
|
+
var SCOPE, enabled, tracer, getMeter, otelLogger, captureContent, providers;
|
|
11380
|
+
var init_otel = __esm({
|
|
11381
|
+
"../server/dist/observability/otel.js"() {
|
|
11382
|
+
"use strict";
|
|
11383
|
+
init_config();
|
|
11384
|
+
SCOPE = "opentelemetry.instrumentation.halo";
|
|
11385
|
+
enabled = false;
|
|
11386
|
+
tracer = trace.getTracer(SCOPE, "1");
|
|
11387
|
+
getMeter = () => metrics2.getMeter(SCOPE, "1");
|
|
11388
|
+
otelLogger = logs2.getLogger(SCOPE, "1");
|
|
11389
|
+
captureContent = () => config.observability.captureContent;
|
|
11390
|
+
providers = [];
|
|
11391
|
+
}
|
|
11392
|
+
});
|
|
11393
|
+
|
|
11394
|
+
// ../server/dist/observability/genai-spans.js
|
|
11395
|
+
import { context, trace as trace2, SpanStatusCode } from "@opentelemetry/api";
|
|
11396
|
+
function instruments() {
|
|
11397
|
+
const meter = getMeter();
|
|
11398
|
+
return {
|
|
11399
|
+
tokenUsage: meter.createHistogram("gen_ai.client.token.usage", { unit: "{token}", description: "Tokens per model call, split by gen_ai.token.type" }),
|
|
11400
|
+
operationDuration: meter.createHistogram("gen_ai.client.operation.duration", { unit: "s", description: "Model call latency" }),
|
|
11401
|
+
toolDuration: meter.createHistogram("halo.tool.duration", { unit: "s", description: "Tool execution latency" }),
|
|
11402
|
+
turnDuration: meter.createHistogram("halo.turn.duration", { unit: "s", description: "Agent turn latency (invoke_agent)" }),
|
|
11403
|
+
modelRetries: meter.createCounter("halo.model.retries", { description: "Model-call retries by kind" })
|
|
11404
|
+
};
|
|
11405
|
+
}
|
|
11406
|
+
function cap(text6, max) {
|
|
11407
|
+
return text6.length > max ? `${text6.slice(0, max)}\u2026[truncated ${text6.length - max} chars]` : text6;
|
|
11408
|
+
}
|
|
11409
|
+
function imagePlaceholder(source) {
|
|
11410
|
+
return `[image mimeType=${source.media_type} size=${Math.floor(source.data.length * 3 / 4)}B]`;
|
|
11411
|
+
}
|
|
11412
|
+
function inputText(message) {
|
|
11413
|
+
if (typeof message === "string")
|
|
11414
|
+
return message;
|
|
11415
|
+
return message.map((b) => {
|
|
11416
|
+
if (b.type === "text")
|
|
11417
|
+
return b.text;
|
|
11418
|
+
if (b.type === "image")
|
|
11419
|
+
return imagePlaceholder(b.source);
|
|
11420
|
+
return "";
|
|
11421
|
+
}).filter(Boolean).join("\n");
|
|
11422
|
+
}
|
|
11423
|
+
function toSemconvMessage(m) {
|
|
11424
|
+
if (typeof m.content === "string")
|
|
11425
|
+
return { role: m.role, parts: [{ type: "text", content: cap(m.content, PART_CAP) }] };
|
|
11426
|
+
const parts = [];
|
|
11427
|
+
let onlyToolResults = m.content.length > 0;
|
|
11428
|
+
for (const b of m.content) {
|
|
11429
|
+
if (b.type !== "tool_result")
|
|
11430
|
+
onlyToolResults = false;
|
|
11431
|
+
switch (b.type) {
|
|
11432
|
+
case "text":
|
|
11433
|
+
parts.push({ type: "text", content: cap(b.text, PART_CAP) });
|
|
11434
|
+
break;
|
|
11435
|
+
case "image":
|
|
11436
|
+
parts.push({ type: "text", content: imagePlaceholder(b.source) });
|
|
11437
|
+
break;
|
|
11438
|
+
case "tool_use":
|
|
11439
|
+
parts.push({ type: "tool_call", id: b.id, name: b.name, arguments: b.input });
|
|
11440
|
+
break;
|
|
11441
|
+
case "tool_result": {
|
|
11442
|
+
const text6 = typeof b.content === "string" ? b.content : b.content.map((c) => c.type === "text" ? c.text : imagePlaceholder(c.source)).join("\n");
|
|
11443
|
+
parts.push({ type: "tool_call_response", id: b.tool_use_id, response: cap(text6, PART_CAP) });
|
|
11444
|
+
break;
|
|
11445
|
+
}
|
|
11446
|
+
}
|
|
11447
|
+
}
|
|
11448
|
+
return { role: onlyToolResults ? "tool" : m.role, parts };
|
|
11449
|
+
}
|
|
11450
|
+
function messagesAttr(messages2) {
|
|
11451
|
+
const parts = messages2.map((m) => JSON.stringify(toSemconvMessage(m)));
|
|
11452
|
+
let total = parts.reduce((n, p) => n + p.length + 1, 1);
|
|
11453
|
+
let dropped = 0;
|
|
11454
|
+
while (total > ATTR_CAP - 100 && dropped < parts.length)
|
|
11455
|
+
total -= parts[dropped++].length + 1;
|
|
11456
|
+
if (dropped === 0)
|
|
11457
|
+
return `[${parts.join(",")}]`;
|
|
11458
|
+
const marker = JSON.stringify({ role: "user", parts: [{ type: "text", content: `[${dropped} earlier messages omitted]` }] });
|
|
11459
|
+
return `[${[marker, ...parts.slice(dropped)].join(",")}]`;
|
|
11460
|
+
}
|
|
11461
|
+
function beginTurn(session, message) {
|
|
11462
|
+
if (!enabled)
|
|
11463
|
+
return;
|
|
11464
|
+
const startMs = Date.now();
|
|
11465
|
+
const span = tracer.startSpan(`invoke_agent ${session.agentName}`, {
|
|
11466
|
+
startTime: startMs,
|
|
11467
|
+
attributes: {
|
|
11468
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
11469
|
+
"gen_ai.system": SYSTEM,
|
|
11470
|
+
"gen_ai.agent.name": session.agentName,
|
|
11471
|
+
"gen_ai.request.model": session.currentModelId,
|
|
11472
|
+
"session.id": session.id
|
|
11473
|
+
}
|
|
11474
|
+
});
|
|
11475
|
+
turns.set(session.id, {
|
|
11476
|
+
span,
|
|
11477
|
+
ctx: trace2.setSpan(context.active(), span),
|
|
11478
|
+
startMs,
|
|
11479
|
+
input: inputText(message),
|
|
11480
|
+
finalText: "",
|
|
11481
|
+
allText: "",
|
|
11482
|
+
cycleToolCalls: [],
|
|
11483
|
+
pendingTools: /* @__PURE__ */ new Map()
|
|
11484
|
+
});
|
|
11485
|
+
}
|
|
11486
|
+
function onAgentEvent(session, event) {
|
|
11487
|
+
if (!enabled)
|
|
11488
|
+
return;
|
|
11489
|
+
const turn = turns.get(session.id);
|
|
11490
|
+
if (!turn)
|
|
11491
|
+
return;
|
|
11492
|
+
switch (event.type) {
|
|
11493
|
+
case "text":
|
|
11494
|
+
turn.allText += event.text ?? "";
|
|
11495
|
+
if (event.final)
|
|
11496
|
+
turn.finalText += event.text ?? "";
|
|
11497
|
+
return;
|
|
11498
|
+
case "tool_call":
|
|
11499
|
+
turn.cycleToolCalls.push({ id: event.toolUseId ?? "", name: event.toolName ?? "", args: event.toolInput });
|
|
11500
|
+
return;
|
|
11501
|
+
case "usage":
|
|
11502
|
+
recordModelCall(session, turn, event);
|
|
11503
|
+
return;
|
|
11504
|
+
case "tool_result":
|
|
11505
|
+
recordToolCall(session, turn, event);
|
|
11506
|
+
return;
|
|
11507
|
+
default:
|
|
11508
|
+
return;
|
|
11509
|
+
}
|
|
11510
|
+
}
|
|
11511
|
+
function recordModelCall(session, turn, event) {
|
|
11512
|
+
const now = Date.now();
|
|
11513
|
+
const durationMs = event.durationMs ?? 0;
|
|
11514
|
+
const model = session.currentModelId;
|
|
11515
|
+
const span = tracer.startSpan(`chat ${model}`, {
|
|
11516
|
+
startTime: now - durationMs,
|
|
11517
|
+
attributes: {
|
|
11518
|
+
"gen_ai.operation.name": "chat",
|
|
11519
|
+
"gen_ai.system": SYSTEM,
|
|
11520
|
+
"gen_ai.request.model": model,
|
|
11521
|
+
"gen_ai.usage.input_tokens": event.usage?.inputTokens ?? 0,
|
|
11522
|
+
"gen_ai.usage.output_tokens": event.usage?.outputTokens ?? 0,
|
|
11523
|
+
"gen_ai.response.finish_reasons": [turn.cycleToolCalls.length > 0 ? "tool_use" : "end_turn"],
|
|
11524
|
+
"session.id": session.id
|
|
11525
|
+
}
|
|
11526
|
+
}, turn.ctx);
|
|
11527
|
+
if (captureContent()) {
|
|
11528
|
+
const messages2 = session.agent.messages;
|
|
11529
|
+
const last = messages2[messages2.length - 1];
|
|
11530
|
+
const hasAssistantTail = last?.role === "assistant";
|
|
11531
|
+
span.setAttribute("gen_ai.input.messages", messagesAttr(hasAssistantTail ? messages2.slice(0, -1) : messages2));
|
|
11532
|
+
if (hasAssistantTail)
|
|
11533
|
+
span.setAttribute("gen_ai.output.messages", messagesAttr([last]));
|
|
11534
|
+
span.setAttribute("gen_ai.system_instructions", cap(session.systemPrompt, SYSTEM_PROMPT_CAP));
|
|
11535
|
+
}
|
|
11536
|
+
span.end(now);
|
|
11537
|
+
const chatCtx = trace2.setSpan(turn.ctx, span);
|
|
11538
|
+
for (const tc of turn.cycleToolCalls) {
|
|
11539
|
+
turn.pendingTools.set(tc.id, { name: tc.name, args: tc.args, parentCtx: chatCtx, startMs: now });
|
|
11540
|
+
}
|
|
11541
|
+
turn.cycleToolCalls = [];
|
|
11542
|
+
const modelAttr = { "gen_ai.request.model": model };
|
|
11543
|
+
const m = metricsFor();
|
|
11544
|
+
m.tokenUsage.record(event.usage?.inputTokens ?? 0, { ...modelAttr, "gen_ai.token.type": "input" });
|
|
11545
|
+
m.tokenUsage.record(event.usage?.outputTokens ?? 0, { ...modelAttr, "gen_ai.token.type": "output" });
|
|
11546
|
+
m.operationDuration.record(durationMs / 1e3, { ...modelAttr, "gen_ai.operation.name": "chat" });
|
|
11547
|
+
}
|
|
11548
|
+
function recordToolCall(session, turn, event) {
|
|
11549
|
+
const id = event.toolUseId ?? "";
|
|
11550
|
+
const pending = turn.pendingTools.get(id);
|
|
11551
|
+
if (!pending)
|
|
11552
|
+
return;
|
|
11553
|
+
turn.pendingTools.delete(id);
|
|
11554
|
+
const now = Date.now();
|
|
11555
|
+
const durationMs = event.durationMs ?? 0;
|
|
11556
|
+
const span = tracer.startSpan(`execute_tool ${pending.name}`, {
|
|
11557
|
+
startTime: now - durationMs,
|
|
11558
|
+
attributes: {
|
|
11559
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11560
|
+
"gen_ai.system": SYSTEM,
|
|
11561
|
+
"gen_ai.tool.name": pending.name,
|
|
11562
|
+
"gen_ai.tool.call.id": id,
|
|
11563
|
+
"session.id": session.id
|
|
11564
|
+
}
|
|
11565
|
+
}, pending.parentCtx);
|
|
11566
|
+
if (captureContent()) {
|
|
11567
|
+
span.setAttribute("gen_ai.tool.call.arguments", cap(JSON.stringify(pending.args ?? null), ATTR_CAP));
|
|
11568
|
+
span.setAttribute("gen_ai.tool.call.result", cap(event.toolResult || "[no output]", ATTR_CAP));
|
|
11569
|
+
}
|
|
11570
|
+
span.end(now);
|
|
11571
|
+
metricsFor().toolDuration.record(durationMs / 1e3, { "gen_ai.tool.name": pending.name });
|
|
11572
|
+
}
|
|
11573
|
+
function endTurn(session, outcome) {
|
|
11574
|
+
if (!enabled)
|
|
11575
|
+
return;
|
|
11576
|
+
const turn = turns.get(session.id);
|
|
11577
|
+
if (!turn)
|
|
11578
|
+
return;
|
|
11579
|
+
turns.delete(session.id);
|
|
11580
|
+
const now = Date.now();
|
|
11581
|
+
for (const [id, pending] of turn.pendingTools) {
|
|
11582
|
+
const orphan = tracer.startSpan(`execute_tool ${pending.name}`, {
|
|
11583
|
+
startTime: pending.startMs,
|
|
11584
|
+
attributes: {
|
|
11585
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11586
|
+
"gen_ai.system": SYSTEM,
|
|
11587
|
+
"gen_ai.tool.name": pending.name,
|
|
11588
|
+
"gen_ai.tool.call.id": id,
|
|
11589
|
+
"session.id": session.id,
|
|
11590
|
+
"halo.tool.orphaned": true
|
|
11591
|
+
}
|
|
11592
|
+
}, pending.parentCtx);
|
|
11593
|
+
orphan.end(now);
|
|
11594
|
+
}
|
|
11595
|
+
if (captureContent()) {
|
|
11596
|
+
turn.span.setAttribute("gen_ai.task.input", cap(turn.input, ATTR_CAP));
|
|
11597
|
+
turn.span.setAttribute("gen_ai.task.output", cap(turn.finalText || turn.allText, ATTR_CAP));
|
|
11598
|
+
}
|
|
11599
|
+
if (outcome.error) {
|
|
11600
|
+
turn.span.setStatus({ code: SpanStatusCode.ERROR, message: outcome.error });
|
|
11601
|
+
const errName = outcome.error.match(/^([A-Za-z]\w*(?:Exception|Error))\b/)?.[1];
|
|
11602
|
+
turn.span.setAttribute("error.type", errName ?? "_OTHER");
|
|
11603
|
+
}
|
|
11604
|
+
turn.span.end(now);
|
|
11605
|
+
metricsFor().turnDuration.record((now - turn.startMs) / 1e3, { "gen_ai.agent.name": session.agentName, outcome: outcome.error ? "error" : "ok" });
|
|
11606
|
+
}
|
|
11607
|
+
function recordRetry(kind) {
|
|
11608
|
+
if (!enabled)
|
|
11609
|
+
return;
|
|
11610
|
+
metricsFor().modelRetries.add(1, { kind });
|
|
11611
|
+
}
|
|
11612
|
+
var turns, SYSTEM, PART_CAP, SYSTEM_PROMPT_CAP, ATTR_CAP, cachedInstruments, metricsFor;
|
|
11613
|
+
var init_genai_spans = __esm({
|
|
11614
|
+
"../server/dist/observability/genai-spans.js"() {
|
|
11615
|
+
"use strict";
|
|
11616
|
+
init_otel();
|
|
11617
|
+
turns = /* @__PURE__ */ new Map();
|
|
11618
|
+
SYSTEM = "halo";
|
|
11619
|
+
PART_CAP = 2 * 1024;
|
|
11620
|
+
SYSTEM_PROMPT_CAP = 8 * 1024;
|
|
11621
|
+
ATTR_CAP = 32 * 1024;
|
|
11622
|
+
cachedInstruments = null;
|
|
11623
|
+
metricsFor = () => cachedInstruments ??= instruments();
|
|
11624
|
+
}
|
|
11625
|
+
});
|
|
11626
|
+
|
|
11213
11627
|
// ../server/dist/agents/session-manager.js
|
|
11214
11628
|
import fsSync7 from "node:fs";
|
|
11215
11629
|
import path30 from "node:path";
|
|
@@ -11340,6 +11754,7 @@ var init_session_manager = __esm({
|
|
|
11340
11754
|
init_session_agent_builder();
|
|
11341
11755
|
init_session_skill_commands();
|
|
11342
11756
|
init_session_state_store();
|
|
11757
|
+
init_genai_spans();
|
|
11343
11758
|
SessionManager = class _SessionManager {
|
|
11344
11759
|
// The fields below are marked `internal:` because they're consumed by
|
|
11345
11760
|
// helpers in this directory (session-tools.ts). Treat them as
|
|
@@ -12028,6 +12443,7 @@ var init_session_manager = __esm({
|
|
|
12028
12443
|
const i = message.findIndex((b) => b.type === "text");
|
|
12029
12444
|
message = i === -1 ? [{ type: "text", text: stamp }, ...message] : message.map((b, j) => j === i && b.type === "text" ? { type: "text", text: stamped(b.text) } : b);
|
|
12030
12445
|
}
|
|
12446
|
+
beginTurn(session, message);
|
|
12031
12447
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
12032
12448
|
session.abortController = new AbortController();
|
|
12033
12449
|
const signal = session.abortController.signal;
|
|
@@ -12047,6 +12463,7 @@ var init_session_manager = __esm({
|
|
|
12047
12463
|
if (event.type === "text") {
|
|
12048
12464
|
resultText += event.text ?? "";
|
|
12049
12465
|
}
|
|
12466
|
+
onAgentEvent(session, event);
|
|
12050
12467
|
this.processSessionEvent(session, event);
|
|
12051
12468
|
if (session.interruptRequested && event.type === "tool_result") {
|
|
12052
12469
|
console.debug(`[SessionManager] Graceful interrupt after tool result in ${session.id}`);
|
|
@@ -12079,8 +12496,10 @@ var init_session_manager = __esm({
|
|
|
12079
12496
|
if (result.compacted) {
|
|
12080
12497
|
session.agent.messages = result.messages;
|
|
12081
12498
|
this.emitEvent(session.id, { type: "system", text: `Session ${session.agentId} context compacted (${result.messages.length} messages remaining, local fallback)` });
|
|
12082
|
-
if (attempt + 1 < maxRetries)
|
|
12499
|
+
if (attempt + 1 < maxRetries) {
|
|
12500
|
+
recordRetry("context_overflow");
|
|
12083
12501
|
continue;
|
|
12502
|
+
}
|
|
12084
12503
|
}
|
|
12085
12504
|
}
|
|
12086
12505
|
const isAccountError = httpStatus !== void 0 ? httpStatus === 401 || httpStatus === 402 || httpStatus === 403 : msg.includes("insufficient balance") || msg.includes("suspended") || msg.includes("invalid api key") || msg.includes("Invalid API Key") || msg.includes("Unauthorized") || msg.includes("authentication");
|
|
@@ -12097,6 +12516,7 @@ var init_session_manager = __esm({
|
|
|
12097
12516
|
const jitter = Math.random() * 1e3;
|
|
12098
12517
|
const delay = Math.min(baseDelay + jitter, 6e4);
|
|
12099
12518
|
console.debug(`[SessionManager] Session ${session.id} rate limited, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
12519
|
+
recordRetry("throttle");
|
|
12100
12520
|
this.emitEvent(session.id, { type: "system", text: `Session ${session.agentId} rate limited, retrying in ${Math.round(delay / 1e3)}s...` });
|
|
12101
12521
|
await sleep(delay);
|
|
12102
12522
|
continue;
|
|
@@ -12108,6 +12528,7 @@ var init_session_manager = __esm({
|
|
|
12108
12528
|
const jitter = Math.random() * 1e3;
|
|
12109
12529
|
const delay = Math.min(baseDelay + jitter, 6e4);
|
|
12110
12530
|
console.debug(`[SessionManager] Session ${session.id} transient server error ${errName || httpStatus}, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
12531
|
+
recordRetry("server_error");
|
|
12111
12532
|
this.emitEvent(session.id, { type: "system", text: `Session ${session.agentId} hit a transient server error, retrying in ${Math.round(delay / 1e3)}s...` });
|
|
12112
12533
|
await sleep(delay);
|
|
12113
12534
|
continue;
|
|
@@ -12117,6 +12538,7 @@ var init_session_manager = __esm({
|
|
|
12117
12538
|
if (attempt + 1 < maxRetries) {
|
|
12118
12539
|
const delay = 1e3 * Math.pow(2, attempt) + Math.random() * 500;
|
|
12119
12540
|
console.warn(`[SessionManager] Session ${session.id} transient network error, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries}): ${msg}`);
|
|
12541
|
+
recordRetry("network");
|
|
12120
12542
|
this.emitEvent(session.id, { type: "system", text: `Network hiccup, retrying in ${Math.round(delay / 1e3)}s...` });
|
|
12121
12543
|
await sleep(delay);
|
|
12122
12544
|
continue;
|
|
@@ -12126,6 +12548,7 @@ var init_session_manager = __esm({
|
|
|
12126
12548
|
if (attempt + 1 < maxRetries) {
|
|
12127
12549
|
const delay = 1e3 * Math.pow(2, attempt) + Math.random() * 500;
|
|
12128
12550
|
console.debug(`[SessionManager] Session ${session.id} Mantle empty response, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries})`);
|
|
12551
|
+
recordRetry("empty_response");
|
|
12129
12552
|
await sleep(delay);
|
|
12130
12553
|
continue;
|
|
12131
12554
|
}
|
|
@@ -12134,6 +12557,7 @@ var init_session_manager = __esm({
|
|
|
12134
12557
|
console.debug(`[SessionManager] Session ${session.id} corrupted conversation (attempt ${attempt + 1}/${maxRetries})`);
|
|
12135
12558
|
session.agent.messages = repairConversationMessages(session.agent.messages, `[Session:${session.id}]`);
|
|
12136
12559
|
if (attempt + 1 < maxRetries) {
|
|
12560
|
+
recordRetry("corrupted");
|
|
12137
12561
|
this.emitEvent(session.id, { type: "system", text: "Repairing conversation state..." });
|
|
12138
12562
|
continue;
|
|
12139
12563
|
}
|
|
@@ -12148,6 +12572,7 @@ var init_session_manager = __esm({
|
|
|
12148
12572
|
this.saveAgentState(session);
|
|
12149
12573
|
console.warn(`[SessionManager] Session ${session.id} multimodal 4xx \u2014 replaced ${replaced} image block(s) with placeholders (attempt ${attempt + 1}/${maxRetries})`);
|
|
12150
12574
|
if (attempt + 1 < maxRetries) {
|
|
12575
|
+
recordRetry("multimodal_4xx");
|
|
12151
12576
|
this.emitEvent(session.id, { type: "system", text: `Model rejected image data \u2014 removed ${replaced} image(s) from history, retrying...` });
|
|
12152
12577
|
continue;
|
|
12153
12578
|
}
|
|
@@ -12162,6 +12587,7 @@ var init_session_manager = __esm({
|
|
|
12162
12587
|
}
|
|
12163
12588
|
}
|
|
12164
12589
|
session.abortController = null;
|
|
12590
|
+
endTurn(session, { error: session.turnError ?? void 0 });
|
|
12165
12591
|
session.agent.messages = repairConversationMessages(session.agent.messages, `[Session:${session.id}]`);
|
|
12166
12592
|
this.db.update(agentSessions).set({ updatedAt: Date.now() }).where(eq9(agentSessions.id, session.id)).run();
|
|
12167
12593
|
await this.maybeAutoCompact(session);
|
|
@@ -13257,12 +13683,12 @@ ${result.summary}`, agentName: "user", report: true, taskId });
|
|
|
13257
13683
|
* caller wants and also eats the trailing JSON fields. Self-cap here,
|
|
13258
13684
|
* tail-kept, under the generic cap so the envelope always survives. */
|
|
13259
13685
|
truncateOutputTail(output) {
|
|
13260
|
-
const
|
|
13261
|
-
if (output.length <=
|
|
13686
|
+
const cap2 = config.limits.toolResultMax - 500;
|
|
13687
|
+
if (output.length <= cap2)
|
|
13262
13688
|
return output;
|
|
13263
|
-
return `[Output truncated: ${output.length} chars total, showing LAST ${
|
|
13689
|
+
return `[Output truncated: ${output.length} chars total, showing LAST ${cap2}. Earlier text omitted.]
|
|
13264
13690
|
|
|
13265
|
-
` + output.slice(-
|
|
13691
|
+
` + output.slice(-cap2);
|
|
13266
13692
|
}
|
|
13267
13693
|
getSessionOutput(sessionId) {
|
|
13268
13694
|
const session = this.sessions.get(sessionId);
|
|
@@ -13432,6 +13858,7 @@ var init_session_manager_registry = __esm({
|
|
|
13432
13858
|
import fs25 from "node:fs";
|
|
13433
13859
|
import path31 from "node:path";
|
|
13434
13860
|
import { homedir as homedir13 } from "node:os";
|
|
13861
|
+
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
13435
13862
|
function ensureDir() {
|
|
13436
13863
|
fs25.mkdirSync(logDir, { recursive: true });
|
|
13437
13864
|
}
|
|
@@ -13493,6 +13920,18 @@ function shouldLog(level) {
|
|
|
13493
13920
|
const threshold = LEVEL_ORDER[config.logging.level] ?? LEVEL_ORDER.warn;
|
|
13494
13921
|
return LEVEL_ORDER[level] >= threshold;
|
|
13495
13922
|
}
|
|
13923
|
+
function emitOtelLog(level, args) {
|
|
13924
|
+
if (!enabled)
|
|
13925
|
+
return;
|
|
13926
|
+
const body = formatArgs(args);
|
|
13927
|
+
const module = body.match(/^\[([^\]]+)\]/)?.[1];
|
|
13928
|
+
otelLogger.emit({
|
|
13929
|
+
severityNumber: OTEL_SEVERITY[level],
|
|
13930
|
+
severityText: level.toUpperCase(),
|
|
13931
|
+
body,
|
|
13932
|
+
attributes: module ? { "halo.module": module } : void 0
|
|
13933
|
+
});
|
|
13934
|
+
}
|
|
13496
13935
|
function initLogger() {
|
|
13497
13936
|
const origLog = console.log.bind(console);
|
|
13498
13937
|
const origError = console.error.bind(console);
|
|
@@ -13503,39 +13942,50 @@ function initLogger() {
|
|
|
13503
13942
|
return;
|
|
13504
13943
|
origDebug(...args);
|
|
13505
13944
|
writeToFile(formatLine("debug", args));
|
|
13945
|
+
emitOtelLog("debug", args);
|
|
13506
13946
|
};
|
|
13507
13947
|
console.log = (...args) => {
|
|
13508
13948
|
if (!shouldLog("info"))
|
|
13509
13949
|
return;
|
|
13510
13950
|
origLog(...args);
|
|
13511
13951
|
writeToFile(formatLine("info", args));
|
|
13952
|
+
emitOtelLog("info", args);
|
|
13512
13953
|
};
|
|
13513
13954
|
console.warn = (...args) => {
|
|
13514
13955
|
if (!shouldLog("warn"))
|
|
13515
13956
|
return;
|
|
13516
13957
|
origWarn(...args);
|
|
13517
13958
|
writeToFile(formatLine("warn", args));
|
|
13959
|
+
emitOtelLog("warn", args);
|
|
13518
13960
|
};
|
|
13519
13961
|
console.error = (...args) => {
|
|
13520
13962
|
if (!shouldLog("error"))
|
|
13521
13963
|
return;
|
|
13522
13964
|
origError(...args);
|
|
13523
13965
|
writeToFile(formatLine("error", args));
|
|
13966
|
+
emitOtelLog("error", args);
|
|
13524
13967
|
};
|
|
13525
13968
|
if (shouldLog("info")) {
|
|
13526
13969
|
writeToFile(formatLine("info", [`[Logger] File logging initialized: ${logFile} (level: ${config.logging.level})`]));
|
|
13527
13970
|
}
|
|
13528
13971
|
}
|
|
13529
|
-
var LOG_FILENAME, logDir, logFile, currentSize, LEVEL_ORDER;
|
|
13972
|
+
var LOG_FILENAME, logDir, logFile, currentSize, LEVEL_ORDER, OTEL_SEVERITY;
|
|
13530
13973
|
var init_logger = __esm({
|
|
13531
13974
|
"../server/dist/logger.js"() {
|
|
13532
13975
|
"use strict";
|
|
13533
13976
|
init_config();
|
|
13977
|
+
init_otel();
|
|
13534
13978
|
LOG_FILENAME = "server.log";
|
|
13535
13979
|
logDir = path31.join(homedir13(), ".halo", "global", "logs");
|
|
13536
13980
|
logFile = path31.join(logDir, LOG_FILENAME);
|
|
13537
13981
|
currentSize = -1;
|
|
13538
13982
|
LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
13983
|
+
OTEL_SEVERITY = {
|
|
13984
|
+
debug: SeverityNumber.DEBUG,
|
|
13985
|
+
info: SeverityNumber.INFO,
|
|
13986
|
+
warn: SeverityNumber.WARN,
|
|
13987
|
+
error: SeverityNumber.ERROR
|
|
13988
|
+
};
|
|
13539
13989
|
}
|
|
13540
13990
|
});
|
|
13541
13991
|
|
|
@@ -15263,7 +15713,7 @@ function filterVerbs(items, rawValue) {
|
|
|
15263
15713
|
const partial = m[2].toLowerCase();
|
|
15264
15714
|
return cmd.verbs.filter((v) => v.name.startsWith(partial) && v.name !== partial).map((v) => ({ slashName: `${cmd.slashName} ${v.name}`, description: v.desc ?? "" }));
|
|
15265
15715
|
}
|
|
15266
|
-
function InputBox({ enabled, placeholder, history, onSubmit, hint, commands = [], workspace, onPopupOpenChange }) {
|
|
15716
|
+
function InputBox({ enabled: enabled2, placeholder, history, onSubmit, hint, commands = [], workspace, onPopupOpenChange }) {
|
|
15267
15717
|
const [edit, setEdit] = useState2({ value: "", cursor: 0 });
|
|
15268
15718
|
const { value, cursor } = edit;
|
|
15269
15719
|
const [historyIdx, setHistoryIdx] = useState2(null);
|
|
@@ -15290,7 +15740,7 @@ function InputBox({ enabled, placeholder, history, onSubmit, hint, commands = []
|
|
|
15290
15740
|
insertion = normalized;
|
|
15291
15741
|
}
|
|
15292
15742
|
applyEdit((e) => insertAt(e, insertion));
|
|
15293
|
-
}, { isActive:
|
|
15743
|
+
}, { isActive: enabled2 });
|
|
15294
15744
|
const expandPastes = (v) => v.replace(/\[#(\d+) pasted \d+ lines?\]/g, (m, id) => pendingPastes.current.get(Number(id)) ?? m);
|
|
15295
15745
|
const cmdMatches = useMemo(() => filterCommands(commands, value), [commands, value]);
|
|
15296
15746
|
const verbMatches = useMemo(() => filterVerbs(commands, value), [commands, value]);
|
|
@@ -15476,17 +15926,17 @@ function InputBox({ enabled, placeholder, history, onSubmit, hint, commands = []
|
|
|
15476
15926
|
if (input.length === 0) return;
|
|
15477
15927
|
const clean = input.replace(/[\r\n]+/g, " ");
|
|
15478
15928
|
applyEdit((e) => insertAt(e, clean));
|
|
15479
|
-
}, { isActive:
|
|
15929
|
+
}, { isActive: enabled2 });
|
|
15480
15930
|
useEffect2(() => {
|
|
15481
15931
|
if (historyIdx !== null && value === "") setHistoryIdx(null);
|
|
15482
15932
|
}, [value, historyIdx]);
|
|
15483
15933
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
15484
15934
|
suggestOpen ? /* @__PURE__ */ jsx8(SlashSuggest, { items: filtered, selectedIdx: suggestIdx }) : null,
|
|
15485
15935
|
!suggestOpen && pathOpen ? /* @__PURE__ */ jsx8(PathSuggest, { items: pathScan.items, selectedIdx: pathIdx, cwdLabel: pathScan.cwdLabel }) : null,
|
|
15486
|
-
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor:
|
|
15936
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: enabled2 ? "cyan" : "gray", paddingX: 1, children: [
|
|
15487
15937
|
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
15488
|
-
/* @__PURE__ */ jsx8(Text8, { color:
|
|
15489
|
-
|
|
15938
|
+
/* @__PURE__ */ jsx8(Text8, { color: enabled2 ? "cyan" : "gray", children: "> " }),
|
|
15939
|
+
enabled2 ? /* @__PURE__ */ jsx8(TextInputView, { value, cursor, placeholder }) : /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: placeholder })
|
|
15490
15940
|
] }),
|
|
15491
15941
|
hint ? /* @__PURE__ */ jsx8(Box7, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: hint }) }) : null
|
|
15492
15942
|
] })
|
|
@@ -18267,7 +18717,14 @@ function getAvailableTools() {
|
|
|
18267
18717
|
return _cachedTools;
|
|
18268
18718
|
const tools = createWorkspaceTools("/tmp");
|
|
18269
18719
|
const { tool: draft } = createDraftTool();
|
|
18270
|
-
|
|
18720
|
+
const relay = buildRelayTools({ workspaceRoot: "/tmp" }, "");
|
|
18721
|
+
const relayChip = {
|
|
18722
|
+
name: "relay_send",
|
|
18723
|
+
description: `Cross-workspace relay (one toggle, grants the whole set; full-access sessions only): ${relay.map((t2) => t2.name).join(", ")}.
|
|
18724
|
+
|
|
18725
|
+
${relay[0].description}`
|
|
18726
|
+
};
|
|
18727
|
+
_cachedTools = [...tools, draft].map((t2) => ({ name: t2.name, description: t2.description })).concat(relayChip);
|
|
18271
18728
|
return _cachedTools;
|
|
18272
18729
|
}
|
|
18273
18730
|
async function writeFileAtomic2(filePath, content) {
|
|
@@ -18304,10 +18761,10 @@ function parseAgentYaml(content) {
|
|
|
18304
18761
|
}
|
|
18305
18762
|
function buildScaffoldModelBlock() {
|
|
18306
18763
|
const registry2 = getModelsRegistry();
|
|
18307
|
-
const
|
|
18764
|
+
const providers2 = registry2.providers ?? [];
|
|
18308
18765
|
const configured = config.agent.defaultProvider;
|
|
18309
|
-
const explicit = configured ?
|
|
18310
|
-
const provider = explicit ??
|
|
18766
|
+
const explicit = configured ? providers2.find((p) => p.id === configured) : void 0;
|
|
18767
|
+
const provider = explicit ?? providers2.find((p) => p.id === FALLBACK_SCAFFOLD_PROVIDER) ?? providers2[0];
|
|
18311
18768
|
if (!provider) {
|
|
18312
18769
|
return { provider: FALLBACK_SCAFFOLD_PROVIDER };
|
|
18313
18770
|
}
|
|
@@ -18787,6 +19244,7 @@ var init_agent_configs = __esm({
|
|
|
18787
19244
|
"use strict";
|
|
18788
19245
|
init_workspace_tools();
|
|
18789
19246
|
init_draft_tool();
|
|
19247
|
+
init_relay();
|
|
18790
19248
|
init_md_loader();
|
|
18791
19249
|
init_config();
|
|
18792
19250
|
init_db();
|
|
@@ -29700,6 +30158,7 @@ async function gracefulShutdown(signal) {
|
|
|
29700
30158
|
server.close();
|
|
29701
30159
|
}
|
|
29702
30160
|
console.log("[Server] Shutdown complete");
|
|
30161
|
+
await shutdownObservability();
|
|
29703
30162
|
process.exit(0);
|
|
29704
30163
|
}
|
|
29705
30164
|
var __dirname3, PORT, HALO_VERSION, PROJECT_ROOT, HALO_HOME4, GIT_SHA, AGENTCORE, app, allowlist, authRoutes, fileRoutes, dataPreviewRoutes, gitRoutes, agentConfigRoutes, skillRoutes, settingsRoutes, evolutionRoutes, cronRoutes, channelDb, registry, sessionRoutes, sessionArchiveRoutes, showRoutes, metricsRoutes, commandRoutes, FRONTEND_DIR, server, wss;
|
|
@@ -29739,13 +30198,14 @@ var init_dist2 = __esm({
|
|
|
29739
30198
|
init_descriptors();
|
|
29740
30199
|
init_auth();
|
|
29741
30200
|
init_logger();
|
|
30201
|
+
init_otel();
|
|
29742
30202
|
init_config();
|
|
29743
30203
|
init_sandbox();
|
|
29744
30204
|
init_init();
|
|
29745
30205
|
init_git_ssh();
|
|
29746
30206
|
__dirname3 = path67.dirname(fileURLToPath3(import.meta.url));
|
|
29747
30207
|
PORT = config.server.port;
|
|
29748
|
-
HALO_VERSION = "1.
|
|
30208
|
+
HALO_VERSION = "1.3.0";
|
|
29749
30209
|
PROJECT_ROOT = findProjectRoot();
|
|
29750
30210
|
HALO_HOME4 = path67.join(homedir24(), ".halo");
|
|
29751
30211
|
GIT_SHA = readGitSha();
|
|
@@ -29784,6 +30244,7 @@ var init_dist2 = __esm({
|
|
|
29784
30244
|
throw new Error(`[Server] Dispatch cases without a descriptor: ${orphanDispatch.join(", ")}. Add a registerDescriptor entry in commands/index.ts or remove the dispatch case.`);
|
|
29785
30245
|
}
|
|
29786
30246
|
}
|
|
30247
|
+
await initObservability();
|
|
29787
30248
|
initLogger();
|
|
29788
30249
|
await initBwrapCheck();
|
|
29789
30250
|
setSandboxHiddenPaths(config.sandbox.hiddenDirs, config.sandbox.hiddenFiles, config.sandbox.writableDirs);
|
|
@@ -30679,7 +31140,7 @@ import path68 from "node:path";
|
|
|
30679
31140
|
import fs61 from "node:fs";
|
|
30680
31141
|
import { homedir as homedir25 } from "node:os";
|
|
30681
31142
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
30682
|
-
var VERSION = "1.
|
|
31143
|
+
var VERSION = "1.3.0";
|
|
30683
31144
|
var HELP_TOP = `Usage: halo <command> [options]
|
|
30684
31145
|
|
|
30685
31146
|
Commands:
|
|
@@ -31059,8 +31520,8 @@ async function cmdSetup(argv = []) {
|
|
|
31059
31520
|
`);
|
|
31060
31521
|
}
|
|
31061
31522
|
async function stepModelProviders() {
|
|
31062
|
-
const
|
|
31063
|
-
if (
|
|
31523
|
+
const providers2 = listModelProviders2();
|
|
31524
|
+
if (providers2.length === 0) return;
|
|
31064
31525
|
const enter = await promptSelect2("Configure model provider keys now? (you can also re-run `halo setup` later)", [
|
|
31065
31526
|
{ value: "skip", label: "Skip" },
|
|
31066
31527
|
{ value: "go", label: "Configure..." }
|
|
@@ -31071,7 +31532,7 @@ async function cmdSetup(argv = []) {
|
|
|
31071
31532
|
return;
|
|
31072
31533
|
}
|
|
31073
31534
|
while (true) {
|
|
31074
|
-
const opts =
|
|
31535
|
+
const opts = providers2.map((p) => ({
|
|
31075
31536
|
value: p.id,
|
|
31076
31537
|
label: p.displayName,
|
|
31077
31538
|
hint: providerStatusHint(p.id, p.fields)
|
|
@@ -31080,7 +31541,7 @@ async function cmdSetup(argv = []) {
|
|
|
31080
31541
|
const pick = await promptSelect2("Pick a provider to configure (Done when finished):", opts);
|
|
31081
31542
|
if (pick == null) abort();
|
|
31082
31543
|
if (pick === "__done__") break;
|
|
31083
|
-
const provider =
|
|
31544
|
+
const provider = providers2.find((p) => p.id === pick);
|
|
31084
31545
|
if (!provider) continue;
|
|
31085
31546
|
await configureSecretGroup(provider.id, provider.bucket, provider.fields);
|
|
31086
31547
|
}
|