@loopover/engine 1.0.0
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/CHANGELOG.md +94 -0
- package/LICENSE +661 -0
- package/README.md +676 -0
- package/dist/advisory/gate-advisory.d.ts +151 -0
- package/dist/advisory/gate-advisory.js +512 -0
- package/dist/ai-policy-map.d.ts +97 -0
- package/dist/ai-policy-map.js +377 -0
- package/dist/ams-policy-spec.d.ts +56 -0
- package/dist/ams-policy-spec.js +165 -0
- package/dist/calibration-dashboard.d.ts +23 -0
- package/dist/calibration-dashboard.js +65 -0
- package/dist/calibration-trend.d.ts +40 -0
- package/dist/calibration-trend.js +60 -0
- package/dist/contributor-fit.d.ts +24 -0
- package/dist/contributor-fit.js +50 -0
- package/dist/discovery-index-contract.d.ts +74 -0
- package/dist/discovery-index-contract.js +184 -0
- package/dist/discovery-soft-claim.d.ts +35 -0
- package/dist/discovery-soft-claim.js +50 -0
- package/dist/duplicate-winner.d.ts +57 -0
- package/dist/duplicate-winner.js +98 -0
- package/dist/feasibility.d.ts +27 -0
- package/dist/feasibility.js +86 -0
- package/dist/finding-severity-calibration.d.ts +116 -0
- package/dist/finding-severity-calibration.js +457 -0
- package/dist/fleet-run-manifest.d.ts +41 -0
- package/dist/fleet-run-manifest.js +151 -0
- package/dist/focus-manifest/guidance.d.ts +21 -0
- package/dist/focus-manifest/guidance.js +222 -0
- package/dist/focus-manifest.d.ts +826 -0
- package/dist/focus-manifest.js +2730 -0
- package/dist/gate-verdict-calibration.d.ts +111 -0
- package/dist/gate-verdict-calibration.js +411 -0
- package/dist/github/constants.d.ts +1 -0
- package/dist/github/constants.js +1 -0
- package/dist/github/sanitize-public-comment.d.ts +1 -0
- package/dist/github/sanitize-public-comment.js +26 -0
- package/dist/goal-model.d.ts +7 -0
- package/dist/goal-model.js +87 -0
- package/dist/governor/action-mode.d.ts +55 -0
- package/dist/governor/action-mode.js +74 -0
- package/dist/governor/budget-cap.d.ts +49 -0
- package/dist/governor/budget-cap.js +34 -0
- package/dist/governor/chokepoint.d.ts +58 -0
- package/dist/governor/chokepoint.js +269 -0
- package/dist/governor/kill-switch.d.ts +38 -0
- package/dist/governor/kill-switch.js +62 -0
- package/dist/governor/rate-limit.d.ts +40 -0
- package/dist/governor/rate-limit.js +63 -0
- package/dist/governor/reputation-throttle.d.ts +50 -0
- package/dist/governor/reputation-throttle.js +94 -0
- package/dist/governor/run-halt.d.ts +39 -0
- package/dist/governor/run-halt.js +83 -0
- package/dist/governor/self-plagiarism.d.ts +47 -0
- package/dist/governor/self-plagiarism.js +200 -0
- package/dist/governor/write-rate-limit.d.ts +52 -0
- package/dist/governor/write-rate-limit.js +132 -0
- package/dist/governor-ledger.d.ts +25 -0
- package/dist/governor-ledger.js +91 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +120 -0
- package/dist/issue-plan-decomposition.d.ts +18 -0
- package/dist/issue-plan-decomposition.js +56 -0
- package/dist/issue-rag-query.d.ts +12 -0
- package/dist/issue-rag-query.js +35 -0
- package/dist/local-scorer.d.ts +34 -0
- package/dist/local-scorer.js +36 -0
- package/dist/metadata-best-min-score.d.ts +9 -0
- package/dist/metadata-best-min-score.js +9 -0
- package/dist/metadata-best-pick.d.ts +9 -0
- package/dist/metadata-best-pick.js +9 -0
- package/dist/metadata-min-score.d.ts +9 -0
- package/dist/metadata-min-score.js +13 -0
- package/dist/metadata-top-min-score.d.ts +9 -0
- package/dist/metadata-top-min-score.js +13 -0
- package/dist/metadata-top-pick.d.ts +9 -0
- package/dist/metadata-top-pick.js +13 -0
- package/dist/miner/acceptance-criteria.d.ts +43 -0
- package/dist/miner/acceptance-criteria.js +77 -0
- package/dist/miner/agent-sdk-driver.d.ts +39 -0
- package/dist/miner/agent-sdk-driver.js +162 -0
- package/dist/miner/attempt-log.d.ts +29 -0
- package/dist/miner/attempt-log.js +100 -0
- package/dist/miner/attempt-metering.d.ts +39 -0
- package/dist/miner/attempt-metering.js +67 -0
- package/dist/miner/cli-subprocess-driver.d.ts +68 -0
- package/dist/miner/cli-subprocess-driver.js +272 -0
- package/dist/miner/coding-agent-driver.d.ts +33 -0
- package/dist/miner/coding-agent-driver.js +36 -0
- package/dist/miner/coding-agent-invoke.d.ts +11 -0
- package/dist/miner/coding-agent-invoke.js +80 -0
- package/dist/miner/coding-agent-mode.d.ts +25 -0
- package/dist/miner/coding-agent-mode.js +43 -0
- package/dist/miner/driver-factory.d.ts +82 -0
- package/dist/miner/driver-factory.js +169 -0
- package/dist/miner/harness-submission-trigger.d.ts +37 -0
- package/dist/miner/harness-submission-trigger.js +46 -0
- package/dist/miner/iterate-loop.d.ts +97 -0
- package/dist/miner/iterate-loop.js +278 -0
- package/dist/miner/iterate-policy.d.ts +98 -0
- package/dist/miner/iterate-policy.js +98 -0
- package/dist/miner/lint-guard.d.ts +50 -0
- package/dist/miner/lint-guard.js +68 -0
- package/dist/miner/local-write-tools.d.ts +81 -0
- package/dist/miner/local-write-tools.js +113 -0
- package/dist/miner/loop-reentry-policy.d.ts +36 -0
- package/dist/miner/loop-reentry-policy.js +53 -0
- package/dist/miner/repo-map.d.ts +51 -0
- package/dist/miner/repo-map.js +268 -0
- package/dist/miner/self-review-adapter.d.ts +106 -0
- package/dist/miner/self-review-adapter.js +83 -0
- package/dist/miner/submission-gate.d.ts +40 -0
- package/dist/miner/submission-gate.js +89 -0
- package/dist/miner/worktree-allocator.d.ts +59 -0
- package/dist/miner/worktree-allocator.js +60 -0
- package/dist/miner/worktree-pool.d.ts +55 -0
- package/dist/miner/worktree-pool.js +65 -0
- package/dist/miner-goal-lane-fit.d.ts +18 -0
- package/dist/miner-goal-lane-fit.js +76 -0
- package/dist/miner-goal-spec.d.ts +139 -0
- package/dist/miner-goal-spec.js +281 -0
- package/dist/miner-prediction-metrics.d.ts +16 -0
- package/dist/miner-prediction-metrics.js +61 -0
- package/dist/miner-telemetry.d.ts +32 -0
- package/dist/miner-telemetry.js +87 -0
- package/dist/objective-anchor.d.ts +125 -0
- package/dist/objective-anchor.js +401 -0
- package/dist/opportunity-competition.d.ts +6 -0
- package/dist/opportunity-competition.js +29 -0
- package/dist/opportunity-freshness.d.ts +18 -0
- package/dist/opportunity-freshness.js +55 -0
- package/dist/opportunity-metadata.d.ts +53 -0
- package/dist/opportunity-metadata.js +204 -0
- package/dist/opportunity-ranker.d.ts +46 -0
- package/dist/opportunity-ranker.js +74 -0
- package/dist/pairwise-calibration.d.ts +49 -0
- package/dist/pairwise-calibration.js +106 -0
- package/dist/phase7-calibration-loop.d.ts +136 -0
- package/dist/phase7-calibration-loop.js +437 -0
- package/dist/plan-blocked.d.ts +6 -0
- package/dist/plan-blocked.js +24 -0
- package/dist/plan-completed.d.ts +5 -0
- package/dist/plan-completed.js +6 -0
- package/dist/plan-completion.d.ts +6 -0
- package/dist/plan-completion.js +7 -0
- package/dist/plan-empty.d.ts +5 -0
- package/dist/plan-empty.js +6 -0
- package/dist/plan-export.d.ts +27 -0
- package/dist/plan-export.js +86 -0
- package/dist/plan-failure.d.ts +5 -0
- package/dist/plan-failure.js +6 -0
- package/dist/plan-overall-status.d.ts +6 -0
- package/dist/plan-overall-status.js +25 -0
- package/dist/plan-pending.d.ts +5 -0
- package/dist/plan-pending.js +6 -0
- package/dist/plan-progress-complete.d.ts +7 -0
- package/dist/plan-progress-complete.js +10 -0
- package/dist/plan-ready.d.ts +6 -0
- package/dist/plan-ready.js +12 -0
- package/dist/plan-running.d.ts +5 -0
- package/dist/plan-running.js +6 -0
- package/dist/plan-skipped.d.ts +5 -0
- package/dist/plan-skipped.js +6 -0
- package/dist/plan-step-count.d.ts +5 -0
- package/dist/plan-step-count.js +6 -0
- package/dist/plan-step-stats.d.ts +5 -0
- package/dist/plan-step-stats.js +6 -0
- package/dist/plan-templates.d.ts +25 -0
- package/dist/plan-templates.js +107 -0
- package/dist/plan-terminated.d.ts +6 -0
- package/dist/plan-terminated.js +9 -0
- package/dist/portfolio/non-convergence.d.ts +37 -0
- package/dist/portfolio/non-convergence.js +57 -0
- package/dist/portfolio/queue.d.ts +30 -0
- package/dist/portfolio/queue.js +150 -0
- package/dist/predicted-gate.d.ts +103 -0
- package/dist/predicted-gate.js +218 -0
- package/dist/prompt-packet.d.ts +11 -0
- package/dist/prompt-packet.js +34 -0
- package/dist/ranked-opportunity-best-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-best-min-score.js +9 -0
- package/dist/ranked-opportunity-best-pick.d.ts +8 -0
- package/dist/ranked-opportunity-best-pick.js +9 -0
- package/dist/ranked-opportunity-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-min-score.js +13 -0
- package/dist/ranked-opportunity-top-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-top-min-score.js +13 -0
- package/dist/review/advisory-ai-routing-config.d.ts +8 -0
- package/dist/review/advisory-ai-routing-config.js +40 -0
- package/dist/review/check-names.d.ts +1 -0
- package/dist/review/check-names.js +1 -0
- package/dist/review/cla-check.d.ts +49 -0
- package/dist/review/cla-check.js +71 -0
- package/dist/review/diff-file-priority.d.ts +1 -0
- package/dist/review/diff-file-priority.js +12 -0
- package/dist/review/enrichment-analyzer-names.d.ts +3 -0
- package/dist/review/enrichment-analyzer-names.js +63 -0
- package/dist/review/guardrail-config.d.ts +10 -0
- package/dist/review/guardrail-config.js +62 -0
- package/dist/review/linked-issue-hard-rules-config.d.ts +4 -0
- package/dist/review/linked-issue-hard-rules-config.js +79 -0
- package/dist/review/linked-issue-label-propagation.d.ts +9 -0
- package/dist/review/linked-issue-label-propagation.js +114 -0
- package/dist/review/pre-merge-checks.d.ts +28 -0
- package/dist/review/pre-merge-checks.js +66 -0
- package/dist/review/review-thread-findings.d.ts +1 -0
- package/dist/review/review-thread-findings.js +1 -0
- package/dist/review/safe-url.d.ts +5 -0
- package/dist/review/safe-url.js +137 -0
- package/dist/review/screenshot-table-gate.d.ts +87 -0
- package/dist/review/screenshot-table-gate.js +340 -0
- package/dist/review/unlinked-issue-guardrail-config.d.ts +9 -0
- package/dist/review/unlinked-issue-guardrail-config.js +44 -0
- package/dist/reviewer-consensus-calibration.d.ts +113 -0
- package/dist/reviewer-consensus-calibration.js +493 -0
- package/dist/reward-risk.d.ts +223 -0
- package/dist/reward-risk.js +618 -0
- package/dist/scoring/label-match.d.ts +4 -0
- package/dist/scoring/label-match.js +145 -0
- package/dist/scoring/model.d.ts +31 -0
- package/dist/scoring/model.js +157 -0
- package/dist/scoring/pending-pr-scenarios.d.ts +44 -0
- package/dist/scoring/pending-pr-scenarios.js +145 -0
- package/dist/scoring/preview.d.ts +246 -0
- package/dist/scoring/preview.js +1108 -0
- package/dist/scoring/types.d.ts +131 -0
- package/dist/scoring/types.js +7 -0
- package/dist/settings/auto-close-exempt.d.ts +10 -0
- package/dist/settings/auto-close-exempt.js +58 -0
- package/dist/settings/autonomy.d.ts +33 -0
- package/dist/settings/autonomy.js +80 -0
- package/dist/settings/command-authorization.d.ts +49 -0
- package/dist/settings/command-authorization.js +222 -0
- package/dist/settings/contributor-blacklist.d.ts +17 -0
- package/dist/settings/contributor-blacklist.js +86 -0
- package/dist/settings/moderation-rules.d.ts +51 -0
- package/dist/settings/moderation-rules.js +104 -0
- package/dist/settings/pr-type-label.d.ts +70 -0
- package/dist/settings/pr-type-label.js +149 -0
- package/dist/signals/change-guardrail.d.ts +49 -0
- package/dist/signals/change-guardrail.js +164 -0
- package/dist/signals/check-summary.d.ts +9 -0
- package/dist/signals/check-summary.js +12 -0
- package/dist/signals/contributor-calibration.d.ts +23 -0
- package/dist/signals/contributor-calibration.js +47 -0
- package/dist/signals/duplicate-winner.d.ts +9 -0
- package/dist/signals/duplicate-winner.js +9 -0
- package/dist/signals/path-matchers.d.ts +36 -0
- package/dist/signals/path-matchers.js +371 -0
- package/dist/signals/predicted-gate-engine.d.ts +62 -0
- package/dist/signals/predicted-gate-engine.js +895 -0
- package/dist/signals/preflight-limits.d.ts +14 -0
- package/dist/signals/preflight-limits.js +14 -0
- package/dist/signals/slop.d.ts +56 -0
- package/dist/signals/slop.js +324 -0
- package/dist/signals/test-evidence.d.ts +25 -0
- package/dist/signals/test-evidence.js +108 -0
- package/dist/subprocess-env.d.ts +23 -0
- package/dist/subprocess-env.js +74 -0
- package/dist/track-record-summary.d.ts +92 -0
- package/dist/track-record-summary.js +301 -0
- package/dist/types/manifest-deps-types.d.ts +514 -0
- package/dist/types/manifest-deps-types.js +4 -0
- package/dist/types/predicted-gate-types.d.ts +338 -0
- package/dist/types/predicted-gate-types.js +3 -0
- package/dist/types/reward-risk-types.d.ts +319 -0
- package/dist/types/reward-risk-types.js +11 -0
- package/dist/utils/json.d.ts +1 -0
- package/dist/utils/json.js +3 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/package.json +80 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Mode-gated `CodingAgentDriver` invocation (#4313). Single call site that applies `CodingAgentExecutionMode`,
|
|
2
|
+
// writes attempt-log events (#4294), and never spawns the underlying agent unless mode is `live`.
|
|
3
|
+
import { codingAgentModeExecutes, } from "./coding-agent-mode.js";
|
|
4
|
+
function shadowSummary(task) {
|
|
5
|
+
return `dry-run: would invoke coding agent in ${task.workingDirectory} (≤${task.maxTurns} turns, criteria ${task.acceptanceCriteriaPath})`;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Invoke a driver under the resolved execution mode. `paused` and `dry_run` never call `driver.run()` — see
|
|
9
|
+
* `coding-agent-mode.ts` for the dry-run tradeoff documentation.
|
|
10
|
+
*/
|
|
11
|
+
export async function invokeCodingAgentDriver(driver, mode, task, log) {
|
|
12
|
+
const base = { attemptId: task.attemptId, actionClass: "codegen", mode };
|
|
13
|
+
if (mode === "paused") {
|
|
14
|
+
log?.append({
|
|
15
|
+
eventType: "attempt_aborted",
|
|
16
|
+
...base,
|
|
17
|
+
reason: "coding_agent_paused",
|
|
18
|
+
payload: { workingDirectory: task.workingDirectory },
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
changedFiles: [],
|
|
23
|
+
summary: "coding agent paused",
|
|
24
|
+
error: "coding_agent_paused",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (!codingAgentModeExecutes(mode)) {
|
|
28
|
+
log?.append({
|
|
29
|
+
eventType: "attempt_shadow",
|
|
30
|
+
...base,
|
|
31
|
+
reason: "dry-run: would invoke coding agent without spawning underlying session",
|
|
32
|
+
payload: {
|
|
33
|
+
workingDirectory: task.workingDirectory,
|
|
34
|
+
acceptanceCriteriaPath: task.acceptanceCriteriaPath,
|
|
35
|
+
maxTurns: task.maxTurns,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
changedFiles: [],
|
|
41
|
+
summary: shadowSummary(task),
|
|
42
|
+
turnsUsed: 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
log?.append({
|
|
46
|
+
eventType: "attempt_started",
|
|
47
|
+
...base,
|
|
48
|
+
reason: "live coding-agent invocation",
|
|
49
|
+
payload: { workingDirectory: task.workingDirectory, maxTurns: task.maxTurns },
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
const result = await driver.run(task);
|
|
53
|
+
log?.append({
|
|
54
|
+
eventType: result.ok ? "attempt_succeeded" : "attempt_failed",
|
|
55
|
+
...base,
|
|
56
|
+
reason: result.summary,
|
|
57
|
+
payload: {
|
|
58
|
+
changedFiles: [...result.changedFiles],
|
|
59
|
+
turnsUsed: result.turnsUsed ?? null,
|
|
60
|
+
error: result.error ?? null,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
67
|
+
log?.append({
|
|
68
|
+
eventType: "attempt_failed",
|
|
69
|
+
...base,
|
|
70
|
+
reason: message,
|
|
71
|
+
payload: { thrown: true },
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
changedFiles: [],
|
|
76
|
+
summary: "coding agent invocation failed",
|
|
77
|
+
error: message,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Whether a coding-agent attempt actually spawns/queries the underlying session. */
|
|
2
|
+
export type CodingAgentExecutionMode = "paused" | "dry_run" | "live";
|
|
3
|
+
/** Global kill-switch for miner coding-agent invocations (`MINER_CODING_AGENT_PAUSED`). Same truthy-string
|
|
4
|
+
* convention as `isGlobalAgentPause` (`AGENT_ACTIONS_PAUSED`). */
|
|
5
|
+
export declare function isGlobalMinerCodingAgentPause(env: {
|
|
6
|
+
MINER_CODING_AGENT_PAUSED?: string | undefined;
|
|
7
|
+
}): boolean;
|
|
8
|
+
/** THE single gate before invoking a `CodingAgentDriver`. Precedence (safest wins): global OR per-config pause
|
|
9
|
+
* → `paused`; else per-config dry-run → `dry_run`; else `live`. Pure. */
|
|
10
|
+
export declare function resolveCodingAgentExecutionMode(input: {
|
|
11
|
+
globalPaused: boolean;
|
|
12
|
+
agentPaused?: boolean | null | undefined;
|
|
13
|
+
agentDryRun?: boolean | null | undefined;
|
|
14
|
+
}): CodingAgentExecutionMode;
|
|
15
|
+
/** Resolve mode from env + optional per-run overrides (mirrors `resolveAgentActionMode` call sites). */
|
|
16
|
+
export declare function resolveCodingAgentModeFromConfig(config: {
|
|
17
|
+
env?: {
|
|
18
|
+
MINER_CODING_AGENT_PAUSED?: string | undefined;
|
|
19
|
+
} | undefined;
|
|
20
|
+
agentPaused?: boolean | null | undefined;
|
|
21
|
+
agentDryRun?: boolean | null | undefined;
|
|
22
|
+
}): CodingAgentExecutionMode;
|
|
23
|
+
/** True only for `live` — the only mode that performs a real driver `run()`. `paused` does nothing; `dry_run`
|
|
24
|
+
* records a shadow result without spawning the underlying agent. */
|
|
25
|
+
export declare function codingAgentModeExecutes(mode: CodingAgentExecutionMode): boolean;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Coding-agent execution mode (#4313). Mirrors `AgentActionMode` at `src/settings/agent-execution.ts:23-48`:
|
|
2
|
+
// three states (`paused` | `dry_run` | `live`), deny-toward-safety precedence (global/per-repo pause beats
|
|
3
|
+
// dry-run beats live), and a single `codingAgentModeExecutes` boolean for callers.
|
|
4
|
+
//
|
|
5
|
+
// Why three states instead of a boolean? The maintainer-action layer already proved the shape: `paused` is an
|
|
6
|
+
// operator kill-switch (attempt never starts), `dry_run` is an observe/shadow path (record intent without the
|
|
7
|
+
// expensive/dangerous work), and `live` is the only mode that actually spawns/queries the underlying agent.
|
|
8
|
+
// A boolean would collapse `paused` into `dry_run`, losing the distinction between "halt entirely" and
|
|
9
|
+
// "shadow what would happen".
|
|
10
|
+
//
|
|
11
|
+
// Dry-run semantics for a CODING agent (#4313): at the driver invocation boundary, `dry_run` is a **pure no-op**
|
|
12
|
+
// — the underlying CLI/SDK session is never spawned. Tradeoff documented here:
|
|
13
|
+
// • Chosen: never spawn (cheapest, safest, mirrors `agentActionModeExecutes` skipping GitHub mutations).
|
|
14
|
+
// • Deferred alternative: run inside an isolated worktree (#4269) but suppress commit/push/PR downstream so
|
|
15
|
+
// file edits remain inspectable — requires the worktree primitive and orchestrator gating on create-phase
|
|
16
|
+
// steps; the attempt log records `attempt_shadow` with mode=`dry_run` so either path stays auditable.
|
|
17
|
+
/** Global kill-switch for miner coding-agent invocations (`MINER_CODING_AGENT_PAUSED`). Same truthy-string
|
|
18
|
+
* convention as `isGlobalAgentPause` (`AGENT_ACTIONS_PAUSED`). */
|
|
19
|
+
export function isGlobalMinerCodingAgentPause(env) {
|
|
20
|
+
return /^(1|true|yes|on)$/i.test(env.MINER_CODING_AGENT_PAUSED ?? "");
|
|
21
|
+
}
|
|
22
|
+
/** THE single gate before invoking a `CodingAgentDriver`. Precedence (safest wins): global OR per-config pause
|
|
23
|
+
* → `paused`; else per-config dry-run → `dry_run`; else `live`. Pure. */
|
|
24
|
+
export function resolveCodingAgentExecutionMode(input) {
|
|
25
|
+
if (input.globalPaused || input.agentPaused === true)
|
|
26
|
+
return "paused";
|
|
27
|
+
if (input.agentDryRun === true)
|
|
28
|
+
return "dry_run";
|
|
29
|
+
return "live";
|
|
30
|
+
}
|
|
31
|
+
/** Resolve mode from env + optional per-run overrides (mirrors `resolveAgentActionMode` call sites). */
|
|
32
|
+
export function resolveCodingAgentModeFromConfig(config) {
|
|
33
|
+
return resolveCodingAgentExecutionMode({
|
|
34
|
+
globalPaused: isGlobalMinerCodingAgentPause(config.env ?? {}),
|
|
35
|
+
agentPaused: config.agentPaused,
|
|
36
|
+
agentDryRun: config.agentDryRun,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** True only for `live` — the only mode that performs a real driver `run()`. `paused` does nothing; `dry_run`
|
|
40
|
+
* records a shadow result without spawning the underlying agent. */
|
|
41
|
+
export function codingAgentModeExecutes(mode) {
|
|
42
|
+
return mode === "live";
|
|
43
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type CodingAgentDriver } from "./coding-agent-driver.js";
|
|
2
|
+
import { type AttemptLogSink } from "./coding-agent-invoke.js";
|
|
3
|
+
import { type CodingAgentExecutionMode } from "./coding-agent-mode.js";
|
|
4
|
+
import type { CodingAgentDriverResult, CodingAgentDriverTask } from "./coding-agent-driver.js";
|
|
5
|
+
import { type LintGuardOptions, type LintGuardResult } from "./lint-guard.js";
|
|
6
|
+
import { type CliSubprocessSpawnFn } from "./cli-subprocess-driver.js";
|
|
7
|
+
import { type AgentSdkHooks, type AgentSdkQueryFn } from "./agent-sdk-driver.js";
|
|
8
|
+
/** Provider names the factory resolves: the two concrete drivers from #4266/#4267 (`claude-cli`/`codex-cli`
|
|
9
|
+
* spawn the respective CLI; `agent-sdk` runs in-process via the Agent SDK) plus the `noop` stub. All are
|
|
10
|
+
* locally-authenticated (no API-key env requirement), mirroring how `isConfiguredSelfHostProvider` treats
|
|
11
|
+
* `claude-code`/`codex` as always-configured. */
|
|
12
|
+
export declare const CODING_AGENT_DRIVER_NAMES: readonly ["noop", "claude-cli", "codex-cli", "agent-sdk"];
|
|
13
|
+
export type CodingAgentDriverName = (typeof CODING_AGENT_DRIVER_NAMES)[number];
|
|
14
|
+
/** Per-provider env keys for coding-agent configuration (mirrors `SELF_HOST_REVIEWER_MODEL_ENV`). Every key
|
|
15
|
+
* declared here is CONSUMED by `createCodingAgentDriver` below — a declared-but-unread entry is dead,
|
|
16
|
+
* misleading config-as-code surface. Deliberately NOT declared: a max-turns key (the turn budget is task-level
|
|
17
|
+
* input — `CodingAgentDriverTask.maxTurns` — set by the orchestrator per attempt, not per-provider config) and
|
|
18
|
+
* an agent-sdk model key (the SDK session uses the account/CLI default; it exposes no model option on the
|
|
19
|
+
* driver today). */
|
|
20
|
+
export declare const CODING_AGENT_DRIVER_CONFIG_ENV: Readonly<Record<CodingAgentDriverName, {
|
|
21
|
+
model?: string;
|
|
22
|
+
timeoutMs?: string;
|
|
23
|
+
}>>;
|
|
24
|
+
/** True when `name` is a known, configured coding-agent driver. Unknown names → false (deny-by-default). */
|
|
25
|
+
export declare function isConfiguredCodingAgentDriver(name: string, _env: Record<string, string | undefined>): boolean;
|
|
26
|
+
export declare function resolveConfiguredCodingAgentDriverNames(env: Record<string, string | undefined>): string[];
|
|
27
|
+
/** Primary-then-fallback resolution over `MINER_CODING_AGENT_PROVIDER`'s comma-separated list (the same
|
|
28
|
+
* fallback-chain semantic `AiRunOptions.fallback` gives reviewers): the FIRST configured name wins; unknown
|
|
29
|
+
* names are skipped (deny-by-default), and an all-unknown/empty list resolves to undefined so the caller
|
|
30
|
+
* fails closed rather than falling through to some implicit default driver. */
|
|
31
|
+
export declare function resolveFirstConfiguredCodingAgentDriverName(env: Record<string, string | undefined>): string | undefined;
|
|
32
|
+
export type CreateCodingAgentDriverOptions = {
|
|
33
|
+
providerName: string;
|
|
34
|
+
env?: Record<string, string | undefined> | undefined;
|
|
35
|
+
/** Test seam — inject a fake driver instead of constructing the named provider. */
|
|
36
|
+
driver?: CodingAgentDriver | undefined;
|
|
37
|
+
/** Subprocess runner for the CLI providers (`claude-cli`/`codex-cli`). REQUIRED for those providers — the
|
|
38
|
+
* engine package ships no default spawn, so constructing a CLI driver without one fails closed rather than
|
|
39
|
+
* producing a driver that can never run. */
|
|
40
|
+
spawn?: CliSubprocessSpawnFn | undefined;
|
|
41
|
+
/** Optional injected `query()` loop for the `agent-sdk` provider (defaults to the real SDK import). */
|
|
42
|
+
query?: AgentSdkQueryFn | undefined;
|
|
43
|
+
/** Forwarded to the `agent-sdk` provider's session (#2343's PreToolUse interception point). */
|
|
44
|
+
hooks?: AgentSdkHooks | undefined;
|
|
45
|
+
/** Optional injected changed-file enumerator for the `agent-sdk` provider (defaults to a real `git diff`/
|
|
46
|
+
* `git ls-files` read over the task's working directory — a test harness pointing at a non-git fake path
|
|
47
|
+
* should inject one, same seam as `query`). */
|
|
48
|
+
listChangedFiles?: ((cwd: string) => Promise<string[]>) | undefined;
|
|
49
|
+
/** Known secret values the CLI providers strip from surfaced output, on top of the token-shape patterns. */
|
|
50
|
+
knownSecrets?: readonly string[] | undefined;
|
|
51
|
+
};
|
|
52
|
+
/** Resolve a concrete driver for `providerName`. Throws on unknown/unconfigured providers (fail-closed). */
|
|
53
|
+
export declare function createCodingAgentDriver(options: CreateCodingAgentDriverOptions): CodingAgentDriver;
|
|
54
|
+
export type RunCodingAgentAttemptOptions = {
|
|
55
|
+
providerName: string;
|
|
56
|
+
env?: Record<string, string | undefined> | undefined;
|
|
57
|
+
agentPaused?: boolean | null | undefined;
|
|
58
|
+
agentDryRun?: boolean | null | undefined;
|
|
59
|
+
task: CodingAgentDriverTask;
|
|
60
|
+
log?: AttemptLogSink | undefined;
|
|
61
|
+
driver?: CodingAgentDriver | undefined;
|
|
62
|
+
/** Provider dependencies, forwarded to `createCodingAgentDriver` (see `CreateCodingAgentDriverOptions`). */
|
|
63
|
+
spawn?: CliSubprocessSpawnFn | undefined;
|
|
64
|
+
query?: AgentSdkQueryFn | undefined;
|
|
65
|
+
hooks?: AgentSdkHooks | undefined;
|
|
66
|
+
/** Optional injected changed-file enumerator for the `agent-sdk` provider (see `CreateCodingAgentDriverOptions`). */
|
|
67
|
+
listChangedFiles?: ((cwd: string) => Promise<string[]>) | undefined;
|
|
68
|
+
knownSecrets?: readonly string[] | undefined;
|
|
69
|
+
/** When supplied, the driver result is run through the lint guard (#4276) before being returned, so a
|
|
70
|
+
* live coding-agent edit that fails its own package's typecheck/node --check never reads as `ok: true`. */
|
|
71
|
+
lintGuard?: LintGuardOptions | undefined;
|
|
72
|
+
};
|
|
73
|
+
/** End-to-end entry: resolve mode from config, pick the driver, invoke under mode gating + attempt log, then
|
|
74
|
+
* (when `lintGuard` is supplied) run the changed files through the lint guard before the caller sees the result. */
|
|
75
|
+
export declare function runCodingAgentAttempt(options: RunCodingAgentAttemptOptions): Promise<{
|
|
76
|
+
mode: CodingAgentExecutionMode;
|
|
77
|
+
result: CodingAgentDriverResult & {
|
|
78
|
+
lintGuard?: LintGuardResult;
|
|
79
|
+
};
|
|
80
|
+
}>;
|
|
81
|
+
/** Exported for parity tests — wraps a driver without changing its behavior (identity helper). */
|
|
82
|
+
export declare function createFakeCodingAgentDriverForFactory(): CodingAgentDriver;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// CodingAgentDriver factory + provider-style config resolution (#4289). Mirrors `src/selfhost/ai-config.ts:41-74`:
|
|
2
|
+
// parse a comma-separated provider list, validate each name against what is actually configured, deny-by-default
|
|
3
|
+
// on unknown/unconfigured names, and expose a model/effort config map analogous to `SELF_HOST_REVIEWER_MODEL_ENV`.
|
|
4
|
+
import { createFakeCodingAgentDriver, createNoopCodingAgentDriver, } from "./coding-agent-driver.js";
|
|
5
|
+
import { invokeCodingAgentDriver, } from "./coding-agent-invoke.js";
|
|
6
|
+
import { codingAgentModeExecutes, resolveCodingAgentModeFromConfig, } from "./coding-agent-mode.js";
|
|
7
|
+
import { guardCodingAgentDriverResult } from "./lint-guard.js";
|
|
8
|
+
import { createCliSubprocessCodingAgentDriver, defaultClaudeCliArgs, defaultCodexCliArgs, } from "./cli-subprocess-driver.js";
|
|
9
|
+
import { createAgentSdkCodingAgentDriver, } from "./agent-sdk-driver.js";
|
|
10
|
+
/** Provider names the factory resolves: the two concrete drivers from #4266/#4267 (`claude-cli`/`codex-cli`
|
|
11
|
+
* spawn the respective CLI; `agent-sdk` runs in-process via the Agent SDK) plus the `noop` stub. All are
|
|
12
|
+
* locally-authenticated (no API-key env requirement), mirroring how `isConfiguredSelfHostProvider` treats
|
|
13
|
+
* `claude-code`/`codex` as always-configured. */
|
|
14
|
+
export const CODING_AGENT_DRIVER_NAMES = Object.freeze(["noop", "claude-cli", "codex-cli", "agent-sdk"]);
|
|
15
|
+
/** Per-provider env keys for coding-agent configuration (mirrors `SELF_HOST_REVIEWER_MODEL_ENV`). Every key
|
|
16
|
+
* declared here is CONSUMED by `createCodingAgentDriver` below — a declared-but-unread entry is dead,
|
|
17
|
+
* misleading config-as-code surface. Deliberately NOT declared: a max-turns key (the turn budget is task-level
|
|
18
|
+
* input — `CodingAgentDriverTask.maxTurns` — set by the orchestrator per attempt, not per-provider config) and
|
|
19
|
+
* an agent-sdk model key (the SDK session uses the account/CLI default; it exposes no model option on the
|
|
20
|
+
* driver today). */
|
|
21
|
+
export const CODING_AGENT_DRIVER_CONFIG_ENV = Object.freeze({
|
|
22
|
+
noop: {},
|
|
23
|
+
"claude-cli": { model: "MINER_CODING_AGENT_CLAUDE_MODEL", timeoutMs: "MINER_CODING_AGENT_TIMEOUT_MS" },
|
|
24
|
+
"codex-cli": { model: "MINER_CODING_AGENT_CODEX_MODEL", timeoutMs: "MINER_CODING_AGENT_TIMEOUT_MS" },
|
|
25
|
+
"agent-sdk": {},
|
|
26
|
+
});
|
|
27
|
+
/** `firstConfigured` (src/selfhost/ai.ts:117-134) pattern: a set-and-non-empty env value, else undefined. */
|
|
28
|
+
function firstConfiguredEnvValue(value) {
|
|
29
|
+
const trimmed = value?.trim();
|
|
30
|
+
return trimmed ? trimmed : undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Positive-integer env parse for the CLI wall-clock ceiling; anything else defers to the driver default. */
|
|
33
|
+
function configuredTimeoutMs(env) {
|
|
34
|
+
const raw = Number(firstConfiguredEnvValue(env.MINER_CODING_AGENT_TIMEOUT_MS));
|
|
35
|
+
return Number.isFinite(raw) && Number.isInteger(raw) && raw > 0 ? raw : undefined;
|
|
36
|
+
}
|
|
37
|
+
function parseDriverNames(env) {
|
|
38
|
+
return (env.MINER_CODING_AGENT_PROVIDER ?? "")
|
|
39
|
+
.split(",")
|
|
40
|
+
.map((value) => value.trim().toLowerCase())
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
}
|
|
43
|
+
/** True when `name` is a known, configured coding-agent driver. Unknown names → false (deny-by-default). */
|
|
44
|
+
export function isConfiguredCodingAgentDriver(name, _env) {
|
|
45
|
+
switch (name) {
|
|
46
|
+
case "noop":
|
|
47
|
+
case "claude-cli":
|
|
48
|
+
case "codex-cli":
|
|
49
|
+
case "agent-sdk":
|
|
50
|
+
return true;
|
|
51
|
+
default:
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function resolveConfiguredCodingAgentDriverNames(env) {
|
|
56
|
+
return parseDriverNames(env).filter((name) => isConfiguredCodingAgentDriver(name, env));
|
|
57
|
+
}
|
|
58
|
+
/** Primary-then-fallback resolution over `MINER_CODING_AGENT_PROVIDER`'s comma-separated list (the same
|
|
59
|
+
* fallback-chain semantic `AiRunOptions.fallback` gives reviewers): the FIRST configured name wins; unknown
|
|
60
|
+
* names are skipped (deny-by-default), and an all-unknown/empty list resolves to undefined so the caller
|
|
61
|
+
* fails closed rather than falling through to some implicit default driver. */
|
|
62
|
+
export function resolveFirstConfiguredCodingAgentDriverName(env) {
|
|
63
|
+
return resolveConfiguredCodingAgentDriverNames(env)[0];
|
|
64
|
+
}
|
|
65
|
+
/** Build a CLI provider's argv: the driver's own real default argv contract, with the CONFIGURED model
|
|
66
|
+
* flag inserted at the RIGHT position for that command — this is where that declared config is actually
|
|
67
|
+
* consumed. claude's `--model` is a top-level flag (prefixed before everything else is fine); codex's
|
|
68
|
+
* `-m`/`--model` is scoped to the `exec` subcommand (per `codex exec --help`) and must be inserted AFTER
|
|
69
|
+
* the leading `"exec"` token, not before it — a single shared prefix-everything scheme would silently
|
|
70
|
+
* misparse for codex. */
|
|
71
|
+
function buildCliArgsWithConfiguredModel(command, model) {
|
|
72
|
+
if (model === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (command === "claude") {
|
|
75
|
+
return (task) => ["--model", model, ...defaultClaudeCliArgs(task)];
|
|
76
|
+
}
|
|
77
|
+
return (task) => {
|
|
78
|
+
const [subcommand, ...rest] = defaultCodexCliArgs(task);
|
|
79
|
+
return [subcommand, "--model", model, ...rest];
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function createCliProvider(command, modelEnvKey, options, env) {
|
|
83
|
+
if (!options.spawn) {
|
|
84
|
+
// Fail-closed (resolveAutonomy's deny-by-default precedent): a CLI provider without a spawn dependency is
|
|
85
|
+
// unconfigured in the way that matters — never hand back a driver whose every run() would throw.
|
|
86
|
+
throw new Error(`unconfigured_coding_agent_driver_missing_spawn:${command}-cli`);
|
|
87
|
+
}
|
|
88
|
+
if (options.hooks !== undefined) {
|
|
89
|
+
// CLI subprocess providers have no hook-registration surface. If a caller supplied house-rule hooks, treating
|
|
90
|
+
// them as "best effort" would silently run prompt-influenced local code without the policy the caller asked
|
|
91
|
+
// for, so fail closed until a CLI-native enforcement layer exists.
|
|
92
|
+
throw new Error(`unsupported_coding_agent_driver_hooks:${command}-cli`);
|
|
93
|
+
}
|
|
94
|
+
const model = firstConfiguredEnvValue(env[modelEnvKey]);
|
|
95
|
+
const timeoutMs = configuredTimeoutMs(env);
|
|
96
|
+
const buildArgs = buildCliArgsWithConfiguredModel(command, model);
|
|
97
|
+
return createCliSubprocessCodingAgentDriver({
|
|
98
|
+
command,
|
|
99
|
+
spawn: options.spawn,
|
|
100
|
+
parentEnv: env,
|
|
101
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
102
|
+
...(buildArgs !== undefined ? { buildArgs } : {}),
|
|
103
|
+
...(options.knownSecrets !== undefined ? { knownSecrets: options.knownSecrets } : {}),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Resolve a concrete driver for `providerName`. Throws on unknown/unconfigured providers (fail-closed). */
|
|
107
|
+
export function createCodingAgentDriver(options) {
|
|
108
|
+
if (options.driver)
|
|
109
|
+
return options.driver;
|
|
110
|
+
const name = options.providerName.trim().toLowerCase();
|
|
111
|
+
const env = options.env ?? {};
|
|
112
|
+
if (!isConfiguredCodingAgentDriver(name, env)) {
|
|
113
|
+
throw new Error(`unconfigured_coding_agent_driver:${name}`);
|
|
114
|
+
}
|
|
115
|
+
switch (name) {
|
|
116
|
+
case "noop":
|
|
117
|
+
return createNoopCodingAgentDriver();
|
|
118
|
+
case "claude-cli":
|
|
119
|
+
return createCliProvider("claude", "MINER_CODING_AGENT_CLAUDE_MODEL", options, env);
|
|
120
|
+
case "codex-cli":
|
|
121
|
+
return createCliProvider("codex", "MINER_CODING_AGENT_CODEX_MODEL", options, env);
|
|
122
|
+
case "agent-sdk":
|
|
123
|
+
// No model/timeout config today — the SDK session uses the account default; hooks/query are optional.
|
|
124
|
+
return createAgentSdkCodingAgentDriver({
|
|
125
|
+
...(options.query !== undefined ? { query: options.query } : {}),
|
|
126
|
+
...(options.hooks !== undefined ? { hooks: options.hooks } : {}),
|
|
127
|
+
...(options.listChangedFiles !== undefined ? { listChangedFiles: options.listChangedFiles } : {}),
|
|
128
|
+
});
|
|
129
|
+
/* v8 ignore next 2 -- isConfiguredCodingAgentDriver already rejects unknown names before this switch. */
|
|
130
|
+
default:
|
|
131
|
+
throw new Error(`unconfigured_coding_agent_driver:${name}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function resolveDriverForAttempt(options, mode) {
|
|
135
|
+
if (options.driver)
|
|
136
|
+
return options.driver;
|
|
137
|
+
// Dry-run/paused attempts never call `driver.run()` (see coding-agent-driver.md lifecycle). Constructing a
|
|
138
|
+
// CLI provider here would require spawn/query deps even though they would never be used — use the noop stub
|
|
139
|
+
// as a stand-in so shadow/paused attempts stay dependency-free (#4289 / gate fix for #4593).
|
|
140
|
+
if (!codingAgentModeExecutes(mode))
|
|
141
|
+
return createNoopCodingAgentDriver();
|
|
142
|
+
return createCodingAgentDriver({
|
|
143
|
+
providerName: options.providerName,
|
|
144
|
+
env: options.env,
|
|
145
|
+
spawn: options.spawn,
|
|
146
|
+
query: options.query,
|
|
147
|
+
hooks: options.hooks,
|
|
148
|
+
listChangedFiles: options.listChangedFiles,
|
|
149
|
+
knownSecrets: options.knownSecrets,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** End-to-end entry: resolve mode from config, pick the driver, invoke under mode gating + attempt log, then
|
|
153
|
+
* (when `lintGuard` is supplied) run the changed files through the lint guard before the caller sees the result. */
|
|
154
|
+
export async function runCodingAgentAttempt(options) {
|
|
155
|
+
const mode = resolveCodingAgentModeFromConfig({
|
|
156
|
+
env: options.env,
|
|
157
|
+
agentPaused: options.agentPaused,
|
|
158
|
+
agentDryRun: options.agentDryRun,
|
|
159
|
+
});
|
|
160
|
+
const driver = resolveDriverForAttempt(options, mode);
|
|
161
|
+
const result = await invokeCodingAgentDriver(driver, mode, options.task, options.log);
|
|
162
|
+
if (!options.lintGuard)
|
|
163
|
+
return { mode, result };
|
|
164
|
+
return { mode, result: await guardCodingAgentDriverResult(result, options.lintGuard) };
|
|
165
|
+
}
|
|
166
|
+
/** Exported for parity tests — wraps a driver without changing its behavior (identity helper). */
|
|
167
|
+
export function createFakeCodingAgentDriverForFactory() {
|
|
168
|
+
return createFakeCodingAgentDriver();
|
|
169
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { MinerKillSwitchScope } from "../governor/kill-switch.js";
|
|
2
|
+
import type { HandoffPacket } from "./iterate-policy.js";
|
|
3
|
+
import type { SelfReviewSlopBand } from "./self-review-adapter.js";
|
|
4
|
+
import { type SubmissionGateMode } from "./submission-gate.js";
|
|
5
|
+
export declare const DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS = 3;
|
|
6
|
+
export type HarnessSubmissionTriggerCandidate = {
|
|
7
|
+
/** Forwarded to `shouldSubmit`'s own kill-switch check (#2339) -- this function does not ALSO short-circuit
|
|
8
|
+
* on it separately (that would be a second, duplicated check, exactly what #2339's "single shared helper,
|
|
9
|
+
* not duplicated per call site" deliverable warns against); `shouldSubmit` is always still called (the
|
|
10
|
+
* circuit breaker above is the only thing that skips it), and its own kill-switch guard covers this. */
|
|
11
|
+
killSwitchScope: MinerKillSwitchScope;
|
|
12
|
+
handoffPacket: HandoffPacket;
|
|
13
|
+
slopThreshold: SelfReviewSlopBand;
|
|
14
|
+
mode: SubmissionGateMode;
|
|
15
|
+
/** Caller-computed count of CONSECUTIVE `allow: false` submission-gate decisions so far this session,
|
|
16
|
+
* ending with (and NOT including) this candidate's own about-to-be-computed decision. The caller owns this
|
|
17
|
+
* tally (mirrors #2338's caller-supplied `consecutiveDisengagements`); a `true` decision anywhere resets it
|
|
18
|
+
* to 0 for the caller's NEXT candidate. */
|
|
19
|
+
consecutiveGateBlocks: number;
|
|
20
|
+
maxConsecutiveGateBlocks?: number | undefined;
|
|
21
|
+
};
|
|
22
|
+
export type HarnessSubmissionTriggerDecision = {
|
|
23
|
+
allow: boolean;
|
|
24
|
+
reasons: string[];
|
|
25
|
+
/** True only when the SESSION-LEVEL circuit breaker (not a normal per-candidate block) is what stopped this
|
|
26
|
+
* decision -- the caller's own driving loop should treat this as "pause the run entirely pending human
|
|
27
|
+
* review," distinct from an ordinary `allow: false` a later, different candidate might still clear. */
|
|
28
|
+
circuitBreakerTripped: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* THE final gate before a real call site may build the `open_pr` local-write spec from a passing
|
|
32
|
+
* `HandoffPacket`. Pure; identical inputs always yield the identical decision. Checks the session-level
|
|
33
|
+
* circuit breaker FIRST (never consults `shouldSubmit` once tripped), then re-checks `shouldSubmit`'s own
|
|
34
|
+
* predicted-gate-pass + slop-under-threshold signals against the handoff's own verdict -- defense in depth,
|
|
35
|
+
* not a blind trust of the fact that a handoff happened at all.
|
|
36
|
+
*/
|
|
37
|
+
export declare function evaluateHarnessSubmissionTrigger(candidate: HarnessSubmissionTriggerCandidate): HarnessSubmissionTriggerDecision;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Harness submission-gate wiring (#2337): connects the gated-submission decision function (`shouldSubmit`,
|
|
2
|
+
// submission-gate.ts, #2336) to the ACTUAL driving loop's own handoff signal -- iterate-loop.ts's (#2333)
|
|
3
|
+
// `HandoffPacket`, the exact object produced the moment a real run's self-review reaches a clean predicted-gate
|
|
4
|
+
// pass. This is the live actuation wiring itself: the trigger surface the safety-tier system reserves for
|
|
5
|
+
// maintainer review, since a bug here means an autonomous write happens when it should not have.
|
|
6
|
+
//
|
|
7
|
+
// WHAT THIS DOES NOT DO: build or invoke the actual `open_pr` local-write spec (`buildOpenPrSpec`,
|
|
8
|
+
// `src/mcp/local-write-tools.ts`) -- that lives in the private root `src/` tree, unreachable from this
|
|
9
|
+
// portable package for the same cross-package-boundary reason self-review-adapter.ts's slop injection exists
|
|
10
|
+
// (#2334's own module doc comment). This function's OUTPUT (`allow: true`) is the gate a real call site
|
|
11
|
+
// (root-side server/CLI integration, wired in a later issue) consults before it builds that spec itself --
|
|
12
|
+
// mirrors #2336's own "gated exclusively through this function" scoping.
|
|
13
|
+
//
|
|
14
|
+
// THE SESSION-LEVEL CIRCUIT BREAKER: distinct from `shouldSubmit`'s own per-candidate signal checks
|
|
15
|
+
// (predicted-gate pass, slop-under-threshold), this issue's own deliverable calls for "N consecutive
|
|
16
|
+
// submission-gate allow:false decisions in one session pauses the run entirely pending human review, never
|
|
17
|
+
// silently loops trying to force a pass." Checked FIRST, before ever consulting `shouldSubmit` -- once tripped,
|
|
18
|
+
// no candidate can un-trip it (that requires a human clearing the session's own consecutive-block tally),
|
|
19
|
+
// unlike a per-candidate block which a later, different candidate can clear on its own merits.
|
|
20
|
+
import { shouldSubmit } from "./submission-gate.js";
|
|
21
|
+
export const DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS = 3;
|
|
22
|
+
/**
|
|
23
|
+
* THE final gate before a real call site may build the `open_pr` local-write spec from a passing
|
|
24
|
+
* `HandoffPacket`. Pure; identical inputs always yield the identical decision. Checks the session-level
|
|
25
|
+
* circuit breaker FIRST (never consults `shouldSubmit` once tripped), then re-checks `shouldSubmit`'s own
|
|
26
|
+
* predicted-gate-pass + slop-under-threshold signals against the handoff's own verdict -- defense in depth,
|
|
27
|
+
* not a blind trust of the fact that a handoff happened at all.
|
|
28
|
+
*/
|
|
29
|
+
export function evaluateHarnessSubmissionTrigger(candidate) {
|
|
30
|
+
const maxConsecutiveGateBlocks = candidate.maxConsecutiveGateBlocks ?? DEFAULT_MAX_CONSECUTIVE_GATE_BLOCKS;
|
|
31
|
+
if (candidate.consecutiveGateBlocks >= maxConsecutiveGateBlocks) {
|
|
32
|
+
return {
|
|
33
|
+
allow: false,
|
|
34
|
+
circuitBreakerTripped: true,
|
|
35
|
+
reasons: [`circuit_breaker_tripped_after_consecutive_blocks:${candidate.consecutiveGateBlocks}>=${maxConsecutiveGateBlocks}`],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const gateDecision = shouldSubmit({
|
|
39
|
+
killSwitchScope: candidate.killSwitchScope,
|
|
40
|
+
predictedGateVerdict: candidate.handoffPacket.selfReviewVerdict.predictedGateVerdict,
|
|
41
|
+
slopAssessment: candidate.handoffPacket.selfReviewVerdict.slopAssessment,
|
|
42
|
+
slopThreshold: candidate.slopThreshold,
|
|
43
|
+
mode: candidate.mode,
|
|
44
|
+
});
|
|
45
|
+
return { allow: gateDecision.allow, reasons: gateDecision.reasons, circuitBreakerTripped: false };
|
|
46
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { CodingAgentDriver, CodingAgentDriverResult } from "./coding-agent-driver.js";
|
|
2
|
+
import { type CodingAgentExecutionMode } from "./coding-agent-mode.js";
|
|
3
|
+
import type { AttemptLogEvent } from "./attempt-log.js";
|
|
4
|
+
import { type SelfReviewAdapterDeps, type SelfReviewContext } from "./self-review-adapter.js";
|
|
5
|
+
import { type IterateLoopDecision, type HandoffPacket } from "./iterate-policy.js";
|
|
6
|
+
import { type AttemptBudget, type AttemptBudgetAxis, type AttemptMeterTotals } from "./attempt-metering.js";
|
|
7
|
+
/** Everything one call to {@link runIterateLoop} needs, aside from the injected {@link IterateLoopDeps}.
|
|
8
|
+
* Identity/context fields mirror self-review-adapter.ts's `AttemptDiffState`/`SelfReviewContext` exactly --
|
|
9
|
+
* the caller assembles these from whatever Phase 2 plan/acceptance-criteria packet exists; that packet's
|
|
10
|
+
* exact combined shape is explicitly out of scope for this issue. */
|
|
11
|
+
export type IterateLoopInput = {
|
|
12
|
+
attemptId: string;
|
|
13
|
+
workingDirectory: string;
|
|
14
|
+
acceptanceCriteriaPath: string;
|
|
15
|
+
instructions: string;
|
|
16
|
+
/** Resolved by the caller (e.g. the Governor chokepoint / action-mode resolution, #2340/#2342) -- this loop
|
|
17
|
+
* does not re-derive execution mode itself, only records whatever mode it is told. */
|
|
18
|
+
mode: CodingAgentExecutionMode;
|
|
19
|
+
/** Hard ceiling on iteration count, enforced every iteration via iterate-policy.ts. `<= 0` abandons before
|
|
20
|
+
* the first driver invocation. */
|
|
21
|
+
maxIterations: number;
|
|
22
|
+
/** Per-iteration turn budget, passed through to each `CodingAgentDriverTask`. */
|
|
23
|
+
maxTurnsPerIteration: number;
|
|
24
|
+
/** Optional cumulative budget ceiling(s), evaluated every iteration against the real running totals
|
|
25
|
+
* (attempt-metering.ts's `AttemptMeterTotals`, #5395). Omitted means no additional ceiling beyond what
|
|
26
|
+
* `maxIterations * maxTurnsPerIteration` already implies. A breach on any axis (turns/costUsd/wallClockMs
|
|
27
|
+
* -- tokens is never real-tracked today) is a HARD, unconditional stop: checked and abandoned on BEFORE
|
|
28
|
+
* self-review even runs, so a same-iteration pass can never bypass the ceiling (this is deliberately NOT
|
|
29
|
+
* routed through iterate-policy.ts's `costCeilingReached` field, whose own precedence checks a self-review
|
|
30
|
+
* pass first -- see the loop body's own comment at the check site for why that ordering doesn't fit a hard
|
|
31
|
+
* budget ceiling). */
|
|
32
|
+
budget?: AttemptBudget | undefined;
|
|
33
|
+
repoFullName: string;
|
|
34
|
+
contributorLogin: string;
|
|
35
|
+
title: string;
|
|
36
|
+
body?: string | undefined;
|
|
37
|
+
labels?: string[] | undefined;
|
|
38
|
+
linkedIssues?: number[] | undefined;
|
|
39
|
+
authorAssociation?: string | undefined;
|
|
40
|
+
/** Optional branch ref for the attempt's worktree, threaded through to a passing {@link HandoffPacket}
|
|
41
|
+
* unchanged -- this loop does not itself manage worktrees/branches (worktree-allocator.ts's job). */
|
|
42
|
+
branchRef?: string | undefined;
|
|
43
|
+
/** Repo-level self-review context (manifest, repo record, issues, pull requests, ...) -- passed through to
|
|
44
|
+
* `runSelfReview` unchanged every iteration. */
|
|
45
|
+
reviewContext: SelfReviewContext;
|
|
46
|
+
/** True when the target repo (or this contributor's history with it) has signaled it does not want
|
|
47
|
+
* automated contributions -- resolved by the caller (AI-policy-map / rejection-state-machine), consumed
|
|
48
|
+
* as-is. See iterate-policy.ts's own `IterationState.rejectionSignaled` doc comment. */
|
|
49
|
+
rejectionSignaled: boolean;
|
|
50
|
+
};
|
|
51
|
+
export type IterateLoopDeps = {
|
|
52
|
+
driver: CodingAgentDriver;
|
|
53
|
+
runSlopAssessment: SelfReviewAdapterDeps["runSlopAssessment"];
|
|
54
|
+
appendAttemptLogEvent: (event: AttemptLogEvent) => void;
|
|
55
|
+
/** Injected clock for real per-iteration wall-clock measurement (#5395), mirroring this package's own
|
|
56
|
+
* injected-dependency discipline elsewhere (never a hardcoded `Date.now()` a test can't control). Defaults
|
|
57
|
+
* to the real `Date.now` when omitted. */
|
|
58
|
+
nowMs?: (() => number) | undefined;
|
|
59
|
+
};
|
|
60
|
+
/** The terminal outcomes a full loop run can end in -- never `"continue"`, which is only ever a per-iteration,
|
|
61
|
+
* non-terminal signal. */
|
|
62
|
+
export type IterateLoopOutcome = "handoff" | "abandon";
|
|
63
|
+
export type IterateLoopIterationRecord = {
|
|
64
|
+
iterationNumber: number;
|
|
65
|
+
driverResult: CodingAgentDriverResult;
|
|
66
|
+
decision: IterateLoopDecision;
|
|
67
|
+
};
|
|
68
|
+
export type IterateLoopResult = {
|
|
69
|
+
outcome: IterateLoopOutcome;
|
|
70
|
+
finalDecision: IterateLoopDecision;
|
|
71
|
+
/** Count of iterations that actually invoked the driver -- `0` for the `maxIterations <= 0` immediate-abandon
|
|
72
|
+
* case, since the driver is never invoked there. */
|
|
73
|
+
iterationsUsed: number;
|
|
74
|
+
/** Cumulative `turnsUsed` summed across every iteration that ran. */
|
|
75
|
+
totalTurnsUsed: number;
|
|
76
|
+
/** Cumulative real dollar cost summed across every iteration that ran, from each iteration's
|
|
77
|
+
* `CodingAgentDriverResult.costUsd`. Only the `agent-sdk` provider reports this today (the CLI-subprocess
|
|
78
|
+
* providers report no cost signal) -- always `0` for a provider that never reports one, never fabricated. */
|
|
79
|
+
totalCostUsd: number;
|
|
80
|
+
/** The real accumulated {@link AttemptMeterTotals} across every iteration that ran (attempt-metering.ts,
|
|
81
|
+
* #5395) -- a superset of `totalTurnsUsed`/`totalCostUsd` above that also carries `wallClockMs` (real,
|
|
82
|
+
* measured around each driver invocation) and `tokens` (always 0 today -- no driver reports a real token
|
|
83
|
+
* count, an honest absence rather than a fabricated number). */
|
|
84
|
+
finalMeterTotals: AttemptMeterTotals;
|
|
85
|
+
/** The budget axes breached at the point this attempt abandoned, when `input.budget` was set and at least
|
|
86
|
+
* one axis was at/over its ceiling -- empty when no budget was configured or none breached. */
|
|
87
|
+
budgetBreaches: AttemptBudgetAxis[];
|
|
88
|
+
iterations: readonly IterateLoopIterationRecord[];
|
|
89
|
+
/** Populated only when `outcome === "handoff"`. */
|
|
90
|
+
handoffPacket?: HandoffPacket | undefined;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Thin wrapper over {@link runIterateLoopCore} that attaches the real accumulated
|
|
94
|
+
* {@link AttemptMeterTotals}/breached axes (#5395) once, at this single always-reached return point -- see
|
|
95
|
+
* {@link IterateLoopCoreResult}'s own doc comment for why the core itself doesn't carry these fields.
|
|
96
|
+
*/
|
|
97
|
+
export declare function runIterateLoop(input: IterateLoopInput, deps: IterateLoopDeps): Promise<IterateLoopResult>;
|