@botbotgo/agent-harness 0.0.475 → 0.0.476
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1234
- package/README.zh.md +3 -1191
- package/dist/acp.js +1 -1
- package/dist/api.js +1 -404
- package/dist/benchmark/checkpoint-resume-cost-benchmark.js +1 -55
- package/dist/benchmark/deepagent-local-model-benchmark.js +2 -35
- package/dist/benchmark/upstream-runtime-ab-benchmark.js +1 -179
- package/dist/cli/chat-interactive.js +25 -244
- package/dist/cli/chat-rendering.js +6 -100
- package/dist/cli/chat-stream.js +23 -512
- package/dist/cli/chat-ui.js +21 -199
- package/dist/cli/chat-workspace.js +2 -210
- package/dist/cli/main.js +21 -428
- package/dist/cli/managed-service-commands.js +9 -63
- package/dist/cli/managed-service.js +2 -137
- package/dist/cli/options-init-chat.js +1 -108
- package/dist/cli/options-runtime.js +1 -158
- package/dist/cli/options-serve.js +1 -282
- package/dist/cli/options.js +2 -19
- package/dist/cli/process-guards.js +1 -139
- package/dist/cli/request-tree.js +7 -296
- package/dist/cli/runtime-commands.js +12 -258
- package/dist/cli/runtime-output.js +16 -155
- package/dist/cli/server-commands.js +16 -270
- package/dist/cli/workspace.js +1 -67
- package/dist/cli.js +1 -7
- package/dist/client/acp.js +1 -1
- package/dist/client/in-process.js +1 -67
- package/dist/client/index.js +1 -2
- package/dist/client/types.js +0 -1
- package/dist/client.js +1 -1
- package/dist/contracts/core.js +1 -1
- package/dist/contracts/runtime-evaluation.js +0 -1
- package/dist/contracts/runtime-memory.js +0 -1
- package/dist/contracts/runtime-observability.js +0 -1
- package/dist/contracts/runtime-requests.js +0 -1
- package/dist/contracts/runtime-scheduling.js +0 -1
- package/dist/contracts/runtime.js +1 -27
- package/dist/contracts/types.js +1 -3
- package/dist/contracts/workspace.js +0 -1
- package/dist/flow/build-flow-graph.js +1 -50
- package/dist/flow/export-mermaid.js +2 -464
- package/dist/flow/export-sequence-mermaid.js +2 -325
- package/dist/flow/flow-graph-normalization.js +1 -214
- package/dist/flow/flow-graph-runtime.js +1 -107
- package/dist/flow/flow-graph-upstream.js +1 -494
- package/dist/flow/index.js +1 -3
- package/dist/flow/types.js +0 -1
- package/dist/index.js +1 -5
- package/dist/init-project.js +1 -1
- package/dist/knowledge/config.js +1 -32
- package/dist/knowledge/contracts.js +0 -1
- package/dist/knowledge/index.js +1 -2
- package/dist/knowledge/module.js +12 -909
- package/dist/knowledge/procedural/config.js +1 -125
- package/dist/knowledge/procedural/index.js +1 -2
- package/dist/knowledge/procedural/manager.js +9 -345
- package/dist/mcp.js +1 -2
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -2
- package/dist/persistence/file-store.js +3 -758
- package/dist/persistence/sqlite-request-context-store.js +5 -54
- package/dist/persistence/sqlite-request-queue-store.js +10 -108
- package/dist/persistence/sqlite-runtime.js +1 -86
- package/dist/persistence/sqlite-store.js +62 -810
- package/dist/persistence/types.js +0 -1
- package/dist/projections/presentation.js +37 -206
- package/dist/projections/request-events.js +2 -502
- package/dist/projections/upstream-events.js +1 -201
- package/dist/protocol/a2a/http-discovery.js +1 -178
- package/dist/protocol/a2a/http-rpc.js +6 -622
- package/dist/protocol/a2a/http.js +1 -138
- package/dist/protocol/a2a/task-state.js +3 -317
- package/dist/protocol/acp/client.js +8 -294
- package/dist/protocol/acp/harness-client.js +1 -218
- package/dist/protocol/acp/http.js +5 -130
- package/dist/protocol/acp/server.js +1 -310
- package/dist/protocol/acp/stdio.js +2 -69
- package/dist/protocol/ag-ui/http.js +3 -378
- package/dist/protocol/mcp/server.js +1 -428
- package/dist/resource/backend/workspace-scoped-backend.js +1 -319
- package/dist/resource/isolation.js +1 -237
- package/dist/resource/mcp/tool-support.js +3 -296
- package/dist/resource/mcp-tool-support.js +1 -2
- package/dist/resource/providers/resource-provider.js +1 -215
- package/dist/resource/resource-impl.js +1 -3
- package/dist/resource/resource-types.js +0 -1
- package/dist/resource/resource.js +1 -1
- package/dist/resource/sources.js +1 -247
- package/dist/resource/tools/function-tool-resolver.js +2 -272
- package/dist/runtime/adapter/compat/deepagent-compat.js +1 -29
- package/dist/runtime/adapter/compat/openai-compatible.js +1 -55
- package/dist/runtime/adapter/direct-builtin-utility.js +2 -90
- package/dist/runtime/adapter/flow/execution-context.js +1 -71
- package/dist/runtime/adapter/flow/invocation-flow.js +8 -425
- package/dist/runtime/adapter/flow/invoke-runtime.js +1 -20
- package/dist/runtime/adapter/flow/stream-runtime.js +11 -1395
- package/dist/runtime/adapter/invocation-result.js +2 -473
- package/dist/runtime/adapter/local-tool-invocation.js +6 -638
- package/dist/runtime/adapter/middleware/context-hygiene.js +1 -83
- package/dist/runtime/adapter/middleware-assembly.js +5 -477
- package/dist/runtime/adapter/model/invocation-request.js +3 -183
- package/dist/runtime/adapter/model/message-assembly.js +1 -28
- package/dist/runtime/adapter/model/model-providers.js +23 -1115
- package/dist/runtime/adapter/model/prompted-json-tool-call-capture.js +1 -40
- package/dist/runtime/adapter/model/prompted-json-tool-policy.js +1 -22
- package/dist/runtime/adapter/resilience.js +1 -104
- package/dist/runtime/adapter/runtime-adapter-support.js +3 -141
- package/dist/runtime/adapter/runtime-shell.js +5 -166
- package/dist/runtime/adapter/stream-event-projection.js +2 -622
- package/dist/runtime/adapter/stream-text-consumption.js +1 -18
- package/dist/runtime/adapter/terminal-status.js +2 -67
- package/dist/runtime/adapter/tool/builtin-middleware-tools.js +6 -627
- package/dist/runtime/adapter/tool/declared-middleware.js +1 -154
- package/dist/runtime/adapter/tool/interrupt-policy.js +1 -34
- package/dist/runtime/adapter/tool/provider-tool.js +1 -25
- package/dist/runtime/adapter/tool/resolved-tool.js +1 -225
- package/dist/runtime/adapter/tool/tool-arguments.js +3 -486
- package/dist/runtime/adapter/tool/tool-hitl.js +1 -346
- package/dist/runtime/adapter/tool/tool-name-mapping.js +1 -128
- package/dist/runtime/adapter/tool/tool-output-artifacts.js +2 -88
- package/dist/runtime/adapter/tool/tool-replay.js +1 -37
- package/dist/runtime/adapter/tool-resolution.js +1 -86
- package/dist/runtime/adapter/upstream-configurable-keys.js +1 -2
- package/dist/runtime/agent-runtime-adapter.js +60 -2338
- package/dist/runtime/agent-runtime-assembly.js +7 -249
- package/dist/runtime/env/runtime-env.js +1 -62
- package/dist/runtime/harness/background-runtime.js +1 -8
- package/dist/runtime/harness/bindings.js +1 -58
- package/dist/runtime/harness/events/event-bus.js +1 -16
- package/dist/runtime/harness/events/event-sink.js +1 -61
- package/dist/runtime/harness/events/events.js +1 -80
- package/dist/runtime/harness/events/listener-runtime.js +1 -13
- package/dist/runtime/harness/events/runtime-event-operations.js +1 -9
- package/dist/runtime/harness/events/streaming.js +1 -100
- package/dist/runtime/harness/events/timeline.js +1 -52
- package/dist/runtime/harness/public-shapes.js +1 -186
- package/dist/runtime/harness/run/artifact-paths.js +1 -15
- package/dist/runtime/harness/run/governance.js +1 -295
- package/dist/runtime/harness/run/helpers.js +1 -71
- package/dist/runtime/harness/run/inspection.js +1 -409
- package/dist/runtime/harness/run/operator-overview.js +1 -80
- package/dist/runtime/harness/run/queue-diagnostics.js +1 -15
- package/dist/runtime/harness/run/recovery.js +1 -162
- package/dist/runtime/harness/run/resources.js +1 -60
- package/dist/runtime/harness/run/resume.js +1 -56
- package/dist/runtime/harness/run/routing.js +1 -48
- package/dist/runtime/harness/run/run-lifecycle.js +1 -66
- package/dist/runtime/harness/run/run-operations.js +1 -217
- package/dist/runtime/harness/run/run-queue.js +1 -43
- package/dist/runtime/harness/run/run-slot-acquisition.js +1 -157
- package/dist/runtime/harness/run/session-records.js +1 -97
- package/dist/runtime/harness/run/start-run.js +1 -120
- package/dist/runtime/harness/run/startup-runtime.js +1 -69
- package/dist/runtime/harness/run/stream-run.js +8 -1418
- package/dist/runtime/harness/run/surface-semantics.js +1 -79
- package/dist/runtime/harness/runtime-defaults.js +1 -39
- package/dist/runtime/harness/system/boundary-analysis.js +1 -234
- package/dist/runtime/harness/system/health-monitor.js +1 -258
- package/dist/runtime/harness/system/inventory.js +1 -129
- package/dist/runtime/harness/system/mem0-ingestion-sync.js +5 -345
- package/dist/runtime/harness/system/policy-engine.js +1 -175
- package/dist/runtime/harness/system/runtime-memory-candidates.js +4 -110
- package/dist/runtime/harness/system/runtime-memory-consolidation.js +1 -51
- package/dist/runtime/harness/system/runtime-memory-manager.js +10 -693
- package/dist/runtime/harness/system/runtime-memory-policy.js +1 -155
- package/dist/runtime/harness/system/runtime-memory-records.js +11 -577
- package/dist/runtime/harness/system/runtime-memory-sync.js +5 -206
- package/dist/runtime/harness/system/session-memory-sync.js +3 -113
- package/dist/runtime/harness/system/skill-requirements.js +1 -112
- package/dist/runtime/harness/system/store.js +9 -365
- package/dist/runtime/harness/tool-gateway/index.js +1 -2
- package/dist/runtime/harness/tool-gateway/policy.js +1 -45
- package/dist/runtime/harness/tool-gateway/validation.js +1 -176
- package/dist/runtime/harness/tool-schema.js +1 -3
- package/dist/runtime/harness.js +3 -1490
- package/dist/runtime/index.js +1 -3
- package/dist/runtime/layout/runtime-layout.js +1 -31
- package/dist/runtime/maintenance/checkpoint-maintenance.js +2 -178
- package/dist/runtime/maintenance/file-checkpoint-saver.js +1 -106
- package/dist/runtime/maintenance/runtime-record-maintenance.js +2 -169
- package/dist/runtime/maintenance/sqlite-checkpoint-saver.js +4 -289
- package/dist/runtime/parsing/output-content.js +10 -550
- package/dist/runtime/parsing/output-parsing.js +1 -4
- package/dist/runtime/parsing/output-recovery.js +3 -213
- package/dist/runtime/parsing/output-tool-args.js +7 -663
- package/dist/runtime/parsing/stream-event-parsing.js +3 -362
- package/dist/runtime/prompts/runtime-prompts.js +4 -73
- package/dist/runtime/scheduling/system-schedule-manager.js +11 -532
- package/dist/runtime/skills/skill-metadata.js +1 -197
- package/dist/runtime/startup-tracing.js +2 -37
- package/dist/runtime/support/compiled-binding.js +1 -290
- package/dist/runtime/support/embedding-models.js +1 -118
- package/dist/runtime/support/harness-support.js +5 -137
- package/dist/runtime/support/llamaindex.js +1 -108
- package/dist/runtime/support/runtime-adapter-options.js +1 -29
- package/dist/runtime/support/runtime-factories.js +1 -51
- package/dist/runtime/support/vector-stores.js +9 -270
- package/dist/scaffold/init-project.js +54 -233
- package/dist/tooling/extensions.js +1 -311
- package/dist/tooling/module-loader.js +1 -55
- package/dist/tools.js +1 -176
- package/dist/utils/agent-display.js +1 -18
- package/dist/utils/bundled-text.js +4 -39
- package/dist/utils/compiled-binding.js +1 -33
- package/dist/utils/fs.js +2 -45
- package/dist/utils/id.js +1 -9
- package/dist/utils/message-content.js +1 -30
- package/dist/utils/object.js +1 -6
- package/dist/workspace/agent-binding-compiler.js +3 -613
- package/dist/workspace/compile.js +1 -472
- package/dist/workspace/framework-contract-validation.js +2 -322
- package/dist/workspace/index.js +1 -1
- package/dist/workspace/object-loader-paths.js +1 -71
- package/dist/workspace/object-loader-readers.js +1 -187
- package/dist/workspace/object-loader.js +1 -754
- package/dist/workspace/resource-compilers.js +1 -374
- package/dist/workspace/support/agent-capabilities.js +1 -37
- package/dist/workspace/support/agent-execution-config.js +1 -44
- package/dist/workspace/support/discovery.js +1 -147
- package/dist/workspace/support/source-collectors.js +1 -30
- package/dist/workspace/support/source-protocols.js +2 -192
- package/dist/workspace/support/workspace-ref-utils.js +1 -362
- package/dist/workspace/tool-hydration.js +1 -280
- package/dist/workspace/validate.js +1 -99
- package/dist/workspace/yaml-object-reader.js +1 -285
- package/package.json +7 -3
|
@@ -1,79 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { formatAgentName } from "../../../utils/agent-display.js";
|
|
3
|
-
import { getBindingMemorySources, getBindingSkills, } from "../../support/compiled-binding.js";
|
|
4
|
-
function asObject(value) {
|
|
5
|
-
return typeof value === "object" && value !== null ? value : null;
|
|
6
|
-
}
|
|
7
|
-
function readStringArray(value) {
|
|
8
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
9
|
-
}
|
|
10
|
-
function normalizeLabel(value) {
|
|
11
|
-
return value.replace(/\s+/g, " ").trim();
|
|
12
|
-
}
|
|
13
|
-
export function normalizeSurfaceToken(value) {
|
|
14
|
-
return value
|
|
15
|
-
.toLowerCase()
|
|
16
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
17
|
-
.replace(/^-+|-+$/g, "")
|
|
18
|
-
.slice(0, 80) || "item";
|
|
19
|
-
}
|
|
20
|
-
export function buildSurfaceId(kind, value) {
|
|
21
|
-
void kind;
|
|
22
|
-
return normalizeSurfaceToken(value);
|
|
23
|
-
}
|
|
24
|
-
export function stripStepPrefix(label) {
|
|
25
|
-
return normalizeLabel(label)
|
|
26
|
-
.replace(/^Calling LLM\s+/i, "")
|
|
27
|
-
.replace(/^Completed LLM\s+/i, "")
|
|
28
|
-
.replace(/^Calling tool\s+/i, "")
|
|
29
|
-
.replace(/^Completed tool\s+/i, "")
|
|
30
|
-
.replace(/^Tool\s+/i, "")
|
|
31
|
-
.replace(/\s+failed$/i, "")
|
|
32
|
-
.replace(/^Calling skill\s+/i, "")
|
|
33
|
-
.replace(/^Completed skill\s+/i, "")
|
|
34
|
-
.replace(/^Accessing memory\s+/i, "")
|
|
35
|
-
.replace(/^Completed memory\s+/i, "");
|
|
36
|
-
}
|
|
37
|
-
function canonicalMemoryName(memorySource) {
|
|
38
|
-
const normalized = normalizeLabel(memorySource.replace(/^memory\//i, ""));
|
|
39
|
-
const segments = normalized.split(/[\\/]/).filter(Boolean);
|
|
40
|
-
return (segments.at(-1) ?? normalized).toLowerCase() || "memory";
|
|
41
|
-
}
|
|
42
|
-
export function resolveSurfaceDisplayName(input) {
|
|
43
|
-
const baseName = stripStepPrefix(input.step) || formatAgentName(input.kind);
|
|
44
|
-
if (input.kind === "memory" && input.binding && baseName.length === 0) {
|
|
45
|
-
const memorySources = getBindingMemorySources(input.binding).filter((name) => name.trim().length > 0);
|
|
46
|
-
return memorySources.length === 1 ? canonicalMemoryName(memorySources[0]) : baseName;
|
|
47
|
-
}
|
|
48
|
-
if (input.kind !== "skill" || !input.binding || baseName.length > 0) {
|
|
49
|
-
return baseName;
|
|
50
|
-
}
|
|
51
|
-
const skillNames = getBindingSkills(input.binding)
|
|
52
|
-
.map((skillPath) => readSkillMetadata(skillPath).name)
|
|
53
|
-
.filter((name) => typeof name === "string" && name.trim().length > 0);
|
|
54
|
-
return skillNames.length === 1 ? skillNames[0] : baseName;
|
|
55
|
-
}
|
|
56
|
-
function resolveMemoryAction(event) {
|
|
57
|
-
const typed = asObject(event);
|
|
58
|
-
const runType = typeof typed?.run_type === "string" ? typed.run_type.trim().toLowerCase() : "";
|
|
59
|
-
if (runType === "memory") {
|
|
60
|
-
return "access";
|
|
61
|
-
}
|
|
62
|
-
return "access";
|
|
63
|
-
}
|
|
64
|
-
export function resolveSurfaceAction(input) {
|
|
65
|
-
switch (input.kind) {
|
|
66
|
-
case "agent":
|
|
67
|
-
return "handoff";
|
|
68
|
-
case "llm":
|
|
69
|
-
return "call";
|
|
70
|
-
case "tool":
|
|
71
|
-
return "execute";
|
|
72
|
-
case "skill":
|
|
73
|
-
return "apply";
|
|
74
|
-
case "memory":
|
|
75
|
-
return resolveMemoryAction(input.event);
|
|
76
|
-
default:
|
|
77
|
-
return "run";
|
|
78
|
-
}
|
|
79
|
-
}
|
|
1
|
+
import{readSkillMetadata as l}from"../../skills/skill-metadata.js";import{formatAgentName as a}from"../../../utils/agent-display.js";import{getBindingMemorySources as s,getBindingSkills as c}from"../../support/compiled-binding.js";function m(e){return typeof e=="object"&&e!==null?e:null}function C(e){return Array.isArray(e)?e.filter(r=>typeof r=="string"&&r.trim().length>0):[]}function o(e){return e.replace(/\s+/g," ").trim()}function p(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||"item"}function S(e,r){return p(r)}function f(e){return o(e).replace(/^Calling LLM\s+/i,"").replace(/^Completed LLM\s+/i,"").replace(/^Calling tool\s+/i,"").replace(/^Completed tool\s+/i,"").replace(/^Tool\s+/i,"").replace(/\s+failed$/i,"").replace(/^Calling skill\s+/i,"").replace(/^Completed skill\s+/i,"").replace(/^Accessing memory\s+/i,"").replace(/^Completed memory\s+/i,"")}function u(e){const r=o(e.replace(/^memory\//i,""));return(r.split(/[\\/]/).filter(Boolean).at(-1)??r).toLowerCase()||"memory"}function b(e){const r=f(e.step)||a(e.kind);if(e.kind==="memory"&&e.binding&&r.length===0){const t=s(e.binding).filter(i=>i.trim().length>0);return t.length===1?u(t[0]):r}if(e.kind!=="skill"||!e.binding||r.length>0)return r;const n=c(e.binding).map(t=>l(t).name).filter(t=>typeof t=="string"&&t.trim().length>0);return n.length===1?n[0]:r}function g(e){const r=m(e);return(typeof r?.run_type=="string"?r.run_type.trim().toLowerCase():"")==="memory","access"}function h(e){switch(e.kind){case"agent":return"handoff";case"llm":return"call";case"tool":return"execute";case"skill":return"apply";case"memory":return g(e.event);default:return"run"}}export{S as buildSurfaceId,p as normalizeSurfaceToken,h as resolveSurfaceAction,b as resolveSurfaceDisplayName,f as stripStepPrefix};
|
|
@@ -1,39 +1 @@
|
|
|
1
|
-
|
|
2
|
-
const runtimeMemory = workspace.bindings.values().next().value?.harnessRuntime.runtimeMemory;
|
|
3
|
-
const syncConfig = typeof runtimeMemory?.sessionMemorySync === "object" && runtimeMemory.sessionMemorySync
|
|
4
|
-
? runtimeMemory.sessionMemorySync
|
|
5
|
-
: undefined;
|
|
6
|
-
return runtimeMemory?.enabled === true && syncConfig?.enabled === true;
|
|
7
|
-
}
|
|
8
|
-
export function isInventoryEnabled(workspace) {
|
|
9
|
-
const runtime = workspace.refs.get("runtime/default");
|
|
10
|
-
const value = runtime && "value" in runtime && typeof runtime.value === "object" && runtime.value
|
|
11
|
-
? runtime.value
|
|
12
|
-
: undefined;
|
|
13
|
-
const inventory = typeof value?.inventory === "object" && value.inventory
|
|
14
|
-
? value.inventory
|
|
15
|
-
: undefined;
|
|
16
|
-
return inventory?.enabled === true;
|
|
17
|
-
}
|
|
18
|
-
export function createDefaultHealthSnapshot(activeRequestSlots, pendingRequestSlots) {
|
|
19
|
-
const updatedAt = new Date().toISOString();
|
|
20
|
-
const healthy = { status: "healthy", updatedAt, reason: "health monitor disabled" };
|
|
21
|
-
return {
|
|
22
|
-
status: "healthy",
|
|
23
|
-
updatedAt,
|
|
24
|
-
checks: {
|
|
25
|
-
runtime: healthy,
|
|
26
|
-
llm: healthy,
|
|
27
|
-
persistence: healthy,
|
|
28
|
-
capacity: healthy,
|
|
29
|
-
workload: healthy,
|
|
30
|
-
},
|
|
31
|
-
symptoms: [],
|
|
32
|
-
stats: {
|
|
33
|
-
activeRequestSlots,
|
|
34
|
-
pendingRequestSlots,
|
|
35
|
-
pendingApprovals: 0,
|
|
36
|
-
stuckRequests: 0,
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
1
|
+
function s(o){const e=o.bindings.values().next().value?.harnessRuntime.runtimeMemory,n=typeof e?.sessionMemorySync=="object"&&e.sessionMemorySync?e.sessionMemorySync:void 0;return e?.enabled===!0&&n?.enabled===!0}function r(o){const e=o.refs.get("runtime/default"),n=e&&"value"in e&&typeof e.value=="object"&&e.value?e.value:void 0;return(typeof n?.inventory=="object"&&n.inventory?n.inventory:void 0)?.enabled===!0}function a(o,e){const n=new Date().toISOString(),t={status:"healthy",updatedAt:n,reason:"health monitor disabled"};return{status:"healthy",updatedAt:n,checks:{runtime:t,llm:t,persistence:t,capacity:t,workload:t},symptoms:[],stats:{activeRequestSlots:o,pendingRequestSlots:e,pendingApprovals:0,stuckRequests:0}}}export{a as createDefaultHealthSnapshot,r as isInventoryEnabled,s as isSessionMemorySyncEnabled};
|
|
@@ -1,234 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { readSkillMetadata } from "../../skills/skill-metadata.js";
|
|
4
|
-
import { getBindingPrimaryTools, getBindingSkills, getBindingSubagents, } from "../../support/compiled-binding.js";
|
|
5
|
-
const BUILTIN_SKILL_TOOLS = new Set(["read_todos", "write_todos"]);
|
|
6
|
-
function uniqueSorted(values) {
|
|
7
|
-
return Array.from(new Set(values.filter((value) => value.trim().length > 0))).sort();
|
|
8
|
-
}
|
|
9
|
-
function addFinding(findings, options, finding) {
|
|
10
|
-
if (finding.severity === "info" && options.includeInfo === false) {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
findings.push(finding);
|
|
14
|
-
}
|
|
15
|
-
function discoverWorkspaceSkillPaths(workspaceRoot, referencedSkillPaths) {
|
|
16
|
-
const skillPaths = new Set(referencedSkillPaths);
|
|
17
|
-
const skillsRoot = path.join(workspaceRoot, "resources", "skills");
|
|
18
|
-
if (!existsSync(skillsRoot)) {
|
|
19
|
-
return uniqueSorted(Array.from(skillPaths));
|
|
20
|
-
}
|
|
21
|
-
for (const entry of readdirSync(skillsRoot)) {
|
|
22
|
-
const skillPath = path.join(skillsRoot, entry);
|
|
23
|
-
try {
|
|
24
|
-
if (statSync(skillPath).isDirectory() && existsSync(path.join(skillPath, "SKILL.md"))) {
|
|
25
|
-
skillPaths.add(skillPath);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return uniqueSorted(Array.from(skillPaths));
|
|
33
|
-
}
|
|
34
|
-
function describeTopLevelSurface(binding) {
|
|
35
|
-
return {
|
|
36
|
-
id: binding.agent.id,
|
|
37
|
-
description: binding.agent.description,
|
|
38
|
-
tools: uniqueSorted(getBindingPrimaryTools(binding).map((tool) => tool.name)),
|
|
39
|
-
skills: uniqueSorted(getBindingSkills(binding)),
|
|
40
|
-
subagents: uniqueSorted(getBindingSubagents(binding).map((subagent) => subagent.name)),
|
|
41
|
-
sourcePath: binding.agent.sourcePath,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function describeSubagentSurface(subagent, parentAgentId) {
|
|
45
|
-
return {
|
|
46
|
-
id: subagent.name,
|
|
47
|
-
description: subagent.description,
|
|
48
|
-
tools: uniqueSorted((subagent.tools ?? []).map((tool) => tool.name)),
|
|
49
|
-
skills: uniqueSorted(subagent.skills ?? []),
|
|
50
|
-
subagents: [],
|
|
51
|
-
parentAgentId,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function addOwner(index, key, agentId) {
|
|
55
|
-
const owners = index.get(key) ?? [];
|
|
56
|
-
owners.push(agentId);
|
|
57
|
-
index.set(key, owners);
|
|
58
|
-
}
|
|
59
|
-
function stripAgentRefPrefix(value) {
|
|
60
|
-
const prefix = "agent/";
|
|
61
|
-
return value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
|
62
|
-
}
|
|
63
|
-
export function analyzeWorkspaceBoundaries(workspace, options = {}) {
|
|
64
|
-
const findings = [];
|
|
65
|
-
const agentSurfaces = [];
|
|
66
|
-
const referencedSkillPaths = [];
|
|
67
|
-
const toolOwners = new Map();
|
|
68
|
-
const skillOwners = new Map();
|
|
69
|
-
const skillSurfaceOwners = new Map();
|
|
70
|
-
for (const binding of workspace.bindings.values()) {
|
|
71
|
-
const topLevel = describeTopLevelSurface(binding);
|
|
72
|
-
agentSurfaces.push(topLevel);
|
|
73
|
-
for (const skillPath of topLevel.skills) {
|
|
74
|
-
referencedSkillPaths.push(skillPath);
|
|
75
|
-
}
|
|
76
|
-
for (const subagent of getBindingSubagents(binding)) {
|
|
77
|
-
const surface = describeSubagentSurface(subagent, binding.agent.id);
|
|
78
|
-
agentSurfaces.push(surface);
|
|
79
|
-
for (const skillPath of surface.skills) {
|
|
80
|
-
referencedSkillPaths.push(skillPath);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
for (const surface of agentSurfaces) {
|
|
85
|
-
if (surface.description.trim().length === 0) {
|
|
86
|
-
addFinding(findings, options, {
|
|
87
|
-
severity: "warning",
|
|
88
|
-
code: "agent-missing-description",
|
|
89
|
-
message: `Agent '${surface.id}' has no description, making routing boundaries ambiguous.`,
|
|
90
|
-
agentId: surface.id,
|
|
91
|
-
parentAgentId: surface.parentAgentId,
|
|
92
|
-
sourcePath: surface.sourcePath,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
if (surface.subagents.length > 0 && (surface.tools.length > 0 || surface.skills.length > 0)) {
|
|
96
|
-
addFinding(findings, options, {
|
|
97
|
-
severity: "warning",
|
|
98
|
-
code: "mixed-routing-and-execution-surface",
|
|
99
|
-
message: `Agent '${surface.id}' declares subagents and also exposes local tools or skills.`,
|
|
100
|
-
agentId: surface.id,
|
|
101
|
-
sourcePath: surface.sourcePath,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
if (surface.subagents.length === 0 && surface.tools.length === 0 && surface.skills.length === 0) {
|
|
105
|
-
addFinding(findings, options, {
|
|
106
|
-
severity: "info",
|
|
107
|
-
code: "empty-execution-surface",
|
|
108
|
-
message: `Agent '${surface.id}' has no tools, skills, or subagents declared.`,
|
|
109
|
-
agentId: surface.id,
|
|
110
|
-
parentAgentId: surface.parentAgentId,
|
|
111
|
-
sourcePath: surface.sourcePath,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
for (const toolName of surface.tools) {
|
|
115
|
-
addOwner(toolOwners, toolName, surface.id);
|
|
116
|
-
}
|
|
117
|
-
for (const skillPath of surface.skills) {
|
|
118
|
-
addOwner(skillOwners, skillPath, surface.id);
|
|
119
|
-
skillSurfaceOwners.set(skillPath, [...(skillSurfaceOwners.get(skillPath) ?? []), surface]);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
for (const agent of workspace.agents.values()) {
|
|
123
|
-
for (const subagentRef of agent.subagentRefs) {
|
|
124
|
-
const subagentId = stripAgentRefPrefix(subagentRef);
|
|
125
|
-
if (!workspace.agents.has(subagentId)) {
|
|
126
|
-
addFinding(findings, options, {
|
|
127
|
-
severity: "error",
|
|
128
|
-
code: "missing-subagent-reference",
|
|
129
|
-
message: `Agent '${agent.id}' references missing subagent '${subagentRef}'.`,
|
|
130
|
-
agentId: agent.id,
|
|
131
|
-
sourcePath: agent.sourcePath,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
for (const [toolName, owners] of toolOwners) {
|
|
137
|
-
const uniqueOwners = uniqueSorted(owners);
|
|
138
|
-
if (uniqueOwners.length > 1) {
|
|
139
|
-
addFinding(findings, options, {
|
|
140
|
-
severity: "info",
|
|
141
|
-
code: "shared-tool-surface",
|
|
142
|
-
message: `Tool '${toolName}' is exposed by multiple agents; review whether the shared surface is intentional.`,
|
|
143
|
-
toolName,
|
|
144
|
-
relatedAgents: uniqueOwners,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
for (const [skillPath, owners] of skillOwners) {
|
|
149
|
-
const uniqueOwners = uniqueSorted(owners);
|
|
150
|
-
if (uniqueOwners.length > 1) {
|
|
151
|
-
const metadata = readSkillMetadata(skillPath);
|
|
152
|
-
addFinding(findings, options, {
|
|
153
|
-
severity: "info",
|
|
154
|
-
code: "shared-skill-surface",
|
|
155
|
-
message: `Skill '${metadata.name}' is exposed by multiple agents; review whether the shared surface is intentional.`,
|
|
156
|
-
skillName: metadata.name,
|
|
157
|
-
skillPath,
|
|
158
|
-
relatedAgents: uniqueOwners,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
for (const tool of workspace.tools.values()) {
|
|
163
|
-
if (!toolOwners.has(tool.name)) {
|
|
164
|
-
addFinding(findings, options, {
|
|
165
|
-
severity: "warning",
|
|
166
|
-
code: "unreferenced-tool",
|
|
167
|
-
message: `Tool '${tool.name}' is defined but not exposed by any agent or subagent.`,
|
|
168
|
-
toolName: tool.name,
|
|
169
|
-
sourcePath: tool.sourcePath,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
const discoveredSkillPaths = discoverWorkspaceSkillPaths(workspace.workspaceRoot, referencedSkillPaths);
|
|
174
|
-
for (const skillPath of discoveredSkillPaths) {
|
|
175
|
-
const metadata = readSkillMetadata(skillPath);
|
|
176
|
-
const owners = skillOwners.get(skillPath) ?? [];
|
|
177
|
-
if (owners.length === 0) {
|
|
178
|
-
addFinding(findings, options, {
|
|
179
|
-
severity: "warning",
|
|
180
|
-
code: "unreferenced-skill",
|
|
181
|
-
message: `Skill '${metadata.name}' is present but not exposed by any agent or subagent.`,
|
|
182
|
-
skillName: metadata.name,
|
|
183
|
-
skillPath,
|
|
184
|
-
});
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
if (!metadata.allowedTools || metadata.allowedTools.length === 0) {
|
|
188
|
-
addFinding(findings, options, {
|
|
189
|
-
severity: "warning",
|
|
190
|
-
code: "skill-missing-allowed-tools",
|
|
191
|
-
message: `Skill '${metadata.name}' does not declare allowed-tools.`,
|
|
192
|
-
skillName: metadata.name,
|
|
193
|
-
skillPath,
|
|
194
|
-
relatedAgents: uniqueSorted(owners),
|
|
195
|
-
});
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
for (const surface of skillSurfaceOwners.get(skillPath) ?? []) {
|
|
199
|
-
const owner = surface.parentAgentId ? `${surface.parentAgentId}/${surface.id}` : surface.id;
|
|
200
|
-
const availableTools = new Set([...surface.tools, ...BUILTIN_SKILL_TOOLS]);
|
|
201
|
-
for (const allowedTool of metadata.allowedTools) {
|
|
202
|
-
if (!availableTools.has(allowedTool)) {
|
|
203
|
-
addFinding(findings, options, {
|
|
204
|
-
severity: "error",
|
|
205
|
-
code: "skill-allowed-tool-unavailable",
|
|
206
|
-
message: `Skill '${metadata.name}' allows tool '${allowedTool}', but agent '${owner}' does not expose it.`,
|
|
207
|
-
agentId: surface.id,
|
|
208
|
-
parentAgentId: surface.parentAgentId,
|
|
209
|
-
toolName: allowedTool,
|
|
210
|
-
skillName: metadata.name,
|
|
211
|
-
skillPath,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
const errorCount = findings.filter((finding) => finding.severity === "error").length;
|
|
218
|
-
const warningCount = findings.filter((finding) => finding.severity === "warning").length;
|
|
219
|
-
const infoCount = findings.filter((finding) => finding.severity === "info").length;
|
|
220
|
-
return {
|
|
221
|
-
workspaceRoot: workspace.workspaceRoot,
|
|
222
|
-
summary: {
|
|
223
|
-
agentCount: agentSurfaces.length,
|
|
224
|
-
toolCount: workspace.tools.size,
|
|
225
|
-
skillCount: discoveredSkillPaths.length,
|
|
226
|
-
findingCount: findings.length,
|
|
227
|
-
errorCount,
|
|
228
|
-
warningCount,
|
|
229
|
-
infoCount,
|
|
230
|
-
},
|
|
231
|
-
agents: agentSurfaces,
|
|
232
|
-
findings,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
1
|
+
import{existsSync as w,readdirSync as $,statSync as x}from"node:fs";import k from"node:path";import{readSkillMetadata as y}from"../../skills/skill-metadata.js";import{getBindingPrimaryTools as T,getBindingSkills as N,getBindingSubagents as S}from"../../support/compiled-binding.js";const L=new Set(["read_todos","write_todos"]);function l(t){return Array.from(new Set(t.filter(s=>s.trim().length>0))).sort()}function c(t,s,n){n.severity==="info"&&s.includeInfo===!1||t.push(n)}function O(t,s){const n=new Set(s),i=k.join(t,"resources","skills");if(!w(i))return l(Array.from(n));for(const g of $(i)){const d=k.join(i,g);try{x(d).isDirectory()&&w(k.join(d,"SKILL.md"))&&n.add(d)}catch{continue}}return l(Array.from(n))}function C(t){return{id:t.agent.id,description:t.agent.description,tools:l(T(t).map(s=>s.name)),skills:l(N(t)),subagents:l(S(t).map(s=>s.name)),sourcePath:t.agent.sourcePath}}function R(t,s){return{id:t.name,description:t.description,tools:l((t.tools??[]).map(n=>n.name)),skills:l(t.skills??[]),subagents:[],parentAgentId:s}}function P(t,s,n){const i=t.get(s)??[];i.push(n),t.set(s,i)}function B(t){const s="agent/";return t.startsWith(s)?t.slice(s.length):t}function W(t,s={}){const n=[],i=[],g=[],d=new Map,f=new Map,u=new Map;for(const e of t.bindings.values()){const o=C(e);i.push(o);for(const r of o.skills)g.push(r);for(const r of S(e)){const a=R(r,e.agent.id);i.push(a);for(const h of a.skills)g.push(h)}}for(const e of i){e.description.trim().length===0&&c(n,s,{severity:"warning",code:"agent-missing-description",message:`Agent '${e.id}' has no description, making routing boundaries ambiguous.`,agentId:e.id,parentAgentId:e.parentAgentId,sourcePath:e.sourcePath}),e.subagents.length>0&&(e.tools.length>0||e.skills.length>0)&&c(n,s,{severity:"warning",code:"mixed-routing-and-execution-surface",message:`Agent '${e.id}' declares subagents and also exposes local tools or skills.`,agentId:e.id,sourcePath:e.sourcePath}),e.subagents.length===0&&e.tools.length===0&&e.skills.length===0&&c(n,s,{severity:"info",code:"empty-execution-surface",message:`Agent '${e.id}' has no tools, skills, or subagents declared.`,agentId:e.id,parentAgentId:e.parentAgentId,sourcePath:e.sourcePath});for(const o of e.tools)P(d,o,e.id);for(const o of e.skills)P(f,o,e.id),u.set(o,[...u.get(o)??[],e])}for(const e of t.agents.values())for(const o of e.subagentRefs){const r=B(o);t.agents.has(r)||c(n,s,{severity:"error",code:"missing-subagent-reference",message:`Agent '${e.id}' references missing subagent '${o}'.`,agentId:e.id,sourcePath:e.sourcePath})}for(const[e,o]of d){const r=l(o);r.length>1&&c(n,s,{severity:"info",code:"shared-tool-surface",message:`Tool '${e}' is exposed by multiple agents; review whether the shared surface is intentional.`,toolName:e,relatedAgents:r})}for(const[e,o]of f){const r=l(o);if(r.length>1){const a=y(e);c(n,s,{severity:"info",code:"shared-skill-surface",message:`Skill '${a.name}' is exposed by multiple agents; review whether the shared surface is intentional.`,skillName:a.name,skillPath:e,relatedAgents:r})}}for(const e of t.tools.values())d.has(e.name)||c(n,s,{severity:"warning",code:"unreferenced-tool",message:`Tool '${e.name}' is defined but not exposed by any agent or subagent.`,toolName:e.name,sourcePath:e.sourcePath});const p=O(t.workspaceRoot,g);for(const e of p){const o=y(e),r=f.get(e)??[];if(r.length===0){c(n,s,{severity:"warning",code:"unreferenced-skill",message:`Skill '${o.name}' is present but not exposed by any agent or subagent.`,skillName:o.name,skillPath:e});continue}if(!o.allowedTools||o.allowedTools.length===0){c(n,s,{severity:"warning",code:"skill-missing-allowed-tools",message:`Skill '${o.name}' does not declare allowed-tools.`,skillName:o.name,skillPath:e,relatedAgents:l(r)});continue}for(const a of u.get(e)??[]){const h=a.parentAgentId?`${a.parentAgentId}/${a.id}`:a.id,I=new Set([...a.tools,...L]);for(const m of o.allowedTools)I.has(m)||c(n,s,{severity:"error",code:"skill-allowed-tool-unavailable",message:`Skill '${o.name}' allows tool '${m}', but agent '${h}' does not expose it.`,agentId:a.id,parentAgentId:a.parentAgentId,toolName:m,skillName:o.name,skillPath:e})}}const b=n.filter(e=>e.severity==="error").length,v=n.filter(e=>e.severity==="warning").length,A=n.filter(e=>e.severity==="info").length;return{workspaceRoot:t.workspaceRoot,summary:{agentCount:i.length,toolCount:t.tools.size,skillCount:p.length,findingCount:n.length,errorCount:b,warningCount:v,infoCount:A},agents:i,findings:n}}export{W as analyzeWorkspaceBoundaries};
|
|
@@ -1,258 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
const DEFAULT_HEALTH_CONFIG = {
|
|
3
|
-
enabled: false,
|
|
4
|
-
evaluateIntervalSeconds: 30,
|
|
5
|
-
emitEvents: true,
|
|
6
|
-
thresholds: {
|
|
7
|
-
llmErrorRate: {
|
|
8
|
-
windowSeconds: 60,
|
|
9
|
-
degradedAbove: 0.15,
|
|
10
|
-
unhealthyAbove: 0.4,
|
|
11
|
-
},
|
|
12
|
-
llmP95LatencyMs: {
|
|
13
|
-
windowSeconds: 300,
|
|
14
|
-
degradedAbove: 8_000,
|
|
15
|
-
unhealthyAbove: 20_000,
|
|
16
|
-
},
|
|
17
|
-
pendingApprovals: {
|
|
18
|
-
degradedAbove: 20,
|
|
19
|
-
unhealthyAbove: 100,
|
|
20
|
-
},
|
|
21
|
-
stuckRunSeconds: {
|
|
22
|
-
degradedAbove: 300,
|
|
23
|
-
unhealthyAbove: 900,
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
const ACTIVE_REQUEST_STATES = [
|
|
28
|
-
"queued",
|
|
29
|
-
"claimed",
|
|
30
|
-
"running",
|
|
31
|
-
"waiting_for_approval",
|
|
32
|
-
"resuming",
|
|
33
|
-
"cancelling",
|
|
34
|
-
];
|
|
35
|
-
function asObject(value) {
|
|
36
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
37
|
-
}
|
|
38
|
-
function readPositiveNumber(value, fallback) {
|
|
39
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
40
|
-
}
|
|
41
|
-
function readNonNegativeNumber(value, fallback) {
|
|
42
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
43
|
-
}
|
|
44
|
-
function maxStatus(left, right) {
|
|
45
|
-
const rank = { healthy: 0, degraded: 1, unhealthy: 2 };
|
|
46
|
-
return rank[left] >= rank[right] ? left : right;
|
|
47
|
-
}
|
|
48
|
-
function buildCheck(status, updatedAt, reason) {
|
|
49
|
-
return reason ? { status, updatedAt, reason } : { status, updatedAt };
|
|
50
|
-
}
|
|
51
|
-
function createSymptom(code, status, message, timestamp) {
|
|
52
|
-
return {
|
|
53
|
-
code,
|
|
54
|
-
severity: status === "unhealthy" ? "error" : status === "degraded" ? "warn" : "info",
|
|
55
|
-
message,
|
|
56
|
-
firstSeenAt: timestamp,
|
|
57
|
-
lastSeenAt: timestamp,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function describeThresholdStatus(value, thresholds) {
|
|
61
|
-
if (value >= thresholds.unhealthyAbove) {
|
|
62
|
-
return "unhealthy";
|
|
63
|
-
}
|
|
64
|
-
if (value >= thresholds.degradedAbove) {
|
|
65
|
-
return "degraded";
|
|
66
|
-
}
|
|
67
|
-
return "healthy";
|
|
68
|
-
}
|
|
69
|
-
function computeP95(values) {
|
|
70
|
-
if (values.length === 0) {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
74
|
-
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * 0.95) - 1));
|
|
75
|
-
return sorted[index];
|
|
76
|
-
}
|
|
77
|
-
export function readHealthMonitorConfig(workspace) {
|
|
78
|
-
const runtimeDefaults = getRuntimeDefaults(workspace.refs);
|
|
79
|
-
const observability = asObject(runtimeDefaults?.observability);
|
|
80
|
-
const health = asObject(observability?.health);
|
|
81
|
-
const thresholds = asObject(health?.thresholds);
|
|
82
|
-
const llmErrorRate = asObject(thresholds?.llmErrorRate);
|
|
83
|
-
const llmP95LatencyMs = asObject(thresholds?.llmP95LatencyMs);
|
|
84
|
-
const pendingApprovals = asObject(thresholds?.pendingApprovals);
|
|
85
|
-
const stuckRunSeconds = asObject(thresholds?.stuckRunSeconds);
|
|
86
|
-
return {
|
|
87
|
-
enabled: health?.enabled === true,
|
|
88
|
-
evaluateIntervalSeconds: readPositiveNumber(health?.evaluateIntervalSeconds, DEFAULT_HEALTH_CONFIG.evaluateIntervalSeconds),
|
|
89
|
-
emitEvents: health?.emitEvents !== false,
|
|
90
|
-
thresholds: {
|
|
91
|
-
llmErrorRate: {
|
|
92
|
-
windowSeconds: readPositiveNumber(llmErrorRate?.windowSeconds, DEFAULT_HEALTH_CONFIG.thresholds.llmErrorRate.windowSeconds),
|
|
93
|
-
degradedAbove: readNonNegativeNumber(llmErrorRate?.degradedAbove, DEFAULT_HEALTH_CONFIG.thresholds.llmErrorRate.degradedAbove),
|
|
94
|
-
unhealthyAbove: readNonNegativeNumber(llmErrorRate?.unhealthyAbove, DEFAULT_HEALTH_CONFIG.thresholds.llmErrorRate.unhealthyAbove),
|
|
95
|
-
},
|
|
96
|
-
llmP95LatencyMs: {
|
|
97
|
-
windowSeconds: readPositiveNumber(llmP95LatencyMs?.windowSeconds, DEFAULT_HEALTH_CONFIG.thresholds.llmP95LatencyMs.windowSeconds),
|
|
98
|
-
degradedAbove: readNonNegativeNumber(llmP95LatencyMs?.degradedAbove, DEFAULT_HEALTH_CONFIG.thresholds.llmP95LatencyMs.degradedAbove),
|
|
99
|
-
unhealthyAbove: readNonNegativeNumber(llmP95LatencyMs?.unhealthyAbove, DEFAULT_HEALTH_CONFIG.thresholds.llmP95LatencyMs.unhealthyAbove),
|
|
100
|
-
},
|
|
101
|
-
pendingApprovals: {
|
|
102
|
-
degradedAbove: readNonNegativeNumber(pendingApprovals?.degradedAbove, DEFAULT_HEALTH_CONFIG.thresholds.pendingApprovals.degradedAbove),
|
|
103
|
-
unhealthyAbove: readNonNegativeNumber(pendingApprovals?.unhealthyAbove, DEFAULT_HEALTH_CONFIG.thresholds.pendingApprovals.unhealthyAbove),
|
|
104
|
-
},
|
|
105
|
-
stuckRunSeconds: {
|
|
106
|
-
degradedAbove: readNonNegativeNumber(stuckRunSeconds?.degradedAbove, DEFAULT_HEALTH_CONFIG.thresholds.stuckRunSeconds.degradedAbove),
|
|
107
|
-
unhealthyAbove: readNonNegativeNumber(stuckRunSeconds?.unhealthyAbove, DEFAULT_HEALTH_CONFIG.thresholds.stuckRunSeconds.unhealthyAbove),
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
export class HealthMonitor {
|
|
113
|
-
options;
|
|
114
|
-
config;
|
|
115
|
-
llmSamples = [];
|
|
116
|
-
timer = null;
|
|
117
|
-
latestSnapshot = null;
|
|
118
|
-
constructor(options) {
|
|
119
|
-
this.options = options;
|
|
120
|
-
this.config = readHealthMonitorConfig(options.workspace);
|
|
121
|
-
}
|
|
122
|
-
recordLlmSuccess(latencyMs, nowMs = Date.now()) {
|
|
123
|
-
this.recordLlmSample({ timestampMs: nowMs, latencyMs, success: true });
|
|
124
|
-
}
|
|
125
|
-
recordLlmFailure(latencyMs, nowMs = Date.now()) {
|
|
126
|
-
this.recordLlmSample({ timestampMs: nowMs, latencyMs, success: false });
|
|
127
|
-
}
|
|
128
|
-
recordLlmSample(sample) {
|
|
129
|
-
this.llmSamples.push(sample);
|
|
130
|
-
const keepAfter = sample.timestampMs - Math.max(this.config.thresholds.llmErrorRate.windowSeconds, this.config.thresholds.llmP95LatencyMs.windowSeconds) * 1000;
|
|
131
|
-
while (this.llmSamples.length > 0 && this.llmSamples[0].timestampMs < keepAfter) {
|
|
132
|
-
this.llmSamples.shift();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async start() {
|
|
136
|
-
if (!this.config.enabled || this.timer) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
await this.evaluate();
|
|
140
|
-
this.timer = setInterval(() => {
|
|
141
|
-
void this.evaluate();
|
|
142
|
-
}, this.config.evaluateIntervalSeconds * 1000);
|
|
143
|
-
this.timer.unref?.();
|
|
144
|
-
}
|
|
145
|
-
async stop() {
|
|
146
|
-
if (this.timer) {
|
|
147
|
-
clearInterval(this.timer);
|
|
148
|
-
this.timer = null;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
async getSnapshot() {
|
|
152
|
-
return this.evaluate();
|
|
153
|
-
}
|
|
154
|
-
async evaluate(nowMs = Date.now()) {
|
|
155
|
-
const updatedAt = new Date(nowMs).toISOString();
|
|
156
|
-
const [requests, approvals] = await Promise.all([
|
|
157
|
-
Promise.all(ACTIVE_REQUEST_STATES.map((state) => this.options.persistence.listRequests({ state }))).then((groups) => groups.flat()),
|
|
158
|
-
this.options.persistence.listApprovals({ status: "pending" }),
|
|
159
|
-
]);
|
|
160
|
-
const pendingApprovals = approvals.length;
|
|
161
|
-
const stuckRequests = this.countStuckRequests(requests, nowMs);
|
|
162
|
-
const llmCheck = this.evaluateLlmCheck(updatedAt, nowMs);
|
|
163
|
-
const workloadCheck = this.evaluateWorkloadCheck(updatedAt, pendingApprovals, stuckRequests);
|
|
164
|
-
const runtimeCheck = buildCheck("healthy", updatedAt, "runtime health monitoring enabled");
|
|
165
|
-
const persistenceCheck = buildCheck("healthy", updatedAt, "runtime persistence accessible");
|
|
166
|
-
const capacityCheck = buildCheck("healthy", updatedAt, "capacity checks disabled");
|
|
167
|
-
const symptoms = [];
|
|
168
|
-
if (describeThresholdStatus(pendingApprovals, this.config.thresholds.pendingApprovals) !== "healthy") {
|
|
169
|
-
symptoms.push(createSymptom("workload.pending-approvals", describeThresholdStatus(pendingApprovals, this.config.thresholds.pendingApprovals), `pendingApprovals=${pendingApprovals}`, updatedAt));
|
|
170
|
-
}
|
|
171
|
-
if (stuckRequests > 0) {
|
|
172
|
-
symptoms.push(createSymptom("workload.stuck-requests", workloadCheck.status, `stuckRequests=${stuckRequests}`, updatedAt));
|
|
173
|
-
}
|
|
174
|
-
if (llmCheck.status !== "healthy") {
|
|
175
|
-
symptoms.push(createSymptom("llm.invocations", llmCheck.status, llmCheck.reason ?? "llm health degraded", updatedAt));
|
|
176
|
-
}
|
|
177
|
-
let overallStatus = "healthy";
|
|
178
|
-
for (const check of [runtimeCheck, llmCheck, persistenceCheck, capacityCheck, workloadCheck]) {
|
|
179
|
-
overallStatus = maxStatus(overallStatus, check.status);
|
|
180
|
-
}
|
|
181
|
-
const snapshot = {
|
|
182
|
-
status: overallStatus,
|
|
183
|
-
updatedAt,
|
|
184
|
-
checks: {
|
|
185
|
-
runtime: runtimeCheck,
|
|
186
|
-
llm: llmCheck,
|
|
187
|
-
persistence: persistenceCheck,
|
|
188
|
-
capacity: capacityCheck,
|
|
189
|
-
workload: workloadCheck,
|
|
190
|
-
},
|
|
191
|
-
symptoms,
|
|
192
|
-
stats: {
|
|
193
|
-
activeRequestSlots: this.options.getActiveRequestSlots(),
|
|
194
|
-
pendingRequestSlots: this.options.getPendingRequestSlots(),
|
|
195
|
-
pendingApprovals,
|
|
196
|
-
stuckRequests,
|
|
197
|
-
...this.llmStats(nowMs),
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
if (this.config.enabled &&
|
|
201
|
-
this.config.emitEvents &&
|
|
202
|
-
this.options.publishEvent &&
|
|
203
|
-
this.latestSnapshot &&
|
|
204
|
-
this.latestSnapshot.status !== snapshot.status) {
|
|
205
|
-
await this.options.publishEvent({
|
|
206
|
-
previousStatus: this.latestSnapshot.status,
|
|
207
|
-
status: snapshot.status,
|
|
208
|
-
checks: snapshot.checks,
|
|
209
|
-
stats: snapshot.stats,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
this.latestSnapshot = snapshot;
|
|
213
|
-
return snapshot;
|
|
214
|
-
}
|
|
215
|
-
llmStats(nowMs) {
|
|
216
|
-
const recent = this.llmSamples.filter((sample) => nowMs - sample.timestampMs <= 60 * 1000);
|
|
217
|
-
const successes = recent.filter((sample) => sample.success).length;
|
|
218
|
-
return {
|
|
219
|
-
llmSuccessRate1m: recent.length > 0 ? successes / recent.length : undefined,
|
|
220
|
-
llmP95LatencyMs1m: computeP95(recent.map((sample) => sample.latencyMs)),
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
evaluateLlmCheck(updatedAt, nowMs) {
|
|
224
|
-
const errorSamples = this.llmSamples.filter((sample) => nowMs - sample.timestampMs <= this.config.thresholds.llmErrorRate.windowSeconds * 1000);
|
|
225
|
-
const latencySamples = this.llmSamples.filter((sample) => nowMs - sample.timestampMs <= this.config.thresholds.llmP95LatencyMs.windowSeconds * 1000);
|
|
226
|
-
if (errorSamples.length === 0 && latencySamples.length === 0) {
|
|
227
|
-
return buildCheck("healthy", updatedAt, "no recent llm invocations");
|
|
228
|
-
}
|
|
229
|
-
const errorRate = errorSamples.length > 0
|
|
230
|
-
? errorSamples.filter((sample) => !sample.success).length / errorSamples.length
|
|
231
|
-
: 0;
|
|
232
|
-
const p95Latency = computeP95(latencySamples.map((sample) => sample.latencyMs));
|
|
233
|
-
let status = describeThresholdStatus(errorRate, this.config.thresholds.llmErrorRate);
|
|
234
|
-
if (p95Latency !== undefined) {
|
|
235
|
-
status = maxStatus(status, describeThresholdStatus(p95Latency, this.config.thresholds.llmP95LatencyMs));
|
|
236
|
-
}
|
|
237
|
-
return buildCheck(status, updatedAt, `errorRate=${errorRate.toFixed(2)}${p95Latency !== undefined ? ` p95LatencyMs=${Math.round(p95Latency)}` : ""}`);
|
|
238
|
-
}
|
|
239
|
-
evaluateWorkloadCheck(updatedAt, pendingApprovals, stuckRequests) {
|
|
240
|
-
let status = describeThresholdStatus(pendingApprovals, this.config.thresholds.pendingApprovals);
|
|
241
|
-
if (stuckRequests > 0) {
|
|
242
|
-
status = maxStatus(status, stuckRequests >= this.config.thresholds.stuckRunSeconds.unhealthyAbove ? "unhealthy" : "degraded");
|
|
243
|
-
}
|
|
244
|
-
return buildCheck(status, updatedAt, `pendingApprovals=${pendingApprovals} stuckRequests=${stuckRequests}`);
|
|
245
|
-
}
|
|
246
|
-
countStuckRequests(requests, nowMs) {
|
|
247
|
-
return requests.filter((request) => {
|
|
248
|
-
if (!["claimed", "running", "resuming", "queued", "cancelling"].includes(request.state)) {
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
const updatedAtMs = Date.parse(request.updatedAt);
|
|
252
|
-
if (!Number.isFinite(updatedAtMs)) {
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
return (nowMs - updatedAtMs) / 1000 >= this.config.thresholds.stuckRunSeconds.degradedAbove;
|
|
256
|
-
}).length;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
1
|
+
import{getRuntimeDefaults as M}from"../../../workspace/support/workspace-ref-utils.js";const h={enabled:!1,evaluateIntervalSeconds:30,emitEvents:!0,thresholds:{llmErrorRate:{windowSeconds:60,degradedAbove:.15,unhealthyAbove:.4},llmP95LatencyMs:{windowSeconds:300,degradedAbove:8e3,unhealthyAbove:2e4},pendingApprovals:{degradedAbove:20,unhealthyAbove:100},stuckRunSeconds:{degradedAbove:300,unhealthyAbove:900}}},E=["queued","claimed","running","waiting_for_approval","resuming","cancelling"];function d(s){return typeof s=="object"&&s!==null&&!Array.isArray(s)?s:void 0}function S(s,e){return typeof s=="number"&&Number.isFinite(s)&&s>0?s:e}function c(s,e){return typeof s=="number"&&Number.isFinite(s)&&s>=0?s:e}function y(s,e){const t={healthy:0,degraded:1,unhealthy:2};return t[s]>=t[e]?s:e}function m(s,e,t){return t?{status:s,updatedAt:e,reason:t}:{status:s,updatedAt:e}}function b(s,e,t,n){return{code:s,severity:e==="unhealthy"?"error":e==="degraded"?"warn":"info",message:t,firstSeenAt:n,lastSeenAt:n}}function g(s,e){return s>=e.unhealthyAbove?"unhealthy":s>=e.degradedAbove?"degraded":"healthy"}function R(s){if(s.length===0)return;const e=[...s].sort((n,o)=>n-o),t=Math.min(e.length-1,Math.max(0,Math.ceil(e.length*.95)-1));return e[t]}function L(s){const e=M(s.refs),t=d(e?.observability),n=d(t?.health),o=d(n?.thresholds),r=d(o?.llmErrorRate),a=d(o?.llmP95LatencyMs),i=d(o?.pendingApprovals),l=d(o?.stuckRunSeconds);return{enabled:n?.enabled===!0,evaluateIntervalSeconds:S(n?.evaluateIntervalSeconds,h.evaluateIntervalSeconds),emitEvents:n?.emitEvents!==!1,thresholds:{llmErrorRate:{windowSeconds:S(r?.windowSeconds,h.thresholds.llmErrorRate.windowSeconds),degradedAbove:c(r?.degradedAbove,h.thresholds.llmErrorRate.degradedAbove),unhealthyAbove:c(r?.unhealthyAbove,h.thresholds.llmErrorRate.unhealthyAbove)},llmP95LatencyMs:{windowSeconds:S(a?.windowSeconds,h.thresholds.llmP95LatencyMs.windowSeconds),degradedAbove:c(a?.degradedAbove,h.thresholds.llmP95LatencyMs.degradedAbove),unhealthyAbove:c(a?.unhealthyAbove,h.thresholds.llmP95LatencyMs.unhealthyAbove)},pendingApprovals:{degradedAbove:c(i?.degradedAbove,h.thresholds.pendingApprovals.degradedAbove),unhealthyAbove:c(i?.unhealthyAbove,h.thresholds.pendingApprovals.unhealthyAbove)},stuckRunSeconds:{degradedAbove:c(l?.degradedAbove,h.thresholds.stuckRunSeconds.degradedAbove),unhealthyAbove:c(l?.unhealthyAbove,h.thresholds.stuckRunSeconds.unhealthyAbove)}}}}class C{options;config;llmSamples=[];timer=null;latestSnapshot=null;constructor(e){this.options=e,this.config=L(e.workspace)}recordLlmSuccess(e,t=Date.now()){this.recordLlmSample({timestampMs:t,latencyMs:e,success:!0})}recordLlmFailure(e,t=Date.now()){this.recordLlmSample({timestampMs:t,latencyMs:e,success:!1})}recordLlmSample(e){this.llmSamples.push(e);const t=e.timestampMs-Math.max(this.config.thresholds.llmErrorRate.windowSeconds,this.config.thresholds.llmP95LatencyMs.windowSeconds)*1e3;for(;this.llmSamples.length>0&&this.llmSamples[0].timestampMs<t;)this.llmSamples.shift()}async start(){!this.config.enabled||this.timer||(await this.evaluate(),this.timer=setInterval(()=>{this.evaluate()},this.config.evaluateIntervalSeconds*1e3),this.timer.unref?.())}async stop(){this.timer&&(clearInterval(this.timer),this.timer=null)}async getSnapshot(){return this.evaluate()}async evaluate(e=Date.now()){const t=new Date(e).toISOString(),[n,o]=await Promise.all([Promise.all(E.map(p=>this.options.persistence.listRequests({state:p}))).then(p=>p.flat()),this.options.persistence.listApprovals({status:"pending"})]),r=o.length,a=this.countStuckRequests(n,e),i=this.evaluateLlmCheck(t,e),l=this.evaluateWorkloadCheck(t,r,a),A=m("healthy",t,"runtime health monitoring enabled"),k=m("healthy",t,"runtime persistence accessible"),w=m("healthy",t,"capacity checks disabled"),v=[];g(r,this.config.thresholds.pendingApprovals)!=="healthy"&&v.push(b("workload.pending-approvals",g(r,this.config.thresholds.pendingApprovals),`pendingApprovals=${r}`,t)),a>0&&v.push(b("workload.stuck-requests",l.status,`stuckRequests=${a}`,t)),i.status!=="healthy"&&v.push(b("llm.invocations",i.status,i.reason??"llm health degraded",t));let f="healthy";for(const p of[A,i,k,w,l])f=y(f,p.status);const u={status:f,updatedAt:t,checks:{runtime:A,llm:i,persistence:k,capacity:w,workload:l},symptoms:v,stats:{activeRequestSlots:this.options.getActiveRequestSlots(),pendingRequestSlots:this.options.getPendingRequestSlots(),pendingApprovals:r,stuckRequests:a,...this.llmStats(e)}};return this.config.enabled&&this.config.emitEvents&&this.options.publishEvent&&this.latestSnapshot&&this.latestSnapshot.status!==u.status&&await this.options.publishEvent({previousStatus:this.latestSnapshot.status,status:u.status,checks:u.checks,stats:u.stats}),this.latestSnapshot=u,u}llmStats(e){const t=this.llmSamples.filter(o=>e-o.timestampMs<=6e4),n=t.filter(o=>o.success).length;return{llmSuccessRate1m:t.length>0?n/t.length:void 0,llmP95LatencyMs1m:R(t.map(o=>o.latencyMs))}}evaluateLlmCheck(e,t){const n=this.llmSamples.filter(l=>t-l.timestampMs<=this.config.thresholds.llmErrorRate.windowSeconds*1e3),o=this.llmSamples.filter(l=>t-l.timestampMs<=this.config.thresholds.llmP95LatencyMs.windowSeconds*1e3);if(n.length===0&&o.length===0)return m("healthy",e,"no recent llm invocations");const r=n.length>0?n.filter(l=>!l.success).length/n.length:0,a=R(o.map(l=>l.latencyMs));let i=g(r,this.config.thresholds.llmErrorRate);return a!==void 0&&(i=y(i,g(a,this.config.thresholds.llmP95LatencyMs))),m(i,e,`errorRate=${r.toFixed(2)}${a!==void 0?` p95LatencyMs=${Math.round(a)}`:""}`)}evaluateWorkloadCheck(e,t,n){let o=g(t,this.config.thresholds.pendingApprovals);return n>0&&(o=y(o,n>=this.config.thresholds.stuckRunSeconds.unhealthyAbove?"unhealthy":"degraded")),m(o,e,`pendingApprovals=${t} stuckRequests=${n}`)}countStuckRequests(e,t){return e.filter(n=>{if(!["claimed","running","resuming","queued","cancelling"].includes(n.state))return!1;const o=Date.parse(n.updatedAt);return Number.isFinite(o)?(t-o)/1e3>=this.config.thresholds.stuckRunSeconds.degradedAbove:!1}).length}}export{C as HealthMonitor,L as readHealthMonitorConfig};
|