@cat-factory/executor-harness 1.50.14 → 1.50.18
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/dist/agent-runner.js +55 -47
- package/dist/agent.js +46 -22
- package/dist/coding-agent.js +15 -2
- package/dist/effort.js +84 -0
- package/dist/job.js +42 -24
- package/dist/pi-workspace.js +14 -2
- package/package.json +3 -3
- package/src/agent-runner.ts +67 -48
- package/src/agent.ts +171 -123
- package/src/coding-agent.ts +38 -21
- package/src/effort.ts +99 -0
- package/src/job.ts +50 -23
- package/src/pi-workspace.ts +15 -2
- package/src/pi.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/executor-harness",
|
|
3
|
-
"version": "1.50.
|
|
3
|
+
"version": "1.50.18",
|
|
4
4
|
"description": "Container payload: a thin TypeScript wrapper that runs the Pi coding agent against a cloned repo and opens a PR. Runs in the Cloudflare Container (and, in local native mode, as a host process); carries no secrets.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"hono": "^4.12.30",
|
|
27
27
|
"typescript": "7.0.2",
|
|
28
28
|
"vitest": "^4.1.10",
|
|
29
|
-
"@cat-factory/server": "0.
|
|
30
|
-
"@cat-factory/spend": "0.12.
|
|
29
|
+
"@cat-factory/server": "0.143.1",
|
|
30
|
+
"@cat-factory/spend": "0.12.75"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsc -p tsconfig.json",
|
package/src/agent-runner.ts
CHANGED
|
@@ -424,21 +424,7 @@ export async function runClaudeCode(opts: SubscriptionRunOptions): Promise<PiRun
|
|
|
424
424
|
await writeNativeSkill(skillsRoot, opts.skill).catch(() => {})
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
|
|
428
|
-
// non-Anthropic Claude-Code vendor (GLM via Z.ai, Kimi via Moonshot, DeepSeek)
|
|
429
|
-
// points Claude Code at its Anthropic-compatible endpoint with an auth-token key.
|
|
430
|
-
// Ambient mode injects neither — the CLI uses the developer's logged-in `~/.claude`.
|
|
431
|
-
const env: Record<string, string> = opts.ambientAuth
|
|
432
|
-
? {}
|
|
433
|
-
: {
|
|
434
|
-
CLAUDE_CONFIG_DIR: configHome!,
|
|
435
|
-
...(opts.subscriptionBaseUrl
|
|
436
|
-
? {
|
|
437
|
-
ANTHROPIC_BASE_URL: opts.subscriptionBaseUrl,
|
|
438
|
-
ANTHROPIC_AUTH_TOKEN: opts.subscriptionToken!,
|
|
439
|
-
}
|
|
440
|
-
: { CLAUDE_CODE_OAUTH_TOKEN: opts.subscriptionToken! }),
|
|
441
|
-
}
|
|
427
|
+
const env = buildClaudeEnv(opts, configHome)
|
|
442
428
|
|
|
443
429
|
// ADR 0026 D3 (path corrected by ADR 0027 Defect A): while the run is live, tail the CLI's
|
|
444
430
|
// subagent `*.jsonl` transcripts so a parallel-subagent review keeps the inactivity
|
|
@@ -484,39 +470,7 @@ export async function runClaudeCode(opts: SubscriptionRunOptions): Promise<PiRun
|
|
|
484
470
|
onEvent,
|
|
485
471
|
)
|
|
486
472
|
|
|
487
|
-
|
|
488
|
-
// subagent calls, which carry their own per-turn tokens, are concatenated).
|
|
489
|
-
attributeCumulativeUsage(calls, usage)
|
|
490
|
-
// Final drain of any subagent transcript writes that landed after the last poll, then
|
|
491
|
-
// fold the subagents' usage + per-call telemetry into the run's outcome — their tokens
|
|
492
|
-
// never appear on the parent stream, so this is the only place they are accounted.
|
|
493
|
-
await subagents?.stop()
|
|
494
|
-
const subUsage = subagents?.usage() ?? { inputTokens: 0, outputTokens: 0 }
|
|
495
|
-
const subCalls = subagents?.calls() ?? []
|
|
496
|
-
const mergedCalls = [...calls, ...subCalls]
|
|
497
|
-
// INVARIANT (do not "fix" this into a double count): the run total is the parent usage
|
|
498
|
-
// PLUS the subagent usage because the two are disjoint sources. The parent `usage` here
|
|
499
|
-
// is the terminal `result` event's cumulative, which covers ONLY the parent loop — the
|
|
500
|
-
// ADR 0026 incident is itself the proof: a heavily subagent-parallelised review reported
|
|
501
|
-
// ~0 tokens, i.e. the parent stream (and its `result` total) never included the subagent
|
|
502
|
-
// spend. The subagent tokens live exclusively in the per-session `subagents/*.jsonl`
|
|
503
|
-
// transcripts, which the watcher reads and nothing else does; it deliberately EXCLUDES the
|
|
504
|
-
// sibling parent session transcript (whose usage `result` already totals), so neither
|
|
505
|
-
// `calls` nor `usage` can already contain the subagent spend.
|
|
506
|
-
const mergedUsage =
|
|
507
|
-
usage || subUsage.inputTokens || subUsage.outputTokens
|
|
508
|
-
? {
|
|
509
|
-
inputTokens: (usage?.inputTokens ?? 0) + subUsage.inputTokens,
|
|
510
|
-
outputTokens: (usage?.outputTokens ?? 0) + subUsage.outputTokens,
|
|
511
|
-
}
|
|
512
|
-
: undefined
|
|
513
|
-
return {
|
|
514
|
-
summary,
|
|
515
|
-
stats,
|
|
516
|
-
stderrTail,
|
|
517
|
-
...(mergedUsage ? { usage: mergedUsage } : {}),
|
|
518
|
-
...(mergedCalls.length ? { callMetrics: mergedCalls } : {}),
|
|
519
|
-
}
|
|
473
|
+
return await assembleClaudeOutcome({ summary, stats, stderrTail, calls, usage, subagents })
|
|
520
474
|
} finally {
|
|
521
475
|
await subagents?.stop()
|
|
522
476
|
if (configHome) {
|
|
@@ -533,6 +487,71 @@ export async function runClaudeCode(opts: SubscriptionRunOptions): Promise<PiRun
|
|
|
533
487
|
}
|
|
534
488
|
}
|
|
535
489
|
|
|
490
|
+
/**
|
|
491
|
+
* Build the child-process env for the `claude` CLI: an isolated config home plus subscription
|
|
492
|
+
* auth (Anthropic OAuth token, or an Anthropic-compatible base URL + auth token for a
|
|
493
|
+
* non-Anthropic Claude-Code vendor like GLM/Kimi/DeepSeek), or an empty env in ambient mode
|
|
494
|
+
* (the developer's own logged-in `~/.claude` is used). Extracted from {@link runClaudeCode} to
|
|
495
|
+
* keep its cyclomatic complexity down; behaviour is a straight move of the original expression.
|
|
496
|
+
*/
|
|
497
|
+
function buildClaudeEnv(
|
|
498
|
+
opts: SubscriptionRunOptions,
|
|
499
|
+
configHome: string | undefined,
|
|
500
|
+
): Record<string, string> {
|
|
501
|
+
if (opts.ambientAuth) return {}
|
|
502
|
+
return {
|
|
503
|
+
CLAUDE_CONFIG_DIR: configHome!,
|
|
504
|
+
...(opts.subscriptionBaseUrl
|
|
505
|
+
? {
|
|
506
|
+
ANTHROPIC_BASE_URL: opts.subscriptionBaseUrl,
|
|
507
|
+
ANTHROPIC_AUTH_TOKEN: opts.subscriptionToken!,
|
|
508
|
+
}
|
|
509
|
+
: { CLAUDE_CODE_OAUTH_TOKEN: opts.subscriptionToken! }),
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Merge the parent-loop telemetry with the subagents' out-of-band usage + per-call metrics into
|
|
515
|
+
* the run outcome. INVARIANT (do not "fix" this into a double count): the run total is the parent
|
|
516
|
+
* usage PLUS the subagent usage because the two are disjoint sources — the parent `usage` (the
|
|
517
|
+
* terminal `result` event's cumulative) covers ONLY the parent loop, and the subagent tokens live
|
|
518
|
+
* exclusively in the per-session `subagents/*.jsonl` transcripts the watcher reads. Extracted from
|
|
519
|
+
* {@link runClaudeCode} verbatim to keep its cyclomatic complexity down.
|
|
520
|
+
*/
|
|
521
|
+
async function assembleClaudeOutcome(args: {
|
|
522
|
+
summary: string
|
|
523
|
+
stats: PiRunStats
|
|
524
|
+
stderrTail: string
|
|
525
|
+
calls: HarnessCallMetric[]
|
|
526
|
+
usage: { inputTokens: number; outputTokens: number } | undefined
|
|
527
|
+
subagents: ReturnType<typeof startSubagentWatcher> | undefined
|
|
528
|
+
}): Promise<PiRunOutcome> {
|
|
529
|
+
const { summary, stats, stderrTail, calls, usage, subagents } = args
|
|
530
|
+
// The parent's cumulative-usage fallback applies to the PARENT calls only (before the
|
|
531
|
+
// subagent calls, which carry their own per-turn tokens, are concatenated).
|
|
532
|
+
attributeCumulativeUsage(calls, usage)
|
|
533
|
+
// Final drain of any subagent transcript writes that landed after the last poll, then
|
|
534
|
+
// fold the subagents' usage + per-call telemetry into the run's outcome.
|
|
535
|
+
await subagents?.stop()
|
|
536
|
+
const subUsage = subagents?.usage() ?? { inputTokens: 0, outputTokens: 0 }
|
|
537
|
+
const subCalls = subagents?.calls() ?? []
|
|
538
|
+
const mergedCalls = [...calls, ...subCalls]
|
|
539
|
+
const mergedUsage =
|
|
540
|
+
usage || subUsage.inputTokens || subUsage.outputTokens
|
|
541
|
+
? {
|
|
542
|
+
inputTokens: (usage?.inputTokens ?? 0) + subUsage.inputTokens,
|
|
543
|
+
outputTokens: (usage?.outputTokens ?? 0) + subUsage.outputTokens,
|
|
544
|
+
}
|
|
545
|
+
: undefined
|
|
546
|
+
return {
|
|
547
|
+
summary,
|
|
548
|
+
stats,
|
|
549
|
+
stderrTail,
|
|
550
|
+
...(mergedUsage ? { usage: mergedUsage } : {}),
|
|
551
|
+
...(mergedCalls.length ? { callMetrics: mergedCalls } : {}),
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
536
555
|
/** Map Claude Code's `TodoWrite` todos array onto subtask counts. */
|
|
537
556
|
function todosToProgress(todos: unknown): TodoProgress | undefined {
|
|
538
557
|
if (!Array.isArray(todos)) return undefined
|
package/src/agent.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
unmergedPaths,
|
|
32
32
|
} from './git.js'
|
|
33
33
|
import type { PiRunStats, RunDiagnostics } from './pi.js'
|
|
34
|
+
import type { EffortReport } from './effort.js'
|
|
34
35
|
import {
|
|
35
36
|
makeDirClaimer,
|
|
36
37
|
noChangesReason,
|
|
@@ -312,6 +313,15 @@ async function cloneServiceCheckout(
|
|
|
312
313
|
return deriveWorkDir(dir, job.repo.serviceDirectory)
|
|
313
314
|
}
|
|
314
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Fold an agent's effort self-assessment (lifted from its sentinel file by `runAgentInWorkspace`)
|
|
318
|
+
* onto its final result. Every container mode routes its result through this so the report reaches
|
|
319
|
+
* the backend uniformly. A run that wrote no report passes through unchanged.
|
|
320
|
+
*/
|
|
321
|
+
function mergeEffort(result: AgentResult, effortReport: EffortReport | undefined): AgentResult {
|
|
322
|
+
return effortReport ? { ...result, effortReport } : result
|
|
323
|
+
}
|
|
324
|
+
|
|
315
325
|
/** Run one generic agent job end to end, dispatching on `mode`. */
|
|
316
326
|
export async function handleAgent(job: AgentJob, opts: RunOptions = {}): Promise<AgentResult> {
|
|
317
327
|
// Private-registry auth first, before any mode runs: every mode with a checkout may
|
|
@@ -558,6 +568,7 @@ async function runExploreMode(job: AgentJob, opts: RunOptions): Promise<AgentRes
|
|
|
558
568
|
usage,
|
|
559
569
|
callMetrics,
|
|
560
570
|
diagnostics: runDiag,
|
|
571
|
+
effortReport,
|
|
561
572
|
} = await runAgentInWorkspace(
|
|
562
573
|
{
|
|
563
574
|
dir: workDir,
|
|
@@ -582,10 +593,13 @@ async function runExploreMode(job: AgentJob, opts: RunOptions): Promise<AgentRes
|
|
|
582
593
|
opts,
|
|
583
594
|
)
|
|
584
595
|
|
|
585
|
-
return
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
596
|
+
return mergeEffort(
|
|
597
|
+
await finalizeExploreResult(
|
|
598
|
+
job,
|
|
599
|
+
{ summary, stats, stderrTail, usage, callMetrics, runDiag },
|
|
600
|
+
{ infra, infraSetupFields, logger, signal: opts.signal },
|
|
601
|
+
),
|
|
602
|
+
effortReport,
|
|
589
603
|
)
|
|
590
604
|
} finally {
|
|
591
605
|
restoreSecrets()
|
|
@@ -808,17 +822,20 @@ async function runMultiRepoExplore(job: AgentJob, opts: RunOptions): Promise<Age
|
|
|
808
822
|
},
|
|
809
823
|
opts,
|
|
810
824
|
)
|
|
811
|
-
return
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
825
|
+
return mergeEffort(
|
|
826
|
+
await finalizeExploreResult(
|
|
827
|
+
job,
|
|
828
|
+
{
|
|
829
|
+
summary: run.summary,
|
|
830
|
+
stats: run.stats,
|
|
831
|
+
stderrTail: run.stderrTail,
|
|
832
|
+
usage: run.usage,
|
|
833
|
+
callMetrics: run.callMetrics,
|
|
834
|
+
runDiag: run.diagnostics,
|
|
835
|
+
},
|
|
836
|
+
{ infraSetupFields: {}, logger, signal: opts.signal },
|
|
837
|
+
),
|
|
838
|
+
run.effortReport,
|
|
822
839
|
)
|
|
823
840
|
})
|
|
824
841
|
}
|
|
@@ -886,6 +903,55 @@ async function runCodingMode(job: AgentJob, opts: RunOptions): Promise<AgentResu
|
|
|
886
903
|
return result
|
|
887
904
|
}
|
|
888
905
|
|
|
906
|
+
/**
|
|
907
|
+
* Assemble the {@link runCodingAgent} spec for the ordinary single-repo coding flow. Extracted
|
|
908
|
+
* from {@link runSingleRepoCoding} so the many optional-field spreads don't inflate that
|
|
909
|
+
* function's cyclomatic complexity; the mapping is a straight field copy off `job`.
|
|
910
|
+
*/
|
|
911
|
+
function buildSingleRepoCodingSpec(
|
|
912
|
+
job: AgentJob,
|
|
913
|
+
pushBranch: string,
|
|
914
|
+
): Parameters<typeof runCodingAgent>[0] {
|
|
915
|
+
return {
|
|
916
|
+
kind: 'agent',
|
|
917
|
+
jobId: job.jobId,
|
|
918
|
+
repo: job.repo,
|
|
919
|
+
cloneBranch: job.branch,
|
|
920
|
+
...(job.newBranch ? { newBranch: job.newBranch } : {}),
|
|
921
|
+
pushBranch,
|
|
922
|
+
ghToken: job.ghToken,
|
|
923
|
+
systemPrompt: job.systemPrompt,
|
|
924
|
+
userPrompt: job.userPrompt,
|
|
925
|
+
model: job.model,
|
|
926
|
+
harness: job.harness,
|
|
927
|
+
subscriptionToken: job.subscriptionToken,
|
|
928
|
+
subscriptionBaseUrl: job.subscriptionBaseUrl,
|
|
929
|
+
ambientAuth: job.ambientAuth,
|
|
930
|
+
proxyBaseUrl: job.proxyBaseUrl,
|
|
931
|
+
sessionToken: job.sessionToken,
|
|
932
|
+
commitMessage: job.commitMessage ?? job.pr?.title ?? 'Agent changes',
|
|
933
|
+
webToolsGuidance: job.webToolsGuidance,
|
|
934
|
+
webSearchProxy: job.webSearch,
|
|
935
|
+
guardLimits: job.guardLimits,
|
|
936
|
+
...(job.persistentCheckout ? { persistentCheckout: true } : {}),
|
|
937
|
+
...(job.streamFollowUps ? { streamFollowUps: true } : {}),
|
|
938
|
+
...(job.referenceBranches?.length ? { referenceBranches: job.referenceBranches } : {}),
|
|
939
|
+
// Repo-sourced skill (slice 2): installed harness-aware by runAgentInWorkspace.
|
|
940
|
+
...(job.skill ? { skill: job.skill } : {}),
|
|
941
|
+
// Ralph loop: run the completion command after the agent commits and report its verdict.
|
|
942
|
+
...(job.validation
|
|
943
|
+
? {
|
|
944
|
+
validation: {
|
|
945
|
+
command: job.validation.command,
|
|
946
|
+
...(job.validation.iteration !== undefined
|
|
947
|
+
? { iteration: job.validation.iteration }
|
|
948
|
+
: {}),
|
|
949
|
+
},
|
|
950
|
+
}
|
|
951
|
+
: {}),
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
889
955
|
/**
|
|
890
956
|
* The ordinary single-repo coding flow: clone `branch` (or resume `newBranch`), run the agent,
|
|
891
957
|
* commit + push to `pushBranch`, and open `pr` when one is set and the run produced changes. A
|
|
@@ -894,51 +960,13 @@ async function runCodingMode(job: AgentJob, opts: RunOptions): Promise<AgentResu
|
|
|
894
960
|
*/
|
|
895
961
|
async function runSingleRepoCoding(job: AgentJob, opts: RunOptions): Promise<AgentResult> {
|
|
896
962
|
const pushBranch = job.pushBranch ?? job.newBranch ?? job.branch
|
|
897
|
-
const { summary, stats, stderrTail, pushed, usage, callMetrics, validation } =
|
|
898
|
-
await runCodingAgent(
|
|
899
|
-
{
|
|
900
|
-
kind: 'agent',
|
|
901
|
-
jobId: job.jobId,
|
|
902
|
-
repo: job.repo,
|
|
903
|
-
cloneBranch: job.branch,
|
|
904
|
-
...(job.newBranch ? { newBranch: job.newBranch } : {}),
|
|
905
|
-
pushBranch,
|
|
906
|
-
ghToken: job.ghToken,
|
|
907
|
-
systemPrompt: job.systemPrompt,
|
|
908
|
-
userPrompt: job.userPrompt,
|
|
909
|
-
model: job.model,
|
|
910
|
-
harness: job.harness,
|
|
911
|
-
subscriptionToken: job.subscriptionToken,
|
|
912
|
-
subscriptionBaseUrl: job.subscriptionBaseUrl,
|
|
913
|
-
ambientAuth: job.ambientAuth,
|
|
914
|
-
proxyBaseUrl: job.proxyBaseUrl,
|
|
915
|
-
sessionToken: job.sessionToken,
|
|
916
|
-
commitMessage: job.commitMessage ?? job.pr?.title ?? 'Agent changes',
|
|
917
|
-
webToolsGuidance: job.webToolsGuidance,
|
|
918
|
-
webSearchProxy: job.webSearch,
|
|
919
|
-
guardLimits: job.guardLimits,
|
|
920
|
-
...(job.persistentCheckout ? { persistentCheckout: true } : {}),
|
|
921
|
-
...(job.streamFollowUps ? { streamFollowUps: true } : {}),
|
|
922
|
-
...(job.referenceBranches?.length ? { referenceBranches: job.referenceBranches } : {}),
|
|
923
|
-
// Repo-sourced skill (slice 2): installed harness-aware by runAgentInWorkspace.
|
|
924
|
-
...(job.skill ? { skill: job.skill } : {}),
|
|
925
|
-
// Ralph loop: run the completion command after the agent commits and report its verdict.
|
|
926
|
-
...(job.validation
|
|
927
|
-
? {
|
|
928
|
-
validation: {
|
|
929
|
-
command: job.validation.command,
|
|
930
|
-
...(job.validation.iteration !== undefined
|
|
931
|
-
? { iteration: job.validation.iteration }
|
|
932
|
-
: {}),
|
|
933
|
-
},
|
|
934
|
-
}
|
|
935
|
-
: {}),
|
|
936
|
-
},
|
|
937
|
-
opts,
|
|
938
|
-
)
|
|
963
|
+
const { summary, stats, stderrTail, pushed, usage, callMetrics, validation, effortReport } =
|
|
964
|
+
await runCodingAgent(buildSingleRepoCodingSpec(job, pushBranch), opts)
|
|
939
965
|
// Ralph loop: the harness-computed validation verdict, forwarded onto the coding result as
|
|
940
966
|
// `ralphVerdict` so the backend's `toRunResult` lifts it onto `AgentRunResult.ralphVerdict`.
|
|
941
967
|
const ralphVerdict = validation ? { ralphVerdict: validation } : {}
|
|
968
|
+
// The agent's effort self-assessment, spread onto every result path below (mirrors ralphVerdict).
|
|
969
|
+
const effort = effortReport ? { effortReport } : {}
|
|
942
970
|
|
|
943
971
|
if (!pushed) {
|
|
944
972
|
// A no-op: a failure for the implementer, a clean non-event for the fixers.
|
|
@@ -951,6 +979,7 @@ async function runSingleRepoCoding(job: AgentJob, opts: RunOptions): Promise<Age
|
|
|
951
979
|
...(usage ? { usage } : {}),
|
|
952
980
|
...(callMetrics ? { callMetrics } : {}),
|
|
953
981
|
...ralphVerdict,
|
|
982
|
+
...effort,
|
|
954
983
|
}
|
|
955
984
|
}
|
|
956
985
|
return {
|
|
@@ -962,6 +991,7 @@ async function runSingleRepoCoding(job: AgentJob, opts: RunOptions): Promise<Age
|
|
|
962
991
|
failureCause: 'no-changes',
|
|
963
992
|
...(usage ? { usage } : {}),
|
|
964
993
|
...(callMetrics ? { callMetrics } : {}),
|
|
994
|
+
...effort,
|
|
965
995
|
}
|
|
966
996
|
}
|
|
967
997
|
|
|
@@ -995,6 +1025,7 @@ async function runSingleRepoCoding(job: AgentJob, opts: RunOptions): Promise<Age
|
|
|
995
1025
|
stats,
|
|
996
1026
|
...(usage ? { usage } : {}),
|
|
997
1027
|
...(callMetrics ? { callMetrics } : {}),
|
|
1028
|
+
...effort,
|
|
998
1029
|
}
|
|
999
1030
|
}
|
|
1000
1031
|
return {
|
|
@@ -1010,6 +1041,7 @@ async function runSingleRepoCoding(job: AgentJob, opts: RunOptions): Promise<Age
|
|
|
1010
1041
|
failureCause: 'no-changes',
|
|
1011
1042
|
...(usage ? { usage } : {}),
|
|
1012
1043
|
...(callMetrics ? { callMetrics } : {}),
|
|
1044
|
+
...effort,
|
|
1013
1045
|
}
|
|
1014
1046
|
}
|
|
1015
1047
|
return {
|
|
@@ -1021,6 +1053,7 @@ async function runSingleRepoCoding(job: AgentJob, opts: RunOptions): Promise<Age
|
|
|
1021
1053
|
...(usage ? { usage } : {}),
|
|
1022
1054
|
...(callMetrics ? { callMetrics } : {}),
|
|
1023
1055
|
...ralphVerdict,
|
|
1056
|
+
...effort,
|
|
1024
1057
|
}
|
|
1025
1058
|
}
|
|
1026
1059
|
return {
|
|
@@ -1031,6 +1064,7 @@ async function runSingleRepoCoding(job: AgentJob, opts: RunOptions): Promise<Age
|
|
|
1031
1064
|
...(usage ? { usage } : {}),
|
|
1032
1065
|
...(callMetrics ? { callMetrics } : {}),
|
|
1033
1066
|
...ralphVerdict,
|
|
1067
|
+
...effort,
|
|
1034
1068
|
}
|
|
1035
1069
|
}
|
|
1036
1070
|
|
|
@@ -1100,23 +1134,24 @@ async function runConflictResolution(job: AgentJob, opts: RunOptions): Promise<A
|
|
|
1100
1134
|
const diff = await conflictDiff(dir, conflicted, signal)
|
|
1101
1135
|
const userPrompt = buildConflictPrompt(mergeBase, job.branch, conflicted, diff, job.userPrompt)
|
|
1102
1136
|
|
|
1103
|
-
const { summary, stats, stderrTail, usage, callMetrics } =
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1137
|
+
const { summary, stats, stderrTail, usage, callMetrics, effortReport } =
|
|
1138
|
+
await runAgentInWorkspace(
|
|
1139
|
+
{
|
|
1140
|
+
dir,
|
|
1141
|
+
systemPrompt: job.systemPrompt,
|
|
1142
|
+
userPrompt,
|
|
1143
|
+
model: job.model,
|
|
1144
|
+
harness: job.harness,
|
|
1145
|
+
subscriptionToken: job.subscriptionToken,
|
|
1146
|
+
subscriptionBaseUrl: job.subscriptionBaseUrl,
|
|
1147
|
+
ambientAuth: job.ambientAuth,
|
|
1148
|
+
proxyBaseUrl: job.proxyBaseUrl,
|
|
1149
|
+
sessionToken: job.sessionToken,
|
|
1150
|
+
contextFiles: job.contextFiles,
|
|
1151
|
+
guardLimits: job.guardLimits,
|
|
1152
|
+
},
|
|
1153
|
+
opts,
|
|
1154
|
+
)
|
|
1120
1155
|
|
|
1121
1156
|
// Never push a half-resolved tree: if any conflict markers / unmerged paths remain,
|
|
1122
1157
|
// the PR would still be broken. Fail so the engine can retry / notify.
|
|
@@ -1125,30 +1160,36 @@ async function runConflictResolution(job: AgentJob, opts: RunOptions): Promise<A
|
|
|
1125
1160
|
logger.error('agent(conflict): unresolved conflicts remain, refusing to push', {
|
|
1126
1161
|
unresolved: unresolved.length,
|
|
1127
1162
|
})
|
|
1128
|
-
return
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1163
|
+
return mergeEffort(
|
|
1164
|
+
{
|
|
1165
|
+
pushed: false,
|
|
1166
|
+
branch: job.branch,
|
|
1167
|
+
summary,
|
|
1168
|
+
stats,
|
|
1169
|
+
error: unresolvedReason(unresolved, stats, stderrTail),
|
|
1170
|
+
failureCause: 'agent',
|
|
1171
|
+
...(usage ? { usage } : {}),
|
|
1172
|
+
...(callMetrics ? { callMetrics } : {}),
|
|
1173
|
+
},
|
|
1174
|
+
effortReport,
|
|
1175
|
+
)
|
|
1138
1176
|
}
|
|
1139
1177
|
// Complete the merge commit with the agent's resolution staged, then push.
|
|
1140
1178
|
await commitAll(dir, `Merge ${mergeBase} into ${job.branch}`, signal)
|
|
1141
1179
|
opts.onPhase?.('push')
|
|
1142
1180
|
logger.info('agent(conflict): pushing resolved branch', { ...stats })
|
|
1143
1181
|
await pushBranch(dir, job.branch, job.ghToken, signal)
|
|
1144
|
-
return
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1182
|
+
return mergeEffort(
|
|
1183
|
+
{
|
|
1184
|
+
pushed: true,
|
|
1185
|
+
branch: job.branch,
|
|
1186
|
+
summary,
|
|
1187
|
+
stats,
|
|
1188
|
+
...(usage ? { usage } : {}),
|
|
1189
|
+
...(callMetrics ? { callMetrics } : {}),
|
|
1190
|
+
},
|
|
1191
|
+
effortReport,
|
|
1192
|
+
)
|
|
1152
1193
|
})
|
|
1153
1194
|
}
|
|
1154
1195
|
|
|
@@ -1239,22 +1280,23 @@ async function runBootstrap(job: AgentJob, opts: RunOptions): Promise<AgentResul
|
|
|
1239
1280
|
|
|
1240
1281
|
opts.onPhase?.('agent')
|
|
1241
1282
|
logger.info('agent(bootstrap): running agent')
|
|
1242
|
-
const { summary, stats, stderrTail, usage, callMetrics } =
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1283
|
+
const { summary, stats, stderrTail, usage, callMetrics, effortReport } =
|
|
1284
|
+
await runAgentInWorkspace(
|
|
1285
|
+
{
|
|
1286
|
+
dir,
|
|
1287
|
+
systemPrompt: job.systemPrompt,
|
|
1288
|
+
userPrompt: job.userPrompt,
|
|
1289
|
+
model: job.model,
|
|
1290
|
+
harness: job.harness,
|
|
1291
|
+
subscriptionToken: job.subscriptionToken,
|
|
1292
|
+
subscriptionBaseUrl: job.subscriptionBaseUrl,
|
|
1293
|
+
ambientAuth: job.ambientAuth,
|
|
1294
|
+
proxyBaseUrl: job.proxyBaseUrl,
|
|
1295
|
+
sessionToken: job.sessionToken,
|
|
1296
|
+
guardLimits: job.guardLimits,
|
|
1297
|
+
},
|
|
1298
|
+
opts,
|
|
1299
|
+
)
|
|
1258
1300
|
|
|
1259
1301
|
// Guard against a no-op run: Pi can exit cleanly having done nothing (e.g. it never
|
|
1260
1302
|
// reached the model), and a force-push would then publish an empty tree — leaving the
|
|
@@ -1263,14 +1305,17 @@ async function runBootstrap(job: AgentJob, opts: RunOptions): Promise<AgentResul
|
|
|
1263
1305
|
if (!(await producedRepoContent(dir, !fromScratch, signal))) {
|
|
1264
1306
|
const error = bootstrapNoOpReason(!fromScratch, stats, summary, stderrTail)
|
|
1265
1307
|
logger.error('agent(bootstrap): agent produced no content, refusing to push', { ...stats })
|
|
1266
|
-
return
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1308
|
+
return mergeEffort(
|
|
1309
|
+
{
|
|
1310
|
+
summary,
|
|
1311
|
+
stats,
|
|
1312
|
+
error,
|
|
1313
|
+
failureCause: 'agent',
|
|
1314
|
+
...(usage ? { usage } : {}),
|
|
1315
|
+
...(callMetrics ? { callMetrics } : {}),
|
|
1316
|
+
},
|
|
1317
|
+
effortReport,
|
|
1318
|
+
)
|
|
1274
1319
|
}
|
|
1275
1320
|
|
|
1276
1321
|
opts.onPhase?.('push')
|
|
@@ -1286,13 +1331,16 @@ async function runBootstrap(job: AgentJob, opts: RunOptions): Promise<AgentResul
|
|
|
1286
1331
|
: `Bootstrap from ${job.repo.owner}/${job.repo.name}`,
|
|
1287
1332
|
})
|
|
1288
1333
|
logger.info('agent(bootstrap): complete', { defaultBranch: boot.target.defaultBranch })
|
|
1289
|
-
return
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1334
|
+
return mergeEffort(
|
|
1335
|
+
{
|
|
1336
|
+
defaultBranch: boot.target.defaultBranch,
|
|
1337
|
+
summary,
|
|
1338
|
+
stats,
|
|
1339
|
+
...(usage ? { usage } : {}),
|
|
1340
|
+
...(callMetrics ? { callMetrics } : {}),
|
|
1341
|
+
},
|
|
1342
|
+
effortReport,
|
|
1343
|
+
)
|
|
1296
1344
|
})
|
|
1297
1345
|
}
|
|
1298
1346
|
|