@loucompanion/forge-bridge 0.1.3-dev.d46a8e73d03d → 0.1.3-dev.eeefdb0d19c1
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/README.md +34 -0
- package/dist/bridge/bridge.base.d.ts +2 -1
- package/dist/bridge/bridge.service.d.ts +22 -3
- package/dist/bridge/bridge.service.js +416 -40
- package/dist/bridge/bridge.types.d.ts +59 -1
- package/dist/cleanup/cleanup.service.d.ts +13 -2
- package/dist/cleanup/cleanup.service.js +435 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.js +37 -3
- package/dist/cli/cli.service.d.ts +30 -2
- package/dist/cli/cli.service.js +182 -23
- package/dist/cli/local-config.d.ts +4 -0
- package/dist/cli/local-config.js +41 -1
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +101 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +86 -0
- package/dist/runtime/attempt-process-supervisor.js +343 -0
- package/dist/runtime/generated-output-reset.d.ts +10 -0
- package/dist/runtime/generated-output-reset.js +103 -0
- package/dist/runtime/host-listener-inspector.d.ts +44 -0
- package/dist/runtime/host-listener-inspector.js +324 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +47 -0
- package/dist/runtime/host-runtime-contract.js +700 -0
- package/dist/runtime/host-runtime-lease.d.ts +133 -0
- package/dist/runtime/host-runtime-lease.js +1460 -0
- package/dist/runtime/host-runtime-promotion.d.ts +23 -0
- package/dist/runtime/host-runtime-promotion.js +159 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +51 -0
- package/dist/runtime/node-host-runtime-adapter.js +268 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +20 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +220 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +158 -0
- package/dist/sandbox/preflight.d.ts +14 -0
- package/dist/sandbox/preflight.js +91 -12
- package/dist/sandbox/process.js +125 -40
- package/dist/sandbox/repository-lock.js +37 -1
- package/dist/sandbox/runtime.d.ts +66 -3
- package/dist/sandbox/runtime.js +670 -105
- package/dist/sandbox/sandbox.types.d.ts +3 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.js +1 -1
- package/dist/sandbox/worker.js +2 -5
- package/dist/sandbox/workspace.service.d.ts +1 -0
- package/dist/sandbox/workspace.service.js +28 -5
- package/dist/session/managed-attempt-tools.d.ts +12 -0
- package/dist/session/managed-attempt-tools.js +350 -0
- package/dist/session/phase-completion.artifacts.js +9 -2
- package/dist/session/phase-completion.d.ts +4 -2
- package/dist/session/phase-completion.helpers.js +27 -3
- package/dist/session/phase-completion.js +12 -3
- package/dist/session/phase-completion.prompts.d.ts +3 -3
- package/dist/session/phase-completion.prompts.js +19 -5
- package/dist/session/phase-completion.runtime.d.ts +12 -1
- package/dist/session/phase-completion.runtime.js +127 -7
- package/dist/session/phase-completion.schemas.js +108 -3
- package/dist/session/phase-completion.validation.d.ts +4 -1
- package/dist/session/phase-completion.validation.js +106 -2
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +90 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +1 -1
- package/dist/session/provider-cli/provider-cli.artifacts.js +28 -4
- package/dist/session/provider-cli/provider-cli.deterministic.js +47 -1
- package/dist/session/provider-cli/provider-cli.process.d.ts +16 -0
- package/dist/session/provider-cli/provider-cli.process.js +212 -25
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +4 -1
- package/dist/session/provider-cli/provider-cli.prompts.js +84 -17
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
- package/dist/session/provider-cli/provider-cli.runtime.js +3 -2
- package/dist/session/provider-cli/provider-cli.service.d.ts +3 -2
- package/dist/session/provider-cli/provider-cli.service.js +254 -71
- package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
- package/dist/session/provider-cli/provider-output-state.js +30 -2
- package/dist/session/session-redaction.d.ts +3 -0
- package/dist/session/session-redaction.js +23 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +436 -41
- package/dist/session/session.types.d.ts +55 -0
- package/dist/session/session.types.js +69 -2
- package/dist/shared/path/path.js +3 -1
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +78 -0
- package/dist/shared/process/host-process-supervisor.d.ts +57 -0
- package/dist/shared/process/host-process-supervisor.js +282 -0
- package/dist/shared/process/process.d.ts +1 -0
- package/dist/shared/process/process.js +1 -0
- package/dist/shared/redaction/redaction.js +6 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +43 -4
- package/dist/worktree/dependency-warmer.d.ts +10 -1
- package/dist/worktree/dependency-warmer.js +94 -5
- package/dist/worktree/index.d.ts +1 -0
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +7 -2
- package/dist/worktree/worktree.service.js +72 -44
- package/dist/worktree/worktree.types.d.ts +3 -1
- package/package.json +3 -2
- package/runtime/windows-job-supervisor.ps1 +356 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -5,13 +5,14 @@ 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, buildPullRequestCheckRepairPrompt, buildPullRequestCommentsPrompt, buildQaPrompt, formatProviderCliCommandForCheckpoint, isDecomposeDraftDispatch, isPullRequestCheckRepairDispatch, 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
|
+
import { writeCiRepairReadinessManifest } from "./ci-repair-readiness.js";
|
|
12
13
|
export { DeterministicProviderCliService } from "./provider-cli.deterministic.js";
|
|
13
14
|
export { buildProviderCliCommand, codexSandboxModeForPlatform, formatProviderCliCommand, formatProviderCliCommandForCheckpoint } from "./provider-cli.prompts.js";
|
|
14
|
-
export { buildProviderChildEnvironment, chooseWindowsCommandCandidate, StreamingProviderProcessRunner } from "./provider-cli.process.js";
|
|
15
|
+
export { buildProviderChildEnvironment, chooseWindowsCommandCandidate, resolveCodexNpmDirectLaunch, resolvePackageManagerDirectLaunch, resolveWindowsCommandFromPath, StreamingProviderProcessRunner } from "./provider-cli.process.js";
|
|
15
16
|
export { buildWindowsLaunchCommand } from "../../shared/process/windows-launch.js";
|
|
16
17
|
async function pushWorktreeBranch(git, worktreePath, branchName, emit) {
|
|
17
18
|
try {
|
|
@@ -38,6 +39,9 @@ async function pushWorktreeBranch(git, worktreePath, branchName, emit) {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
42
|
+
function providerOwnsBranchPublication(context) {
|
|
43
|
+
return context.dispatch.sandbox?.executionMode !== "docker";
|
|
44
|
+
}
|
|
41
45
|
function remoteTrackingRefspec(branchName) {
|
|
42
46
|
return `refs/heads/${branchName}:refs/remotes/origin/${branchName}`;
|
|
43
47
|
}
|
|
@@ -52,6 +56,52 @@ function isNonFastForwardPushError(err) {
|
|
|
52
56
|
function errorMessage(err) {
|
|
53
57
|
return err instanceof Error ? err.message : String(err);
|
|
54
58
|
}
|
|
59
|
+
const defaultProviderToolHeartbeatIntervalMs = 60_000;
|
|
60
|
+
class ProviderToolActivityHeartbeat {
|
|
61
|
+
outputState;
|
|
62
|
+
emit;
|
|
63
|
+
intervalMs;
|
|
64
|
+
timer = null;
|
|
65
|
+
constructor(outputState, emit, intervalMs) {
|
|
66
|
+
this.outputState = outputState;
|
|
67
|
+
this.emit = emit;
|
|
68
|
+
this.intervalMs = intervalMs;
|
|
69
|
+
}
|
|
70
|
+
async consume(line) {
|
|
71
|
+
const events = this.outputState.consume(line);
|
|
72
|
+
this.sync();
|
|
73
|
+
for (const event of events) {
|
|
74
|
+
await this.emit(event);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
stop() {
|
|
78
|
+
if (this.timer) {
|
|
79
|
+
clearInterval(this.timer);
|
|
80
|
+
this.timer = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
sync() {
|
|
84
|
+
if (!this.outputState.hasActiveToolCall) {
|
|
85
|
+
this.stop();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (this.timer) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.timer = setInterval(() => {
|
|
92
|
+
if (!this.outputState.hasActiveToolCall) {
|
|
93
|
+
this.stop();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
void this.emit({
|
|
97
|
+
role: "runner",
|
|
98
|
+
kind: "status",
|
|
99
|
+
content: "A supervised provider tool is still running.",
|
|
100
|
+
eventType: "provider_tool_heartbeat",
|
|
101
|
+
}).catch(() => undefined);
|
|
102
|
+
}, Math.max(1, this.intervalMs));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
55
105
|
const fakeWorkerModel = "server-local-fake-worker";
|
|
56
106
|
export class RoutingProviderCliService {
|
|
57
107
|
realProvider;
|
|
@@ -94,9 +144,11 @@ export class RoutingProviderCliService {
|
|
|
94
144
|
export class ProcessBackedProviderCliService {
|
|
95
145
|
processRunner;
|
|
96
146
|
git;
|
|
97
|
-
|
|
147
|
+
providerToolHeartbeatIntervalMs;
|
|
148
|
+
constructor(processRunner = new StreamingProviderProcessRunner(), git = new GitClient(), providerToolHeartbeatIntervalMs = defaultProviderToolHeartbeatIntervalMs) {
|
|
98
149
|
this.processRunner = processRunner;
|
|
99
150
|
this.git = git;
|
|
151
|
+
this.providerToolHeartbeatIntervalMs = providerToolHeartbeatIntervalMs;
|
|
100
152
|
}
|
|
101
153
|
async runDraft(context) {
|
|
102
154
|
const { dispatch } = context;
|
|
@@ -110,15 +162,18 @@ export class ProcessBackedProviderCliService {
|
|
|
110
162
|
await fs.writeFile(path.join(metadataRoot, "spec.md"), buildDraftSpec(dispatch), "utf8");
|
|
111
163
|
const stagedAttachments = await stageDraftAttachments(dispatch, metadataRoot);
|
|
112
164
|
const decomposeDraft = isDecomposeDraftDispatch(dispatch);
|
|
113
|
-
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: decomposeDraft ? "decompose" : "draft" });
|
|
165
|
+
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: decomposeDraft ? "decompose" : "draft", runtimeEnvironment: context.runtimeEnvironment });
|
|
114
166
|
const draftPrompt = buildDraftPrompt(dispatch, { attachmentReferences: stagedAttachments.textReferences });
|
|
115
|
-
const
|
|
167
|
+
const providerPrompt = buildProviderCliPrompt(dispatch, draftPrompt, context.runtimeEnvironment);
|
|
168
|
+
const pipePromptToStdin = shouldPipePromptToStdin(dispatch.execution.provider, providerPrompt, usesManagedWindowsHost(dispatch));
|
|
116
169
|
const command = buildProviderCliCommand(dispatch, {
|
|
117
170
|
worktreePath: worktree.worktreePath,
|
|
118
|
-
prompt:
|
|
171
|
+
prompt: draftPrompt,
|
|
172
|
+
promptFromStdin: pipePromptToStdin,
|
|
119
173
|
completionTools: completionTools ?? undefined,
|
|
120
174
|
skipGitRepoCheck: false,
|
|
121
|
-
imagePaths: stagedAttachments.imagePaths
|
|
175
|
+
imagePaths: stagedAttachments.imagePaths,
|
|
176
|
+
runtimeEnvironment: context.runtimeEnvironment
|
|
122
177
|
});
|
|
123
178
|
const draftArtifactSlugs = dispatch.artifacts
|
|
124
179
|
.filter((artifact) => artifact.scope === "operational" && ["command", "skill", "hook"].includes(artifact.kind))
|
|
@@ -133,7 +188,7 @@ export class ProcessBackedProviderCliService {
|
|
|
133
188
|
role: "runner",
|
|
134
189
|
kind: "checkpoint",
|
|
135
190
|
content: `Starting provider CLI Draft: ${formatProviderCliCommandForCheckpoint(command)}`,
|
|
136
|
-
eventType: "
|
|
191
|
+
eventType: "provider_cli_starting"
|
|
137
192
|
});
|
|
138
193
|
await context.emit({
|
|
139
194
|
role: "runner",
|
|
@@ -144,6 +199,7 @@ export class ProcessBackedProviderCliService {
|
|
|
144
199
|
eventType: "completion_tools_configured"
|
|
145
200
|
});
|
|
146
201
|
const outputState = createProviderOutputState(dispatch.execution.provider);
|
|
202
|
+
const toolHeartbeat = new ProviderToolActivityHeartbeat(outputState, context.emit, this.providerToolHeartbeatIntervalMs);
|
|
147
203
|
try {
|
|
148
204
|
const providerCancellation = new AbortController();
|
|
149
205
|
const abortProvider = () => providerCancellation.abort();
|
|
@@ -154,17 +210,23 @@ export class ProcessBackedProviderCliService {
|
|
|
154
210
|
context.signal?.addEventListener("abort", abortProvider, { once: true });
|
|
155
211
|
}
|
|
156
212
|
try {
|
|
157
|
-
const runPromise = this.processRunner.run({
|
|
213
|
+
const runPromise = (context.processRunner ?? this.processRunner).run({
|
|
158
214
|
command: command.command,
|
|
159
215
|
args: command.args,
|
|
160
216
|
cwd: worktree.worktreePath,
|
|
161
|
-
env: providerProcessEnv(dispatch),
|
|
162
|
-
stdin: pipePromptToStdin ?
|
|
217
|
+
env: providerProcessEnv(dispatch, context.runtimeEnvironment),
|
|
218
|
+
stdin: pipePromptToStdin ? providerPrompt : undefined
|
|
163
219
|
}, {
|
|
220
|
+
onStarted: async () => {
|
|
221
|
+
await context.emit({
|
|
222
|
+
role: "runner",
|
|
223
|
+
kind: "checkpoint",
|
|
224
|
+
content: "Provider CLI Draft is supervised and ready.",
|
|
225
|
+
eventType: "provider_cli_started"
|
|
226
|
+
});
|
|
227
|
+
},
|
|
164
228
|
onStdoutLine: async (line) => {
|
|
165
|
-
|
|
166
|
-
await context.emit(event);
|
|
167
|
-
}
|
|
229
|
+
await toolHeartbeat.consume(line);
|
|
168
230
|
}
|
|
169
231
|
}, providerCancellation.signal);
|
|
170
232
|
const artifactWaitCancellation = new AbortController();
|
|
@@ -260,6 +322,7 @@ export class ProcessBackedProviderCliService {
|
|
|
260
322
|
}
|
|
261
323
|
}
|
|
262
324
|
finally {
|
|
325
|
+
toolHeartbeat.stop();
|
|
263
326
|
await cleanupCompletionTools(completionTools, context.emit);
|
|
264
327
|
}
|
|
265
328
|
}
|
|
@@ -278,21 +341,26 @@ export class ProcessBackedProviderCliService {
|
|
|
278
341
|
const specPath = path.join(metadataRoot, "spec.md");
|
|
279
342
|
await fs.writeFile(specPath, specBody, "utf8");
|
|
280
343
|
await fs.writeFile(path.join(metadataRoot, "spec.meta.json"), JSON.stringify({ schema_version: 1, status: "approved", produced_by: "forge-lane-command" }) + "\n", "utf8");
|
|
281
|
-
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase });
|
|
344
|
+
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase, runtimeEnvironment: context.runtimeEnvironment });
|
|
345
|
+
const implementationPrompt = debugCycle
|
|
346
|
+
? buildDebugCyclePrompt(dispatch, phase)
|
|
347
|
+
: developCycle
|
|
348
|
+
? buildDevelopCyclePrompt(dispatch, phase)
|
|
349
|
+
: buildImplementationPrompt(dispatch, phase);
|
|
350
|
+
const providerPrompt = buildProviderCliPrompt(dispatch, implementationPrompt, context.runtimeEnvironment);
|
|
351
|
+
const pipePromptToStdin = shouldPipePromptToStdin(dispatch.execution.provider, providerPrompt, usesManagedWindowsHost(dispatch));
|
|
282
352
|
const command = buildProviderCliCommand(dispatch, {
|
|
283
353
|
worktreePath: worktree.worktreePath,
|
|
284
|
-
prompt:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
: buildImplementationPrompt(dispatch, phase),
|
|
289
|
-
completionTools
|
|
354
|
+
prompt: implementationPrompt,
|
|
355
|
+
promptFromStdin: pipePromptToStdin,
|
|
356
|
+
completionTools,
|
|
357
|
+
runtimeEnvironment: context.runtimeEnvironment
|
|
290
358
|
});
|
|
291
359
|
await context.emit({
|
|
292
360
|
role: "runner",
|
|
293
361
|
kind: "checkpoint",
|
|
294
362
|
content: `Starting provider CLI: ${formatProviderCliCommandForCheckpoint(command)} in ${worktree.worktreePath}`,
|
|
295
|
-
eventType: "
|
|
363
|
+
eventType: "provider_cli_starting"
|
|
296
364
|
});
|
|
297
365
|
await context.emit({
|
|
298
366
|
role: "runner",
|
|
@@ -305,24 +373,33 @@ export class ProcessBackedProviderCliService {
|
|
|
305
373
|
eventType: "completion_tools_configured"
|
|
306
374
|
});
|
|
307
375
|
const outputState = createProviderOutputState(dispatch.execution.provider);
|
|
376
|
+
const toolHeartbeat = new ProviderToolActivityHeartbeat(outputState, context.emit, this.providerToolHeartbeatIntervalMs);
|
|
308
377
|
const liveCompletionEvents = new LiveCompletionArtifactEmitter(worktree.worktreePath, dispatch.workItem.id, phase, developCycle, debugCycle, context.emit);
|
|
309
378
|
let result;
|
|
310
379
|
try {
|
|
311
380
|
liveCompletionEvents.start();
|
|
312
|
-
result = await this.processRunner.run({
|
|
381
|
+
result = await (context.processRunner ?? this.processRunner).run({
|
|
313
382
|
command: command.command,
|
|
314
383
|
args: command.args,
|
|
315
384
|
cwd: worktree.worktreePath,
|
|
316
|
-
env: providerProcessEnv(dispatch)
|
|
385
|
+
env: providerProcessEnv(dispatch, context.runtimeEnvironment),
|
|
386
|
+
stdin: pipePromptToStdin ? providerPrompt : undefined
|
|
317
387
|
}, {
|
|
388
|
+
onStarted: async () => {
|
|
389
|
+
await context.emit({
|
|
390
|
+
role: "runner",
|
|
391
|
+
kind: "checkpoint",
|
|
392
|
+
content: "Provider CLI is supervised and ready.",
|
|
393
|
+
eventType: "provider_cli_started"
|
|
394
|
+
});
|
|
395
|
+
},
|
|
318
396
|
onStdoutLine: async (line) => {
|
|
319
|
-
|
|
320
|
-
await context.emit(event);
|
|
321
|
-
}
|
|
397
|
+
await toolHeartbeat.consume(line);
|
|
322
398
|
}
|
|
323
399
|
}, context.signal);
|
|
324
400
|
}
|
|
325
401
|
finally {
|
|
402
|
+
toolHeartbeat.stop();
|
|
326
403
|
await liveCompletionEvents.stop();
|
|
327
404
|
await cleanupCompletionTools(completionTools, context.emit);
|
|
328
405
|
}
|
|
@@ -338,7 +415,7 @@ export class ProcessBackedProviderCliService {
|
|
|
338
415
|
});
|
|
339
416
|
await emitProviderUsageEvent(context.emit, dispatch, outputState);
|
|
340
417
|
const committed = await commitWorktreeChanges(this.git, worktree.worktreePath, dispatch.workItem.id);
|
|
341
|
-
if (developCycle || debugCycle || phase === "implementation") {
|
|
418
|
+
if ((developCycle || debugCycle || phase === "implementation") && providerOwnsBranchPublication(context)) {
|
|
342
419
|
await pushWorktreeBranch(this.git, worktree.worktreePath, worktree.branchName, context.emit);
|
|
343
420
|
await context.emit({
|
|
344
421
|
role: "runner",
|
|
@@ -397,18 +474,23 @@ export class ProcessBackedProviderCliService {
|
|
|
397
474
|
const specPath = path.join(metadataRoot, "spec.md");
|
|
398
475
|
await fs.writeFile(specPath, specBody, "utf8");
|
|
399
476
|
await fs.writeFile(path.join(metadataRoot, "spec.meta.json"), JSON.stringify({ schema_version: 1, status: "pending_approval", produced_by: "forge-lane-command" }) + "\n", "utf8");
|
|
400
|
-
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: "architect" });
|
|
477
|
+
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: "architect", runtimeEnvironment: context.runtimeEnvironment });
|
|
478
|
+
const architectPrompt = buildArchitectPrompt(dispatch);
|
|
479
|
+
const providerPrompt = buildProviderCliPrompt(dispatch, architectPrompt, context.runtimeEnvironment);
|
|
480
|
+
const pipePromptToStdin = shouldPipePromptToStdin(dispatch.execution.provider, providerPrompt, usesManagedWindowsHost(dispatch));
|
|
401
481
|
const command = buildProviderCliCommand(dispatch, {
|
|
402
482
|
worktreePath: worktree.worktreePath,
|
|
403
|
-
prompt:
|
|
404
|
-
|
|
483
|
+
prompt: architectPrompt,
|
|
484
|
+
promptFromStdin: pipePromptToStdin,
|
|
485
|
+
completionTools,
|
|
486
|
+
runtimeEnvironment: context.runtimeEnvironment
|
|
405
487
|
});
|
|
406
488
|
const commandText = formatProviderCliCommandForCheckpoint(command);
|
|
407
489
|
await context.emit({
|
|
408
490
|
role: "runner",
|
|
409
491
|
kind: "checkpoint",
|
|
410
492
|
content: `Starting provider CLI Architect: ${commandText} in ${worktree.worktreePath}`,
|
|
411
|
-
eventType: "
|
|
493
|
+
eventType: "provider_cli_starting"
|
|
412
494
|
});
|
|
413
495
|
await context.emit({
|
|
414
496
|
role: "runner",
|
|
@@ -417,24 +499,31 @@ export class ProcessBackedProviderCliService {
|
|
|
417
499
|
eventType: "completion_tools_configured"
|
|
418
500
|
});
|
|
419
501
|
const outputState = createProviderOutputState(dispatch.execution.provider);
|
|
502
|
+
const toolHeartbeat = new ProviderToolActivityHeartbeat(outputState, context.emit, this.providerToolHeartbeatIntervalMs);
|
|
420
503
|
const liveArchitectEvents = new LiveArchitectArtifactEmitter(worktree.worktreePath, dispatch.workItem.id, context.emit);
|
|
421
504
|
let result;
|
|
422
505
|
try {
|
|
423
506
|
liveArchitectEvents.start();
|
|
424
|
-
result = await this.processRunner.run({
|
|
507
|
+
result = await (context.processRunner ?? this.processRunner).run({
|
|
425
508
|
command: command.command,
|
|
426
509
|
args: command.args,
|
|
427
510
|
cwd: worktree.worktreePath,
|
|
428
|
-
env: providerProcessEnv(dispatch)
|
|
511
|
+
env: providerProcessEnv(dispatch, context.runtimeEnvironment),
|
|
512
|
+
stdin: pipePromptToStdin ? providerPrompt : undefined
|
|
429
513
|
}, {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
514
|
+
onStarted: async () => {
|
|
515
|
+
await context.emit({
|
|
516
|
+
role: "runner",
|
|
517
|
+
kind: "checkpoint",
|
|
518
|
+
content: "Provider CLI Architect is supervised and ready.",
|
|
519
|
+
eventType: "provider_cli_started"
|
|
520
|
+
});
|
|
521
|
+
},
|
|
522
|
+
onStdoutLine: async (line) => await toolHeartbeat.consume(line)
|
|
435
523
|
}, context.signal);
|
|
436
524
|
}
|
|
437
525
|
finally {
|
|
526
|
+
toolHeartbeat.stop();
|
|
438
527
|
await liveArchitectEvents.stop();
|
|
439
528
|
await cleanupCompletionTools(completionTools, context.emit);
|
|
440
529
|
}
|
|
@@ -488,18 +577,23 @@ export class ProcessBackedProviderCliService {
|
|
|
488
577
|
const specPath = path.join(metadataRoot, "spec.md");
|
|
489
578
|
await fs.writeFile(specPath, specBody, "utf8");
|
|
490
579
|
await fs.writeFile(path.join(metadataRoot, "spec.meta.json"), JSON.stringify({ schema_version: 1, status: "approved", produced_by: "forge-lane-command" }) + "\n", "utf8");
|
|
491
|
-
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: "qa" });
|
|
580
|
+
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: "qa", runtimeEnvironment: context.runtimeEnvironment });
|
|
581
|
+
const qaPrompt = buildQaPrompt(dispatch, specBody);
|
|
582
|
+
const providerPrompt = buildProviderCliPrompt(dispatch, qaPrompt, context.runtimeEnvironment);
|
|
583
|
+
const pipePromptToStdin = shouldPipePromptToStdin(dispatch.execution.provider, providerPrompt, usesManagedWindowsHost(dispatch));
|
|
492
584
|
const command = buildProviderCliCommand(dispatch, {
|
|
493
585
|
worktreePath: worktree.worktreePath,
|
|
494
|
-
prompt:
|
|
495
|
-
|
|
586
|
+
prompt: qaPrompt,
|
|
587
|
+
promptFromStdin: pipePromptToStdin,
|
|
588
|
+
completionTools,
|
|
589
|
+
runtimeEnvironment: context.runtimeEnvironment
|
|
496
590
|
});
|
|
497
591
|
const commandText = formatProviderCliCommandForCheckpoint(command);
|
|
498
592
|
await context.emit({
|
|
499
593
|
role: "runner",
|
|
500
594
|
kind: "checkpoint",
|
|
501
595
|
content: `Starting provider CLI QA: ${commandText} in ${worktree.worktreePath}`,
|
|
502
|
-
eventType: "
|
|
596
|
+
eventType: "provider_cli_starting"
|
|
503
597
|
});
|
|
504
598
|
await context.emit({
|
|
505
599
|
role: "runner",
|
|
@@ -508,24 +602,31 @@ export class ProcessBackedProviderCliService {
|
|
|
508
602
|
eventType: "completion_tools_configured"
|
|
509
603
|
});
|
|
510
604
|
const outputState = createProviderOutputState(dispatch.execution.provider);
|
|
605
|
+
const toolHeartbeat = new ProviderToolActivityHeartbeat(outputState, context.emit, this.providerToolHeartbeatIntervalMs);
|
|
511
606
|
const liveQaEvents = new LiveQaArtifactEmitter(worktree.worktreePath, dispatch.workItem.id, dispatch, context.emit);
|
|
512
607
|
let result;
|
|
513
608
|
try {
|
|
514
609
|
liveQaEvents.start();
|
|
515
|
-
result = await this.processRunner.run({
|
|
610
|
+
result = await (context.processRunner ?? this.processRunner).run({
|
|
516
611
|
command: command.command,
|
|
517
612
|
args: command.args,
|
|
518
613
|
cwd: worktree.worktreePath,
|
|
519
|
-
env: providerProcessEnv(dispatch)
|
|
614
|
+
env: providerProcessEnv(dispatch, context.runtimeEnvironment),
|
|
615
|
+
stdin: pipePromptToStdin ? providerPrompt : undefined
|
|
520
616
|
}, {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
617
|
+
onStarted: async () => {
|
|
618
|
+
await context.emit({
|
|
619
|
+
role: "runner",
|
|
620
|
+
kind: "checkpoint",
|
|
621
|
+
content: "Provider CLI QA is supervised and ready.",
|
|
622
|
+
eventType: "provider_cli_started"
|
|
623
|
+
});
|
|
624
|
+
},
|
|
625
|
+
onStdoutLine: async (line) => await toolHeartbeat.consume(line)
|
|
526
626
|
}, context.signal);
|
|
527
627
|
}
|
|
528
628
|
finally {
|
|
629
|
+
toolHeartbeat.stop();
|
|
529
630
|
await liveQaEvents.stop();
|
|
530
631
|
await cleanupCompletionTools(completionTools, context.emit);
|
|
531
632
|
}
|
|
@@ -547,7 +648,7 @@ export class ProcessBackedProviderCliService {
|
|
|
547
648
|
? "Provider CLI QA completed but did not produce the complete durable QA artifact bundle."
|
|
548
649
|
: "Provider CLI QA completed but did not call the Forge QA completion tool.");
|
|
549
650
|
}
|
|
550
|
-
enforceQaBrowserLaunchTargetGate(dispatch, qaArtifact);
|
|
651
|
+
await enforceQaBrowserLaunchTargetGate(dispatch, qaArtifact);
|
|
551
652
|
if (!liveQaEvents.hasEmitted()) {
|
|
552
653
|
await emitQaArtifactRecorded(context.emit, worktree.worktreePath, qaArtifact);
|
|
553
654
|
await emitQaStructuredEvents(context.emit, qaArtifact);
|
|
@@ -581,39 +682,114 @@ export class ProcessBackedProviderCliService {
|
|
|
581
682
|
const { dispatch, worktree } = context;
|
|
582
683
|
const repairChecks = route === "ci_repair" || isPullRequestCheckRepairDispatch(dispatch);
|
|
583
684
|
const addressComments = !repairChecks && isPullRequestCommentsDispatch(dispatch);
|
|
685
|
+
let repairSpec = null;
|
|
686
|
+
let repairEnvironment = context.runtimeEnvironment;
|
|
687
|
+
let completionTools = null;
|
|
688
|
+
if (repairChecks) {
|
|
689
|
+
const metadataRoot = path.join(worktree.worktreePath, ".forge", "work-items", dispatch.workItem.id);
|
|
690
|
+
await fs.mkdir(metadataRoot, { recursive: true });
|
|
691
|
+
const laneCommand = selectLaneCommand(dispatch.artifacts, dispatch.execution.lane, dispatch.cycleRun.phase);
|
|
692
|
+
if (!laneCommand) {
|
|
693
|
+
throw new Error(`CI repair readiness failed: lane command for '${dispatch.execution.lane}' was not resolved, so the approved spec cannot be materialized.`);
|
|
694
|
+
}
|
|
695
|
+
const specBody = buildSpecBody(laneCommand, dispatch.workItem, "ci_repair");
|
|
696
|
+
const specPath = path.join(metadataRoot, "spec.md");
|
|
697
|
+
await fs.writeFile(specPath, specBody, "utf8");
|
|
698
|
+
await fs.writeFile(path.join(metadataRoot, "spec.meta.json"), `${JSON.stringify({ schema_version: 1, status: "approved", produced_by: "forge-lane-command" })}\n`, "utf8");
|
|
699
|
+
repairSpec = {
|
|
700
|
+
repoPath: worktree.worktreePath,
|
|
701
|
+
path: specPath,
|
|
702
|
+
body: specBody,
|
|
703
|
+
producedBy: "forge-lane-command",
|
|
704
|
+
status: "approved",
|
|
705
|
+
};
|
|
706
|
+
const readiness = await writeCiRepairReadinessManifest(dispatch, worktree.worktreePath, specPath, context.runtimeEnvironment);
|
|
707
|
+
repairEnvironment = readiness.environment;
|
|
708
|
+
completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: "ci_repair", runtimeEnvironment: repairEnvironment });
|
|
709
|
+
await context.emit({
|
|
710
|
+
role: "runner",
|
|
711
|
+
kind: "checkpoint",
|
|
712
|
+
content: `CI repair readiness contract prepared at ${path.relative(worktree.worktreePath, readiness.path).replaceAll(path.sep, "/")}; approved spec, attempt URLs, browser readiness, and local-equivalence policy are available before provider start.`,
|
|
713
|
+
eventType: "ci_repair_readiness_ready",
|
|
714
|
+
});
|
|
715
|
+
await context.emit({
|
|
716
|
+
role: "runner",
|
|
717
|
+
kind: "checkpoint",
|
|
718
|
+
content: "Forge CI repair completion tool configured; provider must call forge.complete_ci_repair before Bridge-owned commit and push.",
|
|
719
|
+
eventType: "completion_tools_configured",
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
const repairPrompt = repairChecks
|
|
723
|
+
? buildPullRequestCheckRepairPrompt(dispatch, worktree.branchName)
|
|
724
|
+
: addressComments
|
|
725
|
+
? buildPullRequestCommentsPrompt(dispatch, worktree.branchName)
|
|
726
|
+
: buildConflictResolvePrompt(dispatch, worktree.branchName);
|
|
727
|
+
const providerPrompt = buildProviderCliPrompt(dispatch, repairPrompt, repairEnvironment);
|
|
728
|
+
const pipePromptToStdin = shouldPipePromptToStdin(dispatch.execution.provider, providerPrompt, usesManagedWindowsHost(dispatch));
|
|
584
729
|
const command = buildProviderCliCommand(dispatch, {
|
|
585
730
|
worktreePath: worktree.worktreePath,
|
|
586
|
-
prompt:
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
: buildConflictResolvePrompt(dispatch, worktree.branchName)
|
|
731
|
+
prompt: repairPrompt,
|
|
732
|
+
promptFromStdin: pipePromptToStdin,
|
|
733
|
+
completionTools: completionTools ?? undefined,
|
|
734
|
+
runtimeEnvironment: repairEnvironment
|
|
591
735
|
});
|
|
592
736
|
await context.emit({
|
|
593
737
|
role: "runner",
|
|
594
738
|
kind: "checkpoint",
|
|
595
739
|
content: `Starting provider CLI ${repairChecks ? "CI repair" : addressComments ? "PR comment address" : "conflict resolve"}: ${formatProviderCliCommandForCheckpoint(command)} in ${worktree.worktreePath}`,
|
|
596
|
-
eventType: "
|
|
740
|
+
eventType: "provider_cli_starting"
|
|
597
741
|
});
|
|
598
742
|
const outputState = createProviderOutputState(dispatch.execution.provider);
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
743
|
+
const toolHeartbeat = new ProviderToolActivityHeartbeat(outputState, context.emit, this.providerToolHeartbeatIntervalMs);
|
|
744
|
+
let result;
|
|
745
|
+
try {
|
|
746
|
+
result = await (context.processRunner ?? this.processRunner).run({
|
|
747
|
+
command: command.command,
|
|
748
|
+
args: command.args,
|
|
749
|
+
cwd: worktree.worktreePath,
|
|
750
|
+
env: providerProcessEnv(dispatch, repairEnvironment),
|
|
751
|
+
stdin: pipePromptToStdin ? providerPrompt : undefined
|
|
752
|
+
}, {
|
|
753
|
+
onStarted: async () => {
|
|
754
|
+
await context.emit({
|
|
755
|
+
role: "runner",
|
|
756
|
+
kind: "checkpoint",
|
|
757
|
+
content: `Provider CLI ${repairChecks ? "CI repair" : addressComments ? "PR comment address" : "conflict resolve"} is supervised and ready.`,
|
|
758
|
+
eventType: "provider_cli_started"
|
|
759
|
+
});
|
|
760
|
+
},
|
|
761
|
+
onStdoutLine: async (line) => {
|
|
762
|
+
await toolHeartbeat.consume(line);
|
|
608
763
|
}
|
|
764
|
+
}, context.signal);
|
|
765
|
+
}
|
|
766
|
+
finally {
|
|
767
|
+
toolHeartbeat.stop();
|
|
768
|
+
if (completionTools) {
|
|
769
|
+
await cleanupCompletionTools(completionTools, context.emit);
|
|
609
770
|
}
|
|
610
|
-
}
|
|
771
|
+
}
|
|
611
772
|
if (result.exitCode !== 0) {
|
|
612
773
|
const stderr = clip(result.stderr.trim() || result.stdout.trim(), 1200);
|
|
613
774
|
throw new Error(`${command.command} exited ${result.exitCode}${stderr ? `: ${stderr}` : ""}`);
|
|
614
775
|
}
|
|
776
|
+
let repairArtifact = null;
|
|
777
|
+
if (repairChecks) {
|
|
778
|
+
repairArtifact = await readCompletionArtifact(worktree.worktreePath, dispatch.workItem.id, "ci_repair");
|
|
779
|
+
if (!repairArtifact) {
|
|
780
|
+
throw new Error("Provider CLI completed but did not call forge.complete_ci_repair. Forge did not commit or push the repair.");
|
|
781
|
+
}
|
|
782
|
+
if ((repairArtifact.status ?? "").trim().toLowerCase() !== "pass") {
|
|
783
|
+
throw new Error(`CI repair completion gate recorded ${repairArtifact.status}; Forge did not commit or push the repair.`);
|
|
784
|
+
}
|
|
785
|
+
await emitCompletionArtifactRecorded(context.emit, worktree.worktreePath, repairArtifact);
|
|
786
|
+
await emitPhaseCompletionEvent(context.emit, repairArtifact);
|
|
787
|
+
await emitPhaseGateEvents(context.emit, repairArtifact);
|
|
788
|
+
}
|
|
615
789
|
const committed = await commitWorktreeChanges(this.git, worktree.worktreePath, dispatch.workItem.id);
|
|
616
|
-
|
|
790
|
+
if (providerOwnsBranchPublication(context)) {
|
|
791
|
+
await pushWorktreeBranch(this.git, worktree.worktreePath, worktree.branchName, context.emit);
|
|
792
|
+
}
|
|
617
793
|
await emitProviderUsageEvent(context.emit, dispatch, outputState);
|
|
618
794
|
await context.emit({
|
|
619
795
|
role: "assistant",
|
|
@@ -630,7 +806,10 @@ export class ProcessBackedProviderCliService {
|
|
|
630
806
|
? `${dispatch.execution.provider} CLI repaired PR checks on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
|
|
631
807
|
: addressComments
|
|
632
808
|
? `${dispatch.execution.provider} CLI addressed PR comments on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`
|
|
633
|
-
: `${dispatch.execution.provider} CLI resolved conflicts on ${worktree.branchName} (${committed.commitSha.slice(0, 12)})
|
|
809
|
+
: `${dispatch.execution.provider} CLI resolved conflicts on ${worktree.branchName} (${committed.commitSha.slice(0, 12)}).`,
|
|
810
|
+
spec: repairSpec,
|
|
811
|
+
artifact: repairArtifact,
|
|
812
|
+
artifacts: repairArtifact ? [repairArtifact] : [],
|
|
634
813
|
};
|
|
635
814
|
}
|
|
636
815
|
}
|
|
@@ -696,8 +875,12 @@ function clipFileName(fileName, maxLength) {
|
|
|
696
875
|
return `${stem.slice(0, maxStemLength)}${extension}`;
|
|
697
876
|
}
|
|
698
877
|
const providerPromptStdinThreshold = 8_000;
|
|
699
|
-
function shouldPipePromptToStdin(provider, prompt) {
|
|
700
|
-
return provider.trim().toLowerCase() === "codex"
|
|
878
|
+
function shouldPipePromptToStdin(provider, prompt, force = false) {
|
|
879
|
+
return provider.trim().toLowerCase() === "codex"
|
|
880
|
+
&& (force || Buffer.byteLength(prompt, "utf8") > providerPromptStdinThreshold);
|
|
881
|
+
}
|
|
882
|
+
function usesManagedWindowsHost(dispatch) {
|
|
883
|
+
return process.platform === "win32" && dispatch.sandbox?.executionMode === "host";
|
|
701
884
|
}
|
|
702
885
|
function isFakeWorkerModel(model) {
|
|
703
886
|
return model.trim().toLowerCase() === fakeWorkerModel;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { RunOnceDispatchPacket, RunnerDispatchArtifact, RunnerDispatchEvent, RunnerDispatchSpec } from "../session.types.js";
|
|
2
2
|
import type { WorktreeHandle } from "../../worktree/worktree.types.js";
|
|
3
|
+
import type { ProviderProcessRunner } from "./provider-cli.process.js";
|
|
3
4
|
export interface ProviderCliRunContext {
|
|
4
5
|
readonly dispatch: RunOnceDispatchPacket;
|
|
5
6
|
readonly worktree: WorktreeHandle;
|
|
7
|
+
readonly runtimeEnvironment?: Readonly<Record<string, string>>;
|
|
8
|
+
readonly processRunner?: ProviderProcessRunner;
|
|
6
9
|
readonly signal?: AbortSignal;
|
|
7
10
|
emit(event: RunnerDispatchEvent): Promise<void>;
|
|
8
11
|
}
|
|
9
12
|
export interface ProviderCliDraftContext {
|
|
10
13
|
readonly dispatch: RunOnceDispatchPacket;
|
|
11
14
|
readonly worktree: WorktreeHandle;
|
|
15
|
+
readonly runtimeEnvironment?: Readonly<Record<string, string>>;
|
|
16
|
+
readonly processRunner?: ProviderProcessRunner;
|
|
12
17
|
readonly signal?: AbortSignal;
|
|
13
18
|
emit(event: RunnerDispatchEvent): Promise<void>;
|
|
14
19
|
}
|
|
@@ -5,6 +5,8 @@ export declare function clip(value: string, maxLength: number): string;
|
|
|
5
5
|
export interface ProviderOutputState {
|
|
6
6
|
readonly sessionLabel: string | null;
|
|
7
7
|
finalMessage: string | null;
|
|
8
|
+
/** True only while the provider has reported a tool/command start without a matching completion. */
|
|
9
|
+
readonly hasActiveToolCall: boolean;
|
|
8
10
|
/** Accumulated provider-reported token usage for this run, or null when none was reported. */
|
|
9
11
|
readonly usage: ProviderUsage | null;
|
|
10
12
|
consume(line: string): readonly RunnerDispatchEvent[];
|