chainlesschain 0.162.77 → 0.162.79
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 +37 -1
- package/bin/chainlesschain.js +20 -1
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps--t6qElO3.js → AIOps-D89fD_7T.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-vRiifAww.js → ActionButton-d75flwX8.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BfSlGprA.js → Analytics-gvvBEb4T.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-B6L2I5ld.js → AppLayout-DzHOVS64.js} +4 -4
- package/src/assets/web-panel/assets/{Audit-Dwpk7vO2.js → Audit-15K7MhRC.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D7UuR3M8.js → Backup-BgVHsglI.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BzMDBTyZ.js → BaseInput-BRnvd4sF.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BxYUynGU.js → Chat-B2vtXu7s.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BYyCFqO0.js → ChatBubbleRenderer-d1EFM3dh.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DgpLcrjA.js → Checkbox-B5uV5Jhr.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-N5dSkUbq.js → Codegen-cVDMBVGV.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C1nf7jzT.js → Col-DtL6q7Hw.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BuX21CEd.js → Community-DSyPZkqQ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-M37IFQe_.js → Compact-CzfV_q4O.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-B_OBnXJx.js → Compliance-B_xhhLE1.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BcIn7Sb2.js → Cowork-Y49Qf0JX.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BtmA9AMI.js → Cron-B7JG-oLj.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DlAiW2Iy.js → Crosschain-CukEkQtf.js} +1 -1
- package/src/assets/web-panel/assets/{DID-91Skenc5.js → DID-Bd4UVKdn.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BTzcKrw1.js → Dashboard-DRCQl7H1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-VNCZqfkL.js → Dropdown-De47CuRY.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B_o62liK.js → EmailListRenderer-C-wAkceB.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqceYpym.js → FamilyGuardDashboard-DhLF_E0k.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BJFcp8S0.js → Federation-DAipUS1m.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DwsPen2h.js → FormItemContext-BCFyAd2j.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BOU0nfJP.js → GenericCardRenderer-wMdxrEV9.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C_92Ngor.js → Git-CcwP7HUN.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-SIBJc353.js → Governance-VF760JcB.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DcEwRh8C.js → Inference-DnR-GIn2.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C4LQ8MJI.js → KnowledgeGraph-j-ewVKWO.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BXz2tihi.js → Logs-DgB7wSor.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DqXKQJ2n.js → Marketplace-fiKjzVWE.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CXVGQSUd.js → McpTools-BjXSMQrd.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-Bp8huWkt.js → Memory-C0L5lnSS.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-B3duw6FB.js → MobileBridge-C0Rgg1Su.js} +3 -3
- package/src/assets/web-panel/assets/{MobileProjects-Cn5YO60O.js → MobileProjects-BPA50hQb.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CE_M_-1m.js → Mtc-BUsHuAjB.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Dev-y1Ei.js → MtcAudit-dlH8Q_7U.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-RILPE0-i.js → Multisig-L3_9beuW.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DsBeFM0w.js → NLProgramming-BG4sXy27.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-MONi8b1b.js → Notes-felgIvGS.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-mXNink9t.js → NotificationSettings-DHDT96AK.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Bvtv8CHQ.js → OrderTableRenderer-BYrkEfvR.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-_7rEqDWP.js → Organization-DdtOLkHG.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CuqybwI0.js → Overflow-DLw5Pni7.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BvYN2SXJ.js → P2P-B-mZ5EXz.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BWGnb4i0.js → PdhVaultBrowser-DeOmNCwK.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D2vyDMm6.js → Permissions-BeQMX71K.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BWaT70CA.js → PersonalDataHub-D5PoqtQI.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-T_Pztk-K.js → Pipeline-B0f5sqKF.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BTChzeM8.js → Privacy-Dj_gcxio.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BQQBICYM.js → ProjectInit-DmfPgied.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BOLO_fhW.js → ProjectSettings-BmBfocrv.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BfqkBhzi.js → Projects-BUif48cc.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DpWEAYh2.js → Providers-B0ztEAOV.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-MX8kg-uO.js → QuickAsk-BtMG4jH5.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-UOCp-h2X.js → Recommend-DQKN1En8.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-H_vE2kEf.js → Reputation-D34CvUxg.js} +1 -1
- package/src/assets/web-panel/assets/{Row-dupAolkD.js → Row-nnuDNx31.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DVoerh51.js → RssFeed-DHx9x8_B.js} +2 -2
- package/src/assets/web-panel/assets/{Search-5zxC_iWb.js → Search-CH-JYtn_.js} +1 -1
- package/src/assets/web-panel/assets/{Security-vg3XxdEZ.js → Security-nNKBmzm4.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CWjum0P4.js → Services-xYk_lDxy.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BkVnAX7_.js → Skeleton-DTYyRduA.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-2oMJr3sU.js → Skills-DS19-9sF.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Bv8uLcr_.js → Sla-Cr_ir42Y.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BfvawAGM.js → SpeechSettings-Ch5Iq7Wy.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DzWmsU8K.js → SyncSettings-CTblfa_E.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-cbtuyShK.js → Tasks-z7lz3hjG.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-PP2omXNk.js → Templates-DGoMFd4r.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Cab9qgCx.js → Tenant-DQUPvHxx.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-9kFA0Vf8.js → Terminal-Wt2wrbE6.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Cx8kncEg.js → TimelineRenderer-DGTc2UqL.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-D19NKMv_.js → Tokens-DPVnuARF.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DbRaTJsm.js → Trigger-DV5MPXC1.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-8EFY9ZrM.js → Trust-BBkMKqwS.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign--wxy-nr4.js → UkeySign-B_2E0qev.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BlRqqo2c.js → VideoEditing-CA-qKeVb.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-B2x_RfaK.js → Wallet-Ds4WURh-.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-DISokPYb.js → WebAuthn-BApiHjzz.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DkuLYA12.js → WorkflowEditor-DFzmAnzV.js} +1 -1
- package/src/assets/web-panel/assets/{chat-MMwBSP3l.js → chat-DZ6sHPit.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CPP3K6Jb.js → colors-DgbwhHtE.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-2ruYr7FB.js → compact-item-C4QVYSzd.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BPpiGxaR.js → createContext-yXIs4TwU.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-C-HfIeF7.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CtP3Uqy-.js → hasIn-CPtuUtBl.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpBaxHIL.js → index-4g6pGxnX.js} +1 -1
- package/src/assets/web-panel/assets/{index-9SEWDDhb.js → index-B91hax9S.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2N_-y_W.js → index-B9DUiQ24.js} +3 -3
- package/src/assets/web-panel/assets/index-BBgWatYO.js +1 -0
- package/src/assets/web-panel/assets/{index-2cESIQ4O.js → index-BCb7MYMS.js} +1 -1
- package/src/assets/web-panel/assets/{index-D05imMj-.js → index-BIAxMDe9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bq8gB01i.js → index-BJiGrT6V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BM_RMGj5.js → index-BOKVSmKh.js} +1 -1
- package/src/assets/web-panel/assets/{index-COSm6DcE.js → index-BWJGry74.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf1tNEoB.js → index-BaVYCMJa.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyCZ3fz1.js → index-Bd0JznCS.js} +1 -1
- package/src/assets/web-panel/assets/{index-oaNSovPm.js → index-BpBCKK6W.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSNoOIwN.js → index-BsXF9cn5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIQO-prS.js → index-BxclR5gc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6qsWs-4.js → index-C0LyE6R6.js} +1 -1
- package/src/assets/web-panel/assets/{index-COPqJgcW.js → index-CBAgy5pf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLAT0M1t.js → index-CJ550XXg.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7-Zo0uW.js → index-CTlz3MP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dc1ysHqq.js → index-CaC4gaQZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-6Sly6NDj.js → index-CnP2ftM5.js} +1 -1
- package/src/assets/web-panel/assets/index-CvCTol_u.js +1 -0
- package/src/assets/web-panel/assets/{index-Bjthf-eJ.js → index-D2KRfjEI.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bfr1HIma.js → index-DA0ePxNn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Byddazfj.js → index-DC5iP1VB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkqTFggQ.js → index-DCGmeXfl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BM7y07U3.js → index-DHnWI0jj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZrah8Gb.js → index-DN4qpkKQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DS_ISOVj.js → index-DTLaRwKx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DO_a65ut.js → index-DdFjcgqH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy85x86R.js → index-DopuoTzG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXGebJnR.js → index-DpDAGvyU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUQp8MdV.js → index-EGVlUtH-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cru4qTvK.js → index-IFh9qCi0.js} +1 -1
- package/src/assets/web-panel/assets/{index-oD_rJWBp.js → index-UEeNpaDD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzFVlsbg.js → index-ZCyyy3Zt.js} +1 -1
- package/src/assets/web-panel/assets/{index-h-zAHKNr.js → index-aKZjRwjv.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5g378QF.js → index-jBlY6-bF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZyr02ib.js → index-kbcTc6Pf.js} +1 -1
- package/src/assets/web-panel/assets/{index-P_R2pJ9q.js → index-x9bXmSNB.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BrJf63uk.js → initDefaultProps-BDsxioXk.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BD8UoaTG.js → motion-erZ2fiIQ.js} +1 -1
- package/src/assets/web-panel/assets/{move-DbunBKV-.js → move-BFiiw4WK.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CkGNCa-h.js → omit-BRMVd4pM.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DTvTrHC2.js → pickAttrs-CGjoFXsq.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Cb7oz9vL.js → placementArrow-DU_Bnf7w.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-O7lB6MAE.js → responsiveObserve-DPdkYupg.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C24m1SKv.js → slide-JfHiG96y.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-D_-9GtZ3.js → statusUtils-Dy-1guyd.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CrAYF9jD.js → styleChecker-Chaljnux.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CmgeoVPN.js → useFlexGapSupport-DOHIHBgY.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DzJSzGsy.js → useFs-BxSoQhIY.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Bgqky5YK.js → usePersonalDataHub-MOR76PYB.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-ErTJLgr4.js → vnode-BBGIyeYD.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-wlf3cppM.js → zoom-C1oFENec.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/audit.js +4 -3
- package/src/commands/automation.js +6 -14
- package/src/commands/bi.js +10 -9
- package/src/commands/codegen.js +5 -13
- package/src/commands/dao.js +8 -6
- package/src/commands/dbevo.js +13 -14
- package/src/commands/economy.js +3 -2
- package/src/commands/evolution.js +3 -2
- package/src/commands/federation.js +4 -3
- package/src/commands/governance.js +9 -4
- package/src/commands/hardening.js +5 -4
- package/src/commands/incentive.js +6 -5
- package/src/commands/kg.js +17 -10
- package/src/commands/lowcode.js +23 -11
- package/src/commands/marketplace.js +4 -3
- package/src/commands/mcp.js +17 -5
- package/src/commands/ops.js +9 -4
- package/src/commands/recommend.js +7 -5
- package/src/commands/scim.js +3 -2
- package/src/commands/session.js +9 -6
- package/src/commands/social.js +4 -3
- package/src/commands/sync.js +3 -2
- package/src/commands/tenant.js +11 -6
- package/src/commands/zkp.js +8 -9
- package/src/gateways/ws/ws-agent-handler.js +12 -3
- package/src/gateways/ws/ws-server.js +6 -0
- package/src/harness/background-task-manager.js +44 -18
- package/src/harness/mcp-client.js +125 -46
- package/src/lib/agent-core.js +2 -1
- package/src/lib/chat-core.js +209 -107
- package/src/lib/claude-code-bridge.js +13 -1
- package/src/lib/dao-governance.js +3 -3
- package/src/lib/downloader.js +82 -25
- package/src/lib/headless-config-command.js +62 -0
- package/src/lib/json-schema-output.js +55 -11
- package/src/lib/mcp-oauth.js +110 -21
- package/src/lib/mcp-serve.js +70 -11
- package/src/lib/multisig-runtime.js +22 -3
- package/src/lib/parse-json-option.js +35 -0
- package/src/lib/parse-number-option.js +27 -0
- package/src/lib/runnable-provider.js +97 -0
- package/src/repl/agent-repl.js +76 -17
- package/src/repl/config-summary.js +66 -0
- package/src/runtime/agent-core.js +189 -36
- package/src/runtime/headless-runner.js +49 -1
- package/src/runtime/headless-stream.js +34 -0
- package/src/assets/web-panel/assets/devWarning-D7iybGpP.js +0 -1
- package/src/assets/web-panel/assets/index-BzJrOJ0f.js +0 -1
- package/src/assets/web-panel/assets/index-QN-iyhAl.js +0 -1
package/src/lib/chat-core.js
CHANGED
|
@@ -12,23 +12,65 @@
|
|
|
12
12
|
import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
|
|
13
13
|
import { appendTokenUsage } from "../harness/jsonl-session-store.js";
|
|
14
14
|
|
|
15
|
+
// A streaming chat call must not hang forever if the API accepts the connection
|
|
16
|
+
// but then goes silent (TCP up, no bytes). Abort the request if no data arrives
|
|
17
|
+
// for STREAM_STALL_MS — reset on every chunk, so it's a stall detector, not a
|
|
18
|
+
// total-time cap (long but healthy responses are unaffected). The default is
|
|
19
|
+
// generous (and env-overridable) so a slow local model's first token isn't cut
|
|
20
|
+
// off; raise CC_CHAT_STALL_MS if you run very large local models.
|
|
21
|
+
export const STREAM_STALL_MS = Number(process.env.CC_CHAT_STALL_MS) || 180000;
|
|
22
|
+
|
|
23
|
+
function makeStallGuard(stallMs = STREAM_STALL_MS) {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
let timer = null;
|
|
26
|
+
const bump = () => {
|
|
27
|
+
if (timer) clearTimeout(timer);
|
|
28
|
+
timer = setTimeout(() => controller.abort(), stallMs);
|
|
29
|
+
if (timer && typeof timer.unref === "function") timer.unref();
|
|
30
|
+
};
|
|
31
|
+
const stop = () => {
|
|
32
|
+
if (timer) clearTimeout(timer);
|
|
33
|
+
timer = null;
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
signal: controller.signal,
|
|
37
|
+
bump,
|
|
38
|
+
stop,
|
|
39
|
+
stalled: () => controller.signal.aborted,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
/**
|
|
16
44
|
* Stream a response from Ollama.
|
|
17
45
|
* If `onUsage` is provided, it's called with `{inputTokens, outputTokens}`
|
|
18
46
|
* derived from Ollama's terminal `prompt_eval_count` / `eval_count` fields.
|
|
19
47
|
*/
|
|
20
48
|
export async function streamOllama(messages, model, baseUrl, onToken, onUsage) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
const guard = makeStallGuard();
|
|
50
|
+
guard.bump();
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
response = await fetch(`${baseUrl}/api/chat`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
model,
|
|
58
|
+
messages,
|
|
59
|
+
stream: true,
|
|
60
|
+
}),
|
|
61
|
+
signal: guard.signal,
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
guard.stop();
|
|
65
|
+
throw guard.stalled()
|
|
66
|
+
? new Error(
|
|
67
|
+
`Ollama request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
|
|
68
|
+
)
|
|
69
|
+
: e;
|
|
70
|
+
}
|
|
30
71
|
|
|
31
72
|
if (!response.ok) {
|
|
73
|
+
guard.stop();
|
|
32
74
|
throw new Error(`Ollama error: ${response.status} ${response.statusText}`);
|
|
33
75
|
}
|
|
34
76
|
|
|
@@ -36,31 +78,42 @@ export async function streamOllama(messages, model, baseUrl, onToken, onUsage) {
|
|
|
36
78
|
const decoder = new TextDecoder();
|
|
37
79
|
let fullResponse = "";
|
|
38
80
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
81
|
+
try {
|
|
82
|
+
while (true) {
|
|
83
|
+
const { done, value } = await reader.read();
|
|
84
|
+
if (done) break;
|
|
85
|
+
guard.bump();
|
|
42
86
|
|
|
43
|
-
|
|
44
|
-
|
|
87
|
+
const text = decoder.decode(value, { stream: true });
|
|
88
|
+
const lines = text.split("\n").filter(Boolean);
|
|
45
89
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
if (json.done && onUsage) {
|
|
54
|
-
const inputTokens = Number(json.prompt_eval_count) || 0;
|
|
55
|
-
const outputTokens = Number(json.eval_count) || 0;
|
|
56
|
-
if (inputTokens || outputTokens) {
|
|
57
|
-
onUsage({ inputTokens, outputTokens });
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
try {
|
|
92
|
+
const json = JSON.parse(line);
|
|
93
|
+
if (json.message?.content) {
|
|
94
|
+
fullResponse += json.message.content;
|
|
95
|
+
onToken(json.message.content);
|
|
58
96
|
}
|
|
97
|
+
if (json.done && onUsage) {
|
|
98
|
+
const inputTokens = Number(json.prompt_eval_count) || 0;
|
|
99
|
+
const outputTokens = Number(json.eval_count) || 0;
|
|
100
|
+
if (inputTokens || outputTokens) {
|
|
101
|
+
onUsage({ inputTokens, outputTokens });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// Partial JSON, skip
|
|
59
106
|
}
|
|
60
|
-
} catch {
|
|
61
|
-
// Partial JSON, skip
|
|
62
107
|
}
|
|
63
108
|
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
throw guard.stalled()
|
|
111
|
+
? new Error(
|
|
112
|
+
`Ollama stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
|
|
113
|
+
)
|
|
114
|
+
: e;
|
|
115
|
+
} finally {
|
|
116
|
+
guard.stop();
|
|
64
117
|
}
|
|
65
118
|
|
|
66
119
|
return fullResponse;
|
|
@@ -77,23 +130,37 @@ export async function streamOpenAI(
|
|
|
77
130
|
onToken,
|
|
78
131
|
onUsage,
|
|
79
132
|
) {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
const guard = makeStallGuard();
|
|
134
|
+
guard.bump();
|
|
135
|
+
let response;
|
|
136
|
+
try {
|
|
137
|
+
response = await fetch(`${baseUrl}/chat/completions`, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: {
|
|
140
|
+
"Content-Type": "application/json",
|
|
141
|
+
Authorization: `Bearer ${apiKey}`,
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
model,
|
|
145
|
+
messages,
|
|
146
|
+
stream: true,
|
|
147
|
+
// Opt-in token usage in the terminal chunk (OpenAI-compatible).
|
|
148
|
+
// Servers that don't understand it simply ignore it.
|
|
149
|
+
stream_options: { include_usage: true },
|
|
150
|
+
}),
|
|
151
|
+
signal: guard.signal,
|
|
152
|
+
});
|
|
153
|
+
} catch (e) {
|
|
154
|
+
guard.stop();
|
|
155
|
+
throw guard.stalled()
|
|
156
|
+
? new Error(
|
|
157
|
+
`API request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
|
|
158
|
+
)
|
|
159
|
+
: e;
|
|
160
|
+
}
|
|
95
161
|
|
|
96
162
|
if (!response.ok) {
|
|
163
|
+
guard.stop();
|
|
97
164
|
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
98
165
|
}
|
|
99
166
|
|
|
@@ -101,36 +168,45 @@ export async function streamOpenAI(
|
|
|
101
168
|
const decoder = new TextDecoder();
|
|
102
169
|
let fullResponse = "";
|
|
103
170
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
171
|
+
try {
|
|
172
|
+
while (true) {
|
|
173
|
+
const { done, value } = await reader.read();
|
|
174
|
+
if (done) break;
|
|
175
|
+
guard.bump();
|
|
107
176
|
|
|
108
|
-
|
|
109
|
-
|
|
177
|
+
const text = decoder.decode(value, { stream: true });
|
|
178
|
+
const lines = text.split("\n").filter(Boolean);
|
|
110
179
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
if (line.startsWith("data: ")) {
|
|
182
|
+
const data = line.slice(6);
|
|
183
|
+
if (data === "[DONE]") continue;
|
|
184
|
+
try {
|
|
185
|
+
const json = JSON.parse(data);
|
|
186
|
+
const content = json.choices?.[0]?.delta?.content;
|
|
187
|
+
if (content) {
|
|
188
|
+
fullResponse += content;
|
|
189
|
+
onToken(content);
|
|
190
|
+
}
|
|
191
|
+
if (json.usage && onUsage) {
|
|
192
|
+
const inputTokens = Number(json.usage.prompt_tokens) || 0;
|
|
193
|
+
const outputTokens = Number(json.usage.completion_tokens) || 0;
|
|
194
|
+
if (inputTokens || outputTokens) {
|
|
195
|
+
onUsage({ inputTokens, outputTokens });
|
|
196
|
+
}
|
|
127
197
|
}
|
|
198
|
+
} catch {
|
|
199
|
+
// Partial data
|
|
128
200
|
}
|
|
129
|
-
} catch {
|
|
130
|
-
// Partial data
|
|
131
201
|
}
|
|
132
202
|
}
|
|
133
203
|
}
|
|
204
|
+
} catch (e) {
|
|
205
|
+
throw guard.stalled()
|
|
206
|
+
? new Error(`API stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`)
|
|
207
|
+
: e;
|
|
208
|
+
} finally {
|
|
209
|
+
guard.stop();
|
|
134
210
|
}
|
|
135
211
|
|
|
136
212
|
return fullResponse;
|
|
@@ -161,23 +237,37 @@ export async function streamAnthropic(
|
|
|
161
237
|
}
|
|
162
238
|
}
|
|
163
239
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
240
|
+
const guard = makeStallGuard();
|
|
241
|
+
guard.bump();
|
|
242
|
+
let response;
|
|
243
|
+
try {
|
|
244
|
+
response = await fetch(`${baseUrl}/messages`, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: {
|
|
247
|
+
"Content-Type": "application/json",
|
|
248
|
+
"x-api-key": apiKey,
|
|
249
|
+
"anthropic-version": "2023-06-01",
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
model,
|
|
253
|
+
max_tokens: 4096,
|
|
254
|
+
stream: true,
|
|
255
|
+
...(system ? { system } : {}),
|
|
256
|
+
messages: convo,
|
|
257
|
+
}),
|
|
258
|
+
signal: guard.signal,
|
|
259
|
+
});
|
|
260
|
+
} catch (e) {
|
|
261
|
+
guard.stop();
|
|
262
|
+
throw guard.stalled()
|
|
263
|
+
? new Error(
|
|
264
|
+
`Anthropic request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
|
|
265
|
+
)
|
|
266
|
+
: e;
|
|
267
|
+
}
|
|
179
268
|
|
|
180
269
|
if (!response.ok) {
|
|
270
|
+
guard.stop();
|
|
181
271
|
throw new Error(
|
|
182
272
|
`Anthropic error: ${response.status} ${response.statusText}`,
|
|
183
273
|
);
|
|
@@ -190,36 +280,48 @@ export async function streamAnthropic(
|
|
|
190
280
|
let inputTokens = 0;
|
|
191
281
|
let outputTokens = 0;
|
|
192
282
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
283
|
+
try {
|
|
284
|
+
while (true) {
|
|
285
|
+
const { done, value } = await reader.read();
|
|
286
|
+
if (done) break;
|
|
287
|
+
guard.bump();
|
|
288
|
+
buf += decoder.decode(value, { stream: true });
|
|
289
|
+
const lines = buf.split("\n");
|
|
290
|
+
buf = lines.pop() || "";
|
|
291
|
+
for (const raw of lines) {
|
|
292
|
+
const line = raw.trim();
|
|
293
|
+
if (!line || !line.startsWith("data:")) continue;
|
|
294
|
+
const payload = line.slice(5).trim();
|
|
295
|
+
if (!payload) continue;
|
|
296
|
+
try {
|
|
297
|
+
const obj = JSON.parse(payload);
|
|
298
|
+
if (obj.type === "content_block_delta") {
|
|
299
|
+
const delta = obj.delta?.text;
|
|
300
|
+
if (delta) {
|
|
301
|
+
fullResponse += delta;
|
|
302
|
+
onToken(delta);
|
|
303
|
+
}
|
|
304
|
+
} else if (obj.type === "message_start") {
|
|
305
|
+
inputTokens =
|
|
306
|
+
Number(obj.message?.usage?.input_tokens) || inputTokens;
|
|
307
|
+
outputTokens =
|
|
308
|
+
Number(obj.message?.usage?.output_tokens) || outputTokens;
|
|
309
|
+
} else if (obj.type === "message_delta") {
|
|
310
|
+
outputTokens = Number(obj.usage?.output_tokens) || outputTokens;
|
|
211
311
|
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
outputTokens =
|
|
215
|
-
Number(obj.message?.usage?.output_tokens) || outputTokens;
|
|
216
|
-
} else if (obj.type === "message_delta") {
|
|
217
|
-
outputTokens = Number(obj.usage?.output_tokens) || outputTokens;
|
|
312
|
+
} catch {
|
|
313
|
+
/* skip malformed */
|
|
218
314
|
}
|
|
219
|
-
} catch {
|
|
220
|
-
/* skip malformed */
|
|
221
315
|
}
|
|
222
316
|
}
|
|
317
|
+
} catch (e) {
|
|
318
|
+
throw guard.stalled()
|
|
319
|
+
? new Error(
|
|
320
|
+
`Anthropic stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
|
|
321
|
+
)
|
|
322
|
+
: e;
|
|
323
|
+
} finally {
|
|
324
|
+
guard.stop();
|
|
223
325
|
}
|
|
224
326
|
|
|
225
327
|
if (onUsage && (inputTokens || outputTokens)) {
|
|
@@ -97,6 +97,7 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
97
97
|
timeout = 300_000,
|
|
98
98
|
context = "",
|
|
99
99
|
allowedTools = null,
|
|
100
|
+
killGraceMs = 3000,
|
|
100
101
|
} = options;
|
|
101
102
|
|
|
102
103
|
const fullPrompt = context
|
|
@@ -128,10 +129,19 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
128
129
|
});
|
|
129
130
|
this._proc = proc;
|
|
130
131
|
|
|
132
|
+
// SIGKILL-escalation timer is hoisted so the close/error handlers can
|
|
133
|
+
// clear it — otherwise, when the process dies promptly from SIGTERM, this
|
|
134
|
+
// inner timer still fires a redundant SIGKILL on a dead pid AND holds the
|
|
135
|
+
// event loop open for the full grace period. unref() is a second guard so
|
|
136
|
+
// it never keeps the process alive on its own.
|
|
137
|
+
let killTimer = null;
|
|
131
138
|
const timer = setTimeout(() => {
|
|
132
139
|
timedOut = true;
|
|
133
140
|
proc.kill("SIGTERM");
|
|
134
|
-
setTimeout(() => proc.kill("SIGKILL"),
|
|
141
|
+
killTimer = setTimeout(() => proc.kill("SIGKILL"), killGraceMs);
|
|
142
|
+
if (killTimer && typeof killTimer.unref === "function") {
|
|
143
|
+
killTimer.unref();
|
|
144
|
+
}
|
|
135
145
|
}, timeout);
|
|
136
146
|
|
|
137
147
|
proc.stdout.on("data", (data) => {
|
|
@@ -146,6 +156,7 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
146
156
|
|
|
147
157
|
proc.on("close", (code) => {
|
|
148
158
|
clearTimeout(timer);
|
|
159
|
+
if (killTimer) clearTimeout(killTimer);
|
|
149
160
|
this._proc = null;
|
|
150
161
|
const duration = Date.now() - startTime;
|
|
151
162
|
const rawOutput = outputChunks.join("");
|
|
@@ -178,6 +189,7 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
178
189
|
|
|
179
190
|
proc.on("error", (err) => {
|
|
180
191
|
clearTimeout(timer);
|
|
192
|
+
if (killTimer) clearTimeout(killTimer);
|
|
181
193
|
this._proc = null;
|
|
182
194
|
this.status = AGENT_STATUS.FAILED;
|
|
183
195
|
this.currentTask = null;
|
|
@@ -437,7 +437,7 @@ export function castVote(db, opts) {
|
|
|
437
437
|
`Invalid voteType: ${voteType}. Must be one of: ${Object.values(VOTE_TYPE).join(", ")}`,
|
|
438
438
|
);
|
|
439
439
|
}
|
|
440
|
-
if (
|
|
440
|
+
if (!Number.isFinite(voteCount) || voteCount <= 0) {
|
|
441
441
|
throw new Error("voteCount must be a positive number");
|
|
442
442
|
}
|
|
443
443
|
|
|
@@ -686,7 +686,7 @@ export function allocateFundsV2(db, opts) {
|
|
|
686
686
|
const { proposalId, recipient, amount, asset = "native", memo } = opts || {};
|
|
687
687
|
if (!proposalId) throw new Error("proposalId is required");
|
|
688
688
|
if (!recipient) throw new Error("recipient is required");
|
|
689
|
-
if (
|
|
689
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
690
690
|
throw new Error("amount must be a positive number");
|
|
691
691
|
}
|
|
692
692
|
if (amount > _configV2.maxSingleAllocation) {
|
|
@@ -807,7 +807,7 @@ export function getConfigV2() {
|
|
|
807
807
|
|
|
808
808
|
export function depositToTreasuryV2(db, opts) {
|
|
809
809
|
const { amount, asset = "native", memo, depositorDid } = opts || {};
|
|
810
|
-
if (
|
|
810
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
811
811
|
throw new Error("amount must be a positive number");
|
|
812
812
|
}
|
|
813
813
|
|
package/src/lib/downloader.js
CHANGED
|
@@ -24,6 +24,67 @@ const ASSET_PATTERNS = {
|
|
|
24
24
|
linux: { x64: /chainlesschain.*linux.*x64.*\.zip$/i },
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Stream a fetch Response body to `destPath` with two robustness guards for the
|
|
29
|
+
* download/install path:
|
|
30
|
+
* - idle timeout: if no bytes arrive for `idleTimeoutMs`, abort `controller`
|
|
31
|
+
* (which cancels the underlying fetch) so a hung server can't freeze the
|
|
32
|
+
* download forever. The timer resets on every chunk — a stall detector, not
|
|
33
|
+
* a total-time cap — and is unref'd so it never holds the loop open.
|
|
34
|
+
* - size verification: if the server advertised a Content-Length and fewer
|
|
35
|
+
* bytes arrived, throw. A silently truncated download must not look like a
|
|
36
|
+
* successful one (the caller would otherwise extract/install a corrupt file).
|
|
37
|
+
*
|
|
38
|
+
* Exported for tests. Returns { downloadedBytes, totalBytes }.
|
|
39
|
+
*/
|
|
40
|
+
export async function streamToFileVerified(
|
|
41
|
+
response,
|
|
42
|
+
destPath,
|
|
43
|
+
{
|
|
44
|
+
controller = null,
|
|
45
|
+
idleTimeoutMs = 60000,
|
|
46
|
+
onProgress = null,
|
|
47
|
+
createWriteStreamImpl = createWriteStream,
|
|
48
|
+
} = {},
|
|
49
|
+
) {
|
|
50
|
+
const totalBytes = parseInt(
|
|
51
|
+
response.headers.get("content-length") || "0",
|
|
52
|
+
10,
|
|
53
|
+
);
|
|
54
|
+
let downloadedBytes = 0;
|
|
55
|
+
let idleTimer = null;
|
|
56
|
+
const armIdle = () => {
|
|
57
|
+
if (!controller || !(idleTimeoutMs > 0)) return;
|
|
58
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
59
|
+
idleTimer = setTimeout(() => controller.abort(), idleTimeoutMs);
|
|
60
|
+
if (idleTimer && typeof idleTimer.unref === "function") idleTimer.unref();
|
|
61
|
+
};
|
|
62
|
+
const reader = response.body.getReader();
|
|
63
|
+
async function* track() {
|
|
64
|
+
try {
|
|
65
|
+
while (true) {
|
|
66
|
+
const { done, value } = await reader.read();
|
|
67
|
+
if (done) return;
|
|
68
|
+
armIdle();
|
|
69
|
+
downloadedBytes += value.byteLength;
|
|
70
|
+
if (onProgress) onProgress(downloadedBytes, totalBytes);
|
|
71
|
+
yield value;
|
|
72
|
+
}
|
|
73
|
+
} finally {
|
|
74
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
armIdle();
|
|
78
|
+
await pipeline(track(), createWriteStreamImpl(destPath));
|
|
79
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
80
|
+
if (totalBytes > 0 && downloadedBytes !== totalBytes) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Download truncated: received ${downloadedBytes} of ${totalBytes} bytes`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return { downloadedBytes, totalBytes };
|
|
86
|
+
}
|
|
87
|
+
|
|
27
88
|
export async function downloadRelease(version, options = {}) {
|
|
28
89
|
const binDir = ensureDir(getBinDir());
|
|
29
90
|
|
|
@@ -41,9 +102,14 @@ export async function downloadRelease(version, options = {}) {
|
|
|
41
102
|
const spinner = ora(`Downloading ${assetName}...`).start();
|
|
42
103
|
|
|
43
104
|
try {
|
|
105
|
+
// Abort the fetch if the download stalls (no bytes for idleTimeoutMs) so a
|
|
106
|
+
// hung mirror can't freeze `cc setup` forever; streamToFileVerified also
|
|
107
|
+
// verifies the full body landed (truncation guard).
|
|
108
|
+
const controller = new AbortController();
|
|
44
109
|
const response = await fetch(assetUrl, {
|
|
45
110
|
headers: { Accept: "application/octet-stream" },
|
|
46
111
|
redirect: "follow",
|
|
112
|
+
signal: controller.signal,
|
|
47
113
|
});
|
|
48
114
|
|
|
49
115
|
if (!response.ok) {
|
|
@@ -52,30 +118,16 @@ export async function downloadRelease(version, options = {}) {
|
|
|
52
118
|
);
|
|
53
119
|
}
|
|
54
120
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// avoids the experimental stream.Readable.fromWeb); `pipeline` keeps the
|
|
63
|
-
// backpressure + error handling. Progress is tracked as chunks flow through.
|
|
64
|
-
const reader = response.body.getReader();
|
|
65
|
-
async function* trackProgress() {
|
|
66
|
-
while (true) {
|
|
67
|
-
const { done, value } = await reader.read();
|
|
68
|
-
if (done) return;
|
|
69
|
-
downloadedBytes += value.byteLength;
|
|
70
|
-
if (totalBytes > 0) {
|
|
71
|
-
const pct = ((downloadedBytes / totalBytes) * 100).toFixed(1);
|
|
72
|
-
spinner.text = `Downloading ${assetName}... ${pct}% (${formatBytes(downloadedBytes)}/${formatBytes(totalBytes)})`;
|
|
121
|
+
const { downloadedBytes } = await streamToFileVerified(response, destPath, {
|
|
122
|
+
controller,
|
|
123
|
+
idleTimeoutMs: options.idleTimeoutMs,
|
|
124
|
+
onProgress: (downloaded, total) => {
|
|
125
|
+
if (total > 0) {
|
|
126
|
+
const pct = ((downloaded / total) * 100).toFixed(1);
|
|
127
|
+
spinner.text = `Downloading ${assetName}... ${pct}% (${formatBytes(downloaded)}/${formatBytes(total)})`;
|
|
73
128
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
await pipeline(trackProgress(), createWriteStream(destPath));
|
|
129
|
+
},
|
|
130
|
+
});
|
|
79
131
|
|
|
80
132
|
spinner.succeed(
|
|
81
133
|
`Downloaded ${assetName} (${formatBytes(downloadedBytes)})`,
|
|
@@ -96,11 +148,16 @@ export async function downloadRelease(version, options = {}) {
|
|
|
96
148
|
|
|
97
149
|
return binDir;
|
|
98
150
|
} catch (err) {
|
|
99
|
-
|
|
151
|
+
const stalled =
|
|
152
|
+
err && (err.name === "AbortError" || err.code === "ABORT_ERR");
|
|
153
|
+
const msg = stalled
|
|
154
|
+
? `Download stalled (no data received): ${assetName}`
|
|
155
|
+
: `Download failed: ${err.message}`;
|
|
156
|
+
spinner.fail(msg);
|
|
100
157
|
if (existsSync(destPath)) {
|
|
101
158
|
unlinkSync(destPath);
|
|
102
159
|
}
|
|
103
|
-
throw err;
|
|
160
|
+
throw stalled ? new Error(msg) : err;
|
|
104
161
|
}
|
|
105
162
|
}
|
|
106
163
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless `/config` directive (Claude-Code 2.1.181 parity — "/config key=value
|
|
3
|
+
* in interactive AND -p modes"). The REPL already handles `/config` for the
|
|
4
|
+
* interactive half; this is the `-p` / piped-stdin half: when the headless
|
|
5
|
+
* prompt is a leading `/config …` slash command, treat it as a one-shot config
|
|
6
|
+
* get / set / show instead of a task for the LLM — no model call, no session,
|
|
7
|
+
* no bootstrap. (`cc config get|set` covers the same ground for pure scripting;
|
|
8
|
+
* this gives the symmetric single-interface form Claude Code exposes.)
|
|
9
|
+
*
|
|
10
|
+
* Pure detection + execution; config-manager does the disk I/O (injected for
|
|
11
|
+
* tests). Secrets stay masked on read and write — the same invariant the REPL
|
|
12
|
+
* `/config` upholds.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
parseConfigCommand,
|
|
16
|
+
renderConfigGet,
|
|
17
|
+
renderConfigSet,
|
|
18
|
+
renderConfigSummary,
|
|
19
|
+
} from "../repl/config-summary.js";
|
|
20
|
+
|
|
21
|
+
/** Does this headless prompt start with the `/config` slash command? */
|
|
22
|
+
export function isHeadlessConfigCommand(prompt) {
|
|
23
|
+
return /^\/config(?:\s|$)/.test((prompt || "").trim());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute a `/config …` directive. Returns `{ text, isError }`.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} prompt the full prompt (leading `/config …`)
|
|
30
|
+
* @param {object} deps
|
|
31
|
+
* - configManager: { loadConfig, getConfigValue, setConfigValue } (required)
|
|
32
|
+
* - getConfigPath?: () => string|null (shown in the `show` summary)
|
|
33
|
+
*/
|
|
34
|
+
export function runConfigDirective(prompt, deps = {}) {
|
|
35
|
+
const cm = deps.configManager;
|
|
36
|
+
if (!cm) return { text: "/config: no config manager", isError: true };
|
|
37
|
+
const getConfigPath = deps.getConfigPath || (() => null);
|
|
38
|
+
const argStr = (prompt || "").trim().replace(/^\/config\b/, "");
|
|
39
|
+
const cmd = parseConfigCommand(argStr);
|
|
40
|
+
|
|
41
|
+
if (cmd.action === "error") {
|
|
42
|
+
return { text: `/config: ${cmd.message}`, isError: true };
|
|
43
|
+
}
|
|
44
|
+
if (cmd.action === "get") {
|
|
45
|
+
return {
|
|
46
|
+
text: renderConfigGet(cmd.key, cm.getConfigValue(cmd.key)),
|
|
47
|
+
isError: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (cmd.action === "set") {
|
|
51
|
+
cm.setConfigValue(cmd.key, cmd.value);
|
|
52
|
+
return {
|
|
53
|
+
text: renderConfigSet(cmd.key, cm.getConfigValue(cmd.key)),
|
|
54
|
+
isError: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// show
|
|
58
|
+
return {
|
|
59
|
+
text: renderConfigSummary(cm.loadConfig(), { path: getConfigPath() }),
|
|
60
|
+
isError: false,
|
|
61
|
+
};
|
|
62
|
+
}
|