@cleocode/adapters 2026.4.91 → 2026.4.94

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 (54) hide show
  1. package/dist/index.js +41069 -18278
  2. package/dist/index.js.map +4 -4
  3. package/dist/providers/claude-code/install.d.ts.map +1 -1
  4. package/dist/providers/claude-sdk/index.d.ts +10 -4
  5. package/dist/providers/claude-sdk/index.d.ts.map +1 -1
  6. package/dist/providers/claude-sdk/spawn.d.ts +29 -28
  7. package/dist/providers/claude-sdk/spawn.d.ts.map +1 -1
  8. package/dist/providers/codex/install.d.ts.map +1 -1
  9. package/dist/providers/cursor/install.d.ts.map +1 -1
  10. package/dist/providers/openai-sdk/adapter.d.ts +18 -17
  11. package/dist/providers/openai-sdk/adapter.d.ts.map +1 -1
  12. package/dist/providers/openai-sdk/guardrails.d.ts +71 -18
  13. package/dist/providers/openai-sdk/guardrails.d.ts.map +1 -1
  14. package/dist/providers/openai-sdk/handoff.d.ts +51 -21
  15. package/dist/providers/openai-sdk/handoff.d.ts.map +1 -1
  16. package/dist/providers/openai-sdk/index.d.ts +8 -5
  17. package/dist/providers/openai-sdk/index.d.ts.map +1 -1
  18. package/dist/providers/openai-sdk/install.d.ts +1 -1
  19. package/dist/providers/openai-sdk/spawn.d.ts +54 -21
  20. package/dist/providers/openai-sdk/spawn.d.ts.map +1 -1
  21. package/dist/providers/openai-sdk/tracing.d.ts +87 -21
  22. package/dist/providers/openai-sdk/tracing.d.ts.map +1 -1
  23. package/dist/providers/opencode/install.d.ts.map +1 -1
  24. package/dist/providers/opencode/spawn.d.ts.map +1 -1
  25. package/dist/providers/pi/install.d.ts.map +1 -1
  26. package/dist/providers/shared/paths.d.ts +32 -0
  27. package/dist/providers/shared/paths.d.ts.map +1 -0
  28. package/dist/providers/shared/sdk-result-mapper.d.ts +9 -7
  29. package/dist/providers/shared/sdk-result-mapper.d.ts.map +1 -1
  30. package/package.json +6 -5
  31. package/src/__tests__/claude-code-adapter.test.ts +9 -4
  32. package/src/__tests__/cursor-adapter.test.ts +9 -8
  33. package/src/__tests__/harness-interop.test.ts +451 -0
  34. package/src/__tests__/opencode-adapter.test.ts +9 -4
  35. package/src/providers/claude-code/install.ts +10 -2
  36. package/src/providers/claude-sdk/__tests__/spawn.test.ts +100 -265
  37. package/src/providers/claude-sdk/index.ts +10 -4
  38. package/src/providers/claude-sdk/spawn.ts +69 -106
  39. package/src/providers/codex/install.ts +10 -2
  40. package/src/providers/cursor/install.ts +10 -2
  41. package/src/providers/openai-sdk/__tests__/openai-sdk-spawn.test.ts +134 -103
  42. package/src/providers/openai-sdk/adapter.ts +19 -18
  43. package/src/providers/openai-sdk/guardrails.ts +106 -25
  44. package/src/providers/openai-sdk/handoff.ts +73 -37
  45. package/src/providers/openai-sdk/index.ts +28 -4
  46. package/src/providers/openai-sdk/install.ts +1 -1
  47. package/src/providers/openai-sdk/manifest.json +4 -4
  48. package/src/providers/openai-sdk/spawn.ts +213 -48
  49. package/src/providers/openai-sdk/tracing.ts +105 -22
  50. package/src/providers/opencode/install.ts +10 -2
  51. package/src/providers/opencode/spawn.ts +2 -1
  52. package/src/providers/pi/install.ts +10 -2
  53. package/src/providers/shared/paths.ts +79 -0
  54. package/src/providers/shared/sdk-result-mapper.ts +9 -7
@@ -1,27 +1,33 @@
1
1
  /**
2
- * OpenAI Agents SDK spawn provider.
2
+ * OpenAI SDK spawn provider — Vercel AI SDK edition.
3
+ *
4
+ * Implements `AdapterSpawnProvider` using the Vercel AI SDK
5
+ * (`ai` v6 + `@ai-sdk/openai`) instead of the legacy `@openai/agents`. CLEO
6
+ * retains its own orchestration (handoff topology, guardrails, tracing); the
7
+ * SDK is strictly the LLM bridge.
3
8
  *
4
- * Implements `AdapterSpawnProvider` using the `@openai/agents` SDK runner.
5
9
  * Unlike the Claude Code provider (detached fire-and-forget), this provider
6
- * awaits the run and returns `status: 'completed'` or `status: 'failed'`
7
- * so the orchestrator receives rich output immediately.
10
+ * awaits the run and returns `status: 'completed'` or `status: 'failed'` so
11
+ * the orchestrator receives rich output immediately.
8
12
  *
9
13
  * Key features:
10
14
  * - Tier-based model selection (lead → gpt-4.1, worker → gpt-4.1-mini)
11
- * - Handoff topology built from `SpawnContext.options.handoffs`
12
- * - CLEO path ACL guardrails applied at input stage
15
+ * - Handoff topology built from `SpawnContext.options.handoffs` (CLEO-native)
16
+ * - CLEO path ACL guardrails evaluated before the model call
13
17
  * - Default-on tracing via `CleoConduitTraceProcessor`
14
18
  * - CANT prompt enrichment (best-effort, same as Claude Code provider)
15
19
  *
16
- * @task T582
20
+ * @task T582 (original)
21
+ * @task T933 (SDK consolidation — Vercel AI SDK migration)
22
+ * @see ADR-052 — SDK consolidation decision
17
23
  */
18
24
 
19
25
  import type { AdapterSpawnProvider, SpawnContext, SpawnResult } from '@cleocode/contracts';
20
26
  import { getErrorMessage } from '@cleocode/contracts';
21
- import { addTraceProcessor, OpenAIProvider, Runner, setTracingDisabled } from '@openai/agents';
22
27
  import { mapSdkRunOutcome } from '../shared/sdk-result-mapper.js';
23
- import { buildDefaultGuardrails } from './guardrails.js';
24
- import { buildAgentTopology } from './handoff.js';
28
+ import { buildDefaultGuardrails, evaluateGuardrails } from './guardrails.js';
29
+ import { buildAgentTopology, type CleoAgent } from './handoff.js';
30
+ import type { CleoSpan, CleoTraceProcessor } from './tracing.js';
25
31
  import { CleoConduitTraceProcessor } from './tracing.js';
26
32
 
27
33
  // ---------------------------------------------------------------------------
@@ -68,7 +74,7 @@ export interface OpenAiSdkSpawnOptions {
68
74
 
69
75
  /**
70
76
  * Tool name allowlist. Tools not in this list are documented but not enforced
71
- * (enforcement is structural via the `tools` array on the Agent).
77
+ * (enforcement is structural via CLEO orchestration).
72
78
  *
73
79
  * @defaultValue `[]` (all tools allowed)
74
80
  */
@@ -94,23 +100,88 @@ export interface OpenAiSdkSpawnOptions {
94
100
  const MODEL_LEAD = 'gpt-4.1';
95
101
  const MODEL_WORKER = 'gpt-4.1-mini';
96
102
 
103
+ // ---------------------------------------------------------------------------
104
+ // Trace processor registry (module-scoped)
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /** Registered trace processors that receive span events across the module. */
108
+ const registeredProcessors: CleoTraceProcessor[] = [];
109
+
110
+ /** Module-level flag that disables span dispatch globally. */
111
+ let tracingGlobalDisabled = false;
112
+
113
+ /**
114
+ * Register a CLEO trace processor. Mirrors `addTraceProcessor` from the
115
+ * legacy `@openai/agents` surface so existing consumers continue to work.
116
+ *
117
+ * @param processor - Processor to register.
118
+ */
119
+ export function registerTraceProcessor(processor: CleoTraceProcessor): void {
120
+ registeredProcessors.push(processor);
121
+ }
122
+
123
+ /**
124
+ * Remove a previously registered trace processor.
125
+ *
126
+ * @param processor - Processor to remove.
127
+ */
128
+ export function unregisterTraceProcessor(processor: CleoTraceProcessor): void {
129
+ const idx = registeredProcessors.indexOf(processor);
130
+ if (idx >= 0) {
131
+ registeredProcessors.splice(idx, 1);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Enable or disable global tracing. Equivalent to `setTracingDisabled` from
137
+ * the legacy `@openai/agents` surface.
138
+ *
139
+ * @param disabled - When true, no spans are emitted.
140
+ */
141
+ export function setTracingDisabled(disabled: boolean): void {
142
+ tracingGlobalDisabled = disabled;
143
+ }
144
+
145
+ /**
146
+ * Dispatch a span to every registered trace processor, subject to the global
147
+ * `tracingGlobalDisabled` flag.
148
+ *
149
+ * @param span - Span event to dispatch.
150
+ */
151
+ async function emitSpan(span: CleoSpan): Promise<void> {
152
+ if (tracingGlobalDisabled) return;
153
+ for (const processor of registeredProcessors) {
154
+ try {
155
+ await processor.onSpanEnd(span);
156
+ } catch {
157
+ // Tracing failures must never break a run.
158
+ }
159
+ }
160
+ }
161
+
97
162
  // ---------------------------------------------------------------------------
98
163
  // Provider
99
164
  // ---------------------------------------------------------------------------
100
165
 
101
166
  /**
102
- * Spawn provider for the OpenAI Agents SDK.
167
+ * Spawn provider for the Vercel AI SDK (OpenAI flavour).
103
168
  *
104
- * Spawns SDK-backed agent runs for a given `SpawnContext`. The run is
105
- * awaited synchronously and the result mapped to a `SpawnResult` with
106
- * `status: 'completed'` or `status: 'failed'`. In-flight runs are tracked
107
- * by instance ID so `listRunning()` and `terminate()` work correctly.
169
+ * Spawns SDK-backed agent runs for a given `SpawnContext`. The run is awaited
170
+ * synchronously and the result mapped to a `SpawnResult` with
171
+ * `status: 'completed'` or `status: 'failed'`. In-flight runs are tracked by
172
+ * instance ID so `listRunning()` and `terminate()` work correctly.
108
173
  *
109
174
  * @remarks
110
- * The provider creates a fresh `Runner` per spawn so that `RunConfig`
111
- * settings do not bleed across parallel spawns. Trace processors are
112
- * registered globally via `addTraceProcessor` and removed by disabling
113
- * tracing when the option is set.
175
+ * Handoff topology is resolved by this provider, not the SDK. When the entry
176
+ * agent is a lead with workers, the provider:
177
+ *
178
+ * 1. Runs the lead agent's `generateText` with the enriched prompt.
179
+ * 2. For each worker listed in handoffs, runs a sequential `generateText`
180
+ * using the worker's archetype model, passing the lead's output as the
181
+ * worker prompt. Results are concatenated.
182
+ *
183
+ * This preserves the visible behaviour of the legacy `@openai/agents` runner
184
+ * while keeping CLEO as the orchestration owner.
114
185
  *
115
186
  * @example
116
187
  * ```typescript
@@ -139,7 +210,7 @@ export class OpenAiSdkSpawnProvider implements AdapterSpawnProvider {
139
210
  }
140
211
 
141
212
  /**
142
- * Spawn a subagent via the OpenAI Agents SDK runner.
213
+ * Spawn a subagent via the Vercel AI SDK.
143
214
  *
144
215
  * Awaits the run to completion and returns a fully-resolved `SpawnResult`.
145
216
  *
@@ -184,38 +255,45 @@ export class OpenAiSdkSpawnProvider implements AdapterSpawnProvider {
184
255
  });
185
256
 
186
257
  // Register trace processor globally (default-on).
187
- // The SDK uses global processor registration via addTraceProcessor().
188
258
  let traceProcessor: CleoConduitTraceProcessor | undefined;
259
+ const previousTracingDisabled = tracingGlobalDisabled;
189
260
  if (!opts.tracingDisabled) {
190
261
  traceProcessor = new CleoConduitTraceProcessor(context.taskId);
191
- addTraceProcessor(traceProcessor);
262
+ registerTraceProcessor(traceProcessor);
263
+ setTracingDisabled(false);
192
264
  } else {
193
265
  setTracingDisabled(true);
194
266
  }
195
267
 
196
- // Build OpenAI model provider and runner.
197
- const modelProvider = new OpenAIProvider();
198
- const runner = new Runner({ modelProvider });
199
-
200
- // Run the agent — no extra options needed beyond what is set on the runner.
201
- const runResult = await runner.run(agent, finalPrompt);
202
-
203
- // Restore tracing state if we disabled it for this spawn.
204
- if (opts.tracingDisabled) {
205
- setTracingDisabled(false);
268
+ try {
269
+ // Evaluate guardrails before any model call. A tripped guardrail
270
+ // aborts the run with a structured failure.
271
+ const guardResult = await evaluateGuardrails(agent.inputGuardrails ?? [], finalPrompt);
272
+ if (guardResult.tripwireTriggered) {
273
+ this.runningInstances.delete(instanceId);
274
+ return mapSdkRunOutcome(instanceId, context.taskId, 'openai-sdk', startTime, {
275
+ finalOutput: '',
276
+ succeeded: false,
277
+ errorMessage: `guardrail tripped: ${JSON.stringify(guardResult.outputInfo)}`,
278
+ });
279
+ }
280
+
281
+ // Run the lead/worker topology.
282
+ const runOutput = await runAgentTopology(agent, finalPrompt, emitSpan);
283
+
284
+ this.runningInstances.delete(instanceId);
285
+
286
+ return mapSdkRunOutcome(instanceId, context.taskId, 'openai-sdk', startTime, {
287
+ finalOutput: runOutput,
288
+ succeeded: true,
289
+ });
290
+ } finally {
291
+ // Restore previous tracing state and unregister this run's processor.
292
+ if (traceProcessor) {
293
+ unregisterTraceProcessor(traceProcessor);
294
+ }
295
+ setTracingDisabled(previousTracingDisabled);
206
296
  }
207
-
208
- const finalOutput =
209
- typeof runResult.finalOutput === 'string'
210
- ? runResult.finalOutput
211
- : JSON.stringify(runResult.finalOutput);
212
-
213
- this.runningInstances.delete(instanceId);
214
-
215
- return mapSdkRunOutcome(instanceId, context.taskId, 'openai-sdk', startTime, {
216
- finalOutput,
217
- succeeded: true,
218
- });
219
297
  } catch (error: unknown) {
220
298
  this.runningInstances.delete(instanceId);
221
299
 
@@ -245,9 +323,9 @@ export class OpenAiSdkSpawnProvider implements AdapterSpawnProvider {
245
323
  /**
246
324
  * Terminate a running spawn by instance ID.
247
325
  *
248
- * The OpenAI SDK runner does not support external termination of in-flight
249
- * runs; this method removes the instance from the tracking set so it will
250
- * no longer appear in `listRunning()`.
326
+ * The Vercel AI SDK does not support external termination of in-flight
327
+ * requests; this method removes the instance from the tracking set so it
328
+ * will no longer appear in `listRunning()`.
251
329
  *
252
330
  * @param instanceId - ID of the spawn instance to terminate.
253
331
  */
@@ -298,3 +376,90 @@ export class OpenAiSdkSpawnProvider implements AdapterSpawnProvider {
298
376
  return tier === 'worker' ? MODEL_WORKER : MODEL_LEAD;
299
377
  }
300
378
  }
379
+
380
+ // ---------------------------------------------------------------------------
381
+ // Topology runner
382
+ // ---------------------------------------------------------------------------
383
+
384
+ /**
385
+ * Execute a CLEO agent topology against the Vercel AI SDK.
386
+ *
387
+ * Runs the entry agent via `generateText`. When the entry agent declares
388
+ * handoffs, each worker is executed sequentially with the lead's output as
389
+ * input and the concatenated result returned.
390
+ *
391
+ * @param agent - Entry agent descriptor.
392
+ * @param userPrompt - Enriched user prompt.
393
+ * @param emit - Span emitter invoked after every agent run.
394
+ * @returns Concatenated assistant output.
395
+ */
396
+ async function runAgentTopology(
397
+ agent: CleoAgent,
398
+ userPrompt: string,
399
+ emit: (span: CleoSpan) => Promise<void>,
400
+ ): Promise<string> {
401
+ const { createOpenAI } = await import('@ai-sdk/openai');
402
+ const { generateText } = await import('ai');
403
+
404
+ const apiKey = process.env.OPENAI_API_KEY;
405
+ if (!apiKey) {
406
+ throw new Error('OPENAI_API_KEY is not set — OpenAI SDK provider cannot run');
407
+ }
408
+
409
+ const openai = createOpenAI({ apiKey });
410
+
411
+ // Run the entry agent.
412
+ const startedAt = new Date().toISOString();
413
+ const leadResult = await generateText({
414
+ model: openai(agent.model),
415
+ system: agent.instructions,
416
+ prompt: userPrompt,
417
+ });
418
+ const leadText = (leadResult.text ?? '').trim();
419
+ const leadEnd = new Date().toISOString();
420
+
421
+ await emit({
422
+ spanId: `${agent.name}-${Date.now()}`,
423
+ startedAt,
424
+ endedAt: leadEnd,
425
+ spanData: { type: 'agent', name: agent.name },
426
+ });
427
+
428
+ // When there are no handoffs, return the lead output directly.
429
+ const workers = agent.handoffs ?? [];
430
+ if (workers.length === 0) {
431
+ return leadText;
432
+ }
433
+
434
+ // Sequential handoff execution. Each worker receives the lead output as
435
+ // input, and the concatenated worker outputs become the final run output.
436
+ const workerOutputs: string[] = [];
437
+ for (const worker of workers) {
438
+ const handoffStart = new Date().toISOString();
439
+ await emit({
440
+ spanId: `handoff-${agent.name}-${worker.name}-${Date.now()}`,
441
+ startedAt: handoffStart,
442
+ endedAt: handoffStart,
443
+ spanData: { type: 'handoff', from_agent: agent.name, to_agent: worker.name },
444
+ });
445
+
446
+ const workerStart = new Date().toISOString();
447
+ const workerResult = await generateText({
448
+ model: openai(worker.model),
449
+ system: worker.instructions,
450
+ prompt: leadText,
451
+ });
452
+ const workerEnd = new Date().toISOString();
453
+
454
+ await emit({
455
+ spanId: `${worker.name}-${Date.now()}`,
456
+ startedAt: workerStart,
457
+ endedAt: workerEnd,
458
+ spanData: { type: 'agent', name: worker.name },
459
+ });
460
+
461
+ workerOutputs.push(`[${worker.name}] ${(workerResult.text ?? '').trim()}`);
462
+ }
463
+
464
+ return [leadText, ...workerOutputs].join('\n\n');
465
+ }
@@ -1,32 +1,115 @@
1
1
  /**
2
- * OpenAI Agents SDK trace processor that writes spans to conduit.db.
2
+ * CLEO trace processor that writes spans to conduit.db.
3
3
  *
4
- * `CleoConduitTraceProcessor` implements the SDK `TracingProcessor` interface.
5
- * On every span end it serialises the span and writes a structured event to
6
- * conduit.db via the shared `conduit-trace-writer` transport layer.
4
+ * `CleoConduitTraceProcessor` implements a CLEO-native tracing processor
5
+ * interface that records spans emitted by the OpenAI SDK adapter. Post T933
6
+ * (ADR-052 Vercel AI SDK consolidation) the processor no longer implements
7
+ * `@openai/agents` type surfaces; instead it consumes `CleoSpan` events
8
+ * produced by `OpenAiSdkSpawnProvider` during a run.
7
9
  *
8
10
  * Tracing is on by default for all OpenAI SDK spawns. Set
9
11
  * `options.tracingDisabled = true` in `SpawnContext.options` to opt out when
10
12
  * conduit is unavailable.
11
13
  *
12
- * @task T582
14
+ * @task T582 (original)
15
+ * @task T933 (SDK consolidation — provider-neutral rewrite)
13
16
  */
14
17
 
15
- import type { Span, SpanData, Trace, TracingProcessor } from '@openai/agents';
16
18
  import type { ConduitSpanEvent } from '../shared/conduit-trace-writer.js';
17
19
  import { writeSpanBatchToConduit } from '../shared/conduit-trace-writer.js';
18
20
 
21
+ // ---------------------------------------------------------------------------
22
+ // CLEO-native span shape
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /** Span category emitted by the OpenAI SDK adapter. */
26
+ export type CleoSpanKind = 'agent' | 'handoff' | 'function' | 'model' | 'custom';
27
+
28
+ /**
29
+ * CLEO-native span payload produced by the OpenAI SDK adapter.
30
+ *
31
+ * @remarks
32
+ * Intentionally mirrors the subset of `@openai/agents` span data CLEO
33
+ * actually consumed, rebuilt as a provider-neutral record.
34
+ */
35
+ export interface CleoSpan {
36
+ /** Deterministic span identifier. */
37
+ spanId: string;
38
+ /** ISO timestamp when the span started. */
39
+ startedAt?: string;
40
+ /** ISO timestamp when the span ended. */
41
+ endedAt?: string;
42
+ /** Structured span data (type discriminates payload shape). */
43
+ spanData?: CleoSpanData;
44
+ }
45
+
46
+ /** Agent-kind span payload. */
47
+ export interface CleoAgentSpanData {
48
+ type: 'agent';
49
+ name: string;
50
+ }
51
+
52
+ /** Handoff-kind span payload (agent delegation). */
53
+ export interface CleoHandoffSpanData {
54
+ type: 'handoff';
55
+ from_agent?: string;
56
+ to_agent?: string;
57
+ }
58
+
59
+ /** Function-kind span payload (tool invocation). */
60
+ export interface CleoFunctionSpanData {
61
+ type: 'function';
62
+ name: string;
63
+ }
64
+
65
+ /** Catch-all for future span kinds. */
66
+ export interface CleoGenericSpanData {
67
+ type: Exclude<CleoSpanKind, 'agent' | 'handoff' | 'function'>;
68
+ [key: string]: unknown;
69
+ }
70
+
71
+ /** Tagged union of all span payloads. */
72
+ export type CleoSpanData =
73
+ | CleoAgentSpanData
74
+ | CleoHandoffSpanData
75
+ | CleoFunctionSpanData
76
+ | CleoGenericSpanData;
77
+
78
+ /** CLEO-native trace envelope — currently opaque to the processor. */
79
+ export interface CleoTrace {
80
+ /** Trace identifier. */
81
+ traceId?: string;
82
+ /** Free-form metadata. */
83
+ metadata?: Record<string, unknown>;
84
+ }
85
+
86
+ /**
87
+ * CLEO-native trace processor contract.
88
+ *
89
+ * @remarks
90
+ * Replaces `TracingProcessor` from `@openai/agents`. The shape is compatible
91
+ * with the legacy interface so downstream consumers can continue to invoke
92
+ * the same methods.
93
+ */
94
+ export interface CleoTraceProcessor {
95
+ onTraceStart(trace: CleoTrace): Promise<void>;
96
+ onTraceEnd(trace: CleoTrace): Promise<void>;
97
+ onSpanStart(span: CleoSpan): Promise<void>;
98
+ onSpanEnd(span: CleoSpan): Promise<void>;
99
+ shutdown(timeout?: number): Promise<void>;
100
+ forceFlush(): Promise<void>;
101
+ }
102
+
19
103
  // ---------------------------------------------------------------------------
20
104
  // Processor
21
105
  // ---------------------------------------------------------------------------
22
106
 
23
107
  /**
24
- * CLEO trace processor that persists OpenAI Agents SDK spans to conduit.db.
108
+ * CLEO trace processor that persists OpenAI SDK adapter spans to conduit.db.
25
109
  *
26
- * Implements the `TracingProcessor` interface from `@openai/agents-core`.
27
- * Each `onSpanEnd` call extracts span metadata and enqueues a write to
28
- * conduit via the shared `conduit-trace-writer` module. Writes are
29
- * fire-and-forget — failures are logged but never propagated to the caller.
110
+ * Each `onSpanEnd` call extracts span metadata and enqueues a write to conduit
111
+ * via the shared `conduit-trace-writer` module. Writes are fire-and-forget
112
+ * failures are logged but never propagated to the caller.
30
113
  *
31
114
  * @remarks
32
115
  * `onTraceEnd` performs a batch flush of any buffered spans. Individual
@@ -35,14 +118,14 @@ import { writeSpanBatchToConduit } from '../shared/conduit-trace-writer.js';
35
118
  *
36
119
  * @example
37
120
  * ```typescript
38
- * import { addTraceProcessor } from '@openai/agents';
121
+ * import { registerTraceProcessor } from './spawn.js';
39
122
  * import { CleoConduitTraceProcessor } from './tracing.js';
40
123
  *
41
124
  * const processor = new CleoConduitTraceProcessor('T582');
42
- * addTraceProcessor(processor);
125
+ * registerTraceProcessor(processor);
43
126
  * ```
44
127
  */
45
- export class CleoConduitTraceProcessor implements TracingProcessor {
128
+ export class CleoConduitTraceProcessor implements CleoTraceProcessor {
46
129
  /** CLEO task ID included in every span event for correlation. */
47
130
  private readonly taskId: string;
48
131
 
@@ -61,7 +144,7 @@ export class CleoConduitTraceProcessor implements TracingProcessor {
61
144
  *
62
145
  * @param _trace - The trace that just started (unused).
63
146
  */
64
- async onTraceStart(_trace: Trace): Promise<void> {
147
+ async onTraceStart(_trace: CleoTrace): Promise<void> {
65
148
  this.pendingEvents = [];
66
149
  }
67
150
 
@@ -70,7 +153,7 @@ export class CleoConduitTraceProcessor implements TracingProcessor {
70
153
  *
71
154
  * @param _trace - The trace that just ended (unused — spans were captured via `onSpanEnd`).
72
155
  */
73
- async onTraceEnd(_trace: Trace): Promise<void> {
156
+ async onTraceEnd(_trace: CleoTrace): Promise<void> {
74
157
  if (this.pendingEvents.length > 0) {
75
158
  await writeSpanBatchToConduit(this.pendingEvents);
76
159
  this.pendingEvents = [];
@@ -82,16 +165,16 @@ export class CleoConduitTraceProcessor implements TracingProcessor {
82
165
  *
83
166
  * @param _span - The span that just started (unused).
84
167
  */
85
- async onSpanStart(_span: Span<SpanData>): Promise<void> {
168
+ async onSpanStart(_span: CleoSpan): Promise<void> {
86
169
  // Capture on end so startedAt / endedAt are both populated.
87
170
  }
88
171
 
89
172
  /**
90
173
  * Called when a span ends. Serialises and writes the span to conduit.
91
174
  *
92
- * @param span - The completed span from the SDK.
175
+ * @param span - The completed span from the adapter.
93
176
  */
94
- async onSpanEnd(span: Span<SpanData>): Promise<void> {
177
+ async onSpanEnd(span: CleoSpan): Promise<void> {
95
178
  const event = this.extractSpanEvent(span);
96
179
  if (event) {
97
180
  this.pendingEvents.push(event);
@@ -127,13 +210,13 @@ export class CleoConduitTraceProcessor implements TracingProcessor {
127
210
  // ---------------------------------------------------------------------------
128
211
 
129
212
  /**
130
- * Extract a {@link ConduitSpanEvent} from an SDK span, or `null` if the
213
+ * Extract a {@link ConduitSpanEvent} from an adapter span, or `null` if the
131
214
  * span cannot be meaningfully serialised.
132
215
  *
133
- * @param span - The SDK span to serialise.
216
+ * @param span - The adapter span to serialise.
134
217
  * @returns A conduit span event or `null`.
135
218
  */
136
- private extractSpanEvent(span: Span<SpanData>): ConduitSpanEvent | null {
219
+ private extractSpanEvent(span: CleoSpan): ConduitSpanEvent | null {
137
220
  const spanId =
138
221
  span.spanId ?? `span-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
139
222
 
@@ -10,9 +10,17 @@
10
10
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
12
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
13
+ import { getCleoTemplatesTildePath } from '../shared/paths.js';
13
14
 
14
- /** Lines that should appear in AGENTS.md to reference CLEO. */
15
- const INSTRUCTION_REFERENCES = ['@~/.cleo/templates/CLEO-INJECTION.md', '@.cleo/memory-bridge.md'];
15
+ /**
16
+ * Lines that should appear in AGENTS.md to reference CLEO.
17
+ * The CLEO-INJECTION.md path is resolved dynamically to support non-default
18
+ * XDG / OS installation locations (T916).
19
+ */
20
+ const INSTRUCTION_REFERENCES = [
21
+ `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`,
22
+ '@.cleo/memory-bridge.md',
23
+ ];
16
24
 
17
25
  /**
18
26
  * Install provider for OpenCode.
@@ -16,6 +16,7 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
16
16
  import { join } from 'node:path';
17
17
  import { promisify } from 'node:util';
18
18
  import type { AdapterSpawnProvider, SpawnContext, SpawnResult } from '@cleocode/contracts';
19
+ import { getCleoTemplatesTildePath } from '../shared/paths.js';
19
20
 
20
21
  const execAsync = promisify(exec);
21
22
 
@@ -95,7 +96,7 @@ async function ensureSubagentDefinition(
95
96
  'You are a CLEO subagent executing a delegated task.',
96
97
  'Follow the CLEO protocol and complete the assigned work.',
97
98
  '',
98
- '@~/.cleo/templates/CLEO-INJECTION.md',
99
+ `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`,
99
100
  ].join('\n');
100
101
 
101
102
  const content = buildOpenCodeAgentMarkdown(description, instructions);
@@ -19,9 +19,17 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
19
19
  import { homedir } from 'node:os';
20
20
  import { join } from 'node:path';
21
21
  import type { AdapterInstallProvider, InstallOptions, InstallResult } from '@cleocode/contracts';
22
+ import { getCleoTemplatesTildePath } from '../shared/paths.js';
22
23
 
23
- /** Lines that should appear in AGENTS.md to reference CLEO. */
24
- const INSTRUCTION_REFERENCES = ['@~/.cleo/templates/CLEO-INJECTION.md', '@.cleo/memory-bridge.md'];
24
+ /**
25
+ * Lines that should appear in AGENTS.md to reference CLEO.
26
+ * The CLEO-INJECTION.md path is resolved dynamically to support non-default
27
+ * XDG / OS installation locations (T916).
28
+ */
29
+ const INSTRUCTION_REFERENCES = [
30
+ `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`,
31
+ '@.cleo/memory-bridge.md',
32
+ ];
25
33
 
26
34
  /**
27
35
  * Resolve the Pi global state root directory.
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Path utilities for adapter install providers.
3
+ *
4
+ * These helpers mirror the equivalent functions in `@cleocode/core/paths`
5
+ * but are duplicated here to avoid a circular dependency
6
+ * (`@cleocode/core` → `@cleocode/adapters` → `@cleocode/core`).
7
+ *
8
+ * @remarks
9
+ * Keep the implementation in sync with `getCleoTemplatesTildePath` in
10
+ * `packages/core/src/paths.ts` if the logic ever changes.
11
+ *
12
+ * @task T916
13
+ */
14
+
15
+ import { homedir } from 'node:os';
16
+ import { join } from 'node:path';
17
+
18
+ /**
19
+ * Resolve the XDG / OS-appropriate global CLEO data directory.
20
+ *
21
+ * Respects `CLEO_HOME` env var; otherwise uses the platform default:
22
+ * - Linux: `~/.local/share/cleo`
23
+ * - macOS: `~/Library/Application Support/cleo`
24
+ * - Windows: `%LOCALAPPDATA%\cleo\Data` (approximate)
25
+ *
26
+ * @returns Absolute path to the CLEO data directory
27
+ *
28
+ * @internal
29
+ */
30
+ function getAdapterCleoHome(): string {
31
+ if (process.env['CLEO_HOME']) {
32
+ return process.env['CLEO_HOME'];
33
+ }
34
+ const home = homedir();
35
+ if (process.platform === 'darwin') {
36
+ return join(home, 'Library', 'Application Support', 'cleo');
37
+ }
38
+ if (process.platform === 'win32') {
39
+ const localAppData = process.env['LOCALAPPDATA'];
40
+ if (localAppData) {
41
+ return join(localAppData, 'cleo', 'Data');
42
+ }
43
+ return join(home, 'AppData', 'Local', 'cleo', 'Data');
44
+ }
45
+ // Linux / XDG
46
+ const xdgData = process.env['XDG_DATA_HOME'];
47
+ if (xdgData) {
48
+ return join(xdgData, 'cleo');
49
+ }
50
+ return join(home, '.local', 'share', 'cleo');
51
+ }
52
+
53
+ /**
54
+ * Get the CLEO templates directory as a tilde-prefixed path for use in
55
+ * `@` references (AGENTS.md, CLAUDE.md, etc.). Cross-platform: replaces
56
+ * the user's home directory with `~` so the reference works when loaded
57
+ * by LLM providers that resolve `~` at runtime.
58
+ *
59
+ * @returns Tilde-prefixed path like `"~/.local/share/cleo/templates"` on Linux,
60
+ * `"~/Library/Application Support/cleo/templates"` on macOS, etc.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const ref = `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`;
65
+ * // "@~/.local/share/cleo/templates/CLEO-INJECTION.md" (Linux)
66
+ * ```
67
+ *
68
+ * @task T916
69
+ */
70
+ export function getCleoTemplatesTildePath(): string {
71
+ const absPath = join(getAdapterCleoHome(), 'templates');
72
+ const home = homedir();
73
+ if (absPath.startsWith(home)) {
74
+ // Always use forward slash after tilde for cross-platform @-reference resolution
75
+ const relative = absPath.slice(home.length).replace(/\\/g, '/');
76
+ return `~${relative}`;
77
+ }
78
+ return absPath;
79
+ }