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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/agent-sdk/src/runtime.d.ts +0 -2
- package/dist/agent-sdk/src/types.d.ts +9 -2
- package/dist/contracts/src/index.d.ts +152 -0
- package/package.json +2 -2
- package/src/adapters.ts +79 -90
- package/src/runtime.ts +265 -36
- package/src/types.ts +9 -2
- package/.turbo/turbo-lint.log +0 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> bopodev-agent-sdk@0.1.
|
|
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.
|
|
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
|
|
|
@@ -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.
|
|
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.
|
|
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
|
|
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:
|
|
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:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
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
|
|
234
|
-
"-
|
|
235
|
-
"-
|
|
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
|
-
"-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
"
|
|
242
|
-
|
|
243
|
-
|
|
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,
|
|
268
|
-
{"summary":"
|
|
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
|
-
//
|
|
59
|
-
|
|
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
|
|
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(
|
|
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,
|
|
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" &&
|
|
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
|
-
},
|
|
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(),
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/.turbo/turbo-lint.log
DELETED