chainlesschain 0.166.56 → 0.166.58
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/package.json +2 -1
- package/src/assets/web-panel/assets/{AIOps-CBFnwYRX.js → AIOps-DoyI29Aw.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Sl4eXWpR.js → ActionButton-eZKcEoxc.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DB0lD9Kb.js → Analytics-B2C2e9A7.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-j77EmkRK.js → AppLayout-DkZJiu4X.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CU3izCW9.js → Artifacts-8PD3BXVa.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C44fSibb.js → Audit-Cv6sRlFu.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-pM_NnJW3.js → BackgroundAgents-D-V84Qpm.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-AOHXqhxN.js → Backup-DYJQcxj1.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DaYc8oA_.js → BaseInput-KL2IeywH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CmlCAXxW.js → Chat-Bt9wy_Yn.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CkCSkJDJ.js → ChatBubbleRenderer-ChB6fH-W.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DISI7iaA.js → Checkbox-Dm-nfdnd.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BYpeKUpf.js → Codegen-CYi3yDzv.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Dk6VwWGq.js → Col-C7sQywfG.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BDyTedMu.js → Community-DjxuqAM4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CTNlGM9X.js → Compact-CD_4lbxr.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-C_tw3IdT.js → Compliance-BtDpNJrS.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-cduHWEmo.js → Cowork-l19ANH6Q.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Dqotjv-s.js → Cron-Br-rBoEK.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-ByW7RsJQ.js → Crosschain-60UVvKET.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BI-lafPj.js → DID-B9dqar51.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-8m50TRZL.js → Dashboard-B39hhdHb.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CN3hmRTc.js → Dropdown-Bm5rCfvF.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-t0lzxsDT.js → EmailListRenderer-BI9HANDl.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-DabpA3cQ.js → EvolutionSettings-Cfqn_yCq.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BYN4UshY.js → FamilyGuardDashboard-9Y4_zyhT.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-n44OtIXp.js → Federation-vnbbmMdF.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-B-WXDu_I.js → FormItemContext-BEKNtNF9.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Bm7v-mTf.js → GenericCardRenderer-BmWr16IL.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BMIS6sbo.js → Git-CUUwAXdn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B1EhHobE.js → Governance-Bw8XZmH9.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Dy_HJWHo.js → Inference-CNrml6dn.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BVdfUbZy.js → KnowledgeGraph-BhxOwpW_.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Dph_oLQk.js → Logs-BV8WViUG.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CO1qbiOY.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Bw4HWORj.js → Marketplace-ztcu4mg4.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Be6DPfA1.js → McpTools-D-wzRO7z.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-DlvLafC9.js → Memory-ChdjWC1R.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Upc_Jwlt.js → MobileBridge-B1QHzIjE.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-I17jrUbY.js → MobileProjects-DmaRtaMw.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DyBzY0GH.js → Mtc-DRQMr4WP.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-2-7xAwEo.js → MtcAudit-Cwo0on-u.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DtUZpFmZ.js → Multisig-QplgySFt.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-iInH7jBr.js → NLProgramming-BWkMyuAy.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BO3Z3vaS.js → Notes-BI_pSEM0.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-ClX_0jFE.js → NotificationSettings-DiuFo6Xe.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Czw742w4.js → OrderTableRenderer-DZzQhujZ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BATEx24i.js → Organization-DqEdqWB0.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B_PXMNia.js → Overflow-CO7_2YLN.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BSiE8PqT.js → P2P-BN5RiNst.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BbklUgZY.js → PdhVaultBrowser-CapfG4bl.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-B7bpflT_.js → Permissions-B24c5fWW.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BWZS4BLy.js → PersonalDataHub-D7Mb3_vh.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DP2YSs6H.js → Pipeline-BsfxlbOQ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DB9ptoH0.js → Privacy-Bq7c7IYy.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BAfePyYk.js → ProjectInit-Clse2DPl.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BEXn4Cm3.js → ProjectSettings-CHAEdjri.js} +2 -2
- package/src/assets/web-panel/assets/Projects-C1UHvBEK.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CRX9TdRO.js → Providers-DQyH3PXU.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DUWcSY5L.js → QrScannerModal-t2Z-N49o.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Cgo3Z_hs.js → QuickAsk-BacR7PlI.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Dcinx0IW.js → Recommend-D-NAy0CQ.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BMZzTnAN.js → RemoteSession-CjI3GeVQ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BdZzpOp9.js → Reputation-HoQ3i2oW.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DqM8GVcx.js → Row-CY2jczWa.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Soj_QBK3.js → RssFeed-Bd3o20IY.js} +2 -2
- package/src/assets/web-panel/assets/{Search-Cn2es0-v.js → Search-C88KjgTv.js} +1 -1
- package/src/assets/web-panel/assets/{Security-t7C4hz3n.js → Security-yfmfvujK.js} +4 -4
- package/src/assets/web-panel/assets/{Services-ChF-e_Uf.js → Services-DLPBWoxT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BiPIyK-2.js → Skeleton-DzDjvVQi.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-HgkKJenJ.js → Skills-DqfrJIyP.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BSfluv_k.js → Sla-BXhsGzkm.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Sm0Gz9tM.js → SpeechSettings-jxSl2JDo.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DlhBMnfU.js → SyncSettings-BX2xKtI3.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C7OUvsnp.js → Tasks-P9MmRYt4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CrMtw87y.js → Templates-CTpj9f62.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DSNSB6j5.js → Tenant-nnQuhdPg.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Cmh78FQg.js → Terminal-cJ0vbuTe.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BE8b8lHR.js → TimelineRenderer-DN08D0SD.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BrtgdIvJ.js → Tokens-C8BWWBUD.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-HCmaoKDp.js → Trigger-DhD-LUoe.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-GASQ4F-2.js → Trust-D0nFyc_Z.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B8kSzWGr.js → UkeySign-B59G57LP.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Nn-clhv5.js → VideoEditing-BpYACxVC.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DdYMKTYk.js → Wallet-DlZ3rKGD.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-xJXUr_eK.js → WebAuthn-De4dNyig.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CbSa87av.js → WorkflowEditor-C2Tewnhp.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CVrNLGUV.js → chat-CDBn4JLE.js} +1 -1
- package/src/assets/web-panel/assets/{colors-e2veLfIA.js → colors-BNawl0d_.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CNd7nhUq.js → compact-item-n65WDhze.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DEudTVqv.js → createContext-DAKgOdIy.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-sBSSW127.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BlSAFZa3.js → hasIn-DhxFG46W.js} +1 -1
- package/src/assets/web-panel/assets/{index-CU4Y4VVN.js → index--76HBS1k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmVFeSEq.js → index-0Y-ncsVy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtrqgNBl.js → index-5oJjovnA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRNCKg4q.js → index-BAogaHa9.js} +1 -1
- package/src/assets/web-panel/assets/{index-0y7tHZdT.js → index-BBFjMjmp.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_D7lcnQ.js → index-BDTrf8WH.js} +1 -1
- package/src/assets/web-panel/assets/index-BIJTDSLY.js +1 -0
- package/src/assets/web-panel/assets/{index-DyFZ7gDd.js → index-BQFAuU1c.js} +1 -1
- package/src/assets/web-panel/assets/{index-CK6Rb66R.js → index-BRyzRluv.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7yJceTZ.js → index-BZI8xwwt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHMrAZiR.js → index-BfXdTvku.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bp4Gcn9u.js → index-BudfMkBy.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_UNX_xq.js → index-By-zGgUp.js} +1 -1
- package/src/assets/web-panel/assets/{index-Jk99aZIr.js → index-ByDs4cW4.js} +3 -3
- package/src/assets/web-panel/assets/{index-BpH6T1gA.js → index-BzONk4rW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqeb4jnC.js → index-CE6OcLof.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYNoXhxl.js → index-CSOgoyqx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BptI9bLu.js → index-CW_OUdP8.js} +1 -1
- package/src/assets/web-panel/assets/{index-MuTb62K6.js → index-CZJrAg98.js} +1 -1
- package/src/assets/web-panel/assets/index-CrBauOkp.js +1 -0
- package/src/assets/web-panel/assets/{index-ClROBPWd.js → index-Cv7LqHwv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BG-VNepa.js → index-D34AXxN9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFv-nsIT.js → index-D6sWUgN6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bwtt-0CQ.js → index-DCujBaFg.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1C1wS5c.js → index-DFkamspD.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6zGNZ5H.js → index-DLFRz0_7.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4h_Bvd2.js → index-DV6iHrUO.js} +1 -1
- package/src/assets/web-panel/assets/{index-7-7rr4Mf.js → index-D_-6qQf0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgVwz2jD.js → index-DeTlqus4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwjWRUTf.js → index-DkW0HGr-.js} +1 -1
- package/src/assets/web-panel/assets/{index-4ZT0vIJt.js → index-DleKcclQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-hzUfIn4u.js → index-Ds290C06.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFzvcZ3Q.js → index-KT94nMha.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWuv47z0.js → index-KZkt4e9N.js} +1 -1
- package/src/assets/web-panel/assets/{index-Lm9pm1zm.js → index-L1QnA0gW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ec6VBiV-.js → index-PlOBwfR1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVORJydf.js → index-gG1WJBpa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMIhGZUR.js → index-hA9VUXGr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJ5gsewX.js → index-pzEYIGNA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C6fpNcA5.js → initDefaultProps-B3bCivrt.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Bjh9Come.js → motion-CF5pGsxz.js} +1 -1
- package/src/assets/web-panel/assets/{move-DFa4ul-_.js → move-CaOFtFLU.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-KoYqA_e5.js → mtc-parser-Z4-50Y1c.js} +1 -1
- package/src/assets/web-panel/assets/{omit-ByTjP-wG.js → omit-AK4WqVTT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CG_rmTxx.js → pickAttrs-Dbye0px4.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-LDePZ8V9.js → placementArrow-DCmr-Dks.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DZFzH4RR.js → responsiveObserve-CJ3Zm_-H.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DfXQyTvD.js → slide-BuSk5-ue.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CuhqsJfO.js → statusUtils-8u_lKSFJ.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-qqVjJw5u.js → styleChecker-sy6_m42F.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BPxnCxcb.js → useFlexGapSupport-Di5EfYGo.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D11NLCjV.js → useFs-BTfX_Ri1.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-HbZVcaCX.js → usePersonalDataHub-kqurO26X.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CZHQlfTu.js → vnode-DRkH4mPV.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CV3rUknJ.js → zoom-ON_-UuD_.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +5 -0
- package/src/commands/config.js +19 -10
- package/src/commands/eval.js +39 -4
- package/src/data/changelog.json +26 -0
- package/src/lib/diagnostic-excerpt.js +51 -0
- package/src/lib/diagnostic-workflow.js +10 -0
- package/src/lib/eval/evidence.js +19 -10
- package/src/lib/eval/outcomes.js +373 -0
- package/src/lib/eval/runner.js +6 -0
- package/src/lib/git-tool-input.js +37 -0
- package/src/lib/llm-config-defaults.js +32 -9
- package/src/lib/read-file-loop-guard.js +54 -0
- package/src/lib/remote-read-loop-guard.js +12 -14
- package/src/lib/session-message-fabric.js +74 -37
- package/src/lib/shell-output.js +30 -0
- package/src/lib/task-progress-tracker.js +88 -0
- package/src/runtime/agent-core.js +95 -22
- package/src/runtime/headless-stream.js +25 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-CNHYqcxm.js +0 -1
- package/src/assets/web-panel/assets/Projects-BzSs0rzn.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DW7he-SA.js +0 -1
- package/src/assets/web-panel/assets/index-IkoHuWRe.js +0 -1
- package/src/assets/web-panel/assets/index-MwqsDbb1.js +0 -1
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evalDigest,
|
|
3
|
+
evalHistoryRecordIssues,
|
|
4
|
+
evalExecutionSucceeded,
|
|
5
|
+
} from "./evidence.js";
|
|
6
|
+
|
|
7
|
+
export const OUTCOME_PLAN_SCHEMA = "chainlesschain.outcome-plan/v1";
|
|
8
|
+
export const OUTCOME_REPORT_SCHEMA = "chainlesschain.outcome-report/v1";
|
|
9
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
10
|
+
const CATEGORIES = new Set([
|
|
11
|
+
"repair",
|
|
12
|
+
"support",
|
|
13
|
+
"compatibility",
|
|
14
|
+
"release",
|
|
15
|
+
"test-infrastructure",
|
|
16
|
+
"construction",
|
|
17
|
+
]);
|
|
18
|
+
const STAGES = ["install", "configure", "authenticate", "tool", "artifact"];
|
|
19
|
+
const FAILURE_CAUSES = new Set([
|
|
20
|
+
"install",
|
|
21
|
+
"configuration-identity",
|
|
22
|
+
"model-protocol",
|
|
23
|
+
"tools-permissions",
|
|
24
|
+
"retrieval-context",
|
|
25
|
+
"recovery",
|
|
26
|
+
"artifact",
|
|
27
|
+
"environment",
|
|
28
|
+
"budget",
|
|
29
|
+
"manual-repair",
|
|
30
|
+
]);
|
|
31
|
+
const nonempty = (value) =>
|
|
32
|
+
typeof value === "string" && value.trim().length > 0;
|
|
33
|
+
const nonnegative = (value) => Number.isFinite(value) && value >= 0;
|
|
34
|
+
const count = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
35
|
+
const rate = (numerator, denominator) =>
|
|
36
|
+
denominator ? numerator / denominator : null;
|
|
37
|
+
function assert(value, message) {
|
|
38
|
+
if (!value) throw new Error(message);
|
|
39
|
+
}
|
|
40
|
+
function canonical(value) {
|
|
41
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
42
|
+
if (!value || typeof value !== "object") return value;
|
|
43
|
+
return Object.fromEntries(
|
|
44
|
+
Object.keys(value)
|
|
45
|
+
.sort()
|
|
46
|
+
.map((key) => [key, canonical(value[key])]),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
export function outcomeDigest(value) {
|
|
50
|
+
return evalDigest(JSON.stringify(canonical(value)));
|
|
51
|
+
}
|
|
52
|
+
function epoch(value) {
|
|
53
|
+
const time = Date.parse(value);
|
|
54
|
+
return typeof value === "string" &&
|
|
55
|
+
Number.isFinite(time) &&
|
|
56
|
+
new Date(time).toISOString() === value
|
|
57
|
+
? time
|
|
58
|
+
: NaN;
|
|
59
|
+
}
|
|
60
|
+
function uniqueIndex(rows, key, label) {
|
|
61
|
+
assert(Array.isArray(rows), `${label} must be an array`);
|
|
62
|
+
const index = new Map();
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
assert(
|
|
65
|
+
row && nonempty(row[key]) && !index.has(row[key]),
|
|
66
|
+
`${label} contains a missing or duplicate ${key}`,
|
|
67
|
+
);
|
|
68
|
+
index.set(row[key], row);
|
|
69
|
+
}
|
|
70
|
+
return index;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function validateOutcomePlan(plan) {
|
|
74
|
+
assert(plan?.schema === OUTCOME_PLAN_SCHEMA, "invalid outcome plan schema");
|
|
75
|
+
assert(
|
|
76
|
+
/^[a-f0-9]{40}$/u.test(plan.commitSha || ""),
|
|
77
|
+
"plan requires an exact commit SHA",
|
|
78
|
+
);
|
|
79
|
+
assert(/^[A-Z]{3}$/u.test(plan.currency || ""), "plan requires one currency");
|
|
80
|
+
assert(
|
|
81
|
+
epoch(plan.window?.start) < epoch(plan.window?.end),
|
|
82
|
+
"invalid observation window",
|
|
83
|
+
);
|
|
84
|
+
const samples = uniqueIndex(plan.samples, "id", "plan samples");
|
|
85
|
+
assert(
|
|
86
|
+
samples.size > 0 && samples.size <= 10000,
|
|
87
|
+
"plan requires 1–10000 samples",
|
|
88
|
+
);
|
|
89
|
+
for (const sample of samples.values()) {
|
|
90
|
+
assert(["task", "first-run"].includes(sample.kind), "invalid sample kind");
|
|
91
|
+
assert(
|
|
92
|
+
nonempty(sample.taskId) && nonempty(sample.stratum),
|
|
93
|
+
"sample requires taskId and stratum",
|
|
94
|
+
);
|
|
95
|
+
assert(
|
|
96
|
+
DIGEST.test(sample.comparisonDigest || ""),
|
|
97
|
+
"sample requires an Eval comparison digest",
|
|
98
|
+
);
|
|
99
|
+
assert(
|
|
100
|
+
nonnegative(sample.costBudget) &&
|
|
101
|
+
Number.isFinite(sample.timeBudgetMs) &&
|
|
102
|
+
sample.timeBudgetMs > 0,
|
|
103
|
+
"sample requires finite cost and time budgets",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return samples;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function summarize(rows) {
|
|
110
|
+
const succeeded = rows.filter((row) => row.succeeded).length;
|
|
111
|
+
const observed = rows.filter((row) => row.observed).length;
|
|
112
|
+
const knownCost = rows.reduce((sum, row) => sum + (row.cost ?? 0), 0);
|
|
113
|
+
const costsComplete = rows.every((row) => row.observed && row.cost !== null);
|
|
114
|
+
return {
|
|
115
|
+
planned: rows.length,
|
|
116
|
+
observed,
|
|
117
|
+
missing: rows.length - observed,
|
|
118
|
+
succeeded,
|
|
119
|
+
successRate: rate(succeeded, rows.length),
|
|
120
|
+
knownCost,
|
|
121
|
+
totalCost: costsComplete ? knownCost : null,
|
|
122
|
+
costPerSuccess: costsComplete && succeeded ? knownCost / succeeded : null,
|
|
123
|
+
retries: rows.every((row) => row.retries !== null)
|
|
124
|
+
? rows.reduce((sum, row) => sum + row.retries, 0)
|
|
125
|
+
: null,
|
|
126
|
+
manualRepairs: rows.every((row) => row.manualRepairs !== null)
|
|
127
|
+
? rows.reduce((sum, row) => sum + row.manualRepairs, 0)
|
|
128
|
+
: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Read-only measurement. Declarations and local Eval files are not attestations. */
|
|
133
|
+
export function buildOutcomeReport(
|
|
134
|
+
{ plan, history, observations = [], maintenance = null },
|
|
135
|
+
{ now = Date.now(), expectedPlanDigest } = {},
|
|
136
|
+
) {
|
|
137
|
+
const samples = validateOutcomePlan(plan);
|
|
138
|
+
assert(Number.isFinite(now), "invalid report time");
|
|
139
|
+
const planDigest = outcomeDigest(plan);
|
|
140
|
+
assert(
|
|
141
|
+
!expectedPlanDigest || expectedPlanDigest === planDigest,
|
|
142
|
+
"frozen plan digest mismatch",
|
|
143
|
+
);
|
|
144
|
+
const observed = uniqueIndex(observations, "sampleId", "observations");
|
|
145
|
+
for (const id of observed.keys())
|
|
146
|
+
assert(samples.has(id), "observation is outside the frozen plan");
|
|
147
|
+
const runs = uniqueIndex(history?.runs ?? [], "runId", "Eval history");
|
|
148
|
+
const issues = (history?.issues ?? []).map((issue) => issue.code);
|
|
149
|
+
if (epoch(plan.window.end) > now)
|
|
150
|
+
issues.push("observation_window_not_closed");
|
|
151
|
+
const usedEvidence = new Set();
|
|
152
|
+
const rows = [];
|
|
153
|
+
for (const sample of samples.values()) {
|
|
154
|
+
const observation = observed.get(sample.id);
|
|
155
|
+
const reasons = [];
|
|
156
|
+
const row = {
|
|
157
|
+
sampleId: sample.id,
|
|
158
|
+
kind: sample.kind,
|
|
159
|
+
taskId: sample.taskId,
|
|
160
|
+
stratum: sample.stratum,
|
|
161
|
+
observed: Boolean(observation),
|
|
162
|
+
succeeded: false,
|
|
163
|
+
reasons,
|
|
164
|
+
cost: null,
|
|
165
|
+
elapsedMs: null,
|
|
166
|
+
retries: null,
|
|
167
|
+
manualRepairs: null,
|
|
168
|
+
failureCause: null,
|
|
169
|
+
firstRunCompletedStages: [],
|
|
170
|
+
};
|
|
171
|
+
rows.push(row);
|
|
172
|
+
if (!observation) {
|
|
173
|
+
reasons.push("missing_observation");
|
|
174
|
+
issues.push(`${sample.id}:missing_observation`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
for (const key of ["cost", "elapsedMs", "retries", "manualRepairs"]) {
|
|
178
|
+
const valid = ["retries", "manualRepairs"].includes(key)
|
|
179
|
+
? count(observation[key])
|
|
180
|
+
: nonnegative(observation[key]);
|
|
181
|
+
if (valid) row[key] = observation[key];
|
|
182
|
+
else reasons.push(`missing_or_invalid_${key}`);
|
|
183
|
+
}
|
|
184
|
+
const observedAt = epoch(observation.observedAt);
|
|
185
|
+
if (!(
|
|
186
|
+
observedAt <= Math.min(now, epoch(plan.window.end)) &&
|
|
187
|
+
observedAt - (row.elapsedMs ?? 0) >= epoch(plan.window.start)
|
|
188
|
+
))
|
|
189
|
+
reasons.push("observation_outside_window");
|
|
190
|
+
const run = runs.get(observation.runId);
|
|
191
|
+
const stoppedBeforeTask =
|
|
192
|
+
sample.kind === "first-run" &&
|
|
193
|
+
observation.runId === null &&
|
|
194
|
+
observation.firstRun?.stages?.tool?.passed === false;
|
|
195
|
+
const evidenceKey = JSON.stringify([observation.runId, sample.taskId]);
|
|
196
|
+
if (observation.runId !== null) {
|
|
197
|
+
assert(
|
|
198
|
+
!usedEvidence.has(evidenceKey),
|
|
199
|
+
"a task execution cannot count as multiple samples",
|
|
200
|
+
);
|
|
201
|
+
usedEvidence.add(evidenceKey);
|
|
202
|
+
}
|
|
203
|
+
if (!run && !stoppedBeforeTask) reasons.push("missing_eval_run");
|
|
204
|
+
if (run) {
|
|
205
|
+
// Failed executions are valid observations, not successful outcomes.
|
|
206
|
+
// All other existing Eval integrity rules remain authoritative.
|
|
207
|
+
const integrity = evalHistoryRecordIssues(
|
|
208
|
+
run,
|
|
209
|
+
now,
|
|
210
|
+
Math.max(1, now - epoch(plan.window.start)),
|
|
211
|
+
).filter((reason) => reason !== "execution_not_successful");
|
|
212
|
+
reasons.push(...integrity);
|
|
213
|
+
if (run.label !== plan.commitSha) reasons.push("commit_mismatch");
|
|
214
|
+
if (outcomeDigest(run.comparison) !== sample.comparisonDigest)
|
|
215
|
+
reasons.push("comparison_mismatch");
|
|
216
|
+
if (!(
|
|
217
|
+
epoch(run.ranAt) >= epoch(plan.window.start) &&
|
|
218
|
+
epoch(run.ranAt) <= epoch(plan.window.end)
|
|
219
|
+
))
|
|
220
|
+
reasons.push("run_outside_window");
|
|
221
|
+
}
|
|
222
|
+
const result = run?.results?.find((item) => item?.id === sample.taskId);
|
|
223
|
+
if (!result && !stoppedBeforeTask) reasons.push("missing_task_result");
|
|
224
|
+
if (result && (!nonnegative(result.ms) || row.elapsedMs < result.ms))
|
|
225
|
+
reasons.push("invalid_elapsed_time");
|
|
226
|
+
if (sample.kind === "first-run") {
|
|
227
|
+
if (typeof observation.firstRun?.cleanEnvironment !== "boolean")
|
|
228
|
+
reasons.push("missing_clean_environment_evidence");
|
|
229
|
+
for (const stage of STAGES) {
|
|
230
|
+
const evidence = observation.firstRun?.stages?.[stage];
|
|
231
|
+
if (
|
|
232
|
+
!evidence ||
|
|
233
|
+
typeof evidence.passed !== "boolean" ||
|
|
234
|
+
!nonempty(evidence.receipt)
|
|
235
|
+
)
|
|
236
|
+
reasons.push(`missing_stage_evidence:${stage}`);
|
|
237
|
+
else if (evidence.passed) row.firstRunCompletedStages.push(stage);
|
|
238
|
+
}
|
|
239
|
+
// A later completed stage without its prerequisites is not a valid funnel.
|
|
240
|
+
if (
|
|
241
|
+
row.firstRunCompletedStages.some(
|
|
242
|
+
(stage, index) => stage !== STAGES[index],
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
reasons.push("inconsistent_first_run_stages");
|
|
246
|
+
}
|
|
247
|
+
for (const reason of reasons) issues.push(`${sample.id}:${reason}`);
|
|
248
|
+
const successfulExecution =
|
|
249
|
+
result?.pass === true &&
|
|
250
|
+
result.artifactCheckPassed === true &&
|
|
251
|
+
evalExecutionSucceeded(result) &&
|
|
252
|
+
result.executionEvidence.observedFallback === false;
|
|
253
|
+
if (!successfulExecution) reasons.push("task_not_successful");
|
|
254
|
+
if (row.elapsedMs > sample.timeBudgetMs || row.cost > sample.costBudget)
|
|
255
|
+
reasons.push("budget_exceeded");
|
|
256
|
+
if (row.manualRepairs > 0) reasons.push("manual_repair");
|
|
257
|
+
if (
|
|
258
|
+
sample.kind === "first-run" &&
|
|
259
|
+
(observation.firstRun?.cleanEnvironment !== true ||
|
|
260
|
+
row.firstRunCompletedStages.length !== STAGES.length)
|
|
261
|
+
)
|
|
262
|
+
reasons.push("first_run_not_completed");
|
|
263
|
+
row.succeeded = reasons.length === 0;
|
|
264
|
+
if (!row.succeeded) {
|
|
265
|
+
if (FAILURE_CAUSES.has(observation.failureCause))
|
|
266
|
+
row.failureCause = observation.failureCause;
|
|
267
|
+
else issues.push(`${sample.id}:missing_failure_cause`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const categories = Object.fromEntries(
|
|
271
|
+
[...CATEGORIES].map((category) => [category, 0]),
|
|
272
|
+
);
|
|
273
|
+
let maintenanceComplete = false;
|
|
274
|
+
if (maintenance) {
|
|
275
|
+
const entries = uniqueIndex(
|
|
276
|
+
maintenance.entries,
|
|
277
|
+
"id",
|
|
278
|
+
"maintenance entries",
|
|
279
|
+
);
|
|
280
|
+
maintenanceComplete =
|
|
281
|
+
maintenance.complete === true &&
|
|
282
|
+
outcomeDigest(maintenance.window) === outcomeDigest(plan.window);
|
|
283
|
+
for (const entry of entries.values()) {
|
|
284
|
+
assert(
|
|
285
|
+
CATEGORIES.has(entry.category) &&
|
|
286
|
+
nonnegative(entry.hours) &&
|
|
287
|
+
nonempty(entry.evidenceRef),
|
|
288
|
+
"invalid maintenance entry",
|
|
289
|
+
);
|
|
290
|
+
categories[entry.category] += entry.hours;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!maintenanceComplete) issues.push("incomplete_maintenance_records");
|
|
294
|
+
const maintenanceHours = [...CATEGORIES]
|
|
295
|
+
.filter((category) => category !== "construction")
|
|
296
|
+
.reduce((sum, category) => sum + categories[category], 0);
|
|
297
|
+
const attempts = rows.filter((row) => row.observed).length;
|
|
298
|
+
const allObserved = rows.every((row) => row.observed);
|
|
299
|
+
const firstRuns = rows.filter((row) => row.kind === "first-run");
|
|
300
|
+
const tasks = rows.filter((row) => row.kind === "task");
|
|
301
|
+
if (!firstRuns.length || !tasks.length)
|
|
302
|
+
issues.push("missing_metric_population");
|
|
303
|
+
return {
|
|
304
|
+
schema: OUTCOME_REPORT_SCHEMA,
|
|
305
|
+
generatedAt: new Date(now).toISOString(),
|
|
306
|
+
scope: "local-outcome-baseline",
|
|
307
|
+
productionAttested: false,
|
|
308
|
+
status: issues.length ? "INSUFFICIENT_EVIDENCE" : "BASELINE_RECORDED",
|
|
309
|
+
// Neither a baseline nor operator declarations constitute an improvement gate.
|
|
310
|
+
improvementVerdict: "NOT_EVALUATED",
|
|
311
|
+
planDigest,
|
|
312
|
+
commitSha: plan.commitSha,
|
|
313
|
+
currency: plan.currency,
|
|
314
|
+
window: plan.window,
|
|
315
|
+
task: summarize(tasks),
|
|
316
|
+
firstRun: summarize(firstRuns),
|
|
317
|
+
maintenance: {
|
|
318
|
+
complete: maintenanceComplete,
|
|
319
|
+
knownHours: maintenanceHours,
|
|
320
|
+
totalHours: maintenanceComplete ? maintenanceHours : null,
|
|
321
|
+
per100Attempts:
|
|
322
|
+
maintenanceComplete && allObserved && attempts
|
|
323
|
+
? (maintenanceHours / attempts) * 100
|
|
324
|
+
: null,
|
|
325
|
+
observedAttempts: attempts,
|
|
326
|
+
constructionHours: maintenanceComplete ? categories.construction : null,
|
|
327
|
+
categories,
|
|
328
|
+
},
|
|
329
|
+
groups: [
|
|
330
|
+
...new Set(
|
|
331
|
+
rows.map((row) => JSON.stringify([row.kind, row.stratum, row.taskId])),
|
|
332
|
+
),
|
|
333
|
+
].map((key) => {
|
|
334
|
+
const [kind, stratum, taskId] = JSON.parse(key);
|
|
335
|
+
return {
|
|
336
|
+
kind,
|
|
337
|
+
stratum,
|
|
338
|
+
taskId,
|
|
339
|
+
...summarize(
|
|
340
|
+
rows.filter(
|
|
341
|
+
(row) =>
|
|
342
|
+
row.kind === kind &&
|
|
343
|
+
row.stratum === stratum &&
|
|
344
|
+
row.taskId === taskId,
|
|
345
|
+
),
|
|
346
|
+
),
|
|
347
|
+
};
|
|
348
|
+
}),
|
|
349
|
+
firstRunFunnel: STAGES.map((stage, index) => ({
|
|
350
|
+
stage,
|
|
351
|
+
planned: firstRuns.length,
|
|
352
|
+
passed: firstRuns.filter((row) =>
|
|
353
|
+
STAGES.slice(0, index + 1).every((required) =>
|
|
354
|
+
row.firstRunCompletedStages.includes(required),
|
|
355
|
+
),
|
|
356
|
+
).length,
|
|
357
|
+
})),
|
|
358
|
+
failureCauses: [
|
|
359
|
+
...new Set(
|
|
360
|
+
rows
|
|
361
|
+
.filter((row) => !row.succeeded)
|
|
362
|
+
.map((row) => row.failureCause || "unknown"),
|
|
363
|
+
),
|
|
364
|
+
].map((cause) => ({
|
|
365
|
+
cause,
|
|
366
|
+
count: rows.filter(
|
|
367
|
+
(row) => !row.succeeded && (row.failureCause || "unknown") === cause,
|
|
368
|
+
).length,
|
|
369
|
+
})),
|
|
370
|
+
issues: [...new Set(issues)],
|
|
371
|
+
rows,
|
|
372
|
+
};
|
|
373
|
+
}
|
package/src/lib/eval/runner.js
CHANGED
|
@@ -109,6 +109,8 @@ export async function runEvalSuite(tasks, opts = {}) {
|
|
|
109
109
|
executionSucceeded: false,
|
|
110
110
|
agentOk: false,
|
|
111
111
|
executionEvidence: null,
|
|
112
|
+
usage: null,
|
|
113
|
+
totalCostUsd: null,
|
|
112
114
|
ms: 0,
|
|
113
115
|
detail: "",
|
|
114
116
|
error: null,
|
|
@@ -153,6 +155,10 @@ export async function runEvalSuite(tasks, opts = {}) {
|
|
|
153
155
|
(agentResult.error == null || agentResult.error === "");
|
|
154
156
|
rec.agentOk = rec.executionSucceeded;
|
|
155
157
|
rec.executionEvidence = agentResult?.executionEvidence || null;
|
|
158
|
+
rec.usage = agentResult?.usage || null;
|
|
159
|
+
rec.totalCostUsd = Number.isFinite(agentResult?.totalCostUsd)
|
|
160
|
+
? agentResult.totalCostUsd
|
|
161
|
+
: null;
|
|
156
162
|
if (agentResult?.evaluationMetrics !== undefined) {
|
|
157
163
|
rec.evaluationMetrics = agentResult.evaluationMetrics;
|
|
158
164
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The git tool accepts argv text, not a shell program. Diagnose shell syntax
|
|
2
|
+
// before spawning git while preserving literal operators in quoted arguments.
|
|
3
|
+
export function gitToolInputError(command) {
|
|
4
|
+
let quote = null;
|
|
5
|
+
for (let i = 0; i < command.length; i++) {
|
|
6
|
+
const char = command[i];
|
|
7
|
+
if (char === "\\" && quote === '"') {
|
|
8
|
+
i++;
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (quote) {
|
|
12
|
+
if (char === quote) quote = null;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (char === "'" || char === '"') {
|
|
16
|
+
quote = char;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (/[|&;<>\r\n`]/.test(char) || (char === "$" && command[i + 1] === "(")) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
code: "CC_GIT_SHELL_SYNTAX",
|
|
23
|
+
error:
|
|
24
|
+
"The git tool accepts Git arguments only; shell operators are not executed.",
|
|
25
|
+
hint: 'Submit one Git command without pipes, redirects or &&. For example: "merge-base --is-ancestor main HEAD" (inspect exitCode), "log -n 5 --oneline", or "tag --list v-npm-* --sort=-v:refname". Do not reroute to a shell or execute a partial command automatically.',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (quote)
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
code: "CC_GIT_UNCLOSED_QUOTE",
|
|
33
|
+
error:
|
|
34
|
+
"Unclosed quote in Git arguments. Close the quoted argument and retry.",
|
|
35
|
+
};
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
@@ -55,6 +55,7 @@ export async function reconcileConfigLlmProvider(cfgLlm = {}) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
import { resolveApiKeyFromHelper } from "./api-key-helper.js";
|
|
58
|
+
import { modelForeignToProvider } from "./runnable-provider.js";
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* Key backfill order: config llm.apiKey (plaintext, legacy) first, then
|
|
@@ -111,12 +112,27 @@ function optionalModel(value) {
|
|
|
111
112
|
return model || null;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Claude settings and ANTHROPIC_MODEL carry a model name but no provider.
|
|
117
|
+
* Do not pair an unmistakably provider-specific inherited model with another
|
|
118
|
+
* configured provider. Explicit CLI/--settings choices may opt out because
|
|
119
|
+
* they provide the user's intent for this invocation.
|
|
120
|
+
*/
|
|
121
|
+
export function inheritedModelCompatible(provider, model) {
|
|
122
|
+
const candidate = optionalModel(model);
|
|
123
|
+
return (
|
|
124
|
+
!candidate || !provider || !modelForeignToProvider(provider, candidate)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
114
128
|
/**
|
|
115
129
|
* Resolve the model precedence used by `cc agent` without allowing a default
|
|
116
130
|
* from one source to silently outrank an explicit choice from another:
|
|
117
131
|
*
|
|
118
|
-
* CLI --model > ANTHROPIC_MODEL
|
|
119
|
-
* > ANTHROPIC_DEFAULT_MODEL (new sessions only)
|
|
132
|
+
* CLI --model > provider-compatible ANTHROPIC_MODEL/settings.model >
|
|
133
|
+
* config/organization model > ANTHROPIC_DEFAULT_MODEL (new sessions only)
|
|
134
|
+
* > runtime provider default. An explicit --settings file or managed setting
|
|
135
|
+
* may authorize a foreign-looking alias for a custom provider endpoint.
|
|
120
136
|
*
|
|
121
137
|
* Environment values are supplied by the caller from the process startup
|
|
122
138
|
* environment, before settings-file `env` entries are merged. That prevents a
|
|
@@ -128,18 +144,25 @@ export function applyAgentModelDefaults(options = {}, cfgLlm = {}, opts = {}) {
|
|
|
128
144
|
const settingsModel = optionalModel(opts.settingsModel);
|
|
129
145
|
const defaultModel = optionalModel(opts.anthropicDefaultModel);
|
|
130
146
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
let inheritedModel = environmentModel || settingsModel;
|
|
148
|
+
const targetProvider = options.provider || cfgLlm.provider;
|
|
149
|
+
if (
|
|
150
|
+
inheritedModel &&
|
|
151
|
+
opts.allowForeignInheritedModel !== true &&
|
|
152
|
+
!inheritedModelCompatible(targetProvider, inheritedModel)
|
|
153
|
+
) {
|
|
154
|
+
if (options.model === inheritedModel) delete options.model;
|
|
155
|
+
inheritedModel = null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!explicitModel && inheritedModel) {
|
|
159
|
+
options.model = inheritedModel;
|
|
137
160
|
}
|
|
138
161
|
|
|
139
162
|
applyConfigLlmDefaults(options, cfgLlm, {
|
|
140
163
|
// Reuse the existing config/provider wiring, but do not let its configured
|
|
141
164
|
// model overwrite one selected by a higher-priority source above.
|
|
142
|
-
explicitModel: explicitModel ||
|
|
165
|
+
explicitModel: explicitModel || inheritedModel,
|
|
143
166
|
});
|
|
144
167
|
|
|
145
168
|
if (!options.model && opts.isNewSession !== false && defaultModel) {
|
|
@@ -30,6 +30,46 @@ export class ReadFileLoopGuard {
|
|
|
30
30
|
this.recoveryOfferedAt = 0;
|
|
31
31
|
this.batch = null;
|
|
32
32
|
this.largeOutputs = new Set();
|
|
33
|
+
this.editRecoveryReads = new Map();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
recordEditRecovery(result) {
|
|
37
|
+
if (result?.success === true && result.path) {
|
|
38
|
+
this.editRecoveryReads.delete(keyFor(result.path, { hashed: true }));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const request = result?.recoveryRead;
|
|
42
|
+
if (
|
|
43
|
+
!request ||
|
|
44
|
+
!["hash_mismatch", "content_mismatch"].includes(result.error)
|
|
45
|
+
)
|
|
46
|
+
return;
|
|
47
|
+
const key = keyFor(request.path, { hashed: true });
|
|
48
|
+
// One refresh per file until an edit succeeds. Repeating the failed edit
|
|
49
|
+
// must not mint an unlimited read allowance.
|
|
50
|
+
if (
|
|
51
|
+
!this.editRecoveryReads.has(key) &&
|
|
52
|
+
this.editRecoveryReads.size < MAX_FILES
|
|
53
|
+
) {
|
|
54
|
+
this.editRecoveryReads.set(key, { ...request, used: false });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get hasEditRecoveryReads() {
|
|
59
|
+
return [...this.editRecoveryReads.values()].some((entry) => !entry.used);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
canRecoverEdit(filePath, args = {}) {
|
|
63
|
+
const entry = this.editRecoveryReads.get(keyFor(filePath, args));
|
|
64
|
+
return !!(
|
|
65
|
+
entry &&
|
|
66
|
+
!entry.used &&
|
|
67
|
+
args.hashed === true &&
|
|
68
|
+
args.raw !== true &&
|
|
69
|
+
args.column == null &&
|
|
70
|
+
args.offset === entry.offset &&
|
|
71
|
+
args.limit === entry.limit
|
|
72
|
+
);
|
|
33
73
|
}
|
|
34
74
|
|
|
35
75
|
startBatch() {
|
|
@@ -51,6 +91,16 @@ export class ReadFileLoopGuard {
|
|
|
51
91
|
}
|
|
52
92
|
let page = readPage(args);
|
|
53
93
|
if (page.error || !page.readSpan) return page;
|
|
94
|
+
if (this.canRecoverEdit(filePath, args)) {
|
|
95
|
+
this.editRecoveryReads.get(key).used = true;
|
|
96
|
+
// The authorized read implementation has already refreshed stat/content.
|
|
97
|
+
// Return the requested anchors even if ordinary coverage is exhausted.
|
|
98
|
+
return {
|
|
99
|
+
...page,
|
|
100
|
+
readRecovery: { action: "edit-anchor-refresh" },
|
|
101
|
+
readProgress: { newContent: false },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
54
104
|
const span = page.readSpan;
|
|
55
105
|
const covered = entry.spans.some(
|
|
56
106
|
([start, end]) => start <= span.start && end >= span.end,
|
|
@@ -160,6 +210,8 @@ export class ReadFileLoopGuard {
|
|
|
160
210
|
)
|
|
161
211
|
return;
|
|
162
212
|
if (tool === "read_file") {
|
|
213
|
+
if (result?.readRecovery?.action === "edit-anchor-refresh")
|
|
214
|
+
this.batch.advanced = true;
|
|
163
215
|
if (result?.readProgress?.newContent === true) this.batch.advanced = true;
|
|
164
216
|
if (result?.readProgress?.newContent === false) this.batch.duplicates++;
|
|
165
217
|
} else if (
|
|
@@ -207,12 +259,14 @@ export class ReadFileLoopGuard {
|
|
|
207
259
|
|
|
208
260
|
get hasRecoveryReads() {
|
|
209
261
|
return (
|
|
262
|
+
this.hasEditRecoveryReads ||
|
|
210
263
|
this.hasUnreadPages ||
|
|
211
264
|
[...this.progress.values()].some((entry) => entry.rereads.size < 3)
|
|
212
265
|
);
|
|
213
266
|
}
|
|
214
267
|
|
|
215
268
|
canContinue(filePath, args = {}) {
|
|
269
|
+
if (this.canRecoverEdit(filePath, args)) return true;
|
|
216
270
|
const entry = this.progress.get(keyFor(filePath, args));
|
|
217
271
|
return (
|
|
218
272
|
entry?.summary?.reachedEnd === false ||
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { diagnosticExcerpt } from "./diagnostic-excerpt.js";
|
|
3
|
+
import { DIAGNOSTIC_WORKFLOW_GUIDANCE } from "./diagnostic-workflow.js";
|
|
2
4
|
|
|
3
5
|
const MAX_TARGETS = 32;
|
|
4
6
|
const RECOVERY_AFTER = 3;
|
|
@@ -196,19 +198,7 @@ function failedResult(result) {
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
function excerpt(value) {
|
|
199
|
-
|
|
200
|
-
if (text.length <= 1600) return text;
|
|
201
|
-
// Retain failure lines from full CI logs as well as the opening context.
|
|
202
|
-
const failures = text
|
|
203
|
-
.split("\n")
|
|
204
|
-
.filter((line) => /FAIL|Error:|AssertionError|error TS|×|✕/.test(line))
|
|
205
|
-
.slice(0, 12)
|
|
206
|
-
.join("\n");
|
|
207
|
-
return (
|
|
208
|
-
text.slice(0, 400) +
|
|
209
|
-
"\n[excerpt]\n" +
|
|
210
|
-
(failures || text.slice(-1100)).slice(0, 1100)
|
|
211
|
-
);
|
|
201
|
+
return diagnosticExcerpt(value);
|
|
212
202
|
}
|
|
213
203
|
|
|
214
204
|
/** Per agentLoop, independent of history compaction and other agents' evidence. */
|
|
@@ -290,13 +280,19 @@ export class RemoteReadLoopGuard {
|
|
|
290
280
|
this.activeKey = target.key;
|
|
291
281
|
const previous = this.targets.get(target.key);
|
|
292
282
|
const digests = previous?.digests || new Set();
|
|
293
|
-
const
|
|
283
|
+
const primaryContent =
|
|
294
284
|
result?.output ??
|
|
295
285
|
result?.stdout ??
|
|
296
286
|
result?.content ??
|
|
297
287
|
result?.matches ??
|
|
298
288
|
result?.body ??
|
|
299
289
|
"";
|
|
290
|
+
const content =
|
|
291
|
+
result?.stdout_diagnostics || result?.stderr_diagnostics
|
|
292
|
+
? [result.stdout_diagnostics, result.stderr_diagnostics, primaryContent]
|
|
293
|
+
.filter(Boolean)
|
|
294
|
+
.join("\n")
|
|
295
|
+
: primaryContent;
|
|
300
296
|
const digest = createHash("sha256")
|
|
301
297
|
.update(typeof content === "string" ? content : JSON.stringify(content))
|
|
302
298
|
.digest("hex");
|
|
@@ -453,6 +449,8 @@ export class RemoteReadLoopGuard {
|
|
|
453
449
|
"An aggregate or incomplete-matrix gate can be a downstream symptom; inspect the dependency jobs' conclusions before assuming that the gate itself is wrong. " +
|
|
454
450
|
"When a job is cancelled or logs are unavailable, record that fact and the command error; do not cycle through the same web page and log downloads. " +
|
|
455
451
|
"Use one focused run/job metadata query to resolve the missing status or cancellation reason, then inspect the relevant workflow and make the justified, authorized fix with validation. " +
|
|
452
|
+
DIAGNOSTIC_WORKFLOW_GUIDANCE +
|
|
453
|
+
" " +
|
|
456
454
|
"If available evidence cannot establish a fix, report the specific missing evidence and what was verified. Do not disable a release or safety gate just to make CI pass. " +
|
|
457
455
|
"For a research/review request, synthesize the findings without unsolicited edits; for monitoring, use status queries instead of repeated logs."
|
|
458
456
|
);
|