@wrongstack/core 0.313.0 → 0.316.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chronicle/index.js +84 -6391
- package/dist/chronicle/legacy-journal-import.d.ts +0 -6
- package/dist/chronicle/metrics-ingest.d.ts +14 -0
- package/dist/chronicle/metrics-schema.d.ts +10 -1
- package/dist/chronicle/metrics-store.d.ts +20 -2
- package/dist/chronicle/project-server.js +98 -4874
- package/dist/chronicle/sink.d.ts +13 -0
- package/dist/chronicle/sqlite-journal.d.ts +20 -2
- package/dist/chunk-25LSLC3G.js +334 -0
- package/dist/chunk-27QIURLN.js +1073 -0
- package/dist/chunk-2FK4WTFF.js +207 -0
- package/dist/chunk-2YU4WVBW.js +134 -0
- package/dist/chunk-3AGWVSS7.js +4608 -0
- package/dist/chunk-3BDV7RL3.js +211 -0
- package/dist/chunk-3ECENVHL.js +325 -0
- package/dist/chunk-3XPVB7BA.js +135 -0
- package/dist/chunk-45WI72ZB.js +16884 -0
- package/dist/chunk-4BMVPOHO.js +3748 -0
- package/dist/chunk-4I7HGVVA.js +363 -0
- package/dist/chunk-4LCZVJVF.js +317 -0
- package/dist/chunk-4SJNS7OB.js +217 -0
- package/dist/chunk-4VWO3ZXL.js +342 -0
- package/dist/chunk-57GEWK5X.js +26 -0
- package/dist/chunk-5BMIE7QG.js +46 -0
- package/dist/chunk-5S437PRC.js +50 -0
- package/dist/chunk-5XDUGTKN.js +511 -0
- package/dist/chunk-5YPSXQXM.js +1443 -0
- package/dist/chunk-6FHWXLPT.js +10 -0
- package/dist/chunk-6H5GEOET.js +2406 -0
- package/dist/chunk-6NWEVFEL.js +151 -0
- package/dist/chunk-AYW5YINX.js +58 -0
- package/dist/chunk-B4AMLZPB.js +9 -0
- package/dist/chunk-B7F4WVP4.js +65 -0
- package/dist/chunk-BJHVULHO.js +55 -0
- package/dist/chunk-BPBKKBUK.js +2085 -0
- package/dist/chunk-BYD345OX.js +589 -0
- package/dist/chunk-C27KDLOX.js +279 -0
- package/dist/chunk-CU546ESW.js +597 -0
- package/dist/chunk-DLHHXUAM.js +172 -0
- package/dist/chunk-DOTHQSJ2.js +429 -0
- package/dist/chunk-DYLPMV4Q.js +711 -0
- package/dist/chunk-EODDVJBM.js +82 -0
- package/dist/chunk-F3JWNST7.js +361 -0
- package/dist/chunk-FC2ZGRF3.js +140 -0
- package/dist/chunk-FCBTO76Z.js +5786 -0
- package/dist/chunk-FHZVFYSL.js +396 -0
- package/dist/chunk-FNXVT7HF.js +52 -0
- package/dist/chunk-FWNLEZYG.js +259 -0
- package/dist/chunk-FXQLIX3K.js +211 -0
- package/dist/chunk-G5S3S5ZN.js +38 -0
- package/dist/chunk-GDQ7CZRQ.js +116 -0
- package/dist/chunk-GJJGJB2R.js +1374 -0
- package/dist/chunk-GTCU6DZ3.js +1 -0
- package/dist/chunk-HA4ZYA6T.js +178 -0
- package/dist/chunk-HI3JGZR2.js +188 -0
- package/dist/chunk-HMLIGAU7.js +395 -0
- package/dist/chunk-HXKZ7OBQ.js +303 -0
- package/dist/chunk-I7DALG7J.js +292 -0
- package/dist/chunk-IRUTEHCE.js +960 -0
- package/dist/chunk-IYFBFX34.js +86 -0
- package/dist/chunk-J2WRWR37.js +1104 -0
- package/dist/chunk-JFPJ6LLQ.js +129 -0
- package/dist/chunk-JMULFFLN.js +9 -0
- package/dist/chunk-JO4MGB6Z.js +1264 -0
- package/dist/chunk-JVKB2TBL.js +48 -0
- package/dist/chunk-JX7VBZDK.js +31 -0
- package/dist/chunk-K6BXGITA.js +931 -0
- package/dist/chunk-KDQJCT2J.js +45 -0
- package/dist/chunk-KJ2ZTQSL.js +47 -0
- package/dist/chunk-KZMSZV32.js +187 -0
- package/dist/chunk-LA7CLDRW.js +129 -0
- package/dist/chunk-LHOA5DWA.js +28 -0
- package/dist/chunk-LSMMOS2Z.js +119 -0
- package/dist/chunk-LX5QWLBL.js +27 -0
- package/dist/chunk-LXWCMQNJ.js +789 -0
- package/dist/chunk-M27LIEKV.js +2829 -0
- package/dist/chunk-MJPHJRAL.js +663 -0
- package/dist/chunk-MTL4O5YR.js +228 -0
- package/dist/chunk-NDBIPFNY.js +102 -0
- package/dist/chunk-NEMIWEXC.js +1199 -0
- package/dist/chunk-NHJ75HXY.js +1 -0
- package/dist/chunk-NIOAGFPB.js +1510 -0
- package/dist/chunk-NS52ISWS.js +355 -0
- package/dist/chunk-NSOFPKGT.js +5352 -0
- package/dist/chunk-NVIZG63R.js +757 -0
- package/dist/chunk-O37BQ65S.js +21 -0
- package/dist/chunk-OOYZPOXM.js +308 -0
- package/dist/chunk-OSGLYW5J.js +28 -0
- package/dist/chunk-PFCMSSUF.js +1640 -0
- package/dist/chunk-PFZO3C6H.js +14 -0
- package/dist/chunk-PGRVXIMY.js +158 -0
- package/dist/chunk-PHH7LD45.js +113 -0
- package/dist/chunk-PISBFBJN.js +109 -0
- package/dist/chunk-POOOIWE5.js +9 -0
- package/dist/chunk-PQNS3U7Z.js +1240 -0
- package/dist/chunk-QSTSJOCK.js +402 -0
- package/dist/chunk-RFQMOKWK.js +282 -0
- package/dist/chunk-RJIZEJ42.js +30 -0
- package/dist/chunk-RTOTNK3P.js +484 -0
- package/dist/chunk-RXKSDUAU.js +1172 -0
- package/dist/chunk-S45JPKQN.js +3317 -0
- package/dist/chunk-SAFHACFA.js +152 -0
- package/dist/chunk-SQJVFEBV.js +41 -0
- package/dist/chunk-SVT4RNTR.js +49 -0
- package/dist/chunk-SWUFN7XM.js +43 -0
- package/dist/chunk-SXMVYC5H.js +174 -0
- package/dist/chunk-SZ6AYUIZ.js +23 -0
- package/dist/chunk-TBRL2RCC.js +9 -0
- package/dist/chunk-TCCHINEF.js +655 -0
- package/dist/chunk-THEGTRAG.js +316 -0
- package/dist/chunk-TR4LQE7L.js +2256 -0
- package/dist/chunk-TRT2HRWZ.js +18 -0
- package/dist/chunk-TWJMGXCI.js +900 -0
- package/dist/chunk-U45AXT5D.js +1023 -0
- package/dist/chunk-U4FWBSO3.js +509 -0
- package/dist/chunk-UBGDX6FI.js +147 -0
- package/dist/chunk-UJILKYES.js +75 -0
- package/dist/chunk-ULMAQV3T.js +234 -0
- package/dist/chunk-VVBP6KGT.js +9 -0
- package/dist/chunk-W5VITDM2.js +168 -0
- package/dist/chunk-WAHQVNWT.js +194 -0
- package/dist/chunk-WBPVKEEE.js +227 -0
- package/dist/chunk-WGGYRBXH.js +5434 -0
- package/dist/chunk-WLYKU45P.js +5770 -0
- package/dist/chunk-WP7BVANJ.js +53 -0
- package/dist/chunk-WQQON3SV.js +473 -0
- package/dist/chunk-WQTOVMGV.js +119 -0
- package/dist/chunk-XBDP4QPW.js +7 -0
- package/dist/chunk-XEBHSJLT.js +103 -0
- package/dist/chunk-XF4VUMKW.js +231 -0
- package/dist/chunk-XQ2ECNK3.js +1 -0
- package/dist/chunk-Y5YXDM7D.js +74 -0
- package/dist/chunk-YOPAUVU4.js +2588 -0
- package/dist/chunk-YPT4A46F.js +362 -0
- package/dist/chunk-YWXT3JNM.js +425 -0
- package/dist/chunk-YY6APYUM.js +49 -0
- package/dist/chunk-Z2IJUO45.js +691 -0
- package/dist/chunk-ZHZ2Y2DH.js +1643 -0
- package/dist/chunk-ZT7JNIGX.js +152 -0
- package/dist/coordination/agent-monitor.d.ts +9 -1
- package/dist/coordination/agent-subagent-runner.d.ts +1 -1
- package/dist/coordination/agents/agent-prompts.d.ts +8 -0
- package/dist/coordination/agents/index.d.ts +33 -1
- package/dist/coordination/agents/index.js +184 -6269
- package/dist/coordination/agents/phase3-wave1-platform.d.ts +1 -3
- package/dist/coordination/agents/phase3-wave2-meta.d.ts +1 -3
- package/dist/coordination/agents/phase8-wave3-products.d.ts +1 -3
- package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts +1 -3
- package/dist/coordination/agents/project-agent-identity.d.ts +11 -11
- package/dist/coordination/agents/types.d.ts +13 -2
- package/dist/coordination/brain-monitor.d.ts +30 -7
- package/dist/coordination/collab-debug.d.ts +1 -1
- package/dist/coordination/collab-pause.d.ts +1 -1
- package/dist/coordination/delegate-tool.d.ts +7 -5
- package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -0
- package/dist/coordination/director-options.d.ts +7 -0
- package/dist/coordination/director-spawn-model.d.ts +17 -0
- package/dist/coordination/director-tools.d.ts +2 -2
- package/dist/coordination/director.d.ts +12 -0
- package/dist/coordination/dispatcher.d.ts +8 -0
- package/dist/coordination/fleet-bus.d.ts +35 -4
- package/dist/coordination/fleet-supervisor.d.ts +10 -2
- package/dist/coordination/fleet.d.ts +16 -7
- package/dist/coordination/index.d.ts +4 -0
- package/dist/coordination/index.js +402 -34324
- package/dist/coordination/kanban-dispatch-port.d.ts +2 -2
- package/dist/coordination/mailbox-http-router.d.ts +4 -3
- package/dist/coordination/mailbox-http-validation.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +48 -3276
- package/dist/coordination/mailbox-types.d.ts +24 -6
- package/dist/coordination/model-tier-leader.d.ts +122 -0
- package/dist/coordination/model-tier.d.ts +122 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +24 -0
- package/dist/coordination/origin-session.d.ts +16 -0
- package/dist/coordination/provider-status-tracker.d.ts +41 -5
- package/dist/coordination/session-note-hub.d.ts +44 -0
- package/dist/coordination/session-note-tool.d.ts +10 -0
- package/dist/core/agent.d.ts +15 -2
- package/dist/core/context.d.ts +22 -3
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +239 -12360
- package/dist/core/model-ref.js +5 -23
- package/dist/core/provider-runner.d.ts +36 -1
- package/dist/core/request-conversation-binding.d.ts +37 -0
- package/dist/core/session-notes.d.ts +25 -0
- package/dist/core/system-prompt-builder.d.ts +11 -2
- package/dist/core/system-prompt-variants.d.ts +78 -0
- package/dist/design/index.js +50 -1087
- package/dist/execution/auto-compaction-middleware.d.ts +11 -0
- package/dist/execution/autonomy-brain.d.ts +2 -1
- package/dist/execution/autonomy-prompt-contributor.d.ts +7 -2
- package/dist/execution/compaction-core.d.ts +4 -4
- package/dist/execution/eternal-autonomy.d.ts +1 -1
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +1615 -21895
- package/dist/execution/model-runtime.d.ts +0 -7
- package/dist/execution/parallel-eternal-engine.d.ts +7 -7
- package/dist/execution/prompt-enhancer.js +16 -535
- package/dist/execution/retry-policy.d.ts +31 -1
- package/dist/extension/index.js +7 -311
- package/dist/goal/index.js +33 -3435
- package/dist/hooks/index.js +14 -1138
- package/dist/hq/index.js +114 -5139
- package/dist/hq/persistence/event-log.d.ts +0 -1
- package/dist/hq/persistence.d.ts +4 -4
- package/dist/hq/protocol.js +21 -668
- package/dist/hq/publisher.d.ts +2 -0
- package/dist/index.d.ts +14 -11
- package/dist/index.js +1502 -92655
- package/dist/infrastructure/index.d.ts +4 -4
- package/dist/infrastructure/index.js +504 -5230
- package/dist/infrastructure/token-counter.d.ts +9 -0
- package/dist/kernel/events/agent-events.d.ts +77 -3
- package/dist/kernel/events/brain-events.d.ts +12 -0
- package/dist/kernel/events/provider-events.d.ts +22 -1
- package/dist/kernel/events/session-events.d.ts +14 -0
- package/dist/kernel/events/tool-events.d.ts +13 -0
- package/dist/kernel/index.js +14 -992
- package/dist/mailbox-attach.d.ts +1 -1
- package/dist/models/index.js +41 -1117
- package/dist/notifications/index.js +3 -108
- package/dist/observability/event-bridge.d.ts +25 -5
- package/dist/observability/index.d.ts +9 -8
- package/dist/observability/index.js +477 -631
- package/dist/observability/tool-usage-source.d.ts +65 -0
- package/dist/plugin/index.d.ts +5 -0
- package/dist/plugin/index.js +600 -14835
- package/dist/plugins/auto-review-plugin.d.ts +3 -2
- package/dist/plugins/review-finding-commands.d.ts +0 -8
- package/dist/plugins/review-finding-store.js +22 -0
- package/dist/plugins/review-finding-types.js +19 -0
- package/dist/plugins/review-report-integration.js +21 -0
- package/dist/plugins/review-report-store.js +20 -0
- package/dist/plugins/review-report-types.js +13 -0
- package/dist/prompts/index.js +14 -948
- package/dist/registry/index.js +9 -822
- package/dist/registry/tool-registry.d.ts +59 -9
- package/dist/registry-7N4O3E7W.js +14 -0
- package/dist/replay/index.js +6 -84
- package/dist/security/capabilities.d.ts +6 -0
- package/dist/security/file-permissions.d.ts +1 -1
- package/dist/security/index.js +161 -2235
- package/dist/security/permission-policy.d.ts +12 -1
- package/dist/security/secret-scrubber.d.ts +15 -0
- package/dist/session-catalog/index.d.ts +1 -0
- package/dist/session-catalog/index.js +41 -2103
- package/dist/session-catalog/project-server.js +59 -1435
- package/dist/session-catalog/protocol.d.ts +32 -1
- package/dist/session-catalog/registry.d.ts +17 -1
- package/dist/session-catalog/session-agents.d.ts +56 -0
- package/dist/session-catalog/store-rebuild.d.ts +14 -0
- package/dist/session-catalog/store-schema.d.ts +26 -0
- package/dist/session-catalog/store.d.ts +27 -1
- package/dist/session-note-attach.d.ts +9 -0
- package/dist/skills/index.js +35 -1578
- package/dist/statusline/index.d.ts +43 -0
- package/dist/statusline/index.js +102 -0
- package/dist/storage/board-store-port.d.ts +2 -2
- package/dist/storage/config-loader/in-project-policy.d.ts +29 -0
- package/dist/storage/config-loader.d.ts +3 -2
- package/dist/storage/file-session-writer.d.ts +4 -2
- package/dist/storage/goal-coordination.d.ts +6 -1
- package/dist/storage/goal-kanban.d.ts +4 -1
- package/dist/storage/index.d.ts +12 -4
- package/dist/storage/index.js +198 -15601
- package/dist/storage/input-history-store.d.ts +2 -4
- package/dist/storage/memory-curator.d.ts +133 -0
- package/dist/storage/session-agent-attribution.d.ts +39 -0
- package/dist/storage/session-doctor.d.ts +109 -0
- package/dist/storage/session-event-bridge.d.ts +5 -7
- package/dist/storage/session-outcome.d.ts +42 -0
- package/dist/storage/session-recovery.d.ts +55 -0
- package/dist/storage/session-store/load-session-data.d.ts +11 -0
- package/dist/storage/session-store/prune-helpers.d.ts +1 -1
- package/dist/storage/session-store/resume-session.d.ts +0 -2
- package/dist/storage/session-store/types.d.ts +8 -0
- package/dist/storage/session-store.d.ts +3 -3
- package/dist/storage/session-summary-tracker.d.ts +8 -1
- package/dist/storage/session-write-buffer.d.ts +18 -0
- package/dist/storage/session-writer-truncate.d.ts +2 -0
- package/dist/tasking/index.js +10 -676
- package/dist/tools/fallback-manage-tools.d.ts +6 -4
- package/dist/tools/index.js +52 -9590
- package/dist/tools/model-tier-set-tool.d.ts +66 -0
- package/dist/types/agent-bridge.d.ts +2 -1
- package/dist/types/config/autonomy.d.ts +5 -0
- package/dist/types/config/mcp-features.d.ts +7 -0
- package/dist/types/config/model-tiers.d.ts +148 -0
- package/dist/types/config/root.d.ts +9 -0
- package/dist/types/config/tools.d.ts +73 -0
- package/dist/types/config/ui.d.ts +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/context.d.ts +2 -0
- package/dist/types/default-config.d.ts +6 -0
- package/dist/types/errors.d.ts +20 -1
- package/dist/types/index.d.ts +11 -9
- package/dist/types/index.js +131 -1342
- package/dist/types/multi-agent.d.ts +43 -0
- package/dist/types/plugin.d.ts +1 -1
- package/dist/types/provider-runner.d.ts +8 -0
- package/dist/types/provider.d.ts +15 -0
- package/dist/types/runtime-capability-manifest.d.ts +1 -1
- package/dist/types/secret-scrubber.d.ts +15 -0
- package/dist/types/session-markers.d.ts +37 -10
- package/dist/types/session-markers.js +8 -144
- package/dist/types/session-timeline.d.ts +197 -0
- package/dist/types/session-timeline.js +12 -0
- package/dist/types/session.d.ts +172 -11
- package/dist/types/system-prompt.d.ts +27 -0
- package/dist/utils/config-backup.d.ts +9 -1
- package/dist/utils/error.js +3 -4
- package/dist/utils/expect-defined.js +3 -9
- package/dist/utils/heap-watchdog.js +6 -706
- package/dist/utils/index.js +271 -5685
- package/dist/utils/message-invariants.d.ts +4 -1
- package/dist/utils/regex-guard.d.ts +1 -1
- package/dist/utils/sage-output-block.js +5 -42
- package/dist/utils/socket-path.d.ts +1 -1
- package/dist/utils/token-estimate.d.ts +6 -2
- package/dist/utils/wstack-paths.d.ts +2 -0
- package/dist/wiring/proxy-rewrite.d.ts +46 -0
- package/dist/wiring/proxy-rewrite.js +24 -121
- package/dist/worktree/index.js +6 -887
- package/instructions/agents/explore-companion.md +45 -18
- package/instructions/agents/memory-curator.md +34 -22
- package/instructions/coordination/director-preamble.md +12 -7
- package/instructions/coordination/subagent-baseline.md +26 -5
- package/instructions/leader-after-task.md +4 -1
- package/instructions/llm/chimera-review.md +30 -9
- package/instructions/llm/memory-curator.md +31 -0
- package/instructions/sections/tool/delegation-compact.md +2 -0
- package/instructions/sections/tool/delegation-full.md +5 -1
- package/instructions/sections/tool/mailbox-compact.md +5 -1
- package/instructions/sections/tool/mailbox-full.md +3 -0
- package/instructions/sections/tool/session-note-compact.md +3 -0
- package/instructions/sections/tool/session-note-full.md +15 -0
- package/instructions/system-lite.md +9 -4
- package/instructions/system-pro.md +66 -143
- package/instructions/system.md +73 -175
- package/package.json +32 -4
- package/dist/coordination/agents.d.ts +0 -8
- package/dist/coordination/transport.d.ts +0 -2
- package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +0 -28
|
@@ -1,3 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emitProcessCompleted,
|
|
3
|
+
emitProcessOutput,
|
|
4
|
+
emitProcessStarted,
|
|
5
|
+
redactCommand,
|
|
6
|
+
redactCommandArgs,
|
|
7
|
+
runWithProcessTelemetry
|
|
8
|
+
} from "../chunk-NDBIPFNY.js";
|
|
9
|
+
import {
|
|
10
|
+
runWithNetworkTelemetry,
|
|
11
|
+
startNetworkTelemetryMonitor
|
|
12
|
+
} from "../chunk-HA4ZYA6T.js";
|
|
13
|
+
import "../chunk-PFZO3C6H.js";
|
|
14
|
+
import {
|
|
15
|
+
toErrorMessage
|
|
16
|
+
} from "../chunk-TBRL2RCC.js";
|
|
17
|
+
|
|
18
|
+
// src/observability/event-bridge.ts
|
|
19
|
+
function wireMetricsToEvents(events, sink) {
|
|
20
|
+
const unsubs = [];
|
|
21
|
+
const toolUsage = /* @__PURE__ */ new Map();
|
|
22
|
+
const record = (name, ok, durationMs) => {
|
|
23
|
+
const metricName = metricToolName(name);
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const existing = toolUsage.get(metricName);
|
|
26
|
+
if (existing) {
|
|
27
|
+
existing.invocations += 1;
|
|
28
|
+
if (!ok) existing.failures += 1;
|
|
29
|
+
existing.durationMsTotal += durationMs;
|
|
30
|
+
existing.lastInvokedAt = now;
|
|
31
|
+
} else {
|
|
32
|
+
toolUsage.set(metricName, {
|
|
33
|
+
invocations: 1,
|
|
34
|
+
failures: ok ? 0 : 1,
|
|
35
|
+
durationMsTotal: durationMs,
|
|
36
|
+
lastInvokedAt: now,
|
|
37
|
+
firstInvokedAt: now
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
unsubs.push(
|
|
42
|
+
events.on("session.started", () => sink.counter("agent.sessions.started")),
|
|
43
|
+
events.on("session.ended", (e) => {
|
|
44
|
+
sink.counter("agent.sessions.ended");
|
|
45
|
+
sink.histogram("agent.session.tokens.input", e.usage.input);
|
|
46
|
+
sink.histogram("agent.session.tokens.output", e.usage.output);
|
|
47
|
+
}),
|
|
48
|
+
events.on("session.damaged", () => sink.counter("agent.sessions.damaged")),
|
|
49
|
+
events.on("iteration.completed", () => sink.counter("agent.iterations.total")),
|
|
50
|
+
events.on("iteration.limit_reached", () => sink.counter("agent.iteration_limit.hit")),
|
|
51
|
+
events.on("provider.response", (e) => {
|
|
52
|
+
sink.counter("provider.responses.total", 1, { stop_reason: e.stopReason });
|
|
53
|
+
sink.counter("provider.tokens.input", e.usage.input);
|
|
54
|
+
sink.counter("provider.tokens.output", e.usage.output);
|
|
55
|
+
if (e.usage.cacheRead) sink.counter("provider.tokens.cache_read", e.usage.cacheRead);
|
|
56
|
+
if (e.usage.cacheWrite) sink.counter("provider.tokens.cache_write", e.usage.cacheWrite);
|
|
57
|
+
}),
|
|
58
|
+
events.on(
|
|
59
|
+
"provider.retry",
|
|
60
|
+
(e) => sink.counter("provider.retries.total", 1, {
|
|
61
|
+
provider: e.providerId,
|
|
62
|
+
status: String(e.status)
|
|
63
|
+
})
|
|
64
|
+
),
|
|
65
|
+
events.on(
|
|
66
|
+
"provider.error",
|
|
67
|
+
(e) => sink.counter("provider.errors.total", 1, {
|
|
68
|
+
provider: e.providerId,
|
|
69
|
+
status: String(e.status),
|
|
70
|
+
retryable: String(e.retryable)
|
|
71
|
+
})
|
|
72
|
+
),
|
|
73
|
+
events.on("tool.started", (e) => {
|
|
74
|
+
sink.counter("tool.starts.total", 1, { tool: metricToolName(e.name) });
|
|
75
|
+
record(e.name, true, 0);
|
|
76
|
+
}),
|
|
77
|
+
events.on("tool.executed", (e) => {
|
|
78
|
+
const tool = metricToolName(e.name);
|
|
79
|
+
sink.counter("tool.executions.total", 1, { tool, ok: String(e.ok) });
|
|
80
|
+
sink.histogram("tool.duration_ms", e.durationMs, { tool });
|
|
81
|
+
record(e.name, e.ok, e.durationMs);
|
|
82
|
+
}),
|
|
83
|
+
events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
|
|
84
|
+
events.on("compaction.fired", (e) => {
|
|
85
|
+
sink.counter("compaction.fired.total");
|
|
86
|
+
sink.histogram("compaction.reduction_tokens", e.report.before - e.report.after);
|
|
87
|
+
}),
|
|
88
|
+
// MCP server names are user-controlled and may contain tenant/project
|
|
89
|
+
// identifiers. Keep them out of metric labels to bound cardinality and
|
|
90
|
+
// prevent operational metadata leaks.
|
|
91
|
+
events.on("mcp.server.connected", () => sink.counter("mcp.connects.total")),
|
|
92
|
+
events.on("mcp.server.reconnected", () => sink.counter("mcp.reconnects.total")),
|
|
93
|
+
events.on("mcp.server.disconnected", () => sink.counter("mcp.disconnects.total")),
|
|
94
|
+
events.on("error", (e) => sink.counter("agent.errors.total", 1, { phase: e.phase }))
|
|
95
|
+
);
|
|
96
|
+
return {
|
|
97
|
+
sink,
|
|
98
|
+
getToolUsage() {
|
|
99
|
+
return toolUsage;
|
|
100
|
+
},
|
|
101
|
+
dispose() {
|
|
102
|
+
for (const u of unsubs) u();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function metricToolName(name) {
|
|
107
|
+
return name.startsWith("mcp__") ? "mcp_proxy" : name;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/observability/health.ts
|
|
111
|
+
var SEVERITY = {
|
|
112
|
+
healthy: 0,
|
|
113
|
+
degraded: 1,
|
|
114
|
+
unhealthy: 2
|
|
115
|
+
};
|
|
116
|
+
var DefaultHealthRegistry = class {
|
|
117
|
+
checks = /* @__PURE__ */ new Map();
|
|
118
|
+
timeoutMs;
|
|
119
|
+
constructor(opts = {}) {
|
|
120
|
+
this.timeoutMs = opts.timeoutMs ?? 5e3;
|
|
121
|
+
}
|
|
122
|
+
register(check) {
|
|
123
|
+
this.checks.set(check.name, check);
|
|
124
|
+
}
|
|
125
|
+
unregister(name) {
|
|
126
|
+
this.checks.delete(name);
|
|
127
|
+
}
|
|
128
|
+
async run() {
|
|
129
|
+
const results = await Promise.all(
|
|
130
|
+
Array.from(this.checks.values()).map(async (c) => {
|
|
131
|
+
const result = await this.runOne(c);
|
|
132
|
+
return { name: c.name, ...result };
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
let status = "healthy";
|
|
136
|
+
for (const r of results) {
|
|
137
|
+
if (SEVERITY[r.status] > SEVERITY[status]) status = r.status;
|
|
138
|
+
}
|
|
139
|
+
return { status, timestamp: Date.now(), checks: results };
|
|
140
|
+
}
|
|
141
|
+
async runOne(check) {
|
|
142
|
+
let timer = null;
|
|
143
|
+
const timeout = new Promise((resolve) => {
|
|
144
|
+
timer = setTimeout(
|
|
145
|
+
() => resolve({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
146
|
+
this.timeoutMs
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
try {
|
|
150
|
+
return await Promise.race([check.check(), timeout]);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
return { status: "unhealthy", detail: toErrorMessage(err) };
|
|
153
|
+
} finally {
|
|
154
|
+
if (timer) clearTimeout(timer);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
1
159
|
// src/observability/metrics.ts
|
|
2
160
|
var RESERVOIR_SIZE = 1024;
|
|
3
161
|
function labelKey(labels) {
|
|
@@ -168,638 +326,61 @@ var InMemoryMetricsSink = class {
|
|
|
168
326
|
recordDrop(name) {
|
|
169
327
|
this.droppedByMetric.set(name, (this.droppedByMetric.get(name) ?? 0) + 1);
|
|
170
328
|
}
|
|
171
|
-
};
|
|
172
|
-
function parseLabelKey(key) {
|
|
173
|
-
if (!key) return {};
|
|
174
|
-
const labels = {};
|
|
175
|
-
for (const pair of key.split(",")) {
|
|
176
|
-
const eq = pair.indexOf("=");
|
|
177
|
-
if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
178
|
-
}
|
|
179
|
-
return labels;
|
|
180
|
-
}
|
|
181
|
-
var NoopMetricsSink = class {
|
|
182
|
-
counter() {
|
|
183
|
-
}
|
|
184
|
-
gauge() {
|
|
185
|
-
}
|
|
186
|
-
histogram() {
|
|
187
|
-
}
|
|
188
|
-
snapshot() {
|
|
189
|
-
return { timestamp: Date.now(), series: [] };
|
|
190
|
-
}
|
|
191
|
-
droppedObservations() {
|
|
192
|
-
return 0;
|
|
193
|
-
}
|
|
194
|
-
reset() {
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
// src/utils/error.ts
|
|
199
|
-
function toErrorMessage(err) {
|
|
200
|
-
return err instanceof Error ? err.message : String(err);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/observability/health.ts
|
|
204
|
-
var SEVERITY = {
|
|
205
|
-
healthy: 0,
|
|
206
|
-
degraded: 1,
|
|
207
|
-
unhealthy: 2
|
|
208
|
-
};
|
|
209
|
-
var DefaultHealthRegistry = class {
|
|
210
|
-
checks = /* @__PURE__ */ new Map();
|
|
211
|
-
timeoutMs;
|
|
212
|
-
constructor(opts = {}) {
|
|
213
|
-
this.timeoutMs = opts.timeoutMs ?? 5e3;
|
|
214
|
-
}
|
|
215
|
-
register(check) {
|
|
216
|
-
this.checks.set(check.name, check);
|
|
217
|
-
}
|
|
218
|
-
unregister(name) {
|
|
219
|
-
this.checks.delete(name);
|
|
220
|
-
}
|
|
221
|
-
async run() {
|
|
222
|
-
const results = await Promise.all(
|
|
223
|
-
Array.from(this.checks.values()).map(async (c) => {
|
|
224
|
-
const result = await this.runOne(c);
|
|
225
|
-
return { name: c.name, ...result };
|
|
226
|
-
})
|
|
227
|
-
);
|
|
228
|
-
let status = "healthy";
|
|
229
|
-
for (const r of results) {
|
|
230
|
-
if (SEVERITY[r.status] > SEVERITY[status]) status = r.status;
|
|
231
|
-
}
|
|
232
|
-
return { status, timestamp: Date.now(), checks: results };
|
|
233
|
-
}
|
|
234
|
-
async runOne(check) {
|
|
235
|
-
let timer = null;
|
|
236
|
-
const timeout = new Promise((resolve) => {
|
|
237
|
-
timer = setTimeout(
|
|
238
|
-
() => resolve({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
239
|
-
this.timeoutMs
|
|
240
|
-
);
|
|
241
|
-
});
|
|
242
|
-
try {
|
|
243
|
-
return await Promise.race([check.check(), timeout]);
|
|
244
|
-
} catch (err) {
|
|
245
|
-
return { status: "unhealthy", detail: toErrorMessage(err) };
|
|
246
|
-
} finally {
|
|
247
|
-
if (timer) clearTimeout(timer);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
// src/observability/tracer.ts
|
|
253
|
-
var NoopTracer = class {
|
|
254
|
-
startSpan() {
|
|
255
|
-
return NOOP_SPAN;
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
var NOOP_SPAN = {
|
|
259
|
-
setAttribute() {
|
|
260
|
-
},
|
|
261
|
-
recordError() {
|
|
262
|
-
},
|
|
263
|
-
end() {
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
// src/observability/otel-tracer.ts
|
|
268
|
-
var OTEL_STATUS_ERROR = 2;
|
|
269
|
-
var OTelTracer = class {
|
|
270
|
-
constructor(upstream) {
|
|
271
|
-
this.upstream = upstream;
|
|
272
|
-
}
|
|
273
|
-
upstream;
|
|
274
|
-
startSpan(name, attrs) {
|
|
275
|
-
const otelSpan = this.upstream.startSpan(name, attrs ? { attributes: attrs } : void 0);
|
|
276
|
-
return new OTelSpan(otelSpan);
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
var OTelSpan = class {
|
|
280
|
-
constructor(span) {
|
|
281
|
-
this.span = span;
|
|
282
|
-
}
|
|
283
|
-
span;
|
|
284
|
-
setAttribute(key, value) {
|
|
285
|
-
this.span.setAttribute(key, value);
|
|
286
|
-
}
|
|
287
|
-
recordError(err) {
|
|
288
|
-
this.span.recordException(err);
|
|
289
|
-
this.span.setStatus?.({ code: OTEL_STATUS_ERROR, message: err.message });
|
|
290
|
-
}
|
|
291
|
-
end() {
|
|
292
|
-
this.span.end();
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
// src/observability/event-bridge.ts
|
|
297
|
-
function wireMetricsToEvents(events, sink) {
|
|
298
|
-
const unsubs = [];
|
|
299
|
-
unsubs.push(
|
|
300
|
-
events.on("session.started", () => sink.counter("agent.sessions.started")),
|
|
301
|
-
events.on("session.ended", (e) => {
|
|
302
|
-
sink.counter("agent.sessions.ended");
|
|
303
|
-
sink.histogram("agent.session.tokens.input", e.usage.input);
|
|
304
|
-
sink.histogram("agent.session.tokens.output", e.usage.output);
|
|
305
|
-
}),
|
|
306
|
-
events.on("session.damaged", () => sink.counter("agent.sessions.damaged")),
|
|
307
|
-
events.on("iteration.completed", () => sink.counter("agent.iterations.total")),
|
|
308
|
-
events.on("iteration.limit_reached", () => sink.counter("agent.iteration_limit.hit")),
|
|
309
|
-
events.on("provider.response", (e) => {
|
|
310
|
-
sink.counter("provider.responses.total", 1, { stop_reason: e.stopReason });
|
|
311
|
-
sink.counter("provider.tokens.input", e.usage.input);
|
|
312
|
-
sink.counter("provider.tokens.output", e.usage.output);
|
|
313
|
-
if (e.usage.cacheRead) sink.counter("provider.tokens.cache_read", e.usage.cacheRead);
|
|
314
|
-
if (e.usage.cacheWrite) sink.counter("provider.tokens.cache_write", e.usage.cacheWrite);
|
|
315
|
-
}),
|
|
316
|
-
events.on(
|
|
317
|
-
"provider.retry",
|
|
318
|
-
(e) => sink.counter("provider.retries.total", 1, {
|
|
319
|
-
provider: e.providerId,
|
|
320
|
-
status: String(e.status)
|
|
321
|
-
})
|
|
322
|
-
),
|
|
323
|
-
events.on(
|
|
324
|
-
"provider.error",
|
|
325
|
-
(e) => sink.counter("provider.errors.total", 1, {
|
|
326
|
-
provider: e.providerId,
|
|
327
|
-
status: String(e.status),
|
|
328
|
-
retryable: String(e.retryable)
|
|
329
|
-
})
|
|
330
|
-
),
|
|
331
|
-
events.on(
|
|
332
|
-
"tool.started",
|
|
333
|
-
(e) => sink.counter("tool.starts.total", 1, { tool: metricToolName(e.name) })
|
|
334
|
-
),
|
|
335
|
-
events.on("tool.executed", (e) => {
|
|
336
|
-
const tool = metricToolName(e.name);
|
|
337
|
-
sink.counter("tool.executions.total", 1, { tool, ok: String(e.ok) });
|
|
338
|
-
sink.histogram("tool.duration_ms", e.durationMs, { tool });
|
|
339
|
-
}),
|
|
340
|
-
events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
|
|
341
|
-
events.on("compaction.fired", (e) => {
|
|
342
|
-
sink.counter("compaction.fired.total");
|
|
343
|
-
sink.histogram("compaction.reduction_tokens", e.report.before - e.report.after);
|
|
344
|
-
}),
|
|
345
|
-
// MCP server names are user-controlled and may contain tenant/project
|
|
346
|
-
// identifiers. Keep them out of metric labels to bound cardinality and
|
|
347
|
-
// prevent operational metadata leaks.
|
|
348
|
-
events.on("mcp.server.connected", () => sink.counter("mcp.connects.total")),
|
|
349
|
-
events.on("mcp.server.reconnected", () => sink.counter("mcp.reconnects.total")),
|
|
350
|
-
events.on("mcp.server.disconnected", () => sink.counter("mcp.disconnects.total")),
|
|
351
|
-
events.on("error", (e) => sink.counter("agent.errors.total", 1, { phase: e.phase }))
|
|
352
|
-
);
|
|
353
|
-
return () => {
|
|
354
|
-
for (const u of unsubs) u();
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
function metricToolName(name) {
|
|
358
|
-
return name.startsWith("mcp__") ? "mcp_proxy" : name;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// src/observability/process-telemetry.ts
|
|
362
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
363
|
-
import { createHash } from "node:crypto";
|
|
364
|
-
|
|
365
|
-
// src/utils/expect-defined.ts
|
|
366
|
-
function expectDefined(value, label) {
|
|
367
|
-
if (value === null || value === void 0) {
|
|
368
|
-
const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
|
|
369
|
-
err.name = "ExpectDefinedError";
|
|
370
|
-
throw err;
|
|
371
|
-
}
|
|
372
|
-
return value;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// src/observability/redact-command.ts
|
|
376
|
-
var SENSITIVE_FLAG_PATTERNS = [
|
|
377
|
-
// --flag=value or --flag "value" (value captured up to next space or comma)
|
|
378
|
-
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
379
|
-
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
380
|
-
// The separator group is optional so the attached form (the common one) matches.
|
|
381
|
-
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
382
|
-
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
383
|
-
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
384
|
-
// redacted, not just the first. Synced with packages/tools _redact-command.ts.
|
|
385
|
-
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
386
|
-
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
387
|
-
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
388
|
-
// redaction function (false positive = cosmetic noise; false negative = leak).
|
|
389
|
-
/(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
|
|
390
|
-
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
391
|
-
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD|PASSPHRASE)\s*[=:]\s*[^\s,]+/gi,
|
|
392
|
-
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
393
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
394
|
-
// every such flag in the command line is redacted, not just the first.
|
|
395
|
-
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
396
|
-
];
|
|
397
|
-
function redactCommand(cmd) {
|
|
398
|
-
let result = cmd;
|
|
399
|
-
for (const pattern of SENSITIVE_FLAG_PATTERNS) {
|
|
400
|
-
result = result.replace(pattern, (match) => {
|
|
401
|
-
const eq = match.indexOf("=");
|
|
402
|
-
const sp = match.search(/\s/);
|
|
403
|
-
const delim = eq !== -1 ? "=" : sp !== -1 ? match[sp] : null;
|
|
404
|
-
if (delim !== null) {
|
|
405
|
-
const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
|
|
406
|
-
return `${flag}[REDACTED]`;
|
|
407
|
-
}
|
|
408
|
-
if (match.startsWith("--")) {
|
|
409
|
-
return match;
|
|
410
|
-
}
|
|
411
|
-
return `${match.slice(0, 2)}[REDACTED]`;
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
return result;
|
|
415
|
-
}
|
|
416
|
-
var BARE_SENSITIVE_LONG_FLAG = /^--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)$/i;
|
|
417
|
-
var BARE_SENSITIVE_SHORT_FLAG = /^-(?:p|t|a)$/i;
|
|
418
|
-
function redactCommandArgs(command, args) {
|
|
419
|
-
const redactedCommand = redactCommand(command);
|
|
420
|
-
const redactedArgs = args.map((a) => redactCommand(a));
|
|
421
|
-
for (let i = 0; i < redactedArgs.length - 1; i++) {
|
|
422
|
-
const flag = redactedArgs[i];
|
|
423
|
-
const next = redactedArgs[i + 1];
|
|
424
|
-
if (flag === void 0 || next === void 0) continue;
|
|
425
|
-
const isBareSensitive = (BARE_SENSITIVE_LONG_FLAG.test(flag) || BARE_SENSITIVE_SHORT_FLAG.test(flag)) && !flag.includes("[REDACTED]");
|
|
426
|
-
if (isBareSensitive && !next.startsWith("-")) {
|
|
427
|
-
redactedArgs[i + 1] = "[REDACTED]";
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return { command: redactedCommand, args: redactedArgs };
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// src/observability/process-telemetry.ts
|
|
434
|
-
var storage = new AsyncLocalStorage();
|
|
435
|
-
function runWithProcessTelemetry(context, run) {
|
|
436
|
-
return storage.run(context, run);
|
|
437
|
-
}
|
|
438
|
-
function emitProcessStarted(event) {
|
|
439
|
-
const { command, args } = redactCommandArgs(event.command, event.args);
|
|
440
|
-
emit("process.started", { ...event, command, args });
|
|
441
|
-
}
|
|
442
|
-
function emitProcessOutput(input) {
|
|
443
|
-
const bytes = Buffer.byteLength(input.chunk);
|
|
444
|
-
emit("process.output", {
|
|
445
|
-
...input.pid !== void 0 ? { pid: input.pid } : {},
|
|
446
|
-
stream: input.stream,
|
|
447
|
-
bytes,
|
|
448
|
-
chunkHash: createHash("sha256").update(input.chunk).digest("hex"),
|
|
449
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
function emitProcessCompleted(event) {
|
|
453
|
-
emit("process.completed", event);
|
|
454
|
-
}
|
|
455
|
-
function emit(name, event) {
|
|
456
|
-
const context = storage.getStore();
|
|
457
|
-
if (!context) return;
|
|
458
|
-
const { events, ...identity2 } = context;
|
|
459
|
-
events.emit(name, { ...identity2, ...event });
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// src/observability/network-telemetry.ts
|
|
463
|
-
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
464
|
-
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
465
|
-
import { channel } from "node:diagnostics_channel";
|
|
466
|
-
var storage2 = new AsyncLocalStorage2();
|
|
467
|
-
var requests = /* @__PURE__ */ new WeakMap();
|
|
468
|
-
var users = 0;
|
|
469
|
-
var unsubscribe;
|
|
470
|
-
function runWithNetworkTelemetry(context, run) {
|
|
471
|
-
return storage2.run(context, run);
|
|
472
|
-
}
|
|
473
|
-
function startNetworkTelemetryMonitor() {
|
|
474
|
-
users++;
|
|
475
|
-
if (!unsubscribe) unsubscribe = subscribe();
|
|
476
|
-
let stopped = false;
|
|
477
|
-
return () => {
|
|
478
|
-
if (stopped) return;
|
|
479
|
-
stopped = true;
|
|
480
|
-
users--;
|
|
481
|
-
if (users <= 0) {
|
|
482
|
-
unsubscribe?.();
|
|
483
|
-
unsubscribe = void 0;
|
|
484
|
-
users = 0;
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
function subscribe() {
|
|
489
|
-
const create = channel("undici:request:create");
|
|
490
|
-
const headers = channel("undici:request:headers");
|
|
491
|
-
const error = channel("undici:request:error");
|
|
492
|
-
const onCreate = (message) => {
|
|
493
|
-
const context = storage2.getStore();
|
|
494
|
-
const request = objectField(message, "request");
|
|
495
|
-
if (!context || !request) return;
|
|
496
|
-
const target = safeTarget(request);
|
|
497
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
498
|
-
const requestBytes = numberField(request, "contentLength");
|
|
499
|
-
const state = {
|
|
500
|
-
...context,
|
|
501
|
-
requestId: randomUUID(),
|
|
502
|
-
...target,
|
|
503
|
-
...requestBytes !== void 0 ? { requestBytes } : {},
|
|
504
|
-
startedAt,
|
|
505
|
-
startedNs: process.hrtime.bigint()
|
|
506
|
-
};
|
|
507
|
-
requests.set(request, state);
|
|
508
|
-
context.events.emit("network.request.started", {
|
|
509
|
-
...identity(state),
|
|
510
|
-
requestId: state.requestId,
|
|
511
|
-
method: state.method,
|
|
512
|
-
scheme: state.scheme,
|
|
513
|
-
serverAddress: state.serverAddress,
|
|
514
|
-
...state.serverPort !== void 0 ? { serverPort: state.serverPort } : {},
|
|
515
|
-
pathHash: state.pathHash,
|
|
516
|
-
queryKeys: queryKeys(request),
|
|
517
|
-
...state.requestBytes !== void 0 ? { requestBytes: state.requestBytes } : {},
|
|
518
|
-
startedAt
|
|
519
|
-
});
|
|
520
|
-
};
|
|
521
|
-
const onHeaders = (message) => {
|
|
522
|
-
const request = objectField(message, "request");
|
|
523
|
-
if (!request) return;
|
|
524
|
-
const state = requests.get(request);
|
|
525
|
-
if (!state) return;
|
|
526
|
-
requests.delete(request);
|
|
527
|
-
const response = objectField(message, "response");
|
|
528
|
-
const responseBytes = contentLength(response);
|
|
529
|
-
state.events.emit("network.request.completed", {
|
|
530
|
-
...identity(state),
|
|
531
|
-
requestId: state.requestId,
|
|
532
|
-
method: state.method,
|
|
533
|
-
scheme: state.scheme,
|
|
534
|
-
serverAddress: state.serverAddress,
|
|
535
|
-
...state.serverPort !== void 0 ? { serverPort: state.serverPort } : {},
|
|
536
|
-
pathHash: state.pathHash,
|
|
537
|
-
statusCode: numberField(response, "statusCode") ?? 0,
|
|
538
|
-
durationMs: elapsed(state.startedNs),
|
|
539
|
-
...state.requestBytes !== void 0 ? { requestBytes: state.requestBytes } : {},
|
|
540
|
-
...responseBytes !== void 0 ? { responseBytes } : {},
|
|
541
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
542
|
-
});
|
|
543
|
-
};
|
|
544
|
-
const onError = (message) => {
|
|
545
|
-
const request = objectField(message, "request");
|
|
546
|
-
if (!request) return;
|
|
547
|
-
const state = requests.get(request);
|
|
548
|
-
if (!state) return;
|
|
549
|
-
requests.delete(request);
|
|
550
|
-
const err = objectField(message, "error");
|
|
551
|
-
const errorCode = stringField(err, "code");
|
|
552
|
-
state.events.emit("network.request.failed", {
|
|
553
|
-
...identity(state),
|
|
554
|
-
requestId: state.requestId,
|
|
555
|
-
method: state.method,
|
|
556
|
-
scheme: state.scheme,
|
|
557
|
-
serverAddress: state.serverAddress,
|
|
558
|
-
...state.serverPort !== void 0 ? { serverPort: state.serverPort } : {},
|
|
559
|
-
pathHash: state.pathHash,
|
|
560
|
-
durationMs: elapsed(state.startedNs),
|
|
561
|
-
errorName: stringField(err, "name") ?? "Error",
|
|
562
|
-
...errorCode ? { errorCode } : {},
|
|
563
|
-
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
564
|
-
});
|
|
565
|
-
};
|
|
566
|
-
create.subscribe(onCreate);
|
|
567
|
-
headers.subscribe(onHeaders);
|
|
568
|
-
error.subscribe(onError);
|
|
569
|
-
return () => {
|
|
570
|
-
create.unsubscribe(onCreate);
|
|
571
|
-
headers.unsubscribe(onHeaders);
|
|
572
|
-
error.unsubscribe(onError);
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
function identity(state) {
|
|
576
|
-
return {
|
|
577
|
-
sessionId: state.sessionId,
|
|
578
|
-
...state.traceId ? { traceId: state.traceId } : {},
|
|
579
|
-
...state.agentId ? { agentId: state.agentId } : {},
|
|
580
|
-
...state.toolCallId ? { toolCallId: state.toolCallId } : {},
|
|
581
|
-
...state.attemptId ? { attemptId: state.attemptId } : {},
|
|
582
|
-
initiator: state.initiator,
|
|
583
|
-
operationName: state.operationName
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
function safeTarget(request) {
|
|
587
|
-
const origin = String(request.origin ?? "");
|
|
588
|
-
const path = String(request.path ?? "");
|
|
589
|
-
let url;
|
|
590
|
-
try {
|
|
591
|
-
url = new URL(path, origin);
|
|
592
|
-
} catch {
|
|
593
|
-
url = new URL("http://invalid.local/");
|
|
594
|
-
}
|
|
595
|
-
return {
|
|
596
|
-
method: String(request.method ?? "GET").toUpperCase(),
|
|
597
|
-
scheme: url.protocol.replace(":", ""),
|
|
598
|
-
serverAddress: url.hostname,
|
|
599
|
-
...url.port ? { serverPort: Number(url.port) } : {},
|
|
600
|
-
pathHash: hash(url.pathname)
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
function queryKeys(request) {
|
|
604
|
-
try {
|
|
605
|
-
return [...new URL(String(request.path ?? ""), String(request.origin ?? "")).searchParams.keys()].filter((v, i, a) => a.indexOf(v) === i).slice(0, 50);
|
|
606
|
-
} catch {
|
|
607
|
-
return [];
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
function contentLength(response) {
|
|
611
|
-
const raw = response?.headers;
|
|
612
|
-
if (!Array.isArray(raw)) return void 0;
|
|
613
|
-
for (let index = 0; index < raw.length - 1; index += 2) if (String(raw[index]).toLowerCase() === "content-length") {
|
|
614
|
-
const value = Number(String(raw[index + 1]));
|
|
615
|
-
return Number.isFinite(value) ? value : void 0;
|
|
616
|
-
}
|
|
617
|
-
return void 0;
|
|
618
|
-
}
|
|
619
|
-
function objectField(value, key) {
|
|
620
|
-
return value && typeof value === "object" && value[key] && typeof value[key] === "object" ? value[key] : void 0;
|
|
621
|
-
}
|
|
622
|
-
function stringField(value, key) {
|
|
623
|
-
return typeof value?.[key] === "string" ? value[key] : void 0;
|
|
624
|
-
}
|
|
625
|
-
function numberField(value, key) {
|
|
626
|
-
return typeof value?.[key] === "number" ? value[key] : void 0;
|
|
627
|
-
}
|
|
628
|
-
function elapsed(start) {
|
|
629
|
-
return Number(process.hrtime.bigint() - start) / 1e6;
|
|
630
|
-
}
|
|
631
|
-
function hash(value) {
|
|
632
|
-
return createHash2("sha256").update(value).digest("hex");
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// src/observability/prometheus.ts
|
|
636
|
-
import { timingSafeEqual } from "node:crypto";
|
|
637
|
-
var NUMBER_FORMAT_INFINITY = "NaN";
|
|
638
|
-
function escapeLabelValue(v) {
|
|
639
|
-
return v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
|
|
640
|
-
}
|
|
641
|
-
function formatLabels(labels) {
|
|
642
|
-
const keys = Object.keys(labels);
|
|
643
|
-
if (keys.length === 0) return "";
|
|
644
|
-
const parts = keys.map((k) => `${k}="${escapeLabelValue(labels[k] ?? "")}"`);
|
|
645
|
-
return `{${parts.join(",")}}`;
|
|
646
|
-
}
|
|
647
|
-
function formatNumber(n) {
|
|
648
|
-
if (!Number.isFinite(n)) return NUMBER_FORMAT_INFINITY;
|
|
649
|
-
return Number.isInteger(n) ? n.toString() : n.toString();
|
|
650
|
-
}
|
|
651
|
-
function joinLabels(base, extra) {
|
|
652
|
-
return { ...base, ...extra };
|
|
653
|
-
}
|
|
654
|
-
function renderPrometheus(snapshot) {
|
|
655
|
-
const groups = /* @__PURE__ */ new Map();
|
|
656
|
-
for (const s of snapshot.series) {
|
|
657
|
-
let g = groups.get(s.name);
|
|
658
|
-
if (!g) {
|
|
659
|
-
g = { type: s.type, rows: [] };
|
|
660
|
-
groups.set(s.name, g);
|
|
661
|
-
}
|
|
662
|
-
g.rows.push({ labels: s.labels, values: s.values });
|
|
663
|
-
}
|
|
664
|
-
const lines = [];
|
|
665
|
-
for (const [name, g] of groups) {
|
|
666
|
-
const promType = g.type === "histogram" ? "summary" : g.type;
|
|
667
|
-
lines.push(`# HELP ${name} ${name}`);
|
|
668
|
-
lines.push(`# TYPE ${name} ${promType}`);
|
|
669
|
-
if (g.type === "counter" || g.type === "gauge") {
|
|
670
|
-
for (const row of g.rows) {
|
|
671
|
-
lines.push(`${name}${formatLabels(row.labels)} ${formatNumber(row.values.value ?? 0)}`);
|
|
672
|
-
}
|
|
673
|
-
} else {
|
|
674
|
-
for (const row of g.rows) {
|
|
675
|
-
const { count = 0, sum = 0, p50 = 0, p95 = 0, p99 = 0 } = row.values;
|
|
676
|
-
lines.push(
|
|
677
|
-
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.5" }))} ${formatNumber(p50)}`
|
|
678
|
-
);
|
|
679
|
-
lines.push(
|
|
680
|
-
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.95" }))} ${formatNumber(p95)}`
|
|
681
|
-
);
|
|
682
|
-
lines.push(
|
|
683
|
-
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.99" }))} ${formatNumber(p99)}`
|
|
684
|
-
);
|
|
685
|
-
lines.push(`${name}_sum${formatLabels(row.labels)} ${formatNumber(sum)}`);
|
|
686
|
-
lines.push(`${name}_count${formatLabels(row.labels)} ${formatNumber(count)}`);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
return lines.join("\n") + "\n";
|
|
691
|
-
}
|
|
692
|
-
var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
693
|
-
function isLoopbackBindHost(host) {
|
|
694
|
-
const h = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
695
|
-
return h === "127.0.0.1" || h === "localhost" || h === "::1" || h.startsWith("127.");
|
|
696
|
-
}
|
|
697
|
-
function bearerMatches(header, token) {
|
|
698
|
-
if (!header) return false;
|
|
699
|
-
const prefix = "bearer ";
|
|
700
|
-
if (!header.toLowerCase().startsWith(prefix)) return false;
|
|
701
|
-
const supplied = Buffer.from(header.slice(prefix.length).trim(), "utf8");
|
|
702
|
-
const expected = Buffer.from(token, "utf8");
|
|
703
|
-
if (supplied.length !== expected.length) return false;
|
|
704
|
-
return timingSafeEqual(supplied, expected);
|
|
705
|
-
}
|
|
706
|
-
async function startMetricsServer(opts) {
|
|
707
|
-
const tls = opts.tls;
|
|
708
|
-
const useHttps = !!(tls?.cert && tls?.key);
|
|
709
|
-
const host = opts.host ?? "127.0.0.1";
|
|
710
|
-
const path = opts.path ?? "/metrics";
|
|
711
|
-
const healthPath = opts.healthPath ?? "/healthz";
|
|
712
|
-
const healthRegistry = opts.healthRegistry;
|
|
713
|
-
const token = opts.token;
|
|
714
|
-
if (!isLoopbackBindHost(host) && !token) {
|
|
715
|
-
throw new Error(
|
|
716
|
-
`startMetricsServer: refusing to bind to non-loopback host "${host}" without a token \u2014 pass a token to expose metrics to the network, or bind to 127.0.0.1.`
|
|
717
|
-
);
|
|
718
|
-
}
|
|
719
|
-
const listener = (req, res) => {
|
|
720
|
-
if (!req.url || req.method !== "GET") {
|
|
721
|
-
res.statusCode = req.url ? 405 : 400;
|
|
722
|
-
res.end();
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
if (token && !bearerMatches(req.headers.authorization, token)) {
|
|
726
|
-
res.statusCode = 401;
|
|
727
|
-
res.setHeader("www-authenticate", "Bearer");
|
|
728
|
-
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
729
|
-
res.end("Unauthorized");
|
|
730
|
-
return;
|
|
731
|
-
}
|
|
732
|
-
const url = req.url.split("?")[0];
|
|
733
|
-
if (url === path) {
|
|
734
|
-
let body;
|
|
735
|
-
try {
|
|
736
|
-
body = renderPrometheus(opts.sink.snapshot());
|
|
737
|
-
} catch (err) {
|
|
738
|
-
res.statusCode = 500;
|
|
739
|
-
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
740
|
-
res.end(`metrics render failed: ${toErrorMessage(err)}`);
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
res.statusCode = 200;
|
|
744
|
-
res.setHeader("content-type", PROMETHEUS_CONTENT_TYPE);
|
|
745
|
-
res.end(body);
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
if (healthRegistry && url === healthPath) {
|
|
749
|
-
healthRegistry.run().then(
|
|
750
|
-
(agg) => {
|
|
751
|
-
res.statusCode = agg.status === "unhealthy" ? 503 : 200;
|
|
752
|
-
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
753
|
-
res.end(JSON.stringify(agg, null, 2));
|
|
754
|
-
},
|
|
755
|
-
(err) => {
|
|
756
|
-
res.statusCode = 500;
|
|
757
|
-
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
758
|
-
res.end(`health run failed: ${toErrorMessage(err)}`);
|
|
759
|
-
}
|
|
760
|
-
);
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
res.statusCode = 404;
|
|
764
|
-
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
765
|
-
res.end("Not Found");
|
|
766
|
-
};
|
|
767
|
-
let server;
|
|
768
|
-
if (useHttps && tls) {
|
|
769
|
-
const { createServer } = await import("node:https");
|
|
770
|
-
const { readFileSync } = await import("node:fs");
|
|
771
|
-
server = createServer(
|
|
772
|
-
{ cert: readFileSync(tls.cert), key: readFileSync(tls.key) },
|
|
773
|
-
listener
|
|
774
|
-
);
|
|
775
|
-
} else {
|
|
776
|
-
const { createServer } = await import("node:http");
|
|
777
|
-
server = createServer(listener);
|
|
778
|
-
}
|
|
779
|
-
await new Promise((resolve, reject) => {
|
|
780
|
-
const onError = (err) => {
|
|
781
|
-
server.off("listening", onListening);
|
|
782
|
-
reject(err);
|
|
783
|
-
};
|
|
784
|
-
const onListening = () => {
|
|
785
|
-
server.off("error", onError);
|
|
786
|
-
resolve();
|
|
787
|
-
};
|
|
788
|
-
server.once("error", onError);
|
|
789
|
-
server.once("listening", onListening);
|
|
790
|
-
server.listen(opts.port, host);
|
|
791
|
-
});
|
|
792
|
-
const addr = server.address();
|
|
793
|
-
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
794
|
-
const protocol = useHttps ? "https" : "http";
|
|
795
|
-
return {
|
|
796
|
-
port: boundPort,
|
|
797
|
-
url: `${protocol}://${host}:${boundPort}${path}`,
|
|
798
|
-
close: () => new Promise((resolve, reject) => {
|
|
799
|
-
server.close((err) => err ? reject(err) : resolve());
|
|
800
|
-
})
|
|
801
|
-
};
|
|
329
|
+
};
|
|
330
|
+
function parseLabelKey(key) {
|
|
331
|
+
if (!key) return {};
|
|
332
|
+
const labels = {};
|
|
333
|
+
for (const pair of key.split(",")) {
|
|
334
|
+
const eq = pair.indexOf("=");
|
|
335
|
+
if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
336
|
+
}
|
|
337
|
+
return labels;
|
|
802
338
|
}
|
|
339
|
+
var NoopMetricsSink = class {
|
|
340
|
+
counter() {
|
|
341
|
+
}
|
|
342
|
+
gauge() {
|
|
343
|
+
}
|
|
344
|
+
histogram() {
|
|
345
|
+
}
|
|
346
|
+
snapshot() {
|
|
347
|
+
return { timestamp: Date.now(), series: [] };
|
|
348
|
+
}
|
|
349
|
+
droppedObservations() {
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
reset() {
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// src/observability/otel-tracer.ts
|
|
357
|
+
var OTEL_STATUS_ERROR = 2;
|
|
358
|
+
var OTelTracer = class {
|
|
359
|
+
constructor(upstream) {
|
|
360
|
+
this.upstream = upstream;
|
|
361
|
+
}
|
|
362
|
+
upstream;
|
|
363
|
+
startSpan(name, attrs) {
|
|
364
|
+
const otelSpan = this.upstream.startSpan(name, attrs ? { attributes: attrs } : void 0);
|
|
365
|
+
return new OTelSpan(otelSpan);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
var OTelSpan = class {
|
|
369
|
+
constructor(span) {
|
|
370
|
+
this.span = span;
|
|
371
|
+
}
|
|
372
|
+
span;
|
|
373
|
+
setAttribute(key, value) {
|
|
374
|
+
this.span.setAttribute(key, value);
|
|
375
|
+
}
|
|
376
|
+
recordError(err) {
|
|
377
|
+
this.span.recordException(err);
|
|
378
|
+
this.span.setStatus?.({ code: OTEL_STATUS_ERROR, message: err.message });
|
|
379
|
+
}
|
|
380
|
+
end() {
|
|
381
|
+
this.span.end();
|
|
382
|
+
}
|
|
383
|
+
};
|
|
803
384
|
|
|
804
385
|
// src/observability/otlp-metrics.ts
|
|
805
386
|
var DEFAULT_INTERVAL_MS = 3e4;
|
|
@@ -1064,6 +645,269 @@ function startOtlpTraceExporter(opts) {
|
|
|
1064
645
|
buffered: () => [...buffer]
|
|
1065
646
|
};
|
|
1066
647
|
}
|
|
648
|
+
|
|
649
|
+
// src/observability/prometheus.ts
|
|
650
|
+
import { timingSafeEqual } from "node:crypto";
|
|
651
|
+
var NUMBER_FORMAT_INFINITY = "NaN";
|
|
652
|
+
function escapeLabelValue(v) {
|
|
653
|
+
return v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
|
|
654
|
+
}
|
|
655
|
+
function formatLabels(labels) {
|
|
656
|
+
const keys = Object.keys(labels);
|
|
657
|
+
if (keys.length === 0) return "";
|
|
658
|
+
const parts = keys.map((k) => `${k}="${escapeLabelValue(labels[k] ?? "")}"`);
|
|
659
|
+
return `{${parts.join(",")}}`;
|
|
660
|
+
}
|
|
661
|
+
function formatNumber(n) {
|
|
662
|
+
if (!Number.isFinite(n)) return NUMBER_FORMAT_INFINITY;
|
|
663
|
+
return Number.isInteger(n) ? n.toString() : n.toString();
|
|
664
|
+
}
|
|
665
|
+
function joinLabels(base, extra) {
|
|
666
|
+
return { ...base, ...extra };
|
|
667
|
+
}
|
|
668
|
+
function renderPrometheus(snapshot) {
|
|
669
|
+
const groups = /* @__PURE__ */ new Map();
|
|
670
|
+
for (const s of snapshot.series) {
|
|
671
|
+
let g = groups.get(s.name);
|
|
672
|
+
if (!g) {
|
|
673
|
+
g = { type: s.type, rows: [] };
|
|
674
|
+
groups.set(s.name, g);
|
|
675
|
+
}
|
|
676
|
+
g.rows.push({ labels: s.labels, values: s.values });
|
|
677
|
+
}
|
|
678
|
+
const lines = [];
|
|
679
|
+
for (const [name, g] of groups) {
|
|
680
|
+
const promType = g.type === "histogram" ? "summary" : g.type;
|
|
681
|
+
lines.push(`# HELP ${name} ${name}`);
|
|
682
|
+
lines.push(`# TYPE ${name} ${promType}`);
|
|
683
|
+
if (g.type === "counter" || g.type === "gauge") {
|
|
684
|
+
for (const row of g.rows) {
|
|
685
|
+
lines.push(`${name}${formatLabels(row.labels)} ${formatNumber(row.values.value ?? 0)}`);
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
for (const row of g.rows) {
|
|
689
|
+
const { count = 0, sum = 0, p50 = 0, p95 = 0, p99 = 0 } = row.values;
|
|
690
|
+
lines.push(
|
|
691
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.5" }))} ${formatNumber(p50)}`
|
|
692
|
+
);
|
|
693
|
+
lines.push(
|
|
694
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.95" }))} ${formatNumber(p95)}`
|
|
695
|
+
);
|
|
696
|
+
lines.push(
|
|
697
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.99" }))} ${formatNumber(p99)}`
|
|
698
|
+
);
|
|
699
|
+
lines.push(`${name}_sum${formatLabels(row.labels)} ${formatNumber(sum)}`);
|
|
700
|
+
lines.push(`${name}_count${formatLabels(row.labels)} ${formatNumber(count)}`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return lines.join("\n") + "\n";
|
|
705
|
+
}
|
|
706
|
+
var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
707
|
+
function isLoopbackBindHost(host) {
|
|
708
|
+
const h = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
709
|
+
return h === "127.0.0.1" || h === "localhost" || h === "::1" || h.startsWith("127.");
|
|
710
|
+
}
|
|
711
|
+
function bearerMatches(header, token) {
|
|
712
|
+
if (!header) return false;
|
|
713
|
+
const prefix = "bearer ";
|
|
714
|
+
if (!header.toLowerCase().startsWith(prefix)) return false;
|
|
715
|
+
const supplied = Buffer.from(header.slice(prefix.length).trim(), "utf8");
|
|
716
|
+
const expected = Buffer.from(token, "utf8");
|
|
717
|
+
if (supplied.length !== expected.length) return false;
|
|
718
|
+
return timingSafeEqual(supplied, expected);
|
|
719
|
+
}
|
|
720
|
+
async function startMetricsServer(opts) {
|
|
721
|
+
const tls = opts.tls;
|
|
722
|
+
const useHttps = !!(tls?.cert && tls?.key);
|
|
723
|
+
const host = opts.host ?? "127.0.0.1";
|
|
724
|
+
const path = opts.path ?? "/metrics";
|
|
725
|
+
const healthPath = opts.healthPath ?? "/healthz";
|
|
726
|
+
const healthRegistry = opts.healthRegistry;
|
|
727
|
+
const token = opts.token;
|
|
728
|
+
if (!isLoopbackBindHost(host) && !token) {
|
|
729
|
+
throw new Error(
|
|
730
|
+
`startMetricsServer: refusing to bind to non-loopback host "${host}" without a token \u2014 pass a token to expose metrics to the network, or bind to 127.0.0.1.`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
const listener = (req, res) => {
|
|
734
|
+
if (!req.url || req.method !== "GET") {
|
|
735
|
+
res.statusCode = req.url ? 405 : 400;
|
|
736
|
+
res.end();
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (token && !bearerMatches(req.headers.authorization, token)) {
|
|
740
|
+
res.statusCode = 401;
|
|
741
|
+
res.setHeader("www-authenticate", "Bearer");
|
|
742
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
743
|
+
res.end("Unauthorized");
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const url = req.url.split("?")[0];
|
|
747
|
+
if (url === path) {
|
|
748
|
+
let body;
|
|
749
|
+
try {
|
|
750
|
+
body = renderPrometheus(opts.sink.snapshot());
|
|
751
|
+
} catch (err) {
|
|
752
|
+
res.statusCode = 500;
|
|
753
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
754
|
+
res.end(`metrics render failed: ${toErrorMessage(err)}`);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
res.statusCode = 200;
|
|
758
|
+
res.setHeader("content-type", PROMETHEUS_CONTENT_TYPE);
|
|
759
|
+
res.end(body);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (healthRegistry && url === healthPath) {
|
|
763
|
+
healthRegistry.run().then(
|
|
764
|
+
(agg) => {
|
|
765
|
+
res.statusCode = agg.status === "unhealthy" ? 503 : 200;
|
|
766
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
767
|
+
res.end(JSON.stringify(agg, null, 2));
|
|
768
|
+
},
|
|
769
|
+
(err) => {
|
|
770
|
+
res.statusCode = 500;
|
|
771
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
772
|
+
res.end(`health run failed: ${toErrorMessage(err)}`);
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
res.statusCode = 404;
|
|
778
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
779
|
+
res.end("Not Found");
|
|
780
|
+
};
|
|
781
|
+
let server;
|
|
782
|
+
if (useHttps && tls) {
|
|
783
|
+
const { createServer } = await import("node:https");
|
|
784
|
+
const { readFileSync } = await import("node:fs");
|
|
785
|
+
server = createServer(
|
|
786
|
+
{ cert: readFileSync(tls.cert), key: readFileSync(tls.key) },
|
|
787
|
+
listener
|
|
788
|
+
);
|
|
789
|
+
} else {
|
|
790
|
+
const { createServer } = await import("node:http");
|
|
791
|
+
server = createServer(listener);
|
|
792
|
+
}
|
|
793
|
+
await new Promise((resolve, reject) => {
|
|
794
|
+
const onError = (err) => {
|
|
795
|
+
server.off("listening", onListening);
|
|
796
|
+
reject(err);
|
|
797
|
+
};
|
|
798
|
+
const onListening = () => {
|
|
799
|
+
server.off("error", onError);
|
|
800
|
+
resolve();
|
|
801
|
+
};
|
|
802
|
+
server.once("error", onError);
|
|
803
|
+
server.once("listening", onListening);
|
|
804
|
+
server.listen(opts.port, host);
|
|
805
|
+
});
|
|
806
|
+
const addr = server.address();
|
|
807
|
+
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
808
|
+
const protocol = useHttps ? "https" : "http";
|
|
809
|
+
return {
|
|
810
|
+
port: boundPort,
|
|
811
|
+
url: `${protocol}://${host}:${boundPort}${path}`,
|
|
812
|
+
close: () => new Promise((resolve, reject) => {
|
|
813
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
814
|
+
})
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/observability/tool-usage-source.ts
|
|
819
|
+
var DEFAULT_LIMIT = 500;
|
|
820
|
+
function daysBetween(later, earlier) {
|
|
821
|
+
return Math.max(0, Math.floor((later - earlier) / 864e5));
|
|
822
|
+
}
|
|
823
|
+
function chronicleCandidates(store, opts, now) {
|
|
824
|
+
const rows = store.underusedTools({
|
|
825
|
+
...opts.fromDay !== void 0 ? { from: opts.fromDay } : {},
|
|
826
|
+
...opts.toDay !== void 0 ? { to: opts.toDay } : {},
|
|
827
|
+
limit: opts.limit ?? DEFAULT_LIMIT
|
|
828
|
+
});
|
|
829
|
+
return rows.map((row) => ({
|
|
830
|
+
name: row.toolName,
|
|
831
|
+
invocations: row.invocations,
|
|
832
|
+
failures: row.failures,
|
|
833
|
+
durationMsTotal: row.durationMsTotal,
|
|
834
|
+
lastInvokedAt: row.lastInvokedAt,
|
|
835
|
+
daysSinceLastUse: row.lastInvokedAt === null ? null : daysBetween(now, row.lastInvokedAt),
|
|
836
|
+
source: "chronicle"
|
|
837
|
+
}));
|
|
838
|
+
}
|
|
839
|
+
function inProcessCandidates(usage, opts, now) {
|
|
840
|
+
const windowStart = now - opts.idleDays * 864e5;
|
|
841
|
+
const out = [];
|
|
842
|
+
for (const [name, rec] of usage) {
|
|
843
|
+
if (rec.lastInvokedAt >= windowStart) continue;
|
|
844
|
+
out.push({
|
|
845
|
+
name,
|
|
846
|
+
invocations: rec.invocations,
|
|
847
|
+
failures: rec.failures,
|
|
848
|
+
durationMsTotal: rec.durationMsTotal,
|
|
849
|
+
lastInvokedAt: rec.lastInvokedAt,
|
|
850
|
+
daysSinceLastUse: daysBetween(now, rec.lastInvokedAt),
|
|
851
|
+
source: "in-process"
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
out.sort((a, b) => a.invocations - b.invocations || a.name.localeCompare(b.name));
|
|
855
|
+
return out.slice(0, opts.limit ?? DEFAULT_LIMIT);
|
|
856
|
+
}
|
|
857
|
+
function createToolUsageSource(deps) {
|
|
858
|
+
const now = deps.now ?? (() => Date.now());
|
|
859
|
+
if (deps.chronicle) {
|
|
860
|
+
const store = deps.chronicle;
|
|
861
|
+
return {
|
|
862
|
+
kind: "chronicle",
|
|
863
|
+
async candidates(opts) {
|
|
864
|
+
await store.refresh();
|
|
865
|
+
return chronicleCandidates(store, opts, now());
|
|
866
|
+
},
|
|
867
|
+
async refresh() {
|
|
868
|
+
await store.refresh();
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
if (deps.bridge) {
|
|
873
|
+
const usage = deps.bridge;
|
|
874
|
+
return {
|
|
875
|
+
kind: "in-process",
|
|
876
|
+
async candidates(opts) {
|
|
877
|
+
return inProcessCandidates(usage, opts, now());
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
return {
|
|
882
|
+
kind: "in-process",
|
|
883
|
+
async candidates() {
|
|
884
|
+
return [];
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function filterUnderused(candidates, opts, now = Date.now()) {
|
|
889
|
+
const windowStart = now - opts.idleDays * 864e5;
|
|
890
|
+
return candidates.filter((c) => {
|
|
891
|
+
if (c.invocations > opts.minInvocations) return false;
|
|
892
|
+
if (c.lastInvokedAt === null) return true;
|
|
893
|
+
return c.lastInvokedAt < windowStart;
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/observability/tracer.ts
|
|
898
|
+
var NoopTracer = class {
|
|
899
|
+
startSpan() {
|
|
900
|
+
return NOOP_SPAN;
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
var NOOP_SPAN = {
|
|
904
|
+
setAttribute() {
|
|
905
|
+
},
|
|
906
|
+
recordError() {
|
|
907
|
+
},
|
|
908
|
+
end() {
|
|
909
|
+
}
|
|
910
|
+
};
|
|
1067
911
|
export {
|
|
1068
912
|
DefaultHealthRegistry,
|
|
1069
913
|
InMemoryMetricsSink,
|
|
@@ -1073,9 +917,11 @@ export {
|
|
|
1073
917
|
PROMETHEUS_CONTENT_TYPE,
|
|
1074
918
|
buildOtlpMetricsRequest,
|
|
1075
919
|
buildOtlpTracesRequest,
|
|
920
|
+
createToolUsageSource,
|
|
1076
921
|
emitProcessCompleted,
|
|
1077
922
|
emitProcessOutput,
|
|
1078
923
|
emitProcessStarted,
|
|
924
|
+
filterUnderused,
|
|
1079
925
|
redactCommand,
|
|
1080
926
|
redactCommandArgs,
|
|
1081
927
|
renderPrometheus,
|