chainlesschain 0.166.107 → 0.166.109
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 +18 -9
- package/package.json +1 -1
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DCx6L_HW.js → AIOps-DkrNbDzS.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DYm1CN3Z.js → ActionButton-DJjFXDhv.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D5ItUS_y.js → Analytics-DyUbagRn.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Cb7XeRdH.js → AppLayout-p7kJWSvn.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DDDeqCDQ.js → Artifacts-C3rVh4Ak.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BRt3x13w.js → Audit-Bch_L_YO.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C_oNALMd.js → BackgroundAgents-BNHy8zP6.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-PSU2p6CR.js → Backup-BUaye_Ys.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-G3oL4hcX.js → BaseInput-D49mzAVz.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C2cLI10b.js → Chat-2MDzM_us.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DOX3Y5EJ.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CHLnEgFD.js → Checkbox-5p1T_KEq.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DZm9g_Bc.js → Codegen-TKtiLaoZ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D5Z4zSay.js → Col-BVCMG9mS.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DHw69NSk.js → Community-D09-QczH.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B-vjYNWz.js → Compact-QuRMCnj7.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DaIYVZQY.js → Compliance-CN0PV7fk.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CoA9U-gF.js → Cowork-5N2zhAty.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DXqmxlRC.js → Cron-BxT8UZ0S.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Cdkc3ue1.js → Crosschain-Dp-noQl4.js} +1 -1
- package/src/assets/web-panel/assets/{DID-oKfYyNSR.js → DID-B7MZFkjG.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-n_ycvgWu.js → Dashboard-DulJ3rKV.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D38rZ8WY.js → Dropdown-DV4ov4zF.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BjreeBQ1.js → EmailListRenderer-DB2-ooz6.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-CTxA_SIq.js → EvolutionSettings-B1Rb6p7q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DmECNwV5.js → FamilyGuardDashboard-U8aHayx-.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-B2IaaDmQ.js → Federation-BkQULBHY.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DgcgqUIk.js → FormItemContext-CtviG260.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B1-du6Qm.js → GenericCardRenderer-DnPirB24.js} +1 -1
- package/src/assets/web-panel/assets/{Git-XceM21Il.js → Git-CK57Oro6.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CV97iz3z.js → Governance-DbI-LB8-.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CI7fj305.js → Inference-sEG4T1Jg.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-B5mekWRy.js → KnowledgeGraph-9SLJR3h9.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BlxFA0xY.js → Logs-BIZYcISQ.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-rDE-XyMY.js → MarkdownRenderer-9nfWupWP.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DL1BVCiX.js → Marketplace-CRmLLwAE.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BPtwrO3c.js → McpTools-DkOFkSO1.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-DuSNw4E-.js → Memory-M29Kd9TU.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DTgY9ywI.js → MobileBridge-Tl6JvPw_.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bq-FPDzS.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-h4fCLjXw.js → Mtc-DkUjX1F5.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-fqMDY3-w.js → MtcAudit-BCqr1I4q.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Do3FehBL.js → Multisig-DmoqYGQ5.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CQVY0kQF.js → NLProgramming-DNRQeRke.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-4c3oNvWh.js → Notes-CQSBeiY6.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BKr2JD4k.js → NotificationSettings-gNY9bbH6.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Bq4jgzRt.js → OrderTableRenderer-DW0CNjh8.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Dy3XidyA.js → Organization-CthnbKag.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DFF0qN5K.js → Overflow-_GQ3sbXu.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DAgYYykd.js → P2P-BTr7ea6W.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-D9McHF9I.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-Cev6uSBc.js → Permissions-DMny2Cjs.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-DJzbN06K.js → PersonalDataHub-CyYlMSoI.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CoJWvbQ5.js → Pipeline-FASu2Oc4.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DZxV6etm.js → Privacy-9bC5FnsJ.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BFW87JnM.js → ProjectInit-BOYZwB5G.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-LBrRT27p.js → ProjectSettings-45DViNG-.js} +2 -2
- package/src/assets/web-panel/assets/Projects-C2fZzMZc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CnRbjOYk.js → Providers-BgjvZx9F.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BVFnIS3r.js → QrScannerModal-Brw-3csB.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DrcHx5f1.js → QuickAsk-DWG-A5zB.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CbRgrSJm.js → Recommend-DuQGxjIR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-B2Uz_ElB.js → RemoteSession-T1cdSSRL.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DS0ljTxL.js → Reputation-9FT2dI4G.js} +1 -1
- package/src/assets/web-panel/assets/{Row-ClLobFht.js → Row-yNIbNftX.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DxxcTD0x.js → RssFeed-BIchNoPd.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BbHN0Fik.js → Search-msDJ8UTZ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BAVU_EcY.js → Security-DE46oBYT.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CtqRSVq_.js → Services-Dori7vc3.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-IJ8ZKdSZ.js → Skeleton-D3O3sUFz.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DXxcznnO.js → Skills-BV0ct8SO.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-fRkhnA3i.js → Sla-DflIVOrh.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Bi8sYJ-I.js → SpeechSettings-Cpbv2Zz6.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CjeplUtS.js → SyncSettings-Dgg6-wbe.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D4KB2Hbx.js → Tasks-Cb6e4Klx.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BwVugi8n.js → Templates-GfdAATGz.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CsPuu5xK.js → Tenant-D0gecLXl.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DuaeOPjY.js → Terminal-BFRPJt-0.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D-PMrFxO.js → TimelineRenderer-BobtChxX.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BXZuiQPZ.js → Tokens-CifeZtHV.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DgpTaHL3.js → Trigger-BHBvbBCf.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Bn7lEGRY.js → Trust-6lvqjKYp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-zz-8JAAQ.js → UkeySign-CqzMc-G5.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DCzfQGTc.js → VideoEditing-OIiRG582.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BiRHM_zB.js → Wallet-4MeKq0q_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-QmPo2JwU.js → WebAuthn-eHBRjGQG.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-gL7SsPnY.js → WorkflowEditor-DlzYdEqW.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BysvK4hk.js → chat-CBcjOzkX.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CQq20vqN.js → colors-zgwsYQSa.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CAHlOf1X.js → compact-item-Dcd9H5Bn.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BeF3ESj_.js → createContext-DR1tO-8r.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DYhpRtve.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CiqnRf-U.js → hasIn-CtfbUH_p.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cp3IRTDK.js → index-5XaB_Tjg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKYbru3i.js → index-B3QUK1ks.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCX0daFQ.js → index-BXfGHUz_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgkUkmEJ.js → index-BndPtS9D.js} +1 -1
- package/src/assets/web-panel/assets/{index-zNkw0IY7.js → index-BoxowMYS.js} +1 -1
- package/src/assets/web-panel/assets/{index-i9aJV9qk.js → index-C0kP88Pb.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtWchYfW.js → index-C5XGvJb0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHpHGGmB.js → index-CPLLgPxA.js} +1 -1
- package/src/assets/web-panel/assets/{index-qjaCk1Mj.js → index-CSQCHpDo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CImCEo8S.js → index-CT7zIkqP.js} +1 -1
- package/src/assets/web-panel/assets/{index-Br5tGVbC.js → index-CaGNqkY0.js} +1 -1
- package/src/assets/web-panel/assets/{index-xwtRrgVY.js → index-Cdhy5ckh.js} +1 -1
- package/src/assets/web-panel/assets/{index-CowbNBJs.js → index-CmVIM-m2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTp89vmg.js → index-CmzQzTY5.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7q5VH2n.js → index-Cur8LNNn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeIL3-nY.js → index-CwHgaYVH.js} +1 -1
- package/src/assets/web-panel/assets/{index-jJhemX1R.js → index-D1GxtY-h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTPUVm71.js → index-D7ZOiIKb.js} +1 -1
- package/src/assets/web-panel/assets/{index-QALafVsT.js → index-D8WPohye.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZBuJ6O6g.js → index-DA2-Bhy8.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5941vyo.js → index-DHEiuvhA.js} +1 -1
- package/src/assets/web-panel/assets/index-DPJXFIY7.js +1 -0
- package/src/assets/web-panel/assets/{index-BsnLkZjz.js → index-DSvcz0RO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHGtFwwR.js → index-DTx4FyLU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOiyW31p.js → index-DVpp9YSQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2AOvjgR.js → index-DWG98orp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfJabF_8.js → index-DWuCGhHa.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBmGQHEz.js → index-DYPMJ5Mb.js} +3 -3
- package/src/assets/web-panel/assets/{index-B2xbtl_c.js → index-DeAi4CNt.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bvkbj3x-.js → index-DfzOYmOj.js} +1 -1
- package/src/assets/web-panel/assets/index-DjBhR5M_.js +1 -0
- package/src/assets/web-panel/assets/{index-DXsXhR7p.js → index-DvPOpvvH.js} +1 -1
- package/src/assets/web-panel/assets/{index-yFQWSRoW.js → index-J-ORW1i6.js} +1 -1
- package/src/assets/web-panel/assets/{index-TdVEaJfk.js → index-JSfldpw4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcccefJc.js → index-LN7Hkl1C.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dqkls2_6.js → index-dvF6C39V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcVmt6u_.js → index-hTcre7U9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXT7MKkD.js → index-oWOlF7CM.js} +1 -1
- package/src/assets/web-panel/assets/{index-4a0Lmz_y.js → index-z8lrkcb-.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BQR2HnKb.js → initDefaultProps-CVMtDrWZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-gAz6l-nZ.js → motion-Dye3IU8k.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cy5TN856.js → move-C251Zo34.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BeT7VrGi.js → mtc-parser-BDiOORoQ.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BLN3mlgK.js → omit-DTDA_RHv.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DtwZ8u2d.js → pickAttrs-CqEUTPcT.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-ViMwQSa-.js → placementArrow-BOB8QWH3.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-dEL3u4E7.js → responsiveObserve-CZDXP1d9.js} +1 -1
- package/src/assets/web-panel/assets/{slide-l05us-Gz.js → slide-DPaRw3E9.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Bk-Qn2Og.js → statusUtils-Ba9d7XZ7.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Bn186qeY.js → styleChecker-mZKU3lvW.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-WP-ugR3H.js → useFlexGapSupport-VcauLq0M.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-1SvoeSCL.js → useFs-Cal9KRcx.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CfNU8N3z.js → usePersonalDataHub-DISoRyQB.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Bd2Ya101.js → vnode-B58C6w6B.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Def_MeZv.js → zoom-BPlBAMKB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +29 -3
- package/src/gateways/ws/llm-chat-protocol.js +12 -2
- package/src/gateways/ws/llm-creds.js +50 -22
- package/src/gateways/ws/ws-server.js +47 -0
- package/src/harness/jsonl-session-store.js +128 -5
- package/src/harness/prompt-compressor.js +18 -10
- package/src/harness/structured-handoff.js +151 -13
- package/src/lib/agent-session-export.js +20 -0
- package/src/lib/background-agent-supervisor.js +54 -0
- package/src/lib/coding-task-guidance.js +2 -1
- package/src/lib/context-memory-kernel/live-compaction.js +75 -12
- package/src/lib/read-file-loop-guard.js +124 -8
- package/src/lib/session-host-snapshot.js +3 -0
- package/src/lib/session-message-provenance.js +24 -5
- package/src/lib/session-tool-round.js +39 -0
- package/src/lib/session-transcript-history.js +2 -0
- package/src/lib/session-transcript-page.js +4 -1
- package/src/lib/session-transcript-structure.js +4 -0
- package/src/runtime/__tests__/message-roles.test.js +29 -0
- package/src/runtime/agent-core.js +115 -43
- package/src/runtime/headless-runner.js +191 -34
- package/src/runtime/headless-stream.js +140 -42
- package/src/runtime/message-roles.js +43 -0
- package/src/workers/background-agent-worker.js +146 -47
- package/src/assets/web-panel/assets/ChatBubbleRenderer-KX1OITf8.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-CTpVHJMa.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-DgNHa8b1.js +0 -7
- package/src/assets/web-panel/assets/Projects-O3uz76R1.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D-Iugqxv.js +0 -1
- package/src/assets/web-panel/assets/index-B0Oz2_rQ.js +0 -1
- package/src/assets/web-panel/assets/index-DwoycEQa.js +0 -1
|
@@ -124,6 +124,10 @@ import {
|
|
|
124
124
|
} from "./artifact-protocol.js";
|
|
125
125
|
import { executionBroker } from "../../lib/process-execution-broker/index.js";
|
|
126
126
|
import { collectWorkspacePluginBinSandboxPolicy } from "../../lib/plugin-runtime/bin.js";
|
|
127
|
+
import {
|
|
128
|
+
loadConfig,
|
|
129
|
+
loadUserConfig,
|
|
130
|
+
} from "../../lib/config-manager.js";
|
|
127
131
|
|
|
128
132
|
export const _deps = {
|
|
129
133
|
spawn: (...args) => executionBroker.spawn(...args),
|
|
@@ -195,6 +199,47 @@ function boundedInteger(value, fallback, maximum = Number.MAX_SAFE_INTEGER) {
|
|
|
195
199
|
return Math.min(parsed, maximum);
|
|
196
200
|
}
|
|
197
201
|
|
|
202
|
+
function loadCurrentLlmConfig() {
|
|
203
|
+
const savedLlm =
|
|
204
|
+
loadUserConfig({ failIfUnavailable: true }).llm || {};
|
|
205
|
+
const resolvedLlm = loadConfig({ failIfUnavailable: true }).llm || {};
|
|
206
|
+
const confirmedLlm =
|
|
207
|
+
loadUserConfig({ failIfUnavailable: true }).llm || {};
|
|
208
|
+
const provider =
|
|
209
|
+
typeof savedLlm.provider === "string" && savedLlm.provider.trim()
|
|
210
|
+
? savedLlm.provider.trim()
|
|
211
|
+
: null;
|
|
212
|
+
const routingIdentity = (llm) =>
|
|
213
|
+
JSON.stringify([
|
|
214
|
+
llm.provider ?? null,
|
|
215
|
+
llm.baseUrl ?? null,
|
|
216
|
+
llm.apiKey ?? null,
|
|
217
|
+
llm.model ?? null,
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
// Config writes are atomic, but a save can land between reads. Never pair
|
|
221
|
+
// a key or route from different saved versions in that case.
|
|
222
|
+
if (
|
|
223
|
+
routingIdentity(savedLlm) !== routingIdentity(confirmedLlm) ||
|
|
224
|
+
(provider && provider !== resolvedLlm.provider)
|
|
225
|
+
) {
|
|
226
|
+
throw new Error("LLM configuration changed while being read");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
provider,
|
|
231
|
+
model:
|
|
232
|
+
typeof savedLlm.model === "string" && savedLlm.model.trim()
|
|
233
|
+
? savedLlm.model.trim()
|
|
234
|
+
: null,
|
|
235
|
+
baseUrl:
|
|
236
|
+
typeof savedLlm.baseUrl === "string" && savedLlm.baseUrl.trim()
|
|
237
|
+
? savedLlm.baseUrl.trim()
|
|
238
|
+
: null,
|
|
239
|
+
apiKey: provider ? resolvedLlm.apiKey || null : null,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
198
243
|
function frameByteLength(data) {
|
|
199
244
|
if (typeof data === "string") return Buffer.byteLength(data, "utf8");
|
|
200
245
|
if (Buffer.isBuffer(data)) return data.byteLength;
|
|
@@ -368,6 +413,8 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
368
413
|
|
|
369
414
|
/** Session manager for stateful agent/chat sessions */
|
|
370
415
|
this.sessionManager = options.sessionManager || null;
|
|
416
|
+
/** Read current saved LLM settings for stateless chat requests. */
|
|
417
|
+
this._loadLlmConfig = options.loadLlmConfig || loadCurrentLlmConfig;
|
|
371
418
|
this.projectRoot = options.projectRoot || process.cwd();
|
|
372
419
|
|
|
373
420
|
/** Session handlers: sessionId → WSAgentHandler | WSChatHandler */
|
|
@@ -72,6 +72,12 @@ import {
|
|
|
72
72
|
import { createSessionPersistenceFailure } from "../lib/session-persistence-failure.js";
|
|
73
73
|
import { withSessionHostWriteAuthority } from "../lib/session-host-lease.js";
|
|
74
74
|
import { createSessionTranscriptStructureProjection } from "../lib/session-transcript-structure.js";
|
|
75
|
+
import {
|
|
76
|
+
TOOL_ROUND_EVENT,
|
|
77
|
+
TOOL_ROUND_SCHEMA,
|
|
78
|
+
assertCompleteToolRounds,
|
|
79
|
+
projectToolRoundMessages,
|
|
80
|
+
} from "../lib/session-tool-round.js";
|
|
75
81
|
import {
|
|
76
82
|
computeExecutionLocationTargetFactsDigest,
|
|
77
83
|
createExecutionLocationTargetAttestation,
|
|
@@ -3339,12 +3345,125 @@ export function appendCompactEvent(sessionId, stats) {
|
|
|
3339
3345
|
appendEvent(sessionId, "compact", stats);
|
|
3340
3346
|
}
|
|
3341
3347
|
|
|
3348
|
+
function updateCanonicalReplayValue(
|
|
3349
|
+
hash,
|
|
3350
|
+
value,
|
|
3351
|
+
ancestors = new Set(),
|
|
3352
|
+
depth = 0,
|
|
3353
|
+
) {
|
|
3354
|
+
if (depth > 64)
|
|
3355
|
+
throw new TypeError("Session replay exceeds maximum JSON depth");
|
|
3356
|
+
if (
|
|
3357
|
+
value === null ||
|
|
3358
|
+
typeof value === "string" ||
|
|
3359
|
+
typeof value === "boolean"
|
|
3360
|
+
) {
|
|
3361
|
+
hash.update(JSON.stringify(value), "utf8");
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
if (typeof value === "number") {
|
|
3365
|
+
if (!Number.isFinite(value)) {
|
|
3366
|
+
throw new TypeError("Session replay contains a non-finite number");
|
|
3367
|
+
}
|
|
3368
|
+
hash.update(JSON.stringify(Object.is(value, -0) ? 0 : value), "utf8");
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
if (!value || typeof value !== "object") {
|
|
3372
|
+
throw new TypeError("Session replay contains a non-JSON value");
|
|
3373
|
+
}
|
|
3374
|
+
if (ancestors.has(value))
|
|
3375
|
+
throw new TypeError("Session replay contains a cycle");
|
|
3376
|
+
|
|
3377
|
+
ancestors.add(value);
|
|
3378
|
+
try {
|
|
3379
|
+
if (Array.isArray(value)) {
|
|
3380
|
+
hash.update("[", "utf8");
|
|
3381
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
3382
|
+
if (index > 0) hash.update(",", "utf8");
|
|
3383
|
+
updateCanonicalReplayValue(hash, value[index], ancestors, depth + 1);
|
|
3384
|
+
}
|
|
3385
|
+
hash.update("]", "utf8");
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
const prototype = Object.getPrototypeOf(value);
|
|
3390
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
3391
|
+
throw new TypeError("Session replay objects must be plain data");
|
|
3392
|
+
}
|
|
3393
|
+
hash.update("{", "utf8");
|
|
3394
|
+
const keys = Object.keys(value).sort();
|
|
3395
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
3396
|
+
if (index > 0) hash.update(",", "utf8");
|
|
3397
|
+
const key = keys[index];
|
|
3398
|
+
hash.update(JSON.stringify(key), "utf8");
|
|
3399
|
+
hash.update(":", "utf8");
|
|
3400
|
+
updateCanonicalReplayValue(hash, value[key], ancestors, depth + 1);
|
|
3401
|
+
}
|
|
3402
|
+
hash.update("}", "utf8");
|
|
3403
|
+
} finally {
|
|
3404
|
+
ancestors.delete(value);
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3342
3408
|
function canonicalReplayFingerprint(messages) {
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3409
|
+
// This compares a complete replay, which can exceed scheduler payload limits.
|
|
3410
|
+
// Stream canonical JSON into the digest so memory and scheduler envelope
|
|
3411
|
+
// bounds do not depend on total transcript size. Object key order is ignored;
|
|
3412
|
+
// array order, every field and durable provenance remain significant.
|
|
3413
|
+
const canonicalMessages = projectCanonicalResumeMessages(messages, {
|
|
3414
|
+
strict: true,
|
|
3415
|
+
}).map(encodePersistedMessage);
|
|
3416
|
+
const hash = createHash("sha256");
|
|
3417
|
+
hash.update("chainlesschain.session-replay-fingerprint.v1\0", "utf8");
|
|
3418
|
+
updateCanonicalReplayValue(hash, canonicalMessages);
|
|
3419
|
+
return hash.digest("hex");
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
/** Settle only complete host-owned tool exchanges after the verified prefix.
|
|
3423
|
+
* Both prefix comparisons are required: sampling a newer durable head must
|
|
3424
|
+
* never authorize hiding concurrent input absent from the host's live array. */
|
|
3425
|
+
export function appendToolRoundsIfMessagesMatch(
|
|
3426
|
+
sessionId,
|
|
3427
|
+
liveMessages,
|
|
3428
|
+
expectedMessages,
|
|
3429
|
+
) {
|
|
3430
|
+
const live = projectCanonicalResumeMessages(liveMessages, { strict: true });
|
|
3431
|
+
const expected = projectCanonicalResumeMessages(expectedMessages, {
|
|
3432
|
+
strict: true,
|
|
3433
|
+
});
|
|
3434
|
+
const expectedFingerprint = canonicalReplayFingerprint(expected);
|
|
3435
|
+
const stale = () =>
|
|
3436
|
+
Object.assign(
|
|
3437
|
+
new Error(
|
|
3438
|
+
`Session messages changed for ${sessionId}; refresh before settling tools`,
|
|
3439
|
+
),
|
|
3440
|
+
{ code: "SESSION_REVISION_STALE" },
|
|
3441
|
+
);
|
|
3442
|
+
if (
|
|
3443
|
+
canonicalReplayFingerprint(live.slice(0, expected.length)) !==
|
|
3444
|
+
expectedFingerprint
|
|
3445
|
+
)
|
|
3446
|
+
throw stale();
|
|
3447
|
+
const suffix = live.slice(expected.length);
|
|
3448
|
+
if (suffix.length) assertCompleteToolRounds(suffix);
|
|
3449
|
+
return withVerifiedWsTurnLock(sessionId, (filePath) => {
|
|
3450
|
+
const verification = verifyTranscriptFile(filePath);
|
|
3451
|
+
assertVerifiedTranscriptAnchor(sessionId, verification);
|
|
3452
|
+
const actual = rebuildVerifiedMessagesFromFile(filePath);
|
|
3453
|
+
if (canonicalReplayFingerprint(actual) !== expectedFingerprint)
|
|
3454
|
+
throw stale();
|
|
3455
|
+
if (!suffix.length) return { hash: verification.lastHash };
|
|
3456
|
+
return appendVerifiedWsAuthorityEventLocked(
|
|
3457
|
+
sessionId,
|
|
3458
|
+
filePath,
|
|
3459
|
+
TOOL_ROUND_EVENT,
|
|
3460
|
+
{
|
|
3461
|
+
schema: TOOL_ROUND_SCHEMA,
|
|
3462
|
+
messages: suffix.map(encodePersistedMessage),
|
|
3463
|
+
},
|
|
3464
|
+
verification.lastHash,
|
|
3465
|
+
);
|
|
3466
|
+
});
|
|
3348
3467
|
}
|
|
3349
3468
|
|
|
3350
3469
|
/**
|
|
@@ -5848,6 +5967,10 @@ function rebuildMessagesFromFile(
|
|
|
5848
5967
|
isReplayableMessage(event.data)
|
|
5849
5968
|
) {
|
|
5850
5969
|
suffix.push(decodeMessage(event.data));
|
|
5970
|
+
} else if (event?.type === TOOL_ROUND_EVENT) {
|
|
5971
|
+
suffix.push(
|
|
5972
|
+
...projectToolRoundMessages(event).map(decodeMessage).reverse(),
|
|
5973
|
+
);
|
|
5851
5974
|
} else if (event?.type === WS_TURN_EVENT) {
|
|
5852
5975
|
const turn = projectWsTurnMessages(event);
|
|
5853
5976
|
if (turn) {
|
|
@@ -407,9 +407,9 @@ export class PromptCompressor {
|
|
|
407
407
|
if (
|
|
408
408
|
this.llmQuery &&
|
|
409
409
|
(currentTokens > this.maxTokens || result.length > this.maxMessages) &&
|
|
410
|
-
|
|
410
|
+
messages.length > 4
|
|
411
411
|
) {
|
|
412
|
-
const summarized = await this._summarize(result);
|
|
412
|
+
const summarized = await this._summarize(result, messages);
|
|
413
413
|
result = summarized.messages;
|
|
414
414
|
summaryStats = summarized.stats;
|
|
415
415
|
if (summaryStats.summaryMode !== "none") {
|
|
@@ -565,7 +565,7 @@ export class PromptCompressor {
|
|
|
565
565
|
return result;
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
-
async _summarize(messages) {
|
|
568
|
+
async _summarize(messages, sourceMessages = messages) {
|
|
569
569
|
// Compaction summaries and collapsed tool groups are derived history, not
|
|
570
570
|
// immutable host instructions. Fold them into the next summary so a long
|
|
571
571
|
// session cannot retain one additional durable system message per cycle.
|
|
@@ -573,11 +573,11 @@ export class PromptCompressor {
|
|
|
573
573
|
(m) => m.role === "system" && !isCompactionArtifact(m),
|
|
574
574
|
);
|
|
575
575
|
const last = [...messages].reverse().find((m) => m.role === "user");
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
576
|
+
if (
|
|
577
|
+
sourceMessages.filter(
|
|
578
|
+
(m) => m.role !== "system" || isCompactionArtifact(m),
|
|
579
|
+
).length < 3
|
|
580
|
+
) {
|
|
581
581
|
return {
|
|
582
582
|
messages,
|
|
583
583
|
stats: {
|
|
@@ -590,7 +590,15 @@ export class PromptCompressor {
|
|
|
590
590
|
};
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
-
|
|
593
|
+
// The pinned request remains in the output, but the summarizer also needs
|
|
594
|
+
// it to retain the objective/constraints when there is only one user turn.
|
|
595
|
+
// Pair tool observations against the original history: deduplication and
|
|
596
|
+
// collapse may already have removed empty assistant tool-call messages.
|
|
597
|
+
// The same bounded summary input budget applies to this source history.
|
|
598
|
+
const summarySources = sourceMessages.filter(
|
|
599
|
+
(m) => m.role !== "system" || isCompactionArtifact(m),
|
|
600
|
+
);
|
|
601
|
+
const prompt = summaryPrompt(summarySources, this.summaryInputMaxChars);
|
|
594
602
|
let summary;
|
|
595
603
|
let summaryMode = "llm-structured";
|
|
596
604
|
let degraded = false;
|
|
@@ -713,7 +721,7 @@ export class PromptCompressor {
|
|
|
713
721
|
}
|
|
714
722
|
}
|
|
715
723
|
if (degraded) {
|
|
716
|
-
summary = buildExtractiveHandoff(
|
|
724
|
+
summary = buildExtractiveHandoff(summarySources, {
|
|
717
725
|
maxFallbackSourceChars: Math.min(
|
|
718
726
|
this.summaryInputMaxChars,
|
|
719
727
|
SUMMARY_INPUT_DEFAULT_MAX_CHARS,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* parent-to-child agent context. The field order is part of the wire format.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
6
7
|
import { projectCanonicalResumeMessages } from "../lib/session-message-provenance.js";
|
|
7
8
|
|
|
8
9
|
export const STRUCTURED_HANDOFF_FIELDS = Object.freeze([
|
|
@@ -154,12 +155,33 @@ function selectBoundedSources(messages, maxChars) {
|
|
|
154
155
|
// being quoted into a new handoff. Only the runtime WeakMap capability is
|
|
155
156
|
// admitted; ordinary user/assistant/tool conversation remains eligible.
|
|
156
157
|
const canonicalSources = projectCanonicalResumeMessages(messages);
|
|
158
|
+
const calls = new Map();
|
|
157
159
|
const entries = canonicalSources
|
|
158
|
-
.map((message, index) =>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
.map((message, index) => {
|
|
161
|
+
for (const call of Array.isArray(message.tool_calls)
|
|
162
|
+
? message.tool_calls
|
|
163
|
+
: []) {
|
|
164
|
+
if (message.role === "assistant" && call?.id && call.function)
|
|
165
|
+
calls.set(call.id, call.function);
|
|
166
|
+
}
|
|
167
|
+
const facts =
|
|
168
|
+
message.role === "tool"
|
|
169
|
+
? toolFacts(message, calls.get(message.tool_call_id))
|
|
170
|
+
: message.role === "assistant" || message.role === "system"
|
|
171
|
+
? summaryFacts(message)
|
|
172
|
+
: null;
|
|
173
|
+
return {
|
|
174
|
+
index,
|
|
175
|
+
role: typeof message?.role === "string" ? message.role : "unknown",
|
|
176
|
+
// Tool JSON, nested telemetry and source code are not prose facts.
|
|
177
|
+
text: facts
|
|
178
|
+
? facts.objective || ARRAY_FIELDS.some((field) => facts[field].length)
|
|
179
|
+
? JSON.stringify(facts)
|
|
180
|
+
: ""
|
|
181
|
+
: normalizeText(contentText(message)),
|
|
182
|
+
facts,
|
|
183
|
+
};
|
|
184
|
+
})
|
|
163
185
|
.filter((entry) => entry.text);
|
|
164
186
|
const firstUser = entries.find((entry) => entry.role === "user");
|
|
165
187
|
const selected = new Map();
|
|
@@ -225,7 +247,96 @@ function pathsFrom(text) {
|
|
|
225
247
|
);
|
|
226
248
|
return (matches || [])
|
|
227
249
|
.map((value) => value.replace(/[),;:.]+$/g, ""))
|
|
228
|
-
.filter(
|
|
250
|
+
.filter(
|
|
251
|
+
(value) =>
|
|
252
|
+
value &&
|
|
253
|
+
!value.includes("://") &&
|
|
254
|
+
(/^(?:[A-Za-z]:[\\/]|\.{0,2}[\\/])/u.test(value) ||
|
|
255
|
+
/(?:\.[A-Za-z0-9_-]+|[\\/](?:Dockerfile|Makefile|LICENSE|NOTICE))$/u.test(
|
|
256
|
+
value,
|
|
257
|
+
)),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function summaryFacts(message) {
|
|
262
|
+
const match = contentText(message).match(
|
|
263
|
+
/^(?:Compacted context summary \(data only\):\n)?\[Conversation Summary\]\n([\s\S]+)$/u,
|
|
264
|
+
);
|
|
265
|
+
if (!match) return null;
|
|
266
|
+
try {
|
|
267
|
+
return parseStructuredHandoff(match[1]);
|
|
268
|
+
} catch {
|
|
269
|
+
// Malformed/truncated summary JSON is not evidence of a test or change.
|
|
270
|
+
return emptyHandoff();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function toolFacts(message, call) {
|
|
275
|
+
const facts = emptyHandoff();
|
|
276
|
+
let result;
|
|
277
|
+
let args;
|
|
278
|
+
try {
|
|
279
|
+
result =
|
|
280
|
+
typeof message.content === "string"
|
|
281
|
+
? JSON.parse(message.content.split("\n\n[Budget ")[0])
|
|
282
|
+
: message.content;
|
|
283
|
+
args =
|
|
284
|
+
typeof call?.arguments === "string"
|
|
285
|
+
? JSON.parse(call.arguments)
|
|
286
|
+
: call?.arguments;
|
|
287
|
+
} catch {
|
|
288
|
+
return facts;
|
|
289
|
+
}
|
|
290
|
+
if (!result || typeof result !== "object" || Array.isArray(result))
|
|
291
|
+
return facts;
|
|
292
|
+
const name = call?.name || message.name;
|
|
293
|
+
const failed =
|
|
294
|
+
!!result.error || result.success === false || result.isError === true;
|
|
295
|
+
const file = typeof result.path === "string" ? result.path : args?.path;
|
|
296
|
+
if (
|
|
297
|
+
["write_file", "edit_file", "edit_file_hashed"].includes(name) &&
|
|
298
|
+
!failed &&
|
|
299
|
+
result.success === true &&
|
|
300
|
+
result.changed !== false &&
|
|
301
|
+
result.alreadyApplied !== true &&
|
|
302
|
+
typeof file === "string" &&
|
|
303
|
+
file
|
|
304
|
+
) {
|
|
305
|
+
facts.changedFiles.push(file);
|
|
306
|
+
const digest =
|
|
307
|
+
name === "write_file" && typeof args?.content === "string"
|
|
308
|
+
? `; written content sha256:${createHash("sha256").update(args.content).digest("hex")}`
|
|
309
|
+
: "";
|
|
310
|
+
facts.checkpoints.push(
|
|
311
|
+
`Observed ${name} success: ${file}${digest}. File contents are omitted from this summary.`,
|
|
312
|
+
);
|
|
313
|
+
facts.nextSteps.push(
|
|
314
|
+
`Read the needed current source ranges in ${file} before replacing existing code; this summary is not a reconstruction of the file.`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (
|
|
318
|
+
["run_shell", "run_code"].includes(name) &&
|
|
319
|
+
Number.isInteger(result.exitCode)
|
|
320
|
+
) {
|
|
321
|
+
const command =
|
|
322
|
+
name === "run_shell" && typeof args?.command === "string"
|
|
323
|
+
? truncateText(args.command, 300)
|
|
324
|
+
: name;
|
|
325
|
+
facts.checkpoints.push(
|
|
326
|
+
`Observed ${name}: ${command}; exitCode=${result.exitCode}.`,
|
|
327
|
+
);
|
|
328
|
+
if (
|
|
329
|
+
name === "run_shell" &&
|
|
330
|
+
/\b(?:vitest|jest|pytest|playwright|npm\s+(?:run\s+)?test|node\s+--test)\b/u.test(
|
|
331
|
+
command,
|
|
332
|
+
)
|
|
333
|
+
) {
|
|
334
|
+
facts.tests.push(
|
|
335
|
+
`Observed command ${command}; exitCode=${result.exitCode}.`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return facts;
|
|
229
340
|
}
|
|
230
341
|
|
|
231
342
|
function emptyHandoff(objective = "") {
|
|
@@ -313,12 +424,40 @@ export function buildExtractiveHandoff(messages, options = {}) {
|
|
|
313
424
|
const sources = selectBoundedSources(messages, maxFallbackSourceChars);
|
|
314
425
|
const firstUser = sources.find((entry) => entry.role === "user");
|
|
315
426
|
const candidates = emptyHandoff(
|
|
316
|
-
truncateText(
|
|
427
|
+
truncateText(
|
|
428
|
+
firstUser?.text ||
|
|
429
|
+
sources.find((entry) => entry.facts?.objective)?.facts.objective ||
|
|
430
|
+
"",
|
|
431
|
+
maxItemChars,
|
|
432
|
+
),
|
|
317
433
|
);
|
|
318
434
|
|
|
319
435
|
for (const source of sources) {
|
|
320
|
-
|
|
321
|
-
|
|
436
|
+
if (source.facts) {
|
|
437
|
+
// Extract typed observations before budgeting, never regex-match a
|
|
438
|
+
// serialized tool payload (which may contain arbitrary source text).
|
|
439
|
+
for (const field of ARRAY_FIELDS)
|
|
440
|
+
candidates[field].push(...source.facts[field]);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
// Quoted implementation examples are source data, not executed outcomes.
|
|
444
|
+
const prose = source.text.replace(
|
|
445
|
+
/```[\s\S]*?(?:```|$)|~~~[\s\S]*?(?:~~~|$)/gu,
|
|
446
|
+
"",
|
|
447
|
+
);
|
|
448
|
+
for (const fragment of fragments(prose)) {
|
|
449
|
+
const statusNarrative =
|
|
450
|
+
source.role !== "user" &&
|
|
451
|
+
!/^(?:must|must not|do not|don't|only|next|todo|remaining|then|we will|will|need to)\b/iu.test(
|
|
452
|
+
fragment,
|
|
453
|
+
);
|
|
454
|
+
if (
|
|
455
|
+
statusNarrative &&
|
|
456
|
+
/^(?:changed|modified|updated|created|deleted|wrote|edited)\b/iu.test(
|
|
457
|
+
fragment,
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
candidates.changedFiles.push(...pathsFrom(fragment));
|
|
322
461
|
if (
|
|
323
462
|
/^(?:constraint|constraints|requirement|requirements|must|must not|do not|don't|only|约束|必须|不要|只能|仅限)\b/iu.test(
|
|
324
463
|
fragment,
|
|
@@ -326,10 +465,7 @@ export function buildExtractiveHandoff(messages, options = {}) {
|
|
|
326
465
|
/\bwithout\s+(?:widening|changing|removing|losing)\b/iu.test(fragment)
|
|
327
466
|
) {
|
|
328
467
|
candidates.constraints.push(
|
|
329
|
-
stripLabel(
|
|
330
|
-
fragment,
|
|
331
|
-
"constraint(?:s)?|requirement(?:s)?|must(?: not)?|do not|don't|only|约束|必须|不要|只能|仅限",
|
|
332
|
-
),
|
|
468
|
+
stripLabel(fragment, "constraint(?:s)?|requirement(?:s)?|约束"),
|
|
333
469
|
);
|
|
334
470
|
}
|
|
335
471
|
if (
|
|
@@ -345,6 +481,7 @@ export function buildExtractiveHandoff(messages, options = {}) {
|
|
|
345
481
|
);
|
|
346
482
|
}
|
|
347
483
|
if (
|
|
484
|
+
statusNarrative &&
|
|
348
485
|
/\b(?:test|tests|vitest|jest|pytest|playwright|lint|typecheck|build)\b/iu.test(
|
|
349
486
|
fragment,
|
|
350
487
|
) &&
|
|
@@ -367,6 +504,7 @@ export function buildExtractiveHandoff(messages, options = {}) {
|
|
|
367
504
|
);
|
|
368
505
|
}
|
|
369
506
|
if (
|
|
507
|
+
statusNarrative &&
|
|
370
508
|
/\b(?:checkpoint|commit\s+[0-9a-f]{7,40}|implemented|completed|done|verified|checkpoint|检查点|已完成|已实现)\b/iu.test(
|
|
371
509
|
fragment,
|
|
372
510
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { projectToolRoundMessages } from "./session-tool-round.js";
|
|
1
2
|
/**
|
|
2
3
|
* Agent-session Markdown export — Claude-Code `/export` parity for the JSONL
|
|
3
4
|
* headless/agent session store (`~/.../sessions/<id>.jsonl`, the `--resume`
|
|
@@ -63,6 +64,25 @@ export function renderAgentSessionMarkdown(sessionId, events, opts = {}) {
|
|
|
63
64
|
for (const ev of events) {
|
|
64
65
|
if (!ev || typeof ev !== "object") continue;
|
|
65
66
|
switch (ev.type) {
|
|
67
|
+
case "tool_round":
|
|
68
|
+
for (const message of projectToolRoundMessages(ev)) {
|
|
69
|
+
L.push(
|
|
70
|
+
message.role === "assistant"
|
|
71
|
+
? "## Assistant tool calls"
|
|
72
|
+
: `### Tool result ${message.tool_call_id}`,
|
|
73
|
+
);
|
|
74
|
+
L.push(
|
|
75
|
+
"",
|
|
76
|
+
fence(
|
|
77
|
+
message.role === "assistant"
|
|
78
|
+
? message.tool_calls
|
|
79
|
+
: message.content,
|
|
80
|
+
"json",
|
|
81
|
+
),
|
|
82
|
+
"",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
66
86
|
case "user_message":
|
|
67
87
|
users += 1;
|
|
68
88
|
L.push("## 👤 User");
|
|
@@ -1394,6 +1394,60 @@ export function mutateBackgroundAgentState(id, updater, options = {}) {
|
|
|
1394
1394
|
);
|
|
1395
1395
|
}
|
|
1396
1396
|
|
|
1397
|
+
export async function mutateBackgroundAgentStateWithLockRetry(
|
|
1398
|
+
id,
|
|
1399
|
+
updater,
|
|
1400
|
+
options = {},
|
|
1401
|
+
) {
|
|
1402
|
+
const now = options.now || Date.now;
|
|
1403
|
+
const sleep =
|
|
1404
|
+
options.sleep ||
|
|
1405
|
+
((milliseconds) =>
|
|
1406
|
+
new Promise((resolvePromise) =>
|
|
1407
|
+
setTimeout(resolvePromise, milliseconds),
|
|
1408
|
+
));
|
|
1409
|
+
const mutate = options.mutate || mutateBackgroundAgentState;
|
|
1410
|
+
const numericOption = (value, fallback, minimum) => {
|
|
1411
|
+
const number = Number(value);
|
|
1412
|
+
return Number.isFinite(number) ? Math.max(minimum, number) : fallback;
|
|
1413
|
+
};
|
|
1414
|
+
const timeoutMs = numericOption(options.timeoutMs ?? 30_000, 30_000, 0);
|
|
1415
|
+
const lockTimeoutMs = numericOption(
|
|
1416
|
+
options.lockTimeoutMs ?? 10_000,
|
|
1417
|
+
10_000,
|
|
1418
|
+
0,
|
|
1419
|
+
);
|
|
1420
|
+
const initialRetryMs = numericOption(options.initialRetryMs ?? 100, 100, 1);
|
|
1421
|
+
const maxRetryMs = Math.max(
|
|
1422
|
+
initialRetryMs,
|
|
1423
|
+
numericOption(options.maxRetryMs ?? 1_000, 1_000, 1),
|
|
1424
|
+
);
|
|
1425
|
+
const deadline = now() + timeoutMs;
|
|
1426
|
+
let attempts = 0;
|
|
1427
|
+
|
|
1428
|
+
for (;;) {
|
|
1429
|
+
attempts += 1;
|
|
1430
|
+
try {
|
|
1431
|
+
return mutate(id, updater, {
|
|
1432
|
+
timeoutMs: Math.min(lockTimeoutMs, Math.max(0, deadline - now())),
|
|
1433
|
+
});
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
if (error?.code !== "STATE_LOCK_UNAVAILABLE") throw error;
|
|
1436
|
+
const remainingMs = deadline - now();
|
|
1437
|
+
if (remainingMs <= 0) {
|
|
1438
|
+
error.attempts = attempts;
|
|
1439
|
+
throw error;
|
|
1440
|
+
}
|
|
1441
|
+
const retryMs = Math.min(
|
|
1442
|
+
maxRetryMs,
|
|
1443
|
+
initialRetryMs * 2 ** Math.min(attempts - 1, 4),
|
|
1444
|
+
remainingMs,
|
|
1445
|
+
);
|
|
1446
|
+
await sleep(retryMs);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1397
1451
|
export function writeBackgroundAgentState(state, options = {}) {
|
|
1398
1452
|
return mutateBackgroundAgentState(state.id, () => state, {
|
|
1399
1453
|
createIfMissing: options.createIfMissing === true,
|
|
@@ -5,8 +5,9 @@ export const CODING_TASK_QUALITY_GUIDANCE = `Coding task quality:
|
|
|
5
5
|
- Resolve each relative import from the destination file's directory. Read back the completed file and fix newDiagnostics from write/edit results before finishing. A successful write confirms persistence, not syntax, test execution or acceptance.
|
|
6
6
|
- Regression tests need a working positive control and a negative control that exercises the requested behavior through the real production entry point. Assert observable effects; avoid mocks or alternate calls that bypass the behavior under test. Complete the current worker/request when testing cancellation and replacement.
|
|
7
7
|
- For boundary regressions, derive expected defaults, computed values and rejection rules from the implementation or the explicit requested contract. Inject mismatched, tampered or stale input through the real entry point while keeping other prerequisites valid; verify that the fixture reaches the intended check rather than failing during setup. Compare observable results with independent expected values or authoritative state, not a helper that merely recomputes the same result.
|
|
8
|
+
- Trace rejection before asserting its aftermath: a request can be rejected before registration or persistence. Check whether state stays unchanged, a failed record is saved, or an event is emitted; do not assume every rejection creates a failed task. When testing attribution, verify independently recorded identities and provenance as well as message contents; identical text can still be attributed to the wrong source.
|
|
8
9
|
- Use available execution tools for a focused check when permitted. When execution tools are unavailable, inspect imports, API contracts and fixtures using the available file tools, and clearly say that tests were not run. Never describe a read-back or static check as a passing test or end-to-end verification.
|
|
9
|
-
- Finish requested file changes before the final response. Reserve output and remaining requests for writing, read-back and focused validation. For large files, aim for a few thousand characters of new content per call: write a small complete section, then extend it with edit_file using a unique existing anchor and a replacement containing that anchor plus the next complete section. Keep each tool call complete and wait for its result before extending the file again; do not repeatedly rewrite the growing file. State what was delivered, what was checked and what remains unverified.`;
|
|
10
|
+
- Finish requested file changes before the final response. Reserve output and remaining requests for writing, read-back and focused validation. For large files, aim for a few thousand characters of new content per call: write a small complete section, then extend it with edit_file using a unique existing anchor and a replacement containing that anchor plus the next complete section. A list of available tool names is not a limit on the number of tool calls; use further calls within the actual request budget to finish and inspect the file. Keep each tool call complete and wait for its result before extending the file again; do not repeatedly rewrite the growing file. State what was delivered, what was checked and what remains unverified, without repeating that account in multiple drafts.`;
|
|
10
11
|
|
|
11
12
|
export const HERMETIC_CODING_SYSTEM_PROMPT = `You are a hermetic coding assistant. The exposed file tools are the complete tool surface. Inspect and mutate only the exact paths authorized by the user request. Do not invoke hidden tools, commands, language servers or other execution paths.
|
|
12
13
|
|