bopodev-agent-sdk 0.1.26 → 0.1.28

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.26 build /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
3
+ > bopodev-agent-sdk@0.1.28 build /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopodev/packages/agent-sdk
4
4
  > tsc -p tsconfig.json --emitDeclarationOnly
5
5
 
@@ -1,4 +1,4 @@
1
1
 
2
- > bopodev-agent-sdk@0.1.24 typecheck /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
2
+ > bopodev-agent-sdk@0.1.20 typecheck /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopodev/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`
@@ -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;
@@ -4351,6 +4422,15 @@ export declare const AuditEventSchema: z.ZodObject<{
4351
4422
  payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
4352
4423
  createdAt: z.ZodString;
4353
4424
  }, z.core.$strip>;
4425
+ export declare const HeartbeatRunTypeSchema: z.ZodEnum<{
4426
+ failed: "failed";
4427
+ running: "running";
4428
+ no_assigned_work: "no_assigned_work";
4429
+ work: "work";
4430
+ budget_skip: "budget_skip";
4431
+ overlap_skip: "overlap_skip";
4432
+ other_skip: "other_skip";
4433
+ }>;
4354
4434
  export declare const HeartbeatRunSchema: z.ZodObject<{
4355
4435
  id: z.ZodString;
4356
4436
  companyId: z.ZodString;
@@ -4369,6 +4449,15 @@ export declare const HeartbeatRunSchema: z.ZodObject<{
4369
4449
  startedAt: z.ZodString;
4370
4450
  finishedAt: z.ZodNullable<z.ZodString>;
4371
4451
  message: z.ZodOptional<z.ZodString>;
4452
+ runType: z.ZodOptional<z.ZodEnum<{
4453
+ failed: "failed";
4454
+ running: "running";
4455
+ no_assigned_work: "no_assigned_work";
4456
+ work: "work";
4457
+ budget_skip: "budget_skip";
4458
+ overlap_skip: "overlap_skip";
4459
+ other_skip: "other_skip";
4460
+ }>>;
4372
4461
  }, z.core.$strip>;
4373
4462
  export declare const HeartbeatRunMessageSchema: z.ZodObject<{
4374
4463
  id: z.ZodString;
@@ -4457,6 +4546,15 @@ export declare const HeartbeatRunDetailSchema: z.ZodObject<{
4457
4546
  startedAt: z.ZodString;
4458
4547
  finishedAt: z.ZodNullable<z.ZodString>;
4459
4548
  message: z.ZodOptional<z.ZodString>;
4549
+ runType: z.ZodOptional<z.ZodEnum<{
4550
+ failed: "failed";
4551
+ running: "running";
4552
+ no_assigned_work: "no_assigned_work";
4553
+ work: "work";
4554
+ budget_skip: "budget_skip";
4555
+ overlap_skip: "overlap_skip";
4556
+ other_skip: "other_skip";
4557
+ }>>;
4460
4558
  }, z.core.$strip>;
4461
4559
  details: z.ZodNullable<z.ZodObject<{
4462
4560
  status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -4539,6 +4637,7 @@ export declare const HeartbeatRunDetailSchema: z.ZodObject<{
4539
4637
  label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
4540
4638
  relativePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
4541
4639
  absolutePath: z.ZodOptional<z.ZodNullable<z.ZodString>>;
4640
+ verifiedOnDisk: z.ZodOptional<z.ZodBoolean>;
4542
4641
  }, z.core.$strip>>>;
4543
4642
  blockers: z.ZodDefault<z.ZodArray<z.ZodString>>;
4544
4643
  nextAction: z.ZodString;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "bopodev-agent-sdk",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
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.26"
9
+ "bopodev-contracts": "0.1.28"
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
- item.body ? ` Body: ${item.body}` : null,
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
- ` - ${attachment.fileName} | path: ${attachment.absolutePath} | relative: ${attachment.relativePath}`
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 memoryTacitNotes = memoryContext?.tacitNotes?.trim()
2743
+ const memoryTacitNotesRaw = memoryContext?.tacitNotes?.trim()
2679
2744
  ? memoryContext.tacitNotes.trim()
2680
2745
  : "No tacit notes were recorded yet.";
2681
- const memoryDurableFacts =
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 memoryDailyNotes =
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 controlPlaneApiBaseUrl =
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
- At the end of your response, output exactly one JSON object on a single line and nothing else. Use this exact schema:
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
- if (config?.thinkingEffort && config.thinkingEffort !== "auto") {
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;
@@ -1,4 +0,0 @@
1
-
2
- > bopodev-agent-sdk@0.1.15 lint /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
3
- > tsc -p tsconfig.json --noEmit
4
-