issue-flow 0.11.1 → 0.13.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/README.md +79 -274
- package/dist/agent-BXRIEKVM.js +238 -0
- package/dist/agent-BXRIEKVM.js.map +1 -0
- package/dist/{analyze-XV3K25K3.js → analyze-WS47UYGN.js} +32 -15
- package/dist/analyze-WS47UYGN.js.map +1 -0
- package/dist/{apply-WUJHDD7A.js → apply-OS62UWRN.js} +15 -5
- package/dist/{apply-WUJHDD7A.js.map → apply-OS62UWRN.js.map} +1 -1
- package/dist/bench-SA3BOME5.js +906 -0
- package/dist/bench-SA3BOME5.js.map +1 -0
- package/dist/chunk-37N2RJW2.js +120 -0
- package/dist/chunk-37N2RJW2.js.map +1 -0
- package/dist/chunk-3XLCNVMD.js +180 -0
- package/dist/chunk-3XLCNVMD.js.map +1 -0
- package/dist/chunk-5TB6KLC2.js +72 -0
- package/dist/chunk-5TB6KLC2.js.map +1 -0
- package/dist/{chunk-OBLVLIUS.js → chunk-664Y3NDX.js} +25 -13
- package/dist/chunk-664Y3NDX.js.map +1 -0
- package/dist/chunk-6FDFRNXZ.js +357 -0
- package/dist/chunk-6FDFRNXZ.js.map +1 -0
- package/dist/chunk-7KD66LIM.js +658 -0
- package/dist/chunk-7KD66LIM.js.map +1 -0
- package/dist/chunk-7Z4Y3VKM.js +26 -0
- package/dist/chunk-7Z4Y3VKM.js.map +1 -0
- package/dist/chunk-ARFVHC2F.js +1111 -0
- package/dist/chunk-ARFVHC2F.js.map +1 -0
- package/dist/{chunk-RKTEV6EG.js → chunk-BHJCGWQX.js} +141 -12
- package/dist/chunk-BHJCGWQX.js.map +1 -0
- package/dist/chunk-BQDQTIM7.js +74 -0
- package/dist/chunk-BQDQTIM7.js.map +1 -0
- package/dist/chunk-CNBYCMSU.js +2709 -0
- package/dist/chunk-CNBYCMSU.js.map +1 -0
- package/dist/chunk-CRGIQFUQ.js +83 -0
- package/dist/chunk-CRGIQFUQ.js.map +1 -0
- package/dist/chunk-DIG5XKQQ.js +298 -0
- package/dist/chunk-DIG5XKQQ.js.map +1 -0
- package/dist/chunk-FZDR7CGH.js +71 -0
- package/dist/chunk-FZDR7CGH.js.map +1 -0
- package/dist/chunk-HEF2RMKN.js +413 -0
- package/dist/chunk-HEF2RMKN.js.map +1 -0
- package/dist/{chunk-IVHZEYAX.js → chunk-HJGGNLWI.js} +25 -19
- package/dist/chunk-HJGGNLWI.js.map +1 -0
- package/dist/chunk-HQAHNIAR.js +283 -0
- package/dist/chunk-HQAHNIAR.js.map +1 -0
- package/dist/{chunk-GANSDJV7.js → chunk-IHIEXUTC.js} +46 -24
- package/dist/chunk-IHIEXUTC.js.map +1 -0
- package/dist/chunk-IV5SS4JN.js +84 -0
- package/dist/chunk-IV5SS4JN.js.map +1 -0
- package/dist/{chunk-HC2MT7BX.js → chunk-IYV6KNVF.js} +158 -592
- package/dist/chunk-IYV6KNVF.js.map +1 -0
- package/dist/{chunk-LVKQ2AKT.js → chunk-J62QSVP6.js} +10 -8
- package/dist/chunk-J62QSVP6.js.map +1 -0
- package/dist/chunk-KMMASOA6.js +134 -0
- package/dist/chunk-KMMASOA6.js.map +1 -0
- package/dist/chunk-KQHB7ZT3.js +559 -0
- package/dist/chunk-KQHB7ZT3.js.map +1 -0
- package/dist/chunk-L6HSKTQE.js +77 -0
- package/dist/chunk-L6HSKTQE.js.map +1 -0
- package/dist/{chunk-6N7LZZ5U.js → chunk-LZ6GFQAG.js} +474 -160
- package/dist/chunk-LZ6GFQAG.js.map +1 -0
- package/dist/{chunk-HQYXVIH6.js → chunk-MIVHK5YI.js} +5 -5
- package/dist/chunk-NEZZ6KDR.js +1217 -0
- package/dist/chunk-NEZZ6KDR.js.map +1 -0
- package/dist/{run-DBF2VWUN.js → chunk-OPNGPUKJ.js} +747 -594
- package/dist/chunk-OPNGPUKJ.js.map +1 -0
- package/dist/chunk-P3LRUNC6.js +16 -0
- package/dist/chunk-P3LRUNC6.js.map +1 -0
- package/dist/{chunk-BXCPXRBR.js → chunk-PIVGJM6K.js} +129 -32
- package/dist/chunk-PIVGJM6K.js.map +1 -0
- package/dist/{chunk-AAY6KM7N.js → chunk-PV4OX5TC.js} +167 -196
- package/dist/chunk-PV4OX5TC.js.map +1 -0
- package/dist/chunk-QCXPNSEL.js +141 -0
- package/dist/chunk-QCXPNSEL.js.map +1 -0
- package/dist/{chunk-34WNTK3K.js → chunk-QXDNBYQF.js} +53 -13
- package/dist/chunk-QXDNBYQF.js.map +1 -0
- package/dist/chunk-SNTTOQAD.js +30 -0
- package/dist/chunk-SNTTOQAD.js.map +1 -0
- package/dist/{chunk-EY6WMHV2.js → chunk-TM67EHSV.js} +67 -23
- package/dist/chunk-TM67EHSV.js.map +1 -0
- package/dist/chunk-TVN2JVOF.js +77 -0
- package/dist/chunk-TVN2JVOF.js.map +1 -0
- package/dist/chunk-TX5WIVYP.js +129 -0
- package/dist/chunk-TX5WIVYP.js.map +1 -0
- package/dist/{chunk-KQ4JQN7T.js → chunk-UJ7XWK7J.js} +83 -21
- package/dist/chunk-UJ7XWK7J.js.map +1 -0
- package/dist/{chunk-47VIN33V.js → chunk-UOCPC2Y2.js} +596 -17
- package/dist/chunk-UOCPC2Y2.js.map +1 -0
- package/dist/chunk-XEYH6ZZI.js +289 -0
- package/dist/chunk-XEYH6ZZI.js.map +1 -0
- package/dist/cli.js +390 -40
- package/dist/cli.js.map +1 -1
- package/dist/config-MEM2IWWN.js +76 -0
- package/dist/contract-JH2WBIO6.js +8 -0
- package/dist/conventions-WV4NDXGH.js +143 -0
- package/dist/conventions-WV4NDXGH.js.map +1 -0
- package/dist/execute-NITIHHCK.js +31 -0
- package/dist/{generate-TBVTLMOP.js → generate-MVQI7A45.js} +39 -19
- package/dist/{generate-TBVTLMOP.js.map → generate-MVQI7A45.js.map} +1 -1
- package/dist/git-YRVRUN3C.js +31 -0
- package/dist/init-OKWEC3EV.js +21 -0
- package/dist/operations-LU6JA3BX.js +374 -0
- package/dist/operations-LU6JA3BX.js.map +1 -0
- package/dist/permissions-7NMDFU27.js +18 -0
- package/dist/plan-LEJJF3DE.js +31 -0
- package/dist/{policy-XQW5NUIE.js → policy-JSYEK5B2.js} +17 -4
- package/dist/policy-JSYEK5B2.js.map +1 -0
- package/dist/pr-HPVUZ7S3.js +36 -0
- package/dist/pr-review-R2QZBT3F.js +29 -0
- package/dist/pr-review-R2QZBT3F.js.map +1 -0
- package/dist/prd-5O6GYCC6.js +30 -0
- package/dist/prd-5O6GYCC6.js.map +1 -0
- package/dist/ps-TNE44R5V.js +90 -0
- package/dist/ps-TNE44R5V.js.map +1 -0
- package/dist/recorder-XGF55WXI.js +31 -0
- package/dist/recorder-XGF55WXI.js.map +1 -0
- package/dist/registry-LGPE53QD.js +15 -0
- package/dist/registry-LGPE53QD.js.map +1 -0
- package/dist/resume-QMEPMSZW.js +238 -0
- package/dist/resume-QMEPMSZW.js.map +1 -0
- package/dist/review-YKWD6SNN.js +32 -0
- package/dist/review-YKWD6SNN.js.map +1 -0
- package/dist/routing-2ZUMZPJB.js +91 -0
- package/dist/routing-2ZUMZPJB.js.map +1 -0
- package/dist/run-YU4TR62R.js +50 -0
- package/dist/run-YU4TR62R.js.map +1 -0
- package/dist/state-manager-X3PT3FGT.js +36 -0
- package/dist/state-manager-X3PT3FGT.js.map +1 -0
- package/dist/usage-VCFLDA5Y.js +174 -0
- package/dist/usage-VCFLDA5Y.js.map +1 -0
- package/dist/{web-ANQQTTRD.js → web-XVHLX62B.js} +43 -27
- package/dist/web-XVHLX62B.js.map +1 -0
- package/package.json +6 -2
- package/prompts/execute.md +7 -8
- package/prompts/plan.md +4 -5
- package/prompts/pr.md +13 -6
- package/web/AGENTS.md +3 -3
- package/web/public/app.css +38 -0
- package/web/public/app.js +132 -10
- package/web/public/index.html +24 -0
- package/dist/analyze-XV3K25K3.js.map +0 -1
- package/dist/chunk-34WNTK3K.js.map +0 -1
- package/dist/chunk-3BOHTH4V.js +0 -58
- package/dist/chunk-3BOHTH4V.js.map +0 -1
- package/dist/chunk-47VIN33V.js.map +0 -1
- package/dist/chunk-6N7LZZ5U.js.map +0 -1
- package/dist/chunk-AAY6KM7N.js.map +0 -1
- package/dist/chunk-BXCPXRBR.js.map +0 -1
- package/dist/chunk-EWDJENFW.js +0 -303
- package/dist/chunk-EWDJENFW.js.map +0 -1
- package/dist/chunk-EY6WMHV2.js.map +0 -1
- package/dist/chunk-GANSDJV7.js.map +0 -1
- package/dist/chunk-HC2MT7BX.js.map +0 -1
- package/dist/chunk-HDV3YDTG.js +0 -52
- package/dist/chunk-HDV3YDTG.js.map +0 -1
- package/dist/chunk-IVHZEYAX.js.map +0 -1
- package/dist/chunk-KQ4JQN7T.js.map +0 -1
- package/dist/chunk-LVKQ2AKT.js.map +0 -1
- package/dist/chunk-OBLVLIUS.js.map +0 -1
- package/dist/chunk-RKTEV6EG.js.map +0 -1
- package/dist/chunk-VQ5VN7DF.js +0 -1018
- package/dist/chunk-VQ5VN7DF.js.map +0 -1
- package/dist/config-4THHOXNN.js +0 -41
- package/dist/execute-5OEKLM5X.js +0 -15
- package/dist/init-PRMOTQ7N.js +0 -9
- package/dist/plan-Y2BYZD24.js +0 -17
- package/dist/policy-XQW5NUIE.js.map +0 -1
- package/dist/pr-H6GLOSXF.js +0 -21
- package/dist/pr-review-CK5FR6WX.js +0 -17
- package/dist/prd-4PWLCGH5.js +0 -17
- package/dist/review-QGVAHEZF.js +0 -15
- package/dist/run-DBF2VWUN.js.map +0 -1
- package/dist/web-ANQQTTRD.js.map +0 -1
- /package/dist/{chunk-HQYXVIH6.js.map → chunk-MIVHK5YI.js.map} +0 -0
- /package/dist/{config-4THHOXNN.js.map → config-MEM2IWWN.js.map} +0 -0
- /package/dist/{execute-5OEKLM5X.js.map → contract-JH2WBIO6.js.map} +0 -0
- /package/dist/{init-PRMOTQ7N.js.map → execute-NITIHHCK.js.map} +0 -0
- /package/dist/{plan-Y2BYZD24.js.map → git-YRVRUN3C.js.map} +0 -0
- /package/dist/{pr-H6GLOSXF.js.map → init-OKWEC3EV.js.map} +0 -0
- /package/dist/{pr-review-CK5FR6WX.js.map → permissions-7NMDFU27.js.map} +0 -0
- /package/dist/{prd-4PWLCGH5.js.map → plan-LEJJF3DE.js.map} +0 -0
- /package/dist/{review-QGVAHEZF.js.map → pr-HPVUZ7S3.js.map} +0 -0
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
decideRouting
|
|
4
|
+
} from "./chunk-3XLCNVMD.js";
|
|
5
|
+
import {
|
|
6
|
+
parseJournal
|
|
7
|
+
} from "./chunk-QCXPNSEL.js";
|
|
8
|
+
import {
|
|
9
|
+
ensureRunnersRegistered,
|
|
10
|
+
getActiveResilienceConfig,
|
|
11
|
+
getAgentCliOverrides,
|
|
12
|
+
getRegisteredProviders,
|
|
13
|
+
getShutdownSignal,
|
|
14
|
+
hasExplicitAgentSelection,
|
|
15
|
+
loadAgentConfig,
|
|
16
|
+
loadRoutingConfig,
|
|
17
|
+
peekHarnessVersion,
|
|
18
|
+
resolveAgentFor,
|
|
19
|
+
resolveAntigravityTimeoutMs,
|
|
20
|
+
runnerFor
|
|
21
|
+
} from "./chunk-CNBYCMSU.js";
|
|
22
|
+
import {
|
|
23
|
+
resolveProjectPaths
|
|
24
|
+
} from "./chunk-HQAHNIAR.js";
|
|
25
|
+
import {
|
|
26
|
+
ElapsedTimer,
|
|
27
|
+
createSpinner,
|
|
28
|
+
formatDuration,
|
|
29
|
+
getActivityCallback,
|
|
30
|
+
getIcons,
|
|
31
|
+
getInactivityTimeout,
|
|
32
|
+
getOutputCallback,
|
|
33
|
+
getSessionPublisher,
|
|
34
|
+
isVerbose,
|
|
35
|
+
useColor
|
|
36
|
+
} from "./chunk-IYV6KNVF.js";
|
|
37
|
+
import {
|
|
38
|
+
beginExecution,
|
|
39
|
+
endExecution
|
|
40
|
+
} from "./chunk-6FDFRNXZ.js";
|
|
41
|
+
import {
|
|
42
|
+
isoNow,
|
|
43
|
+
sumUsage
|
|
44
|
+
} from "./chunk-DIG5XKQQ.js";
|
|
45
|
+
import {
|
|
46
|
+
isAgentProviderId,
|
|
47
|
+
providersHealthSchema
|
|
48
|
+
} from "./chunk-NEZZ6KDR.js";
|
|
49
|
+
import {
|
|
50
|
+
ensureCursorStorageGrant
|
|
51
|
+
} from "./chunk-TVN2JVOF.js";
|
|
52
|
+
import {
|
|
53
|
+
abortableDelay,
|
|
54
|
+
resolvePolicy,
|
|
55
|
+
shouldFailover,
|
|
56
|
+
withRetry
|
|
57
|
+
} from "./chunk-KQHB7ZT3.js";
|
|
58
|
+
import {
|
|
59
|
+
classify
|
|
60
|
+
} from "./chunk-XEYH6ZZI.js";
|
|
61
|
+
|
|
62
|
+
// src/routing/budget.ts
|
|
63
|
+
var DEFAULT_CEILINGS = {
|
|
64
|
+
maxCostUsdPerIssue: null,
|
|
65
|
+
maxDurationMsPerIssue: null,
|
|
66
|
+
maxExecutionsPerIssue: null,
|
|
67
|
+
onCeiling: "block"
|
|
68
|
+
};
|
|
69
|
+
function evaluateCeilings(input) {
|
|
70
|
+
const ceilings = { ...DEFAULT_CEILINGS, ...input.ceilings };
|
|
71
|
+
const enforced = [];
|
|
72
|
+
if (ceilings.maxCostUsdPerIssue !== null && input.spent.costStatus === "reported") {
|
|
73
|
+
enforced.push("cost");
|
|
74
|
+
}
|
|
75
|
+
if (ceilings.maxDurationMsPerIssue !== null) enforced.push("duration");
|
|
76
|
+
if (ceilings.maxExecutionsPerIssue !== null) enforced.push("executions");
|
|
77
|
+
if (ceilings.maxCostUsdPerIssue !== null && input.spent.costStatus === "reported" && input.spent.reportedUsd >= ceilings.maxCostUsdPerIssue) {
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
stopReason: "max_cost",
|
|
81
|
+
binding: "cost",
|
|
82
|
+
onCeiling: "block",
|
|
83
|
+
numbers: { spent: input.spent.reportedUsd, ceiling: ceilings.maxCostUsdPerIssue },
|
|
84
|
+
enforced
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (ceilings.maxDurationMsPerIssue !== null && input.spent.durationMs >= ceilings.maxDurationMsPerIssue) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
stopReason: "max_duration",
|
|
91
|
+
binding: "duration",
|
|
92
|
+
onCeiling: "block",
|
|
93
|
+
numbers: { spent: input.spent.durationMs, ceiling: ceilings.maxDurationMsPerIssue },
|
|
94
|
+
enforced
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (ceilings.maxExecutionsPerIssue !== null && input.spent.executions >= ceilings.maxExecutionsPerIssue) {
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
stopReason: "max_attempts",
|
|
101
|
+
binding: "executions",
|
|
102
|
+
onCeiling: "block",
|
|
103
|
+
numbers: { spent: input.spent.executions, ceiling: ceilings.maxExecutionsPerIssue },
|
|
104
|
+
enforced
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { ok: true, binding: null };
|
|
108
|
+
}
|
|
109
|
+
function spendFromCost(cost) {
|
|
110
|
+
if (cost.status === "reported") {
|
|
111
|
+
return { reportedUsd: cost.amount, costStatus: "reported" };
|
|
112
|
+
}
|
|
113
|
+
return { reportedUsd: 0, costStatus: cost.status };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/agents/health.ts
|
|
117
|
+
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
118
|
+
import { dirname } from "path";
|
|
119
|
+
var DEFAULT_PROVIDER_COOLDOWN_MS = 6e4;
|
|
120
|
+
var DEFAULT_PROVIDER_MAX_COOLDOWN_MS = 30 * 6e4;
|
|
121
|
+
var DEFAULT_PROVIDER_FAILURE_WINDOW_MS = 5 * 6e4;
|
|
122
|
+
var DEFAULT_PROVIDER_FAILURES_TO_TRIP = 3;
|
|
123
|
+
function resolveProviderHealthConfig(config = {}) {
|
|
124
|
+
return {
|
|
125
|
+
cooldownMs: Math.max(0, config.cooldownMs ?? DEFAULT_PROVIDER_COOLDOWN_MS),
|
|
126
|
+
maxCooldownMs: Math.max(
|
|
127
|
+
config.cooldownMs ?? DEFAULT_PROVIDER_COOLDOWN_MS,
|
|
128
|
+
config.maxCooldownMs ?? DEFAULT_PROVIDER_MAX_COOLDOWN_MS
|
|
129
|
+
),
|
|
130
|
+
failureWindowMs: Math.max(1, config.failureWindowMs ?? DEFAULT_PROVIDER_FAILURE_WINDOW_MS),
|
|
131
|
+
failuresToTrip: Math.max(
|
|
132
|
+
1,
|
|
133
|
+
Math.trunc(config.failuresToTrip ?? DEFAULT_PROVIDER_FAILURES_TO_TRIP)
|
|
134
|
+
)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function emptyHealth() {
|
|
138
|
+
return providersHealthSchema.parse({});
|
|
139
|
+
}
|
|
140
|
+
function emptyRecord() {
|
|
141
|
+
return providersHealthSchema.parse({ providers: { provider: {} } }).providers.provider;
|
|
142
|
+
}
|
|
143
|
+
async function readProvidersHealth(filePath) {
|
|
144
|
+
try {
|
|
145
|
+
return providersHealthSchema.parse(JSON.parse(await readFile(filePath, "utf-8")));
|
|
146
|
+
} catch {
|
|
147
|
+
return emptyHealth();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function writeProvidersHealth(filePath, value) {
|
|
151
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
152
|
+
const tmp = `${filePath}.tmp-${process.pid}`;
|
|
153
|
+
await writeFile(tmp, `${JSON.stringify(value, null, 2)}
|
|
154
|
+
`, "utf-8");
|
|
155
|
+
await rename(tmp, filePath);
|
|
156
|
+
}
|
|
157
|
+
async function updateRecord(filePath, provider, update) {
|
|
158
|
+
const health = await readProvidersHealth(filePath);
|
|
159
|
+
const next = update(health.providers[provider] ?? emptyRecord());
|
|
160
|
+
await writeProvidersHealth(filePath, {
|
|
161
|
+
...health,
|
|
162
|
+
providers: { ...health.providers, [provider]: next }
|
|
163
|
+
});
|
|
164
|
+
return next;
|
|
165
|
+
}
|
|
166
|
+
function cooldownDelay(level, config) {
|
|
167
|
+
return Math.min(config.maxCooldownMs, config.cooldownMs * 2 ** Math.max(0, level));
|
|
168
|
+
}
|
|
169
|
+
var BREAKER_KINDS = /* @__PURE__ */ new Set([
|
|
170
|
+
"timeout",
|
|
171
|
+
"stalled",
|
|
172
|
+
"provider_down",
|
|
173
|
+
"provider_crash"
|
|
174
|
+
]);
|
|
175
|
+
async function recordProviderFailure(filePath, provider, failure, options = {}) {
|
|
176
|
+
const nowMs = options.now?.() ?? Date.now();
|
|
177
|
+
const at = new Date(nowMs).toISOString();
|
|
178
|
+
const config = resolveProviderHealthConfig(options.config);
|
|
179
|
+
return updateRecord(filePath, provider, (record) => {
|
|
180
|
+
const failures = [
|
|
181
|
+
...record.failures.filter((entry) => {
|
|
182
|
+
const timestamp = Date.parse(entry.at);
|
|
183
|
+
return Number.isFinite(timestamp) && nowMs - timestamp <= config.failureWindowMs;
|
|
184
|
+
}),
|
|
185
|
+
{ at, kind: failure.kind }
|
|
186
|
+
];
|
|
187
|
+
const consecutiveFailures = record.consecutiveFailures + 1;
|
|
188
|
+
if (failure.kind === "authentication") {
|
|
189
|
+
return {
|
|
190
|
+
...record,
|
|
191
|
+
status: "auth_failed",
|
|
192
|
+
failures,
|
|
193
|
+
consecutiveFailures,
|
|
194
|
+
cooldownUntil: null,
|
|
195
|
+
lastFailureKind: failure.kind,
|
|
196
|
+
lastFailureAt: at,
|
|
197
|
+
probeInFlight: false,
|
|
198
|
+
probeStartedAt: null
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (failure.kind === "rate_limit") {
|
|
202
|
+
const delay = failure.retryAfterMs ?? cooldownDelay(record.cooldownLevel, config);
|
|
203
|
+
return {
|
|
204
|
+
...record,
|
|
205
|
+
status: "rate_limited",
|
|
206
|
+
failures,
|
|
207
|
+
consecutiveFailures,
|
|
208
|
+
cooldownLevel: failure.retryAfterMs === void 0 ? record.cooldownLevel + 1 : record.cooldownLevel,
|
|
209
|
+
cooldownUntil: new Date(nowMs + delay).toISOString(),
|
|
210
|
+
lastFailureKind: failure.kind,
|
|
211
|
+
lastFailureAt: at,
|
|
212
|
+
probeInFlight: false,
|
|
213
|
+
probeStartedAt: null
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (BREAKER_KINDS.has(failure.kind)) {
|
|
217
|
+
const recentProviderFailures = failures.filter(
|
|
218
|
+
(entry) => BREAKER_KINDS.has(entry.kind)
|
|
219
|
+
).length;
|
|
220
|
+
const unavailable = record.status === "half_open" || recentProviderFailures >= config.failuresToTrip;
|
|
221
|
+
const delay = unavailable ? cooldownDelay(record.cooldownLevel, config) : 0;
|
|
222
|
+
return {
|
|
223
|
+
...record,
|
|
224
|
+
status: unavailable ? "unavailable" : "degraded",
|
|
225
|
+
failures,
|
|
226
|
+
consecutiveFailures,
|
|
227
|
+
cooldownLevel: unavailable ? record.cooldownLevel + 1 : record.cooldownLevel,
|
|
228
|
+
cooldownUntil: unavailable ? new Date(nowMs + delay).toISOString() : null,
|
|
229
|
+
lastFailureKind: failure.kind,
|
|
230
|
+
lastFailureAt: at,
|
|
231
|
+
probeInFlight: false,
|
|
232
|
+
probeStartedAt: null
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
...record,
|
|
237
|
+
lastFailureKind: failure.kind,
|
|
238
|
+
lastFailureAt: at,
|
|
239
|
+
probeInFlight: false,
|
|
240
|
+
probeStartedAt: null
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async function recordProviderSuccess(filePath, provider, options = {}) {
|
|
245
|
+
const at = new Date(options.now?.() ?? Date.now()).toISOString();
|
|
246
|
+
return updateRecord(filePath, provider, (record) => ({
|
|
247
|
+
...record,
|
|
248
|
+
status: "healthy",
|
|
249
|
+
failures: [],
|
|
250
|
+
consecutiveFailures: 0,
|
|
251
|
+
cooldownLevel: 0,
|
|
252
|
+
cooldownUntil: null,
|
|
253
|
+
lastFailureKind: null,
|
|
254
|
+
lastSuccessAt: at,
|
|
255
|
+
probeInFlight: false,
|
|
256
|
+
probeStartedAt: null
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
async function openProviderCircuit(filePath, provider, options = {}) {
|
|
260
|
+
const nowMs = options.now?.() ?? Date.now();
|
|
261
|
+
const config = resolveProviderHealthConfig(options.config);
|
|
262
|
+
return updateRecord(filePath, provider, (record) => {
|
|
263
|
+
if (record.status === "unavailable" || record.status === "rate_limited") return record;
|
|
264
|
+
const delay = cooldownDelay(record.cooldownLevel, config);
|
|
265
|
+
return {
|
|
266
|
+
...record,
|
|
267
|
+
status: "unavailable",
|
|
268
|
+
cooldownLevel: record.cooldownLevel + 1,
|
|
269
|
+
cooldownUntil: new Date(nowMs + delay).toISOString(),
|
|
270
|
+
probeInFlight: false,
|
|
271
|
+
probeStartedAt: null
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async function acquireHalfOpenProbe(filePath, provider, options = {}) {
|
|
276
|
+
const nowMs = options.now?.() ?? Date.now();
|
|
277
|
+
const at = new Date(nowMs).toISOString();
|
|
278
|
+
const config = resolveProviderHealthConfig(options.config);
|
|
279
|
+
let acquired = false;
|
|
280
|
+
const record = await updateRecord(filePath, provider, (current) => {
|
|
281
|
+
const cooldownUntil = current.cooldownUntil === null ? 0 : Date.parse(current.cooldownUntil);
|
|
282
|
+
if (Number.isFinite(cooldownUntil) && cooldownUntil > nowMs) return current;
|
|
283
|
+
const probeStarted = current.probeStartedAt === null ? 0 : Date.parse(current.probeStartedAt);
|
|
284
|
+
const probeIsFresh = current.probeInFlight && Number.isFinite(probeStarted) && nowMs - probeStarted < config.cooldownMs;
|
|
285
|
+
if (probeIsFresh) return current;
|
|
286
|
+
acquired = true;
|
|
287
|
+
return {
|
|
288
|
+
...current,
|
|
289
|
+
status: "half_open",
|
|
290
|
+
cooldownUntil: null,
|
|
291
|
+
probeInFlight: true,
|
|
292
|
+
probeStartedAt: at
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
return { acquired, record };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/agents/select.ts
|
|
299
|
+
var AgentSelectionBlockedError = class extends Error {
|
|
300
|
+
provider;
|
|
301
|
+
kind;
|
|
302
|
+
constructor(provider, kind) {
|
|
303
|
+
super(
|
|
304
|
+
`Authentication failed for agent provider '${provider}' (${kind}); the run is blocked. Authenticate or repair that provider before resuming.`
|
|
305
|
+
);
|
|
306
|
+
this.name = "AgentSelectionBlockedError";
|
|
307
|
+
this.provider = provider;
|
|
308
|
+
this.kind = kind;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
function remainingMs(until, nowMs) {
|
|
312
|
+
if (until === null) return null;
|
|
313
|
+
const parsed = Date.parse(until);
|
|
314
|
+
return Number.isFinite(parsed) ? Math.max(0, parsed - nowMs) : null;
|
|
315
|
+
}
|
|
316
|
+
function failoverIsDue(record, config) {
|
|
317
|
+
if (record?.lastFailureKind == null) return false;
|
|
318
|
+
return shouldFailover(resolvePolicy(record.lastFailureKind, config), record.consecutiveFailures);
|
|
319
|
+
}
|
|
320
|
+
async function verdictFor(provider, record, options) {
|
|
321
|
+
if (record === void 0 || record.status === "healthy") {
|
|
322
|
+
return { available: true, waitMs: null, cooldownUntil: null, reason: null, blocked: false };
|
|
323
|
+
}
|
|
324
|
+
const reason = record.lastFailureKind;
|
|
325
|
+
if (record.status === "auth_failed") {
|
|
326
|
+
const mayFailover = reason !== null && failoverIsDue(record, options.config);
|
|
327
|
+
return {
|
|
328
|
+
available: false,
|
|
329
|
+
waitMs: null,
|
|
330
|
+
cooldownUntil: null,
|
|
331
|
+
reason,
|
|
332
|
+
blocked: options.primary && !mayFailover
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (record.status === "degraded") {
|
|
336
|
+
const due = failoverIsDue(record, options.config);
|
|
337
|
+
const opened = due ? await openProviderCircuit(options.healthFile, provider, {
|
|
338
|
+
now: () => options.nowMs,
|
|
339
|
+
config: options.config.providers
|
|
340
|
+
}) : record;
|
|
341
|
+
return {
|
|
342
|
+
available: !due,
|
|
343
|
+
waitMs: due ? remainingMs(opened.cooldownUntil, options.nowMs) : null,
|
|
344
|
+
cooldownUntil: opened.cooldownUntil,
|
|
345
|
+
reason,
|
|
346
|
+
blocked: false
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (record.status === "unavailable" || record.status === "rate_limited" || record.status === "half_open") {
|
|
350
|
+
const waitMs = remainingMs(record.cooldownUntil, options.nowMs);
|
|
351
|
+
if (waitMs !== null && waitMs > 0) {
|
|
352
|
+
return {
|
|
353
|
+
available: false,
|
|
354
|
+
waitMs,
|
|
355
|
+
cooldownUntil: record.cooldownUntil,
|
|
356
|
+
reason,
|
|
357
|
+
blocked: false
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const probe = await acquireHalfOpenProbe(options.healthFile, provider, {
|
|
361
|
+
now: () => options.nowMs,
|
|
362
|
+
config: options.config.providers
|
|
363
|
+
});
|
|
364
|
+
return {
|
|
365
|
+
available: probe.acquired,
|
|
366
|
+
waitMs: probe.acquired ? null : resolveProviderHealthConfig(options.config.providers).cooldownMs,
|
|
367
|
+
cooldownUntil: probe.record.cooldownUntil,
|
|
368
|
+
reason,
|
|
369
|
+
blocked: false
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return { available: true, waitMs: null, cooldownUntil: null, reason, blocked: false };
|
|
373
|
+
}
|
|
374
|
+
function providerOrder(primary, config) {
|
|
375
|
+
ensureRunnersRegistered();
|
|
376
|
+
const registered = getRegisteredProviders();
|
|
377
|
+
const configured = (config.providers?.chain ?? []).filter(isAgentProviderId);
|
|
378
|
+
return [.../* @__PURE__ */ new Set([primary, ...configured, ...registered])];
|
|
379
|
+
}
|
|
380
|
+
function settingsFor(base, provider) {
|
|
381
|
+
if (provider === base.provider) return base;
|
|
382
|
+
return {
|
|
383
|
+
...base,
|
|
384
|
+
provider,
|
|
385
|
+
// A model name belongs to the configured primary unless the selected
|
|
386
|
+
// provider is that primary. Passing a Claude alias to Codex (or vice
|
|
387
|
+
// versa) is less useful than letting the fallback use its own default.
|
|
388
|
+
model: null
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
async function selectAgentForInvocation(phase, options = {}) {
|
|
392
|
+
const config = options.config ?? getActiveResilienceConfig();
|
|
393
|
+
const base = await resolveAgentFor(phase);
|
|
394
|
+
const failover = config.providers?.failover === true;
|
|
395
|
+
if (!failover) {
|
|
396
|
+
return {
|
|
397
|
+
primary: base.provider,
|
|
398
|
+
provider: base.provider,
|
|
399
|
+
settings: base,
|
|
400
|
+
healthFile: null,
|
|
401
|
+
failover: false,
|
|
402
|
+
reason: null,
|
|
403
|
+
cooldownUntil: null
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
let healthFile;
|
|
407
|
+
try {
|
|
408
|
+
healthFile = (await resolveProjectPaths()).providersHealthFile;
|
|
409
|
+
} catch {
|
|
410
|
+
return {
|
|
411
|
+
primary: base.provider,
|
|
412
|
+
provider: base.provider,
|
|
413
|
+
settings: base,
|
|
414
|
+
healthFile: null,
|
|
415
|
+
failover: true,
|
|
416
|
+
reason: null,
|
|
417
|
+
cooldownUntil: null
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const delay = options.delay ?? abortableDelay;
|
|
421
|
+
selection: for (; ; ) {
|
|
422
|
+
const nowMs = options.now?.() ?? Date.now();
|
|
423
|
+
const health = await readProvidersHealth(healthFile);
|
|
424
|
+
const waits = [];
|
|
425
|
+
let primaryReason = null;
|
|
426
|
+
let primaryCooldown = null;
|
|
427
|
+
for (const provider of providerOrder(base.provider, config)) {
|
|
428
|
+
const verdict = await verdictFor(provider, health.providers[provider], {
|
|
429
|
+
healthFile,
|
|
430
|
+
config,
|
|
431
|
+
primary: provider === base.provider,
|
|
432
|
+
nowMs
|
|
433
|
+
});
|
|
434
|
+
if (provider === base.provider) {
|
|
435
|
+
primaryReason = verdict.reason;
|
|
436
|
+
primaryCooldown = verdict.cooldownUntil;
|
|
437
|
+
}
|
|
438
|
+
if (verdict.blocked && verdict.reason !== null) {
|
|
439
|
+
throw new AgentSelectionBlockedError(provider, verdict.reason);
|
|
440
|
+
}
|
|
441
|
+
if (provider === base.provider && !verdict.available && verdict.waitMs !== null && !failoverIsDue(health.providers[provider], config)) {
|
|
442
|
+
if (!await delay(Math.max(1, verdict.waitMs), { signal: getShutdownSignal() })) {
|
|
443
|
+
throw new Error("Agent provider selection was interrupted while waiting for cooldown.");
|
|
444
|
+
}
|
|
445
|
+
continue selection;
|
|
446
|
+
}
|
|
447
|
+
if (verdict.available) {
|
|
448
|
+
return {
|
|
449
|
+
primary: base.provider,
|
|
450
|
+
provider,
|
|
451
|
+
settings: settingsFor(base, provider),
|
|
452
|
+
healthFile,
|
|
453
|
+
failover: provider !== base.provider,
|
|
454
|
+
reason: provider === base.provider ? null : primaryReason,
|
|
455
|
+
cooldownUntil: primaryCooldown
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
if (verdict.waitMs !== null) waits.push(verdict.waitMs);
|
|
459
|
+
}
|
|
460
|
+
const waitMs = waits.length > 0 ? Math.max(1, Math.min(...waits)) : null;
|
|
461
|
+
if (waitMs === null) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
"No healthy agent provider is available and none has a cooldown to wait for."
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
if (!await delay(waitMs, { signal: getShutdownSignal() })) {
|
|
467
|
+
throw new Error("Agent provider selection was interrupted while waiting for cooldown.");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/agents/invoke.ts
|
|
473
|
+
async function selectionForForced(invocation) {
|
|
474
|
+
const settings = await resolveAgentFor(invocation.phase, {
|
|
475
|
+
cli: { forceProvider: invocation.forceProvider }
|
|
476
|
+
});
|
|
477
|
+
return {
|
|
478
|
+
primary: settings.provider,
|
|
479
|
+
provider: settings.provider,
|
|
480
|
+
settings,
|
|
481
|
+
healthFile: null,
|
|
482
|
+
failover: false,
|
|
483
|
+
reason: null,
|
|
484
|
+
cooldownUntil: null
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
var attempts = /* @__PURE__ */ new Map();
|
|
488
|
+
var lastFailure = /* @__PURE__ */ new Map();
|
|
489
|
+
var issueSpend = {
|
|
490
|
+
reportedUsd: 0,
|
|
491
|
+
durationMs: 0,
|
|
492
|
+
executions: 0,
|
|
493
|
+
sawReported: false
|
|
494
|
+
};
|
|
495
|
+
function declaredAgentIdentity(provider) {
|
|
496
|
+
switch (provider) {
|
|
497
|
+
case "claude":
|
|
498
|
+
return { harness: "claude-code", vendor: "anthropic" };
|
|
499
|
+
case "codex":
|
|
500
|
+
return { harness: "codex-cli", vendor: "openai" };
|
|
501
|
+
case "cursor":
|
|
502
|
+
return { harness: "cursor-cli", vendor: "cursor" };
|
|
503
|
+
case "antigravity":
|
|
504
|
+
return { harness: "antigravity-cli", vendor: "google" };
|
|
505
|
+
default: {
|
|
506
|
+
const _exhaustive = provider;
|
|
507
|
+
return _exhaustive;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function triggerOf(selection, attempt) {
|
|
512
|
+
if (selection.failover) return "fallback";
|
|
513
|
+
if (attempt > 1) return "retry";
|
|
514
|
+
return "initial";
|
|
515
|
+
}
|
|
516
|
+
function nextAttempt(phase) {
|
|
517
|
+
const attempt = (attempts.get(phase) ?? 0) + 1;
|
|
518
|
+
attempts.set(phase, attempt);
|
|
519
|
+
return attempt;
|
|
520
|
+
}
|
|
521
|
+
function resetAgentInvocationState() {
|
|
522
|
+
attempts.clear();
|
|
523
|
+
lastFailure.clear();
|
|
524
|
+
issueSpend.reportedUsd = 0;
|
|
525
|
+
issueSpend.durationMs = 0;
|
|
526
|
+
issueSpend.executions = 0;
|
|
527
|
+
issueSpend.sawReported = false;
|
|
528
|
+
}
|
|
529
|
+
async function invokeSelectedAgent(invocation) {
|
|
530
|
+
const config = getActiveResilienceConfig();
|
|
531
|
+
const selection = invocation.forceProvider === void 0 ? await selectAgentForInvocation(invocation.phase, { config }) : await selectionForForced(invocation);
|
|
532
|
+
const attempt = nextAttempt(invocation.phase);
|
|
533
|
+
const publisher = getSessionPublisher();
|
|
534
|
+
publisher.publish({
|
|
535
|
+
type: "agent:attempt",
|
|
536
|
+
at: isoNow(),
|
|
537
|
+
attempt,
|
|
538
|
+
provider: selection.provider,
|
|
539
|
+
model: selection.settings.model,
|
|
540
|
+
primaryProvider: selection.primary
|
|
541
|
+
});
|
|
542
|
+
if (selection.failover) {
|
|
543
|
+
publisher.publish({
|
|
544
|
+
type: "failover",
|
|
545
|
+
at: isoNow(),
|
|
546
|
+
from: selection.primary,
|
|
547
|
+
to: selection.provider,
|
|
548
|
+
reason: selection.reason,
|
|
549
|
+
cooldownUntil: selection.cooldownUntil
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
const identity = declaredAgentIdentity(selection.provider);
|
|
553
|
+
const requested = selection.settings.model;
|
|
554
|
+
const routingCfg = await loadRoutingConfig();
|
|
555
|
+
const agentCfg = await loadAgentConfig();
|
|
556
|
+
const routingDecision = decideRouting({
|
|
557
|
+
phase: invocation.phase,
|
|
558
|
+
actualHarness: identity.harness,
|
|
559
|
+
mode: routingCfg.mode,
|
|
560
|
+
profile: routingCfg.profile,
|
|
561
|
+
skipScore: hasExplicitAgentSelection(agentCfg, getAgentCliOverrides()),
|
|
562
|
+
requiresExtraDirectories: (invocation.addDirs?.length ?? 0) > 0
|
|
563
|
+
});
|
|
564
|
+
const executionId = await beginExecution({
|
|
565
|
+
purpose: invocation.purpose ?? invocation.phase,
|
|
566
|
+
attempt,
|
|
567
|
+
trigger: triggerOf(selection, attempt),
|
|
568
|
+
triggerReason: selection.failover ? selection.reason : attempt > 1 ? lastFailure.get(invocation.phase) ?? null : null,
|
|
569
|
+
harness: identity.harness,
|
|
570
|
+
provider: identity.vendor,
|
|
571
|
+
harnessVersion: peekHarnessVersion(selection.provider) ?? null,
|
|
572
|
+
modelRequested: requested,
|
|
573
|
+
modelResolved: null,
|
|
574
|
+
modelSource: requested ? "config" : "unavailable",
|
|
575
|
+
...routingDecision === null ? {} : { routingDecision }
|
|
576
|
+
});
|
|
577
|
+
const runner = runnerFor(selection.provider);
|
|
578
|
+
const ceiling = evaluateCeilings({
|
|
579
|
+
ceilings: routingCfg.ceilings,
|
|
580
|
+
spent: {
|
|
581
|
+
reportedUsd: issueSpend.reportedUsd,
|
|
582
|
+
costStatus: issueSpend.sawReported ? "reported" : "unknown",
|
|
583
|
+
durationMs: issueSpend.durationMs,
|
|
584
|
+
executions: issueSpend.executions
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
if (!ceiling.ok) {
|
|
588
|
+
const detail = `blocked by ${ceiling.binding} ceiling (${ceiling.numbers.spent} \u2265 ${ceiling.numbers.ceiling}). Enforced by Issue Flow, not by a harness flag.`;
|
|
589
|
+
const run2 = {
|
|
590
|
+
success: false,
|
|
591
|
+
result: "",
|
|
592
|
+
rawOutput: detail,
|
|
593
|
+
exitCode: 1,
|
|
594
|
+
usage: null,
|
|
595
|
+
error: detail,
|
|
596
|
+
agent: { provider: selection.provider, model: selection.settings.model },
|
|
597
|
+
harnessVersion: peekHarnessVersion(selection.provider)
|
|
598
|
+
};
|
|
599
|
+
if (executionId !== null) {
|
|
600
|
+
await endExecution({
|
|
601
|
+
id: executionId,
|
|
602
|
+
status: "failed",
|
|
603
|
+
error: detail,
|
|
604
|
+
exitCode: 1,
|
|
605
|
+
stopReason: ceiling.stopReason
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
run: run2,
|
|
610
|
+
failure: null,
|
|
611
|
+
selection,
|
|
612
|
+
attempt,
|
|
613
|
+
health: null,
|
|
614
|
+
blocked: { stopReason: ceiling.stopReason, detail, actor: "human" }
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
if (runner.capabilities.nativeTimeout && invocation.timeout === 0 && resolveAntigravityTimeoutMs(invocation, selection.settings) === null) {
|
|
618
|
+
const run2 = {
|
|
619
|
+
success: false,
|
|
620
|
+
result: "",
|
|
621
|
+
rawOutput: "configuration: provider has nativeTimeout and received timeout: 0 without a ceiling.",
|
|
622
|
+
exitCode: 1,
|
|
623
|
+
usage: null,
|
|
624
|
+
error: "configuration: provider has nativeTimeout and received timeout: 0 without a ceiling. Set agent.antigravity.executeTimeout.",
|
|
625
|
+
agent: { provider: selection.provider, model: selection.settings.model },
|
|
626
|
+
harnessVersion: peekHarnessVersion(selection.provider)
|
|
627
|
+
};
|
|
628
|
+
if (executionId !== null) {
|
|
629
|
+
await endExecution({
|
|
630
|
+
id: executionId,
|
|
631
|
+
status: "failed",
|
|
632
|
+
error: run2.error,
|
|
633
|
+
exitCode: 1
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
const failure2 = classify({ source: "agent", exitCode: 1, stdout: run2.error });
|
|
637
|
+
return { run: run2, failure: failure2, selection, attempt, health: null };
|
|
638
|
+
}
|
|
639
|
+
if ((invocation.addDirs?.length ?? 0) > 0 && runner.capabilities.extraDirectories === "permission-file") {
|
|
640
|
+
const grant = selection.settings.cursor.permissionsFile === "none" ? { skipped: true, reason: "none" } : await ensureCursorStorageGrant({
|
|
641
|
+
mode: selection.settings.cursor.permissionsFile ?? "global"
|
|
642
|
+
});
|
|
643
|
+
if ("skipped" in grant && selection.settings.cursor.permissionsFile !== "none") {
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Phase '${invocation.phase}' needs extraDirectories on '${selection.provider}', which only grants them via a permission file. Run \`issue-flow agent use cursor\` or set agent.cursor.permissionsFile.`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
} else if ((invocation.addDirs?.length ?? 0) > 0 && runner.capabilities.extraDirectories === "none") {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`Phase '${invocation.phase}' needs extraDirectories, but '${selection.provider}' cannot grant them.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
let run;
|
|
654
|
+
const startedMs = Date.now();
|
|
655
|
+
try {
|
|
656
|
+
run = await runner.run(
|
|
657
|
+
{
|
|
658
|
+
...invocation,
|
|
659
|
+
onLine: (line) => {
|
|
660
|
+
publisher.publish({
|
|
661
|
+
type: "agent:activity",
|
|
662
|
+
at: isoNow(),
|
|
663
|
+
provider: selection.provider
|
|
664
|
+
});
|
|
665
|
+
invocation.onLine?.(line);
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
selection.settings
|
|
669
|
+
);
|
|
670
|
+
} catch (err) {
|
|
671
|
+
if (executionId !== null) {
|
|
672
|
+
await endExecution({
|
|
673
|
+
id: executionId,
|
|
674
|
+
status: "failed",
|
|
675
|
+
error: err instanceof Error ? err.message : String(err)
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
throw err;
|
|
679
|
+
}
|
|
680
|
+
issueSpend.executions += 1;
|
|
681
|
+
issueSpend.durationMs += Math.max(0, Date.now() - startedMs);
|
|
682
|
+
if (run.usage?.costUsd !== void 0) {
|
|
683
|
+
issueSpend.reportedUsd += run.usage.costUsd;
|
|
684
|
+
issueSpend.sawReported = true;
|
|
685
|
+
}
|
|
686
|
+
const failure = run.success && run.exitCode === 0 ? null : classify({
|
|
687
|
+
source: "agent",
|
|
688
|
+
exitCode: run.exitCode,
|
|
689
|
+
stdout: run.rawOutput || run.error || ""
|
|
690
|
+
});
|
|
691
|
+
if (executionId !== null) {
|
|
692
|
+
await endExecution({
|
|
693
|
+
id: executionId,
|
|
694
|
+
status: failure === null ? "completed" : failure.kind === "timeout" ? "timeout" : "failed",
|
|
695
|
+
usage: run.usage,
|
|
696
|
+
error: failure === null ? null : run.error ?? run.rawOutput,
|
|
697
|
+
exitCode: run.exitCode,
|
|
698
|
+
modelResolved: run.agent.model,
|
|
699
|
+
modelSource: run.agent.model ? requested ? "config" : "provider" : requested ? "config" : "unavailable",
|
|
700
|
+
harnessVersion: run.harnessVersion ?? peekHarnessVersion(selection.provider) ?? null,
|
|
701
|
+
providerSessionId: run.sessionId ?? null
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
if (failure === null) lastFailure.delete(invocation.phase);
|
|
705
|
+
else lastFailure.set(invocation.phase, failure.kind);
|
|
706
|
+
let health = null;
|
|
707
|
+
if (selection.healthFile !== null) {
|
|
708
|
+
health = failure === null ? await recordProviderSuccess(selection.healthFile, selection.provider) : await recordProviderFailure(selection.healthFile, selection.provider, failure, {
|
|
709
|
+
config: config.providers
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
publisher.publish({
|
|
713
|
+
type: "agent:result",
|
|
714
|
+
at: isoNow(),
|
|
715
|
+
provider: selection.provider,
|
|
716
|
+
success: failure === null,
|
|
717
|
+
...failure === null ? {} : { failureKind: failure.kind },
|
|
718
|
+
cooldownUntil: health?.cooldownUntil ?? null
|
|
719
|
+
});
|
|
720
|
+
if (failure === null) attempts.delete(invocation.phase);
|
|
721
|
+
return { run, failure, selection, attempt, health };
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/core/decompose.ts
|
|
725
|
+
var DECOMPOSITION_THRESHOLDS = {
|
|
726
|
+
/** Timeouts on the same phase. Two is already a pattern, not bad luck. */
|
|
727
|
+
timeoutsPerPhase: 2,
|
|
728
|
+
/** Stories in the plan. Past this, the plan is a backlog. */
|
|
729
|
+
stories: 15,
|
|
730
|
+
/** Iterations in a row that completed no story. */
|
|
731
|
+
barrenIterations: 5,
|
|
732
|
+
/** Files touched on the branch. */
|
|
733
|
+
filesTouched: 40,
|
|
734
|
+
/** Characters in the issue body. */
|
|
735
|
+
issueBodyChars: 2e4
|
|
736
|
+
};
|
|
737
|
+
var DECOMPOSITION_MIN_SIGNALS = 2;
|
|
738
|
+
function timeoutsByPhase(journal) {
|
|
739
|
+
const counts = /* @__PURE__ */ new Map();
|
|
740
|
+
let phase = "(unknown)";
|
|
741
|
+
for (const { event } of parseJournal(journal)) {
|
|
742
|
+
if (event.type === "phase:start") {
|
|
743
|
+
phase = event.phase;
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
if (event.type !== "retry") continue;
|
|
747
|
+
const kind = event.kind;
|
|
748
|
+
const reason = event.reason ?? "";
|
|
749
|
+
const timedOut = kind === "timeout" || kind === "stalled" || /timed out|stalled/i.test(reason);
|
|
750
|
+
if (!timedOut) continue;
|
|
751
|
+
counts.set(phase, (counts.get(phase) ?? 0) + 1);
|
|
752
|
+
}
|
|
753
|
+
return counts;
|
|
754
|
+
}
|
|
755
|
+
function longestBarrenRun(journal) {
|
|
756
|
+
let passing = 0;
|
|
757
|
+
let current = 0;
|
|
758
|
+
let longest = 0;
|
|
759
|
+
for (const { event } of parseJournal(journal)) {
|
|
760
|
+
if (event.type === "iteration:start") {
|
|
761
|
+
current++;
|
|
762
|
+
longest = Math.max(longest, current);
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (event.type !== "stories:update") continue;
|
|
766
|
+
const nowPassing = event.stories.filter((story) => story.passes).length;
|
|
767
|
+
if (nowPassing > passing) current = 0;
|
|
768
|
+
passing = Math.max(passing, nowPassing);
|
|
769
|
+
}
|
|
770
|
+
return longest;
|
|
771
|
+
}
|
|
772
|
+
function assessDecomposition(input) {
|
|
773
|
+
const signals = [];
|
|
774
|
+
const journal = input.journal ?? "";
|
|
775
|
+
for (const [phase, count] of timeoutsByPhase(journal)) {
|
|
776
|
+
if (count >= DECOMPOSITION_THRESHOLDS.timeoutsPerPhase) {
|
|
777
|
+
signals.push({
|
|
778
|
+
id: "repeated-timeouts",
|
|
779
|
+
detail: `The '${phase}' phase timed out ${count} times (threshold: ${DECOMPOSITION_THRESHOLDS.timeoutsPerPhase}).`
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const stories = input.plan?.userStories.length ?? 0;
|
|
784
|
+
if (stories > DECOMPOSITION_THRESHOLDS.stories) {
|
|
785
|
+
signals.push({
|
|
786
|
+
id: "story-count",
|
|
787
|
+
detail: `The plan has ${stories} user stories (threshold: ${DECOMPOSITION_THRESHOLDS.stories}).`
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
const barren = longestBarrenRun(journal);
|
|
791
|
+
if (barren >= DECOMPOSITION_THRESHOLDS.barrenIterations) {
|
|
792
|
+
signals.push({
|
|
793
|
+
id: "barren-iterations",
|
|
794
|
+
detail: `${barren} iterations in a row completed no story (threshold: ${DECOMPOSITION_THRESHOLDS.barrenIterations}).`
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
const files = input.filesTouched ?? 0;
|
|
798
|
+
if (files > DECOMPOSITION_THRESHOLDS.filesTouched) {
|
|
799
|
+
signals.push({
|
|
800
|
+
id: "files-touched",
|
|
801
|
+
detail: `${files} files were touched on this branch (threshold: ${DECOMPOSITION_THRESHOLDS.filesTouched}).`
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
const body = input.issueBody?.length ?? 0;
|
|
805
|
+
if (body > DECOMPOSITION_THRESHOLDS.issueBodyChars) {
|
|
806
|
+
signals.push({
|
|
807
|
+
id: "issue-size",
|
|
808
|
+
detail: `The issue body is ${body} characters (threshold: ${DECOMPOSITION_THRESHOLDS.issueBodyChars}).`
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
if (input.hitMaxIterations === true) {
|
|
812
|
+
signals.push({
|
|
813
|
+
id: "max-iterations",
|
|
814
|
+
detail: "The execute loop stopped because it ran out of iterations, not because it finished."
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
return { signals, oversized: signals.length >= DECOMPOSITION_MIN_SIGNALS };
|
|
818
|
+
}
|
|
819
|
+
var STORIES_PER_SUBISSUE = 5;
|
|
820
|
+
function proposeSubIssues(plan) {
|
|
821
|
+
const pending = [...plan?.userStories ?? []].filter((story) => !story.passes).sort((a, b) => a.priority - b.priority);
|
|
822
|
+
if (pending.length === 0) return [];
|
|
823
|
+
const chunks = [];
|
|
824
|
+
for (let index = 0; index < pending.length; index += STORIES_PER_SUBISSUE) {
|
|
825
|
+
const stories = pending.slice(index, index + STORIES_PER_SUBISSUE);
|
|
826
|
+
const first = stories[0];
|
|
827
|
+
const last = stories[stories.length - 1];
|
|
828
|
+
const range = first === void 0 || last === void 0 || first.id === last.id ? first?.id ?? "" : `${first.id}\u2013${last.id}`;
|
|
829
|
+
chunks.push({
|
|
830
|
+
title: `${plan?.description ?? "Issue"} \u2014 part ${chunks.length + 1} (${range})`,
|
|
831
|
+
stories,
|
|
832
|
+
dependsOn: chunks.length === 0 ? [] : [chunks[chunks.length - 1]?.title ?? ""]
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
return chunks;
|
|
836
|
+
}
|
|
837
|
+
function buildDecompositionReport(input) {
|
|
838
|
+
const { issueNumber, assessment, plan, at } = input;
|
|
839
|
+
const proposals = proposeSubIssues(plan);
|
|
840
|
+
const lines = [];
|
|
841
|
+
lines.push(`# Issue #${issueNumber} looks larger than one run`, "");
|
|
842
|
+
lines.push(`Detected at ${at}.`, "");
|
|
843
|
+
lines.push(
|
|
844
|
+
"This is a report, not an action. Nothing has been split, and nothing will be",
|
|
845
|
+
"without you asking for it.",
|
|
846
|
+
""
|
|
847
|
+
);
|
|
848
|
+
lines.push("## What was detected", "");
|
|
849
|
+
for (const signal of assessment.signals) {
|
|
850
|
+
lines.push(`- **${signal.id}** \u2014 ${signal.detail}`);
|
|
851
|
+
}
|
|
852
|
+
lines.push("");
|
|
853
|
+
if (proposals.length === 0) {
|
|
854
|
+
lines.push("## Proposed split", "");
|
|
855
|
+
lines.push(
|
|
856
|
+
"No pending user stories were found, so there is nothing to cut here.",
|
|
857
|
+
"The signals above are about the run rather than about the plan.",
|
|
858
|
+
""
|
|
859
|
+
);
|
|
860
|
+
return `${lines.join("\n")}
|
|
861
|
+
`;
|
|
862
|
+
}
|
|
863
|
+
lines.push("## Proposed split", "");
|
|
864
|
+
lines.push(
|
|
865
|
+
`The ${proposals.reduce((total, proposal) => total + proposal.stories.length, 0)} pending stories, in priority order, cut into ${proposals.length} sub-issues of at most ${STORIES_PER_SUBISSUE}. Each depends on the one before it, which is the only`,
|
|
866
|
+
"dependency shape that can be derived from the plan alone \u2014 read it as a starting",
|
|
867
|
+
"point, not as an answer.",
|
|
868
|
+
""
|
|
869
|
+
);
|
|
870
|
+
for (const proposal of proposals) {
|
|
871
|
+
lines.push(`### ${proposal.title}`, "");
|
|
872
|
+
if (proposal.dependsOn.length > 0) {
|
|
873
|
+
lines.push(`Depends on: ${proposal.dependsOn.join(", ")}`, "");
|
|
874
|
+
}
|
|
875
|
+
for (const story of proposal.stories) {
|
|
876
|
+
lines.push(`- \`${story.id}\` ${story.title}`);
|
|
877
|
+
}
|
|
878
|
+
lines.push("");
|
|
879
|
+
}
|
|
880
|
+
lines.push("## What to do next", "");
|
|
881
|
+
lines.push(
|
|
882
|
+
"- Adjust the cut above until it matches how you would actually split the work;",
|
|
883
|
+
'- create the sub-issues (`issue-flow generate "\u2026"`) and run them as a queue;',
|
|
884
|
+
"- or decide the issue is fine as it is and re-run it \u2014 nothing here blocks that,",
|
|
885
|
+
" beyond the report you are reading.",
|
|
886
|
+
""
|
|
887
|
+
);
|
|
888
|
+
return `${lines.join("\n")}
|
|
889
|
+
`;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/core/headless.ts
|
|
893
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
894
|
+
import chalk from "chalk";
|
|
895
|
+
var DEFAULT_HEADLESS_TIMEOUT_MS = 9e5;
|
|
896
|
+
async function escalatedTimeout(timeoutMs, history) {
|
|
897
|
+
if (timeoutMs === 0 || history === void 0) return timeoutMs;
|
|
898
|
+
const journal = (await Promise.all(
|
|
899
|
+
history.journalFiles.map(async (file) => {
|
|
900
|
+
try {
|
|
901
|
+
return await readFile2(file, "utf-8");
|
|
902
|
+
} catch {
|
|
903
|
+
return "";
|
|
904
|
+
}
|
|
905
|
+
})
|
|
906
|
+
)).join("");
|
|
907
|
+
const timeouts = timeoutsByPhase(journal).get(history.phase) ?? 0;
|
|
908
|
+
if (timeouts < DECOMPOSITION_THRESHOLDS.timeoutsPerPhase) return timeoutMs;
|
|
909
|
+
return Math.min(timeoutMs * 2, Number.MAX_SAFE_INTEGER);
|
|
910
|
+
}
|
|
911
|
+
function printAgentEvent(event, onOutput) {
|
|
912
|
+
const icons = getIcons();
|
|
913
|
+
const colored = useColor();
|
|
914
|
+
const emit = onOutput ?? ((msg) => process.stdout.write(`${msg}
|
|
915
|
+
`));
|
|
916
|
+
if (event.kind === "text") {
|
|
917
|
+
const lines = event.text.split("\n");
|
|
918
|
+
for (const textLine of lines) {
|
|
919
|
+
if (!textLine.trim()) continue;
|
|
920
|
+
const prefix2 = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;
|
|
921
|
+
const text = colored ? chalk.dim(textLine) : textLine;
|
|
922
|
+
emit(`${prefix2}${text}`);
|
|
923
|
+
}
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
getSessionPublisher().publish({
|
|
927
|
+
type: "activity",
|
|
928
|
+
at: isoNow(),
|
|
929
|
+
tool: event.name,
|
|
930
|
+
detail: event.detail
|
|
931
|
+
});
|
|
932
|
+
const toolName = event.name.padEnd(12);
|
|
933
|
+
const prefix = colored ? chalk.dim(` ${icons.connector} `) : ` ${icons.connector} `;
|
|
934
|
+
const toolIcon = colored ? chalk.cyan(icons.tool) : icons.tool;
|
|
935
|
+
const toolLabel = colored ? chalk.cyan(toolName) : toolName;
|
|
936
|
+
const contextText = event.detail ? colored ? chalk.dim(event.detail) : event.detail : "";
|
|
937
|
+
emit(`${prefix}${toolIcon} ${toolLabel} ${contextText}`);
|
|
938
|
+
}
|
|
939
|
+
async function invokeHeadlessAgent(invocation) {
|
|
940
|
+
const config = getActiveResilienceConfig();
|
|
941
|
+
if (config.providers?.failover !== true) {
|
|
942
|
+
return { run: (await invokeSelectedAgent(invocation)).run, retryExhausted: false };
|
|
943
|
+
}
|
|
944
|
+
let usage = null;
|
|
945
|
+
const outcome = await withRetry(() => invokeSelectedAgent(invocation), {
|
|
946
|
+
policy: (failure) => resolvePolicy(failure.kind, config),
|
|
947
|
+
signal: getShutdownSignal(),
|
|
948
|
+
evaluate: (selected) => selected.failure,
|
|
949
|
+
onAttempt: ({ value, failure, willRetry, delayMs, attempt }) => {
|
|
950
|
+
if (value.run.usage !== null) usage = sumUsage(usage, value.run.usage);
|
|
951
|
+
if (failure === null || !willRetry) return;
|
|
952
|
+
getSessionPublisher().publish({
|
|
953
|
+
type: "retry",
|
|
954
|
+
at: isoNow(),
|
|
955
|
+
attempt,
|
|
956
|
+
delaySeconds: delayMs / 1e3,
|
|
957
|
+
reason: failure.message,
|
|
958
|
+
kind: failure.kind
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
return {
|
|
963
|
+
run: usage === null ? outcome.value.run : { ...outcome.value.run, usage },
|
|
964
|
+
retryExhausted: outcome.exhausted
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
async function runHeadless(options) {
|
|
968
|
+
const {
|
|
969
|
+
prompt,
|
|
970
|
+
maxTurns = 10,
|
|
971
|
+
timeout: configuredTimeout = DEFAULT_HEADLESS_TIMEOUT_MS,
|
|
972
|
+
outputFormat = "json",
|
|
973
|
+
allowedTools,
|
|
974
|
+
addDirs,
|
|
975
|
+
statusMessage,
|
|
976
|
+
onOutput,
|
|
977
|
+
phase = "analyze",
|
|
978
|
+
permission = "workspace",
|
|
979
|
+
forceProvider,
|
|
980
|
+
purpose
|
|
981
|
+
} = options;
|
|
982
|
+
const timeout = await escalatedTimeout(configuredTimeout, options.timeoutHistory);
|
|
983
|
+
const settings = await resolveAgentFor(phase);
|
|
984
|
+
const verbose = isVerbose();
|
|
985
|
+
const effectiveOnOutput = onOutput ?? getOutputCallback();
|
|
986
|
+
if (verbose) {
|
|
987
|
+
const icons = getIcons();
|
|
988
|
+
const colored = useColor();
|
|
989
|
+
const emit = effectiveOnOutput ?? ((msg) => process.stdout.write(`${msg}
|
|
990
|
+
`));
|
|
991
|
+
if (statusMessage) {
|
|
992
|
+
const msg = colored ? chalk.blue(`${icons.start} ${statusMessage}`) : `${icons.start} ${statusMessage}`;
|
|
993
|
+
emit(msg);
|
|
994
|
+
}
|
|
995
|
+
const connectorLine = colored ? chalk.dim(` ${icons.connector}`) : ` ${icons.connector}`;
|
|
996
|
+
emit(connectorLine);
|
|
997
|
+
const startTime = Date.now();
|
|
998
|
+
const { run, retryExhausted } = await invokeHeadlessAgent({
|
|
999
|
+
prompt,
|
|
1000
|
+
phase,
|
|
1001
|
+
addDirs,
|
|
1002
|
+
timeout,
|
|
1003
|
+
permission,
|
|
1004
|
+
maxTurns,
|
|
1005
|
+
allowedTools,
|
|
1006
|
+
...forceProvider === void 0 ? {} : { forceProvider },
|
|
1007
|
+
...purpose === void 0 ? {} : { purpose },
|
|
1008
|
+
onEvent: (event) => printAgentEvent(event, effectiveOnOutput)
|
|
1009
|
+
});
|
|
1010
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1e3);
|
|
1011
|
+
const durationStr = formatDuration(elapsedSec);
|
|
1012
|
+
const doneLine = colored ? chalk.dim(` ${icons.connector} ${chalk.italic(`Done in ${durationStr}`)}`) : ` ${icons.connector} Done in ${durationStr}`;
|
|
1013
|
+
emit(doneLine);
|
|
1014
|
+
emit(connectorLine);
|
|
1015
|
+
return mapHeadlessResult(run, outputFormat, retryExhausted);
|
|
1016
|
+
}
|
|
1017
|
+
const spinner = statusMessage ? createSpinner(statusMessage).start() : null;
|
|
1018
|
+
let timer = null;
|
|
1019
|
+
if (spinner) {
|
|
1020
|
+
timer = new ElapsedTimer((elapsed) => {
|
|
1021
|
+
spinner.suffixText = useColor() ? chalk.dim(`(${elapsed})`) : `(${elapsed})`;
|
|
1022
|
+
}).start();
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
const { run, retryExhausted } = await invokeHeadlessAgent({
|
|
1026
|
+
prompt,
|
|
1027
|
+
phase,
|
|
1028
|
+
addDirs,
|
|
1029
|
+
timeout,
|
|
1030
|
+
permission,
|
|
1031
|
+
maxTurns,
|
|
1032
|
+
allowedTools,
|
|
1033
|
+
inactivityTimeoutMs: getInactivityTimeout(),
|
|
1034
|
+
...forceProvider === void 0 ? {} : { forceProvider },
|
|
1035
|
+
...purpose === void 0 ? {} : { purpose },
|
|
1036
|
+
onEvent: (event) => {
|
|
1037
|
+
if (event.kind === "text") return;
|
|
1038
|
+
getSessionPublisher().publish({
|
|
1039
|
+
type: "activity",
|
|
1040
|
+
at: isoNow(),
|
|
1041
|
+
tool: event.name,
|
|
1042
|
+
detail: event.detail
|
|
1043
|
+
});
|
|
1044
|
+
getActivityCallback()?.({ tool: event.name, detail: event.detail });
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
const elapsed = timer?.stop() ?? 0;
|
|
1048
|
+
const dur = useColor() ? chalk.dim(` (${formatDuration(elapsed)})`) : ` (${formatDuration(elapsed)})`;
|
|
1049
|
+
if (!run.success) {
|
|
1050
|
+
spinner?.fail(`${statusMessage}${dur}`);
|
|
1051
|
+
return mapHeadlessResult(run, outputFormat, retryExhausted);
|
|
1052
|
+
}
|
|
1053
|
+
spinner?.succeed(`${statusMessage}${dur}`);
|
|
1054
|
+
return mapHeadlessResult(run, outputFormat, retryExhausted);
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1057
|
+
const catchElapsed = timer?.stop() ?? 0;
|
|
1058
|
+
const catchDur = useColor() ? chalk.dim(` (${formatDuration(catchElapsed)})`) : ` (${formatDuration(catchElapsed)})`;
|
|
1059
|
+
spinner?.fail(`${statusMessage}${catchDur}`);
|
|
1060
|
+
return {
|
|
1061
|
+
success: false,
|
|
1062
|
+
result: "",
|
|
1063
|
+
cost: null,
|
|
1064
|
+
error: message,
|
|
1065
|
+
agent: { provider: settings.provider, model: settings.model }
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
function mapHeadlessResult(run, outputFormat, retryExhausted = false) {
|
|
1070
|
+
if (outputFormat === "text") {
|
|
1071
|
+
return {
|
|
1072
|
+
success: run.success,
|
|
1073
|
+
result: run.success ? run.rawOutput || run.result : "",
|
|
1074
|
+
cost: null,
|
|
1075
|
+
error: run.error,
|
|
1076
|
+
agent: run.agent,
|
|
1077
|
+
...retryExhausted ? { retryExhausted: true } : {}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
if (!run.success) {
|
|
1081
|
+
return {
|
|
1082
|
+
success: false,
|
|
1083
|
+
result: "",
|
|
1084
|
+
cost: null,
|
|
1085
|
+
error: run.error,
|
|
1086
|
+
agent: run.agent,
|
|
1087
|
+
...retryExhausted ? { retryExhausted: true } : {}
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return {
|
|
1091
|
+
success: true,
|
|
1092
|
+
result: run.result,
|
|
1093
|
+
cost: run.usage,
|
|
1094
|
+
error: null,
|
|
1095
|
+
agent: run.agent,
|
|
1096
|
+
...retryExhausted ? { retryExhausted: true } : {}
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
export {
|
|
1101
|
+
evaluateCeilings,
|
|
1102
|
+
spendFromCost,
|
|
1103
|
+
resetAgentInvocationState,
|
|
1104
|
+
invokeSelectedAgent,
|
|
1105
|
+
assessDecomposition,
|
|
1106
|
+
proposeSubIssues,
|
|
1107
|
+
buildDecompositionReport,
|
|
1108
|
+
DEFAULT_HEADLESS_TIMEOUT_MS,
|
|
1109
|
+
runHeadless
|
|
1110
|
+
};
|
|
1111
|
+
//# sourceMappingURL=chunk-ARFVHC2F.js.map
|