bopodev-agent-sdk 0.1.8 → 0.1.9

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.
@@ -1,5 +1,5 @@
1
1
 
2
2
  
3
- > bopodev-agent-sdk@0.1.8 build /Users/danielkrusenstrahle/Documents/Projects/Monorepo/BopoHQ/packages/agent-sdk
3
+ > bopodev-agent-sdk@0.1.9 build /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
4
4
  > tsc -p tsconfig.json --emitDeclarationOnly
5
5
 
@@ -1,4 +1,4 @@
1
1
 
2
- > bopodev-agent-sdk@0.1.7 typecheck /Users/danielkrusenstrahle/Documents/Projects/Monorepo/BopoHQ/packages/agent-sdk
2
+ > bopodev-agent-sdk@0.1.8 typecheck /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
3
3
  > tsc -p tsconfig.json --noEmit
4
4
 
@@ -14,8 +14,6 @@ export interface RuntimeExecutionOutput {
14
14
  tokenOutput?: number;
15
15
  usdCost?: number;
16
16
  summary?: string;
17
- executionMode?: "local_work" | "control_plane";
18
- gatedControlPlaneReasons?: string[];
19
17
  };
20
18
  }
21
19
  export interface RuntimeAttemptTrace {
@@ -57,9 +57,17 @@ export interface AgentRuntimeConfig {
57
57
  args?: string[];
58
58
  cwd?: string;
59
59
  timeoutMs?: number;
60
+ interruptGraceSec?: number;
60
61
  retryCount?: number;
61
62
  retryBackoffMs?: number;
62
63
  env?: Record<string, string>;
64
+ model?: string;
65
+ thinkingEffort?: "auto" | "low" | "medium" | "high";
66
+ bootstrapPrompt?: string;
67
+ runPolicy?: {
68
+ sandboxMode?: "workspace_write" | "full_access";
69
+ allowWebSearch?: boolean;
70
+ };
63
71
  }
64
72
  export interface AdapterTrace {
65
73
  command?: string;
@@ -67,6 +75,7 @@ export interface AdapterTrace {
67
75
  elapsedMs?: number;
68
76
  timedOut?: boolean;
69
77
  failureType?: string;
78
+ usageSource?: "structured" | "estimated" | "none" | "unknown";
70
79
  attemptCount?: number;
71
80
  attempts?: Array<{
72
81
  attempt: number;
@@ -79,6 +88,4 @@ export interface AdapterTrace {
79
88
  }>;
80
89
  stdoutPreview?: string;
81
90
  stderrPreview?: string;
82
- executionMode?: "local_work" | "control_plane";
83
- gatedControlPlaneReasons?: string[];
84
91
  }
@@ -109,6 +109,133 @@ export declare const ProviderTypeSchema: z.ZodEnum<{
109
109
  shell: "shell";
110
110
  }>;
111
111
  export type ProviderType = z.infer<typeof ProviderTypeSchema>;
112
+ export declare const ThinkingEffortSchema: z.ZodEnum<{
113
+ low: "low";
114
+ medium: "medium";
115
+ high: "high";
116
+ auto: "auto";
117
+ }>;
118
+ export type ThinkingEffort = z.infer<typeof ThinkingEffortSchema>;
119
+ export declare const SandboxModeSchema: z.ZodEnum<{
120
+ workspace_write: "workspace_write";
121
+ full_access: "full_access";
122
+ }>;
123
+ export type SandboxMode = z.infer<typeof SandboxModeSchema>;
124
+ export declare const RunPolicySchema: z.ZodObject<{
125
+ sandboxMode: z.ZodDefault<z.ZodEnum<{
126
+ workspace_write: "workspace_write";
127
+ full_access: "full_access";
128
+ }>>;
129
+ allowWebSearch: z.ZodDefault<z.ZodBoolean>;
130
+ }, z.core.$strip>;
131
+ export type RunPolicy = z.infer<typeof RunPolicySchema>;
132
+ export declare const AgentRuntimeConfigSchema: z.ZodObject<{
133
+ runtimeCommand: z.ZodOptional<z.ZodString>;
134
+ runtimeArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
135
+ runtimeCwd: z.ZodOptional<z.ZodString>;
136
+ runtimeEnv: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
137
+ runtimeModel: z.ZodOptional<z.ZodString>;
138
+ runtimeThinkingEffort: z.ZodDefault<z.ZodEnum<{
139
+ low: "low";
140
+ medium: "medium";
141
+ high: "high";
142
+ auto: "auto";
143
+ }>>;
144
+ bootstrapPrompt: z.ZodOptional<z.ZodString>;
145
+ runtimeTimeoutSec: z.ZodDefault<z.ZodNumber>;
146
+ interruptGraceSec: z.ZodDefault<z.ZodNumber>;
147
+ runPolicy: z.ZodDefault<z.ZodObject<{
148
+ sandboxMode: z.ZodDefault<z.ZodEnum<{
149
+ workspace_write: "workspace_write";
150
+ full_access: "full_access";
151
+ }>>;
152
+ allowWebSearch: z.ZodDefault<z.ZodBoolean>;
153
+ }, z.core.$strip>>;
154
+ }, z.core.$strip>;
155
+ export type AgentRuntimeConfig = z.infer<typeof AgentRuntimeConfigSchema>;
156
+ export declare const AgentCreateRequestSchema: z.ZodObject<{
157
+ managerAgentId: z.ZodOptional<z.ZodString>;
158
+ role: z.ZodString;
159
+ name: z.ZodString;
160
+ providerType: z.ZodEnum<{
161
+ claude_code: "claude_code";
162
+ codex: "codex";
163
+ http: "http";
164
+ shell: "shell";
165
+ }>;
166
+ heartbeatCron: z.ZodString;
167
+ monthlyBudgetUsd: z.ZodNumber;
168
+ canHireAgents: z.ZodDefault<z.ZodBoolean>;
169
+ requestApproval: z.ZodDefault<z.ZodBoolean>;
170
+ runtimeConfig: z.ZodDefault<z.ZodObject<{
171
+ runtimeCommand: z.ZodOptional<z.ZodOptional<z.ZodString>>;
172
+ runtimeArgs: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
173
+ runtimeCwd: z.ZodOptional<z.ZodOptional<z.ZodString>>;
174
+ runtimeEnv: z.ZodOptional<z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>>;
175
+ runtimeModel: z.ZodOptional<z.ZodOptional<z.ZodString>>;
176
+ runtimeThinkingEffort: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
177
+ low: "low";
178
+ medium: "medium";
179
+ high: "high";
180
+ auto: "auto";
181
+ }>>>;
182
+ bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
183
+ runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
184
+ interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
185
+ runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
186
+ sandboxMode: z.ZodDefault<z.ZodEnum<{
187
+ workspace_write: "workspace_write";
188
+ full_access: "full_access";
189
+ }>>;
190
+ allowWebSearch: z.ZodDefault<z.ZodBoolean>;
191
+ }, z.core.$strip>>>;
192
+ }, z.core.$strip>>;
193
+ }, z.core.$strip>;
194
+ export type AgentCreateRequest = z.infer<typeof AgentCreateRequestSchema>;
195
+ export declare const AgentUpdateRequestSchema: z.ZodObject<{
196
+ managerAgentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
197
+ role: z.ZodOptional<z.ZodString>;
198
+ name: z.ZodOptional<z.ZodString>;
199
+ providerType: z.ZodOptional<z.ZodEnum<{
200
+ claude_code: "claude_code";
201
+ codex: "codex";
202
+ http: "http";
203
+ shell: "shell";
204
+ }>>;
205
+ status: z.ZodOptional<z.ZodEnum<{
206
+ paused: "paused";
207
+ idle: "idle";
208
+ running: "running";
209
+ terminated: "terminated";
210
+ }>>;
211
+ heartbeatCron: z.ZodOptional<z.ZodString>;
212
+ monthlyBudgetUsd: z.ZodOptional<z.ZodNumber>;
213
+ canHireAgents: z.ZodOptional<z.ZodBoolean>;
214
+ runtimeConfig: z.ZodOptional<z.ZodObject<{
215
+ runtimeCommand: z.ZodOptional<z.ZodOptional<z.ZodString>>;
216
+ runtimeArgs: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
217
+ runtimeCwd: z.ZodOptional<z.ZodOptional<z.ZodString>>;
218
+ runtimeEnv: z.ZodOptional<z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>>;
219
+ runtimeModel: z.ZodOptional<z.ZodOptional<z.ZodString>>;
220
+ runtimeThinkingEffort: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
221
+ low: "low";
222
+ medium: "medium";
223
+ high: "high";
224
+ auto: "auto";
225
+ }>>>;
226
+ bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
227
+ runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
228
+ interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
229
+ runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
230
+ sandboxMode: z.ZodDefault<z.ZodEnum<{
231
+ workspace_write: "workspace_write";
232
+ full_access: "full_access";
233
+ }>>;
234
+ allowWebSearch: z.ZodDefault<z.ZodBoolean>;
235
+ }, z.core.$strip>>>;
236
+ }, z.core.$strip>>;
237
+ }, z.core.$strip>;
238
+ export type AgentUpdateRequest = z.infer<typeof AgentUpdateRequestSchema>;
112
239
  export declare const AgentSchema: z.ZodObject<{
113
240
  id: z.ZodString;
114
241
  companyId: z.ZodString;
@@ -132,6 +259,22 @@ export declare const AgentSchema: z.ZodObject<{
132
259
  usedBudgetUsd: z.ZodDefault<z.ZodNumber>;
133
260
  tokenUsage: z.ZodDefault<z.ZodNumber>;
134
261
  canHireAgents: z.ZodDefault<z.ZodBoolean>;
262
+ avatarSeed: z.ZodOptional<z.ZodString>;
263
+ runtimeCommand: z.ZodOptional<z.ZodNullable<z.ZodString>>;
264
+ runtimeArgsJson: z.ZodOptional<z.ZodNullable<z.ZodString>>;
265
+ runtimeCwd: z.ZodOptional<z.ZodNullable<z.ZodString>>;
266
+ runtimeEnvJson: z.ZodOptional<z.ZodNullable<z.ZodString>>;
267
+ runtimeModel: z.ZodOptional<z.ZodNullable<z.ZodString>>;
268
+ runtimeThinkingEffort: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
269
+ low: "low";
270
+ medium: "medium";
271
+ high: "high";
272
+ auto: "auto";
273
+ }>>>;
274
+ bootstrapPrompt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
275
+ runtimeTimeoutSec: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
276
+ interruptGraceSec: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
277
+ runPolicyJson: z.ZodOptional<z.ZodNullable<z.ZodString>>;
135
278
  createdAt: z.ZodString;
136
279
  updatedAt: z.ZodString;
137
280
  }, z.core.$strip>;
@@ -340,6 +483,7 @@ export declare const OfficeOccupantSchema: z.ZodObject<{
340
483
  http: "http";
341
484
  shell: "shell";
342
485
  }>>;
486
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
343
487
  focusEntityType: z.ZodNullable<z.ZodEnum<{
344
488
  agent: "agent";
345
489
  approval: "approval";
@@ -381,6 +525,7 @@ export declare const OfficeSpaceEventSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
381
525
  http: "http";
382
526
  shell: "shell";
383
527
  }>>;
528
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
384
529
  focusEntityType: z.ZodNullable<z.ZodEnum<{
385
530
  agent: "agent";
386
531
  approval: "approval";
@@ -421,6 +566,7 @@ export declare const OfficeSpaceEventSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
421
566
  http: "http";
422
567
  shell: "shell";
423
568
  }>>;
569
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
424
570
  focusEntityType: z.ZodNullable<z.ZodEnum<{
425
571
  agent: "agent";
426
572
  approval: "approval";
@@ -545,6 +691,7 @@ export declare const RealtimeEventEnvelopeSchema: z.ZodDiscriminatedUnion<[z.Zod
545
691
  http: "http";
546
692
  shell: "shell";
547
693
  }>>;
694
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
548
695
  focusEntityType: z.ZodNullable<z.ZodEnum<{
549
696
  agent: "agent";
550
697
  approval: "approval";
@@ -585,6 +732,7 @@ export declare const RealtimeEventEnvelopeSchema: z.ZodDiscriminatedUnion<[z.Zod
585
732
  http: "http";
586
733
  shell: "shell";
587
734
  }>>;
735
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
588
736
  focusEntityType: z.ZodNullable<z.ZodEnum<{
589
737
  agent: "agent";
590
738
  approval: "approval";
@@ -717,6 +865,7 @@ export declare const RealtimeEventMessageSchema: z.ZodDiscriminatedUnion<[z.ZodO
717
865
  http: "http";
718
866
  shell: "shell";
719
867
  }>>;
868
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
720
869
  focusEntityType: z.ZodNullable<z.ZodEnum<{
721
870
  agent: "agent";
722
871
  approval: "approval";
@@ -757,6 +906,7 @@ export declare const RealtimeEventMessageSchema: z.ZodDiscriminatedUnion<[z.ZodO
757
906
  http: "http";
758
907
  shell: "shell";
759
908
  }>>;
909
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
760
910
  focusEntityType: z.ZodNullable<z.ZodEnum<{
761
911
  agent: "agent";
762
912
  approval: "approval";
@@ -887,6 +1037,7 @@ export declare const RealtimeMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
887
1037
  http: "http";
888
1038
  shell: "shell";
889
1039
  }>>;
1040
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
890
1041
  focusEntityType: z.ZodNullable<z.ZodEnum<{
891
1042
  agent: "agent";
892
1043
  approval: "approval";
@@ -927,6 +1078,7 @@ export declare const RealtimeMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
927
1078
  http: "http";
928
1079
  shell: "shell";
929
1080
  }>>;
1081
+ avatarSeed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
930
1082
  focusEntityType: z.ZodNullable<z.ZodEnum<{
931
1083
  agent: "agent";
932
1084
  approval: "approval";
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "bopodev-agent-sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "types": "src/index.ts",
8
8
  "dependencies": {
9
- "bopodev-contracts": "0.1.8"
9
+ "bopodev-contracts": "0.1.9"
10
10
  },
11
11
  "scripts": {
12
12
  "build": "tsc -p tsconfig.json --emitDeclarationOnly",
package/src/adapters.ts CHANGED
@@ -54,19 +54,12 @@ export class GenericHeartbeatAdapter implements AgentAdapter {
54
54
  };
55
55
  }
56
56
 
57
- const credentialState = resolveControlPlaneCredentialState(context);
58
- const prompt = createPrompt(context, credentialState);
57
+ const prompt = createPrompt(context);
59
58
  const runtime = await executePromptRuntime(context.runtime.command, prompt, context.runtime);
60
- const executionMode = runtime.parsedUsage?.executionMode ?? defaultExecutionMode(credentialState);
61
- const gatedControlPlaneReasons = runtime.parsedUsage?.gatedControlPlaneReasons ?? credentialState.missingReasons;
62
59
  if (runtime.ok) {
63
60
  return {
64
61
  status: "ok",
65
- summary: withExecutionDiagnostics(
66
- runtime.parsedUsage?.summary ?? `${this.providerType} runtime finished in ${runtime.elapsedMs}ms.`,
67
- executionMode,
68
- gatedControlPlaneReasons
69
- ),
62
+ summary: runtime.parsedUsage?.summary ?? `${this.providerType} runtime finished in ${runtime.elapsedMs}ms.`,
70
63
  tokenInput: runtime.parsedUsage?.tokenInput ?? 0,
71
64
  tokenOutput: runtime.parsedUsage?.tokenOutput ?? 0,
72
65
  usdCost: runtime.parsedUsage?.usdCost ?? 0,
@@ -79,24 +72,22 @@ export class GenericHeartbeatAdapter implements AgentAdapter {
79
72
  attemptCount: runtime.attemptCount,
80
73
  attempts: runtime.attempts,
81
74
  stdoutPreview: toPreview(runtime.stdout),
82
- stderrPreview: toPreview(runtime.stderr),
83
- executionMode,
84
- gatedControlPlaneReasons
75
+ stderrPreview: toPreview(runtime.stderr)
85
76
  },
86
77
  nextState: withProviderMetadata(context, this.providerType, runtime.elapsedMs, runtime.code)
87
78
  };
88
79
  }
89
80
 
81
+ const failedUsage = resolveFailedUsage(runtime, prompt, {
82
+ inputRate: 0.000001,
83
+ outputRate: 0.000004
84
+ });
90
85
  return {
91
86
  status: "failed",
92
- summary: withExecutionDiagnostics(
93
- `${this.providerType} runtime failed: ${runtime.stderr || "unknown error"}`,
94
- executionMode,
95
- gatedControlPlaneReasons
96
- ),
97
- tokenInput: 0,
98
- tokenOutput: 0,
99
- usdCost: 0,
87
+ summary: runtime.parsedUsage?.summary ?? `${this.providerType} runtime failed: ${runtime.stderr || "unknown error"}`,
88
+ tokenInput: failedUsage.tokenInput,
89
+ tokenOutput: failedUsage.tokenOutput,
90
+ usdCost: failedUsage.usdCost,
100
91
  trace: {
101
92
  command: context.runtime.command,
102
93
  exitCode: runtime.code,
@@ -105,10 +96,9 @@ export class GenericHeartbeatAdapter implements AgentAdapter {
105
96
  failureType: runtime.failureType,
106
97
  attemptCount: runtime.attemptCount,
107
98
  attempts: runtime.attempts,
99
+ usageSource: failedUsage.source,
108
100
  stdoutPreview: toPreview(runtime.stdout),
109
- stderrPreview: toPreview(runtime.stderr),
110
- executionMode,
111
- gatedControlPlaneReasons
101
+ stderrPreview: toPreview(runtime.stderr)
112
102
  },
113
103
  nextState: context.state
114
104
  };
@@ -131,11 +121,8 @@ async function runProviderWork(
131
121
  provider: "claude_code" | "codex",
132
122
  pricing: { inputRate: number; outputRate: number }
133
123
  ): Promise<AdapterExecutionResult> {
134
- const credentialState = resolveControlPlaneCredentialState(context);
135
- const prompt = createPrompt(context, credentialState);
124
+ const prompt = createPrompt(context);
136
125
  const runtime = await executeAgentRuntime(provider, prompt, context.runtime);
137
- const executionMode = runtime.parsedUsage?.executionMode ?? defaultExecutionMode(credentialState);
138
- const gatedControlPlaneReasons = runtime.parsedUsage?.gatedControlPlaneReasons ?? credentialState.missingReasons;
139
126
  if (runtime.ok) {
140
127
  const fallbackOutputTokens = Math.max(Math.round(runtime.stdout.length / 4), 80);
141
128
  const fallbackInputTokens = Math.max(Math.round(prompt.length / 4), 120);
@@ -144,11 +131,7 @@ async function runProviderWork(
144
131
  const usdCost =
145
132
  runtime.parsedUsage?.usdCost ??
146
133
  Number((tokenInput * pricing.inputRate + tokenOutput * pricing.outputRate).toFixed(6));
147
- const summary = withExecutionDiagnostics(
148
- runtime.parsedUsage?.summary ?? `${provider} runtime finished in ${runtime.elapsedMs}ms.`,
149
- executionMode,
150
- gatedControlPlaneReasons
151
- );
134
+ const summary = runtime.parsedUsage?.summary ?? `${provider} runtime finished in ${runtime.elapsedMs}ms.`;
152
135
 
153
136
  return {
154
137
  status: "ok",
@@ -165,23 +148,18 @@ async function runProviderWork(
165
148
  attemptCount: runtime.attemptCount,
166
149
  attempts: runtime.attempts,
167
150
  stdoutPreview: toPreview(runtime.stdout),
168
- stderrPreview: toPreview(runtime.stderr),
169
- executionMode,
170
- gatedControlPlaneReasons
151
+ stderrPreview: toPreview(runtime.stderr)
171
152
  },
172
153
  nextState: withProviderMetadata(context, provider, runtime.elapsedMs, runtime.code)
173
154
  };
174
155
  }
156
+ const failedUsage = resolveFailedUsage(runtime, prompt, pricing);
175
157
  return {
176
158
  status: "failed",
177
- summary: withExecutionDiagnostics(
178
- `${provider} runtime failed: ${runtime.stderr || "unknown error"}`,
179
- executionMode,
180
- gatedControlPlaneReasons
181
- ),
182
- tokenInput: 0,
183
- tokenOutput: 0,
184
- usdCost: 0,
159
+ summary: runtime.parsedUsage?.summary ?? `${provider} runtime failed: ${runtime.stderr || "unknown error"}`,
160
+ tokenInput: failedUsage.tokenInput,
161
+ tokenOutput: failedUsage.tokenOutput,
162
+ usdCost: failedUsage.usdCost,
185
163
  trace: {
186
164
  command: context.runtime?.command ?? provider,
187
165
  exitCode: runtime.code,
@@ -190,16 +168,58 @@ async function runProviderWork(
190
168
  failureType: runtime.failureType,
191
169
  attemptCount: runtime.attemptCount,
192
170
  attempts: runtime.attempts,
171
+ usageSource: failedUsage.source,
193
172
  stdoutPreview: toPreview(runtime.stdout),
194
- stderrPreview: toPreview(runtime.stderr),
195
- executionMode,
196
- gatedControlPlaneReasons
173
+ stderrPreview: toPreview(runtime.stderr)
197
174
  },
198
175
  nextState: context.state
199
176
  };
200
177
  }
201
178
 
202
- function createPrompt(context: HeartbeatContext, credentialState = resolveControlPlaneCredentialState(context)) {
179
+ function resolveFailedUsage(
180
+ runtime: {
181
+ parsedUsage?: {
182
+ tokenInput?: number;
183
+ tokenOutput?: number;
184
+ usdCost?: number;
185
+ summary?: string;
186
+ };
187
+ failureType?: "timeout" | "spawn_error" | "nonzero_exit";
188
+ stdout: string;
189
+ stderr: string;
190
+ },
191
+ prompt: string,
192
+ pricing: { inputRate: number; outputRate: number }
193
+ ) {
194
+ if (runtime.parsedUsage) {
195
+ return {
196
+ tokenInput: runtime.parsedUsage.tokenInput ?? 0,
197
+ tokenOutput: runtime.parsedUsage.tokenOutput ?? 0,
198
+ usdCost: runtime.parsedUsage.usdCost ?? 0,
199
+ source: "structured" as const
200
+ };
201
+ }
202
+ if (runtime.failureType === "spawn_error") {
203
+ return {
204
+ tokenInput: 0,
205
+ tokenOutput: 0,
206
+ usdCost: 0,
207
+ source: "none" as const
208
+ };
209
+ }
210
+ const estimatedInput = Math.max(1, Math.floor(prompt.length / 4));
211
+ const estimatedOutput = Math.max(1, Math.floor((runtime.stdout.length + runtime.stderr.length) / 8));
212
+ const estimatedCost = Number((estimatedInput * pricing.inputRate + estimatedOutput * pricing.outputRate).toFixed(6));
213
+ return {
214
+ tokenInput: estimatedInput,
215
+ tokenOutput: estimatedOutput,
216
+ usdCost: estimatedCost,
217
+ source: "estimated" as const
218
+ };
219
+ }
220
+
221
+ function createPrompt(context: HeartbeatContext) {
222
+ const bootstrapPrompt = context.runtime?.bootstrapPrompt?.trim();
203
223
  const companyGoals = context.goalContext?.companyGoals.length
204
224
  ? context.goalContext.companyGoals.map((goal) => `- ${goal}`).join("\n")
205
225
  : "- No active company goals";
@@ -230,20 +250,19 @@ function createPrompt(context: HeartbeatContext, credentialState = resolveContro
230
250
  const executionDirectives = [
231
251
  "Execution directives:",
232
252
  "- You are running inside a BopoHQ heartbeat for local repository work.",
233
- "- Use injected skills from the local Codex/agent skills directory when relevant.",
234
- "- Prefer completing assigned issue work in this repository over control-plane orchestration tasks.",
235
- "- Only use BopoHQ control-plane APIs/skills when BOPOHQ_API_URL and BOPOHQ_API_KEY are present.",
236
- "- If those credentials are missing, do not attempt control-plane calls; continue with local issue execution and report constraints briefly.",
253
+ "- Use BopoHQ-specific injected skills only (bopohq-control-plane, bopohq-create-agent, para-memory-files) when relevant.",
254
+ "- Ignore unrelated third-party control-plane skills even if they exist in the runtime environment.",
255
+ "- Prefer completing assigned issue work in this repository over non-essential coordination tasks.",
237
256
  "- Keep command usage minimal and task-focused; avoid broad repository scans unless strictly required for the assigned issue.",
238
- "- If any command fails, avoid further exploratory commands and still return the required final JSON summary."
239
- ].join("\n");
240
- const credentialSnapshot = [
241
- "Control-plane credential snapshot:",
242
- `- BopoHQ credentials: ${credentialState.bopohqEnabled ? "present" : "missing"}`,
243
- ...credentialState.missingReasons.map((reason) => `- Gate: ${reason}`)
257
+ "- Shell commands run under zsh on macOS; avoid Bash-only features such as `local -n`, `declare -n`, `mapfile`, and `readarray`.",
258
+ "- Prefer POSIX/zsh-compatible shell snippets, direct `curl` headers, `jq`, and temp JSON files/heredocs.",
259
+ "- If control-plane API connectivity fails, report the exact failing command/error once and stop retry loops for the same endpoint.",
260
+ "- If any command fails, avoid further exploratory commands and still return the required final JSON summary.",
261
+ "- Your final output must be only the JSON object below, with no prose before or after it.",
262
+ "- Do not invent token or cost values; the runtime records usage separately."
244
263
  ].join("\n");
245
264
 
246
- return `You are ${context.agent.name} (${context.agent.role}), agent ${context.agentId}.
265
+ return `${bootstrapPrompt ? `${bootstrapPrompt}\n\n` : ""}You are ${context.agent.name} (${context.agent.role}), agent ${context.agentId}.
247
266
  Heartbeat run ${context.heartbeatRunId}.
248
267
 
249
268
  Company:
@@ -262,42 +281,12 @@ Assigned issues:
262
281
  ${workItems}
263
282
 
264
283
  ${executionDirectives}
265
- ${credentialSnapshot}
266
284
 
267
- At the end of your response, include exactly one JSON object on a single line:
268
- {"summary":"...","tokenInput":123,"tokenOutput":456,"usdCost":0.123456,"executionMode":"local_work","gatedControlPlaneReasons":["..."]}
285
+ At the end of your response, output exactly one JSON object on a single line and nothing else:
286
+ {"summary":"brief outcome and any blocker"}
269
287
  `;
270
288
  }
271
289
 
272
- function resolveControlPlaneCredentialState(context: HeartbeatContext) {
273
- const env = {
274
- ...process.env,
275
- ...(context.runtime?.env ?? {})
276
- };
277
- const bopohqEnabled = Boolean(env.BOPOHQ_API_URL && env.BOPOHQ_API_KEY);
278
- const missingReasons: string[] = [];
279
- if (!bopohqEnabled) {
280
- missingReasons.push("bopohq credentials missing (BOPOHQ_API_URL and/or BOPOHQ_API_KEY)");
281
- }
282
- return {
283
- bopohqEnabled,
284
- missingReasons
285
- };
286
- }
287
-
288
- function defaultExecutionMode(credentialState: {
289
- bopohqEnabled: boolean;
290
- }) {
291
- return credentialState.bopohqEnabled ? "control_plane" : "local_work";
292
- }
293
-
294
- function withExecutionDiagnostics(summary: string, executionMode: "local_work" | "control_plane", reasons: string[]) {
295
- if (reasons.length === 0) {
296
- return `${summary} [mode=${executionMode}]`;
297
- }
298
- return `${summary} [mode=${executionMode}; gated=${reasons.join(" | ")}]`;
299
- }
300
-
301
290
  function withProviderMetadata(
302
291
  context: HeartbeatContext,
303
292
  provider: string,
package/src/runtime.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { access, lstat, mkdir, mkdtemp, readdir, rm, symlink } from "node:fs/promises";
2
+ import { access, cp, lstat, mkdir, mkdtemp, readdir, rm, symlink } from "node:fs/promises";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -20,8 +20,6 @@ export interface RuntimeExecutionOutput {
20
20
  tokenOutput?: number;
21
21
  usdCost?: number;
22
22
  summary?: string;
23
- executionMode?: "local_work" | "control_plane";
24
- gatedControlPlaneReasons?: string[];
25
23
  };
26
24
  }
27
25
 
@@ -50,13 +48,73 @@ function pickDefaultCommand(provider: "claude_code" | "codex") {
50
48
  return "codex";
51
49
  }
52
50
 
53
- function providerDefaultArgs(provider: "claude_code" | "codex") {
51
+ function providerDefaultArgs(provider: "claude_code" | "codex", config?: AgentRuntimeConfig) {
54
52
  if (provider === "claude_code") {
55
53
  return ["-p"];
56
54
  }
57
55
  // Keep Codex non-interactive, sandboxed, and writable in-workspace by default.
58
- // This avoids inheriting read-only profiles that can cause flaky heartbeat failures.
59
- return ["exec", "--full-auto"];
56
+ // Codex CLI rejects combining --full-auto with sandbox bypass flags.
57
+ if (shouldBypassCodexSandbox(config)) {
58
+ return ["exec", "--skip-git-repo-check"];
59
+ }
60
+ // Include skip-git-repo-check to allow execution from deterministic fallback workspaces
61
+ // that may not be trusted git directories.
62
+ return ["exec", "--full-auto", "--skip-git-repo-check"];
63
+ }
64
+
65
+ function providerConfigArgs(provider: "claude_code" | "codex", config?: AgentRuntimeConfig) {
66
+ const args: string[] = [];
67
+ if (provider === "codex") {
68
+ if (config?.model?.trim()) {
69
+ args.push("--model", config.model.trim());
70
+ }
71
+ if (config?.thinkingEffort && config.thinkingEffort !== "auto") {
72
+ args.push("--reasoning-effort", config.thinkingEffort);
73
+ }
74
+ if (config?.runPolicy?.allowWebSearch) {
75
+ args.push("--search");
76
+ }
77
+ if (shouldBypassCodexSandbox(config)) {
78
+ args.push("--dangerously-bypass-approvals-and-sandbox");
79
+ }
80
+ }
81
+ if (provider === "claude_code") {
82
+ if (config?.model?.trim()) {
83
+ args.push("--model", config.model.trim());
84
+ }
85
+ if (config?.thinkingEffort && config.thinkingEffort !== "auto") {
86
+ args.push("--effort", config.thinkingEffort);
87
+ }
88
+ if (config?.runPolicy?.sandboxMode === "full_access") {
89
+ args.push("--dangerously-skip-permissions");
90
+ }
91
+ }
92
+ return args;
93
+ }
94
+
95
+ function shouldBypassCodexSandbox(config?: AgentRuntimeConfig) {
96
+ if (config?.runPolicy?.sandboxMode === "full_access") {
97
+ return true;
98
+ }
99
+ const env = config?.env;
100
+ if (!env) {
101
+ return false;
102
+ }
103
+ const hasControlPlaneContext =
104
+ typeof env.BOPOHQ_API_BASE_URL === "string" &&
105
+ env.BOPOHQ_API_BASE_URL.trim().length > 0 &&
106
+ typeof env.BOPOHQ_REQUEST_HEADERS_JSON === "string" &&
107
+ env.BOPOHQ_REQUEST_HEADERS_JSON.trim().length > 0;
108
+ if (!hasControlPlaneContext) {
109
+ return false;
110
+ }
111
+ const enforceSandbox = String(env.BOPOHQ_ENFORCE_SANDBOX ?? "")
112
+ .trim()
113
+ .toLowerCase();
114
+ if (enforceSandbox === "1" || enforceSandbox === "true") {
115
+ return false;
116
+ }
117
+ return true;
60
118
  }
61
119
 
62
120
  export async function executeAgentRuntime(
@@ -67,7 +125,8 @@ export async function executeAgentRuntime(
67
125
  const commandOverride = Boolean(config?.command && config.command.trim().length > 0);
68
126
  const effectiveRetryCount = config?.retryCount ?? (provider === "codex" ? 1 : 0);
69
127
  const mergedArgs = [
70
- ...(commandOverride ? [] : providerDefaultArgs(provider)),
128
+ ...(commandOverride ? [] : providerDefaultArgs(provider, config)),
129
+ ...(commandOverride ? [] : providerConfigArgs(provider, config)),
71
130
  ...(config?.args ?? [])
72
131
  ];
73
132
  return executePromptRuntime(
@@ -88,12 +147,16 @@ export async function executePromptRuntime(
88
147
  ): Promise<RuntimeExecutionOutput> {
89
148
  const baseArgs = [...(config?.args ?? [])];
90
149
  const timeoutMs = config?.timeoutMs ?? 120_000;
150
+ const interruptGraceMs = Math.max(0, config?.interruptGraceSec ?? 2) * 1000;
91
151
  const maxAttempts = Math.max(1, Math.min(3, 1 + (config?.retryCount ?? 0)));
92
152
  const retryBackoffMs = Math.max(100, config?.retryBackoffMs ?? 400);
93
- const env = {
153
+ const mergedEnv = {
94
154
  ...process.env,
95
155
  ...(config?.env ?? {})
96
156
  };
157
+ const normalizedEnv = normalizeProviderAuthEnv(options?.provider, mergedEnv);
158
+ const providerIsolation = await withProviderRuntimeIsolation(options?.provider, normalizedEnv);
159
+ const env = providerIsolation.env;
97
160
  const provider = options?.provider;
98
161
  const injection = await prepareSkillInjection(provider, env);
99
162
  const args = [...baseArgs, ...injection.additionalArgs, prompt];
@@ -112,9 +175,17 @@ export async function executePromptRuntime(
112
175
  | undefined;
113
176
  try {
114
177
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
115
- const attemptResult = await executeSinglePromptAttempt(command, args, config?.cwd || process.cwd(), env, timeoutMs);
178
+ const attemptResult = await executeSinglePromptAttempt(
179
+ command,
180
+ args,
181
+ config?.cwd || process.cwd(),
182
+ env,
183
+ timeoutMs,
184
+ interruptGraceMs
185
+ );
186
+ const normalizedStderr = provider === "codex" ? stripCodexRolloutNoise(attemptResult.stderr) : attemptResult.stderr;
116
187
  stdout += attemptResult.stdout;
117
- stderr = [stderr, attemptResult.stderr].filter(Boolean).join("\n").trim();
188
+ stderr = [stderr, normalizedStderr].filter(Boolean).join("\n").trim();
118
189
  attempts.push({
119
190
  attempt,
120
191
  code: attemptResult.code,
@@ -151,7 +222,11 @@ export async function executePromptRuntime(
151
222
  attemptResult.spawnErrorCode && TRANSIENT_SPAWN_ERROR_CODES.has(attemptResult.spawnErrorCode)
152
223
  );
153
224
  const retryableCodexNonZero =
154
- provider === "codex" && !attemptResult.timedOut && !attemptResult.spawnErrorCode && attemptResult.code !== 0;
225
+ provider === "codex" &&
226
+ !attemptResult.timedOut &&
227
+ !attemptResult.spawnErrorCode &&
228
+ attemptResult.code !== 0 &&
229
+ !containsCodexAuthFailure(`${attemptResult.stdout}\n${normalizedStderr}`);
155
230
  const retryable = retryableSpawnError || retryableCodexNonZero;
156
231
  if (!retryable || attempt >= maxAttempts) {
157
232
  break;
@@ -172,6 +247,7 @@ export async function executePromptRuntime(
172
247
  parsedUsage: parseStructuredUsage(stdout)
173
248
  };
174
249
  } finally {
250
+ await providerIsolation.cleanup();
175
251
  await injection.cleanup();
176
252
  }
177
253
  }
@@ -179,6 +255,11 @@ export async function executePromptRuntime(
179
255
  const SKILLS_DIR_NAME = "skills";
180
256
  const CLAUDE_SKILLS_DIR = ".claude/skills";
181
257
  const SKILL_MD = "SKILL.md";
258
+ const DEFAULT_CODEX_HOME_ROOT = ".bopodev/runtime/codex-home";
259
+ const DEFAULT_CODEX_HOME_FALLBACK = ".codex";
260
+ const CODEX_VOLATILE_STATE_ENTRIES = ["rollouts", "state.db", "data/rollouts", "data/state.db"];
261
+ const CODEX_ROLLOUT_NOISE_RE =
262
+ /^\d{4}-\d{2}-\d{2}T[^\s]+\s+ERROR\s+codex_core::rollout::list:\s+state db missing rollout path for thread\s+[a-z0-9-]+$/i;
182
263
 
183
264
  type SkillInjectionContext = {
184
265
  additionalArgs: string[];
@@ -238,14 +319,13 @@ function noSkillInjection(): SkillInjectionContext {
238
319
  }
239
320
 
240
321
  const TRANSIENT_SPAWN_ERROR_CODES = new Set(["EAGAIN", "EMFILE", "ENFILE", "ETXTBSY", "EBUSY"]);
241
- const FORCE_KILL_AFTER_MS = 2_000;
242
-
243
322
  async function executeSinglePromptAttempt(
244
323
  command: string,
245
324
  args: string[],
246
325
  cwd: string,
247
326
  env: NodeJS.ProcessEnv,
248
- timeoutMs: number
327
+ timeoutMs: number,
328
+ interruptGraceMs: number
249
329
  ) {
250
330
  const startedAt = Date.now();
251
331
  return new Promise<{
@@ -279,7 +359,7 @@ async function executeSinglePromptAttempt(
279
359
  forcedKill = true;
280
360
  child.kill("SIGKILL");
281
361
  }
282
- }, FORCE_KILL_AFTER_MS);
362
+ }, interruptGraceMs);
283
363
  }, timeoutMs);
284
364
 
285
365
  child.stdout.on("data", (chunk) => {
@@ -352,6 +432,14 @@ function sleep(ms: number) {
352
432
  return new Promise<void>((resolve) => setTimeout(resolve, ms));
353
433
  }
354
434
 
435
+ function containsCodexAuthFailure(text: string) {
436
+ const normalized = text.toLowerCase();
437
+ if (!normalized.includes("401 unauthorized")) {
438
+ return false;
439
+ }
440
+ return normalized.includes("missing bearer") || normalized.includes("authentication");
441
+ }
442
+
355
443
  export async function checkRuntimeCommandHealth(
356
444
  command: string,
357
445
  options?: { cwd?: string; timeoutMs?: number }
@@ -450,7 +538,155 @@ function resolveCodexHome(env: NodeJS.ProcessEnv) {
450
538
  if (configured) {
451
539
  return configured;
452
540
  }
453
- return join(homedir(), ".codex");
541
+ return join(homedir(), DEFAULT_CODEX_HOME_FALLBACK);
542
+ }
543
+
544
+ function normalizeProviderAuthEnv(
545
+ provider: "claude_code" | "codex" | undefined,
546
+ env: NodeJS.ProcessEnv
547
+ ) {
548
+ if (provider !== "codex") {
549
+ return env;
550
+ }
551
+ const apiKey = env.OPENAI_API_KEY;
552
+ if (typeof apiKey === "string" && apiKey.trim().length === 0) {
553
+ const nextEnv = { ...env };
554
+ delete nextEnv.OPENAI_API_KEY;
555
+ return nextEnv;
556
+ }
557
+ return env;
558
+ }
559
+
560
+ type ProviderIsolationContext = {
561
+ env: NodeJS.ProcessEnv;
562
+ cleanup: () => Promise<void>;
563
+ };
564
+
565
+ async function withProviderRuntimeIsolation(
566
+ provider: "claude_code" | "codex" | undefined,
567
+ env: NodeJS.ProcessEnv
568
+ ): Promise<ProviderIsolationContext> {
569
+ if (provider !== "codex") {
570
+ return {
571
+ env,
572
+ cleanup: async () => {}
573
+ };
574
+ }
575
+ const forceManagedCodexHome = env.BOPOHQ_FORCE_MANAGED_CODEX_HOME === "true";
576
+ if (env.CODEX_HOME?.trim() && !forceManagedCodexHome) {
577
+ await sanitizeCodexHomeVolatileState(env.CODEX_HOME.trim());
578
+ return {
579
+ env,
580
+ cleanup: async () => {}
581
+ };
582
+ }
583
+ const hasApiKey = typeof env.OPENAI_API_KEY === "string" && env.OPENAI_API_KEY.trim().length > 0;
584
+ if (hasApiKey) {
585
+ const runScopedCodexHome = await mkdtemp(join(tmpdir(), "bopohq-codex-home-run-"));
586
+ await sanitizeCodexHomeVolatileState(runScopedCodexHome);
587
+ return {
588
+ env: {
589
+ ...env,
590
+ CODEX_HOME: runScopedCodexHome
591
+ },
592
+ cleanup: async () => {
593
+ await rm(runScopedCodexHome, { recursive: true, force: true });
594
+ }
595
+ };
596
+ }
597
+ if (!forceManagedCodexHome) {
598
+ return {
599
+ env: {
600
+ ...env,
601
+ CODEX_HOME: resolveCodexHome(env)
602
+ },
603
+ cleanup: async () => {}
604
+ };
605
+ }
606
+ const targetCodexHome = resolveManagedCodexHome(env);
607
+ await prepareManagedCodexHome(targetCodexHome, env);
608
+ return {
609
+ env: {
610
+ ...env,
611
+ CODEX_HOME: targetCodexHome
612
+ },
613
+ cleanup: async () => {}
614
+ };
615
+ }
616
+
617
+ function resolveManagedCodexHome(env: NodeJS.ProcessEnv) {
618
+ const managedRoot = resolveManagedCodexHomeRoot(env);
619
+ const companyId = sanitizePathSegment(env.BOPOHQ_COMPANY_ID);
620
+ const agentId = sanitizePathSegment(env.BOPOHQ_AGENT_ID);
621
+ if (companyId && agentId) {
622
+ return join(managedRoot, companyId, agentId);
623
+ }
624
+ return join(managedRoot, "shared");
625
+ }
626
+
627
+ function resolveManagedCodexHomeRoot(env: NodeJS.ProcessEnv) {
628
+ const configuredRoot = env.BOPO_CODEX_HOME_ROOT?.trim();
629
+ if (configuredRoot) {
630
+ return configuredRoot;
631
+ }
632
+ return join(tmpdir(), "bopohq-codex-home");
633
+ }
634
+
635
+ async function prepareManagedCodexHome(targetCodexHome: string, env: NodeJS.ProcessEnv) {
636
+ await mkdir(targetCodexHome, { recursive: true });
637
+ await seedCodexHomeIfEmpty(targetCodexHome, env);
638
+ await sanitizeCodexHomeVolatileState(targetCodexHome);
639
+ }
640
+
641
+ async function seedCodexHomeIfEmpty(targetCodexHome: string, env: NodeJS.ProcessEnv) {
642
+ if (env.BOPO_CODEX_ALLOW_HOME_SEED !== "true") {
643
+ return;
644
+ }
645
+ const currentEntries = await readdir(targetCodexHome).catch(() => []);
646
+ if (currentEntries.length > 0) {
647
+ return;
648
+ }
649
+ const sourceCodexHome = join(homedir(), DEFAULT_CODEX_HOME_FALLBACK);
650
+ const sourceEntries = await readdir(sourceCodexHome, { withFileTypes: true }).catch(() => []);
651
+ for (const entry of sourceEntries) {
652
+ if (CODEX_VOLATILE_STATE_ENTRIES.includes(entry.name)) {
653
+ continue;
654
+ }
655
+ const source = join(sourceCodexHome, entry.name);
656
+ const target = join(targetCodexHome, entry.name);
657
+ await cp(source, target, {
658
+ recursive: true,
659
+ force: false,
660
+ errorOnExist: false
661
+ }).catch(() => undefined);
662
+ }
663
+ }
664
+
665
+ async function sanitizeCodexHomeVolatileState(codexHome: string) {
666
+ for (const entry of CODEX_VOLATILE_STATE_ENTRIES) {
667
+ await rm(join(codexHome, entry), { recursive: true, force: true });
668
+ }
669
+ }
670
+
671
+ function stripCodexRolloutNoise(text: string) {
672
+ return text
673
+ .split(/\r?\n/)
674
+ .filter((line) => {
675
+ const trimmed = line.trim();
676
+ return !trimmed || !CODEX_ROLLOUT_NOISE_RE.test(trimmed);
677
+ })
678
+ .join("\n");
679
+ }
680
+
681
+ function sanitizePathSegment(value: string | undefined) {
682
+ if (!value) {
683
+ return null;
684
+ }
685
+ const trimmed = value.trim();
686
+ if (!trimmed) {
687
+ return null;
688
+ }
689
+ return trimmed.replace(/[^a-zA-Z0-9._-]/g, "_");
454
690
  }
455
691
 
456
692
  async function buildClaudeSkillsAddDir(skillsSourceDir: string) {
@@ -525,27 +761,18 @@ function tryParseUsage(candidate: string) {
525
761
  const tokenOutput = toNumber(parsed.tokenOutput);
526
762
  const usdCost = toNumber(parsed.usdCost);
527
763
  const summary = typeof parsed.summary === "string" ? parsed.summary : undefined;
528
- const executionMode: "local_work" | "control_plane" | undefined =
529
- parsed.executionMode === "local_work" || parsed.executionMode === "control_plane"
530
- ? parsed.executionMode
531
- : undefined;
532
- const gatedControlPlaneReasons = Array.isArray(parsed.gatedControlPlaneReasons)
533
- ? parsed.gatedControlPlaneReasons.map((item) => String(item))
534
- : undefined;
535
- if (isPromptTemplateUsage(summary, tokenInput, tokenOutput, usdCost, executionMode, gatedControlPlaneReasons)) {
764
+ if (isPromptTemplateUsage(summary, tokenInput, tokenOutput, usdCost)) {
536
765
  return undefined;
537
766
  }
538
767
  if (
539
768
  tokenInput === undefined &&
540
769
  tokenOutput === undefined &&
541
770
  usdCost === undefined &&
542
- !summary &&
543
- !executionMode &&
544
- !gatedControlPlaneReasons
771
+ !summary
545
772
  ) {
546
773
  return undefined;
547
774
  }
548
- return { tokenInput, tokenOutput, usdCost, summary, executionMode, gatedControlPlaneReasons };
775
+ return { tokenInput, tokenOutput, usdCost, summary };
549
776
  } catch {
550
777
  return undefined;
551
778
  }
@@ -555,19 +782,21 @@ function isPromptTemplateUsage(
555
782
  summary: string | undefined,
556
783
  tokenInput: number | undefined,
557
784
  tokenOutput: number | undefined,
558
- usdCost: number | undefined,
559
- executionMode: "local_work" | "control_plane" | undefined,
560
- gatedControlPlaneReasons: string[] | undefined
785
+ usdCost: number | undefined
561
786
  ) {
787
+ if (
788
+ summary?.trim().toLowerCase() === "brief outcome and any blocker" &&
789
+ tokenInput === undefined &&
790
+ tokenOutput === undefined &&
791
+ usdCost === undefined
792
+ ) {
793
+ return true;
794
+ }
562
795
  return (
563
796
  summary === "..." &&
564
797
  tokenInput === 123 &&
565
798
  tokenOutput === 456 &&
566
- usdCost === 0.123456 &&
567
- executionMode === "local_work" &&
568
- Array.isArray(gatedControlPlaneReasons) &&
569
- gatedControlPlaneReasons.length === 1 &&
570
- gatedControlPlaneReasons[0] === "..."
799
+ usdCost === 0.123456
571
800
  );
572
801
  }
573
802
 
package/src/types.ts CHANGED
@@ -64,9 +64,17 @@ export interface AgentRuntimeConfig {
64
64
  args?: string[];
65
65
  cwd?: string;
66
66
  timeoutMs?: number;
67
+ interruptGraceSec?: number;
67
68
  retryCount?: number;
68
69
  retryBackoffMs?: number;
69
70
  env?: Record<string, string>;
71
+ model?: string;
72
+ thinkingEffort?: "auto" | "low" | "medium" | "high";
73
+ bootstrapPrompt?: string;
74
+ runPolicy?: {
75
+ sandboxMode?: "workspace_write" | "full_access";
76
+ allowWebSearch?: boolean;
77
+ };
70
78
  }
71
79
 
72
80
  export interface AdapterTrace {
@@ -75,6 +83,7 @@ export interface AdapterTrace {
75
83
  elapsedMs?: number;
76
84
  timedOut?: boolean;
77
85
  failureType?: string;
86
+ usageSource?: "structured" | "estimated" | "none" | "unknown";
78
87
  attemptCount?: number;
79
88
  attempts?: Array<{
80
89
  attempt: number;
@@ -87,6 +96,4 @@ export interface AdapterTrace {
87
96
  }>;
88
97
  stdoutPreview?: string;
89
98
  stderrPreview?: string;
90
- executionMode?: "local_work" | "control_plane";
91
- gatedControlPlaneReasons?: string[];
92
99
  }
@@ -1,4 +0,0 @@
1
-
2
- > @bopo/agent-sdk@0.1.0 lint /Users/danielkrusenstrahle/Documents/Projects/Monorepo/BopoHQ/packages/agent-sdk
3
- > tsc -p tsconfig.json --noEmit
4
-