chainlesschain 0.162.101 → 0.162.102
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 +1 -1
- package/src/assets/web-panel/assets/{AIOps-NeD6mA1O.js → AIOps-C1k00Gsz.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Bhp7A7vO.js → ActionButton-BinI6ZDq.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DgdI84Jy.js → Analytics-Cg0jo_cd.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Qw29caa7.js → AppLayout-CClMs_AA.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-DgkzhBjz.js → Audit-BtQgvFdM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CiWEQoW5.js → Backup-43FRi7ZI.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BWNbkeDF.js → BaseInput-Cfza-FZU.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CUcj4Vyd.js → Chat-7AjHSiSC.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-bAMNCgEj.js → ChatBubbleRenderer-Bh1YNdtx.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-B0uZTW8f.js → Checkbox-DgjfqlWW.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BPk7b-Xa.js → Codegen-CkwOBThj.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C2cV_dw4.js → Col-Cohk5WYM.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CDN6Yggb.js → Community-D3LMGh3F.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-C4WPtW_V.js → Compact-DqoMJmh0.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BGUfeH-3.js → Compliance-BVjWZlEj.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CsnVgF-o.js → Cowork-C0lqQPqu.js} +2 -2
- package/src/assets/web-panel/assets/{Cron--rGanASk.js → Cron-DHd_PJZS.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-D_CLrd-0.js → Crosschain-BphK2Q9u.js} +1 -1
- package/src/assets/web-panel/assets/{DID-iYkbVc8W.js → DID-CWIw0aG4.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CuFMU98t.js → Dashboard-CKgDU9U6.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BPSjv_Ye.js → Dropdown-DkIlDUXF.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DBmk7czO.js → EmailListRenderer-5sXA9hwZ.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BaCArN11.js → FamilyGuardDashboard-CgcdJLH7.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsTV-8Ca.js → Federation-DPOcJISA.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C6fRr_6F.js → FormItemContext-DxOutBMJ.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-AfuZCfyv.js → GenericCardRenderer-BYVNtkcd.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CAFIrGhb.js → Git-B37LKxOu.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C-NaxDs4.js → Governance-CnKcx5un.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BPfeNZP7.js → Inference-CbRAWN3_.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DDUBte_m.js → KnowledgeGraph-D0w6RfE_.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DWysA6JP.js → Logs-DxBDKpzq.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CAK68hXc.js → Marketplace-DNLi6O_3.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CNmmoDAk.js → McpTools-s0sBCPXw.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D_KmFoui.js → Memory-BdSEgpmP.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CaY-6kjj.js → MobileBridge-Dz7nz4K9.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-DakdA2PC.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-YnLrtuXT.js → Mtc-Be9OBypk.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-D38v836u.js → MtcAudit-KI2ih4Tl.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-Dv3ad9S4.js → Multisig-Cg3jHFjL.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-MYTh2eWC.js → NLProgramming-BepHx8fU.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DYO-0Hva.js → Notes-2f05HyqN.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-D39gOb3h.js → NotificationSettings-Blwio_G5.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CXdO0z3I.js → OrderTableRenderer-D7HJfvo_.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DOYTwQwt.js → Organization-Uihlz1cI.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cm-go0yU.js → Overflow-gZx8kADL.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CakGtorw.js → P2P-DqEEGqba.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-BHUi1nb5.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-C_podo04.js → Permissions-ERF2ZQ3G.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-nBbehIKp.js → PersonalDataHub-CzDy9CFB.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CGzQUoKv.js → Pipeline-Dy4x9Ejw.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BuD-Dd9m.js → Privacy-C6UJys52.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CLcFmGXU.js → ProjectInit-Bmi027fa.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BxXTR0dH.js → ProjectSettings-NinSZmGA.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DkpBjAfd.js → Projects-DeW-p-hT.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DwHK42ht.js → Providers-4hxrBTLo.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-idJPKErk.js → QuickAsk-B5inZlW8.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BvJj0Nu2.js → Recommend-7lpBm00-.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BK9l4Rcf.js → Reputation-QfdGZxYO.js} +1 -1
- package/src/assets/web-panel/assets/{Row-rLxg7xk5.js → Row-DqHC7BCT.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Cw6ZcHBY.js → RssFeed-B3F1aGP6.js} +3 -3
- package/src/assets/web-panel/assets/{Search-D8mKp506.js → Search-BXtjuoYO.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CXunojmQ.js → Security-Iy0MOV-A.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BE5DgmPl.js → Services-C_puMp_6.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CssL5wmE.js → Skeleton-Bg6mTVZU.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-zyS_1YrT.js → Skills-D4wXhanR.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DIVn2Roa.js → Sla-Cgr2B9R6.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CjNdZWdj.js → SpeechSettings-ctYqZEY5.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BCbu5WQT.js → SyncSettings-C3qWh9y4.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BjFrgXge.js → Tasks-D3AAgUx1.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Cl4Y3lKN.js → Templates-Bm_Dyz8T.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-k1T0F_eH.js → Tenant-DOSrOw9X.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-DrG0bEOS.js +3 -0
- package/src/assets/web-panel/assets/TimelineRenderer-BB8Qq6Cx.js +1 -0
- package/src/assets/web-panel/assets/{Tokens-BUWEiyab.js → Tokens-DqyF_Flh.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-9srMXwpo.js → Trigger-ClwQC2hP.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-hmGor_R2.js → Trust-DGQSLPQ0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Dst7K10a.js → UkeySign-BAkeYsUO.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D_utUv1u.js → VideoEditing-WyUxS65Y.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BpYTTseI.js → Wallet-Br7GdjEg.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CEIw8QrF.js → WebAuthn-bKs1dahj.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Bkb4bUZ_.js → WorkflowEditor-CVqAALYe.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CTqDgGRH.js → chat-BDGDRbab.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CnJHVFLk.js → colors-DIUxZwT8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D4-v0ahl.js → compact-item-C2iM4GmN.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CYDbnHZX.js → createContext-D27NeHwO.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Daz0PHhS.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CbYwlEz5.js → hasIn-D1lXdsFj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BR4facv0.js → index-B5ZB5lsV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMKNVIqJ.js → index-B6kc4-nD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cl7-mbsb.js → index-B8POWlbO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVc_5aBF.js → index-BE9bNvdn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCQ4kcL3.js → index-BIVYPlpl.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8uhiRML.js → index-BTUFhfHs.js} +1 -1
- package/src/assets/web-panel/assets/{index-qNwkSspX.js → index-BTedVe_v.js} +1 -1
- package/src/assets/web-panel/assets/{index-OqmUv5QO.js → index-Bi8u5GsP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP-UKwu8.js → index-Bil9t91t.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfshMjFN.js → index-BqbjsJcS.js} +1 -1
- package/src/assets/web-panel/assets/{index-tmNsJzQU.js → index-CAnMUhHO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOUSaCsn.js → index-CDzSUTpa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dl_NdY39.js → index-CEO9vhv7.js} +3 -3
- package/src/assets/web-panel/assets/{index-DxvFrsxl.js → index-CFEwtE3q.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2fICr5d.js → index-CFY1KjB1.js} +1 -1
- package/src/assets/web-panel/assets/{index-5NXdAIZK.js → index-CKMDgjfh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BR0FfrCL.js → index-CNk98bb1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaCCLe7i.js → index-Ca8n97h9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGUOVPjO.js → index-Cb9u9MMo.js} +1 -1
- package/src/assets/web-panel/assets/index-Cbe_eJAR.js +1 -0
- package/src/assets/web-panel/assets/{index-CxKdSB-p.js → index-Cbqr8kW6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY971z_9.js → index-CfR3Pvsu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVKfa-z4.js → index-Cgf3zMwq.js} +1 -1
- package/src/assets/web-panel/assets/{index-4767GEls.js → index-CiA-L5ld.js} +1 -1
- package/src/assets/web-panel/assets/{index-CK8nROWH.js → index-CoygRreE.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-hiXmVK.js → index-CsIw-YQF.js} +1 -1
- package/src/assets/web-panel/assets/{index-wxz2s6uW.js → index-DFhdJm7M.js} +1 -1
- package/src/assets/web-panel/assets/{index-8cmtr1jC.js → index-DK0jgmJg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BygyTsjr.js → index-Dc4k4V1u.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8MYVsVB.js → index-DfSav-yQ.js} +1 -1
- package/src/assets/web-panel/assets/index-DgcPjW1c.js +1 -0
- package/src/assets/web-panel/assets/{index-xDe5BkjU.js → index-DkOu8er7.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4chfOjw.js → index-DnCVI295.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNz3yht2.js → index-Do13fQ5W.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdRtpQFq.js → index-DrS-Mrm-.js} +1 -1
- package/src/assets/web-panel/assets/{index-B78d_-pR.js → index-ET5LYk9B.js} +1 -1
- package/src/assets/web-panel/assets/{index-YuO8gxKb.js → index-HZGu1woY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjFQuno4.js → index-Y1rkRas5.js} +1 -1
- package/src/assets/web-panel/assets/{index-dKpwhR5x.js → index-yIM-KxVm.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-qCQgpAty.js → initDefaultProps-C0Yrc6yY.js} +1 -1
- package/src/assets/web-panel/assets/{motion-SkVSGdwu.js → motion-DnOI85ek.js} +1 -1
- package/src/assets/web-panel/assets/{move-BpWxaW_x.js → move-DT9zQPZp.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DlZWM2H4.js → omit-DFtDi5Wd.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DobtUnVe.js → pickAttrs-Cobkb_rd.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CD6mYRZi.js → placementArrow-DWQDu4i-.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DvIIMTD_.js → responsiveObserve-daoQcWcX.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CVgyUfaA.js → slide-BLkzd2OV.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DrUDnjy_.js → statusUtils-Uyxi81M0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BNYQS60m.js → styleChecker-Dtw5lUcs.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DYv7Tvk0.js → useFlexGapSupport-_JpMgpdt.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DmAD2atJ.js → useFs-huwAlPD-.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DbLVKA3I.js → usePersonalDataHub-7cafj8_f.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C3WEt6_P.js → vnode-DYUSfRt7.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CRoSX_oZ.js → zoom-B-tjvmer.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/a2a.js +2 -1
- package/src/commands/bi.js +4 -3
- package/src/commands/browse.js +3 -2
- package/src/commands/codegen.js +7 -6
- package/src/commands/collab.js +2 -1
- package/src/commands/crosschain.js +5 -4
- package/src/commands/dao.js +10 -10
- package/src/commands/dbevo.js +3 -2
- package/src/commands/economy.js +8 -6
- package/src/commands/evolution.js +10 -8
- package/src/commands/export.js +2 -1
- package/src/commands/federation.js +8 -7
- package/src/commands/fusion.js +4 -3
- package/src/commands/git.js +2 -1
- package/src/commands/governance.js +6 -5
- package/src/commands/hardening.js +3 -2
- package/src/commands/hmemory.js +2 -1
- package/src/commands/inference.js +9 -8
- package/src/commands/infra.js +9 -8
- package/src/commands/instinct.js +66 -0
- package/src/commands/learning.js +66 -0
- package/src/commands/marketplace.js +5 -4
- package/src/commands/matrix.js +2 -1
- package/src/commands/memory.js +66 -0
- package/src/commands/nlprog.js +4 -3
- package/src/commands/nostr.js +4 -3
- package/src/commands/note.js +2 -1
- package/src/commands/ops.js +3 -2
- package/src/commands/perception.js +6 -5
- package/src/commands/privacy.js +13 -12
- package/src/commands/quantization.js +3 -2
- package/src/commands/recommend.js +8 -7
- package/src/commands/reputation.js +4 -3
- package/src/commands/runtime.js +6 -5
- package/src/commands/sandbox.js +5 -4
- package/src/commands/session.js +2 -1
- package/src/commands/sla.js +6 -5
- package/src/commands/social.js +6 -5
- package/src/commands/stress.js +7 -6
- package/src/commands/sync.js +2 -1
- package/src/commands/tenant.js +3 -2
- package/src/commands/trust.js +7 -6
- package/src/commands/wallet.js +2 -1
- package/src/lib/__tests__/agents.test.js +225 -0
- package/src/lib/__tests__/cowork-observe-html.test.js +141 -0
- package/src/lib/__tests__/logger.test.js +110 -0
- package/src/lib/__tests__/project-inventory.test.js +280 -0
- package/src/lib/__tests__/project-root.test.js +96 -0
- package/src/lib/__tests__/provider-stream.test.js +190 -0
- package/src/lib/cli-arg.js +41 -0
- package/src/lib/cli-numeric.js +12 -3
- package/src/lib/instinct-manager.js +64 -0
- package/src/lib/learning/learning-tables.js +83 -0
- package/src/lib/mcp-oauth.js +10 -2
- package/src/lib/memory-manager.js +67 -0
- package/src/lib/plan-mode.js +3 -2
- package/src/repl/__tests__/mcp-prompt.test.js +155 -0
- package/src/repl/__tests__/slash-macro.test.js +81 -0
- package/src/runtime/__tests__/coding-agent-contract-shared.test.js +136 -0
- package/src/runtime/__tests__/coding-agent-events.test.js +263 -0
- package/src/assets/web-panel/assets/MobileProjects-DhDxrWze.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-BlmbHJnb.js +0 -7
- package/src/assets/web-panel/assets/Terminal-i0tHF8fy.js +0 -3
- package/src/assets/web-panel/assets/TimelineRenderer-FmPaNyqK.js +0 -1
- package/src/assets/web-panel/assets/devWarning-OhxeajH6.js +0 -1
- package/src/assets/web-panel/assets/index-Bbtoi_47.js +0 -1
- package/src/assets/web-panel/assets/index-Cb8RWwaB.js +0 -1
|
@@ -20,6 +20,70 @@ export function ensureInstinctsTable(db) {
|
|
|
20
20
|
`);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** datetime('now')-style timestamp for rows missing one on import. */
|
|
24
|
+
function _instinctNowSqlite() {
|
|
25
|
+
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Export ALL instincts (full dump) for §8.3 cross-device backup. Deterministic
|
|
30
|
+
* order so the same DB exports identically.
|
|
31
|
+
*/
|
|
32
|
+
export function exportInstincts(db) {
|
|
33
|
+
ensureInstinctsTable(db);
|
|
34
|
+
return db
|
|
35
|
+
.prepare(
|
|
36
|
+
`SELECT id, category, pattern, confidence, occurrences, last_seen, created_at
|
|
37
|
+
FROM instincts ORDER BY created_at ASC, id ASC`,
|
|
38
|
+
)
|
|
39
|
+
.all();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Import instincts (§8.3 restore): idempotent upsert by id — preserves created_at,
|
|
44
|
+
* updates category/pattern/confidence/occurrences/last_seen. Never aborts mid-batch.
|
|
45
|
+
*/
|
|
46
|
+
export function importInstincts(db, rows) {
|
|
47
|
+
ensureInstinctsTable(db);
|
|
48
|
+
if (!Array.isArray(rows)) {
|
|
49
|
+
throw new Error("importInstincts: rows must be a JSON array");
|
|
50
|
+
}
|
|
51
|
+
const stmt = db.prepare(
|
|
52
|
+
`INSERT INTO instincts (id, category, pattern, confidence, occurrences, last_seen, created_at)
|
|
53
|
+
VALUES (@id, @category, @pattern, @confidence, @occurrences, @last_seen, @created_at)
|
|
54
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
55
|
+
category = excluded.category,
|
|
56
|
+
pattern = excluded.pattern,
|
|
57
|
+
confidence = excluded.confidence,
|
|
58
|
+
occurrences = excluded.occurrences,
|
|
59
|
+
last_seen = excluded.last_seen`,
|
|
60
|
+
);
|
|
61
|
+
let imported = 0;
|
|
62
|
+
let failed = 0;
|
|
63
|
+
const errors = [];
|
|
64
|
+
for (const r of rows) {
|
|
65
|
+
try {
|
|
66
|
+
if (!r || !r.id || !r.category || !r.pattern) {
|
|
67
|
+
throw new Error("invalid instinct (need id + category + pattern)");
|
|
68
|
+
}
|
|
69
|
+
stmt.run({
|
|
70
|
+
id: String(r.id),
|
|
71
|
+
category: r.category,
|
|
72
|
+
pattern: r.pattern,
|
|
73
|
+
confidence: typeof r.confidence === "number" ? r.confidence : 0.5,
|
|
74
|
+
occurrences: Number.isInteger(r.occurrences) ? r.occurrences : 1,
|
|
75
|
+
last_seen: r.last_seen || _instinctNowSqlite(),
|
|
76
|
+
created_at: r.created_at || _instinctNowSqlite(),
|
|
77
|
+
});
|
|
78
|
+
imported += 1;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
failed += 1;
|
|
81
|
+
if (errors.length < 10) errors.push({ id: r && r.id, error: e.message });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { ok: failed === 0, imported, failed, errors };
|
|
85
|
+
}
|
|
86
|
+
|
|
23
87
|
function generateId() {
|
|
24
88
|
const hex = () =>
|
|
25
89
|
Math.floor(Math.random() * 0x10000)
|
|
@@ -64,3 +64,86 @@ export function ensureLearningTables(db) {
|
|
|
64
64
|
)
|
|
65
65
|
`);
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
/** datetime('now')-style timestamp for rows missing one on import. */
|
|
69
|
+
function _learningNowSqlite() {
|
|
70
|
+
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Export ALL learning trajectories (full dump) for §8.3 cross-device backup.
|
|
75
|
+
* Includes the synthesized_skill linkage (synthesized skills travel with the
|
|
76
|
+
* trajectories that produced them). The learning_trajectory_tags side-table is
|
|
77
|
+
* derived and intentionally not exported in v1.
|
|
78
|
+
*/
|
|
79
|
+
export function exportTrajectories(db) {
|
|
80
|
+
ensureLearningTables(db);
|
|
81
|
+
return db
|
|
82
|
+
.prepare(
|
|
83
|
+
`SELECT id, session_id, user_intent, tool_chain, tool_count, final_response,
|
|
84
|
+
outcome_score, outcome_source, complexity_level, synthesized_skill,
|
|
85
|
+
created_at, completed_at
|
|
86
|
+
FROM learning_trajectories ORDER BY created_at ASC, id ASC`,
|
|
87
|
+
)
|
|
88
|
+
.all();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Import learning trajectories (§8.3 restore): idempotent upsert by id —
|
|
93
|
+
* preserves created_at, updates the rest. Never aborts mid-batch.
|
|
94
|
+
*/
|
|
95
|
+
export function importTrajectories(db, rows) {
|
|
96
|
+
ensureLearningTables(db);
|
|
97
|
+
if (!Array.isArray(rows)) {
|
|
98
|
+
throw new Error("importTrajectories: rows must be a JSON array");
|
|
99
|
+
}
|
|
100
|
+
const stmt = db.prepare(
|
|
101
|
+
`INSERT INTO learning_trajectories
|
|
102
|
+
(id, session_id, user_intent, tool_chain, tool_count, final_response,
|
|
103
|
+
outcome_score, outcome_source, complexity_level, synthesized_skill,
|
|
104
|
+
created_at, completed_at)
|
|
105
|
+
VALUES (@id, @session_id, @user_intent, @tool_chain, @tool_count, @final_response,
|
|
106
|
+
@outcome_score, @outcome_source, @complexity_level, @synthesized_skill,
|
|
107
|
+
@created_at, @completed_at)
|
|
108
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
109
|
+
session_id = excluded.session_id,
|
|
110
|
+
user_intent = excluded.user_intent,
|
|
111
|
+
tool_chain = excluded.tool_chain,
|
|
112
|
+
tool_count = excluded.tool_count,
|
|
113
|
+
final_response = excluded.final_response,
|
|
114
|
+
outcome_score = excluded.outcome_score,
|
|
115
|
+
outcome_source = excluded.outcome_source,
|
|
116
|
+
complexity_level = excluded.complexity_level,
|
|
117
|
+
synthesized_skill = excluded.synthesized_skill,
|
|
118
|
+
completed_at = excluded.completed_at`,
|
|
119
|
+
);
|
|
120
|
+
let imported = 0;
|
|
121
|
+
let failed = 0;
|
|
122
|
+
const errors = [];
|
|
123
|
+
for (const r of rows) {
|
|
124
|
+
try {
|
|
125
|
+
if (!r || !r.id || !r.session_id) {
|
|
126
|
+
throw new Error("invalid trajectory (need id + session_id)");
|
|
127
|
+
}
|
|
128
|
+
stmt.run({
|
|
129
|
+
id: String(r.id),
|
|
130
|
+
session_id: String(r.session_id),
|
|
131
|
+
user_intent: r.user_intent ?? null,
|
|
132
|
+
tool_chain: r.tool_chain ?? "[]",
|
|
133
|
+
tool_count: Number.isInteger(r.tool_count) ? r.tool_count : 0,
|
|
134
|
+
final_response: r.final_response ?? null,
|
|
135
|
+
outcome_score: typeof r.outcome_score === "number" ? r.outcome_score : null,
|
|
136
|
+
outcome_source: r.outcome_source ?? null,
|
|
137
|
+
complexity_level: r.complexity_level ?? "simple",
|
|
138
|
+
synthesized_skill: r.synthesized_skill ?? null,
|
|
139
|
+
created_at: r.created_at || _learningNowSqlite(),
|
|
140
|
+
completed_at: r.completed_at ?? null,
|
|
141
|
+
});
|
|
142
|
+
imported += 1;
|
|
143
|
+
} catch (e) {
|
|
144
|
+
failed += 1;
|
|
145
|
+
if (errors.length < 10) errors.push({ id: r && r.id, error: e.message });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { ok: failed === 0, imported, failed, errors };
|
|
149
|
+
}
|
package/src/lib/mcp-oauth.js
CHANGED
|
@@ -410,7 +410,7 @@ function defaultOpenBrowser(url) {
|
|
|
410
410
|
}
|
|
411
411
|
|
|
412
412
|
/** Wait for the OAuth redirect on a localhost callback server; resolve {code,state}. */
|
|
413
|
-
function waitForCallback({
|
|
413
|
+
export function waitForCallback({
|
|
414
414
|
port,
|
|
415
415
|
host = "127.0.0.1",
|
|
416
416
|
path = "/callback",
|
|
@@ -446,7 +446,15 @@ function waitForCallback({
|
|
|
446
446
|
server.close();
|
|
447
447
|
reject(new Error("timed out waiting for the OAuth callback"));
|
|
448
448
|
}, timeout);
|
|
449
|
-
|
|
449
|
+
// Backstop timer should never keep the process alive on its own (the
|
|
450
|
+
// listening server does that); the loop exits promptly once it closes.
|
|
451
|
+
timer.unref?.();
|
|
452
|
+
server.on("error", (err) => {
|
|
453
|
+
// Without clearing the timer here, a bind failure (e.g. port in use)
|
|
454
|
+
// left it pending for the full `timeout`, delaying process exit.
|
|
455
|
+
clearTimeout(timer);
|
|
456
|
+
reject(err);
|
|
457
|
+
});
|
|
450
458
|
server.listen(port, host);
|
|
451
459
|
});
|
|
452
460
|
}
|
|
@@ -133,6 +133,73 @@ export function listMemory(db, options = {}) {
|
|
|
133
133
|
.all(limit);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Export ALL memory entries (full dump, no limit) for §8.3 cross-device backup.
|
|
138
|
+
* Returns rows with id/content/category/importance/source/created_at/updated_at,
|
|
139
|
+
* ordered deterministically so the same DB exports identically.
|
|
140
|
+
*/
|
|
141
|
+
export function exportMemory(db) {
|
|
142
|
+
ensureMemoryTable(db);
|
|
143
|
+
return db
|
|
144
|
+
.prepare(
|
|
145
|
+
`SELECT id, content, category, importance, source, created_at, updated_at
|
|
146
|
+
FROM memory_entries ORDER BY created_at ASC, id ASC`,
|
|
147
|
+
)
|
|
148
|
+
.all();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** datetime('now')-style timestamp for entries missing one on import. */
|
|
152
|
+
function _nowSqlite() {
|
|
153
|
+
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Import memory entries (§8.3 backup restore): idempotent upsert by id —
|
|
158
|
+
* preserves the original created_at, updates content/category/importance/source/
|
|
159
|
+
* updated_at. Never aborts mid-batch; returns {ok, imported, failed, errors}.
|
|
160
|
+
*/
|
|
161
|
+
export function importMemory(db, entries) {
|
|
162
|
+
ensureMemoryTable(db);
|
|
163
|
+
if (!Array.isArray(entries)) {
|
|
164
|
+
throw new Error("importMemory: entries must be a JSON array");
|
|
165
|
+
}
|
|
166
|
+
const stmt = db.prepare(
|
|
167
|
+
`INSERT INTO memory_entries
|
|
168
|
+
(id, content, category, importance, source, created_at, updated_at)
|
|
169
|
+
VALUES (@id, @content, @category, @importance, @source, @created_at, @updated_at)
|
|
170
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
171
|
+
content = excluded.content,
|
|
172
|
+
category = excluded.category,
|
|
173
|
+
importance = excluded.importance,
|
|
174
|
+
source = excluded.source,
|
|
175
|
+
updated_at = excluded.updated_at`,
|
|
176
|
+
);
|
|
177
|
+
let imported = 0;
|
|
178
|
+
let failed = 0;
|
|
179
|
+
const errors = [];
|
|
180
|
+
for (const e of entries) {
|
|
181
|
+
try {
|
|
182
|
+
if (!e || !e.id || typeof e.content !== "string") {
|
|
183
|
+
throw new Error("invalid memory entry (need id + content)");
|
|
184
|
+
}
|
|
185
|
+
stmt.run({
|
|
186
|
+
id: String(e.id),
|
|
187
|
+
content: e.content,
|
|
188
|
+
category: e.category || "general",
|
|
189
|
+
importance: Number.isInteger(e.importance) ? e.importance : 3,
|
|
190
|
+
source: e.source || "user",
|
|
191
|
+
created_at: e.created_at || _nowSqlite(),
|
|
192
|
+
updated_at: e.updated_at || _nowSqlite(),
|
|
193
|
+
});
|
|
194
|
+
imported += 1;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
failed += 1;
|
|
197
|
+
if (errors.length < 10) errors.push({ id: e && e.id, error: err.message });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { ok: failed === 0, imported, failed, errors };
|
|
201
|
+
}
|
|
202
|
+
|
|
136
203
|
/**
|
|
137
204
|
* Delete a memory entry
|
|
138
205
|
*/
|
package/src/lib/plan-mode.js
CHANGED
|
@@ -31,6 +31,7 @@ export const PlanState = {
|
|
|
31
31
|
APPROVED: "approved",
|
|
32
32
|
EXECUTING: "executing",
|
|
33
33
|
COMPLETED: "completed",
|
|
34
|
+
FAILED: "failed",
|
|
34
35
|
REJECTED: "rejected",
|
|
35
36
|
};
|
|
36
37
|
|
|
@@ -499,10 +500,10 @@ export class PlanModeManager extends EventEmitter {
|
|
|
499
500
|
});
|
|
500
501
|
|
|
501
502
|
const allDone = results.every((r) => r.success);
|
|
502
|
-
this.state = allDone ? PlanState.COMPLETED : PlanState.
|
|
503
|
+
this.state = allDone ? PlanState.COMPLETED : PlanState.FAILED;
|
|
503
504
|
this.currentPlan.status = allDone
|
|
504
505
|
? PlanState.COMPLETED
|
|
505
|
-
: PlanState.
|
|
506
|
+
: PlanState.FAILED;
|
|
506
507
|
|
|
507
508
|
return { results, success: allDone };
|
|
508
509
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* mcp-prompt tests (previously untested) — the pure core that surfaces MCP
|
|
5
|
+
* server prompts as `/mcp__<server>__<prompt>` slash commands. Covers
|
|
6
|
+
* parseMcpPromptCommand (server/name split incl. `__` in names, JSON-object args
|
|
7
|
+
* vs non-JSON tail, reject non-mcp / missing parts), renderPromptMessages
|
|
8
|
+
* (text + embedded resource text, ignore binary, single vs array content),
|
|
9
|
+
* expandMcpPrompt (parse -> getPrompt -> render, null for non-mcp, throw when no
|
|
10
|
+
* client) and renderMcpSurface (overview / empty / no-client). No I/O.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, vi } from "vitest";
|
|
14
|
+
import {
|
|
15
|
+
parseMcpPromptCommand,
|
|
16
|
+
renderPromptMessages,
|
|
17
|
+
expandMcpPrompt,
|
|
18
|
+
renderMcpSurface,
|
|
19
|
+
} from "../mcp-prompt.js";
|
|
20
|
+
|
|
21
|
+
describe("parseMcpPromptCommand", () => {
|
|
22
|
+
it("returns null for non-mcp lines", () => {
|
|
23
|
+
expect(parseMcpPromptCommand("hello")).toBe(null);
|
|
24
|
+
expect(parseMcpPromptCommand("/other__a__b")).toBe(null);
|
|
25
|
+
expect(parseMcpPromptCommand("")).toBe(null);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("parses server and prompt name with empty args", () => {
|
|
29
|
+
expect(parseMcpPromptCommand("/mcp__github__review")).toEqual({
|
|
30
|
+
server: "github",
|
|
31
|
+
name: "review",
|
|
32
|
+
args: {},
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("keeps '__' inside the prompt name", () => {
|
|
37
|
+
expect(parseMcpPromptCommand("/mcp__srv__deep__name").name).toBe(
|
|
38
|
+
"deep__name",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("parses a trailing JSON object as args", () => {
|
|
43
|
+
expect(parseMcpPromptCommand('/mcp__s__p {"a":1,"b":"x"}').args).toEqual({
|
|
44
|
+
a: 1,
|
|
45
|
+
b: "x",
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("treats a non-JSON tail as { input: tail }", () => {
|
|
50
|
+
expect(parseMcpPromptCommand("/mcp__s__p just some text").args).toEqual({
|
|
51
|
+
input: "just some text",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("treats a JSON array tail as { input } (not an object)", () => {
|
|
56
|
+
expect(parseMcpPromptCommand("/mcp__s__p [1,2]").args).toEqual({
|
|
57
|
+
input: "[1,2]",
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns null when server or prompt is missing", () => {
|
|
62
|
+
expect(parseMcpPromptCommand("/mcp__srv")).toBe(null); // < 3 parts
|
|
63
|
+
expect(parseMcpPromptCommand("/mcp____p")).toBe(null); // empty server
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("renderPromptMessages", () => {
|
|
68
|
+
it("returns '' when there are no messages", () => {
|
|
69
|
+
expect(renderPromptMessages(null)).toBe("");
|
|
70
|
+
expect(renderPromptMessages({ messages: [] })).toBe("");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("joins text blocks with blank lines", () => {
|
|
74
|
+
const r = renderPromptMessages({
|
|
75
|
+
messages: [
|
|
76
|
+
{ content: [{ type: "text", text: "one" }] },
|
|
77
|
+
{ content: [{ type: "text", text: "two" }] },
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
expect(r).toBe("one\n\ntwo");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("includes embedded resource text and ignores binary/unknown blocks", () => {
|
|
84
|
+
const r = renderPromptMessages({
|
|
85
|
+
messages: [
|
|
86
|
+
{
|
|
87
|
+
content: [
|
|
88
|
+
{ type: "text", text: "T" },
|
|
89
|
+
{ type: "resource", resource: { text: "R" } },
|
|
90
|
+
{ type: "image", data: "..." },
|
|
91
|
+
null,
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
expect(r).toBe("T\n\nR");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("handles content given as a single block (not an array)", () => {
|
|
100
|
+
const r = renderPromptMessages({
|
|
101
|
+
messages: [{ content: { type: "text", text: "solo" } }],
|
|
102
|
+
});
|
|
103
|
+
expect(r).toBe("solo");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("expandMcpPrompt", () => {
|
|
108
|
+
it("returns null for a non-mcp line", async () => {
|
|
109
|
+
expect(await expandMcpPrompt("hi", {})).toBe(null);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("throws when no MCP client is connected", async () => {
|
|
113
|
+
await expect(expandMcpPrompt("/mcp__s__p", null)).rejects.toThrow(
|
|
114
|
+
/No MCP servers/,
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("calls getPrompt with parsed args and renders the result", async () => {
|
|
119
|
+
const getPrompt = vi.fn(async () => ({
|
|
120
|
+
messages: [{ content: [{ type: "text", text: "rendered" }] }],
|
|
121
|
+
}));
|
|
122
|
+
const out = await expandMcpPrompt('/mcp__s__p {"k":1}', { getPrompt });
|
|
123
|
+
expect(getPrompt).toHaveBeenCalledWith("s", "p", { k: 1 });
|
|
124
|
+
expect(out).toBe("rendered");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("renderMcpSurface", () => {
|
|
129
|
+
it("reports when no client is connected", () => {
|
|
130
|
+
expect(renderMcpSurface(null)).toMatch(/No MCP servers/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("lists resources and prompts with counts", () => {
|
|
134
|
+
const surface = renderMcpSurface({
|
|
135
|
+
listResources: () => [{ uri: "file://x", server: "fs", name: "X" }],
|
|
136
|
+
listPrompts: () => [
|
|
137
|
+
{ server: "gh", name: "review", description: "code review" },
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
expect(surface).toContain("MCP resources (1):");
|
|
141
|
+
expect(surface).toContain("file://x [fs] — X");
|
|
142
|
+
expect(surface).toContain("MCP prompts (1):");
|
|
143
|
+
expect(surface).toContain("/mcp__gh__review — code review");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("notes when servers expose nothing", () => {
|
|
147
|
+
const surface = renderMcpSurface({
|
|
148
|
+
listResources: () => [],
|
|
149
|
+
listPrompts: () => [],
|
|
150
|
+
});
|
|
151
|
+
expect(surface).toContain(
|
|
152
|
+
"(connected servers expose no resources or prompts)",
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* resolveSlashMacro tests (previously untested) — the REPL's user-command-macro
|
|
5
|
+
* wire (.claude/commands/*.md). The routing safety matters: a leading `/name`
|
|
6
|
+
* that resolves to a macro is expanded, but plain text or an unknown `/...`
|
|
7
|
+
* (e.g. asking about "/etc/hosts") must pass through verbatim to the LLM and
|
|
8
|
+
* never be swallowed. Deps (getCommand/expandCommand) are injected; no I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, vi } from "vitest";
|
|
12
|
+
import { resolveSlashMacro } from "../slash-macro.js";
|
|
13
|
+
|
|
14
|
+
const deps = (macro, expand = { prompt: "EXPANDED", warnings: [] }) => ({
|
|
15
|
+
getCommand: vi.fn(() => macro),
|
|
16
|
+
expandCommand: vi.fn(() => expand),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("resolveSlashMacro", () => {
|
|
20
|
+
it("returns plain text unchanged (no slash)", async () => {
|
|
21
|
+
const r = await resolveSlashMacro(" just a question ", {
|
|
22
|
+
deps: deps(null),
|
|
23
|
+
});
|
|
24
|
+
expect(r.matched).toBe(false);
|
|
25
|
+
expect(r.promptText).toBe("just a question"); // trimmed
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("passes an unknown /... through verbatim (does not swallow /etc/hosts)", async () => {
|
|
29
|
+
const d = deps(null);
|
|
30
|
+
const r = await resolveSlashMacro("/etc/hosts is interesting", { deps: d });
|
|
31
|
+
expect(r.matched).toBe(false);
|
|
32
|
+
expect(r.promptText).toBe("/etc/hosts is interesting");
|
|
33
|
+
// first token after the slash was probed, but no macro matched
|
|
34
|
+
expect(d.getCommand).toHaveBeenCalledWith("etc/hosts", expect.any(String));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("expands a resolved macro and reports its name/scope", async () => {
|
|
38
|
+
const macro = { name: "deploy", scope: "project" };
|
|
39
|
+
const d = deps(macro);
|
|
40
|
+
const r = await resolveSlashMacro("/deploy prod fast", {
|
|
41
|
+
deps: d,
|
|
42
|
+
cwd: "/proj",
|
|
43
|
+
});
|
|
44
|
+
expect(r.matched).toBe(true);
|
|
45
|
+
expect(r.promptText).toBe("EXPANDED");
|
|
46
|
+
expect(r.name).toBe("deploy");
|
|
47
|
+
expect(r.scope).toBe("project");
|
|
48
|
+
expect(d.getCommand).toHaveBeenCalledWith("deploy", "/proj");
|
|
49
|
+
expect(d.expandCommand).toHaveBeenCalledWith(macro, ["prod", "fast"], {
|
|
50
|
+
cwd: "/proj",
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("propagates warnings from expandCommand", async () => {
|
|
55
|
+
const d = deps(
|
|
56
|
+
{ name: "x", scope: "personal" },
|
|
57
|
+
{
|
|
58
|
+
prompt: "P",
|
|
59
|
+
warnings: ["missing $2"],
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
const r = await resolveSlashMacro("/x a", { deps: d });
|
|
63
|
+
expect(r.warnings).toEqual(["missing $2"]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("does not treat a bare '/' as a command", async () => {
|
|
67
|
+
const d = deps({ name: "should-not-fire", scope: "project" });
|
|
68
|
+
const r = await resolveSlashMacro("/", { deps: d });
|
|
69
|
+
expect(r.matched).toBe(false);
|
|
70
|
+
expect(d.getCommand).not.toHaveBeenCalled(); // empty head → never probed
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("handles empty/nullish input", async () => {
|
|
74
|
+
expect((await resolveSlashMacro("", { deps: deps(null) })).matched).toBe(
|
|
75
|
+
false,
|
|
76
|
+
);
|
|
77
|
+
expect(
|
|
78
|
+
(await resolveSlashMacro(null, { deps: deps(null) })).promptText,
|
|
79
|
+
).toBe("");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* coding-agent-contract-shared tests (previously untested) — the single source
|
|
5
|
+
* of truth for the coding agent's tool surface (the function-calling contracts
|
|
6
|
+
* the LLM sees). Rather than hardcode the whole table, this pins the accessor
|
|
7
|
+
* contracts + cross-table invariants: every listed name resolves to a contract
|
|
8
|
+
* AND a function definition, lookups return defensive clones, unknown -> null,
|
|
9
|
+
* the OpenAI function-tool shape, names/tier filtering, MVP/extension membership
|
|
10
|
+
* and the policy projection. Anchored on the stable `read_file` tool. No I/O.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect } from "vitest";
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const {
|
|
18
|
+
CODING_AGENT_MVP_TOOL_NAMES,
|
|
19
|
+
CODING_AGENT_EXTENSION_TOOL_NAMES,
|
|
20
|
+
getCodingAgentFunctionToolDefinition,
|
|
21
|
+
getCodingAgentFunctionToolDefinitions,
|
|
22
|
+
getCodingAgentToolContract,
|
|
23
|
+
getCodingAgentToolContracts,
|
|
24
|
+
getCodingAgentToolPolicy,
|
|
25
|
+
isCodingAgentMvpTool,
|
|
26
|
+
listCodingAgentToolNames,
|
|
27
|
+
} = require("../coding-agent-contract-shared.cjs");
|
|
28
|
+
|
|
29
|
+
describe("listCodingAgentToolNames + consistency", () => {
|
|
30
|
+
it("returns a non-empty list of unique names including read_file", () => {
|
|
31
|
+
const names = listCodingAgentToolNames();
|
|
32
|
+
expect(names.length).toBeGreaterThan(0);
|
|
33
|
+
expect(new Set(names).size).toBe(names.length);
|
|
34
|
+
expect(names).toContain("read_file");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("every listed name resolves to both a contract and a function definition", () => {
|
|
38
|
+
for (const n of listCodingAgentToolNames()) {
|
|
39
|
+
expect(getCodingAgentToolContract(n)).toBeTruthy();
|
|
40
|
+
expect(getCodingAgentFunctionToolDefinition(n)).toBeTruthy();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("getCodingAgentToolContract", () => {
|
|
46
|
+
it("returns the contract for a known tool", () => {
|
|
47
|
+
const c = getCodingAgentToolContract("read_file");
|
|
48
|
+
expect(c.name).toBe("read_file");
|
|
49
|
+
expect(c.tier).toBe("mvp");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns null for an unknown tool", () => {
|
|
53
|
+
expect(getCodingAgentToolContract("__nope__")).toBe(null);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns a defensive clone (mutation does not leak back)", () => {
|
|
57
|
+
const a = getCodingAgentToolContract("read_file");
|
|
58
|
+
a.name = "MUTATED";
|
|
59
|
+
expect(getCodingAgentToolContract("read_file").name).toBe("read_file");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("getCodingAgentFunctionToolDefinition", () => {
|
|
64
|
+
it("emits an OpenAI function-tool definition for a known tool", () => {
|
|
65
|
+
const def = getCodingAgentFunctionToolDefinition("read_file");
|
|
66
|
+
expect(def.type).toBe("function");
|
|
67
|
+
expect(def.function.name).toBe("read_file");
|
|
68
|
+
expect(typeof def.function.description).toBe("string");
|
|
69
|
+
expect(def.function.parameters.type).toBe("object");
|
|
70
|
+
expect(def.function.parameters.required).toContain("path");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("returns null for an unknown tool", () => {
|
|
74
|
+
expect(getCodingAgentFunctionToolDefinition("__nope__")).toBe(null);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("getCodingAgentFunctionToolDefinitions filtering", () => {
|
|
79
|
+
it("filters by an explicit names allow-list", () => {
|
|
80
|
+
const defs = getCodingAgentFunctionToolDefinitions({
|
|
81
|
+
names: ["read_file"],
|
|
82
|
+
});
|
|
83
|
+
expect(defs).toHaveLength(1);
|
|
84
|
+
expect(defs[0].function.name).toBe("read_file");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("treats an empty names array as no filter (returns all)", () => {
|
|
88
|
+
const all = getCodingAgentFunctionToolDefinitions();
|
|
89
|
+
expect(getCodingAgentFunctionToolDefinitions({ names: [] })).toHaveLength(
|
|
90
|
+
all.length,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns [] for unknown names", () => {
|
|
95
|
+
expect(
|
|
96
|
+
getCodingAgentFunctionToolDefinitions({ names: ["__nope__"] }),
|
|
97
|
+
).toHaveLength(0);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("tier filtering", () => {
|
|
102
|
+
it("getCodingAgentToolContracts({tier}) returns only that tier", () => {
|
|
103
|
+
const mvp = getCodingAgentToolContracts({ tier: "mvp" });
|
|
104
|
+
expect(mvp.length).toBeGreaterThan(0);
|
|
105
|
+
expect(mvp.every((t) => t.tier === "mvp")).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("isCodingAgentMvpTool + membership", () => {
|
|
110
|
+
it("is true for every MVP name and false for unknown", () => {
|
|
111
|
+
for (const n of CODING_AGENT_MVP_TOOL_NAMES)
|
|
112
|
+
expect(isCodingAgentMvpTool(n)).toBe(true);
|
|
113
|
+
expect(isCodingAgentMvpTool("__nope__")).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("MVP and extension names are all valid tool names", () => {
|
|
117
|
+
const all = new Set(listCodingAgentToolNames());
|
|
118
|
+
for (const n of CODING_AGENT_MVP_TOOL_NAMES) expect(all.has(n)).toBe(true);
|
|
119
|
+
for (const n of CODING_AGENT_EXTENSION_TOOL_NAMES)
|
|
120
|
+
expect(all.has(n)).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("getCodingAgentToolPolicy", () => {
|
|
125
|
+
it("projects the policy fields for a known tool", () => {
|
|
126
|
+
const p = getCodingAgentToolPolicy("read_file");
|
|
127
|
+
expect(p).toHaveProperty("tier");
|
|
128
|
+
expect(p).toHaveProperty("riskLevel");
|
|
129
|
+
expect(p).toHaveProperty("permissions");
|
|
130
|
+
expect(typeof p.planModeBehavior).toBe("string"); // defaults to "standard"
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns null for an unknown tool", () => {
|
|
134
|
+
expect(getCodingAgentToolPolicy("__nope__")).toBe(null);
|
|
135
|
+
});
|
|
136
|
+
});
|