bopodev-agent-sdk 0.1.26 → 0.1.27
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/README.md +27 -0
- package/dist/adapters/codex/src/server/quota.d.ts +2 -0
- package/dist/agent-sdk/src/quota.d.ts +4 -0
- package/dist/agent-sdk/src/types.d.ts +10 -0
- package/dist/contracts/src/index.d.ts +72 -0
- package/package.json +2 -2
- package/src/adapters.ts +90 -12
- package/src/runtime.ts +16 -1
- package/src/types.ts +11 -0
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.27 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.26 typecheck /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
|
|
3
3
|
> tsc -p tsconfig.json --noEmit
|
|
4
4
|
|
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# `packages/agent-sdk`
|
|
2
|
+
|
|
3
|
+
Runtime orchestration SDK for adapter resolution, execution integration, and runtime health checks.
|
|
4
|
+
|
|
5
|
+
## Responsibilities
|
|
6
|
+
|
|
7
|
+
- Resolve and register adapter modules.
|
|
8
|
+
- Provide shared execution/runtime helpers used by API heartbeat workflows.
|
|
9
|
+
- Expose runtime command health checks used at API startup and health endpoints.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Primary consumers:
|
|
14
|
+
|
|
15
|
+
- `apps/api` heartbeat and startup paths.
|
|
16
|
+
- `packages/adapters/*` through adapter module contracts.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
- `pnpm --filter bopodev-agent-sdk build`
|
|
21
|
+
- `pnpm --filter bopodev-agent-sdk typecheck`
|
|
22
|
+
|
|
23
|
+
## Related Docs
|
|
24
|
+
|
|
25
|
+
- `docs/adapters/overview.md`
|
|
26
|
+
- `docs/adapter-authoring.md`
|
|
27
|
+
- `docs/developer/architecture.md`
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AgentProviderType, AdapterQuotaProbeResult, AgentRuntimeConfig } from "./types";
|
|
2
|
+
export declare function createUnsupportedQuotaProbeResult(providerType: AgentProviderType, message: string): AdapterQuotaProbeResult;
|
|
3
|
+
export declare function createAuthRequiredQuotaProbeResult(providerType: AgentProviderType, message: string): AdapterQuotaProbeResult;
|
|
4
|
+
export declare function resolveMergedEnv(runtime?: AgentRuntimeConfig): NodeJS.ProcessEnv;
|
|
@@ -19,6 +19,8 @@ export interface AgentWorkItem {
|
|
|
19
19
|
fileSizeBytes: number;
|
|
20
20
|
relativePath: string;
|
|
21
21
|
absolutePath: string;
|
|
22
|
+
/** API-relative path, e.g. `/issues/{issueId}/attachments/{id}/download` */
|
|
23
|
+
downloadPath?: string;
|
|
22
24
|
}>;
|
|
23
25
|
}
|
|
24
26
|
export interface AgentState {
|
|
@@ -36,11 +38,19 @@ export interface AgentMemoryContext {
|
|
|
36
38
|
durableFacts: string[];
|
|
37
39
|
dailyNotes: string[];
|
|
38
40
|
}
|
|
41
|
+
export type HeartbeatPromptMode = "full" | "compact";
|
|
39
42
|
export interface HeartbeatContext {
|
|
40
43
|
companyId: string;
|
|
41
44
|
agentId: string;
|
|
42
45
|
providerType: AgentProviderType;
|
|
43
46
|
heartbeatRunId: string;
|
|
47
|
+
/** Controls how much issue/memory text is inlined in the heartbeat prompt. Default when omitted: full. */
|
|
48
|
+
promptMode?: HeartbeatPromptMode;
|
|
49
|
+
/**
|
|
50
|
+
* When true, emit a minimal idle prompt (no assigned work). Set by the API when
|
|
51
|
+
* `BOPO_HEARTBEAT_IDLE_POLICY=micro_prompt` and there are no work items.
|
|
52
|
+
*/
|
|
53
|
+
idleMicroPrompt?: boolean;
|
|
44
54
|
company: {
|
|
45
55
|
name: string;
|
|
46
56
|
mission?: string | null;
|
|
@@ -168,6 +168,72 @@ export declare const IssueAttachmentSchema: z.ZodObject<{
|
|
|
168
168
|
createdAt: z.ZodString;
|
|
169
169
|
}, z.core.$strip>;
|
|
170
170
|
export type IssueAttachment = z.infer<typeof IssueAttachmentSchema>;
|
|
171
|
+
/** Single-issue GET: core issue fields plus attachment metadata and API download paths. */
|
|
172
|
+
export declare const IssueAttachmentWithDownloadSchema: z.ZodObject<{
|
|
173
|
+
id: z.ZodString;
|
|
174
|
+
companyId: z.ZodString;
|
|
175
|
+
issueId: z.ZodString;
|
|
176
|
+
projectId: z.ZodString;
|
|
177
|
+
fileName: z.ZodString;
|
|
178
|
+
mimeType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
179
|
+
fileSizeBytes: z.ZodNumber;
|
|
180
|
+
relativePath: z.ZodString;
|
|
181
|
+
uploadedByActorType: z.ZodEnum<{
|
|
182
|
+
human: "human";
|
|
183
|
+
agent: "agent";
|
|
184
|
+
system: "system";
|
|
185
|
+
}>;
|
|
186
|
+
uploadedByActorId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
187
|
+
createdAt: z.ZodString;
|
|
188
|
+
downloadPath: z.ZodString;
|
|
189
|
+
}, z.core.$strip>;
|
|
190
|
+
export declare const IssueDetailSchema: z.ZodObject<{
|
|
191
|
+
id: z.ZodString;
|
|
192
|
+
companyId: z.ZodString;
|
|
193
|
+
projectId: z.ZodString;
|
|
194
|
+
parentIssueId: z.ZodNullable<z.ZodString>;
|
|
195
|
+
title: z.ZodString;
|
|
196
|
+
body: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
197
|
+
status: z.ZodEnum<{
|
|
198
|
+
blocked: "blocked";
|
|
199
|
+
todo: "todo";
|
|
200
|
+
in_progress: "in_progress";
|
|
201
|
+
in_review: "in_review";
|
|
202
|
+
done: "done";
|
|
203
|
+
canceled: "canceled";
|
|
204
|
+
}>;
|
|
205
|
+
priority: z.ZodEnum<{
|
|
206
|
+
none: "none";
|
|
207
|
+
low: "low";
|
|
208
|
+
medium: "medium";
|
|
209
|
+
high: "high";
|
|
210
|
+
urgent: "urgent";
|
|
211
|
+
}>;
|
|
212
|
+
assigneeAgentId: z.ZodNullable<z.ZodString>;
|
|
213
|
+
labels: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
214
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
215
|
+
createdAt: z.ZodString;
|
|
216
|
+
updatedAt: z.ZodString;
|
|
217
|
+
attachments: z.ZodArray<z.ZodObject<{
|
|
218
|
+
id: z.ZodString;
|
|
219
|
+
companyId: z.ZodString;
|
|
220
|
+
issueId: z.ZodString;
|
|
221
|
+
projectId: z.ZodString;
|
|
222
|
+
fileName: z.ZodString;
|
|
223
|
+
mimeType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
224
|
+
fileSizeBytes: z.ZodNumber;
|
|
225
|
+
relativePath: z.ZodString;
|
|
226
|
+
uploadedByActorType: z.ZodEnum<{
|
|
227
|
+
human: "human";
|
|
228
|
+
agent: "agent";
|
|
229
|
+
system: "system";
|
|
230
|
+
}>;
|
|
231
|
+
uploadedByActorId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
232
|
+
createdAt: z.ZodString;
|
|
233
|
+
downloadPath: z.ZodString;
|
|
234
|
+
}, z.core.$strip>>;
|
|
235
|
+
}, z.core.$strip>;
|
|
236
|
+
export type IssueDetail = z.infer<typeof IssueDetailSchema>;
|
|
171
237
|
export declare const IssueCommentRecipientSchema: z.ZodObject<{
|
|
172
238
|
recipientType: z.ZodEnum<{
|
|
173
239
|
agent: "agent";
|
|
@@ -1618,6 +1684,9 @@ export declare const ControlPlaneRuntimeEnvSchema: z.ZodObject<{
|
|
|
1618
1684
|
BOPODEV_WAKE_REASON: z.ZodOptional<z.ZodString>;
|
|
1619
1685
|
BOPODEV_WAKE_COMMENT_ID: z.ZodOptional<z.ZodString>;
|
|
1620
1686
|
BOPODEV_LINKED_ISSUE_IDS: z.ZodOptional<z.ZodString>;
|
|
1687
|
+
BOPODEV_COMPANY_WORKSPACE_ROOT: z.ZodOptional<z.ZodString>;
|
|
1688
|
+
BOPODEV_AGENT_HOME: z.ZodOptional<z.ZodString>;
|
|
1689
|
+
BOPODEV_AGENT_OPERATING_DIR: z.ZodOptional<z.ZodString>;
|
|
1621
1690
|
}, z.core.$strip>;
|
|
1622
1691
|
export type ControlPlaneRuntimeEnv = z.infer<typeof ControlPlaneRuntimeEnvSchema>;
|
|
1623
1692
|
export declare const ExecutionOutcomeKindSchema: z.ZodEnum<{
|
|
@@ -1732,6 +1801,7 @@ export declare const RunArtifactSchema: z.ZodObject<{
|
|
|
1732
1801
|
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1733
1802
|
relativePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1734
1803
|
absolutePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1804
|
+
verifiedOnDisk: z.ZodOptional<z.ZodBoolean>;
|
|
1735
1805
|
}, z.core.$strip>;
|
|
1736
1806
|
export type RunArtifact = z.infer<typeof RunArtifactSchema>;
|
|
1737
1807
|
export declare const RunCostSummarySchema: z.ZodObject<{
|
|
@@ -1808,6 +1878,7 @@ export declare const RunCompletionReportSchema: z.ZodObject<{
|
|
|
1808
1878
|
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1809
1879
|
relativePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1810
1880
|
absolutePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1881
|
+
verifiedOnDisk: z.ZodOptional<z.ZodBoolean>;
|
|
1811
1882
|
}, z.core.$strip>>>;
|
|
1812
1883
|
blockers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
1813
1884
|
nextAction: z.ZodString;
|
|
@@ -4539,6 +4610,7 @@ export declare const HeartbeatRunDetailSchema: z.ZodObject<{
|
|
|
4539
4610
|
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
4540
4611
|
relativePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
4541
4612
|
absolutePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
4613
|
+
verifiedOnDisk: z.ZodOptional<z.ZodBoolean>;
|
|
4542
4614
|
}, z.core.$strip>>>;
|
|
4543
4615
|
blockers: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
4544
4616
|
nextAction: z.ZodString;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bopodev-agent-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
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.27"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc -p tsconfig.json --emitDeclarationOnly",
|
package/src/adapters.ts
CHANGED
|
@@ -8,7 +8,8 @@ import type {
|
|
|
8
8
|
AgentAdapter,
|
|
9
9
|
AgentProviderType,
|
|
10
10
|
AgentRuntimeConfig,
|
|
11
|
-
HeartbeatContext
|
|
11
|
+
HeartbeatContext,
|
|
12
|
+
HeartbeatPromptMode
|
|
12
13
|
} from "./types";
|
|
13
14
|
import { ExecutionOutcomeSchema, type ExecutionOutcome } from "bopodev-contracts";
|
|
14
15
|
import {
|
|
@@ -2616,8 +2617,66 @@ export function toEnvironmentStatus(checks: AdapterEnvironmentCheck[]): "pass" |
|
|
|
2616
2617
|
return "pass";
|
|
2617
2618
|
}
|
|
2618
2619
|
|
|
2620
|
+
function resolveHeartbeatPromptModeForPrompt(context: HeartbeatContext): HeartbeatPromptMode {
|
|
2621
|
+
return context.promptMode === "compact" ? "compact" : "full";
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
/** Max chars per memory section (tacit notes, durable facts block, daily notes block). Env overrides; compact defaults to 8000. */
|
|
2625
|
+
function resolveMemorySectionMaxChars(mode: HeartbeatPromptMode): number | null {
|
|
2626
|
+
const raw = process.env.BOPO_HEARTBEAT_PROMPT_MEMORY_MAX_CHARS?.trim();
|
|
2627
|
+
if (raw) {
|
|
2628
|
+
const n = Number.parseInt(raw, 10);
|
|
2629
|
+
if (Number.isFinite(n) && n > 0) {
|
|
2630
|
+
return n;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
if (mode === "compact") {
|
|
2634
|
+
return 8000;
|
|
2635
|
+
}
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
function clipPromptText(text: string, max: number | null): string {
|
|
2640
|
+
if (!max || text.length <= max) {
|
|
2641
|
+
return text;
|
|
2642
|
+
}
|
|
2643
|
+
return `${text.slice(0, max)}\n…(truncated for prompt size)`;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
const HEARTBEAT_JSON_SCHEMA_FOOTER = `At the end of your response, output exactly one JSON object on a single line and nothing else. Use this exact schema:
|
|
2647
|
+
{"employee_comment":"markdown update to the manager","results":["short concrete outcome"],"errors":[],"artifacts":[{"kind":"file","path":"relative/path"}]}`;
|
|
2648
|
+
|
|
2649
|
+
function buildIdleMicroPrompt(context: HeartbeatContext): string {
|
|
2650
|
+
const bootstrapPrompt = context.runtime?.bootstrapPrompt?.trim();
|
|
2651
|
+
return `${bootstrapPrompt ? `${bootstrapPrompt}\n\n` : ""}Idle heartbeat (micro prompt): agent ${context.agentId} (${context.agent.name}) has no assigned issues this run. Summarize readiness in \`employee_comment\`; leave \`results\` empty unless you completed verifiable work. Use \`BOPODEV_*\` for control-plane API calls when needed.
|
|
2652
|
+
|
|
2653
|
+
${HEARTBEAT_JSON_SCHEMA_FOOTER}
|
|
2654
|
+
`;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
function formatAttachmentLine(
|
|
2658
|
+
attachment: NonNullable<HeartbeatContext["workItems"][number]["attachments"]>[number],
|
|
2659
|
+
mode: HeartbeatPromptMode,
|
|
2660
|
+
apiBase: string
|
|
2661
|
+
): string {
|
|
2662
|
+
const base = apiBase.replace(/\/$/, "");
|
|
2663
|
+
const apiUrl = attachment.downloadPath ? `${base}${attachment.downloadPath}` : null;
|
|
2664
|
+
if (mode === "compact" && apiUrl) {
|
|
2665
|
+
return ` - ${attachment.fileName} | api: ${apiUrl} | path: ${attachment.absolutePath} | relative: ${attachment.relativePath}`;
|
|
2666
|
+
}
|
|
2667
|
+
const apiSuffix = apiUrl ? ` | api: ${apiUrl}` : "";
|
|
2668
|
+
return ` - ${attachment.fileName} | path: ${attachment.absolutePath} | relative: ${attachment.relativePath}${apiSuffix}`;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2619
2671
|
export function createPrompt(context: HeartbeatContext) {
|
|
2672
|
+
const isCommentOrderRunEarly = context.wakeContext?.reason === "issue_comment_recipient";
|
|
2673
|
+
if (context.idleMicroPrompt && context.workItems.length === 0 && !isCommentOrderRunEarly) {
|
|
2674
|
+
return buildIdleMicroPrompt(context);
|
|
2675
|
+
}
|
|
2620
2676
|
const bootstrapPrompt = context.runtime?.bootstrapPrompt?.trim();
|
|
2677
|
+
const promptMode = resolveHeartbeatPromptModeForPrompt(context);
|
|
2678
|
+
const isCompact = promptMode === "compact";
|
|
2679
|
+
const memoryCap = resolveMemorySectionMaxChars(promptMode);
|
|
2621
2680
|
const companyGoals = context.goalContext?.companyGoals.length
|
|
2622
2681
|
? context.goalContext.companyGoals.map((goal) => `- ${goal}`).join("\n")
|
|
2623
2682
|
: "- No active company goals";
|
|
@@ -2628,6 +2687,8 @@ export function createPrompt(context: HeartbeatContext) {
|
|
|
2628
2687
|
? context.goalContext.agentGoals.map((goal) => `- ${goal}`).join("\n")
|
|
2629
2688
|
: "- No active agent goals";
|
|
2630
2689
|
const isCommentOrderRun = context.wakeContext?.reason === "issue_comment_recipient";
|
|
2690
|
+
const controlPlaneApiBaseUrl =
|
|
2691
|
+
context.runtime?.env?.BOPODEV_API_BASE_URL?.trim() || context.runtime?.env?.BOPODEV_API_URL?.trim() || "";
|
|
2631
2692
|
const workItems = context.workItems.length
|
|
2632
2693
|
? context.workItems
|
|
2633
2694
|
.map((item) =>
|
|
@@ -2638,14 +2699,18 @@ export function createPrompt(context: HeartbeatContext) {
|
|
|
2638
2699
|
item.childIssueIds?.length ? ` Sub-issues: ${item.childIssueIds.join(", ")}` : null,
|
|
2639
2700
|
item.status ? ` Status: ${item.status}` : null,
|
|
2640
2701
|
item.priority ? ` Priority: ${item.priority}` : null,
|
|
2641
|
-
|
|
2702
|
+
isCompact
|
|
2703
|
+
? ` Body: (omitted — fetch with GET ${controlPlaneApiBaseUrl || "$BOPODEV_API_BASE_URL"}/issues/${item.issueId})`
|
|
2704
|
+
: item.body
|
|
2705
|
+
? ` Body: ${item.body}`
|
|
2706
|
+
: null,
|
|
2642
2707
|
item.labels?.length ? ` Labels: ${item.labels.join(", ")}` : null,
|
|
2643
2708
|
item.tags?.length ? ` Tags: ${item.tags.join(", ")}` : null,
|
|
2644
2709
|
item.attachments?.length
|
|
2645
2710
|
? [
|
|
2646
2711
|
" Attachments:",
|
|
2647
2712
|
...item.attachments.map((attachment) =>
|
|
2648
|
-
|
|
2713
|
+
formatAttachmentLine(attachment, promptMode, controlPlaneApiBaseUrl || "http://127.0.0.1:4020")
|
|
2649
2714
|
)
|
|
2650
2715
|
].join("\n")
|
|
2651
2716
|
: null
|
|
@@ -2675,22 +2740,36 @@ export function createPrompt(context: HeartbeatContext) {
|
|
|
2675
2740
|
].join("\n")
|
|
2676
2741
|
: "";
|
|
2677
2742
|
const memoryContext = context.memoryContext;
|
|
2678
|
-
const
|
|
2743
|
+
const memoryTacitNotesRaw = memoryContext?.tacitNotes?.trim()
|
|
2679
2744
|
? memoryContext.tacitNotes.trim()
|
|
2680
2745
|
: "No tacit notes were recorded yet.";
|
|
2681
|
-
const
|
|
2746
|
+
const memoryTacitNotes = clipPromptText(memoryTacitNotesRaw, memoryCap);
|
|
2747
|
+
const memoryDurableFactsRaw =
|
|
2682
2748
|
memoryContext?.durableFacts && memoryContext.durableFacts.length > 0
|
|
2683
2749
|
? memoryContext.durableFacts.map((fact) => `- ${fact}`).join("\n")
|
|
2684
2750
|
: "- No durable facts available.";
|
|
2685
|
-
const
|
|
2751
|
+
const memoryDurableFacts = clipPromptText(memoryDurableFactsRaw, memoryCap);
|
|
2752
|
+
const memoryDailyNotesRaw =
|
|
2686
2753
|
memoryContext?.dailyNotes && memoryContext.dailyNotes.length > 0
|
|
2687
2754
|
? memoryContext.dailyNotes.map((note) => `- ${note}`).join("\n")
|
|
2688
2755
|
: "- No recent daily notes.";
|
|
2689
|
-
const
|
|
2690
|
-
context.runtime?.env?.BOPODEV_API_BASE_URL?.trim() || context.runtime?.env?.BOPODEV_API_URL?.trim() || "";
|
|
2756
|
+
const memoryDailyNotes = clipPromptText(memoryDailyNotesRaw, memoryCap);
|
|
2691
2757
|
const hasControlPlaneHeaders = Boolean(context.runtime?.env?.BOPODEV_REQUEST_HEADERS_JSON?.trim());
|
|
2692
2758
|
const safeControlPlaneCurl =
|
|
2693
2759
|
'curl -sS -H "x-company-id: $BOPODEV_COMPANY_ID" -H "x-actor-type: $BOPODEV_ACTOR_TYPE" -H "x-actor-id: $BOPODEV_ACTOR_ID" -H "x-actor-companies: $BOPODEV_ACTOR_COMPANIES" -H "x-actor-permissions: $BOPODEV_ACTOR_PERMISSIONS" "$BOPODEV_API_BASE_URL/agents"';
|
|
2760
|
+
const compactHydration =
|
|
2761
|
+
isCompact &&
|
|
2762
|
+
(context.workItems.length > 0 ||
|
|
2763
|
+
(context.wakeContext?.issueIds && context.wakeContext.issueIds.length > 0))
|
|
2764
|
+
? [
|
|
2765
|
+
"Context hydration (compact prompt mode):",
|
|
2766
|
+
"- Load full issue description and attachment list (with `downloadPath` for each file) via GET `$BOPODEV_API_BASE_URL`/issues/{issueId} before substantive work.",
|
|
2767
|
+
"- Use the same actor headers as in the control-plane section below.",
|
|
2768
|
+
`- Example: curl -sS -H "x-company-id: $BOPODEV_COMPANY_ID" -H "x-actor-type: $BOPODEV_ACTOR_TYPE" -H "x-actor-id: $BOPODEV_ACTOR_ID" -H "x-actor-companies: $BOPODEV_ACTOR_COMPANIES" -H "x-actor-permissions: $BOPODEV_ACTOR_PERMISSIONS" "${controlPlaneApiBaseUrl || "$BOPODEV_API_BASE_URL"}/issues/<issueId>"`,
|
|
2769
|
+
""
|
|
2770
|
+
].join("\n")
|
|
2771
|
+
: "";
|
|
2772
|
+
|
|
2694
2773
|
const controlPlaneDirectives = [
|
|
2695
2774
|
"Control-plane API directives:",
|
|
2696
2775
|
controlPlaneApiBaseUrl
|
|
@@ -2742,7 +2821,7 @@ export function createPrompt(context: HeartbeatContext) {
|
|
|
2742
2821
|
|
|
2743
2822
|
return `${bootstrapPrompt ? `${bootstrapPrompt}\n\n` : ""}You are ${context.agent.name} (${context.agent.role}), agent ${context.agentId}.
|
|
2744
2823
|
Heartbeat run ${context.heartbeatRunId}.
|
|
2745
|
-
|
|
2824
|
+
${isCompact ? "Prompt profile: compact (issue bodies are not inlined—use GET /issues/:id to hydrate).\n" : ""}
|
|
2746
2825
|
Company:
|
|
2747
2826
|
- Name: ${context.company.name}
|
|
2748
2827
|
- Mission: ${context.company.mission ?? "No mission set"}
|
|
@@ -2755,7 +2834,7 @@ ${projectGoals}
|
|
|
2755
2834
|
Agent goals:
|
|
2756
2835
|
${agentGoals}
|
|
2757
2836
|
|
|
2758
|
-
${isCommentOrderRun ? "Linked issue context (read-only):" : "Assigned issues:"}
|
|
2837
|
+
${compactHydration}${isCommentOrderRun ? "Linked issue context (read-only):" : "Assigned issues:"}
|
|
2759
2838
|
${workItems}
|
|
2760
2839
|
|
|
2761
2840
|
${wakeContextLines}
|
|
@@ -2775,8 +2854,7 @@ ${executionDirectives}
|
|
|
2775
2854
|
|
|
2776
2855
|
${controlPlaneDirectives}
|
|
2777
2856
|
|
|
2778
|
-
|
|
2779
|
-
{"employee_comment":"markdown update to the manager","results":["short concrete outcome"],"errors":[],"artifacts":[{"kind":"file","path":"relative/path"}]}
|
|
2857
|
+
${HEARTBEAT_JSON_SCHEMA_FOOTER}
|
|
2780
2858
|
`;
|
|
2781
2859
|
}
|
|
2782
2860
|
|
package/src/runtime.ts
CHANGED
|
@@ -155,6 +155,16 @@ function providerDefaultArgs(provider: "claude_code" | "codex", config?: AgentRu
|
|
|
155
155
|
return ["exec", "--full-auto", "--skip-git-repo-check"];
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function shouldPassCodexReasoningEffortFlag(config?: AgentRuntimeConfig): boolean {
|
|
159
|
+
const raw = (
|
|
160
|
+
config?.env?.BOPO_CODEX_PASS_REASONING_EFFORT ?? process.env.BOPO_CODEX_PASS_REASONING_EFFORT ??
|
|
161
|
+
""
|
|
162
|
+
)
|
|
163
|
+
.trim()
|
|
164
|
+
.toLowerCase();
|
|
165
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
166
|
+
}
|
|
167
|
+
|
|
158
168
|
function providerConfigArgs(provider: "claude_code" | "codex", config?: AgentRuntimeConfig) {
|
|
159
169
|
const args: string[] = [];
|
|
160
170
|
if (provider === "codex") {
|
|
@@ -165,7 +175,12 @@ function providerConfigArgs(provider: "claude_code" | "codex", config?: AgentRun
|
|
|
165
175
|
// Codex JSON events carry usage metrics we need for reliable cost accounting.
|
|
166
176
|
args.push("--json");
|
|
167
177
|
}
|
|
168
|
-
|
|
178
|
+
// Many `codex` builds reject `--reasoning-effort`; only pass when explicitly enabled.
|
|
179
|
+
if (
|
|
180
|
+
shouldPassCodexReasoningEffortFlag(config) &&
|
|
181
|
+
config?.thinkingEffort &&
|
|
182
|
+
config.thinkingEffort !== "auto"
|
|
183
|
+
) {
|
|
169
184
|
args.push("--reasoning-effort", config.thinkingEffort);
|
|
170
185
|
}
|
|
171
186
|
if (config?.runPolicy?.allowWebSearch) {
|
package/src/types.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface AgentWorkItem {
|
|
|
21
21
|
fileSizeBytes: number;
|
|
22
22
|
relativePath: string;
|
|
23
23
|
absolutePath: string;
|
|
24
|
+
/** API-relative path, e.g. `/issues/{issueId}/attachments/{id}/download` */
|
|
25
|
+
downloadPath?: string;
|
|
24
26
|
}>;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -41,11 +43,20 @@ export interface AgentMemoryContext {
|
|
|
41
43
|
dailyNotes: string[];
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
export type HeartbeatPromptMode = "full" | "compact";
|
|
47
|
+
|
|
44
48
|
export interface HeartbeatContext {
|
|
45
49
|
companyId: string;
|
|
46
50
|
agentId: string;
|
|
47
51
|
providerType: AgentProviderType;
|
|
48
52
|
heartbeatRunId: string;
|
|
53
|
+
/** Controls how much issue/memory text is inlined in the heartbeat prompt. Default when omitted: full. */
|
|
54
|
+
promptMode?: HeartbeatPromptMode;
|
|
55
|
+
/**
|
|
56
|
+
* When true, emit a minimal idle prompt (no assigned work). Set by the API when
|
|
57
|
+
* `BOPO_HEARTBEAT_IDLE_POLICY=micro_prompt` and there are no work items.
|
|
58
|
+
*/
|
|
59
|
+
idleMicroPrompt?: boolean;
|
|
49
60
|
company: {
|
|
50
61
|
name: string;
|
|
51
62
|
mission?: string | null;
|