@turmind/halo 1.2.1 → 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.
Files changed (70) hide show
  1. package/admin-out/404.html +1 -1
  2. package/admin-out/_next/static/{AB2GFuqDjcxue9wBBq1zq → Z6KkY5u-XmAPBuUfiAvaG}/_buildManifest.js +1 -1
  3. package/admin-out/_next/static/chunks/132-29179dca2ef093ce.js +1 -0
  4. package/admin-out/_next/static/chunks/{330.3b784cf0dcd69163.js → 170.14d53d7f32df020c.js} +1 -1
  5. package/admin-out/_next/static/chunks/187.9e822f6f0d366d9a.js +1 -0
  6. package/admin-out/_next/static/chunks/188.0cc6998eb0ed3519.js +1 -0
  7. package/admin-out/_next/static/chunks/{1d2d5650-810a3ca1ae20cab5.js → 1d2d5650-f5c38b9df5ca7466.js} +1 -1
  8. package/admin-out/_next/static/chunks/{304eda7d.b1ee7f523653e027.js → 304eda7d.662c3a1a7cd50701.js} +1 -1
  9. package/admin-out/_next/static/chunks/311.3cfaf322ec2a9170.js +1 -0
  10. package/admin-out/_next/static/chunks/401.c5e64882d4305a08.js +1 -0
  11. package/admin-out/_next/static/chunks/{443.d1b289781793f8de.js → 444.c191bedee971e484.js} +1 -1
  12. package/admin-out/_next/static/chunks/496.7997fc6558c88c22.js +1 -0
  13. package/admin-out/_next/static/chunks/530.03c0a6ec971d3528.js +1 -0
  14. package/admin-out/_next/static/chunks/540.a663b46781b2c309.js +1 -0
  15. package/admin-out/_next/static/chunks/556.10e21b9f7b45d412.js +1 -0
  16. package/admin-out/_next/static/chunks/813-9056b8ef0984bee6.js +1 -0
  17. package/admin-out/_next/static/chunks/{533.5f2d4bfe3d076095.js → 841.48a39cec2ef962f3.js} +1 -1
  18. package/admin-out/_next/static/chunks/891.cfe754015339e975.js +1 -0
  19. package/admin-out/_next/static/chunks/915.c6ee9ab9bee0ce9b.js +1 -0
  20. package/admin-out/_next/static/chunks/957-48c734b74e5437a8.js +1 -0
  21. package/admin-out/_next/static/chunks/968.1676baa9449dbd9d.js +1 -0
  22. package/admin-out/_next/static/chunks/app/_not-found/page-2ea4ee5eada04f69.js +1 -0
  23. package/admin-out/_next/static/chunks/app/layout-1853d21b0644f5a1.js +1 -0
  24. package/admin-out/_next/static/chunks/app/page-2ab109c9a25f867e.js +1 -0
  25. package/admin-out/_next/static/chunks/db534642.a148101e95a7d317.js +1 -0
  26. package/admin-out/_next/static/chunks/e3080c2c-fbf81b7cad547976.js +1 -0
  27. package/admin-out/_next/static/chunks/framework-67e5f0af3ea64f2b.js +1 -0
  28. package/admin-out/_next/static/chunks/main-6b28974253a3bc7c.js +1 -0
  29. package/admin-out/_next/static/chunks/main-app-3367dd99b8642c55.js +1 -0
  30. package/admin-out/_next/static/chunks/pages/_app-51a53534a0a4a898.js +1 -0
  31. package/admin-out/_next/static/chunks/pages/_error-dfd19566fa6f79ad.js +1 -0
  32. package/admin-out/_next/static/chunks/webpack-bf41edfde9cdefec.js +1 -0
  33. package/admin-out/index.html +1 -1
  34. package/admin-out/index.txt +13 -13
  35. package/bundled-docs/design/agentcore.md +9 -0
  36. package/bundled-docs/design/observability.md +212 -0
  37. package/bundled-docs/design/session.md +2 -0
  38. package/bundled-docs/design/storage.md +4 -0
  39. package/bundled-docs/dev/api.md +9 -0
  40. package/bundled-docs/dev/deploy.md +21 -1
  41. package/bundled-docs/dev/env.md +4 -0
  42. package/bundled-docs/guide/workspace.md +1 -0
  43. package/dist/index.js +427 -37
  44. package/package.json +12 -1
  45. package/admin-out/_next/static/chunks/133-e2df20fc6362a02b.js +0 -1
  46. package/admin-out/_next/static/chunks/200.dd28be896c7765de.js +0 -1
  47. package/admin-out/_next/static/chunks/220.e5a2532bda77e816.js +0 -1
  48. package/admin-out/_next/static/chunks/438-866a1a20e9efa78a.js +0 -1
  49. package/admin-out/_next/static/chunks/442.14eec8e615cc727b.js +0 -1
  50. package/admin-out/_next/static/chunks/515.b793a7455c5e8342.js +0 -1
  51. package/admin-out/_next/static/chunks/525.fe35ab8501121729.js +0 -1
  52. package/admin-out/_next/static/chunks/579.3ab0b2bb9244fc37.js +0 -1
  53. package/admin-out/_next/static/chunks/635-d7fd95bff8674116.js +0 -1
  54. package/admin-out/_next/static/chunks/65.3aac567c42cec99c.js +0 -1
  55. package/admin-out/_next/static/chunks/688.4e014ac6cfc7e152.js +0 -1
  56. package/admin-out/_next/static/chunks/868.0c95dfe283400a00.js +0 -1
  57. package/admin-out/_next/static/chunks/896.2069ab525ef81643.js +0 -1
  58. package/admin-out/_next/static/chunks/935.91ddb965b44041c9.js +0 -1
  59. package/admin-out/_next/static/chunks/app/_not-found/page-8e35399c8fd5016d.js +0 -1
  60. package/admin-out/_next/static/chunks/app/layout-e75df4cae90cf325.js +0 -1
  61. package/admin-out/_next/static/chunks/app/page-ca15438e8bddb403.js +0 -1
  62. package/admin-out/_next/static/chunks/db534642.09d3c650ff587175.js +0 -1
  63. package/admin-out/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js +0 -1
  64. package/admin-out/_next/static/chunks/framework-c3031ae54a63fe16.js +0 -1
  65. package/admin-out/_next/static/chunks/main-14b48e3c2588354f.js +0 -1
  66. package/admin-out/_next/static/chunks/main-app-483b436873410ddc.js +0 -1
  67. package/admin-out/_next/static/chunks/pages/_app-b2fc6e5f920d1b1a.js +0 -1
  68. package/admin-out/_next/static/chunks/pages/_error-f3696a8f3d67d150.js +0 -1
  69. package/admin-out/_next/static/chunks/webpack-f80c9b8cca4447a3.js +0 -1
  70. /package/admin-out/_next/static/{AB2GFuqDjcxue9wBBq1zq → 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 providers = [];
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
- providers.push(parsed);
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 providers = modelsRegistry().providers;
957
- if (providers) {
958
- for (const provider of providers) {
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 cap = tu.name === "activate_skill" ? Infinity : config.limits.toolResultMax;
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 ${cap}. Re-run with narrower scope (e.g. grep / file_read with offset+limit) to see specific sections.]`;
1607
- if (typeof resultContent === "string" && resultContent.length > cap) {
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, cap) + truncationNote(orig);
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 > cap) {
1629
+ if (b.type === "text" && b.text.length > cap2) {
1615
1630
  mutated = true;
1616
- return { type: "text", text: b.text.slice(0, cap) + truncationNote(b.text.length) };
1631
+ return { type: "text", text: b.text.slice(0, cap2) + truncationNote(b.text.length) };
1617
1632
  }
1618
1633
  return b;
1619
1634
  });
@@ -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 cap = config.limits.autoReportMax;
3415
- const body = report.length > cap ? report.slice(0, cap) + `
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 cap = config.limits.autoReportMax;
5391
- const body = report.length > cap ? report.slice(0, cap) + `
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}]`;
@@ -11273,6 +11288,342 @@ var init_session_state_store = __esm({
11273
11288
  }
11274
11289
  });
11275
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
+
11276
11627
  // ../server/dist/agents/session-manager.js
11277
11628
  import fsSync7 from "node:fs";
11278
11629
  import path30 from "node:path";
@@ -11403,6 +11754,7 @@ var init_session_manager = __esm({
11403
11754
  init_session_agent_builder();
11404
11755
  init_session_skill_commands();
11405
11756
  init_session_state_store();
11757
+ init_genai_spans();
11406
11758
  SessionManager = class _SessionManager {
11407
11759
  // The fields below are marked `internal:` because they're consumed by
11408
11760
  // helpers in this directory (session-tools.ts). Treat them as
@@ -12091,6 +12443,7 @@ var init_session_manager = __esm({
12091
12443
  const i = message.findIndex((b) => b.type === "text");
12092
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);
12093
12445
  }
12446
+ beginTurn(session, message);
12094
12447
  for (let attempt = 0; attempt < maxRetries; attempt++) {
12095
12448
  session.abortController = new AbortController();
12096
12449
  const signal = session.abortController.signal;
@@ -12110,6 +12463,7 @@ var init_session_manager = __esm({
12110
12463
  if (event.type === "text") {
12111
12464
  resultText += event.text ?? "";
12112
12465
  }
12466
+ onAgentEvent(session, event);
12113
12467
  this.processSessionEvent(session, event);
12114
12468
  if (session.interruptRequested && event.type === "tool_result") {
12115
12469
  console.debug(`[SessionManager] Graceful interrupt after tool result in ${session.id}`);
@@ -12142,8 +12496,10 @@ var init_session_manager = __esm({
12142
12496
  if (result.compacted) {
12143
12497
  session.agent.messages = result.messages;
12144
12498
  this.emitEvent(session.id, { type: "system", text: `Session ${session.agentId} context compacted (${result.messages.length} messages remaining, local fallback)` });
12145
- if (attempt + 1 < maxRetries)
12499
+ if (attempt + 1 < maxRetries) {
12500
+ recordRetry("context_overflow");
12146
12501
  continue;
12502
+ }
12147
12503
  }
12148
12504
  }
12149
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");
@@ -12160,6 +12516,7 @@ var init_session_manager = __esm({
12160
12516
  const jitter = Math.random() * 1e3;
12161
12517
  const delay = Math.min(baseDelay + jitter, 6e4);
12162
12518
  console.debug(`[SessionManager] Session ${session.id} rate limited, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries})`);
12519
+ recordRetry("throttle");
12163
12520
  this.emitEvent(session.id, { type: "system", text: `Session ${session.agentId} rate limited, retrying in ${Math.round(delay / 1e3)}s...` });
12164
12521
  await sleep(delay);
12165
12522
  continue;
@@ -12171,6 +12528,7 @@ var init_session_manager = __esm({
12171
12528
  const jitter = Math.random() * 1e3;
12172
12529
  const delay = Math.min(baseDelay + jitter, 6e4);
12173
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");
12174
12532
  this.emitEvent(session.id, { type: "system", text: `Session ${session.agentId} hit a transient server error, retrying in ${Math.round(delay / 1e3)}s...` });
12175
12533
  await sleep(delay);
12176
12534
  continue;
@@ -12180,6 +12538,7 @@ var init_session_manager = __esm({
12180
12538
  if (attempt + 1 < maxRetries) {
12181
12539
  const delay = 1e3 * Math.pow(2, attempt) + Math.random() * 500;
12182
12540
  console.warn(`[SessionManager] Session ${session.id} transient network error, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries}): ${msg}`);
12541
+ recordRetry("network");
12183
12542
  this.emitEvent(session.id, { type: "system", text: `Network hiccup, retrying in ${Math.round(delay / 1e3)}s...` });
12184
12543
  await sleep(delay);
12185
12544
  continue;
@@ -12189,6 +12548,7 @@ var init_session_manager = __esm({
12189
12548
  if (attempt + 1 < maxRetries) {
12190
12549
  const delay = 1e3 * Math.pow(2, attempt) + Math.random() * 500;
12191
12550
  console.debug(`[SessionManager] Session ${session.id} Mantle empty response, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxRetries})`);
12551
+ recordRetry("empty_response");
12192
12552
  await sleep(delay);
12193
12553
  continue;
12194
12554
  }
@@ -12197,6 +12557,7 @@ var init_session_manager = __esm({
12197
12557
  console.debug(`[SessionManager] Session ${session.id} corrupted conversation (attempt ${attempt + 1}/${maxRetries})`);
12198
12558
  session.agent.messages = repairConversationMessages(session.agent.messages, `[Session:${session.id}]`);
12199
12559
  if (attempt + 1 < maxRetries) {
12560
+ recordRetry("corrupted");
12200
12561
  this.emitEvent(session.id, { type: "system", text: "Repairing conversation state..." });
12201
12562
  continue;
12202
12563
  }
@@ -12211,6 +12572,7 @@ var init_session_manager = __esm({
12211
12572
  this.saveAgentState(session);
12212
12573
  console.warn(`[SessionManager] Session ${session.id} multimodal 4xx \u2014 replaced ${replaced} image block(s) with placeholders (attempt ${attempt + 1}/${maxRetries})`);
12213
12574
  if (attempt + 1 < maxRetries) {
12575
+ recordRetry("multimodal_4xx");
12214
12576
  this.emitEvent(session.id, { type: "system", text: `Model rejected image data \u2014 removed ${replaced} image(s) from history, retrying...` });
12215
12577
  continue;
12216
12578
  }
@@ -12225,6 +12587,7 @@ var init_session_manager = __esm({
12225
12587
  }
12226
12588
  }
12227
12589
  session.abortController = null;
12590
+ endTurn(session, { error: session.turnError ?? void 0 });
12228
12591
  session.agent.messages = repairConversationMessages(session.agent.messages, `[Session:${session.id}]`);
12229
12592
  this.db.update(agentSessions).set({ updatedAt: Date.now() }).where(eq9(agentSessions.id, session.id)).run();
12230
12593
  await this.maybeAutoCompact(session);
@@ -13320,12 +13683,12 @@ ${result.summary}`, agentName: "user", report: true, taskId });
13320
13683
  * caller wants and also eats the trailing JSON fields. Self-cap here,
13321
13684
  * tail-kept, under the generic cap so the envelope always survives. */
13322
13685
  truncateOutputTail(output) {
13323
- const cap = config.limits.toolResultMax - 500;
13324
- if (output.length <= cap)
13686
+ const cap2 = config.limits.toolResultMax - 500;
13687
+ if (output.length <= cap2)
13325
13688
  return output;
13326
- return `[Output truncated: ${output.length} chars total, showing LAST ${cap}. Earlier text omitted.]
13689
+ return `[Output truncated: ${output.length} chars total, showing LAST ${cap2}. Earlier text omitted.]
13327
13690
 
13328
- ` + output.slice(-cap);
13691
+ ` + output.slice(-cap2);
13329
13692
  }
13330
13693
  getSessionOutput(sessionId) {
13331
13694
  const session = this.sessions.get(sessionId);
@@ -13495,6 +13858,7 @@ var init_session_manager_registry = __esm({
13495
13858
  import fs25 from "node:fs";
13496
13859
  import path31 from "node:path";
13497
13860
  import { homedir as homedir13 } from "node:os";
13861
+ import { SeverityNumber } from "@opentelemetry/api-logs";
13498
13862
  function ensureDir() {
13499
13863
  fs25.mkdirSync(logDir, { recursive: true });
13500
13864
  }
@@ -13556,6 +13920,18 @@ function shouldLog(level) {
13556
13920
  const threshold = LEVEL_ORDER[config.logging.level] ?? LEVEL_ORDER.warn;
13557
13921
  return LEVEL_ORDER[level] >= threshold;
13558
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
+ }
13559
13935
  function initLogger() {
13560
13936
  const origLog = console.log.bind(console);
13561
13937
  const origError = console.error.bind(console);
@@ -13566,39 +13942,50 @@ function initLogger() {
13566
13942
  return;
13567
13943
  origDebug(...args);
13568
13944
  writeToFile(formatLine("debug", args));
13945
+ emitOtelLog("debug", args);
13569
13946
  };
13570
13947
  console.log = (...args) => {
13571
13948
  if (!shouldLog("info"))
13572
13949
  return;
13573
13950
  origLog(...args);
13574
13951
  writeToFile(formatLine("info", args));
13952
+ emitOtelLog("info", args);
13575
13953
  };
13576
13954
  console.warn = (...args) => {
13577
13955
  if (!shouldLog("warn"))
13578
13956
  return;
13579
13957
  origWarn(...args);
13580
13958
  writeToFile(formatLine("warn", args));
13959
+ emitOtelLog("warn", args);
13581
13960
  };
13582
13961
  console.error = (...args) => {
13583
13962
  if (!shouldLog("error"))
13584
13963
  return;
13585
13964
  origError(...args);
13586
13965
  writeToFile(formatLine("error", args));
13966
+ emitOtelLog("error", args);
13587
13967
  };
13588
13968
  if (shouldLog("info")) {
13589
13969
  writeToFile(formatLine("info", [`[Logger] File logging initialized: ${logFile} (level: ${config.logging.level})`]));
13590
13970
  }
13591
13971
  }
13592
- var LOG_FILENAME, logDir, logFile, currentSize, LEVEL_ORDER;
13972
+ var LOG_FILENAME, logDir, logFile, currentSize, LEVEL_ORDER, OTEL_SEVERITY;
13593
13973
  var init_logger = __esm({
13594
13974
  "../server/dist/logger.js"() {
13595
13975
  "use strict";
13596
13976
  init_config();
13977
+ init_otel();
13597
13978
  LOG_FILENAME = "server.log";
13598
13979
  logDir = path31.join(homedir13(), ".halo", "global", "logs");
13599
13980
  logFile = path31.join(logDir, LOG_FILENAME);
13600
13981
  currentSize = -1;
13601
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
+ };
13602
13989
  }
13603
13990
  });
13604
13991
 
@@ -15326,7 +15713,7 @@ function filterVerbs(items, rawValue) {
15326
15713
  const partial = m[2].toLowerCase();
15327
15714
  return cmd.verbs.filter((v) => v.name.startsWith(partial) && v.name !== partial).map((v) => ({ slashName: `${cmd.slashName} ${v.name}`, description: v.desc ?? "" }));
15328
15715
  }
15329
- function InputBox({ enabled, placeholder, history, onSubmit, hint, commands = [], workspace, onPopupOpenChange }) {
15716
+ function InputBox({ enabled: enabled2, placeholder, history, onSubmit, hint, commands = [], workspace, onPopupOpenChange }) {
15330
15717
  const [edit, setEdit] = useState2({ value: "", cursor: 0 });
15331
15718
  const { value, cursor } = edit;
15332
15719
  const [historyIdx, setHistoryIdx] = useState2(null);
@@ -15353,7 +15740,7 @@ function InputBox({ enabled, placeholder, history, onSubmit, hint, commands = []
15353
15740
  insertion = normalized;
15354
15741
  }
15355
15742
  applyEdit((e) => insertAt(e, insertion));
15356
- }, { isActive: enabled });
15743
+ }, { isActive: enabled2 });
15357
15744
  const expandPastes = (v) => v.replace(/\[#(\d+) pasted \d+ lines?\]/g, (m, id) => pendingPastes.current.get(Number(id)) ?? m);
15358
15745
  const cmdMatches = useMemo(() => filterCommands(commands, value), [commands, value]);
15359
15746
  const verbMatches = useMemo(() => filterVerbs(commands, value), [commands, value]);
@@ -15539,17 +15926,17 @@ function InputBox({ enabled, placeholder, history, onSubmit, hint, commands = []
15539
15926
  if (input.length === 0) return;
15540
15927
  const clean = input.replace(/[\r\n]+/g, " ");
15541
15928
  applyEdit((e) => insertAt(e, clean));
15542
- }, { isActive: enabled });
15929
+ }, { isActive: enabled2 });
15543
15930
  useEffect2(() => {
15544
15931
  if (historyIdx !== null && value === "") setHistoryIdx(null);
15545
15932
  }, [value, historyIdx]);
15546
15933
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
15547
15934
  suggestOpen ? /* @__PURE__ */ jsx8(SlashSuggest, { items: filtered, selectedIdx: suggestIdx }) : null,
15548
15935
  !suggestOpen && pathOpen ? /* @__PURE__ */ jsx8(PathSuggest, { items: pathScan.items, selectedIdx: pathIdx, cwdLabel: pathScan.cwdLabel }) : null,
15549
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: enabled ? "cyan" : "gray", paddingX: 1, children: [
15936
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: enabled2 ? "cyan" : "gray", paddingX: 1, children: [
15550
15937
  /* @__PURE__ */ jsxs7(Box7, { children: [
15551
- /* @__PURE__ */ jsx8(Text8, { color: enabled ? "cyan" : "gray", children: "> " }),
15552
- enabled ? /* @__PURE__ */ jsx8(TextInputView, { value, cursor, placeholder }) : /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: placeholder })
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 })
15553
15940
  ] }),
15554
15941
  hint ? /* @__PURE__ */ jsx8(Box7, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx8(Text8, { color: "gray", dimColor: true, children: hint }) }) : null
15555
15942
  ] })
@@ -18374,10 +18761,10 @@ function parseAgentYaml(content) {
18374
18761
  }
18375
18762
  function buildScaffoldModelBlock() {
18376
18763
  const registry2 = getModelsRegistry();
18377
- const providers = registry2.providers ?? [];
18764
+ const providers2 = registry2.providers ?? [];
18378
18765
  const configured = config.agent.defaultProvider;
18379
- const explicit = configured ? providers.find((p) => p.id === configured) : void 0;
18380
- const provider = explicit ?? providers.find((p) => p.id === FALLBACK_SCAFFOLD_PROVIDER) ?? providers[0];
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];
18381
18768
  if (!provider) {
18382
18769
  return { provider: FALLBACK_SCAFFOLD_PROVIDER };
18383
18770
  }
@@ -29771,6 +30158,7 @@ async function gracefulShutdown(signal) {
29771
30158
  server.close();
29772
30159
  }
29773
30160
  console.log("[Server] Shutdown complete");
30161
+ await shutdownObservability();
29774
30162
  process.exit(0);
29775
30163
  }
29776
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;
@@ -29810,13 +30198,14 @@ var init_dist2 = __esm({
29810
30198
  init_descriptors();
29811
30199
  init_auth();
29812
30200
  init_logger();
30201
+ init_otel();
29813
30202
  init_config();
29814
30203
  init_sandbox();
29815
30204
  init_init();
29816
30205
  init_git_ssh();
29817
30206
  __dirname3 = path67.dirname(fileURLToPath3(import.meta.url));
29818
30207
  PORT = config.server.port;
29819
- HALO_VERSION = "1.2.1";
30208
+ HALO_VERSION = "1.3.0";
29820
30209
  PROJECT_ROOT = findProjectRoot();
29821
30210
  HALO_HOME4 = path67.join(homedir24(), ".halo");
29822
30211
  GIT_SHA = readGitSha();
@@ -29855,6 +30244,7 @@ var init_dist2 = __esm({
29855
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.`);
29856
30245
  }
29857
30246
  }
30247
+ await initObservability();
29858
30248
  initLogger();
29859
30249
  await initBwrapCheck();
29860
30250
  setSandboxHiddenPaths(config.sandbox.hiddenDirs, config.sandbox.hiddenFiles, config.sandbox.writableDirs);
@@ -30750,7 +31140,7 @@ import path68 from "node:path";
30750
31140
  import fs61 from "node:fs";
30751
31141
  import { homedir as homedir25 } from "node:os";
30752
31142
  import { parseArgs as parseArgs2 } from "node:util";
30753
- var VERSION = "1.2.1";
31143
+ var VERSION = "1.3.0";
30754
31144
  var HELP_TOP = `Usage: halo <command> [options]
30755
31145
 
30756
31146
  Commands:
@@ -31130,8 +31520,8 @@ async function cmdSetup(argv = []) {
31130
31520
  `);
31131
31521
  }
31132
31522
  async function stepModelProviders() {
31133
- const providers = listModelProviders2();
31134
- if (providers.length === 0) return;
31523
+ const providers2 = listModelProviders2();
31524
+ if (providers2.length === 0) return;
31135
31525
  const enter = await promptSelect2("Configure model provider keys now? (you can also re-run `halo setup` later)", [
31136
31526
  { value: "skip", label: "Skip" },
31137
31527
  { value: "go", label: "Configure..." }
@@ -31142,7 +31532,7 @@ async function cmdSetup(argv = []) {
31142
31532
  return;
31143
31533
  }
31144
31534
  while (true) {
31145
- const opts = providers.map((p) => ({
31535
+ const opts = providers2.map((p) => ({
31146
31536
  value: p.id,
31147
31537
  label: p.displayName,
31148
31538
  hint: providerStatusHint(p.id, p.fields)
@@ -31151,7 +31541,7 @@ async function cmdSetup(argv = []) {
31151
31541
  const pick = await promptSelect2("Pick a provider to configure (Done when finished):", opts);
31152
31542
  if (pick == null) abort();
31153
31543
  if (pick === "__done__") break;
31154
- const provider = providers.find((p) => p.id === pick);
31544
+ const provider = providers2.find((p) => p.id === pick);
31155
31545
  if (!provider) continue;
31156
31546
  await configureSecretGroup(provider.id, provider.bucket, provider.fields);
31157
31547
  }