@senad-d/observme 0.1.1 → 0.1.2

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 (37) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +103 -13
  3. package/SECURITY.md +1 -1
  4. package/dashboards/observme-slos.yaml +1 -1
  5. package/docs/README.md +54 -0
  6. package/docs/agent-subagent-observability-requirements.md +34 -38
  7. package/docs/configuration.md +9 -0
  8. package/docs/extension-integration.md +222 -0
  9. package/docs/reference/00-README.md +81 -0
  10. package/{ObservMe-Production-Docs → docs/reference}/05-otel-pipeline-and-collector.md +5 -0
  11. package/{ObservMe-Production-Docs → docs/reference}/07-extension-implementation-blueprint.md +18 -7
  12. package/{ObservMe-Production-Docs → docs/reference}/pi-session-format.md +1 -1
  13. package/docs/review-validation.md +1 -1
  14. package/docs/validation-flow.md +8 -1
  15. package/examples/README.md +52 -0
  16. package/examples/collector.yaml +5 -0
  17. package/examples/integrations/subagent-runner.ts +262 -0
  18. package/examples/observme.yaml +4 -0
  19. package/package.json +11 -2
  20. package/skills/observme-docs/SKILL.md +65 -0
  21. package/src/integration.ts +134 -0
  22. package/src/pi/handlers.ts +11 -0
  23. package/src/pi/integration-api.ts +224 -0
  24. package/src/pi/subagent-spawn.ts +1 -1
  25. package/tsconfig.json +1 -1
  26. package/ObservMe-Production-Docs/00-README.md +0 -79
  27. /package/{ObservMe-Production-Docs → docs/reference}/01-requirements-and-scope.md +0 -0
  28. /package/{ObservMe-Production-Docs → docs/reference}/02-reference-architecture.md +0 -0
  29. /package/{ObservMe-Production-Docs → docs/reference}/03-pi-event-and-session-model.md +0 -0
  30. /package/{ObservMe-Production-Docs → docs/reference}/04-telemetry-semantic-conventions.md +0 -0
  31. /package/{ObservMe-Production-Docs → docs/reference}/06-security-privacy-redaction.md +0 -0
  32. /package/{ObservMe-Production-Docs → docs/reference}/08-query-grafana-integration.md +0 -0
  33. /package/{ObservMe-Production-Docs → docs/reference}/09-dashboards-alerts-slos.md +0 -0
  34. /package/{ObservMe-Production-Docs → docs/reference}/10-testing-release-operations.md +0 -0
  35. /package/{ObservMe-Production-Docs → docs/reference}/11-deployment-runbooks.md +0 -0
  36. /package/{ObservMe-Production-Docs → docs/reference}/12-configuration-reference.md +0 -0
  37. /package/{ObservMe-Production-Docs → docs/reference}/13-source-notes.md +0 -0
@@ -0,0 +1,262 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ requestObservMeIntegration,
4
+ type ObservMeChildStatus,
5
+ type ObservMeIntegrationApi,
6
+ type ObservMeJoinStatus,
7
+ type ObservMeProcessEnvironment,
8
+ type ObservMeSpawnReason,
9
+ type ObservMeSpawnType,
10
+ type ObservMeStartedSubagent,
11
+ } from "@senad-d/observme/integration";
12
+
13
+ /**
14
+ * Transport-neutral context supplied to a child launcher.
15
+ *
16
+ * Implement ChildTransport with child_process, Pi RPC, tmux, SSH, a container,
17
+ * a queue, or another process manager. Always pass environment unchanged to
18
+ * the child Pi process and never log it.
19
+ */
20
+ export interface ChildLaunchContext {
21
+ readonly environment: ObservMeProcessEnvironment;
22
+ readonly spawnId?: string;
23
+ readonly childAgentId?: string;
24
+ readonly traceContextPropagated: boolean;
25
+ }
26
+
27
+ export interface ChildRunResult<Value> {
28
+ readonly status: "completed" | "failed" | "cancelled" | "timeout";
29
+ readonly value?: Value;
30
+ readonly failurePropagated?: boolean;
31
+ }
32
+
33
+ export interface ChildTransport<Request, Handle, Value> {
34
+ launch(request: Request, context: ChildLaunchContext, signal?: AbortSignal): Promise<Handle>;
35
+ wait(handle: Handle, signal?: AbortSignal): Promise<ChildRunResult<Value>>;
36
+ }
37
+
38
+ export interface ObservableSubagentRunOptions<Request> {
39
+ readonly request: Request;
40
+ readonly command?: string;
41
+ readonly args?: readonly string[];
42
+ readonly spawnType?: ObservMeSpawnType;
43
+ readonly spawnReason?: ObservMeSpawnReason;
44
+ readonly toolCallId?: string;
45
+ readonly environment?: ObservMeProcessEnvironment;
46
+ readonly signal?: AbortSignal;
47
+ }
48
+
49
+ /**
50
+ * Generic adapter that adds ObservMe lifecycle reporting to any child transport.
51
+ *
52
+ * The transport owns process control and result delivery. ObservMe owns only
53
+ * spawn/wait/join telemetry and propagation context. When ObservMe is absent or
54
+ * inactive, the transport still runs with the supplied base environment.
55
+ */
56
+ export class ObservableSubagentRunner<Request, Handle, Value> {
57
+ readonly #pi: ExtensionAPI;
58
+ readonly #transport: ChildTransport<Request, Handle, Value>;
59
+
60
+ constructor(pi: ExtensionAPI, transport: ChildTransport<Request, Handle, Value>) {
61
+ this.#pi = pi;
62
+ this.#transport = transport;
63
+ }
64
+
65
+ async run(options: ObservableSubagentRunOptions<Request>): Promise<ChildRunResult<Value>> {
66
+ const observme = requestObservMeIntegration(this.#pi);
67
+ const started = startObservMeSubagent(observme, options);
68
+ const context = createLaunchContext(started, options.environment);
69
+ let handle: Handle;
70
+
71
+ try {
72
+ handle = await this.#transport.launch(options.request, context, options.signal);
73
+ completeObservMeLaunch(observme, started);
74
+ } catch (error) {
75
+ failObservMeLaunch(observme, started, error);
76
+ throw error;
77
+ }
78
+
79
+ return this.waitForResult(observme, started, handle, options.signal);
80
+ }
81
+
82
+ private async waitForResult(
83
+ observme: ObservMeIntegrationApi | undefined,
84
+ started: ObservMeStartedSubagent | undefined,
85
+ handle: Handle,
86
+ signal: AbortSignal | undefined,
87
+ ): Promise<ChildRunResult<Value>> {
88
+ const wait = startObservMeWait(observme, started);
89
+
90
+ try {
91
+ const result = await this.#transport.wait(handle, signal);
92
+ endObservMeWait(observme, started, wait, result);
93
+ recordObservMeJoin(observme, started, result);
94
+ return result;
95
+ } catch (error) {
96
+ endObservMeWaitFailure(observme, started, wait);
97
+ recordObservMeJoinFailure(observme, started);
98
+ throw error;
99
+ }
100
+ }
101
+ }
102
+
103
+ function startObservMeSubagent<Request>(
104
+ observme: ObservMeIntegrationApi | undefined,
105
+ options: ObservableSubagentRunOptions<Request>,
106
+ ): ObservMeStartedSubagent | undefined {
107
+ const result = observme?.startSubagent({
108
+ command: options.command,
109
+ args: options.args,
110
+ spawnType: options.spawnType ?? "extension",
111
+ spawnReason: options.spawnReason ?? "delegated_task",
112
+ toolCallId: options.toolCallId,
113
+ env: options.environment ?? process.env,
114
+ });
115
+ return result?.ok ? result : undefined;
116
+ }
117
+
118
+ function createLaunchContext(
119
+ started: ObservMeStartedSubagent | undefined,
120
+ environment: ObservMeProcessEnvironment | undefined,
121
+ ): ChildLaunchContext {
122
+ return {
123
+ environment: started?.env ?? environment ?? process.env,
124
+ spawnId: started?.spawnId,
125
+ childAgentId: started?.childAgentId,
126
+ traceContextPropagated: started?.traceContextPropagated ?? false,
127
+ };
128
+ }
129
+
130
+ function completeObservMeLaunch(
131
+ observme: ObservMeIntegrationApi | undefined,
132
+ started: ObservMeStartedSubagent | undefined,
133
+ ): void {
134
+ if (!observme || !started) return;
135
+ observme.completeSubagent(started.spawnId, {
136
+ childAgentId: started.childAgentId,
137
+ childStatus: "active",
138
+ });
139
+ }
140
+
141
+ function failObservMeLaunch(
142
+ observme: ObservMeIntegrationApi | undefined,
143
+ started: ObservMeStartedSubagent | undefined,
144
+ error: unknown,
145
+ ): void {
146
+ if (!observme || !started) return;
147
+ observme.failSubagent(started.spawnId, {
148
+ childAgentId: started.childAgentId,
149
+ errorClass: safeErrorClass(error),
150
+ });
151
+ }
152
+
153
+ function startObservMeWait(
154
+ observme: ObservMeIntegrationApi | undefined,
155
+ started: ObservMeStartedSubagent | undefined,
156
+ ) {
157
+ if (!observme || !started) return undefined;
158
+ const wait = observme.startWait({
159
+ spawnId: started.spawnId,
160
+ childAgentId: started.childAgentId,
161
+ childStatus: "active",
162
+ reason: "child_running",
163
+ });
164
+ return wait.ok ? wait : undefined;
165
+ }
166
+
167
+ function endObservMeWait<Value>(
168
+ observme: ObservMeIntegrationApi | undefined,
169
+ started: ObservMeStartedSubagent | undefined,
170
+ wait: { readonly id: string } | undefined,
171
+ result: ChildRunResult<Value>,
172
+ ): void {
173
+ if (!observme || !started || !wait) return;
174
+ observme.endWait(wait.id, {
175
+ spawnId: started.spawnId,
176
+ childAgentId: started.childAgentId,
177
+ childStatus: childStatus(result.status),
178
+ joinStatus: joinStatus(result.status),
179
+ reason: "child_running",
180
+ });
181
+ }
182
+
183
+ function endObservMeWaitFailure(
184
+ observme: ObservMeIntegrationApi | undefined,
185
+ started: ObservMeStartedSubagent | undefined,
186
+ wait: { readonly id: string } | undefined,
187
+ ): void {
188
+ if (!observme || !started || !wait) return;
189
+ observme.endWait(wait.id, {
190
+ spawnId: started.spawnId,
191
+ childAgentId: started.childAgentId,
192
+ childStatus: "failed",
193
+ joinStatus: "failed",
194
+ reason: "child_running",
195
+ failurePropagated: true,
196
+ });
197
+ }
198
+
199
+ function recordObservMeJoin<Value>(
200
+ observme: ObservMeIntegrationApi | undefined,
201
+ started: ObservMeStartedSubagent | undefined,
202
+ result: ChildRunResult<Value>,
203
+ ): void {
204
+ if (!observme || !started) return;
205
+ const status = joinStatus(result.status);
206
+ const child = childStatus(result.status);
207
+ const join = observme.startJoin({
208
+ spawnId: started.spawnId,
209
+ childAgentId: started.childAgentId,
210
+ childStatus: child,
211
+ joinStatus: status,
212
+ reason: "dependency",
213
+ });
214
+ if (!join.ok) return;
215
+ observme.endJoin(join.id, {
216
+ spawnId: started.spawnId,
217
+ childAgentId: started.childAgentId,
218
+ childStatus: child,
219
+ joinStatus: status,
220
+ reason: "dependency",
221
+ failurePropagated: result.failurePropagated ?? result.status === "failed",
222
+ });
223
+ }
224
+
225
+ function recordObservMeJoinFailure(
226
+ observme: ObservMeIntegrationApi | undefined,
227
+ started: ObservMeStartedSubagent | undefined,
228
+ ): void {
229
+ if (!observme || !started) return;
230
+ const join = observme.startJoin({
231
+ spawnId: started.spawnId,
232
+ childAgentId: started.childAgentId,
233
+ childStatus: "failed",
234
+ joinStatus: "failed",
235
+ reason: "dependency",
236
+ });
237
+ if (!join.ok) return;
238
+ observme.endJoin(join.id, {
239
+ spawnId: started.spawnId,
240
+ childAgentId: started.childAgentId,
241
+ childStatus: "failed",
242
+ joinStatus: "failed",
243
+ reason: "dependency",
244
+ failurePropagated: true,
245
+ });
246
+ }
247
+
248
+ function childStatus(status: ChildRunResult<unknown>["status"]): ObservMeChildStatus {
249
+ if (status === "completed") return "completed";
250
+ if (status === "cancelled") return "cancelled";
251
+ return "failed";
252
+ }
253
+
254
+ function joinStatus(status: ChildRunResult<unknown>["status"]): ObservMeJoinStatus {
255
+ return status;
256
+ }
257
+
258
+ function safeErrorClass(error: unknown): string {
259
+ if (!(error instanceof Error)) return "launcher_error";
260
+ const normalized = error.name.trim().toLowerCase().replaceAll(/[^a-z0-9_.:-]/gu, "_");
261
+ return /^[a-z][a-z0-9_.:-]{0,63}$/u.test(normalized) ? normalized : "launcher_error";
262
+ }
@@ -1,3 +1,7 @@
1
+ # Privacy-preserving local-development profile for the supported ObservMe stack.
2
+ # Copy to <project>/<CONFIG_DIR_NAME>/observme.yaml (normally .pi/observme.yaml).
3
+ # Keep credentials in environment variables or a trusted project .env, not here.
4
+ # Guide: examples/README.md; full schema: docs/reference/12-configuration-reference.md.
1
5
  observme:
2
6
  enabled: true
3
7
  environment: development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senad-d/observme",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "ObservMe: a Pi extension that instruments Pi agent sessions and exports OpenTelemetry traces, metrics, and logs to an external observability stack (OTel Collector, Grafana Tempo/Loki/Prometheus).",
6
6
  "license": "MIT",
@@ -9,6 +9,10 @@
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/senad-d/ObservMe.git"
11
11
  },
12
+ "exports": {
13
+ ".": "./src/extension.ts",
14
+ "./integration": "./src/integration.ts"
15
+ },
12
16
  "bugs": {
13
17
  "url": "https://github.com/senad-d/ObservMe/issues"
14
18
  },
@@ -34,9 +38,11 @@
34
38
  "SECURITY.md",
35
39
  "CHANGELOG.md",
36
40
  "docs/**/*.md",
37
- "ObservMe-Production-Docs/**/*.md",
41
+ "skills/**/*.md",
38
42
  "dashboards/**/*.json",
39
43
  "dashboards/**/*.yaml",
44
+ "examples/**/*.md",
45
+ "examples/**/*.ts",
40
46
  "examples/**/*.yaml",
41
47
  "img/*",
42
48
  "src/**/*.ts",
@@ -69,6 +75,9 @@
69
75
  "extensions": [
70
76
  "./src/extension.ts"
71
77
  ],
78
+ "skills": [
79
+ "./skills"
80
+ ],
72
81
  "image": "https://raw.githubusercontent.com/senad-d/ObservMe/main/img/demo.gif"
73
82
  },
74
83
  "peerDependencies": {
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: observme-docs
3
+ description: Answers questions about the ObservMe Pi extension, including installation, /obs commands, configuration, privacy and content capture, OpenTelemetry export, Grafana dashboards, metrics, traces, logs, troubleshooting, compatibility, development, and parent/subagent lineage. Use whenever a user asks how ObservMe works, how to set it up or operate it, or where its behavior is documented.
4
+ ---
5
+
6
+ # ObservMe documentation
7
+
8
+ Use the packaged ObservMe documentation to answer questions accurately and point users to the most relevant file and section. Do not modify files unless the user separately asks for changes.
9
+
10
+ ## Resolve packaged documentation paths
11
+
12
+ This skill and its documentation ship in the same installed Pi package. Do not resolve documentation paths against the current working directory or assume that an ObservMe repository checkout exists.
13
+
14
+ 1. Use the absolute path from which Pi loaded this `SKILL.md` as `SKILL_FILE`.
15
+ 2. Set `SKILL_DIR` to the directory containing `SKILL_FILE`.
16
+ 3. Set `PACKAGE_ROOT` to the absolute path obtained by resolving `../..` from `SKILL_DIR`.
17
+ 4. Treat every `package:<path>` entry below as a path relative to `PACKAGE_ROOT`. Pass the resulting absolute filesystem path to the read tool.
18
+
19
+ For example, `package:docs/configuration.md` means the absolute equivalent of `<PACKAGE_ROOT>/docs/configuration.md`, regardless of where npm, git, or a local Pi package was installed. Never pass the literal `package:` prefix to a file tool.
20
+
21
+ Start with `package:docs/README.md` only when the request is broad or its topic is unclear. For a specific request, read the primary document from the routing table directly. Do not load the entire documentation set. If a routed file is absent from `PACKAGE_ROOT`, report that the installed ObservMe package is incomplete instead of silently reading a similarly named file from the current project.
22
+
23
+ ## Route questions
24
+
25
+ | User intent | Primary document | Optional deeper reference |
26
+ | --- | --- | --- |
27
+ | Overview, capabilities, install, quick start, command list, telemetry catalog | `package:README.md` | `package:docs/reference/01-requirements-and-scope.md` |
28
+ | Project config creation, what to edit, credentials, precedence | `package:docs/configuration.md` | `package:docs/reference/12-configuration-reference.md` |
29
+ | Exact config key, environment variable, validation rule, safe default | `package:docs/reference/12-configuration-reference.md` | `package:src/config/schema.ts` only for an implementation-level question or suspected documentation drift |
30
+ | Privacy defaults, prompt/response/tool/Bash capture, redaction, hashing, paths, TLS | `package:SECURITY.md` | `package:docs/reference/06-security-privacy-redaction.md` |
31
+ | Metrics, spans, log events, attributes, labels, cardinality | `package:README.md`, section `Available Telemetry` | `package:docs/reference/04-telemetry-semantic-conventions.md` |
32
+ | OTLP endpoints, Collector processors/exporters, Tempo, Loki, Prometheus, Mimir | `package:examples/README.md` | `package:docs/reference/05-otel-pipeline-and-collector.md` |
33
+ | Local example configuration | `package:examples/observme.yaml` | `package:docs/configuration.md` |
34
+ | Production Collector example | `package:examples/collector.yaml` | `package:docs/reference/05-otel-pipeline-and-collector.md` |
35
+ | `/obs status`, health, session, cost, trace, tools, errors, logs, agents, backfill | `package:README.md`, section `Commands` | `package:docs/reference/08-query-grafana-integration.md` |
36
+ | Grafana auth, datasource UIDs, TLS/DNS, data visible but commands fail | `package:docs/validation-flow.md` | `package:docs/reference/08-query-grafana-integration.md` |
37
+ | Dashboards, panel meaning, drill-downs, PromQL, LogQL, TraceQL, alerts, SLOs | `package:docs/reference/09-dashboards-alerts-slos.md` | `package:README.md`, section `Dashboards and Examples` |
38
+ | Deployment, CI, outages, missing traces/logs/metrics | `package:docs/reference/11-deployment-runbooks.md` | `package:docs/validation-flow.md` |
39
+ | Supported Pi, Node.js, OTEL, Collector, and Grafana-stack versions | `package:docs/compatibility-matrix.md` | `package:docs/reference/10-testing-release-operations.md` |
40
+ | Architecture, lifecycle, extension boundary | `package:docs/reference/02-reference-architecture.md` | `package:docs/reference/07-extension-implementation-blueprint.md` |
41
+ | Pi events, sessions, branches, compaction, recovery | `package:docs/reference/03-pi-event-and-session-model.md` | `package:docs/reference/pi-session-format.md` |
42
+ | Integrating another Pi extension, orchestrator, subagent runner, process manager, or remote executor | `package:docs/extension-integration.md` | `package:examples/integrations/subagent-runner.ts` |
43
+ | Root agents, subagents, tmux, lineage, trace propagation, wait/join | `package:docs/agent-subagent-observability-requirements.md` | `package:docs/reference/03-pi-event-and-session-model.md` |
44
+ | Tests, validation, release, package checks | `package:docs/reference/10-testing-release-operations.md` | `package:docs/review-validation.md` |
45
+ | Source assumptions and upstream documentation basis | `package:docs/reference/13-source-notes.md` | Read the cited upstream source only if the user asks for verification |
46
+ | Security vulnerability reporting | `package:SECURITY.md` | None |
47
+
48
+ ## Answer workflow
49
+
50
+ 1. Classify the question using the routing table.
51
+ 2. Read the primary document completely enough to cover the relevant section. Follow only Markdown links that are necessary to resolve the question.
52
+ 3. Read the deeper reference when the primary document lacks exact details or the user asks for architecture, schema, or implementation depth.
53
+ 4. Answer the question directly, then cite the relevant package-relative file path and section heading.
54
+ 5. If the user asks only where something is documented, provide the shortest matching path and section without summarizing unrelated material.
55
+ 6. If documents conflict, say so. Prefer current user-facing behavior in `package:README.md` and `package:docs/` over design or contributor notes, and identify any apparent drift.
56
+ 7. Distinguish live telemetry from names marked reserved or registered but not yet recorded.
57
+
58
+ ## Safety and accuracy
59
+
60
+ - Never request, print, or repeat Grafana tokens, OTLP tokens, passwords, hash salts, raw prompts, raw commands, or private paths.
61
+ - Recommend environment variables or a trusted project `.env` for secrets; do not recommend putting credentials in YAML.
62
+ - Keep content capture disabled by default. If explaining opt-in capture, keep redaction enabled and mention the hash-salt requirement.
63
+ - Never suggest workflow, session, agent, trace, span, entry, or tool-call IDs as Prometheus labels.
64
+ - Do not claim arbitrary subagent launchers are automatically correlated; full lineage requires an ObservMe-aware launcher and propagated context.
65
+ - Do not invent commands, configuration keys, telemetry names, supported versions, or remediation steps. If the packaged documentation does not answer the question, state that limitation and point to the closest reference.
@@ -0,0 +1,134 @@
1
+ export const OBSERVME_INTEGRATION_CHANNEL = "observme:integration:request";
2
+ export const OBSERVME_INTEGRATION_VERSION = 1 as const;
3
+
4
+ export type ObservMeIntegrationVersion = typeof OBSERVME_INTEGRATION_VERSION;
5
+ export type ObservMeProcessEnvironment = Record<string, string | undefined>;
6
+ export type ObservMeSpawnType = "command" | "tool" | "extension" | "unknown";
7
+ export type ObservMeSpawnReason = "delegated_task" | "parallel_search" | "review" | "tool_wrapper" | "unknown";
8
+ export type ObservMeAgentWaitReason = "dependency" | "rate_limit" | "child_running" | "unknown";
9
+ export type ObservMeChildStatus = "starting" | "active" | "completed" | "failed" | "cancelled" | "orphaned";
10
+ export type ObservMeJoinStatus = "completed" | "failed" | "cancelled" | "timeout" | "unknown" | "waiting";
11
+ export type ObservMeIntegrationFailureReason =
12
+ | "session_unavailable"
13
+ | "spawn_not_found"
14
+ | "wait_not_found"
15
+ | "join_not_found"
16
+ | "operation_failed";
17
+
18
+ export interface ObservMeIntegrationFailure {
19
+ readonly ok: false;
20
+ readonly reason: ObservMeIntegrationFailureReason;
21
+ }
22
+
23
+ export interface ObservMeIntegrationContext {
24
+ readonly workflowId: string;
25
+ readonly workflowRootAgentId: string;
26
+ readonly agentId: string;
27
+ readonly parentAgentId?: string;
28
+ readonly rootAgentId: string;
29
+ readonly depth: number;
30
+ readonly role: "root" | "subagent" | "orchestrator" | "worker" | "reviewer" | "unknown";
31
+ readonly capability?: string;
32
+ readonly sessionId?: string;
33
+ readonly traceId?: string;
34
+ }
35
+
36
+ export interface ObservMeIntegrationContextSuccess {
37
+ readonly ok: true;
38
+ readonly context: ObservMeIntegrationContext;
39
+ }
40
+
41
+ export interface ObservMeStartSubagentOptions {
42
+ readonly spawnId?: string;
43
+ readonly childAgentId?: string;
44
+ readonly command?: string;
45
+ readonly args?: readonly string[];
46
+ readonly spawnType?: ObservMeSpawnType;
47
+ readonly spawnReason?: ObservMeSpawnReason;
48
+ readonly toolCallId?: string;
49
+ readonly env?: ObservMeProcessEnvironment;
50
+ }
51
+
52
+ export interface ObservMeStartedSubagent {
53
+ readonly ok: true;
54
+ readonly spawnId: string;
55
+ readonly childAgentId: string;
56
+ readonly env: ObservMeProcessEnvironment;
57
+ readonly traceContextPropagated: boolean;
58
+ }
59
+
60
+ export interface ObservMeCompleteSubagentOptions {
61
+ readonly childAgentId?: string;
62
+ readonly childStatus?: ObservMeChildStatus;
63
+ readonly outcome?: "completed" | "failed" | "cancelled";
64
+ }
65
+
66
+ export interface ObservMeFailSubagentOptions {
67
+ readonly childAgentId?: string;
68
+ readonly errorClass?: string;
69
+ }
70
+
71
+ export interface ObservMeWaitJoinOptions {
72
+ readonly id?: string;
73
+ readonly spawnId?: string;
74
+ readonly childAgentId?: string;
75
+ readonly childStatus?: ObservMeChildStatus;
76
+ readonly joinStatus?: ObservMeJoinStatus;
77
+ readonly reason?: ObservMeAgentWaitReason;
78
+ readonly failurePropagated?: boolean;
79
+ readonly durationMs?: number;
80
+ }
81
+
82
+ export interface ObservMeStartedWaitJoin {
83
+ readonly ok: true;
84
+ readonly id: string;
85
+ }
86
+
87
+ export interface ObservMeIntegrationSuccess {
88
+ readonly ok: true;
89
+ }
90
+
91
+ export interface ObservMeIntegrationApi {
92
+ readonly version: ObservMeIntegrationVersion;
93
+ getContext(): ObservMeIntegrationContextSuccess | ObservMeIntegrationFailure;
94
+ startSubagent(options?: ObservMeStartSubagentOptions): ObservMeStartedSubagent | ObservMeIntegrationFailure;
95
+ completeSubagent(spawnId: string, options?: ObservMeCompleteSubagentOptions): ObservMeIntegrationSuccess | ObservMeIntegrationFailure;
96
+ failSubagent(spawnId: string, options?: ObservMeFailSubagentOptions): ObservMeIntegrationSuccess | ObservMeIntegrationFailure;
97
+ startWait(options?: ObservMeWaitJoinOptions): ObservMeStartedWaitJoin | ObservMeIntegrationFailure;
98
+ endWait(waitId: string, options?: ObservMeWaitJoinOptions): ObservMeIntegrationSuccess | ObservMeIntegrationFailure;
99
+ startJoin(options?: ObservMeWaitJoinOptions): ObservMeStartedWaitJoin | ObservMeIntegrationFailure;
100
+ endJoin(joinId: string, options?: ObservMeWaitJoinOptions): ObservMeIntegrationSuccess | ObservMeIntegrationFailure;
101
+ }
102
+
103
+ export interface ObservMeIntegrationRequest {
104
+ readonly supportedVersions: readonly ObservMeIntegrationVersion[];
105
+ readonly respond: (api: ObservMeIntegrationApi) => void;
106
+ }
107
+
108
+ export interface ObservMeIntegrationEventBus {
109
+ emit(channel: string, data: unknown): void;
110
+ }
111
+
112
+ export interface ObservMeIntegrationHost {
113
+ readonly events: ObservMeIntegrationEventBus;
114
+ }
115
+
116
+ interface IntegrationResponseHolder {
117
+ api?: ObservMeIntegrationApi;
118
+ }
119
+
120
+ export function requestObservMeIntegration(host: ObservMeIntegrationHost): ObservMeIntegrationApi | undefined {
121
+ const holder: IntegrationResponseHolder = {};
122
+ const request: ObservMeIntegrationRequest = {
123
+ supportedVersions: [OBSERVME_INTEGRATION_VERSION],
124
+ respond: receiveObservMeIntegration.bind(undefined, holder),
125
+ };
126
+
127
+ host.events.emit(OBSERVME_INTEGRATION_CHANNEL, request);
128
+ return holder.api;
129
+ }
130
+
131
+ function receiveObservMeIntegration(holder: IntegrationResponseHolder, api: ObservMeIntegrationApi): void {
132
+ if (api.version !== OBSERVME_INTEGRATION_VERSION || holder.api) return;
133
+ holder.api = api;
134
+ }
@@ -3,6 +3,7 @@ import { registerLifecycleHandlers } from "./event-handlers/lifecycle.ts";
3
3
  import { registerLlmHandlers } from "./event-handlers/llm.ts";
4
4
  import { registerSessionEventHandlers } from "./event-handlers/session-events.ts";
5
5
  import { registerToolBashHandlers } from "./event-handlers/tool-bash.ts";
6
+ import { registerObservMeIntegration } from "./integration-api.ts";
6
7
  import {
7
8
  HandlerRegistrar,
8
9
  SerializedLifecycleQueue,
@@ -71,5 +72,15 @@ export function registerHandlers(pi: unknown, options: RegisterHandlersOptions =
71
72
  registerLlmHandlers(registrar, state);
72
73
  registerToolBashHandlers(registrar, state);
73
74
  registerSessionEventHandlers(registrar, state);
75
+ registerIntegrationCleanup(registrar, registerObservMeIntegration(pi, state));
74
76
  registrar.commit();
75
77
  }
78
+
79
+ function registerIntegrationCleanup(registrar: HandlerRegistrar, unsubscribe: (() => void) | undefined): void {
80
+ if (!unsubscribe) return;
81
+ registrar.add("session_shutdown", unsubscribeIntegration.bind(undefined, unsubscribe));
82
+ }
83
+
84
+ function unsubscribeIntegration(unsubscribe: () => void): void {
85
+ unsubscribe();
86
+ }