@loucompanion/forge-bridge 0.1.3-dev.b3bce6ba65a6 → 0.1.3-dev.ba7d36726eda

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +78 -0
  2. package/dist/bridge/bridge.base.d.ts +5 -1
  3. package/dist/bridge/bridge.service.d.ts +52 -3
  4. package/dist/bridge/bridge.service.js +776 -19
  5. package/dist/bridge/bridge.types.d.ts +131 -1
  6. package/dist/cleanup/cleanup.service.d.ts +13 -2
  7. package/dist/cleanup/cleanup.service.js +435 -3
  8. package/dist/cleanup/cleanup.types.d.ts +10 -0
  9. package/dist/cleanup/index.d.ts +1 -1
  10. package/dist/cleanup/index.js +1 -1
  11. package/dist/cleanup/internals.d.ts +2 -2
  12. package/dist/cleanup/internals.js +1 -1
  13. package/dist/cli/cli.help.js +46 -0
  14. package/dist/cli/cli.service.d.ts +42 -2
  15. package/dist/cli/cli.service.js +350 -23
  16. package/dist/cli/local-config.d.ts +4 -0
  17. package/dist/cli/local-config.js +39 -0
  18. package/dist/runtime/attempt-node-runtime-launcher.d.ts +15 -0
  19. package/dist/runtime/attempt-node-runtime-launcher.js +50 -0
  20. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  21. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  22. package/dist/runtime/attempt-process-supervisor.d.ts +74 -0
  23. package/dist/runtime/attempt-process-supervisor.js +290 -0
  24. package/dist/runtime/host-listener-inspector.d.ts +28 -0
  25. package/dist/runtime/host-listener-inspector.js +185 -0
  26. package/dist/runtime/host-port-broker.d.ts +76 -0
  27. package/dist/runtime/host-port-broker.js +333 -0
  28. package/dist/runtime/host-runtime-contract.d.ts +39 -0
  29. package/dist/runtime/host-runtime-contract.js +295 -0
  30. package/dist/runtime/host-runtime-lease.d.ts +122 -0
  31. package/dist/runtime/host-runtime-lease.js +1241 -0
  32. package/dist/runtime/host-runtime-promotion.d.ts +21 -0
  33. package/dist/runtime/host-runtime-promotion.js +128 -0
  34. package/dist/runtime/node-host-runtime-adapter.d.ts +44 -0
  35. package/dist/runtime/node-host-runtime-adapter.js +205 -0
  36. package/dist/runtime/playwright-host-runtime-adapter.d.ts +20 -0
  37. package/dist/runtime/playwright-host-runtime-adapter.js +136 -0
  38. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  39. package/dist/runtime/runtime-isolation-lease.js +158 -0
  40. package/dist/sandbox/command-runner.d.ts +4 -0
  41. package/dist/sandbox/command-runner.js +47 -0
  42. package/dist/sandbox/contract.d.ts +5 -0
  43. package/dist/sandbox/contract.js +276 -0
  44. package/dist/sandbox/event-budget.d.ts +11 -0
  45. package/dist/sandbox/event-budget.js +27 -0
  46. package/dist/sandbox/image-policy.d.ts +35 -0
  47. package/dist/sandbox/image-policy.js +247 -0
  48. package/dist/sandbox/index.d.ts +5 -0
  49. package/dist/sandbox/index.js +5 -0
  50. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  51. package/dist/sandbox/lease-watchdog.js +67 -0
  52. package/dist/sandbox/preflight.d.ts +45 -0
  53. package/dist/sandbox/preflight.js +300 -0
  54. package/dist/sandbox/process.d.ts +20 -0
  55. package/dist/sandbox/process.js +209 -0
  56. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  57. package/dist/sandbox/provider-auth/codex.js +212 -0
  58. package/dist/sandbox/repository-lock.d.ts +5 -0
  59. package/dist/sandbox/repository-lock.js +119 -0
  60. package/dist/sandbox/runtime.d.ts +297 -0
  61. package/dist/sandbox/runtime.js +2338 -0
  62. package/dist/sandbox/sandbox.types.d.ts +86 -0
  63. package/dist/sandbox/sandbox.types.js +1 -0
  64. package/dist/sandbox/smoke.d.ts +76 -0
  65. package/dist/sandbox/smoke.js +380 -0
  66. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  67. package/dist/sandbox/worker-supervisor.js +36 -0
  68. package/dist/sandbox/worker.d.ts +1 -0
  69. package/dist/sandbox/worker.js +225 -0
  70. package/dist/sandbox/workspace.service.d.ts +13 -0
  71. package/dist/sandbox/workspace.service.js +67 -0
  72. package/dist/session/phase-completion.artifacts.js +23 -2
  73. package/dist/session/phase-completion.helpers.js +6 -1
  74. package/dist/session/phase-completion.runtime.js +3 -1
  75. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +1 -1
  76. package/dist/session/provider-cli/provider-cli.artifacts.js +28 -4
  77. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  78. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
  79. package/dist/session/provider-cli/provider-cli.deterministic.js +20 -9
  80. package/dist/session/provider-cli/provider-cli.process.d.ts +3 -0
  81. package/dist/session/provider-cli/provider-cli.process.js +65 -14
  82. package/dist/session/provider-cli/provider-cli.prompts.d.ts +14 -2
  83. package/dist/session/provider-cli/provider-cli.prompts.js +99 -15
  84. package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
  85. package/dist/session/provider-cli/provider-cli.runtime.js +3 -2
  86. package/dist/session/provider-cli/provider-cli.service.d.ts +4 -1
  87. package/dist/session/provider-cli/provider-cli.service.js +167 -28
  88. package/dist/session/provider-cli/provider-cli.types.d.ts +2 -0
  89. package/dist/session/session-redaction.d.ts +3 -0
  90. package/dist/session/session-redaction.js +23 -0
  91. package/dist/session/session.base.d.ts +4 -0
  92. package/dist/session/session.service.d.ts +6 -1
  93. package/dist/session/session.service.js +184 -38
  94. package/dist/session/session.types.d.ts +58 -1
  95. package/dist/session/session.types.js +172 -2
  96. package/dist/shared/path/path.js +3 -1
  97. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  98. package/dist/shared/process/host-process-recovery.js +78 -0
  99. package/dist/shared/process/host-process-supervisor.d.ts +40 -0
  100. package/dist/shared/process/host-process-supervisor.js +195 -0
  101. package/dist/shared/provider-capabilities.d.ts +1 -0
  102. package/dist/shared/provider-capabilities.js +3 -0
  103. package/dist/shared/redaction/redaction.js +6 -1
  104. package/dist/version.d.ts +1 -1
  105. package/dist/version.js +1 -1
  106. package/dist/worktree/index.d.ts +1 -0
  107. package/dist/worktree/index.js +1 -0
  108. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  109. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  110. package/dist/worktree/worktree-paths.d.ts +4 -0
  111. package/dist/worktree/worktree-paths.js +30 -0
  112. package/dist/worktree/worktree.base.d.ts +3 -0
  113. package/dist/worktree/worktree.service.d.ts +6 -2
  114. package/dist/worktree/worktree.service.js +60 -43
  115. package/dist/worktree/worktree.types.d.ts +3 -1
  116. package/package.json +6 -3
  117. package/runtime/windows-job-supervisor.ps1 +287 -0
  118. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -5,14 +5,56 @@ import { buildSpecBody, selectLaneCommand } from "../../worktree/artifact-inject
5
5
  import { completionPhaseFromDispatch, prepareCompletionToolRuntime, readCompletionArtifact, usesDebugCycleCompletion, usesDevelopCycleCompletion } from "../phase-completion.js";
6
6
  import { cleanupCompletionTools, commitWorktreeChanges, completionPhaseFromArtifact, emitArchitectStructuredEvents, emitCompletionArtifactRecorded, emitDecomposeGateArtifacts, emitDraftGateArtifacts, emitPhaseCompletionEvent, emitPhaseGateEvents, emitQaArtifactRecorded, emitQaStructuredEvents, enforceQaBrowserLaunchTargetGate, LiveArchitectArtifactEmitter, LiveCompletionArtifactEmitter, LiveQaArtifactEmitter, readCompletionArtifactsForRun, readQaCompletionArtifactBundle, summarizeStderr, waitForCompletionArtifact } from "./provider-cli.artifacts.js";
7
7
  import { DeterministicProviderCliService } from "./provider-cli.deterministic.js";
8
- import { buildArchitectPrompt, buildConflictResolvePrompt, buildDebugCyclePrompt, buildDevelopCyclePrompt, buildDraftPrompt, buildDraftSpec, buildImplementationPrompt, buildProviderCliCommand, durableDraftInput, buildPullRequestCommentsPrompt, buildQaPrompt, formatProviderCliCommandForCheckpoint, isDecomposeDraftDispatch, isPullRequestCommentsDispatch } from "./provider-cli.prompts.js";
8
+ import { buildArchitectPrompt, buildConflictResolvePrompt, buildDebugCyclePrompt, buildDevelopCyclePrompt, buildDraftPrompt, buildDraftSpec, buildImplementationPrompt, buildProviderCliPrompt, buildProviderCliCommand, durableDraftInput, buildPullRequestCheckRepairPrompt, buildPullRequestCommentsPrompt, buildQaPrompt, formatProviderCliCommandForCheckpoint, isDecomposeDraftDispatch, isPullRequestCheckRepairDispatch, isPullRequestCommentsDispatch } from "./provider-cli.prompts.js";
9
9
  import { StreamingProviderProcessRunner } from "./provider-cli.process.js";
10
10
  import { emitProviderUsageEvent, providerProcessEnv } from "./provider-cli.runtime.js";
11
11
  import { clip, createProviderOutputState, parseJsonLine } from "./provider-output-state.js";
12
12
  export { DeterministicProviderCliService } from "./provider-cli.deterministic.js";
13
13
  export { buildProviderCliCommand, codexSandboxModeForPlatform, formatProviderCliCommand, formatProviderCliCommandForCheckpoint } from "./provider-cli.prompts.js";
14
- export { buildProviderChildEnvironment, chooseWindowsCommandCandidate, StreamingProviderProcessRunner } from "./provider-cli.process.js";
14
+ export { buildProviderChildEnvironment, chooseWindowsCommandCandidate, resolveCodexNpmDirectLaunch, StreamingProviderProcessRunner } from "./provider-cli.process.js";
15
15
  export { buildWindowsLaunchCommand } from "../../shared/process/windows-launch.js";
16
+ async function pushWorktreeBranch(git, worktreePath, branchName, emit) {
17
+ try {
18
+ await git.run(worktreePath, ["push", "origin", `HEAD:${branchName}`]);
19
+ return;
20
+ }
21
+ catch (err) {
22
+ if (!isNonFastForwardPushError(err)) {
23
+ throw err;
24
+ }
25
+ await emit?.({
26
+ role: "runner",
27
+ kind: "checkpoint",
28
+ content: `Remote branch ${branchName} moved before push; fetching latest and rebasing local changes.`,
29
+ eventType: "worktree_branch_push_rebase"
30
+ });
31
+ try {
32
+ await git.run(worktreePath, ["fetch", "origin", remoteTrackingRefspec(branchName)]);
33
+ await git.run(worktreePath, ["rebase", `refs/remotes/origin/${branchName}`]);
34
+ await git.run(worktreePath, ["push", "origin", `HEAD:${branchName}`]);
35
+ }
36
+ catch (retryErr) {
37
+ throw new Error(`git push origin HEAD:${branchName} was rejected because the remote branch moved, and automatic fetch/rebase failed: ${errorMessage(retryErr)}`, { cause: retryErr });
38
+ }
39
+ }
40
+ }
41
+ function providerOwnsBranchPublication(context) {
42
+ return context.dispatch.sandbox?.executionMode !== "docker";
43
+ }
44
+ function remoteTrackingRefspec(branchName) {
45
+ return `refs/heads/${branchName}:refs/remotes/origin/${branchName}`;
46
+ }
47
+ function isNonFastForwardPushError(err) {
48
+ const message = errorMessage(err).toLowerCase();
49
+ return message.includes("non-fast-forward")
50
+ || message.includes("fetch first")
51
+ || message.includes("tip of your current branch is behind")
52
+ || message.includes("updates were rejected because the tip of your current branch is behind")
53
+ || message.includes("stale info");
54
+ }
55
+ function errorMessage(err) {
56
+ return err instanceof Error ? err.message : String(err);
57
+ }
16
58
  const fakeWorkerModel = "server-local-fake-worker";
17
59
  export class RoutingProviderCliService {
18
60
  realProvider;
@@ -31,6 +73,11 @@ export class RoutingProviderCliService {
31
73
  ? await this.deterministicProvider.runArchitect(context)
32
74
  : await this.realProvider.runArchitect(context);
33
75
  }
76
+ async runCiRepair(context) {
77
+ return isFakeWorkerModel(context.dispatch.execution.model)
78
+ ? await this.deterministicProvider.runCiRepair(context)
79
+ : await this.realProvider.runCiRepair(context);
80
+ }
34
81
  async runConflictResolve(context) {
35
82
  return isFakeWorkerModel(context.dispatch.execution.model)
36
83
  ? await this.deterministicProvider.runConflictResolve(context)
@@ -64,13 +111,20 @@ export class ProcessBackedProviderCliService {
64
111
  await fs.mkdir(metadataRoot, { recursive: true });
65
112
  await fs.writeFile(path.join(metadataRoot, "draft-input.json"), JSON.stringify(durableDraftInput(dispatch), null, 2) + "\n", "utf8");
66
113
  await fs.writeFile(path.join(metadataRoot, "spec.md"), buildDraftSpec(dispatch), "utf8");
114
+ const stagedAttachments = await stageDraftAttachments(dispatch, metadataRoot);
67
115
  const decomposeDraft = isDecomposeDraftDispatch(dispatch);
68
116
  const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: decomposeDraft ? "decompose" : "draft" });
117
+ const draftPrompt = buildDraftPrompt(dispatch, { attachmentReferences: stagedAttachments.textReferences });
118
+ const providerPrompt = buildProviderCliPrompt(dispatch, draftPrompt, context.runtimeEnvironment);
119
+ const pipePromptToStdin = shouldPipePromptToStdin(dispatch.execution.provider, providerPrompt);
69
120
  const command = buildProviderCliCommand(dispatch, {
70
121
  worktreePath: worktree.worktreePath,
71
- prompt: buildDraftPrompt(dispatch),
122
+ prompt: draftPrompt,
123
+ promptFromStdin: pipePromptToStdin,
72
124
  completionTools: completionTools ?? undefined,
73
- skipGitRepoCheck: false
125
+ skipGitRepoCheck: false,
126
+ imagePaths: stagedAttachments.imagePaths,
127
+ runtimeEnvironment: context.runtimeEnvironment
74
128
  });
75
129
  const draftArtifactSlugs = dispatch.artifacts
76
130
  .filter((artifact) => artifact.scope === "operational" && ["command", "skill", "hook"].includes(artifact.kind))
@@ -110,7 +164,8 @@ export class ProcessBackedProviderCliService {
110
164
  command: command.command,
111
165
  args: command.args,
112
166
  cwd: worktree.worktreePath,
113
- env: providerProcessEnv(dispatch)
167
+ env: providerProcessEnv(dispatch, context.runtimeEnvironment),
168
+ stdin: pipePromptToStdin ? providerPrompt : undefined
114
169
  }, {
115
170
  onStdoutLine: async (line) => {
116
171
  for (const event of outputState.consume(line)) {
@@ -237,7 +292,8 @@ export class ProcessBackedProviderCliService {
237
292
  : developCycle
238
293
  ? buildDevelopCyclePrompt(dispatch, phase)
239
294
  : buildImplementationPrompt(dispatch, phase),
240
- completionTools
295
+ completionTools,
296
+ runtimeEnvironment: context.runtimeEnvironment
241
297
  });
242
298
  await context.emit({
243
299
  role: "runner",
@@ -264,7 +320,7 @@ export class ProcessBackedProviderCliService {
264
320
  command: command.command,
265
321
  args: command.args,
266
322
  cwd: worktree.worktreePath,
267
- env: providerProcessEnv(dispatch)
323
+ env: providerProcessEnv(dispatch, context.runtimeEnvironment)
268
324
  }, {
269
325
  onStdoutLine: async (line) => {
270
326
  for (const event of outputState.consume(line)) {
@@ -289,8 +345,8 @@ export class ProcessBackedProviderCliService {
289
345
  });
290
346
  await emitProviderUsageEvent(context.emit, dispatch, outputState);
291
347
  const committed = await commitWorktreeChanges(this.git, worktree.worktreePath, dispatch.workItem.id);
292
- if (developCycle || debugCycle || phase === "implementation") {
293
- await this.git.run(worktree.worktreePath, ["push", "origin", `HEAD:${worktree.branchName}`]);
348
+ if ((developCycle || debugCycle || phase === "implementation") && providerOwnsBranchPublication(context)) {
349
+ await pushWorktreeBranch(this.git, worktree.worktreePath, worktree.branchName, context.emit);
294
350
  await context.emit({
295
351
  role: "runner",
296
352
  kind: "checkpoint",
@@ -352,7 +408,8 @@ export class ProcessBackedProviderCliService {
352
408
  const command = buildProviderCliCommand(dispatch, {
353
409
  worktreePath: worktree.worktreePath,
354
410
  prompt: buildArchitectPrompt(dispatch),
355
- completionTools
411
+ completionTools,
412
+ runtimeEnvironment: context.runtimeEnvironment
356
413
  });
357
414
  const commandText = formatProviderCliCommandForCheckpoint(command);
358
415
  await context.emit({
@@ -376,7 +433,7 @@ export class ProcessBackedProviderCliService {
376
433
  command: command.command,
377
434
  args: command.args,
378
435
  cwd: worktree.worktreePath,
379
- env: providerProcessEnv(dispatch)
436
+ env: providerProcessEnv(dispatch, context.runtimeEnvironment)
380
437
  }, {
381
438
  onStdoutLine: async (line) => {
382
439
  for (const event of outputState.consume(line)) {
@@ -443,7 +500,8 @@ export class ProcessBackedProviderCliService {
443
500
  const command = buildProviderCliCommand(dispatch, {
444
501
  worktreePath: worktree.worktreePath,
445
502
  prompt: buildQaPrompt(dispatch, specBody),
446
- completionTools
503
+ completionTools,
504
+ runtimeEnvironment: context.runtimeEnvironment
447
505
  });
448
506
  const commandText = formatProviderCliCommandForCheckpoint(command);
449
507
  await context.emit({
@@ -467,7 +525,7 @@ export class ProcessBackedProviderCliService {
467
525
  command: command.command,
468
526
  args: command.args,
469
527
  cwd: worktree.worktreePath,
470
- env: providerProcessEnv(dispatch)
528
+ env: providerProcessEnv(dispatch, context.runtimeEnvironment)
471
529
  }, {
472
530
  onStdoutLine: async (line) => {
473
531
  for (const event of outputState.consume(line)) {
@@ -498,7 +556,7 @@ export class ProcessBackedProviderCliService {
498
556
  ? "Provider CLI QA completed but did not produce the complete durable QA artifact bundle."
499
557
  : "Provider CLI QA completed but did not call the Forge QA completion tool.");
500
558
  }
501
- enforceQaBrowserLaunchTargetGate(dispatch, qaArtifact);
559
+ await enforceQaBrowserLaunchTargetGate(dispatch, qaArtifact);
502
560
  if (!liveQaEvents.hasEmitted()) {
503
561
  await emitQaArtifactRecorded(context.emit, worktree.worktreePath, qaArtifact);
504
562
  await emitQaStructuredEvents(context.emit, qaArtifact);
@@ -522,19 +580,29 @@ export class ProcessBackedProviderCliService {
522
580
  artifacts: [qaArtifact]
523
581
  };
524
582
  }
583
+ async runCiRepair(context) {
584
+ return await this.runPullRequestRepair(context, "ci_repair");
585
+ }
525
586
  async runConflictResolve(context) {
587
+ return await this.runPullRequestRepair(context, "conflict_resolve");
588
+ }
589
+ async runPullRequestRepair(context, route) {
526
590
  const { dispatch, worktree } = context;
527
- const addressComments = isPullRequestCommentsDispatch(dispatch);
591
+ const repairChecks = route === "ci_repair" || isPullRequestCheckRepairDispatch(dispatch);
592
+ const addressComments = !repairChecks && isPullRequestCommentsDispatch(dispatch);
528
593
  const command = buildProviderCliCommand(dispatch, {
529
594
  worktreePath: worktree.worktreePath,
530
- prompt: addressComments
531
- ? buildPullRequestCommentsPrompt(dispatch, worktree.branchName)
532
- : buildConflictResolvePrompt(dispatch, worktree.branchName)
595
+ prompt: repairChecks
596
+ ? buildPullRequestCheckRepairPrompt(dispatch, worktree.branchName)
597
+ : addressComments
598
+ ? buildPullRequestCommentsPrompt(dispatch, worktree.branchName)
599
+ : buildConflictResolvePrompt(dispatch, worktree.branchName),
600
+ runtimeEnvironment: context.runtimeEnvironment
533
601
  });
534
602
  await context.emit({
535
603
  role: "runner",
536
604
  kind: "checkpoint",
537
- content: `Starting provider CLI ${addressComments ? "PR comment address" : "conflict resolve"}: ${formatProviderCliCommandForCheckpoint(command)} in ${worktree.worktreePath}`,
605
+ content: `Starting provider CLI ${repairChecks ? "CI repair" : addressComments ? "PR comment address" : "conflict resolve"}: ${formatProviderCliCommandForCheckpoint(command)} in ${worktree.worktreePath}`,
538
606
  eventType: "provider_cli_started"
539
607
  });
540
608
  const outputState = createProviderOutputState(dispatch.execution.provider);
@@ -542,7 +610,7 @@ export class ProcessBackedProviderCliService {
542
610
  command: command.command,
543
611
  args: command.args,
544
612
  cwd: worktree.worktreePath,
545
- env: providerProcessEnv(dispatch)
613
+ env: providerProcessEnv(dispatch, context.runtimeEnvironment)
546
614
  }, {
547
615
  onStdoutLine: async (line) => {
548
616
  for (const event of outputState.consume(line)) {
@@ -555,23 +623,94 @@ export class ProcessBackedProviderCliService {
555
623
  throw new Error(`${command.command} exited ${result.exitCode}${stderr ? `: ${stderr}` : ""}`);
556
624
  }
557
625
  const committed = await commitWorktreeChanges(this.git, worktree.worktreePath, dispatch.workItem.id);
558
- await this.git.run(worktree.worktreePath, ["push", "origin", `HEAD:${worktree.branchName}`]);
626
+ if (providerOwnsBranchPublication(context)) {
627
+ await pushWorktreeBranch(this.git, worktree.worktreePath, worktree.branchName, context.emit);
628
+ }
559
629
  await emitProviderUsageEvent(context.emit, dispatch, outputState);
560
630
  await context.emit({
561
631
  role: "assistant",
562
632
  kind: "message",
563
- content: outputState.finalMessage || (addressComments
564
- ? `${dispatch.execution.provider} CLI addressed pull request comments for ${worktree.branchName}.`
565
- : `${dispatch.execution.provider} CLI resolved merge conflicts for ${worktree.branchName}.`),
566
- eventType: addressComments ? "pr_comments_complete" : "conflict_resolve_complete"
633
+ content: outputState.finalMessage || (repairChecks
634
+ ? `${dispatch.execution.provider} CLI repaired pull request checks for ${worktree.branchName}.`
635
+ : addressComments
636
+ ? `${dispatch.execution.provider} CLI addressed pull request comments for ${worktree.branchName}.`
637
+ : `${dispatch.execution.provider} CLI resolved merge conflicts for ${worktree.branchName}.`),
638
+ eventType: repairChecks ? "pr_checks_complete" : addressComments ? "pr_comments_complete" : "conflict_resolve_complete"
567
639
  });
568
640
  return {
569
- summary: addressComments
570
- ? `${dispatch.execution.provider} CLI addressed PR comments on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
571
- : `${dispatch.execution.provider} CLI resolved conflicts on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
641
+ summary: repairChecks
642
+ ? `${dispatch.execution.provider} CLI repaired PR checks on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
643
+ : addressComments
644
+ ? `${dispatch.execution.provider} CLI addressed PR comments on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
645
+ : `${dispatch.execution.provider} CLI resolved conflicts on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
572
646
  };
573
647
  }
574
648
  }
649
+ async function stageDraftAttachments(dispatch, metadataRoot) {
650
+ const attachments = dispatch.draft?.attachments ?? [];
651
+ const stageable = attachments.filter((attachment) => (attachment.kind === "text" && typeof attachment.textContent === "string")
652
+ || (attachment.kind === "image" && typeof attachment.contentBase64 === "string"));
653
+ if (stageable.length === 0) {
654
+ return { textReferences: [], imagePaths: [] };
655
+ }
656
+ const attachmentRoot = path.join(metadataRoot, "draft-attachments");
657
+ const worktreeRoot = path.resolve(metadataRoot, "../../..");
658
+ await fs.mkdir(attachmentRoot, { recursive: true });
659
+ const textReferences = [];
660
+ const imagePaths = [];
661
+ for (const attachment of stageable) {
662
+ const fileName = `${String(attachment.index + 1).padStart(2, "0")}-${safeAttachmentFileName(attachment.fileName)}`;
663
+ const absolutePath = path.join(attachmentRoot, fileName);
664
+ if (attachment.kind === "text") {
665
+ await fs.writeFile(absolutePath, attachment.textContent ?? "", { encoding: "utf8", mode: 0o600 });
666
+ textReferences.push({ index: attachment.index, path: path.relative(worktreeRoot, absolutePath).replaceAll(path.sep, "/") });
667
+ continue;
668
+ }
669
+ const imageContent = decodeDraftImageAttachment(attachment.contentBase64 ?? "", attachment.fileName, attachment.index);
670
+ await fs.writeFile(absolutePath, imageContent, { mode: 0o600 });
671
+ imagePaths.push(absolutePath);
672
+ }
673
+ return { textReferences, imagePaths };
674
+ }
675
+ function decodeDraftImageAttachment(contentBase64, fileName, index) {
676
+ const normalized = contentBase64.replace(/\s+/gu, "");
677
+ if (normalized.length === 0 || normalized.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/u.test(normalized)) {
678
+ throw new Error(`Draft image attachment ${index + 1} (${fileName}) is not valid base64.`);
679
+ }
680
+ const decoded = Buffer.from(normalized, "base64");
681
+ if (decoded.length === 0 || decoded.toString("base64") !== normalized) {
682
+ throw new Error(`Draft image attachment ${index + 1} (${fileName}) is not valid base64.`);
683
+ }
684
+ return decoded;
685
+ }
686
+ function safeAttachmentFileName(fileName) {
687
+ const sanitized = path.basename(fileName || "attachment.txt")
688
+ .replace(/[^a-zA-Z0-9._-]/gu, "_")
689
+ .replace(/[. ]+$/gu, "") || "attachment.txt";
690
+ const maxFileNameLength = 120;
691
+ const clipped = clipFileName(sanitized, maxFileNameLength);
692
+ const extension = path.extname(clipped);
693
+ const stem = path.basename(clipped, extension).replace(/[. ]+$/gu, "") || "attachment";
694
+ const windowsReservedName = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/iu;
695
+ const safeStem = stem === "." || stem === ".." || windowsReservedName.test(stem) ? `attachment-${stem.replaceAll(".", "_")}` : stem;
696
+ return clipFileName(`${safeStem}${extension}`, maxFileNameLength);
697
+ }
698
+ function clipFileName(fileName, maxLength) {
699
+ if (fileName.length <= maxLength) {
700
+ return fileName;
701
+ }
702
+ const originalExtension = path.extname(fileName);
703
+ const extension = originalExtension.length > maxLength - 1
704
+ ? originalExtension.slice(0, maxLength - 1)
705
+ : originalExtension;
706
+ const stem = path.basename(fileName, originalExtension);
707
+ const maxStemLength = Math.max(1, maxLength - extension.length);
708
+ return `${stem.slice(0, maxStemLength)}${extension}`;
709
+ }
710
+ const providerPromptStdinThreshold = 8_000;
711
+ function shouldPipePromptToStdin(provider, prompt) {
712
+ return provider.trim().toLowerCase() === "codex" && Buffer.byteLength(prompt, "utf8") > providerPromptStdinThreshold;
713
+ }
575
714
  function isFakeWorkerModel(model) {
576
715
  return model.trim().toLowerCase() === fakeWorkerModel;
577
716
  }
@@ -3,12 +3,14 @@ import type { WorktreeHandle } from "../../worktree/worktree.types.js";
3
3
  export interface ProviderCliRunContext {
4
4
  readonly dispatch: RunOnceDispatchPacket;
5
5
  readonly worktree: WorktreeHandle;
6
+ readonly runtimeEnvironment?: Readonly<Record<string, string>>;
6
7
  readonly signal?: AbortSignal;
7
8
  emit(event: RunnerDispatchEvent): Promise<void>;
8
9
  }
9
10
  export interface ProviderCliDraftContext {
10
11
  readonly dispatch: RunOnceDispatchPacket;
11
12
  readonly worktree: WorktreeHandle;
13
+ readonly runtimeEnvironment?: Readonly<Record<string, string>>;
12
14
  readonly signal?: AbortSignal;
13
15
  emit(event: RunnerDispatchEvent): Promise<void>;
14
16
  }
@@ -0,0 +1,3 @@
1
+ import type { RunOnceResultPacket, RunnerDispatchEvent } from "./session.types.js";
2
+ export declare function redactRunnerDispatchEvent(event: RunnerDispatchEvent, exactSecrets?: readonly string[]): RunnerDispatchEvent;
3
+ export declare function redactRunOnceResultPacket(result: RunOnceResultPacket, exactSecrets?: readonly string[]): RunOnceResultPacket;
@@ -0,0 +1,23 @@
1
+ import { redactSecrets } from "../shared/redaction/redaction.js";
2
+ const integrityProtectedEventDataTypes = new Set(["sandbox_started"]);
3
+ export function redactRunnerDispatchEvent(event, exactSecrets = []) {
4
+ if (!event.data || !integrityProtectedEventDataTypes.has(event.eventType)) {
5
+ return redactSecrets(event, exactSecrets);
6
+ }
7
+ // sandbox_started data is runner-authored protocol metadata. Its runtime
8
+ // lease repeats the dispatch fence so the API can validate ownership. The
9
+ // transcript redactor treats any serialized `*Token` field as a secret and
10
+ // must not rewrite that identity before transport or result persistence.
11
+ const { data, ...transcript } = event;
12
+ return {
13
+ ...redactSecrets(transcript, exactSecrets),
14
+ data,
15
+ };
16
+ }
17
+ export function redactRunOnceResultPacket(result, exactSecrets = []) {
18
+ const { events, ...payload } = result;
19
+ return {
20
+ ...redactSecrets(payload, exactSecrets),
21
+ events: events.map(event => redactRunnerDispatchEvent(event, exactSecrets)),
22
+ };
23
+ }
@@ -1,7 +1,11 @@
1
1
  import type { RunOnceDispatchPacket, RunOnceResultPacket, RunnerDispatchEvent } from "./session.types.js";
2
+ import type { HostRuntimeCapacitySnapshot, HostRuntimeReapOptions, HostRuntimeReapReport } from "../runtime/host-runtime-lease.js";
2
3
  export interface SessionEventSink {
3
4
  emit(event: RunnerDispatchEvent): Promise<void>;
4
5
  }
5
6
  export interface SessionService {
6
7
  runOnce(dispatch: RunOnceDispatchPacket, sink: SessionEventSink, signal?: AbortSignal): Promise<RunOnceResultPacket>;
8
+ updateLease?(dispatchAttemptId: string, fencingToken: string, expiresAt: string): Promise<void>;
9
+ reapHostRuntime?(options: HostRuntimeReapOptions): Promise<HostRuntimeReapReport | null>;
10
+ runtimeCapacity?(): HostRuntimeCapacitySnapshot | null;
7
11
  }
@@ -2,9 +2,14 @@ import type { ProviderCliService } from "./provider-cli/provider-cli.base.js";
2
2
  import type { SessionEventSink, SessionService } from "./session.base.js";
3
3
  import type { RunOnceDispatchPacket, RunOnceResultPacket } from "./session.types.js";
4
4
  import type { WorktreeService } from "../worktree/worktree.base.js";
5
+ import { type HostRuntimeCapacitySnapshot, type HostRuntimeLeaseRuntime, type HostRuntimeReapOptions, type HostRuntimeReapReport } from "../runtime/host-runtime-lease.js";
5
6
  export declare class BridgeSessionService implements SessionService {
6
7
  private readonly worktrees;
7
8
  private readonly providerCli;
8
- constructor(worktrees?: WorktreeService, providerCli?: ProviderCliService);
9
+ private readonly hostRuntime?;
10
+ constructor(worktrees?: WorktreeService, providerCli?: ProviderCliService, hostRuntime?: HostRuntimeLeaseRuntime | undefined);
11
+ updateLease(dispatchAttemptId: string, fencingToken: string, expiresAt: string): Promise<void>;
12
+ reapHostRuntime(options: HostRuntimeReapOptions): Promise<HostRuntimeReapReport | null>;
13
+ runtimeCapacity(): HostRuntimeCapacitySnapshot | null;
9
14
  runOnce(dispatch: RunOnceDispatchPacket, sink: SessionEventSink, signal?: AbortSignal): Promise<RunOnceResultPacket>;
10
15
  }