@senad-d/observme 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.env.example +4 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +36 -11
  4. package/dashboards/observme-agent-node-graphs.json +5 -5
  5. package/dashboards/observme-agents.json +169 -4
  6. package/dashboards/observme-alerts.yaml +16 -3
  7. package/dashboards/observme-overview.json +6 -6
  8. package/dashboards/observme-trace-journey.json +4 -4
  9. package/docs/agent-subagent-observability-requirements.md +19 -10
  10. package/docs/compatibility-matrix.md +21 -4
  11. package/docs/configuration.md +7 -2
  12. package/docs/extension-integration.md +20 -13
  13. package/docs/reference/03-pi-event-and-session-model.md +6 -6
  14. package/docs/reference/04-telemetry-semantic-conventions.md +39 -1
  15. package/docs/reference/05-otel-pipeline-and-collector.md +23 -10
  16. package/docs/reference/06-security-privacy-redaction.md +13 -6
  17. package/docs/reference/08-query-grafana-integration.md +30 -7
  18. package/docs/reference/09-dashboards-alerts-slos.md +132 -3
  19. package/docs/reference/10-testing-release-operations.md +44 -14
  20. package/docs/reference/11-deployment-runbooks.md +82 -5
  21. package/docs/reference/12-configuration-reference.md +48 -5
  22. package/docs/review-validation.md +17 -0
  23. package/examples/README.md +7 -2
  24. package/examples/collector.yaml +8 -8
  25. package/examples/integrations/subagent-runner.ts +8 -5
  26. package/examples/observme.yaml +3 -2
  27. package/package.json +14 -4
  28. package/src/commands/obs-agents.ts +17 -12
  29. package/src/commands/obs-backfill.ts +2 -2
  30. package/src/commands/obs-command-support.ts +2 -1
  31. package/src/commands/obs-cost.ts +15 -7
  32. package/src/commands/obs-health.ts +22 -2
  33. package/src/commands/obs-loki-summary.ts +4 -8
  34. package/src/commands/obs-session.ts +35 -28
  35. package/src/commands/obs-status.ts +56 -13
  36. package/src/commands/obs-tools.ts +19 -8
  37. package/src/commands/obs-trace.ts +9 -0
  38. package/src/commands/obs.ts +6 -1
  39. package/src/config/bootstrap-project-config.ts +50 -32
  40. package/src/config/defaults.ts +1 -2
  41. package/src/config/load-config.ts +270 -81
  42. package/src/config/project-paths.ts +318 -6
  43. package/src/config/query-limits.ts +10 -0
  44. package/src/config/schema.ts +18 -8
  45. package/src/config/transport-security.ts +107 -0
  46. package/src/config/validate.ts +88 -12
  47. package/src/extension.ts +2 -12
  48. package/src/integration.ts +6 -2
  49. package/src/otel/logs.ts +24 -13
  50. package/src/otel/metrics.ts +24 -13
  51. package/src/otel/otlp-endpoint.ts +41 -2
  52. package/src/otel/otlp-http-options.ts +11 -0
  53. package/src/otel/sdk.ts +155 -9
  54. package/src/otel/shutdown.ts +39 -11
  55. package/src/otel/traces.ts +34 -16
  56. package/src/pi/active-agent-lease.ts +101 -0
  57. package/src/pi/agent-tree-tracker.ts +14 -1
  58. package/src/pi/compatibility.ts +121 -0
  59. package/src/pi/event-handlers/agent-turn.ts +16 -9
  60. package/src/pi/event-handlers/lifecycle.ts +312 -106
  61. package/src/pi/event-handlers/llm.ts +17 -9
  62. package/src/pi/event-handlers/session-events.ts +53 -19
  63. package/src/pi/event-handlers/tool-bash.ts +21 -27
  64. package/src/pi/handler-internals.ts +16 -26
  65. package/src/pi/handler-runtime.ts +159 -54
  66. package/src/pi/handler-types.ts +40 -17
  67. package/src/pi/handlers.ts +12 -1
  68. package/src/pi/integration-api.ts +27 -15
  69. package/src/pi/session-correlation.ts +167 -0
  70. package/src/pi/subagent-spawn.ts +164 -31
  71. package/src/privacy/redact.ts +574 -68
  72. package/src/privacy/secret-patterns.ts +6 -1
  73. package/src/query/grafana-readiness.ts +18 -2
  74. package/src/query/grafana-transport.ts +150 -27
  75. package/src/query/grafana-url.ts +36 -0
  76. package/src/query/grafana.ts +4 -136
  77. package/src/query/loki.ts +2 -4
  78. package/src/query/prometheus.ts +8 -10
  79. package/src/query/tempo.ts +2 -4
  80. package/src/query/trace-link.ts +186 -0
  81. package/src/safety/display-bounds.ts +84 -0
  82. package/src/semconv/metrics.ts +7 -0
  83. package/src/semconv/values.ts +2 -0
@@ -0,0 +1,121 @@
1
+ import { VERSION as PI_VERSION } from "@earendil-works/pi-coding-agent";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
+
4
+ export const MINIMUM_SUPPORTED_PI_VERSION = "0.80.5";
5
+ export const RELEASE_TESTED_PI_VERSION = "0.80.6";
6
+ const FIRST_UNSUPPORTED_PI_VERSION = "0.81.0";
7
+ export const SUPPORTED_PI_VERSION_RANGE =
8
+ `>=${MINIMUM_SUPPORTED_PI_VERSION} <${FIRST_UNSUPPORTED_PI_VERSION}`;
9
+
10
+ const compatibilityErrorPrefix = "ObservMe/Pi API compatibility error";
11
+ const MAX_DETECTED_PI_VERSION_LENGTH = 128;
12
+ const piVersionCorePattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
13
+ const semanticVersionIdentifierPattern = /^[0-9A-Za-z-]+$/u;
14
+ const numericIdentifierPattern = /^\d+$/u;
15
+ const requiredPiMethods = ["on", "registerCommand", "appendEntry", "getThinkingLevel"] as const;
16
+
17
+ export function assertObservMePiCompatibility(
18
+ pi: unknown,
19
+ piVersion = PI_VERSION,
20
+ ): asserts pi is ExtensionAPI {
21
+ const missingMethods = findMissingPiMethods(pi);
22
+ const detectedVersion = normalizeDetectedVersion(piVersion);
23
+ const versionSupported = isSupportedPiVersion(detectedVersion);
24
+ if (versionSupported && missingMethods.length === 0) return;
25
+
26
+ const reasons: string[] = [];
27
+ if (!versionSupported) reasons.push(`detected Pi ${detectedVersion}`);
28
+ if (missingMethods.length > 0) reasons.push(`missing required API method(s): ${missingMethods.join(", ")}`);
29
+
30
+ throw new TypeError(
31
+ `${compatibilityErrorPrefix}: ObservMe requires @earendil-works/pi-coding-agent ${SUPPORTED_PI_VERSION_RANGE}; ${reasons.join("; ")}. No ObservMe event handlers or commands were registered.`,
32
+ );
33
+ }
34
+
35
+ function findMissingPiMethods(pi: unknown): string[] {
36
+ if (!isRecord(pi)) return [...requiredPiMethods];
37
+
38
+ const missing: string[] = [];
39
+ for (const method of requiredPiMethods) {
40
+ if (typeof pi[method] !== "function") missing.push(method);
41
+ }
42
+ return missing;
43
+ }
44
+
45
+ function isRecord(value: unknown): value is Record<string, unknown> {
46
+ return typeof value === "object" && value !== null;
47
+ }
48
+
49
+ function normalizeDetectedVersion(value: unknown): string {
50
+ if (typeof value !== "string") return "unknown";
51
+ const trimmed = value.trim();
52
+ if (trimmed.length === 0 || trimmed.length > MAX_DETECTED_PI_VERSION_LENGTH) return "unknown";
53
+ return parsePiVersion(trimmed) ? trimmed : "unknown";
54
+ }
55
+
56
+ function isSupportedPiVersion(version: string): boolean {
57
+ const detected = parsePiVersion(version);
58
+ const minimum = parsePiVersion(MINIMUM_SUPPORTED_PI_VERSION);
59
+ const maximum = parsePiVersion(FIRST_UNSUPPORTED_PI_VERSION);
60
+ if (!detected || detected.prerelease || !minimum || !maximum) return false;
61
+
62
+ return comparePiVersions(detected, minimum) >= 0 && comparePiVersions(detected, maximum) < 0;
63
+ }
64
+
65
+ interface PiVersion {
66
+ major: number;
67
+ minor: number;
68
+ patch: number;
69
+ prerelease?: string;
70
+ }
71
+
72
+ function parsePiVersion(version: string): PiVersion | undefined {
73
+ const buildSeparatorIndex = version.indexOf("+");
74
+ const versionAndPrerelease = buildSeparatorIndex === -1 ? version : version.slice(0, buildSeparatorIndex);
75
+ const build = buildSeparatorIndex === -1 ? undefined : version.slice(buildSeparatorIndex + 1);
76
+ if (build !== undefined && !hasValidSemanticVersionIdentifiers(build, true)) return undefined;
77
+
78
+ const prereleaseSeparatorIndex = versionAndPrerelease.indexOf("-");
79
+ const coreVersion = prereleaseSeparatorIndex === -1
80
+ ? versionAndPrerelease
81
+ : versionAndPrerelease.slice(0, prereleaseSeparatorIndex);
82
+ const prerelease = prereleaseSeparatorIndex === -1
83
+ ? undefined
84
+ : versionAndPrerelease.slice(prereleaseSeparatorIndex + 1);
85
+ if (prerelease !== undefined && !hasValidSemanticVersionIdentifiers(prerelease, false)) return undefined;
86
+
87
+ const match = piVersionCorePattern.exec(coreVersion);
88
+ if (!match) return undefined;
89
+
90
+ const major = Number(match[1]);
91
+ const minor = Number(match[2]);
92
+ const patch = Number(match[3]);
93
+ if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) || !Number.isSafeInteger(patch)) {
94
+ return undefined;
95
+ }
96
+
97
+ return {
98
+ major,
99
+ minor,
100
+ patch,
101
+ ...(prerelease === undefined ? {} : { prerelease }),
102
+ };
103
+ }
104
+
105
+ function hasValidSemanticVersionIdentifiers(value: string, allowLeadingZeros: boolean): boolean {
106
+ for (const identifier of value.split(".")) {
107
+ if (!semanticVersionIdentifierPattern.test(identifier)) return false;
108
+ if (!allowLeadingZeros && hasInvalidNumericIdentifier(identifier)) return false;
109
+ }
110
+ return true;
111
+ }
112
+
113
+ function hasInvalidNumericIdentifier(identifier: string): boolean {
114
+ return identifier.length > 1 && identifier.startsWith("0") && numericIdentifierPattern.test(identifier);
115
+ }
116
+
117
+ function comparePiVersions(left: PiVersion, right: PiVersion): number {
118
+ if (left.major !== right.major) return left.major - right.major;
119
+ if (left.minor !== right.minor) return left.minor - right.minor;
120
+ return left.patch - right.patch;
121
+ }
@@ -1,4 +1,11 @@
1
1
  import { SpanStatusCode } from "@opentelemetry/api";
2
+ import type {
3
+ AgentEndEvent,
4
+ AgentStartEvent,
5
+ ExtensionContext,
6
+ TurnEndEvent,
7
+ TurnStartEvent,
8
+ } from "@earendil-works/pi-coding-agent";
2
9
  import { recordObsSessionTurn } from "../../commands/obs-session.ts";
3
10
  import { AGENT_RUN_ATTRIBUTES, TURN_ATTRIBUTES } from "../../semconv/attributes.ts";
4
11
  import { LOG_EVENT_NAMES } from "../../semconv/metrics.ts";
@@ -21,7 +28,7 @@ import {
21
28
  } from "../handler-internals.ts";
22
29
  import { workflowFailed } from "../handler-runtime.ts";
23
30
  import type { HandlerRegistrar } from "../handler-runtime.ts";
24
- import type { Handler, HandlerSessionState, ObservMeHandlerContext } from "../handler-types.ts";
31
+ import type { HandlerSessionState, PiHandler } from "../handler-types.ts";
25
32
 
26
33
  export function registerAgentTurnHandlers(registrar: HandlerRegistrar, state: HandlerSessionState): void {
27
34
  registrar.add("agent_start", createAgentStartHandler(state));
@@ -30,11 +37,11 @@ export function registerAgentTurnHandlers(registrar: HandlerRegistrar, state: Ha
30
37
  registrar.add("agent_end", createAgentEndHandler(state));
31
38
  }
32
39
 
33
- function createAgentStartHandler(state: HandlerSessionState): Handler {
40
+ function createAgentStartHandler(state: HandlerSessionState): PiHandler<"agent_start"> {
34
41
  return handleAgentStart.bind(undefined, state);
35
42
  }
36
43
 
37
- function handleAgentStart(state: HandlerSessionState, event: unknown, _ctx: ObservMeHandlerContext): void {
44
+ function handleAgentStart(state: HandlerSessionState, event: AgentStartEvent, _ctx: ExtensionContext): void {
38
45
  const session = state.session;
39
46
  if (!session) return;
40
47
 
@@ -48,11 +55,11 @@ function handleAgentStart(state: HandlerSessionState, event: unknown, _ctx: Obse
48
55
  emitLifecycleLog(session.logger, LOG_EVENT_NAMES.AGENT_RUN_STARTED, attributes);
49
56
  }
50
57
 
51
- function createAgentEndHandler(state: HandlerSessionState): Handler {
58
+ function createAgentEndHandler(state: HandlerSessionState): PiHandler<"agent_end"> {
52
59
  return handleAgentEnd.bind(undefined, state);
53
60
  }
54
61
 
55
- function handleAgentEnd(state: HandlerSessionState, event: unknown, _ctx: ObservMeHandlerContext): void {
62
+ function handleAgentEnd(state: HandlerSessionState, event: AgentEndEvent, _ctx: ExtensionContext): void {
56
63
  const session = state.session;
57
64
  if (!session) return;
58
65
 
@@ -75,11 +82,11 @@ function handleAgentEnd(state: HandlerSessionState, event: unknown, _ctx: Observ
75
82
  });
76
83
  }
77
84
 
78
- function createTurnStartHandler(state: HandlerSessionState): Handler {
85
+ function createTurnStartHandler(state: HandlerSessionState): PiHandler<"turn_start"> {
79
86
  return handleTurnStart.bind(undefined, state);
80
87
  }
81
88
 
82
- function handleTurnStart(state: HandlerSessionState, event: unknown, _ctx: ObservMeHandlerContext): void {
89
+ function handleTurnStart(state: HandlerSessionState, event: TurnStartEvent, _ctx: ExtensionContext): void {
83
90
  const session = state.session;
84
91
  if (!session) return;
85
92
 
@@ -99,11 +106,11 @@ function handleTurnStart(state: HandlerSessionState, event: unknown, _ctx: Obser
99
106
  emitLifecycleLog(session.logger, LOG_EVENT_NAMES.TURN_STARTED, attributes);
100
107
  }
101
108
 
102
- function createTurnEndHandler(state: HandlerSessionState): Handler {
109
+ function createTurnEndHandler(state: HandlerSessionState): PiHandler<"turn_end"> {
103
110
  return handleTurnEnd.bind(undefined, state);
104
111
  }
105
112
 
106
- function handleTurnEnd(state: HandlerSessionState, event: unknown, _ctx: ObservMeHandlerContext): void {
113
+ function handleTurnEnd(state: HandlerSessionState, event: TurnEndEvent, _ctx: ExtensionContext): void {
107
114
  const session = state.session;
108
115
  if (!session) return;
109
116