@senad-d/observme 0.1.2 → 0.1.3
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/CHANGELOG.md +1 -0
- package/README.md +3 -1
- package/docs/extension-integration.md +14 -2
- package/package.json +1 -1
- package/src/integration.ts +46 -4
- package/src/pi/integration-api.ts +184 -13
package/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
- Fixed session, workflow, agent, turn, LLM, tool, Bash, histogram, active-span, failure/recovery, and session-count telemetry accuracy.
|
|
31
31
|
- Fixed lifecycle serialization, duplicate session replacement, bounded shutdown/flush behavior, backfill cancellation, parallel tool correlation, and bounded agent-tree state cleanup.
|
|
32
32
|
- Hardened configuration validation, project-root path confinement, endpoint security, custom redaction patterns, tenant-salted hashing, and environment propagation.
|
|
33
|
+
- Hardened integration discovery and lifecycle mutations against malformed providers, unsafe or oversized runtime inputs, and duplicate active spawn/wait/join identifiers.
|
|
33
34
|
- Restored trusted-project redacted LLM capture and OpenAI Responses-style prompt capture while keeping capture disabled and redaction enabled by default.
|
|
34
35
|
- Remediated SonarQube maintainability and security findings across parsing, regexes, fallback handling, diagnostics, and default assignment.
|
|
35
36
|
- Fixed npm/CI dependency installation, package contents, README command drift, Grafana/Loki schema handling, and dashboard provisioning/query validation.
|
package/README.md
CHANGED
|
@@ -167,7 +167,9 @@ if (started?.ok) {
|
|
|
167
167
|
}
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
The discovery helper fails open when the event bus or a provider is malformed. API methods also reject unsafe/oversized requests and duplicate active lifecycle IDs without replacing existing telemetry state. Handle every discriminated failure result locally and keep orchestration functional.
|
|
171
|
+
|
|
172
|
+
Use [`docs/extension-integration.md`](docs/extension-integration.md) for the complete lifecycle, validation limits, and failure contract. The shipped [`examples/integrations/subagent-runner.ts`](examples/integrations/subagent-runner.ts) wraps a generic child transport, while [`docs/agent-subagent-observability-requirements.md`](docs/agent-subagent-observability-requirements.md) covers the larger orchestration design.
|
|
171
173
|
|
|
172
174
|
## Commands
|
|
173
175
|
|
|
@@ -35,7 +35,7 @@ const observme: ObservMeIntegrationApi | undefined = requestObservMeIntegration(
|
|
|
35
35
|
|
|
36
36
|
ObservMe registers no global object and does not require another extension to import its internal telemetry session. The event bus is the runtime boundary; the package subpath provides the stable constants, types, and request helper.
|
|
37
37
|
|
|
38
|
-
The API can be absent when ObservMe is not installed, disabled by package configuration, not loaded yet, or
|
|
38
|
+
The API can be absent when ObservMe is not installed, disabled by package configuration, not loaded yet, incompatible, or connected through a failing/malformed event-bus provider. A method returns `{ ok: false, reason: "session_unavailable" }` when ObservMe is loaded but no telemetry session is active. Orchestration must remain functional in both cases and may run the child without ObservMe correlation after reporting a bounded local warning.
|
|
39
39
|
|
|
40
40
|
A package that cannot take a runtime dependency can implement the same synchronous request directly. Keep this channel and version stable:
|
|
41
41
|
|
|
@@ -118,9 +118,21 @@ Do not put raw tasks, prompts, command lines, environment values, child output,
|
|
|
118
118
|
| `toolCallId` | Optional high-cardinality trace/log correlation when a tool initiated the spawn. |
|
|
119
119
|
| `env` | Base child environment. ObservMe removes stale lineage/W3C keys and returns the replacement environment. |
|
|
120
120
|
|
|
121
|
+
Runtime callers are validated even when JavaScript bypasses the TypeScript types. Caller-provided lifecycle identifiers must match `[A-Za-z0-9._:-]{1,128}`. Commands and individual arguments are capped at 4096 characters, argument lists at 256 items, and environment objects at 4096 entries. Durations must be finite, non-negative milliseconds. Invalid or duplicate active operations return a failure without replacing an existing span.
|
|
122
|
+
|
|
121
123
|
Completion and wait/join methods use bounded child states (`starting`, `active`, `completed`, `failed`, `cancelled`, `orphaned`), join states (`waiting`, `completed`, `failed`, `cancelled`, `timeout`, `unknown`), and wait reasons (`dependency`, `rate_limit`, `child_running`, `unknown`). `failurePropagated=false` on a completed join confirms that the parent recovered from a failed child.
|
|
122
124
|
|
|
123
|
-
All mutation methods return a discriminated result. Handle
|
|
125
|
+
All mutation methods return a discriminated result. Handle these reasons without crashing Pi:
|
|
126
|
+
|
|
127
|
+
| Reason | Meaning |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `session_unavailable` | ObservMe is loaded but no telemetry session is active. |
|
|
130
|
+
| `invalid_request` | An identifier, enum, duration, command/argument field, or environment shape is invalid or oversized. |
|
|
131
|
+
| `spawn_already_exists` / `wait_already_exists` / `join_already_exists` | The requested lifecycle identifier is already active. Generate a unique identifier or finish the active operation; do not overwrite it. |
|
|
132
|
+
| `spawn_not_found` / `wait_not_found` / `join_not_found` | The lifecycle handle is absent or has already ended. |
|
|
133
|
+
| `operation_failed` | ObservMe could not safely complete the operation. |
|
|
134
|
+
|
|
135
|
+
Do not retry a completed lifecycle handle blindly; repeated completion can otherwise hide an orchestration-state bug.
|
|
124
136
|
|
|
125
137
|
## Propagation environment
|
|
126
138
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@senad-d/observme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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",
|
package/src/integration.ts
CHANGED
|
@@ -10,6 +10,10 @@ export type ObservMeChildStatus = "starting" | "active" | "completed" | "failed"
|
|
|
10
10
|
export type ObservMeJoinStatus = "completed" | "failed" | "cancelled" | "timeout" | "unknown" | "waiting";
|
|
11
11
|
export type ObservMeIntegrationFailureReason =
|
|
12
12
|
| "session_unavailable"
|
|
13
|
+
| "invalid_request"
|
|
14
|
+
| "spawn_already_exists"
|
|
15
|
+
| "wait_already_exists"
|
|
16
|
+
| "join_already_exists"
|
|
13
17
|
| "spawn_not_found"
|
|
14
18
|
| "wait_not_found"
|
|
15
19
|
| "join_not_found"
|
|
@@ -118,17 +122,55 @@ interface IntegrationResponseHolder {
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
export function requestObservMeIntegration(host: ObservMeIntegrationHost): ObservMeIntegrationApi | undefined {
|
|
125
|
+
const events = resolveIntegrationEventBus(host);
|
|
126
|
+
if (!events) return undefined;
|
|
127
|
+
|
|
121
128
|
const holder: IntegrationResponseHolder = {};
|
|
122
129
|
const request: ObservMeIntegrationRequest = {
|
|
123
130
|
supportedVersions: [OBSERVME_INTEGRATION_VERSION],
|
|
124
131
|
respond: receiveObservMeIntegration.bind(undefined, holder),
|
|
125
132
|
};
|
|
126
133
|
|
|
127
|
-
|
|
134
|
+
try {
|
|
135
|
+
events.emit(OBSERVME_INTEGRATION_CHANNEL, request);
|
|
136
|
+
} catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
128
139
|
return holder.api;
|
|
129
140
|
}
|
|
130
141
|
|
|
131
|
-
function
|
|
132
|
-
if (
|
|
133
|
-
|
|
142
|
+
function resolveIntegrationEventBus(host: unknown): ObservMeIntegrationEventBus | undefined {
|
|
143
|
+
if (!host || typeof host !== "object") return undefined;
|
|
144
|
+
try {
|
|
145
|
+
const events = (host as Partial<ObservMeIntegrationHost>).events;
|
|
146
|
+
return events && typeof events.emit === "function" ? events : undefined;
|
|
147
|
+
} catch {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function receiveObservMeIntegration(holder: IntegrationResponseHolder, value: unknown): void {
|
|
153
|
+
if (holder.api || !isObservMeIntegrationApi(value)) return;
|
|
154
|
+
holder.api = value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isObservMeIntegrationApi(value: unknown): value is ObservMeIntegrationApi {
|
|
158
|
+
if (!value || typeof value !== "object") return false;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const api = value as Partial<ObservMeIntegrationApi>;
|
|
162
|
+
return (
|
|
163
|
+
api.version === OBSERVME_INTEGRATION_VERSION &&
|
|
164
|
+
typeof api.getContext === "function" &&
|
|
165
|
+
typeof api.startSubagent === "function" &&
|
|
166
|
+
typeof api.completeSubagent === "function" &&
|
|
167
|
+
typeof api.failSubagent === "function" &&
|
|
168
|
+
typeof api.startWait === "function" &&
|
|
169
|
+
typeof api.endWait === "function" &&
|
|
170
|
+
typeof api.startJoin === "function" &&
|
|
171
|
+
typeof api.endJoin === "function"
|
|
172
|
+
);
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
134
176
|
}
|
|
@@ -33,12 +33,22 @@ interface IntegrationPiApi {
|
|
|
33
33
|
readonly events?: IntegrationEventBus;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
const integrationIdentifierPattern = /^[A-Za-z0-9._:-]{1,128}$/u;
|
|
37
|
+
const maximumIntegrationCommandLength = 4096;
|
|
38
|
+
const maximumIntegrationArgumentCount = 256;
|
|
39
|
+
const maximumIntegrationArgumentLength = 4096;
|
|
40
|
+
const maximumIntegrationEnvironmentEntries = 4096;
|
|
41
|
+
|
|
36
42
|
export function registerObservMeIntegration(pi: unknown, state: HandlerSessionState): (() => void) | undefined {
|
|
37
43
|
const events = resolveIntegrationEventBus(pi);
|
|
38
44
|
if (!events) return undefined;
|
|
39
45
|
|
|
40
46
|
const api = new SessionBackedObservMeIntegrationApi(state);
|
|
41
|
-
|
|
47
|
+
try {
|
|
48
|
+
return events.on(OBSERVME_INTEGRATION_CHANNEL, api.handleRequest.bind(api));
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationApi {
|
|
@@ -50,8 +60,8 @@ export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationA
|
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
handleRequest(value: unknown): void {
|
|
53
|
-
if (!isCompatibleIntegrationRequest(value)) return;
|
|
54
63
|
try {
|
|
64
|
+
if (!isCompatibleIntegrationRequest(value)) return;
|
|
55
65
|
value.respond(this);
|
|
56
66
|
} catch {
|
|
57
67
|
return;
|
|
@@ -82,8 +92,12 @@ export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationA
|
|
|
82
92
|
startSubagent(options: ObservMeStartSubagentOptions = {}): ObservMeStartedSubagent | ObservMeIntegrationFailure {
|
|
83
93
|
const session = this.#state.session;
|
|
84
94
|
if (!session) return integrationFailure("session_unavailable");
|
|
85
|
-
|
|
86
95
|
try {
|
|
96
|
+
if (!isValidStartSubagentOptions(options)) return integrationFailure("invalid_request");
|
|
97
|
+
if (options.spawnId && session.spans.activeSubagentSpawns.has(options.spawnId)) {
|
|
98
|
+
return integrationFailure("spawn_already_exists");
|
|
99
|
+
}
|
|
100
|
+
|
|
87
101
|
const started = startSubagentSpawn(session, options);
|
|
88
102
|
return {
|
|
89
103
|
ok: true,
|
|
@@ -103,9 +117,12 @@ export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationA
|
|
|
103
117
|
): ObservMeIntegrationSuccess | ObservMeIntegrationFailure {
|
|
104
118
|
const session = this.#state.session;
|
|
105
119
|
if (!session) return integrationFailure("session_unavailable");
|
|
106
|
-
if (!session.spans.activeSubagentSpawns.get(spawnId)) return integrationFailure("spawn_not_found");
|
|
107
|
-
|
|
108
120
|
try {
|
|
121
|
+
if (!isValidIntegrationIdentifier(spawnId) || !isValidCompleteSubagentOptions(options)) {
|
|
122
|
+
return integrationFailure("invalid_request");
|
|
123
|
+
}
|
|
124
|
+
if (!session.spans.activeSubagentSpawns.has(spawnId)) return integrationFailure("spawn_not_found");
|
|
125
|
+
|
|
109
126
|
completeSubagentSpawn(session, spawnId, options);
|
|
110
127
|
return integrationSuccess();
|
|
111
128
|
} catch {
|
|
@@ -119,9 +136,12 @@ export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationA
|
|
|
119
136
|
): ObservMeIntegrationSuccess | ObservMeIntegrationFailure {
|
|
120
137
|
const session = this.#state.session;
|
|
121
138
|
if (!session) return integrationFailure("session_unavailable");
|
|
122
|
-
if (!session.spans.activeSubagentSpawns.get(spawnId)) return integrationFailure("spawn_not_found");
|
|
123
|
-
|
|
124
139
|
try {
|
|
140
|
+
if (!isValidIntegrationIdentifier(spawnId) || !isValidFailSubagentOptions(options)) {
|
|
141
|
+
return integrationFailure("invalid_request");
|
|
142
|
+
}
|
|
143
|
+
if (!session.spans.activeSubagentSpawns.has(spawnId)) return integrationFailure("spawn_not_found");
|
|
144
|
+
|
|
125
145
|
failSubagentSpawn(session, spawnId, options);
|
|
126
146
|
return integrationSuccess();
|
|
127
147
|
} catch {
|
|
@@ -157,8 +177,15 @@ export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationA
|
|
|
157
177
|
): ObservMeStartedWaitJoin | ObservMeIntegrationFailure {
|
|
158
178
|
const session = this.#state.session;
|
|
159
179
|
if (!session) return integrationFailure("session_unavailable");
|
|
160
|
-
|
|
161
180
|
try {
|
|
181
|
+
if (!isValidWaitJoinOptions(options)) return integrationFailure("invalid_request");
|
|
182
|
+
|
|
183
|
+
const requestedId = resolveRequestedWaitJoinId(options, kind);
|
|
184
|
+
const registry = kind === "wait" ? session.spans.activeAgentWaits : session.spans.activeAgentJoins;
|
|
185
|
+
if (requestedId && registry.has(requestedId)) {
|
|
186
|
+
return integrationFailure(kind === "wait" ? "wait_already_exists" : "join_already_exists");
|
|
187
|
+
}
|
|
188
|
+
|
|
162
189
|
const started = kind === "wait" ? startAgentWait(session, options) : startAgentJoin(session, options);
|
|
163
190
|
return { ok: true, id: started.id };
|
|
164
191
|
} catch {
|
|
@@ -173,10 +200,13 @@ export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationA
|
|
|
173
200
|
): ObservMeIntegrationSuccess | ObservMeIntegrationFailure {
|
|
174
201
|
const session = this.#state.session;
|
|
175
202
|
if (!session) return integrationFailure("session_unavailable");
|
|
176
|
-
const registry = kind === "wait" ? session.spans.activeAgentWaits : session.spans.activeAgentJoins;
|
|
177
|
-
if (!registry.get(id)) return integrationFailure(kind === "wait" ? "wait_not_found" : "join_not_found");
|
|
178
|
-
|
|
179
203
|
try {
|
|
204
|
+
if (!isValidIntegrationIdentifier(id) || !isValidWaitJoinOptions(options)) {
|
|
205
|
+
return integrationFailure("invalid_request");
|
|
206
|
+
}
|
|
207
|
+
const registry = kind === "wait" ? session.spans.activeAgentWaits : session.spans.activeAgentJoins;
|
|
208
|
+
if (!registry.has(id)) return integrationFailure(kind === "wait" ? "wait_not_found" : "join_not_found");
|
|
209
|
+
|
|
180
210
|
if (kind === "wait") endAgentWait(session, id, options);
|
|
181
211
|
else endAgentJoin(session, id, options);
|
|
182
212
|
return integrationSuccess();
|
|
@@ -188,8 +218,12 @@ export class SessionBackedObservMeIntegrationApi implements ObservMeIntegrationA
|
|
|
188
218
|
|
|
189
219
|
function resolveIntegrationEventBus(pi: unknown): IntegrationEventBus | undefined {
|
|
190
220
|
if (!pi || typeof pi !== "object") return undefined;
|
|
191
|
-
|
|
192
|
-
|
|
221
|
+
try {
|
|
222
|
+
const events = (pi as IntegrationPiApi).events;
|
|
223
|
+
return events && typeof events.on === "function" ? events : undefined;
|
|
224
|
+
} catch {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
193
227
|
}
|
|
194
228
|
|
|
195
229
|
function isCompatibleIntegrationRequest(value: unknown): value is ObservMeIntegrationRequest {
|
|
@@ -202,6 +236,143 @@ function isCompatibleIntegrationRequest(value: unknown): value is ObservMeIntegr
|
|
|
202
236
|
);
|
|
203
237
|
}
|
|
204
238
|
|
|
239
|
+
function isValidStartSubagentOptions(value: unknown): value is ObservMeStartSubagentOptions {
|
|
240
|
+
if (!isIntegrationRecord(value)) return false;
|
|
241
|
+
const options = value as Partial<ObservMeStartSubagentOptions>;
|
|
242
|
+
return (
|
|
243
|
+
isOptionalIntegrationIdentifier(options.spawnId) &&
|
|
244
|
+
isOptionalIntegrationIdentifier(options.childAgentId) &&
|
|
245
|
+
isOptionalBoundedString(options.command, maximumIntegrationCommandLength) &&
|
|
246
|
+
isValidIntegrationArguments(options.args) &&
|
|
247
|
+
isOptionalSpawnType(options.spawnType) &&
|
|
248
|
+
isOptionalSpawnReason(options.spawnReason) &&
|
|
249
|
+
isOptionalIntegrationIdentifier(options.toolCallId) &&
|
|
250
|
+
isValidIntegrationEnvironment(options.env)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isValidCompleteSubagentOptions(value: unknown): value is ObservMeCompleteSubagentOptions {
|
|
255
|
+
if (!isIntegrationRecord(value)) return false;
|
|
256
|
+
const options = value as Partial<ObservMeCompleteSubagentOptions>;
|
|
257
|
+
return (
|
|
258
|
+
isOptionalIntegrationIdentifier(options.childAgentId) &&
|
|
259
|
+
isOptionalChildStatus(options.childStatus) &&
|
|
260
|
+
(options.outcome === undefined || options.outcome === "completed" || options.outcome === "failed" || options.outcome === "cancelled")
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isValidFailSubagentOptions(value: unknown): value is ObservMeFailSubagentOptions {
|
|
265
|
+
if (!isIntegrationRecord(value)) return false;
|
|
266
|
+
const options = value as Partial<ObservMeFailSubagentOptions>;
|
|
267
|
+
return isOptionalIntegrationIdentifier(options.childAgentId) && isOptionalBoundedString(options.errorClass, 256);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isValidWaitJoinOptions(value: unknown): value is ObservMeWaitJoinOptions {
|
|
271
|
+
if (!isIntegrationRecord(value)) return false;
|
|
272
|
+
const options = value as Partial<ObservMeWaitJoinOptions>;
|
|
273
|
+
return (
|
|
274
|
+
isOptionalIntegrationIdentifier(options.id) &&
|
|
275
|
+
isOptionalIntegrationIdentifier(options.spawnId) &&
|
|
276
|
+
isOptionalIntegrationIdentifier(options.childAgentId) &&
|
|
277
|
+
isOptionalChildStatus(options.childStatus) &&
|
|
278
|
+
isOptionalJoinStatus(options.joinStatus) &&
|
|
279
|
+
isOptionalWaitReason(options.reason) &&
|
|
280
|
+
(options.failurePropagated === undefined || typeof options.failurePropagated === "boolean") &&
|
|
281
|
+
(options.durationMs === undefined || isValidDuration(options.durationMs))
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isIntegrationRecord(value: unknown): value is Record<string, unknown> {
|
|
286
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function isValidIntegrationIdentifier(value: unknown): value is string {
|
|
290
|
+
return typeof value === "string" && integrationIdentifierPattern.test(value);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function isOptionalIntegrationIdentifier(value: unknown): value is string | undefined {
|
|
294
|
+
return value === undefined || isValidIntegrationIdentifier(value);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isOptionalBoundedString(value: unknown, maximumLength: number): value is string | undefined {
|
|
298
|
+
return value === undefined || (typeof value === "string" && value.length <= maximumLength);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function isValidIntegrationArguments(value: unknown): value is readonly string[] | undefined {
|
|
302
|
+
return (
|
|
303
|
+
value === undefined ||
|
|
304
|
+
(Array.isArray(value) &&
|
|
305
|
+
value.length <= maximumIntegrationArgumentCount &&
|
|
306
|
+
value.every(isValidIntegrationArgument))
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function isValidIntegrationArgument(value: unknown): value is string {
|
|
311
|
+
return typeof value === "string" && value.length <= maximumIntegrationArgumentLength;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function isValidIntegrationEnvironment(value: unknown): boolean {
|
|
315
|
+
if (value === undefined) return true;
|
|
316
|
+
if (!isIntegrationRecord(value)) return false;
|
|
317
|
+
const entries = Object.entries(value);
|
|
318
|
+
return entries.length <= maximumIntegrationEnvironmentEntries && entries.every(isValidIntegrationEnvironmentEntry);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function isValidIntegrationEnvironmentEntry(entry: [string, unknown]): boolean {
|
|
322
|
+
return entry[0].length > 0 && (typeof entry[1] === "string" || entry[1] === undefined);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isOptionalSpawnType(value: unknown): boolean {
|
|
326
|
+
return value === undefined || value === "command" || value === "tool" || value === "extension" || value === "unknown";
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function isOptionalSpawnReason(value: unknown): boolean {
|
|
330
|
+
return (
|
|
331
|
+
value === undefined ||
|
|
332
|
+
value === "delegated_task" ||
|
|
333
|
+
value === "parallel_search" ||
|
|
334
|
+
value === "review" ||
|
|
335
|
+
value === "tool_wrapper" ||
|
|
336
|
+
value === "unknown"
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function isOptionalChildStatus(value: unknown): boolean {
|
|
341
|
+
return (
|
|
342
|
+
value === undefined ||
|
|
343
|
+
value === "starting" ||
|
|
344
|
+
value === "active" ||
|
|
345
|
+
value === "completed" ||
|
|
346
|
+
value === "failed" ||
|
|
347
|
+
value === "cancelled" ||
|
|
348
|
+
value === "orphaned"
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function isOptionalJoinStatus(value: unknown): boolean {
|
|
353
|
+
return (
|
|
354
|
+
value === undefined ||
|
|
355
|
+
value === "completed" ||
|
|
356
|
+
value === "failed" ||
|
|
357
|
+
value === "cancelled" ||
|
|
358
|
+
value === "timeout" ||
|
|
359
|
+
value === "unknown" ||
|
|
360
|
+
value === "waiting"
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function isOptionalWaitReason(value: unknown): boolean {
|
|
365
|
+
return value === undefined || value === "dependency" || value === "rate_limit" || value === "child_running" || value === "unknown";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function isValidDuration(value: unknown): value is number {
|
|
369
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function resolveRequestedWaitJoinId(options: ObservMeWaitJoinOptions, kind: "wait" | "join"): string | undefined {
|
|
373
|
+
return options.id ?? (options.spawnId ? `${kind}-${options.spawnId}` : undefined);
|
|
374
|
+
}
|
|
375
|
+
|
|
205
376
|
function readSessionId(session: ObservMeTelemetrySession): string | undefined {
|
|
206
377
|
const value = session.sessionAttributes?.[SESSION_ATTRIBUTES.PI_SESSION_ID];
|
|
207
378
|
return typeof value === "string" ? value : undefined;
|