lousho 1.0.0-alpha.31 → 1.0.0-alpha.34
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/CHANGELOG.md +728 -712
- package/CONTRIBUTING.md +141 -141
- package/LICENSE +21 -21
- package/README.md +113 -373
- package/SECURITY.md +70 -70
- package/apps/agent-forge/dist/assets/index-Cz-JDmAH.css +1 -0
- package/apps/agent-forge/dist/assets/index-XM7i4Req.js +954 -0
- package/apps/agent-forge/dist/index.html +21 -21
- package/apps/agent-forge/dist-server/index.cjs +6796 -947
- package/apps/agent-forge/package.json +61 -59
- package/bin/lousho.js +0 -0
- package/dist/SqliteStore-3ZXXF3IQ.mjs +15 -0
- package/dist/SqliteStore-ZAYFLLC4.js +24 -0
- package/dist/{ToolRegistry-wFNmj1r-.d.mts → ToolRegistry-CMZH-QbP.d.mts} +1 -1
- package/dist/{ToolRegistry-E9Tc2rV9.d.ts → ToolRegistry-DlntOsTk.d.ts} +1 -1
- package/dist/{agentStore-DMMzuCTt.d.ts → agentStore-CeWkX4v9.d.ts} +3 -3
- package/dist/{agentStore-BCZx0uYB.d.mts → agentStore-pcNQZ0Rg.d.mts} +3 -3
- package/dist/{approvalPolicies-jiu0M9AU.d.ts → approvalPolicies-B_7cDrij.d.ts} +1 -1
- package/dist/{approvalPolicies-BT_SE3W1.d.mts → approvalPolicies-akA5u7dA.d.mts} +1 -1
- package/dist/auth/index.js +10 -10
- package/dist/auth/index.mjs +3 -3
- package/dist/{checkpoint-CUQ2QAYh.d.mts → checkpoint-DD1Rwn7p.d.mts} +5 -5
- package/dist/{checkpoint-CQNJqLX7.d.ts → checkpoint-DWLRsUxE.d.ts} +5 -5
- package/dist/{chunk-RDFMS3I5.mjs → chunk-22GLUPMJ.mjs} +2 -2
- package/dist/{chunk-RRW2NL6M.mjs → chunk-23IWIKE5.mjs} +2 -2
- package/dist/{chunk-XSZGVL35.js → chunk-2I5GH4LO.js} +11 -11
- package/dist/{chunk-ZMTNJXY7.mjs → chunk-2L43LYYC.mjs} +1 -1
- package/dist/{chunk-D6JQ65F2.js → chunk-2QNMCC3N.js} +16 -16
- package/dist/chunk-2XB3UWJ4.js +2505 -0
- package/dist/{chunk-3JI3SLYH.mjs → chunk-32I456HL.mjs} +1 -1
- package/dist/chunk-33KGFSWU.mjs +37 -0
- package/dist/{chunk-UOBAMFF3.js → chunk-35VYXADZ.js} +2 -2
- package/dist/{chunk-K7L2IHHL.js → chunk-3DL23T34.js} +2 -2
- package/dist/{chunk-BQLSEGNR.js → chunk-3GJ3VOYA.js} +4 -4
- package/dist/{chunk-CIIRP2XV.mjs → chunk-3H4TQJQH.mjs} +6 -37
- package/dist/{chunk-ZXUKVO2R.mjs → chunk-3KR4YPNC.mjs} +1 -14
- package/dist/chunk-3QUZ5CIW.mjs +47 -0
- package/dist/{chunk-EC5SN2Y6.mjs → chunk-3TX2DXKZ.mjs} +6 -204
- package/dist/{chunk-LM5734H6.mjs → chunk-3UZYIEIL.mjs} +4 -72
- package/dist/{chunk-TQIHIANT.mjs → chunk-4AO4C27V.mjs} +300 -353
- package/dist/chunk-4JAFEXVK.js +224 -0
- package/dist/chunk-4W3QDJMS.mjs +1449 -0
- package/dist/chunk-4Y7CH5WZ.js +17 -0
- package/dist/chunk-5FLCBF7Q.js +140 -0
- package/dist/{chunk-NAGJZG3H.js → chunk-5IAHDETO.js} +11 -151
- package/dist/chunk-5IFV4FGC.mjs +55 -0
- package/dist/{chunk-7Z6IGY5D.mjs → chunk-5KR47N6J.mjs} +1 -1
- package/dist/{chunk-EVPTMIWN.mjs → chunk-5O5HCUHB.mjs} +1 -1
- package/dist/{chunk-HSNOGS2G.js → chunk-5QSQL4NW.js} +25 -126
- package/dist/chunk-5X25FOTM.js +96 -0
- package/dist/{chunk-2QQIZTXB.mjs → chunk-65C7EZWQ.mjs} +3 -136
- package/dist/{chunk-5BIXVKG5.js → chunk-6C2RCXZC.js} +3 -3
- package/dist/chunk-6LA5JT56.mjs +71 -0
- package/dist/{chunk-YXXDYESY.mjs → chunk-6LIQY5QQ.mjs} +1 -1
- package/dist/chunk-6PSPZQ2O.js +75 -0
- package/dist/{chunk-RRIUY2QN.mjs → chunk-6RKBYNHP.mjs} +3 -14
- package/dist/{chunk-EAPQR2MX.mjs → chunk-7357KKWS.mjs} +2 -72
- package/dist/chunk-7LGRJHEW.mjs +595 -0
- package/dist/{chunk-QS6MZJFC.js → chunk-7U5PXNYQ.js} +0 -15
- package/dist/chunk-AHGSLGVO.js +52 -0
- package/dist/{chunk-HMEYMWCX.js → chunk-AQ3675FI.js} +12 -12
- package/dist/{chunk-D7E6LI6Y.mjs → chunk-B6CUTUK3.mjs} +5 -5
- package/dist/{chunk-D7KKXNGV.js → chunk-BAX2O2IU.js} +6 -6
- package/dist/{chunk-OJNBYWJR.mjs → chunk-BHEHO6LW.mjs} +1 -1
- package/dist/{chunk-CZKCRCHF.js → chunk-BW3DBIX3.js} +11 -10
- package/dist/{chunk-MZPMLWOK.js → chunk-BX46D7Q2.js} +1 -1
- package/dist/{chunk-DI3O6FNP.js → chunk-CILSQTD3.js} +1381 -79
- package/dist/{chunk-F225EGE6.mjs → chunk-DDIKYVQL.mjs} +3 -3
- package/dist/{chunk-H35QCG2N.js → chunk-DM7GH35M.js} +11 -11
- package/dist/{chunk-KEHT6ZXW.js → chunk-DQM7BS6Y.js} +5 -5
- package/dist/{chunk-OZBHLG66.mjs → chunk-E7WBKGQA.mjs} +60 -40
- package/dist/chunk-EIPOBLZV.js +264 -0
- package/dist/{chunk-DR4UZII6.js → chunk-EMTLHU3D.js} +17 -17
- package/dist/chunk-EWLEZZ3L.mjs +246 -0
- package/dist/{chunk-PGXNHYWM.js → chunk-EZEP62CM.js} +42 -19
- package/dist/{chunk-7DAUAMBJ.js → chunk-F752KJQT.js} +2 -2
- package/dist/{chunk-QQLYDMKO.mjs → chunk-FCSMZATF.mjs} +1 -0
- package/dist/{chunk-56IEO3FR.js → chunk-FSEHQAKJ.js} +31 -31
- package/dist/chunk-FYSDJCSZ.js +111 -0
- package/dist/{chunk-QC6233PH.mjs → chunk-GHLLE5NF.mjs} +2 -2
- package/dist/{chunk-XQUOMPH3.js → chunk-GLJLH2JK.js} +5 -5
- package/dist/{chunk-XJGPXK4A.js → chunk-GLSJFARB.js} +12 -12
- package/dist/{chunk-BX73PWPF.mjs → chunk-GSDYMYAN.mjs} +2 -2
- package/dist/{chunk-QS2TKSZR.mjs → chunk-GY4FUCU2.mjs} +2 -0
- package/dist/chunk-GYZYQEGO.mjs +32 -0
- package/dist/{chunk-CSJ62MMP.mjs → chunk-H2J5E645.mjs} +1 -1
- package/dist/{chunk-3C63ZD2M.mjs → chunk-H4VQPFZ2.mjs} +1 -1
- package/dist/chunk-HCF6EVNN.mjs +103 -0
- package/dist/chunk-HLCIDJF5.mjs +92 -0
- package/dist/chunk-HTCYDJKJ.mjs +900 -0
- package/dist/chunk-JQDE6UCL.mjs +13 -0
- package/dist/{chunk-5FBSUMP6.mjs → chunk-JQFSOPC3.mjs} +1 -1
- package/dist/{chunk-QA7HYTBN.js → chunk-JTAFYOEI.js} +3 -3
- package/dist/chunk-K2HNGQK4.js +142 -0
- package/dist/{chunk-JPUMHHZE.mjs → chunk-K6P6WHFU.mjs} +2 -2
- package/dist/chunk-KGN4OO3M.mjs +204 -0
- package/dist/{chunk-6XU4HBID.js → chunk-LA47BOBC.js} +7 -7
- package/dist/chunk-LERJKVHM.js +791 -0
- package/dist/chunk-M45KDCV5.js +34 -0
- package/dist/{chunk-VTWQ752U.js → chunk-MVB62PTT.js} +19 -91
- package/dist/{chunk-IPOLTKQF.mjs → chunk-NFWWLPZN.mjs} +2 -2
- package/dist/{chunk-OYZHGVFH.mjs → chunk-NKUFFBCP.mjs} +1277 -15
- package/dist/{chunk-RU2YU46G.js → chunk-NMR27CF7.js} +74 -74
- package/dist/{chunk-ODGXWBEF.mjs → chunk-NRBR4E5W.mjs} +4 -4
- package/dist/{chunk-VJ5LFVO7.js → chunk-O7P7FFA5.js} +2 -2
- package/dist/{chunk-VVBCVGHK.mjs → chunk-ODDCPWAF.mjs} +2 -2
- package/dist/{chunk-X2IJ2SNW.js → chunk-PEN23K2V.js} +420 -476
- package/dist/chunk-POPIJAQR.js +16 -0
- package/dist/{chunk-NLASVQTS.mjs → chunk-POSCCRRJ.mjs} +27 -4
- package/dist/{chunk-2Z2SBL2C.js → chunk-PWI2SWRY.js} +2 -0
- package/dist/chunk-QB32Z77K.mjs +2443 -0
- package/dist/{chunk-BEEADJZP.js → chunk-QBC3DYM5.js} +5 -17
- package/dist/{chunk-CE3SUIY3.mjs → chunk-QK7STEIU.mjs} +1 -1
- package/dist/chunk-QKFEISEM.js +926 -0
- package/dist/{chunk-ET54KC7H.mjs → chunk-QLWXWNCP.mjs} +3 -3
- package/dist/{chunk-55DHJSAA.mjs → chunk-QWR3YGLK.mjs} +8 -103
- package/dist/chunk-R45L455L.mjs +138 -0
- package/dist/{chunk-4ZPWS2D2.mjs → chunk-R6GTGN44.mjs} +5 -4
- package/dist/chunk-RHZE2M4R.mjs +210 -0
- package/dist/chunk-RKY5Z4O4.js +597 -0
- package/dist/{chunk-XC4QULWN.mjs → chunk-RNP6AOL4.mjs} +1 -1
- package/dist/{chunk-ETHXGB4U.js → chunk-RPVTDLIO.js} +18 -18
- package/dist/{chunk-JYYISLUC.js → chunk-RWI2XZCS.js} +2 -2
- package/dist/chunk-S4M6L4EL.mjs +73 -0
- package/dist/{chunk-MIWBLDNC.mjs → chunk-SBJ34H33.mjs} +1 -1
- package/dist/chunk-SHJ7WL7O.mjs +71 -0
- package/dist/chunk-SKC25QZW.mjs +131 -0
- package/dist/{chunk-UZU2ZFDT.mjs → chunk-SOT2H2YP.mjs} +3 -3
- package/dist/chunk-T3O4P36Y.mjs +786 -0
- package/dist/chunk-T4UJ4PNK.js +248 -0
- package/dist/chunk-TBDQ7AOK.js +76 -0
- package/dist/chunk-TPBDB65M.js +78 -0
- package/dist/{chunk-7DIN2MUF.js → chunk-TUV55EMM.js} +14 -14
- package/dist/{chunk-UDJCPHDJ.mjs → chunk-UCLOL2IF.mjs} +1 -1
- package/dist/{chunk-QBQM23RM.js → chunk-UEOBNUSV.js} +18 -18
- package/dist/{chunk-LPUG4E24.mjs → chunk-UNGIQ2JC.mjs} +3 -3
- package/dist/{chunk-IJFVE7RA.mjs → chunk-UOSBAA7K.mjs} +214 -2732
- package/dist/{chunk-CIRSQUQP.js → chunk-UVK3P54M.js} +3 -3
- package/dist/{chunk-YIECOH6M.js → chunk-UZ4NCQOW.js} +387 -367
- package/dist/chunk-VFHIV6NU.mjs +260 -0
- package/dist/chunk-VFTL5JPK.js +40 -0
- package/dist/chunk-VL4YWMIQ.js +221 -0
- package/dist/{chunk-GPC42MEB.mjs → chunk-VU5VWITN.mjs} +3 -3
- package/dist/{chunk-BPHWS62L.js → chunk-VV4EVCCM.js} +3 -3
- package/dist/{chunk-357VMCCI.js → chunk-W45EL244.js} +1 -0
- package/dist/{chunk-PYPNHTKD.js → chunk-W6POKP2Z.js} +12 -12
- package/dist/{chunk-Q2NNGMA2.mjs → chunk-WA36C2SE.mjs} +1 -1
- package/dist/chunk-WD3UGAZH.js +3567 -0
- package/dist/{chunk-CIJT24A6.js → chunk-WGEXAPI7.js} +4 -4
- package/dist/chunk-WLM5OK2V.js +15 -0
- package/dist/{chunk-YBRHZ46G.js → chunk-WPA2Q33R.js} +29 -62
- package/dist/chunk-WRBQ27HB.js +206 -0
- package/dist/{chunk-GDJ77XO5.mjs → chunk-WST7RIQR.mjs} +3 -3
- package/dist/{chunk-X7KSRSWO.mjs → chunk-XA5NW5PF.mjs} +2 -2
- package/dist/chunk-XFW3LAFC.js +57 -0
- package/dist/{chunk-U77CMWE2.js → chunk-XN3NMRCX.js} +31 -230
- package/dist/chunk-XNPXCJF5.js +1475 -0
- package/dist/{chunk-52GHJ7QO.mjs → chunk-XQ6NUDK4.mjs} +1 -1
- package/dist/{chunk-3L7OH6ZU.js → chunk-XS64EVWQ.js} +27 -1
- package/dist/{chunk-556YBHWB.js → chunk-XUY7ESCL.js} +17 -17
- package/dist/{chunk-HKHXF3JI.js → chunk-Y4BDEONP.js} +28 -28
- package/dist/chunk-Y4NNVX3N.mjs +216 -0
- package/dist/{chunk-2P7DX62W.mjs → chunk-YBGXGHYX.mjs} +25 -2
- package/dist/{chunk-KK7KYOB7.js → chunk-YCM3MGQM.js} +3 -3
- package/dist/chunk-YSSBZLKT.mjs +13 -0
- package/dist/{chunk-6GCQ6U4Z.mjs → chunk-YUMGFFFO.mjs} +8 -8
- package/dist/{chunk-K45R7M3H.js → chunk-YWD2MCKW.js} +7 -80
- package/dist/chunk-ZJRP66WQ.mjs +14 -0
- package/dist/{chunk-RLEQG467.js → chunk-ZL6SBK5Q.js} +2 -2
- package/dist/cli/acp.mjs +57 -38
- package/dist/cli/add.mjs +6 -6
- package/dist/cli/build.mjs +50 -63
- package/dist/cli/chat.mjs +56 -37
- package/dist/cli/dev-ui/index.html +273 -273
- package/dist/cli/dev.mjs +56 -37
- package/dist/cli/doctor.mjs +54 -35
- package/dist/cli/eval.mjs +10 -9
- package/dist/cli/init.mjs +6 -5
- package/dist/cli/mcp.mjs +55 -36
- package/dist/cli/observe.mjs +248 -12
- package/dist/cli/prompt.mjs +7 -7
- package/dist/cli/studio.mjs +4 -4
- package/dist/cli/traces.mjs +5 -5
- package/dist/{createAgent-DBl3Aiu6.d.ts → createAgent-DTTbuFCL.d.ts} +37 -9
- package/dist/{createAgent-B_G5ALJN.d.mts → createAgent-Id-3_Azu.d.mts} +37 -9
- package/dist/{defineMemory-DMqXVtMo.d.mts → defineMemory-B2iNUhET.d.mts} +1 -1
- package/dist/{defineMemory-B4_Smmur.d.ts → defineMemory-D37Nw7l7.d.ts} +1 -1
- package/dist/deploy/kv.d.mts +7 -6
- package/dist/deploy/kv.d.ts +7 -6
- package/dist/deploy/kv.js +24 -21
- package/dist/deploy/kv.mjs +11 -8
- package/dist/deploy/worker.d.mts +11 -10
- package/dist/deploy/worker.d.ts +11 -10
- package/dist/deploy/worker.js +595 -377
- package/dist/deploy/worker.mjs +595 -377
- package/dist/execution/hooks.d.mts +6 -6
- package/dist/execution/hooks.d.ts +6 -6
- package/dist/execution/otel.js +15 -15
- package/dist/execution/otel.mjs +1 -1
- package/dist/executor/index.d.mts +8 -8
- package/dist/executor/index.d.ts +8 -8
- package/dist/executor/index.js +32 -28
- package/dist/executor/index.mjs +25 -21
- package/dist/flows/index.d.mts +25 -14
- package/dist/flows/index.d.ts +25 -14
- package/dist/flows/index.js +133 -161
- package/dist/flows/index.mjs +57 -93
- package/dist/improve/index.d.mts +162 -0
- package/dist/improve/index.d.ts +162 -0
- package/dist/improve/index.js +430 -0
- package/dist/improve/index.mjs +426 -0
- package/dist/{index-GLDUM4eQ.d.mts → index-CzAUrxm4.d.mts} +42 -2
- package/dist/{index-zX--dFw6.d.ts → index-D2O_nViD.d.ts} +42 -2
- package/dist/index.d.mts +316 -710
- package/dist/index.d.ts +316 -710
- package/dist/index.js +783 -2087
- package/dist/index.mjs +186 -1616
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/index.d.ts +2 -2
- package/dist/integrations/index.js +20 -19
- package/dist/integrations/index.mjs +9 -8
- package/dist/{parseEventStream-DNzfrcLa.d.mts → parseEventStream-CIhK3ENw.d.mts} +2 -2
- package/dist/{parseEventStream-B8xKZ-C8.d.ts → parseEventStream-DOhOnrPA.d.ts} +2 -2
- package/dist/{providers-YXbcpedJ.d.mts → providers-5bWbzCOL.d.mts} +2 -7
- package/dist/{providers-C2UC6bWX.d.ts → providers-DZwzvAut.d.ts} +2 -7
- package/dist/react/index.d.mts +8 -8
- package/dist/react/index.d.ts +8 -8
- package/dist/react/index.js +11 -11
- package/dist/react/index.mjs +5 -5
- package/dist/spec/index.d.mts +152 -0
- package/dist/spec/index.d.ts +152 -0
- package/dist/spec/index.js +458 -0
- package/dist/spec/index.mjs +371 -0
- package/dist/sqliteVectorMemory-EQYCEFYA.js +27 -0
- package/dist/sqliteVectorMemory-SCPC4YSE.mjs +18 -0
- package/dist/storage/sqlite/index.d.mts +6 -5
- package/dist/storage/sqlite/index.d.ts +6 -5
- package/dist/storage/sqlite/index.js +28 -514
- package/dist/storage/sqlite/index.mjs +19 -511
- package/dist/svelte/index.d.mts +8 -8
- package/dist/svelte/index.d.ts +8 -8
- package/dist/svelte/index.js +12 -12
- package/dist/svelte/index.mjs +5 -5
- package/dist/{teamsChannel-BQr6xeDz.d.mts → teamsChannel-CcSzYdlJ.d.mts} +3 -3
- package/dist/{teamsChannel-Dz_VPVB8.d.ts → teamsChannel-DMZNYb2T.d.ts} +3 -3
- package/dist/testing/index.d.mts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +9 -8
- package/dist/testing/index.mjs +5 -4
- package/dist/tokenCipher-BAYy4XP6.d.mts +6 -0
- package/dist/tokenCipher-BAYy4XP6.d.ts +6 -0
- package/dist/tools/index.d.mts +9 -9
- package/dist/tools/index.d.ts +9 -9
- package/dist/tools/index.js +80 -74
- package/dist/tools/index.mjs +23 -17
- package/dist/tools/mcp/index.d.mts +7 -7
- package/dist/tools/mcp/index.d.ts +7 -7
- package/dist/tools/mcp/index.js +20 -18
- package/dist/tools/mcp/index.mjs +9 -7
- package/dist/traces/index.js +19 -19
- package/dist/traces/index.mjs +7 -7
- package/dist/triggerCatalog-Bq6DY3jB.d.ts +1008 -0
- package/dist/triggerCatalog-PFQwL8_7.d.mts +1008 -0
- package/dist/triggers/index.d.mts +6 -6
- package/dist/triggers/index.d.ts +6 -6
- package/dist/triggers/index.js +7 -7
- package/dist/triggers/index.mjs +3 -3
- package/dist/types/index.d.mts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +8 -8
- package/dist/utils/index.mjs +4 -4
- package/dist/{vectorProvider-Dq2SAOG4.d.mts → vectorProvider-CIzaravb.d.mts} +1 -1
- package/dist/{vectorProvider-CnlK8Tbt.d.ts → vectorProvider-DFE1yCje.d.ts} +1 -1
- package/dist/vue/index.d.mts +8 -8
- package/dist/vue/index.d.ts +8 -8
- package/dist/vue/index.js +12 -12
- package/dist/vue/index.mjs +5 -5
- package/dist/{workerMemory-l_lhBDvy.d.mts → workerMemory-BTAFsYZx.d.mts} +2 -2
- package/dist/{workerMemory-Q0fOW63m.d.ts → workerMemory-C5RC9YLB.d.ts} +2 -2
- package/docs/acp.md +132 -132
- package/docs/agent-directories.md +392 -392
- package/docs/agent-forge.md +887 -885
- package/docs/agent-spec.md +385 -0
- package/docs/ai-sdk-ui.md +159 -159
- package/docs/api-overview.md +352 -352
- package/docs/approvals.md +590 -590
- package/docs/auth.md +643 -643
- package/docs/build-a-coding-agent.md +311 -311
- package/docs/channels.md +774 -774
- package/docs/claude-projects.md +96 -96
- package/docs/cli.md +358 -356
- package/docs/cloudflare-workers.md +455 -455
- package/docs/code-mode.md +220 -220
- package/docs/compaction.md +371 -371
- package/docs/configuration.md +595 -593
- package/docs/connectors.md +82 -82
- package/docs/decisions.md +114 -114
- package/docs/deployment.md +382 -382
- package/docs/durable-execution.md +847 -829
- package/docs/errors.md +1443 -1431
- package/docs/evals.md +536 -536
- package/docs/executor-api.md +73 -73
- package/docs/flows.md +540 -491
- package/docs/guardrails.md +250 -250
- package/docs/handoffs.md +273 -273
- package/docs/hooks.md +352 -266
- package/docs/hosted-tools.md +241 -241
- package/docs/hostinger-vps.md +43 -43
- package/docs/improve.md +83 -0
- package/docs/installation.md +337 -353
- package/docs/mcp.md +346 -346
- package/docs/memory.md +441 -441
- package/docs/migrating-from-ai-sdk.md +147 -148
- package/docs/migrating-to-create-agent.md +174 -174
- package/docs/models-and-cost.md +102 -102
- package/docs/nextjs.md +171 -171
- package/docs/oauth.md +469 -469
- package/docs/observability.md +981 -955
- package/docs/openapi-tools.md +187 -187
- package/docs/pause-and-resume.md +205 -205
- package/docs/permission-modes.md +197 -197
- package/docs/prompting-techniques.md +146 -146
- package/docs/providers.md +587 -587
- package/docs/queue-and-steer.md +115 -115
- package/docs/quick-start.md +316 -316
- package/docs/react.md +250 -250
- package/docs/reasoning.md +112 -112
- package/docs/registry.md +266 -266
- package/docs/runs.md +153 -153
- package/docs/schedules.md +222 -207
- package/docs/sessions.md +569 -569
- package/docs/skills.md +107 -107
- package/docs/stream-events.md +155 -154
- package/docs/streaming.md +384 -384
- package/docs/structured-output.md +375 -375
- package/docs/sub-agents.md +641 -641
- package/docs/svelte.md +98 -98
- package/docs/testing.md +295 -295
- package/docs/tool-search.md +191 -191
- package/docs/tools.md +431 -431
- package/docs/triggers.md +127 -127
- package/docs/troubleshooting.md +146 -146
- package/docs/upgrading.md +325 -324
- package/docs/utilities.md +44 -44
- package/docs/vue.md +88 -88
- package/docs/workspace-tools.md +714 -714
- package/llms.txt +100 -98
- package/package.json +534 -506
- package/registry/dist/items/coding-kit.json +58 -58
- package/registry/dist/items/coding-pi.json +70 -70
- package/registry/dist/items/generic-webhook.json +19 -19
- package/registry/dist/items/github-issues.json +24 -24
- package/registry/dist/items/inbox-triage.json +72 -72
- package/registry/dist/items/incident-response.json +50 -50
- package/registry/dist/items/open-meteo-weather.json +21 -21
- package/src/acp/serveAcp.ts +314 -314
- package/src/agentDir/closest.ts +40 -40
- package/src/agentDir/collectTools.ts +84 -84
- package/src/agentDir/fsUtil.ts +45 -45
- package/src/agentDir/importModule.ts +201 -201
- package/src/agentDir/index.ts +15 -13
- package/src/agentDir/loadAgentDir.ts +692 -692
- package/src/agentDir/loadAuth.ts +50 -50
- package/src/agentDir/loadChannels.ts +25 -25
- package/src/agentDir/loadDefaultExports.ts +34 -34
- package/src/agentDir/loadMemory.ts +37 -37
- package/src/agentDir/loadSchedules.ts +19 -19
- package/src/agentDir/loadTools.ts +22 -22
- package/src/agentDir/readConfig.ts +72 -72
- package/src/agentDir/registryEnforce.ts +371 -371
- package/src/agentDir/spec.ts +487 -0
- package/src/agentDir/subagentDelegation.ts +101 -101
- package/src/agentDir/subagents.ts +35 -35
- package/src/agentDir/validateConfig.ts +708 -708
- package/src/agentPending.ts +96 -96
- package/src/agentRecover.ts +269 -143
- package/src/auth/basic.ts +114 -114
- package/src/auth/encoding.ts +72 -72
- package/src/auth/index.ts +11 -11
- package/src/auth/jwt.ts +542 -542
- package/src/auth/oidc.ts +105 -105
- package/src/auth/routeAuth.ts +84 -84
- package/src/auth/types.ts +48 -48
- package/src/channels/channelCore.ts +513 -513
- package/src/channels/channelSupport.ts +156 -156
- package/src/channels/defineChannel.ts +234 -234
- package/src/channels/discordChannel.ts +237 -237
- package/src/channels/fetchChannels.ts +226 -226
- package/src/channels/githubAppAuth.ts +139 -139
- package/src/channels/githubChannel.ts +477 -477
- package/src/channels/httpChannel.ts +66 -66
- package/src/channels/index.ts +14 -14
- package/src/channels/mountChannels.ts +140 -140
- package/src/channels/slackChannel.ts +257 -257
- package/src/channels/teamsChannel.ts +360 -360
- package/src/channels/telegramChannel.ts +263 -263
- package/src/channels/webhookChannel.ts +126 -126
- package/src/claude/claudeProject.ts +307 -307
- package/src/claude/index.ts +6 -6
- package/src/cli/acp.ts +76 -76
- package/src/cli/add.ts +288 -288
- package/src/cli/addCheck.ts +364 -364
- package/src/cli/addReceipt.ts +76 -76
- package/src/cli/addWrite.ts +110 -110
- package/src/cli/args.ts +126 -126
- package/src/cli/build.ts +169 -169
- package/src/cli/buildPeers.ts +102 -102
- package/src/cli/chat.ts +146 -146
- package/src/cli/chatRepl.ts +295 -295
- package/src/cli/dev-ui/index.html +273 -273
- package/src/cli/dev.ts +281 -281
- package/src/cli/devEvents.ts +32 -32
- package/src/cli/devReload.ts +438 -412
- package/src/cli/doctor.ts +172 -172
- package/src/cli/doctorChecks.ts +400 -400
- package/src/cli/doctorCore.ts +50 -50
- package/src/cli/doctorRender.ts +38 -38
- package/src/cli/doctorSpec.ts +198 -198
- package/src/cli/doctorTypes.ts +70 -70
- package/src/cli/eval.ts +438 -438
- package/src/cli/evalReport.ts +311 -311
- package/src/cli/init/options.ts +142 -142
- package/src/cli/init/projectConfig.ts +38 -38
- package/src/cli/init/prompts.ts +68 -68
- package/src/cli/init/scaffold.ts +41 -41
- package/src/cli/init/sdkDependency.ts +60 -60
- package/src/cli/init/telegramTemplate.ts +781 -781
- package/src/cli/init/templates.ts +461 -461
- package/src/cli/init.ts +221 -221
- package/src/cli/mcp.ts +91 -91
- package/src/cli/observe.ts +586 -549
- package/src/cli/prompt.ts +285 -285
- package/src/cli/registry.ts +262 -262
- package/src/cli/studio.ts +332 -332
- package/src/cli/traces.ts +330 -330
- package/src/cli/versionRange.ts +60 -60
- package/src/context/agentCompaction.ts +53 -53
- package/src/context/compaction.ts +608 -608
- package/src/context/contextWindow.ts +71 -71
- package/src/context/index.ts +6 -6
- package/src/core/AgentBuilder.ts +197 -197
- package/src/core/index.ts +1 -1
- package/src/createAgent.ts +1892 -1891
- package/src/createAgentApprovals.ts +591 -591
- package/src/createAgentKeys.ts +85 -85
- package/src/decisions/decide.ts +261 -261
- package/src/decisions/index.ts +18 -18
- package/src/deploy/adapters/cloudflare-dir.ts +308 -308
- package/src/deploy/adapters/cloudflare.ts +483 -483
- package/src/deploy/adapters/docker.ts +77 -77
- package/src/deploy/adapters/node-server-dir.ts +138 -138
- package/src/deploy/adapters/node-server.ts +475 -475
- package/src/deploy/adapters/wranglerToml.ts +127 -127
- package/src/deploy/bundle.ts +467 -467
- package/src/deploy/checkpointBinding.ts +16 -16
- package/src/deploy/evalModule.ts +63 -63
- package/src/deploy/index.ts +22 -22
- package/src/deploy/kv.ts +10 -10
- package/src/deploy/kvBytes.ts +13 -13
- package/src/deploy/kvCheckpointStore.ts +315 -315
- package/src/deploy/kvStore.ts +183 -183
- package/src/deploy/kvTokenStore.ts +75 -75
- package/src/deploy/nodeServer.ts +333 -333
- package/src/deploy/otelTracing.ts +159 -159
- package/src/deploy/runtime.ts +21 -21
- package/src/deploy/runtime.worker.ts +407 -407
- package/src/deploy/shims/google.worker.ts +19 -19
- package/src/deploy/shims/isolatedVm.worker.ts +21 -21
- package/src/deploy/shims/mcp.worker.ts +53 -53
- package/src/deploy/shims/node.worker.ts +58 -58
- package/src/deploy/shims/ollama.worker.ts +23 -23
- package/src/deploy/shims/pi.worker.ts +34 -34
- package/src/deploy/shims/quickjs.worker.ts +16 -16
- package/src/deploy/shims/sandboxCore.worker.ts +28 -28
- package/src/deploy/specExecution.ts +46 -46
- package/src/deploy/types.ts +53 -53
- package/src/deploy/worker.ts +68 -68
- package/src/deploy/workerAgentDir.ts +318 -318
- package/src/deploy/workerMemory.ts +99 -99
- package/src/deploy/workerSdk.ts +38 -38
- package/src/deploy/workerSdkExports.ts +56 -56
- package/src/deploy/workerSupport.ts +23 -23
- package/src/evals/cassettes.ts +212 -212
- package/src/evals/checks.ts +126 -126
- package/src/evals/dataset.ts +85 -85
- package/src/evals/defineEval.ts +407 -407
- package/src/evals/drift.ts +138 -138
- package/src/evals/evalResult.ts +91 -91
- package/src/evals/index.ts +17 -17
- package/src/evals/llmJudge.ts +252 -252
- package/src/evals/observe.ts +204 -204
- package/src/evals/recorder.ts +33 -33
- package/src/evals/remoteTarget.ts +101 -101
- package/src/evals/scorers.ts +162 -162
- package/src/evals/toolMatch.ts +59 -59
- package/src/evals/trajectory.ts +407 -407
- package/src/execution/AgentExecutor.ts +2232 -2211
- package/src/execution/ApprovalGate.ts +432 -432
- package/src/execution/InMemoryApprovalStore.ts +43 -43
- package/src/execution/abortReason.ts +13 -13
- package/src/execution/abortable.ts +23 -23
- package/src/execution/agentEvents.ts +622 -608
- package/src/execution/agentFingerprint.ts +144 -144
- package/src/execution/agentRun.ts +809 -809
- package/src/execution/agentRunState.ts +576 -576
- package/src/execution/approvalTtl.ts +22 -0
- package/src/execution/budget.ts +239 -239
- package/src/execution/checkpoint.ts +573 -573
- package/src/execution/codeMode.ts +384 -384
- package/src/execution/codeModeIsolate.ts +398 -398
- package/src/execution/delegation.ts +337 -337
- package/src/execution/errors.ts +802 -802
- package/src/execution/fork.ts +74 -72
- package/src/execution/genAiSpans.ts +351 -350
- package/src/execution/generateStep.ts +436 -436
- package/src/execution/guardrailCatalog.ts +62 -60
- package/src/execution/guardrailLists.ts +22 -0
- package/src/execution/guardrailStarterSet.ts +316 -324
- package/src/execution/handoffRun.ts +437 -437
- package/src/execution/hooks.ts +355 -353
- package/src/execution/hostedToolCalls.ts +96 -96
- package/src/execution/index.ts +152 -150
- package/src/execution/inputQueue.ts +171 -171
- package/src/execution/ioGuardrails.ts +387 -338
- package/src/execution/logger.ts +33 -33
- package/src/execution/markSpan.ts +106 -0
- package/src/execution/modelSettings.ts +113 -113
- package/src/execution/otel.ts +326 -326
- package/src/execution/outputCheck.ts +21 -0
- package/src/execution/patchChecks.ts +386 -386
- package/src/execution/permissions.ts +465 -477
- package/src/execution/progressGuard.ts +192 -0
- package/src/execution/propagatingToolError.ts +68 -68
- package/src/execution/resume.ts +1673 -1666
- package/src/execution/resumeSubagent.ts +123 -119
- package/src/execution/runFailure.ts +57 -57
- package/src/execution/runLease.ts +198 -198
- package/src/execution/runPrincipal.ts +65 -65
- package/src/execution/runUsage.ts +287 -287
- package/src/execution/sandboxGuard.ts +119 -119
- package/src/execution/scrubSecrets.ts +59 -59
- package/src/execution/secretPatterns.ts +21 -21
- package/src/execution/semconv.ts +210 -174
- package/src/execution/streamStep.ts +183 -183
- package/src/execution/strictShapes.ts +290 -290
- package/src/execution/structuredOutput.ts +561 -561
- package/src/execution/subagentRuntime.ts +277 -277
- package/src/execution/toolArgsValidation.ts +233 -233
- package/src/execution/toolBatch.ts +284 -284
- package/src/execution/toolCallExecution.ts +679 -674
- package/src/execution/toolDeferral.ts +83 -83
- package/src/execution/toolErrors.ts +147 -147
- package/src/execution/toolNames.ts +45 -45
- package/src/execution/toolPartials.ts +72 -72
- package/src/execution/toolResult.ts +115 -115
- package/src/execution/toolRunContext.ts +88 -88
- package/src/execution/toolSearch.ts +206 -206
- package/src/execution/tracing.ts +247 -273
- package/src/execution/transcript.ts +136 -136
- package/src/executor/index.ts +24 -24
- package/src/flows/FlowBuilder.ts +172 -172
- package/src/flows/FlowExecutor.ts +1714 -1677
- package/src/flows/flowCheckpoint.ts +277 -277
- package/src/flows/flowToolGate.ts +133 -126
- package/src/flows/index.ts +9 -9
- package/src/flows/inputs.ts +233 -233
- package/src/flows/safeExpression.ts +499 -499
- package/src/flows/validators.ts +114 -114
- package/src/handoffAgents.ts +251 -251
- package/src/handoffs.ts +109 -109
- package/src/improve/evidence.ts +67 -0
- package/src/improve/harness.ts +107 -0
- package/src/improve/improve.ts +225 -0
- package/src/improve/index.ts +3 -0
- package/src/improve/prompts.ts +47 -0
- package/src/improve/types.ts +148 -0
- package/src/index.ts +138 -130
- package/src/integrations/index.ts +12 -12
- package/src/memory/defineMemory.ts +238 -238
- package/src/memory/embeddings.ts +98 -98
- package/src/memory/fileMemory.ts +81 -81
- package/src/memory/index.ts +15 -15
- package/src/memory/keyedQueue.ts +19 -19
- package/src/memory/migrateMemoryKeys.ts +68 -68
- package/src/memory/providerContract.ts +105 -105
- package/src/memory/providers.ts +118 -118
- package/src/memory/vectorProvider.ts +205 -205
- package/src/memory/withMemory.ts +344 -344
- package/src/models/catalog.ts +140 -0
- package/src/models/estimateTokens.ts +160 -160
- package/src/models/index.ts +8 -8
- package/src/models/modelData.ts +51 -48
- package/src/models/registry.ts +185 -173
- package/src/models/usage.ts +176 -176
- package/src/oauth/agentOAuth.ts +93 -93
- package/src/oauth/defineOAuthProvider.ts +137 -137
- package/src/oauth/index.ts +20 -20
- package/src/oauth/memoryTokenStore.ts +73 -73
- package/src/oauth/sealedTokenStore.ts +153 -153
- package/src/oauth/signIn.ts +602 -602
- package/src/oauth/signInPending.ts +25 -25
- package/src/oauth/tokenCipher.ts +157 -157
- package/src/oauth/tokenStore.contract.ts +205 -205
- package/src/oauth/tokenStoreKey.ts +133 -133
- package/src/oauth/types.ts +76 -76
- package/src/observe/alerts.ts +159 -159
- package/src/observe/blockingPost.ts +67 -67
- package/src/observe/builtins.ts +14 -14
- package/src/observe/daemon.ts +349 -349
- package/src/observe/datasets.ts +152 -152
- package/src/observe/emit.ts +155 -155
- package/src/observe/envelope.ts +171 -171
- package/src/observe/evaluatorTrace.ts +97 -97
- package/src/observe/evaluatorWorker.ts +353 -353
- package/src/observe/evaluators.ts +152 -152
- package/src/observe/failures.ts +250 -0
- package/src/observe/httpClient.ts +44 -44
- package/src/observe/hubClient.ts +235 -235
- package/src/observe/humanScores.ts +64 -64
- package/src/observe/identity.ts +182 -182
- package/src/observe/judgeJobs.ts +172 -172
- package/src/observe/promptClient.ts +214 -214
- package/src/observe/queues.ts +205 -205
- package/src/observe/requestGuard.ts +79 -79
- package/src/observe/routes/agents.ts +52 -52
- package/src/observe/routes/datasets.ts +117 -117
- package/src/observe/routes/evaluators.ts +231 -231
- package/src/observe/routes/httpKit.ts +101 -101
- package/src/observe/routes/hub.ts +135 -135
- package/src/observe/routes/index.ts +77 -77
- package/src/observe/routes/ingest.ts +40 -40
- package/src/observe/routes/insights.ts +29 -29
- package/src/observe/routes/prompts.ts +247 -247
- package/src/observe/routes/queues.ts +216 -216
- package/src/observe/routes/scores.ts +81 -81
- package/src/observe/routes/sessions.ts +47 -47
- package/src/observe/routes/status.ts +48 -48
- package/src/observe/routes/traces.ts +83 -83
- package/src/observe/schema.ts +286 -286
- package/src/observe/store.ts +1508 -1508
- package/src/observe/sweep.ts +318 -318
- package/src/projectInstructions.ts +80 -80
- package/src/providers/AnthropicProvider.ts +156 -156
- package/src/providers/GoogleProvider.ts +55 -55
- package/src/providers/OllamaProvider.ts +116 -116
- package/src/providers/OpenAIProvider.ts +183 -183
- package/src/providers/OpenRouterProvider.ts +649 -649
- package/src/providers/abortableDelay.ts +19 -19
- package/src/providers/aiSdkCompat.ts +800 -800
- package/src/providers/aiSdkProvider.ts +521 -521
- package/src/providers/builtinProviders.ts +66 -66
- package/src/providers/content.ts +74 -74
- package/src/providers/delegateCapabilities.ts +17 -17
- package/src/providers/filePartsFixtures.ts +63 -63
- package/src/providers/fromAiSdk.ts +173 -173
- package/src/providers/fromAiSdkTools.ts +175 -175
- package/src/providers/hostedToolMapping.ts +32 -32
- package/src/providers/index.ts +37 -37
- package/src/providers/interception.ts +46 -46
- package/src/providers/llm.ts +568 -568
- package/src/providers/mock.ts +195 -195
- package/src/providers/mockMarker.ts +9 -9
- package/src/providers/optionalPeer.ts +134 -134
- package/src/providers/pi/PiProvider.ts +500 -500
- package/src/providers/pi/messages.ts +197 -197
- package/src/providers/pi/piTypes.ts +197 -197
- package/src/providers/pi/schema.ts +243 -243
- package/src/providers/promptCaching.ts +104 -104
- package/src/providers/providerEvents.ts +75 -75
- package/src/providers/providerFetch.ts +436 -436
- package/src/providers/providerSpec.ts +409 -409
- package/src/providers/rateLimit.ts +294 -294
- package/src/providers/reasoning.ts +113 -104
- package/src/providers/resilience.ts +484 -484
- package/src/providers/resolveProvider.ts +35 -35
- package/src/react/index.ts +30 -30
- package/src/react/parseEventStream.ts +2 -2
- package/src/react/useLoushoAgent.ts +54 -54
- package/src/react/useTodos.ts +21 -21
- package/src/sandbox/errors.ts +41 -41
- package/src/sandbox/index.ts +19 -19
- package/src/sandbox/isolate.ts +173 -173
- package/src/sandbox/sandbox.ts +104 -104
- package/src/sandbox/toolSandbox.ts +82 -82
- package/src/sandbox/types.ts +114 -114
- package/src/schedules/defineSchedule.ts +87 -87
- package/src/schedules/fireSchedule.ts +114 -114
- package/src/schedules/index.ts +8 -8
- package/src/schedules/scheduled.ts +64 -64
- package/src/schedules/specSchedules.ts +45 -41
- package/src/schedules/startSchedules.ts +102 -102
- package/src/security/commandEnv.ts +57 -57
- package/src/security/credentialBroker.ts +317 -317
- package/src/security/crypto.ts +294 -294
- package/src/security/hostPattern.ts +19 -19
- package/src/security/index.ts +14 -14
- package/src/security/privateAddress.ts +157 -157
- package/src/security/sandbox.ts +481 -481
- package/src/security/sandboxCore.ts +123 -123
- package/src/security/sandboxEgress.ts +128 -128
- package/src/security/types.ts +28 -28
- package/src/server/chatRoutes.ts +112 -112
- package/src/server/fetchRoutes.ts +1040 -1040
- package/src/server/hostCheck.ts +68 -68
- package/src/server/requestGuard.ts +261 -261
- package/src/server/routeHandler.ts +208 -208
- package/src/server/sendJson.ts +7 -7
- package/src/server/sessionClient.ts +144 -144
- package/src/server/uiMessageStream.ts +322 -322
- package/src/session/AgentSession.ts +1471 -1471
- package/src/session/checkpointId.ts +69 -69
- package/src/session/index.ts +16 -16
- package/src/session/sessionCompact.ts +68 -68
- package/src/session/sessionFork.ts +104 -104
- package/src/session/sessionId.ts +25 -25
- package/src/session/sessionStore.ts +204 -204
- package/src/session/sessionStream.ts +133 -133
- package/src/skills/defineSkill.ts +90 -90
- package/src/skills/index.ts +3 -3
- package/src/skills/loadSkills.ts +147 -147
- package/src/skills/withSkills.ts +182 -182
- package/src/spec/generators/claude-code.ts +118 -118
- package/src/spec/generators/codex.ts +67 -67
- package/src/spec/generators/pi.ts +53 -53
- package/src/spec/generators/tsLiteral.ts +75 -0
- package/src/spec/generators/typescript.ts +334 -0
- package/src/spec/guardrailBuilders.ts +39 -39
- package/src/spec/guardrailOptions.ts +96 -96
- package/src/spec/index.ts +30 -15
- package/src/spec/loadSpec.ts +58 -58
- package/src/spec/policy.ts +96 -96
- package/src/spec/provider.ts +34 -34
- package/src/spec/runLimitsSchema.ts +23 -23
- package/src/spec/schema.ts +349 -343
- package/src/spec/specFile.ts +42 -42
- package/src/spec/specToAgent.ts +101 -101
- package/src/spec/v2/common.ts +86 -86
- package/src/spec/v2/familyInstructions.ts +20 -20
- package/src/spec/v2/files.ts +23 -23
- package/src/spec/v2/flow.ts +237 -0
- package/src/spec/v2/guardrails.ts +126 -120
- package/src/spec/v2/handoffs.ts +75 -75
- package/src/spec/v2/hooks.ts +105 -93
- package/src/spec/v2/host.ts +37 -28
- package/src/spec/v2/index.ts +70 -0
- package/src/spec/v2/jsonSchema.ts +111 -0
- package/src/spec/v2/jsonSchemaAdapter.ts +68 -64
- package/src/spec/v2/loadSpec.ts +26 -26
- package/src/spec/v2/mcpServers.ts +121 -113
- package/src/spec/v2/memory.ts +170 -129
- package/src/spec/v2/nodeIdExporter.ts +233 -0
- package/src/spec/v2/nodeIds.ts +130 -0
- package/src/spec/v2/output.ts +28 -28
- package/src/spec/v2/parse.ts +76 -76
- package/src/spec/v2/permissions.ts +104 -102
- package/src/spec/v2/resolve/guardrails.ts +37 -37
- package/src/spec/v2/resolve/handoffs.ts +35 -35
- package/src/spec/v2/resolve/hooks.ts +151 -151
- package/src/spec/v2/resolve/integrationTools.ts +85 -85
- package/src/spec/v2/resolve/mcpServers.ts +66 -66
- package/src/spec/v2/resolve/memory.ts +118 -118
- package/src/spec/v2/resolve/permissions.ts +67 -67
- package/src/spec/v2/resolve/secrets.ts +62 -62
- package/src/spec/v2/resolve/skills.ts +105 -105
- package/src/spec/v2/resolve/subagents.ts +94 -94
- package/src/spec/v2/resolve/toolFiles.ts +103 -103
- package/src/spec/v2/resolve/tools.ts +151 -151
- package/src/spec/v2/resolve/triggers.ts +35 -0
- package/src/spec/v2/runtime.ts +159 -159
- package/src/spec/v2/schema.ts +114 -94
- package/src/spec/v2/secrets.ts +72 -64
- package/src/spec/v2/sections.ts +148 -0
- package/src/spec/v2/skills.ts +84 -84
- package/src/spec/v2/specToAgent.ts +231 -185
- package/src/spec/v2/subagents.ts +75 -75
- package/src/spec/v2/tools.ts +451 -414
- package/src/spec/v2/triggerCatalog.ts +200 -0
- package/src/spec/v2/triggers.ts +242 -0
- package/src/spec/v2/types.ts +165 -162
- package/src/spec/v2/unsupported.ts +33 -33
- package/src/spec/v2/upgrade.ts +191 -191
- package/src/storage/StorageService.ts +285 -285
- package/src/storage/agentStore.ts +154 -154
- package/src/storage/atomicFile.ts +109 -109
- package/src/storage/fileLock.ts +144 -144
- package/src/storage/fileNames.ts +64 -64
- package/src/storage/fileStore.ts +613 -613
- package/src/storage/fsRetry.ts +51 -51
- package/src/storage/jsonBytes.ts +22 -22
- package/src/storage/leaseRecord.ts +136 -136
- package/src/storage/sqlite/SqliteStore.ts +173 -173
- package/src/storage/sqlite/connection.ts +123 -123
- package/src/storage/sqlite/driver.ts +56 -56
- package/src/storage/sqlite/index.ts +8 -8
- package/src/storage/sqlite/migrations.ts +147 -147
- package/src/storage/sqlite/sqliteMemory.ts +47 -47
- package/src/storage/sqlite/sqliteVectorMemory.ts +83 -83
- package/src/storage/sqlite/stores.ts +285 -285
- package/src/storage/sqlite/tokenStore.ts +63 -63
- package/src/storage/types.ts +77 -77
- package/src/subagents/backgroundTasks.ts +257 -257
- package/src/subagents/index.ts +8 -8
- package/src/subagents/piAgent.ts +578 -578
- package/src/subagents/remoteAgent.ts +139 -139
- package/src/subagents/taskSessions.ts +142 -142
- package/src/subagents/types.ts +89 -89
- package/src/subagents/withSubagents.ts +631 -631
- package/src/svelte/index.ts +29 -29
- package/src/svelte/loushoAgent.ts +81 -81
- package/src/svelte/loushoTodos.ts +28 -28
- package/src/testing/cassette.ts +224 -224
- package/src/testing/fingerprint.ts +355 -355
- package/src/testing/hashEmbedder.ts +54 -54
- package/src/testing/index.ts +35 -35
- package/src/testing/mockModel.ts +424 -424
- package/src/testing/notMock.ts +16 -16
- package/src/testing/recordReplay.ts +532 -532
- package/src/testing/testToolContext.ts +42 -42
- package/src/tools/ToolRegistry.ts +138 -138
- package/src/tools/approvalPolicies.ts +113 -113
- package/src/tools/built-in/askQuestion.ts +88 -88
- package/src/tools/built-in/assertOk.ts +16 -16
- package/src/tools/built-in/currentDate.ts +18 -18
- package/src/tools/built-in/dayName.ts +21 -21
- package/src/tools/built-in/email.ts +121 -121
- package/src/tools/built-in/github.ts +1661 -1661
- package/src/tools/built-in/htmlToText.ts +194 -194
- package/src/tools/built-in/http.ts +274 -274
- package/src/tools/built-in/httpCore.ts +212 -212
- package/src/tools/built-in/index.ts +13 -13
- package/src/tools/built-in/jira.ts +1167 -1167
- package/src/tools/built-in/sandboxFetch.ts +311 -311
- package/src/tools/built-in/slack.ts +208 -208
- package/src/tools/built-in/todo.ts +207 -207
- package/src/tools/built-in/toolFailure.ts +12 -12
- package/src/tools/built-in/webFetch.ts +326 -326
- package/src/tools/built-in/workerHttp.ts +145 -145
- package/src/tools/builtinRegistry.ts +35 -35
- package/src/tools/catalog.ts +71 -0
- package/src/tools/defineTool.ts +380 -380
- package/src/tools/hosted.ts +165 -165
- package/src/tools/index.ts +4 -4
- package/src/tools/mcp/McpToolLoader.ts +341 -341
- package/src/tools/mcp/agentMcp.ts +168 -168
- package/src/tools/mcp/connect.ts +437 -437
- package/src/tools/mcp/index.ts +13 -13
- package/src/tools/mcp/mcpOAuth.ts +286 -286
- package/src/tools/mcp/result.ts +175 -175
- package/src/tools/mcp/schema.ts +329 -329
- package/src/tools/mcp/server/buildServer.ts +141 -141
- package/src/tools/mcp/server/httpTransport.ts +304 -304
- package/src/tools/mcp/server/serveMcp.ts +147 -147
- package/src/tools/mcp/server/toolNames.ts +32 -32
- package/src/tools/openapi/index.ts +2 -2
- package/src/tools/openapi/openApiTools.ts +453 -453
- package/src/tools/openapi/operations.ts +320 -320
- package/src/tools/openapi/privateAddresses.ts +107 -107
- package/src/tools/rootTools.ts +17 -17
- package/src/tools/toolContract.ts +67 -67
- package/src/tools/toolEntries.ts +106 -106
- package/src/tools/toolSearchRank.ts +150 -150
- package/src/tools/workspace/MemoryWorkspace.ts +162 -162
- package/src/tools/workspace/NodeWorkspace.ts +296 -296
- package/src/tools/workspace/SandboxShell.ts +108 -108
- package/src/tools/workspace/checkpointFileStore.ts +130 -130
- package/src/tools/workspace/checkpoints.ts +442 -442
- package/src/tools/workspace/fsOperations.ts +315 -315
- package/src/tools/workspace/fsTools.ts +307 -307
- package/src/tools/workspace/glob.ts +118 -118
- package/src/tools/workspace/grepMatcher.ts +215 -215
- package/src/tools/workspace/index.ts +42 -42
- package/src/tools/workspace/keyedQueue.ts +18 -18
- package/src/tools/workspace/nodeProcess.ts +160 -160
- package/src/tools/workspace/paths.ts +182 -182
- package/src/tools/workspace/shellTool.ts +346 -346
- package/src/tools/workspace/types.ts +150 -150
- package/src/traces/fileTraceExporter.ts +132 -132
- package/src/traces/format.ts +22 -22
- package/src/traces/index.ts +22 -22
- package/src/traces/readTraces.ts +210 -210
- package/src/triggers/TriggerRegistry.ts +93 -93
- package/src/triggers/cronExpression.ts +315 -315
- package/src/triggers/index.ts +12 -12
- package/src/triggers/slackSignature.ts +68 -68
- package/src/triggers/types.ts +123 -123
- package/src/triggers/webhookAuth.ts +212 -212
- package/src/types/agent.ts +27 -27
- package/src/types/flow.ts +421 -388
- package/src/types/index.ts +4 -4
- package/src/types/tool.ts +274 -274
- package/src/ui/agentRunner.ts +201 -201
- package/src/ui/index.ts +27 -27
- package/src/ui/parseEventStream.ts +103 -103
- package/src/ui/reducer.ts +329 -329
- package/src/ui/todos.ts +32 -32
- package/src/utils/base64url.ts +6 -6
- package/src/utils/brand.ts +53 -53
- package/src/utils/closestMatch.ts +31 -31
- package/src/utils/cyrb53.ts +20 -20
- package/src/utils/errorCodes.ts +164 -162
- package/src/utils/errors.ts +106 -106
- package/src/utils/id.ts +11 -11
- package/src/utils/index.ts +22 -22
- package/src/utils/knownOptionKeys.ts +28 -28
- package/src/utils/moduleDir.ts +17 -17
- package/src/utils/partialJson.ts +203 -203
- package/src/utils/sdkError.ts +96 -96
- package/src/utils/validators.ts +76 -76
- package/src/utils/zodCompat.ts +336 -326
- package/src/version.ts +2 -2
- package/src/vue/index.ts +29 -29
- package/src/vue/useLoushoAgent.ts +70 -70
- package/src/vue/useTodos.ts +26 -26
- package/templates/docs-qa/README.md +14 -0
- package/templates/docs-qa/spec.yaml +45 -0
- package/templates/docs-qa/template.json +1 -0
- package/templates/inbox-triage/README.md +16 -0
- package/templates/inbox-triage/spec.yaml +67 -0
- package/templates/inbox-triage/template.json +1 -0
- package/templates/incident-responder/README.md +15 -0
- package/templates/incident-responder/spec.yaml +59 -0
- package/templates/incident-responder/template.json +1 -0
- package/templates/pr-reviewer/README.md +15 -0
- package/templates/pr-reviewer/spec.yaml +52 -0
- package/templates/pr-reviewer/template.json +1 -0
- package/templates/research-assistant/README.md +12 -0
- package/templates/research-assistant/spec.yaml +36 -0
- package/templates/research-assistant/template.json +1 -0
- package/templates/support-triage/README.md +14 -0
- package/templates/support-triage/spec.yaml +52 -0
- package/templates/support-triage/template.json +1 -0
- package/apps/agent-forge/dist/assets/index-CW_o_6tm.css +0 -1
- package/apps/agent-forge/dist/assets/index-DUar5sde.js +0 -906
- package/dist/chunk-EOCLONF3.js +0 -2301
- package/dist/chunk-H5PNP7QP.mjs +0 -1
- package/dist/chunk-S23L64PR.js +0 -2
- package/dist/chunk-WZACYF7N.mjs +0 -2270
- package/dist/chunk-Y46YLRFL.js +0 -6153
- package/docs/comparison.md +0 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,712 +1,728 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to @lousho/build-ai-agent will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
## [1.0.0-alpha.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## [1.0.0-alpha.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
- `
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- `
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
- `
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
- The
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
|
|
113
|
-
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- `
|
|
117
|
-
-
|
|
118
|
-
- `
|
|
119
|
-
- `lousho init
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
|
|
129
|
-
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- `
|
|
135
|
-
-
|
|
136
|
-
- `lousho add`
|
|
137
|
-
-
|
|
138
|
-
- `
|
|
139
|
-
- `
|
|
140
|
-
- `
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
- `
|
|
154
|
-
-
|
|
155
|
-
- `
|
|
156
|
-
- `
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- `
|
|
173
|
-
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
|
|
178
|
-
###
|
|
179
|
-
|
|
180
|
-
- `
|
|
181
|
-
- `
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
- `
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
- `
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
-
|
|
237
|
-
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
-
|
|
243
|
-
- `
|
|
244
|
-
- `
|
|
245
|
-
-
|
|
246
|
-
- `
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
250
|
-
- `
|
|
251
|
-
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
### Security
|
|
258
|
-
- **Behavior change:**
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
-
|
|
263
|
-
-
|
|
264
|
-
- **Behavior change:**
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
-
|
|
268
|
-
|
|
269
|
-
-
|
|
270
|
-
|
|
271
|
-
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
-
|
|
275
|
-
-
|
|
276
|
-
-
|
|
277
|
-
- `
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
- **Behavior change:**
|
|
281
|
-
-
|
|
282
|
-
-
|
|
283
|
-
-
|
|
284
|
-
-
|
|
285
|
-
-
|
|
286
|
-
-
|
|
287
|
-
-
|
|
288
|
-
-
|
|
289
|
-
-
|
|
290
|
-
-
|
|
291
|
-
-
|
|
292
|
-
-
|
|
293
|
-
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
- **Behavior change:** `
|
|
297
|
-
- **Behavior change:** a
|
|
298
|
-
- **Behavior change:**
|
|
299
|
-
- **Behavior change
|
|
300
|
-
- **Behavior change:** `
|
|
301
|
-
- **Behavior change (
|
|
302
|
-
- **Behavior change:**
|
|
303
|
-
-
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
-
|
|
307
|
-
|
|
308
|
-
-
|
|
309
|
-
-
|
|
310
|
-
|
|
311
|
-
-
|
|
312
|
-
-
|
|
313
|
-
-
|
|
314
|
-
-
|
|
315
|
-
-
|
|
316
|
-
-
|
|
317
|
-
-
|
|
318
|
-
-
|
|
319
|
-
-
|
|
320
|
-
-
|
|
321
|
-
-
|
|
322
|
-
-
|
|
323
|
-
|
|
324
|
-
-
|
|
325
|
-
-
|
|
326
|
-
|
|
327
|
-
-
|
|
328
|
-
-
|
|
329
|
-
-
|
|
330
|
-
|
|
331
|
-
-
|
|
332
|
-
-
|
|
333
|
-
-
|
|
334
|
-
-
|
|
335
|
-
-
|
|
336
|
-
-
|
|
337
|
-
-
|
|
338
|
-
-
|
|
339
|
-
-
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
-
|
|
346
|
-
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
351
|
-
-
|
|
352
|
-
-
|
|
353
|
-
-
|
|
354
|
-
-
|
|
355
|
-
-
|
|
356
|
-
-
|
|
357
|
-
-
|
|
358
|
-
-
|
|
359
|
-
-
|
|
360
|
-
-
|
|
361
|
-
-
|
|
362
|
-
-
|
|
363
|
-
-
|
|
364
|
-
-
|
|
365
|
-
- `
|
|
366
|
-
-
|
|
367
|
-
-
|
|
368
|
-
-
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
-
|
|
401
|
-
-
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
-
|
|
408
|
-
-
|
|
409
|
-
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
-
|
|
413
|
-
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
-
|
|
417
|
-
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
-
|
|
421
|
-
-
|
|
422
|
-
-
|
|
423
|
-
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
-
|
|
461
|
-
-
|
|
462
|
-
-
|
|
463
|
-
-
|
|
464
|
-
-
|
|
465
|
-
-
|
|
466
|
-
-
|
|
467
|
-
-
|
|
468
|
-
- `
|
|
469
|
-
-
|
|
470
|
-
-
|
|
471
|
-
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
-
|
|
475
|
-
-
|
|
476
|
-
-
|
|
477
|
-
-
|
|
478
|
-
-
|
|
479
|
-
-
|
|
480
|
-
- `
|
|
481
|
-
-
|
|
482
|
-
-
|
|
483
|
-
- `
|
|
484
|
-
-
|
|
485
|
-
- `
|
|
486
|
-
-
|
|
487
|
-
-
|
|
488
|
-
-
|
|
489
|
-
- `
|
|
490
|
-
-
|
|
491
|
-
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
- `
|
|
498
|
-
-
|
|
499
|
-
-
|
|
500
|
-
-
|
|
501
|
-
- `
|
|
502
|
-
-
|
|
503
|
-
- `
|
|
504
|
-
-
|
|
505
|
-
- `
|
|
506
|
-
-
|
|
507
|
-
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
- `
|
|
514
|
-
-
|
|
515
|
-
- `
|
|
516
|
-
-
|
|
517
|
-
- `
|
|
518
|
-
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
-
|
|
522
|
-
-
|
|
523
|
-
-
|
|
524
|
-
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
-
|
|
528
|
-
-
|
|
529
|
-
-
|
|
530
|
-
-
|
|
531
|
-
-
|
|
532
|
-
-
|
|
533
|
-
-
|
|
534
|
-
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
- `docs
|
|
538
|
-
-
|
|
539
|
-
-
|
|
540
|
-
-
|
|
541
|
-
-
|
|
542
|
-
- docs/auth.md
|
|
543
|
-
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
-
|
|
557
|
-
-
|
|
558
|
-
-
|
|
559
|
-
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
-
|
|
565
|
-
-
|
|
566
|
-
-
|
|
567
|
-
- `
|
|
568
|
-
-
|
|
569
|
-
-
|
|
570
|
-
-
|
|
571
|
-
-
|
|
572
|
-
-
|
|
573
|
-
-
|
|
574
|
-
-
|
|
575
|
-
-
|
|
576
|
-
-
|
|
577
|
-
-
|
|
578
|
-
-
|
|
579
|
-
-
|
|
580
|
-
-
|
|
581
|
-
-
|
|
582
|
-
-
|
|
583
|
-
- `
|
|
584
|
-
-
|
|
585
|
-
- BREAKING:
|
|
586
|
-
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
-
|
|
590
|
-
-
|
|
591
|
-
-
|
|
592
|
-
-
|
|
593
|
-
-
|
|
594
|
-
-
|
|
595
|
-
-
|
|
596
|
-
-
|
|
597
|
-
-
|
|
598
|
-
-
|
|
599
|
-
-
|
|
600
|
-
-
|
|
601
|
-
-
|
|
602
|
-
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
-
|
|
606
|
-
-
|
|
607
|
-
-
|
|
608
|
-
-
|
|
609
|
-
-
|
|
610
|
-
-
|
|
611
|
-
-
|
|
612
|
-
-
|
|
613
|
-
-
|
|
614
|
-
-
|
|
615
|
-
-
|
|
616
|
-
-
|
|
617
|
-
-
|
|
618
|
-
- `
|
|
619
|
-
-
|
|
620
|
-
-
|
|
621
|
-
-
|
|
622
|
-
-
|
|
623
|
-
-
|
|
624
|
-
-
|
|
625
|
-
-
|
|
626
|
-
-
|
|
627
|
-
-
|
|
628
|
-
-
|
|
629
|
-
-
|
|
630
|
-
-
|
|
631
|
-
-
|
|
632
|
-
-
|
|
633
|
-
-
|
|
634
|
-
-
|
|
635
|
-
-
|
|
636
|
-
-
|
|
637
|
-
-
|
|
638
|
-
-
|
|
639
|
-
-
|
|
640
|
-
-
|
|
641
|
-
-
|
|
642
|
-
-
|
|
643
|
-
-
|
|
644
|
-
-
|
|
645
|
-
-
|
|
646
|
-
-
|
|
647
|
-
-
|
|
648
|
-
-
|
|
649
|
-
-
|
|
650
|
-
-
|
|
651
|
-
-
|
|
652
|
-
-
|
|
653
|
-
- `lousho
|
|
654
|
-
-
|
|
655
|
-
-
|
|
656
|
-
-
|
|
657
|
-
-
|
|
658
|
-
-
|
|
659
|
-
-
|
|
660
|
-
-
|
|
661
|
-
-
|
|
662
|
-
-
|
|
663
|
-
- `
|
|
664
|
-
-
|
|
665
|
-
- `
|
|
666
|
-
-
|
|
667
|
-
-
|
|
668
|
-
-
|
|
669
|
-
-
|
|
670
|
-
-
|
|
671
|
-
-
|
|
672
|
-
-
|
|
673
|
-
-
|
|
674
|
-
-
|
|
675
|
-
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
- `
|
|
679
|
-
-
|
|
680
|
-
-
|
|
681
|
-
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
-
|
|
688
|
-
-
|
|
689
|
-
-
|
|
690
|
-
-
|
|
691
|
-
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
-
|
|
695
|
-
-
|
|
696
|
-
-
|
|
697
|
-
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
- `
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
- The
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to @lousho/build-ai-agent will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.0-alpha.34] - 2026-11-07
|
|
11
|
+
|
|
12
|
+
## [1.0.0-alpha.33] - 2026-11-07
|
|
13
|
+
|
|
14
|
+
## [1.0.0-alpha.32] - 2026-11-07
|
|
15
|
+
|
|
16
|
+
- Install docs use the public npm packages again (`npm install lousho`, `npm create lousho`).
|
|
17
|
+
- `lousho/improve` (experimental): `improveAgent()` proposes tested edits to an agent's instructions and skills, offline, gated against the current harness and held-out cases. It applies nothing. See [Improving an agent](docs/improve.md).
|
|
18
|
+
- `agent.recover({ retryWithDiagnosis: true })` gives a run that reached `maxAttempts` one fork told why it failed, before quarantining it. `createOutputCheck()` is a new hook that runs your check after a tool succeeds and tells the model what it found. A fork no longer inherits `lastError` and `attempts` from its source.
|
|
19
|
+
- **Behavior change:** `agent.recover()` no longer resumes a failing run forever. It backs off exponentially from the last failure (up to 15 minutes) and stops after `maxAttempts` failures in a row (default 10), calling the new `onQuarantined(run)` once. `agent.resume()` still runs a quarantined run. Pass a larger `maxAttempts` to keep retrying longer.
|
|
20
|
+
- `lousho observe failures` also lists runs whose process died and scores below `--min-score` (default 0.5).
|
|
21
|
+
- `agent.recover()` pauses during a provider outage instead of using up each run's attempts: after 3 provider failures in a row it stops resuming, probes with one run at growing intervals, and reports through the new `onOutage(paused)`.
|
|
22
|
+
- `createProgressGuard()` gains `maxSameResults` (default 5), which denies a tool that keeps returning the same result even when its arguments differ. Run spans record `lousho.run.finish_reason`, and `lousho observe failures` lists runs that ended without an answer and names rate limits.
|
|
23
|
+
- `lousho observe failures` lists recurring tool and model failures by agent, from the observe database or from trace files (`--traces`), each with a class and trace ids. See [Observability](docs/observability.md).
|
|
24
|
+
- `createProgressGuard()`: an opt-in hook that adds advice to repeated tool failures, denies calls that keep returning the same result and tools that keep failing the same way, and stops the run with `LOUSHO_RUN_NO_PROGRESS` if denials continue. Actions stream as the new `heal.action` event; tool hooks get `ctx.emit`, and tool spans record `lousho.tool.replaced_by_hook`. See [Hooks](docs/hooks.md#stop-a-run-that-makes-no-progress).
|
|
25
|
+
|
|
26
|
+
## [1.0.0-alpha.31] - 2026-11-01
|
|
27
|
+
|
|
28
|
+
- **Behavior change:** the package is renamed from `@lousho/build-ai-agent` to `lousho`, and the scaffolder from `create-lousho-agent` to `create-lousho` (`npm create lousho`). Update imports from `@lousho/build-ai-agent` to `lousho` (subpaths such as `lousho/testing` keep their names). Install with `npm install lousho` or `npm create lousho` (no token needed). The packages are also mirrored to GitHub Packages as `@linuxdevil/lousho` and `@linuxdevil/create-lousho` for the repository's Packages tab.
|
|
29
|
+
|
|
30
|
+
## [1.0.0-alpha.30] - 2026-10-31
|
|
31
|
+
|
|
32
|
+
What is on `main` and not yet on npm. Entries marked **Behavior change** can
|
|
33
|
+
break working code; [Upgrading](docs/upgrading.md) lists each one with its
|
|
34
|
+
opt-out.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- The `lousho observe` daemon: a local collector (loopback, `http://127.0.0.1:7777`, SQLite at `~/.lousho/observe.db`) that gathers every agent's traces, heartbeats and eval scores when `LOUSHO_OBSERVE` is set — no per-agent code changes. `lousho observe start|status|stop|rebuild-rollups|token create`; a second `start` on a busy port attaches when it is already the observe daemon. See [Observability](docs/observability.md#the-observe-daemon).
|
|
39
|
+
- Agent Forge is rebuilt around a left navigation grouped Build (Agents, Playground), Observe (Traces, Threads, Monitoring), Evaluate (Evaluations: Scores, Datasets, Evaluators, Annotation queues) and Assets (Prompts, Context Hub), plus Settings. It replaces the Build/Observe toggle and the old Observe tabs, and the Build toolbar shows only on Agents. See [Agent Forge](docs/agent-forge.md#navigation).
|
|
40
|
+
- Every Studio screen has a hash route with its filters in the query string (`#/traces?agent=…&status=error`, `#/traces/<id>?obs=<step>`), so a reload, a bookmark, a pasted link or back/forward lands on the same view. See [Routes and deep links](docs/agent-forge.md#routes-and-deep-links).
|
|
41
|
+
- Studio keyboard: `Ctrl+K` / `Cmd+K` opens a command palette (go to a screen, open an agent, open a pasted trace id, toggle the light/dark theme), `?` lists the shortcuts, and `j`/`k`/`Enter` move through and open table rows. See [Keyboard](docs/agent-forge.md#keyboard).
|
|
42
|
+
- The last Studio nav item shows whether the collector answers. While the daemon is down, each daemon screen shows the command to start it and fills in once it is back. Daemon screens read it through the token-gated `/observe/**` proxy on the studio server, which forwards reads plus only the writes the screens make. See [The collector](docs/agent-forge.md#the-collector-observe-daemon).
|
|
43
|
+
- Studio Traces and Trace screens: the list filters by search (name, input, output), agent, status, model, project, prompt and time range (15m to 30d or custom, default 24 h), sorts by duration, tokens or cost, and shows input/output previews, the error message and the model. A trace opens in one viewer (tree or waterfall, the failed step selected by default) whose inspector shows the error, input and output as chat bubbles with tool-call chips (Rendered, Raw or JSON), thumbs up/down feedback, usage and attributes; the header links the agent and thread and has **Copy link**, **Add to dataset** and **Add to queue**. Model-call steps carry **Open in Playground**. See [Traces](docs/agent-forge.md#traces).
|
|
44
|
+
- Studio Threads (`#/threads`, one row per session, a session's turns oldest first), Monitoring (`#/monitoring`: the fleet table with status, last heartbeat, runs today, error rate, p95 latency and dropped events, plus Runs, Latency p95, Error rate, Cost and Tokens charts over 1h to 30d whose buckets open the matching traces) and Scores (`#/evals/scores`: one row per eval with pass rate, trend, per-check pass rates and latest failures, filtered by eval, agent, source and range). See [Monitoring](docs/agent-forge.md#monitoring).
|
|
45
|
+
- Studio Playground (`#/playground`): system, user and assistant messages with `{name}` variables, a provider and model (`mock` with no key), tools borrowed from a saved agent, a streamed run with **Stop**, **Open trace** when it finishes, and **Save as new prompt version**. See [Playground](docs/agent-forge.md#playground).
|
|
46
|
+
- Playground deep links: `#/playground?trace=<id>&fromObs=<obs>` loads a model call from a trace, and `#/playground?prompt=<name>[@<ref>]` loads a registry prompt.
|
|
47
|
+
- `POST /playground/run` on the studio server runs a playground request and streams SSE `delta`, `tool_call`, `done` and `error` events; runs report to the daemon as agent `playground`. See [Playground run endpoint](docs/agent-forge.md#playground-run-endpoint).
|
|
48
|
+
- Studio Prompts (`#/prompts`: versions, labels, runs that used each version, a side-by-side diff, **Move `<label>` here**, **Open in Playground**) and Context Hub (`#/hub`: namespaces viewed as one agent, `private` or `fleet` scope, and editing a fleet entry). See [Prompts](docs/agent-forge.md#prompts).
|
|
49
|
+
- The Agents builder: node cards show the model, tool count and status; an empty canvas offers templates; Inspector sections collapse and the bottom drawer resizes (both remembered); and a run's status pill links **Open trace**. Builder runs report to the observe daemon under their agent id, with captured content kept out of the project's trace files. See [Agents](docs/agent-forge.md#agents).
|
|
50
|
+
- Datasets: the daemon stores named datasets of `{ input, expected? }` examples (`GET`/`POST /api/v1/datasets`, `GET /api/v1/datasets/:name`, `POST /api/v1/datasets/:name/examples` with an example or `{ from_trace_id }`, which copies a trace's root input and output, and `DELETE …/examples/:id`). `defineEval({ dataset: 'name' })` in place of `cases` runs one test per example (`c` is `{ input, expected, label, example_id, source_trace_id }`) and fails with `LOUSHO_DATASET_UNAVAILABLE` when the daemon cannot serve it. Studio's `#/evals/datasets` lists, creates and browses datasets and deletes examples, and a trace's **Add to dataset** copies it into one. See [Datasets](docs/observability.md#datasets) and [Evals](docs/evals.md#from-the-daemon).
|
|
51
|
+
- Online evaluator rules: `/api/v1/evaluators` holds rules with a `filter` on agent, status and name, a `sample_rate` and an `enabled` switch. Each enabled rule scores new traces as their root ends, as `source: online` scores named after the rule. See [Online evaluator rules](docs/observability.md#online-evaluator-rules).
|
|
52
|
+
- Code evaluators: a `code` rule is a function `(trace) => number | { value, comment }` the daemon runs in the isolate sandbox. It needs `isolated-vm`, plus `NODE_OPTIONS=--no-node-snapshot` on Node 20+; without them code rules are skipped with one warning. `POST /api/v1/evaluators/:id/test` dry-runs a rule on one trace without writing a score. See [Online evaluators](docs/observability.md#online-evaluators).
|
|
53
|
+
- Judge evaluators: a `judge` rule is a model and a rubric. The daemon queues a job per matching trace, and Agent Forge runs the judge with its provider keys (a key saved in Settings, or the provider's env var; the saved key wins), so judges run only while Studio is running. `LOUSHO_STUDIO_JUDGE=0` turns Studio's judge loop off. Studio's `/observe/api/v1/evaluators/:id/test` dry-runs judge rules too. See [Judge rules](docs/observability.md#judge-rules-through-agent-forge).
|
|
54
|
+
- Studio Evaluators screen (`#/evals/evaluators`): lists the rules with their last run and runs in the last 24 hours, and creates, edits, enables, disables and deletes them. **Test on trace** dry-runs a rule on one trace id. The judge kind stays disabled until Studio has a provider key, saved in Settings or set in the provider's env var. See [Evaluators](docs/agent-forge.md#evaluators).
|
|
55
|
+
- Annotation queues: `/api/v1/queues` holds named lists of traces with an optional rubric (`name`, `description`, `min`, `max` per score); `GET /api/v1/queues/:id/next` returns the oldest pending item and `POST …/items/:item/complete` writes its scores as human scores (or skips it). Studio's `#/evals/queues` creates queues and reviews them one trace at a time beside the rubric (Enter submits, `S` skips), and a trace's **Add to queue** adds it. See [Annotation queues](docs/observability.md#annotation-queues).
|
|
56
|
+
- Human feedback scores: `POST /api/v1/scores` `{ trace_id, obs_id?, name, value, comment? }` adds a `source: human` score (`201 { score_id }`; unknown trace or step `404`). Each eval case also records one `<evalName>` score: 1 when no gate check failed and the case did not throw, else 0 with the failed gates in the comment. See [The observe daemon](docs/observability.md#the-observe-daemon).
|
|
57
|
+
- Prompt labels: a label (`prod`, `staging`) points at one version; `lousho prompt label <name> <label> <version>` (or `PUT /api/v1/prompts/:name/labels/:label`) moves it, `push --labels` sets it on the pushed version, and `createAgent({ prompt: 'name@prod' })`, `lousho prompt get name@prod` and `GET /api/v1/prompts/:name/:ref` resolve it. A push whose text and variables equal the latest version's mints nothing (`unchanged: true`). See [`lousho prompt`](docs/cli.md#lousho-prompt).
|
|
58
|
+
- Trace list API: `GET /api/v1/traces` takes `from`/`to` (half-open ms), `q` (substring of name, input or output), `min_duration_ms`, `model`, `prompt` (`<name>[@<version>]`), `sort` (`start`, `duration`, `tokens`, `cost`) and `order`, and rows add `agent_name`, `model`, `error_message` and input/output previews. See [Trace list API](docs/observability.md#trace-list-api).
|
|
59
|
+
- Sessions API: `GET /api/v1/sessions` (filter by agent, status and start window, paged) and `GET /api/v1/sessions/:id` (the session and its traces, oldest first) with per-session trace counts, tokens, cost and last input. See [Sessions API](docs/observability.md#sessions-threads-api).
|
|
60
|
+
- Retention: once a day the daemon deletes finished traces whose last observation ended more than `lousho observe start --retention-days N` ago (default 30, `0` keeps everything), with their scores and prompt-use links, plus older scores whose trace is gone and sessions that are not `active` and no run references. A trace with a run still open and an `active` session are kept. Rollups, prompts, hub entries, the agent registry, tokens, datasets, evaluator rules and annotation queues are never pruned. `lousho observe prune --older-than <N>d` prunes now and runs `VACUUM`. See [Retention](docs/observability.md#retention).
|
|
61
|
+
- `lousho observe export [--since 7d] [--agent X] [--out file]` writes traces as JSONL (`{ trace, observations, scores }` per line) straight from the db, without a running daemon. See [Export](docs/observability.md#export).
|
|
62
|
+
- Insights series carry `p95_latency_ms` and `error_rate` per agent and bucket. A db written by an older daemon lacks them for past buckets: run `lousho observe rebuild-rollups` once to backfill. See [The observe daemon](docs/observability.md#the-observe-daemon).
|
|
63
|
+
- Heartbeats carry the reporter's dropped-event count, and each `/api/v1/agents` row shows it as `dropped` (`null` for older SDKs); Studio's Monitoring fleet table shows it. See [The observe daemon](docs/observability.md#the-observe-daemon) and [Monitoring](docs/agent-forge.md#monitoring).
|
|
64
|
+
- Prompt registry: `createAgent({ prompt: 'name@version' })` or `LOUSHO_PROMPT` resolves versioned instructions from the daemon at build; every run records the version used. `lousho prompt push|list|get` manages it. When the daemon is unreachable, embedded `instructions` are used; with no `instructions` the agent fails closed: every run rejects with `LOUSHO_PROMPT_UNRESOLVED` instead of running without a system prompt. A failed fetch is not cached for good: runs in the next 30 seconds resolve at once, then the next run fetches again (waiting up to 2.5 seconds).
|
|
65
|
+
- Context hub: `hub()` / `hubTools()` publish and query namespaced shared memory scoped `private` (the writing agent) or `fleet` (every agent on the daemon).
|
|
66
|
+
- Fleet monitoring: heartbeat-derived agent status, `agent.deregister` on graceful exit, a crashed agent's open sessions and unfinished observations flip to `interrupted`, and webhook alerts (`--webhook`, `alerts.json`) on down transitions and error-rate thresholds.
|
|
67
|
+
- Eval scores report to the daemon: every `AssertionResult` lands as a score row on the exercised run's trace (`source: eval` or `judge`); `lousho eval --observe` forces emission. Each case's `eval` span and score rows are flushed when the case finishes, each wait capped at 1 second; the scores share the reporter's queue, so they go out after the span and are retried while the daemon is down. A wait that runs out skips the waits of the next 30 seconds of cases.
|
|
68
|
+
- Reporting to the daemon: spans that finished without an error report status `ok`. A down daemon's batches are requeued and retried with exponential backoff (1 s up to 30 s); batches are capped at 1000 events and 4 MB, and a batch the daemon refuses (`400`/`413`) is dropped and counted in `dropped`. The queue holds at most 5000 events or 32 MB, oldest evicted first. A process dying of an uncaught exception or unhandled rejection sends the newest 16 MB it holds, blocking for at most 1 second; a graceful exit makes one final attempt capped at about 2 seconds. A run's root span is reported when the run starts, so a run whose process is killed is still listed and turns `interrupted`.
|
|
69
|
+
- `LOUSHO_OBSERVE` accepts `1`/`true`/`on`/`yes`, an `http(s)://` URL, or `0`/`false`/`off`/`no`/`none`/`disabled`/empty (any case). Any other value warns once naming `LOUSHO_OBSERVE` and disables reporting, instead of being posted to as a URL.
|
|
70
|
+
- With `LOUSHO_OBSERVE` pointing at a loopback daemon, `createAgent` captures message and tool content for the daemon by default (`captureContent: false` turns it off). Only the daemon gets it: the agent's own `exporter` (trace files, OTLP or vendor exporters, the built server's OTel export) still records no content unless the agent sets `captureContent: true`. Captured content goes through `redactContent` first (see the scrubber entry under Changed). See [Observability](docs/observability.md#message-and-argument-content-is-opt-in).
|
|
71
|
+
- Each observe daemon writes its own pidfile beside its db (`foo.db` gives `foo.pid`), so two dbs in one folder never share one. `lousho observe start` exits 1 when the port already serves another db, and `stop` refuses to signal when the daemon on the pidfile's port reports another db or answers as a different service. `/health` names the db it serves only to callers on the same machine. A daemon started with `--db foo.db` by an earlier build of this feature wrote the shared pidfile and is not found by the new `stop`: stop it before updating.
|
|
72
|
+
- `lousho observe start` and Studio print the `LOUSHO_OBSERVE` value for the port and host the daemon really uses (`LOUSHO_OBSERVE=http://127.0.0.1:7788` on a non-default port), and the Studio proxy answers `/observe/__target` with the daemon URL reporters should use.
|
|
73
|
+
- `sandbox({ driver: 'isolate' })` runs tool code in an `isolated-vm` V8 isolate (optional peer `isolated-vm`); `defineTool({ sandbox })` opts a tool in, timeouts and OOM map to `sandbox.error` on the tool span. `'docker'` and `'e2b'` driver names are reserved for later drivers.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- **Behavior change:** content captured for traces is scrubbed for secrets by default. `redactContent` is `boolean | ((text) => string)`: unset or `true` runs a built-in scrubber over message text, system instructions, tool-call arguments and tool results that replaces API-key shapes (`sk-…`, `lsv2_…`, GitHub tokens, AWS access key ids, the token after `Bearer`) with `[redacted]`; a function replaces the scrubber (text it throws on is recorded as `[redacted]`); `false` records content as is. It catches common key formats, not every secret, so set `captureContent: false` if content must never reach the db. See [Secrets in captured content](docs/observability.md#secrets-in-captured-content).
|
|
78
|
+
- **Behavior change:** sessions end. The daemon marks a session `ended` once none of its runs is open and nothing in it has started or finished for `lousho observe start --session-idle-minutes N` (default 30, `0` turns it off); `ended_at` is the last activity, and a new run makes it `active` again. Sessions in an existing db close on the first sweep that finds them idle, and ended sessions then age out with retention. See [Idle sessions](docs/observability.md#idle-sessions).
|
|
79
|
+
- **Behavior change:** the daemon keeps 30 days of runs by default. On its first sweep after start, and daily after that, it deletes traces whose observations all finished more than `--retention-days` ago, with their scores; a trace with a run still open, and a session still `active`, is kept. Start it with `--retention-days 0` to keep an existing db's history. See [Retention](docs/observability.md#retention).
|
|
80
|
+
- Studio redesign: a new look in light and dark (Settings > Appearance picks Light, Dark or System, plus Compact or Comfortable table density), a left nav that collapses to an icon rail, and a Settings screen in sections (among them Appearance, Provider keys with Set / From environment / Not set, Collector, Keyboard shortcuts). Traces gains **Refresh** and a **Live** toggle that refreshes every 5 seconds, the trace viewer switches between **Tree** and **Waterfall**, Evaluations moves between Scores, Datasets, Evaluators and Annotation queues with tabs, Ctrl/Cmd+Enter runs the Playground, and the Agents canvas opens with the whole graph fitted and the Inspector folded until you select a node. See [Agent Forge](docs/agent-forge.md).
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- Studio filter bar: the search box keeps what you type — a trailing space (`tool `) no longer gets reset when the URL echoes the trimmed query; clearing filters or navigating still updates it.
|
|
85
|
+
- Insights runs/day averages over the days an agent was active in the window, so an agent first seen today with 12 runs reports 12/day instead of 12/7.
|
|
86
|
+
- Studio threads: each turn now shows its full input and output (chat messages render as bubbles) instead of the 200-char previews; a turn fetches its trace as it scrolls near the viewport, shows the preview labelled "Preview (truncated)" while loading or if the trace can't be read, and says so when the recorder truncated the turn's I/O.
|
|
87
|
+
- An open run's root start is kept when the reporter's buffer trims or a crash flush cuts to its newest 16 MB, so a run whose child spans overflow the buffer is still listed.
|
|
88
|
+
- Studio Scores and `GET /api/v1/scores?aggregate=eval`: `human` and `online` scores now pass only on a value of exactly 1, so a thumbs-down or an online rule returning 0 (or 0.9) without a comment counts as a failure instead of a pass; `eval` and `judge` scores keep their rule (no failure comment, or a perfect 1), and the table and the eval detail agree.
|
|
89
|
+
- Studio: the command palette and `?` shortcuts sheet now keep every keydown to themselves, so `S` no longer skips the annotation-queue item behind them; Traces, Threads, a Thread, Prompts and the Context Hub recover in place after the daemon restarts instead of staying on "daemon down"; and the queue review's trace tree no longer wraps a row name one letter per line at 1280px.
|
|
90
|
+
- The run inspector shows `error`, not `running`, for a failed step that has no end time.
|
|
91
|
+
- Studio trace viewer: an observation the crash sweep marked `interrupted` (or an open `error` one) now shows as terminal instead of "running" forever; its duration and the waterfall stop at the latest known end and the header clock stops ticking.
|
|
92
|
+
- The observe daemon refuses cross-site browser writes and DNS rebinding on `/api/*`: a loopback-bound daemon answers only loopback `Host` names (403), writes carrying a non-loopback `Origin` (including `null`) or `Sec-Fetch-Site: cross-site` get 403, and a write with a body must be `Content-Type: application/json` (415) — `curl -d` needs `-H "Content-Type: application/json"`. The SDK reporter, CLI and Studio already comply. See [Observability](docs/observability.md).
|
|
93
|
+
- Observe retention (`lousho observe start --retention-days`, `lousho observe prune`) also deletes old traces the sweep marked `interrupted` (a crashed run whose end never arrived); their age counts from when they were interrupted.
|
|
94
|
+
- The observe daemon's online evaluator worker no longer blocks the daemon: it yields between traces, checks each rule's filter against the root row before loading anything else, hands code rules at most 200 observations (attrs over 8192 characters become `{ truncated: true }`) and no scores, and judge jobs claimed by Agent Forge carry the root fields only (`observations: []`).
|
|
95
|
+
- Error text recorded on spans (`error` attributes and the span status message, on runs, LLM calls, tools, resumed runs and flows) goes through the same `redactContent` choice as captured content, so tokens in an error no longer reach traces, exports, evaluators or judge prompts; the built-in scrubber also catches case-insensitive `Bearer`, Stripe `sk_`/`rk_` live and test keys, Slack `xox[abprs]-` tokens, Google `AIza` keys and JWTs. `recordSpanError` and `withSpan` take an optional redactor (default: the built-in scrubber).
|
|
96
|
+
|
|
97
|
+
### Documentation
|
|
98
|
+
|
|
99
|
+
- [Agent Forge](docs/agent-forge.md) documents the left navigation, every screen, the deep-link routes and the keyboard shortcuts, with screenshots in `docs/images`; [Observability](docs/observability.md), [CLI](docs/cli.md) and [Evals](docs/evals.md) link to the screens. The screenshots come from an opt-in Playwright spec: `LOUSHO_DOC_SHOTS=1 npx playwright test -c apps/agent-forge/playwright.config.ts docShots`. See [Keyboard](docs/agent-forge.md#keyboard) and [Routes and deep links](docs/agent-forge.md#routes-and-deep-links).
|
|
100
|
+
|
|
101
|
+
### Security
|
|
102
|
+
|
|
103
|
+
- Agent Forge's studio server requires its per-launch token on every API route regardless of path case or encoding: `/PLAYGROUND/run`, `/Observe/api/v1/scores`, `//settings/...` or `/%70layground/run` no longer skip the check. Routing is case-sensitive, and every request except a `GET`/`HEAD` of `/health` or the built client needs the token.
|
|
104
|
+
- Agent Forge's `/observe/**` proxy only ever forwards to the `LOUSHO_OBSERVE_URL` daemon: the upstream URL is built from the request's path and query alone, so an absolute-form request line (`GET http://a@evil.example/observe/...`) can no longer point it at another host, and a path that URL parsing would rewrite (`..`, `%2e`, a literal `\`) is refused with 400 instead of reaching a write route the allowlist does not name. The proxy sends `LOUSHO_OBSERVE_TOKEN` as the bearer to a daemon that requires one, and the Playground's "Save as new prompt version" (`POST /api/v1/prompts`) is forwarded instead of answering 405.
|
|
105
|
+
|
|
106
|
+
## [1.0.0-alpha.29] - 2026-10-21
|
|
107
|
+
|
|
108
|
+
### Changed
|
|
109
|
+
|
|
110
|
+
- **Behavior change:** the built node-server and Docker image refuse to start on a non-loopback host when no auth is configured (no `LOUSHO_API_TOKEN`, build token or `auth.ts`), with `LOUSHO_SERVER_UNPROTECTED`. Set a token, add `auth.ts`, bind to `127.0.0.1`, or set `LOUSHO_ALLOW_PUBLIC=1` to serve an open agent on purpose. `lousho build --target=docker` prints the run command with `-e LOUSHO_API_TOKEN=<token>`.
|
|
111
|
+
- The `limits.maxCostUsd` warning for a model with no known price names the model, says the limit is not enforced for it, and shows the `registerModel()` call that fixes it. It is printed once per model instead of on every step.
|
|
112
|
+
- Scaffolded READMEs link to the docs site (`https://lousho.com/quickstart`) instead of the GitHub file on `main`.
|
|
113
|
+
- [Sessions](docs/sessions.md#sharing-a-sqlite-file) has one statement on sharing a SQLite store, used across the docs: several processes on one host and a local disk are fine; network filesystems and multiple hosts are not.
|
|
114
|
+
|
|
115
|
+
- **Behavior change:** the built node-server and Docker image resume runs a crash left in progress when they start (`agent.recover()`), and check again every 30 seconds until shutdown. Runs paused on an approval or a question are left alone, and a run another replica holds is skipped. `LOUSHO_RECOVER_ON_START=0` turns it off. See [Crash recovery](docs/deployment.md#crash-recovery).
|
|
116
|
+
- **Behavior change:** `createAgent()` and `defineTool()` reject an option key they don't know with `LOUSHO_CONFIG_INVALID` and a suggestion (`requireApproval` → `needsApproval`, `maxOutputTokens` → `modelSettings.maxTokens`) instead of ignoring it. A mistyped `needsApproval` used to run the tool unapproved. Keys set to `undefined` are ignored.
|
|
117
|
+
- **Behavior change:** the workspace file tools deny `.env` and `.env.*` by default as well as `.git`. `.env.example`, `.env.sample` and `.env.template` stay readable. A `deny` list you pass still replaces the defaults.
|
|
118
|
+
- `POST /chat` answers `409` with `Retry-After: 2` and `code: LOUSHO_SESSION_BUSY` when another process or replica is running the session, instead of a `200` stream holding only the error. An `Idempotency-Key` resend that hits a running turn gets the same `409`.
|
|
119
|
+
- Scaffolded agents (`lousho init`, `create-lousho-agent`) set `modelSettings: { maxTokens: 4096 }`, so a first run on a low-credit OpenRouter key no longer fails with "requires more credits, or fewer max_tokens". The generated README explains the error.
|
|
120
|
+
- Error `docs` links point to `https://lousho.com/errors#<code>` instead of the GitHub file on `main`.
|
|
121
|
+
- The Docker image creates `/app/data` owned by `node` and declares it a `VOLUME`, so a fresh named volume works with `LOUSHO_STORE=sqlite:/app/data/lousho.db`.
|
|
122
|
+
- The [Authentication](docs/auth.md) examples read secrets from the environment and fail at startup when they are missing, instead of falling back to a literal secret.
|
|
123
|
+
|
|
124
|
+
- **Behavior change:** the built node-server (and the Docker image) now guards `POST /channels/<name>` and its approvals route for a channel without `verify` with `LOUSHO_API_TOKEN` / `auth.ts`, the same as `/chat`. Before, such a channel answered anyone even with a token set. Channels that verify their own requests (Slack, Discord, Telegram, GitHub, Teams, `webhookChannel` with a secret, any channel with `verify`) are unchanged, and the start-up log lists each channel route's guard (`self-verified`, `server-auth` or `open`).
|
|
125
|
+
- **Behavior change:** `webhookChannel` replies with `{ sessionId, text, finishReason, approval? }` (plus `approvalPrompt` on a pause), as `httpChannel` does, instead of the full `ExecutionResult`. The full result echoed the system prompt and the whole transcript to the caller. `fullResult: true` restores the old body.
|
|
126
|
+
- Channels answer a turn on a session that another process or replica is running (`LOUSHO_SESSION_BUSY`) with `409` and `Retry-After: 2` instead of a `500`, and report a turn that fails before the request is acknowledged to `onError` with stage `'turn'` (it said `'parse'`).
|
|
127
|
+
- README and [Comparison](docs/comparison.md) no longer list ACP and MCP as differentiators, and describe Agent Forge as a local visual console.
|
|
128
|
+
|
|
129
|
+
- CI is one workflow with two jobs: `ci` (type checks, lint, build, tests with coverage, docs, API and fallow gates, pack-smoke, size check, and a zod 4 type check) and `compat` (the newest peers, `ai` 7 with zod 4, plus the real-Docker sandbox tests). The separate Docker Engine, Agent Forge E2E and weekly registry-smoke workflows and the `ai` 6 / zod 3 matrix entries are gone; `npm run registry-smoke` and `npm run test:e2e:studio` still run locally. When the fallow gate fails, CI now prints its full health and duplication reports.
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
- `GET /ready` on the built node-server and Docker image: `200 {"status":"ready"}` while serving, `503 {"status":"draining"}` once shutdown starts. `/health` is unchanged. See [Deployment](docs/deployment.md).
|
|
133
|
+
- The built server exports OpenTelemetry traces over OTLP/HTTP when `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) is set, next to any exporter the agent already has. It loads `@opentelemetry/sdk-trace-node`, `@opentelemetry/resources` and `@opentelemetry/exporter-trace-otlp-http` only then, says how to install them when missing, and honours `OTEL_SERVICE_NAME`. See [Observability](docs/observability.md).
|
|
134
|
+
- `createAgent({ toolErrorMessage })` replaces the text of a thrown tool error (`kind: 'execution'`) before it reaches the model, `tool.error` events and hooks; a sanitizer that throws gives "The tool failed.". See [Tools](docs/tools.md).
|
|
135
|
+
- `lousho init` and `create-lousho-agent` generate an `AGENTS.md` for coding agents (the project's real scripts, conventions and docs links) and a `CLAUDE.md` that imports it.
|
|
136
|
+
- `lousho add` falls back to the registry bundled in the npm package (`registry/dist`) when `registry.lousho.com` and its GitHub fallbacks are unreachable, with a notice that the bundled copy may be older.
|
|
137
|
+
- [Pause, restart and resume](docs/pause-and-resume.md): one tutorial for an approval that waits across a restart, crash recovery, and resending over HTTP.
|
|
138
|
+
- `SECURITY.md`: supported versions, private reporting through GitHub, and the disclosure policy.
|
|
139
|
+
- `httpChannel()` created without `verify` logs one warning that the channel is unauthenticated and its `sessionKey` values are guessable. [Channels](docs/channels.md) shows `verify` in the quick start, and [Deployment](docs/deployment.md) says which routes `LOUSHO_API_TOKEN` guards.
|
|
140
|
+
- `POST /chat` accepts an `Idempotency-Key` header: a resend with the same key replays the finished turn's reply (`Idempotent-Replayed: true`, nothing runs), resumes a turn a crash interrupted instead of appending the message again, and is a `409` (`LOUSHO_SESSION_BUSY`) while that turn still runs. The key is stored on the turn's user message (`metadata.idempotencyKey`), so it survives restarts; `session.pending()` reports it as `idempotencyKey`. See [Resending a turn](docs/deployment.md#resending-a-turn).
|
|
141
|
+
- `fromAiSdkTools(record)` converts Vercel AI SDK tools (`tool({ ... })` or plain `{ description, inputSchema | parameters, execute, needsApproval? }` objects) into Lousho tools, keyed by name: zod and Standard Schemas are kept, `jsonSchema()` wrappers and plain JSON Schemas are converted (their own `validate` still runs), `needsApproval` (boolean or function) pauses the run for approval, and `execute` gets the call id and the run's abort signal. It imports nothing from `ai`, so tools built on `ai` 4 to 7 work with any installed version. New guide: [Migrating from the AI SDK](docs/migrating-from-ai-sdk.md).
|
|
142
|
+
- `lousho eval` without vitest installed now says it runs `*.eval.ts` files through vitest, the directory it searched from, and the install command; [Evals](docs/evals.md) states the requirement up front.
|
|
143
|
+
- `result.usage.delegated.toolErrors`: the tool calls that failed inside a run's sub-agents (and theirs), which the lead's own `result.toolErrors` does not count. `ToolCallContext.onDelegatedUsage` takes the child's `toolErrors` as an optional second argument.
|
|
144
|
+
- The session HTTP API (`createRouteHandler()`, the deployed node server, the Worker, `lousho dev`) writes an SSE comment (`: ping`) after 15 seconds without an event, so proxies and client idle timeouts no longer drop a turn whose model or tool is slow; `heartbeatMs` changes the interval (`0` turns it off). See [HTTP API](docs/deployment.md#http-api).
|
|
145
|
+
- `google/<model>` model strings (e.g. `createAgent({ model: 'google/gemini-2.5-flash' })`) and a `GoogleProvider`, backed by the optional peer `@ai-sdk/google` (`^1.2.0` with `ai` 4, `^3.0.0` with `ai` 6, `^4.0.0` with `ai` 7) and reading `GOOGLE_GENERATIVE_AI_API_KEY`. `lousho init --provider google`, `lousho doctor` (package pairing, key, `--ping`) and the missing-peer install hint cover it. See [Providers](docs/providers.md).
|
|
146
|
+
- An unknown model prefix (`mistral/...`, `xai/...`) now says how to reach the vendor anyway: `openrouter/<vendor>/<model>`, `pi/<vendor>/<model>` when pi serves it, or `fromAiSdk()` with the vendor's AI SDK package. See [Vendors without a prefix](docs/providers.md#vendors-without-a-prefix).
|
|
147
|
+
- `lousho doctor` fails an installed `ai` 5 (AI SDK v5 is not supported) with an upgrade hint to `ai` 6 or 7 and the matching `@ai-sdk/*` majors.
|
|
148
|
+
|
|
149
|
+
### Fixed
|
|
150
|
+
- `lousho build` works again in a project without the optional `@opentelemetry/*` packages (every fresh scaffold): the OTel exporter added for the built server is loaded only when `OTEL_EXPORTER_OTLP_ENDPOINT` is set, and the build leaves those packages unresolved instead of failing with `Could not resolve "@opentelemetry/api"`. The scaffold end-to-end test now builds the server and checks `/health`.
|
|
151
|
+
- The `httpChannel()` "no verify" warning no longer says the channel is unauthenticated when the built server guards it with `LOUSHO_API_TOKEN` or `auth.ts`, and [Channels](docs/channels.md) shows `webhookChannel`'s trimmed reply.
|
|
152
|
+
- `lousho add` works again when `registry.lousho.com` is unreachable: the fallback now tries the release tag as the repository names it (`1.0.0-alpha.24`), then the `v`-prefixed tag, then `main`. It asked only for `v<version>`, which 404'd.
|
|
153
|
+
- The file tools' `deny` list (`.git` by default) is now also checked where a path really leads: a symlink or Windows junction inside the workspace pointing at `.git` or into it (`gitj -> .git`) let `edit_file gitj/config` rewrite `.git/config` (for example `core.hooksPath`, which runs code on the next git command) and `write_file gitj/PWNED` create files in `.git`. Reads, writes, edits, `list_dir`, and `glob`/`grep` given such a link as `path` are now refused. Providers with links can implement the new optional `FsProvider.realPath(path)`; `NodeWorkspace` does. See [Workspace tools](docs/workspace-tools.md#security-model).
|
|
154
|
+
- `NodeWorkspace.writeFile` (and so `write_file` / `edit_file`) writes a temp file in the same directory and renames it into place, so a process killed mid-write no longer leaves a truncated file. A replaced file keeps its permission bits; a read-only file is still refused.
|
|
155
|
+
- `WorkspaceCheckpoints` stores a file's backup before `write_file` / `edit_file` writes it, not after. A process killed between the write and the backup lost the original: on resume the re-run call backed up the agent's own content, so `rewind()` reported `restored` but put back the agent's version (or, for `edit_file`, recorded nothing). A re-run now keeps the turn's first backup, and rewind treats a file still holding its backed-up content as nothing to undo. `track()` also accepts a `PreparedWorkspaceWrite` (`{ prepare, write }`), which the built-in tools use.
|
|
156
|
+
- `edit_file` re-run after a crash (the edit already in the file: `old_string` gone, a non-empty `new_string` present, also with `replace_all`) now succeeds with "the edit was already applied" and writes nothing, instead of failing with `old_string was not found` and sending the model into a loop. A `write_file` / `edit_file` that leaves the file unchanged no longer records a checkpoint backup.
|
|
157
|
+
- [Sessions](docs/sessions.md) no longer says to call `resume()` before resolving an approval after a restart: `agent.approvals.resolve()` / `streamResolve()` from a fresh process finds the session itself. It and [Build a coding agent](docs/build-a-coding-agent.md) now say how to show a resumed turn's progress (`session.on()`, `onEvent`), and the guide points long coding turns at compaction.
|
|
158
|
+
- MCP tool schemas with OpenAPI's `nullable: true` (any type) now accept `null`; the model's valid `null` was rejected with `ToolArgumentsValidationError`.
|
|
159
|
+
- A failed MCP tool call (server exited, timeout, protocol error) now rejects with `LOUSHO_MCP_CALL_FAILED`, naming `<server>__<tool>` with the MCP error as `cause`, instead of a bare `MCP error -32000: Connection closed`. [MCP](docs/mcp.md) now says stdio `env` is merged onto the MCP SDK's default allow-list, not `process.env`.
|
|
160
|
+
- Flows (consumer harness): a `{{variable}}` holding an object (a `toolCall` result, an MCP result) no longer reaches a prompt as `[object Object]`: an object whose only field is `text` (an MCP tool result) is that text, a raw MCP `{ content }` result its text parts, and any other object or array its JSON. Placeholders take dotted paths (`{{res.user.name}}`, `{{list.0}}`, own properties only), also as whole tool-argument values. An `evaluator` step stores its result in `outputVariable`. See [Template values](docs/flows.md#template-values).
|
|
161
|
+
- Flows: a durable run is saved before its first step, so a run whose first step failed can be `resume()`d instead of failing with `LOUSHO_CHECKPOINT_NOT_FOUND`, whose hint for a flow run now points at `FlowExecutor.execute()` rather than forking session history. A step's checkpoint is saved before its `step-complete` event, so a crash in between no longer re-runs a step reported complete.
|
|
162
|
+
- Flows: an `llmCall` whose model request fails rejects with the same compacted `CompactedLLMProviderError` as an agent run (`LOUSHO_PROVIDER_*` code, `compacted.category`), not the provider SDK's raw `APICallError`, and its `retry` no longer retries a failure marked `retryable: false` (a 401, an exhausted quota, a too-long prompt).
|
|
163
|
+
- A provider answering a streaming request with HTTP 200 and a body that is neither an event stream nor valid JSON (for example a truncated `{"choices": [ {"oops`) no longer ends as a silent empty reply. It now fails as a retryable 502, so `withRetry` retries it and an exhausted retry surfaces as a provider error.
|
|
164
|
+
- A sub-agent with an `output` schema that ran out of steps and gave a valid object in its forced final call is now a `task` result (the object, with a line saying the steps ran out), not a failure telling the lead it gave no final answer - which made leads re-run the task.
|
|
165
|
+
- Sub-agents are held to their lead's token, cost and duration `limits` while they run, counted with what the lead and its other running sub-agents have spent, so parallel sub-agents no longer each spend the lead's whole budget (a lead with `maxCostUsd` could end at several times it). A sub-agent stopped this way reports the limit to the lead. A spend limit stays a hard stop with no forced `output` answer; the finished tool results stay in `result.messages` ([Budgets](docs/configuration.md#budgets)).
|
|
166
|
+
- **Behavior change:** `POST /chat` to a session paused on an approval is a `409` JSON body (`LOUSHO_SESSION_AWAITING_APPROVAL`, with `approvalId` and `approvalIds`) instead of a `200` stream ending in a generic error.
|
|
167
|
+
- A decision on an approval whose winning decision is still streaming is a `409` (`LOUSHO_APPROVAL_CONFLICT`), as docs/auth.md says, instead of a `404`.
|
|
168
|
+
- The session routes' `400` bodies carry a `code`: `LOUSHO_REQUEST_INVALID` (invalid JSON, missing or empty fields), `LOUSHO_APPROVAL_DECISION_INVALID` (an approval body with neither `approved` nor `answer`), `LOUSHO_SESSION_ID_INVALID`.
|
|
169
|
+
- `lousho chat` prints a failed turn with its error code, the provider's failure category and HTTP status, and the fix (`error: User not found. (auth-failure, HTTP 401)` then `[LOUSHO_PROVIDER_REQUEST_FAILED] ...`), as docs/cli.md says, instead of the bare provider message. The terminal chat of a project scaffolded by `lousho init` / `create-lousho-agent` does the same.
|
|
170
|
+
- A `.env` saved with a UTF-8 byte order mark (PowerShell 5.1's `Set-Content -Encoding utf8`) no longer loses its first variable: the `lousho` commands and the scaffolded `src/index.ts` drop the mark before parsing. Variables already set in the environment still win.
|
|
171
|
+
- `lousho doctor src/agent.ts` loads a `.ts`/`.js` agent module the way `lousho dev` does instead of failing with `LOUSHO_SPEC_UNSUPPORTED_FORMAT`, and a missing key for the module's provider is a failure. A scaffolded project's `npm run doctor` now runs `lousho doctor src/agent.ts`, so it fails when the agent's key is missing.
|
|
172
|
+
- `lousho doctor --ping` checks OpenRouter at `/api/v1/key`, which needs the key (its `/models` answers without one, so a wrong key passed); a key the provider rejects is now a failure, and a ping of a custom `OPENAI_BASE_URL` says it shows reachability only.
|
|
173
|
+
- `lousho doctor` fix lines use PowerShell syntax (`$env:NAME="<your key>"`) on Windows.
|
|
174
|
+
- The `lousho eval` table shows the scores of passing scored gates (such as `t.check('words', n, atLeast(3))`), not only in `--json`.
|
|
175
|
+
- **Behavior change.** An agent with `output` and tools no longer sends the strict `responseFormat` schema on calls that offer tools: `openai/gpt-4o-mini` via OpenRouter then called tools on every step until `maxSteps` and never answered. `responseFormat` now goes only on calls without tools (an agent without tools, the "answer now" call at `maxSteps`, the repair step); tool-offering calls answer from the schema in the prompt, with the existing parse and repair. `output: { schema, responseFormat: 'always' }` restores the old behavior, and is the default with `promptSchema: false`. `object.delta` events still stream on every step of an `output` run. See [Structured output](docs/structured-output.md#how-it-works).
|
|
176
|
+
- An agent directory built with `lousho build --target=node-server` (or `docker`) can now change who may decide an approval and who may use a session: `auth.ts` may export functions named `authorizeApproval` and `authorizeSession` next to its default auth list, `resolveAgentDir()` returns them, and the built server applies them. Before, the built server always used the defaults, so a manager could not approve another user's paused call while that user could approve their own. See [Who may decide an approval](docs/auth.md#who-may-decide-an-approval).
|
|
177
|
+
|
|
178
|
+
### Documentation
|
|
179
|
+
|
|
180
|
+
- Tool idempotency: `toolCallId` and `ctx.approval.id` identify one call; after a crash the model may retry with a new call, so side effects that must happen once need a key from your own data. New guidance for ending a run after a rejected approval, and for extract-then-approve-then-act with structured output.
|
|
181
|
+
- [Durable execution](docs/durable-execution.md#recovering-after-a-crash): `agent.resume()` resolves with a result, not a stream (stream a resumed run with `agent.stream([], { sessionId })`), and `agent.pending()` also lists runs still active in another process. [Installation](docs/installation.md#typescript-settings): TypeScript 6+ projects need `"types": ["node"]` for the SDK's declarations.
|
|
182
|
+
|
|
183
|
+
## [1.0.0-alpha.24] - 2026-10-17
|
|
184
|
+
|
|
185
|
+
Two fixes on top of 1.0.0-alpha.23, published from the release branch.
|
|
186
|
+
|
|
187
|
+
### Added
|
|
188
|
+
- `model-unloaded` provider error category: "model is not loaded/unloaded" replies from a local server (LM Studio, Ollama) classify as retryable, so `withRetry()` rides out the server evicting or still loading a model.
|
|
189
|
+
- A run ended by `maxSteps` or a budget without a final answer now returns explanatory text instead of an empty string.
|
|
190
|
+
|
|
191
|
+
### Changed
|
|
192
|
+
- MCP: a warning is logged when a server's tool schemas exceed about 16K tokens (deferred tools excluded). A server marked `optional: true` connects in the background, so a slow or failing optional server no longer blocks the first `send()`.
|
|
193
|
+
|
|
194
|
+
### Fixed
|
|
195
|
+
- Compaction: when the provider rejects a request for context size and the configured `contextWindow` is larger than the model's real one, the error is marked on the transcript and folded into it for a retry when a compaction hook exists; the hook compacts under the server's observed window (or the rejected request size when unnamed) for the rest of the run.
|
|
196
|
+
- `toolCalls` of a run continued from a transcript (approval resolve, resume) are collected from its messages, so they no longer disappear from the result.
|
|
197
|
+
- `recall_<slot>` with a query that matches nothing now says the slot is not empty, instead of returning a bare `[]` that contradicted the injected `<memory>` block.
|
|
198
|
+
- The guardrail starter set's `ignore-instructions` signal also catches "ignore these/those/her/his/their/our instructions" and "ignore your instructions", without a qualifier such as "previous".
|
|
199
|
+
|
|
200
|
+
## [1.0.0-alpha.23] - 2026-10-16
|
|
201
|
+
|
|
202
|
+
The same tree as 1.0.0-alpha.21; no code changes. create-lousho-agent
|
|
203
|
+
0.1.2-alpha.7.
|
|
204
|
+
|
|
205
|
+
## [1.0.0-alpha.22]
|
|
206
|
+
|
|
207
|
+
Never published to npm (the registry goes from 1.0.0-alpha.21 to
|
|
208
|
+
1.0.0-alpha.23); no changes.
|
|
209
|
+
|
|
210
|
+
## [1.0.0-alpha.21] - 2026-10-16
|
|
211
|
+
|
|
212
|
+
Everything merged to `main` after 1.0.0-alpha.20. create-lousho-agent
|
|
213
|
+
0.1.2-alpha.6.
|
|
214
|
+
|
|
215
|
+
### Breaking
|
|
216
|
+
- Removed the deprecated trigger adapters and their types from `@lousho/build-ai-agent/triggers`. `TriggerAdapter`, `TriggerRegistry`, the `WebhookAuth` helpers, `verifySlackSignature()` and `parseCronExpression()` stay. Migration (see [Migrating from triggers](docs/upgrading.md#migrating-from-triggers)):
|
|
217
|
+
- `WebhookTriggerAdapter`, `WebhookTriggerAdapterOptions`, `WebhookTriggerHandle` -> `webhookChannel()` with `mountChannels()` (same `auth` option)
|
|
218
|
+
- `SlackTriggerAdapter`, `SlackTriggerAdapterOptions` -> `slackChannel()` with `mountChannels()`
|
|
219
|
+
- `CronTriggerAdapter`, `CronTriggerAdapterOptions`, `CronIntervalOptions`, `CronExpressionOptions` -> `defineSchedule()` with `startSchedules()` (no `intervalMs`: use a cron expression such as `*/5 * * * *`)
|
|
220
|
+
|
|
221
|
+
### Added
|
|
222
|
+
- `agent.recover({ intervalMs?, signal?, onError?, onRecovered? })` finishes interrupted durable runs in the background: it polls `agent.pending()` and resumes each `'in-progress'` run once its crashed holder's run lease is free, leaving approval and question pauses (and non-retryable failures) alone; returns `{ done, stop() }` ([Durable execution](docs/durable-execution.md#recovering-automatically)).
|
|
223
|
+
- An agent directory's config accepts `retry` (`agent.json`: `maxRetries`, `timeoutMs`, `backoff`, ...) and, in an `agent.ts` code config, `output` (a Standard Schema), so `lousho dev`/`chat`/`build ./agent` keep structured output and per-attempt timeouts. `loadAgentDir(dir, { output })` now returns `SimpleAgent<Infer<output>>`, so `result.object` is typed.
|
|
224
|
+
- `result.toolErrors` counts a run's tool calls that ended in an error result, so an answer the model gave after its tools failed (`finishReason: 'stop'`) is no longer indistinguishable from a good one; `limits.maxToolErrors` stops the run (`'budget-exceeded'`, `budget.limit: 'maxToolErrors'`) once that many failed. See [Failed calls in the result](docs/tools.md#failed-calls-in-the-result).
|
|
225
|
+
- An error a run rejects with carries what the run spent as `error.usage` (also on `PropagatingToolError` and your own errors). A sub-agent's spend is saved on its lead's checkpoint as it runs, so a lead resumed after a crash mid-sub-agent counts the interrupted sub-agent's model calls.
|
|
226
|
+
- A `send(msg, { sessionId })` or `agent.resume()` run that rejects records `lastError` and `attempts` on its checkpoint, and `agent.pending()` lists them (`attempts` is new for session turns too), so a restart loop can tell a failing run from a crashed one.
|
|
227
|
+
- Docs: a webhook-redelivery recipe (run a job once by checking the checkpoint before `send()`), a note on structured-output schemas that mirror a side-effect tool, and the `fetch` option for proxies and fault injection on every provider class.
|
|
228
|
+
|
|
229
|
+
### Changed
|
|
230
|
+
- **Behavior change:** the fallback default model for the OpenAI, OpenRouter and Pi providers (what `lousho init` scaffolds and what runs when no model is configured) is now `gpt-4.1-mini` instead of `gpt-4o-mini`; set `model` or `LOUSHO_MODEL` to keep the old one. README and Quick Start use it. Added `docs/comparison.md`; CI also runs on pushes to `main`; README no longer claims unverifiable live validation.
|
|
231
|
+
- `createAgent({ prompt })` is deprecated: `instructions` is the one documented name (`prompt` still works as an alias, marked `@deprecated`). Docs, README and examples now use `instructions`, and `npm run docs:verify-snippets` fails a docs snippet that passes `prompt` to `createAgent`.
|
|
232
|
+
- The published package no longer ships source maps (`dist/**/*.map`, ~3.7 MB; tsup no longer emits them). The tarball goes from 965 files / 18.9 MB unpacked / 5.0 MB packed to 774 files / 15.1 MB / 4.3 MB. `src/` still ships because `lousho build` bundles the SDK from it. `npm run size-check` now guards the tarball size and a hello-world bundle against `scripts/size-budget.json`.
|
|
233
|
+
- `docs/comparison.md` is now a dated (October 2026) per-competitor table covering the AI SDK 7, Mastra, LangGraph.js, OpenAI Agents SDK, Claude Agent SDK, eve, VoltAgent and open-harness, and the README's differentiator list keeps only claims still true against them.
|
|
234
|
+
|
|
235
|
+
### Fixed
|
|
236
|
+
- **Behavior change:** a session without a `checkpointStore` that pauses on an approval now keeps an in-process pause marker, so a second `send()` / `stream()` / `compact()` on it throws `SessionAwaitingApprovalError` (`LOUSHO_SESSION_AWAITING_APPROVAL`) — before, the new turn ran over the truncated transcript and the later `approvals.resolve()` silently overwrote the committed turn. The marker covers `send`, `stream`, `compact`, `clear`, `pending` and `discardPending`, and is dropped when the turn resolves, aborts, fails or is discarded. Across processes there is still no durable marker: a session that may pause needs a `checkpointStore`.
|
|
237
|
+
- Session turn checkpoints no longer reuse a `<id>.turn-<n>` id after `compact()` shortened the transcript: the `n` now comes from a persisted high-water mark (`<id>.turn-counter`), so `fork()` can no longer resolve a stale checkpoint of a different turn, and `prune()` keeps the counter while its session's paused run needs it.
|
|
238
|
+
- Memory: two parallel `remember_<slot>` calls with an `itemKey` no longer store duplicate items — the check-and-replace is serialized per memory key. A remembered item is capped at 20,000 characters and the injected `<memory>` block at 20,000 (each line 4,000), so one item can no longer outgrow the context window of every later run.
|
|
239
|
+
- `lousho add <kit> --dir <new-dir>` no longer creates the target directory before the install is decided: a non-interactive refusal, a manifest refusal or a 'no' at the prompt now truly leaves "nothing was written" — before, an empty directory stayed behind.
|
|
240
|
+
- A malformed or truncated provider response before any output (the AI SDK `JSONParseError`/`TypeValidationError` on a garbage first SSE chunk, or a stream cut by `UND_ERR_SOCKET` / "terminated" / "other side closed", also inside a `cause`) is now retried by `withRetry()` like other network errors, with a `provider.retry` event, instead of ending the turn with `LOUSHO_PROVIDER_REQUEST_FAILED`. Nothing is retried once text or a tool call was emitted.
|
|
241
|
+
- `lousho build --target=node-server` now lists the provider package in the build's `package.json` when the agent module (or a code `agent.ts`) uses an SDK provider class such as `new OpenRouterProvider(...)`, not only a literal `model: '<provider>/...'`, and warns when it detects no provider. `docs/deployment.md` no longer tells readers to call the internal `createDeployedServer()`; it shows a graceful shutdown for a self-served agent.
|
|
242
|
+
- Errors and cost (consumer harness): a session turn that fails outside its run (e.g. `LOUSHO_SESSION_BUSY`) now sends the error's stable `code` on the `error` event, with the message still sanitized; `provider.fallback` events carry `fromModel` / `toModel`, so two providers of the same name (both OpenRouter) can be told apart; and one step with no known price no longer discards the run's cost: `usage.costUsd` keeps the priced steps' sum and `usage.costPartial: true` flags it (also on `byModel` entries, `delegated` and `step.done` / `run.done` usage), and `limits.maxCostUsd` is enforced on that partial total.
|
|
243
|
+
- `grep` no longer times out on a cheap pattern over a slow disk: the 10 s budget now counts only the time spent matching, not reading files. The timeout error says whether the pattern is slow or the many files used the budget, and how many files were scanned. New `createFsTools({ grepTimeoutMs })`.
|
|
244
|
+
- Permissions: a `permissions` rule naming a tool the agent does not have (a typo such as `write_flie`, so a `deny` silently guarded nothing) now logs a `[lousho]` warning at `createAgent()` with a "did you mean", and `lousho doctor <agent-dir>` reports it as a warning. Wildcards, globs, patterns and agents whose tools may arrive later (MCP, tool search, sub-agents) are only checked for near-miss typos.
|
|
245
|
+
- `lousho init` / `create-lousho-agent` template: `npm run dev` with piped input (`printf 'hi\n' | npm run dev`) no longer exits before the reply; it streams the reply and exits when the turn is done.
|
|
246
|
+
- `lousho eval`: the "with soft failures" total (`softFailed` in the JSON summary) also counts cases that failed a gate or threw; the JUnit report keeps their soft failures as `<system-out>` notes.
|
|
247
|
+
- Sessions: a checkpointed turn whose first model call fails (a 5xx after retries, an exhausted 429, a malformed response) no longer loses the user's message. The turn is kept as an `in-progress` pending turn (new `Checkpoint.unstarted`), so `pending()` and the route's `GET` show it, and the next `send()` / `resume()` runs it again with the message in the transcript once. Over the session routes, that stored turn claims the session for its caller.
|
|
248
|
+
- `lousho eval --replay` / `--record` skip a `mockModel()` provider: a scripted mock needs no network, so a mock eval no longer fails with `no cassette ... [LOUSHO_CASSETTE_INVALID]`.
|
|
249
|
+
- `limits.maxCostUsd` no longer logs "cost limit is not enforced" on runs that use `mockModel()`, which has no price.
|
|
250
|
+
- `lousho doctor` lists optional packages the agent does not use as one info line instead of a warning each.
|
|
251
|
+
- Docs: `fileMemory({ dir })` in an agent directory's `memory/*.ts` resolves against the working directory (anchor it to the module); `lousho mcp` takes an agent directory; running `create-lousho-agent` from a local tarball; `lousho dev`/`chat` cannot pass run metadata.
|
|
252
|
+
- Flows: a durable run (`checkpointStore` and `runId`) holds a run lease, like an agent run: a concurrent `FlowExecutor.execute()` or `resume()` of the same `runId` (in this process, another process or through another store object) is refused with `LOUSHO_SESSION_BUSY` and runs nothing, instead of running the remaining steps twice. A lease left by a crashed process expires, so the run can still be resumed. See [Durable runs](docs/flows.md#durable-runs).
|
|
253
|
+
- Flows: a `toolCall` argument that is exactly `'{{name}}'` or `'$name'` passes the variable's value (a number, an object) instead of its text, so `{ n: '{{n}}' }` works with a `z.number()` tool. Mixed strings are still interpolated as text. See [Argument values](docs/flows.md#argument-values).
|
|
254
|
+
- Flows: a `step` node (`StepNode`) fails with a message naming the supported way to run a `createAgent()` agent in a flow (a tool that calls `agent.send()`). See [Running an agent in a flow](docs/flows.md#running-an-agent-in-a-flow).
|
|
255
|
+
- Sub-agents: a sub-agent's own `memory` works when the lead's `task` tool runs it (recall in its system prompt, its `remember_<name>` / `recall_<name>` tools), scoped by the lead run's `sessionId`, `metadata` and `principal`; before, its slots were silently ignored there. New `subagentOptions: { continuation: false }` leaves `taskId` and `mode` out of the `task` tool, and the error for another sub-agent's `taskId` tells the model to omit it for a new task. See [Sub-agents](docs/sub-agents.md#continuing-a-task).
|
|
256
|
+
|
|
257
|
+
### Security
|
|
258
|
+
- **Behavior change:** the file tools from `createFsTools()` (`read_file`, `list_dir`, `glob`, `grep`, `write_file`, `edit_file`) refuse any path with a `.git` segment (`.git/config`, `./.git`, `a/../.git`, `.GIT`, a worktree's `.git` file; on Windows also 8.3 names such as `GIT~1`) with a `WorkspaceError`, and `glob`, `grep` and `list_dir` leave `.git` out of their results. `.git/config` can hold a CI checkout token (`http.<url>.extraheader`) and a worktree's `.git` names a host path. The new `deny` option sets the names (default `['.git']`; `[]` turns it off). The `shell` tool is not path-filtered. See [Security model](docs/workspace-tools.md#security-model).
|
|
259
|
+
- `lousho build <dir> --target=node-server|docker` no longer copies an agent directory's `.env` files or its `.lousho/` session store (real user transcripts) into `dist/` and the Docker image. Only the files the server reads are copied: `instructions.md`, `instructions/`, `skills/` and a JSON/YAML config, in the directory and each sub-agent; `.env*`, `.lousho`, a JSON/YAML config's `store.dir`, `.git` and `node_modules` are skipped at any depth. `dist/` is emptied before each build so sessions a previous build's server wrote there are not resurrected, and the generated `.dockerignore` also lists `**/.env`, `**/.env.*` and `**/.lousho`. Rebuild and redeploy images built from an agent directory with an earlier version. See [Deployment](docs/deployment.md#agent-directories).
|
|
260
|
+
- `createRouteHandler()`'s UI stream (`POST <base>/ui`) and framework hook route (`POST <base>`) now enforce the documented 1 MB body cap (`413`) like `POST /chat` does; they previously buffered the full body via `request.json()`.
|
|
261
|
+
- A channel approval decision now binds to the session recorded in the approval's own durable snapshot, not the `sessionKey` the caller sends, so a request carrying a foreign session key can no longer resolve another conversation's pending approval.
|
|
262
|
+
- `POST /chat/:sessionId/approvals/:id` checks the approval's kind after authorization: `answer` only decides question approvals and `approved`/`note` only tool approvals; a mismatched body is `400 LOUSHO_APPROVAL_KIND_MISMATCH` instead of silently approving a tool.
|
|
263
|
+
- `POST /chat` message arrays may carry `role: 'user'` only; client-supplied `system`, `assistant` and `tool` messages are dropped before the turn (content-part arrays without roles are unaffected).
|
|
264
|
+
- **Behavior change:** `crossSiteRequest` no longer trusts `X-Forwarded-Host` or `Forwarded: host=` as same-site evidence — those headers are client-controllable. A request is same-site when its `Origin` matches the server's actual `Host` (or an `allowedOrigins` entry); behind a trusted proxy, set `allowedOrigins` to the public origin.
|
|
265
|
+
- Session-owner bookkeeping adds a per-principal cap (1024 claimed sessions per caller) under the global 100,000 LRU, so one caller flooding new session ids can no longer evict everyone else's ownership claims.
|
|
266
|
+
- `webhookChannel()` only defines `verify` when an `auth` option is configured; an unauthenticated webhook is treated as an open channel and its cross-site requests are refused like `httpChannel()`'s. The deprecated `WebhookTriggerAdapter` treats a missing `verify` as success.
|
|
267
|
+
- Malformed percent-encoding in route parameters (`%zz`, truncated UTF-8) answers `400` instead of a `URIError` 500 on `/chat/:sessionId/...` and channel approval routes.
|
|
268
|
+
|
|
269
|
+
## [1.0.0-alpha.20] - 2026-10-16
|
|
270
|
+
|
|
271
|
+
create-lousho-agent 0.1.2-alpha.5.
|
|
272
|
+
|
|
273
|
+
### Security
|
|
274
|
+
- **Behavior change:** with route auth and no `authorizeSession`, a session belongs to the first principal that sends it a turn (reading claims nothing); any other principal gets `403 LOUSHO_SESSION_FORBIDDEN`. Owners are kept in memory per agent for the 100,000 most recently used sessions, so a stored session whose owner is not on record (after a restart, on another replica, past the cap) is refused to everyone: pass `authorizeSession` for multi-instance deployments and durable stores, or `authorizeSession: () => true` for the old behavior. A route behind one bearer token has a single caller and checks no owner. `POST /chat` with an empty `input` (and `POST <base>/ui` without a user message) is a `400` that claims nothing, and a turn that stores nothing drops its claim, so an owner can no longer be evicted by flooding. See [Who may use a session](docs/auth.md#who-may-use-a-session).
|
|
275
|
+
- **Behavior change:** `POST /chat/:sessionId/approvals/:id` answers `404` unless the approval belongs to that session, and by default only the caller the paused run acts for may decide it; allow others (a supervisor) with `authorizeApproval`.
|
|
276
|
+
- **Behavior change:** the session routes (`createRouteHandler()`, `createDeployedServer()`, the Worker, `lousho dev`) refuse cross-site requests: a POST whose `Origin` is neither the server's own (`Host`, `X-Forwarded-Host` or `Forwarded: host=`) nor in the new `allowedOrigins`, or that the browser marked `Sec-Fetch-Site: cross-site`, is `403 LOUSHO_REQUEST_FORBIDDEN`, and a POST body must be `Content-Type: application/json` (`415 LOUSHO_UNSUPPORTED_MEDIA_TYPE`). Requests without `Origin` are unaffected. Opt out with `allowedOrigins: ['*']` and `requireJsonBody: false`; `allowedHosts` refuses unlisted `Host` headers. Built servers read `LOUSHO_ALLOWED_ORIGINS`, `LOUSHO_ALLOWED_HOSTS` and `LOUSHO_REQUIRE_JSON_BODY=false`. The React/Vue/Svelte bindings no longer send `Content-Type` twice and accept a `Headers` instance. See [Cross-site requests](docs/auth.md#cross-site-requests).
|
|
277
|
+
- **Behavior change:** `lousho dev` answers `403` to a non-loopback `Host` (off loopback: an IP address or the `--host` name; new `--allowed-hosts a,b`), so a web page cannot reach it through DNS rebinding, and warns when bound off loopback. A channel without `verify` refuses a cross-site browser request (`403`), and `httpChannel()` without `verify` requires a JSON body (`415`).
|
|
278
|
+
- **Behavior change:** the Cloudflare Worker refuses every `/chat` route (`403 LOUSHO_REQUEST_FORBIDDEN`) when no `LOUSHO_API_TOKEN` secret is set; set the var `LOUSHO_ALLOW_OPEN_SESSIONS` (or the earlier `LOUSHO_ALLOW_OPEN_TRANSCRIPTS`) to `"true"` to allow it. `/health`, the OAuth callback and channels stay open, and `lousho build --target=cloudflare-worker` warns about the secret.
|
|
279
|
+
- **Behavior change:** `WebhookTriggerAdapter` binds `127.0.0.1` by default (was `0.0.0.0`), refuses a non-loopback `host` without `auth` unless `allowUnauthenticated: true` (`LOUSHO_TRIGGER_INVALID`), refuses a foreign `Host` or an `Origin` on loopback without `auth`, caps bodies at 1 MB (`413`) before auth, and no longer echoes handler errors. See [Webhook](docs/triggers.md#webhook-authentication).
|
|
280
|
+
- **Behavior change:** MCP tools ask for approval by default (`approval: 'always'`); a server's `readOnlyHint` / `destructiveHint` count only with `approval: 'annotations'`. An MCP tool named like a local tool is refused with `LOUSHO_CONFIG_INVALID` instead of replacing it. See [MCP](docs/mcp.md#approval-for-mcp-tools).
|
|
281
|
+
- Flows: a `toolCall` step passes the same gate as an agent's tool call (`permissions`, `permissionMode`, `onPermissionDecision` and a new `approve` option on `FlowExecutionContext`); a call that needs approval and is not approved fails with `LOUSHO_FLOW_TOOL_DENIED` without running. Step arguments are validated against the tool's schema (`LOUSHO_TOOL_ARGS_INVALID`) and a flow's declared `inputs` are enforced (`LOUSHO_VALIDATION_FAILED`). See [Flows](docs/flows.md#tool-calls).
|
|
282
|
+
- Registry: the approval an install receipt requires for an `exec`, `needsApproval` or unattested item can no longer be waived from inside the agent directory (an `allow` rule, `acceptEdits` or the directory's own `approve` file); only a person or an `approve` passed in code decides it. Receipts now also cover a kit's `subagents/<name>/` tools. `coding-kit` / `coding-pi` refuse writes to their own config, tools and `.git/`, drop `git diff` from the shell allowlist and accept `node --test` only with test file paths (reinstall with `lousho add coding-kit --overwrite`). See [Registry](docs/registry.md).
|
|
283
|
+
- Shell tool: an `allow` string pattern refuses arguments that escape the workspace (`..` segments, also in `--flag=../x`, `~`, absolute paths, quoted or escaped forms such as `cat "../x"`, globs that can expand to `..`, and `$`, `{`, `}`), and under cmd.exe `%` and `^` count as shell operators. Use a `CommandRule` or an anchored RegExp to allow such arguments.
|
|
284
|
+
- Agent Forge (`lousho studio`) no longer answers any origin: it sends no CORS headers, refuses a foreign `Origin` or a non-loopback `Host`, and every API and WebSocket call needs the per-launch token printed in its URL. See [Agent Forge](docs/agent-forge.md#access-token).
|
|
285
|
+
- Tracing: `redactContent` defaults to `true`, so prompts, instructions and tool output are no longer written to span attributes or `.lousho/traces` unless you pass `redactContent: false` (or `captureContent: true` for `gen_ai.*` content).
|
|
286
|
+
- The session routes no longer send raw error text to clients: `error` events, `500`s and `provider.retry` / `provider.fallback` events carry the error's `name` and `code` with a generic message, and the full error is logged. Pass `exposeErrors: true` for the old behavior (`lousho dev` does). Channel `500`s no longer echo internal errors either.
|
|
287
|
+
- `fromUIMessages()` (and `POST <base>/ui`) keeps only `user` and `assistant` messages from the client; a client-sent `system` message is dropped.
|
|
288
|
+
- OAuth: a principal without an issuer gets tokens namespaced by its authenticator, so the same id from Slack and GitHub no longer shares a token (docs/oauth.md has the one-time move); `telegramChannel` and `teamsChannel` send a sign-in link only in a 1:1 chat; the `[REDACTED]` net for `ctx.getToken()` tokens also covers thrown errors, object keys, URLs, class instances and base64 forms of the token.
|
|
289
|
+
- `serveMcp` over HTTP validates `Host` and `Origin` against DNS rebinding (new `allowedHosts` / `allowedOrigins`), and loopback checks there and in `WebhookTriggerAdapter` accept exactly `localhost`, `127.x.x.x` and `::1` (`127.attacker.example` passed).
|
|
290
|
+
- The `openrouter` provider without `apiKey` reads `OPENROUTER_API_KEY` and no longer sends `OPENAI_API_KEY` to openrouter.ai.
|
|
291
|
+
- Slack and Discord signatures accept a timestamp at most a minute ahead (was five minutes either way), read in seconds; `discordChannel` refuses timestamps more than five minutes old. `githubChannel({ deliveries })` takes a shared `DeliveryStore` for `X-GitHub-Delivery` replay protection.
|
|
292
|
+
- The `@modelcontextprotocol/sdk` peer range is `^1.31.0` (was `^1.30.1`): 1.30.x is affected by GHSA-6qxp-vccf-f47h, which MCP OAuth relies on.
|
|
293
|
+
- `SubprocessSandbox`: `writeFile()` stays under the new `root` option (`LOUSHO_SANDBOX_PATH_DENIED`, also through symlinks), and containers run as `1000:1000` with all capabilities dropped, `no-new-privileges` and default limits (512 MiB, 256 pids, 1 CPU); pass `user: 'root'` for the old user. See [Guardrails](docs/guardrails.md#sandboxed-tools).
|
|
294
|
+
|
|
295
|
+
### Changed
|
|
296
|
+
- **Behavior change:** session turns keep the checkpoint history of the latest 10 committed turns (`agent.session({ id, turnHistoryLimit })`; `0` keeps none); storage used to grow with the square of the conversation. Excess history already in a store is deleted over the next commits, also when each turn uses a new process or store object. New `fileStore(dir).prune({ olderThanMs })` returns `checkpointHistory` too; `fileStore()` returns the new `FileStore` type.
|
|
297
|
+
- **Behavior change:** a checkpointed run holds a run lease while it executes (new optional `CheckpointStore.acquireLease()`, `releaseLease()`, `renewLease()`, `saveIfLeased()`; file, SQLite, memory and best-effort KV stores). A second process, replica or store object that sends to the same session, resumes it or resolves its approval gets `LOUSHO_SESSION_BUSY` instead of running its tool calls again, and a stalled holder can no longer overwrite the run that took over. A dead holder on the same host is taken over at once; on another machine 30-60 s after its last heartbeat. Schedule fires are claimed the same way, so replicas fire a tick once. See [Run leases](docs/durable-execution.md#run-leases).
|
|
298
|
+
- **Behavior change:** approvals run at most once: deciding one re-saves the turn as `'in-progress'` with the call in `Checkpoint.startedToolCallIds`, and resuming after a crash mid-call gives it a `kind: 'interrupted'` tool error instead of running it again. Resolving an approval whose run was deleted or replaced fails with `LOUSHO_APPROVAL_ORPHANED` and withdraws it.
|
|
299
|
+
- **Behavior change:** a session turn paused for approval locks the session id: a new turn on it throws `SessionAwaitingApprovalError` (`LOUSHO_SESSION_AWAITING_APPROVAL`) until the approval is decided or `discardPending()` drops the turn.
|
|
300
|
+
- **Behavior change:** a deterministic provider error records `lastError` on the turn's checkpoint, and later calls on that session fail fast with `LOUSHO_SESSION_TURN_FAILED` instead of replaying the failing turn; `resume()` retries it.
|
|
301
|
+
- **Behavior change:** run ids (`send({ sessionId })`, a fork's `newSessionId`) are 1-200 characters of `A-Za-z0-9_.-` on every store and may not end in `.turn-<n>`, `.paused` or `.fire`; `''` is refused. Session ids (`agent.session({ id })`, the routes) stay 1-128 characters of `A-Za-z0-9_-`. The error messages now say which of the two rules applies.
|
|
302
|
+
- **Behavior change:** with a store, tools, `needsApproval`, permission rules and hooks get the session id as `ctx.sessionId`, not the checkpoint key `<id>.turn-<n>` (new `ExecuteOptions.contextSessionId`).
|
|
303
|
+
- **Behavior change:** a model step with several calls that need approval pauses once, on all of them (`result.approvalIds`, `agent.approvals.resolveAll()`); calls that need no approval run before the pause. See [Several calls in one step](docs/approvals.md#several-calls-in-one-step).
|
|
304
|
+
- **Behavior change:** `once()` no longer remembers a yes from a `createAgent({ approve })` callback, and `'defer'` is reserved: an approval note that is exactly `'defer'` defers.
|
|
305
|
+
- **Behavior change:** `approvalTtlMs` and an `ask` rule's `ttlMs` above 100 years throw `LOUSHO_CONFIG_INVALID` (they meant "never expires").
|
|
306
|
+
- **Behavior change:** memory items are keyed `<slot name>#<scope key>`, so two slots on one scope no longer share items. Run `migrateMemoryKeys(slot)` once to copy items stored under the old bare key. See [Memory](docs/memory.md#items-from-before-slot-name-keys).
|
|
307
|
+
- **Behavior change:** a prompt schedule runs every fire in its own session `schedule-<name>-<fire time>` (`defineSchedule({ sharedSession: true })` keeps one shared session), and a fire that does not finish `'stop'` reports the new `LOUSHO_SCHEDULE_RUN_INCOMPLETE`.
|
|
308
|
+
- **Behavior change:** channels without a `store` keep transcripts in the agent's `createAgent({ store })` when it has `sessions`.
|
|
309
|
+
- **Behavior change:** the routes let a session turn run to its end and save it when the client disconnects (`onDisconnect: 'abort'` for the old behavior; `waitUntil` keeps a serverless function alive). An aborted turn keeps the user's input, the tool calls that ran and the partial reply (`metadata: { interrupted: true }`), and its usage counts.
|
|
310
|
+
- **Behavior change:** tool results are capped at `maxToolResultChars` (default `50_000`, `Infinity` for none) for every tool; MCP image/audio/blob data reaches the model as a placeholder and MCP tool descriptions are cut at 2,000 characters.
|
|
311
|
+
- **Behavior change:** a user file part the provider cannot send rejects the call with `LOUSHO_UNSUPPORTED_CONTENT` (under fallback the next provider gets it) instead of a text note; `unsupportedFiles: 'text-note'` keeps the note. See [Multimodal input](docs/providers.md#multimodal-input).
|
|
312
|
+
- **Behavior change:** `createAgent({ provider })` with a built-in or `fromAiSdk()` provider and no `retry` wraps it in `withRetry()` (provider's `maxRetries`, default 2), and `withRetry()` is the only retry layer: the built-in providers turn the `ai` SDK's own retries off (`retry: false` used to send 3 requests, `maxRetries: 3` sent 12).
|
|
313
|
+
- **Behavior change:** a rate-limited call has the code `LOUSHO_PROVIDER_RATE_LIMITED`, and an out-of-credit account (`insufficient_quota`, 402) is the new non-retryable category `quota-exceeded`.
|
|
314
|
+
- **Behavior change:** `OpenAIProvider` sends `store: false` on the Responses API unless `store: true` is set.
|
|
315
|
+
- **Behavior change (types):** `LLMProviderConfig` has no index signature, so a misspelled option is a type error; `LLMProviderRegistry.register()` / `create()` take the provider's own config type (new `BuiltinProviderConfigs`). `ExecutionFinishReason` is a closed union, and an unknown provider finish reason is reported as `'other'`.
|
|
316
|
+
- **Behavior change:** `agent.stream()` rethrows the run's error after the `error` and `run.done` events (`{ throwOnError: false }` for the old behavior).
|
|
317
|
+
- **Behavior change (CLI):** every usage error exits 2 and 1 means a command ran and failed. `lousho eval`, `build`, `dev`, `chat`, `acp`, `mcp` and `doctor` load `./.env`. The fallback registry URL points at the installed version's release tag.
|
|
318
|
+
- **Behavior change:** `connectMcp()` rethrows a server's start failure as `McpStartError` (`LOUSHO_MCP_START_FAILED`, with exit code and the last stderr lines) instead of wrapping it.
|
|
319
|
+
- Evals: `t.maxCostUsd(n)` fails when a run reports no cost (`{ unknownCost: 'skip' }` for the old, now warning, behavior); `judge.model` and `llmJudge()` / `llmCritique()`'s `model` are optional.
|
|
320
|
+
- Pruned tool results leave a stub naming the call and its arguments (`[pruned: search({"query":"deploy"}) result, 18234 chars; already read]`).
|
|
321
|
+
- Agent Forge: a run on a real provider without an API key fails with `LOUSHO_PROVIDER_MISSING_API_KEY` (HTTP 422) instead of silently using the mock provider, and the provider pill shows what actually ran.
|
|
322
|
+
- The examples import `@lousho/build-ai-agent` and its public subpaths instead of the repository's `src/`, so they can be copied into an app as they are.
|
|
323
|
+
|
|
324
|
+
- The `package.json` that `lousho build --target=node-server|docker` writes lists only the optional peers the bundled server loads (the provider package, `@modelcontextprotocol/sdk` with `mcpServers`), no longer `@lousho/build-ai-agent`: `dist/server.js` bundles the SDK, `ai` and `zod`, so the image's install is smaller.
|
|
325
|
+
- The npm package no longer ships the CommonJS build of the CLI (`dist/cli/*.js` and maps) or the CLI's type declarations: `bin/lousho.js` loads only `dist/cli/*.mjs`.
|
|
326
|
+
### Added
|
|
327
|
+
- Providers: a `timeout` option (default 10 minutes until the response starts and between chunks; `0` or `Infinity` turns it off) and a `fetch` option on every built-in provider. `OpenAIProvider({ api: 'chat' })` and `LOUSHO_OPENAI_API=chat` use Chat Completions for OpenAI-compatible servers (LM Studio, Ollama, vLLM, llama.cpp; the built-in hosted tools are then refused with `LOUSHO_HOSTED_TOOL_UNSUPPORTED`); see [Local and OpenAI-compatible servers](docs/providers.md#local-and-openai-compatible-servers). `withRateLimit()` / `createRateLimiter()` and `createAgent({ rateLimit })` queue model calls client-side. `promptCaching: 'auto'` (the default) marks Anthropic cache breakpoints for Claude on `anthropic` and `openrouter`. `withRetry({ idleTimeoutMs })` fails a stream that stalls between chunks (`timeoutMs` now bounds only the wait for the first chunk). `withFallback()` results carry `servedBy`.
|
|
328
|
+
- Model settings: `createAgent({ modelSettings })` and per-call `modelSettings` (`maxTokens`, `temperature`, `topP`, penalties, `stop`, `seed`, `toolChoice`), range-checked up front, also in `agent.json`. Per-call `maxSteps` and `instructions` on `send()` / `stream()`. See [Configuration](docs/configuration.md#model-settings).
|
|
329
|
+
- Cost: built-in prices for more default models (Ollama `llama3` window, OpenRouter variants), cache read/write prices (`cachedInputCostPerMTok`, `cacheWriteCostPerMTok`, `cacheWriteTokens`) and OpenRouter's reported `usage.cost` for `costUsd` and `limits.maxCostUsd`.
|
|
330
|
+
- Approvals: `remember: 'session'` and edited `args` on a decision, `'defer'` from an `approve` callback, `approvalTtlMs` / `ttlMs` deadlines (`expiresAt` on events and UI state), `agent.approvals.list()` across processes (new optional `ApprovalStore.list()`), `agent.pending()` for unfinished runs (new optional `CheckpointStore.list()`), and `resumedAfterApproval` on tool-call hook contexts. See [Approvals](docs/approvals.md) and [Recovering after a crash](docs/durable-execution.md#recovering-after-a-crash).
|
|
331
|
+
- Routes: `authorizeSession` and `authorizeApproval` on `createRouteHandler()` / `createDeployedServer()`; approvals report their `sessionId` and error events their `code`. Built node and Docker servers drain on SIGTERM (`shutdown({ timeoutMs })`, `LOUSHO_SHUTDOWN_TIMEOUT_MS`). Channel routes decide each call of a multi-call pause and accept a `sessionKey` after a restart.
|
|
332
|
+
- Flows: durable runs (`checkpointStore` + `runId`, `FlowExecutor.resume()`), pausing for approval (`status: 'awaiting-approval'`), per-node `retry` and `timeoutMs`, cancellation through `signal`, and `usage` on the result. See [Flows](docs/flows.md).
|
|
333
|
+
- Structured output: partial-object streaming (`object.delta`, `partialObject` in the UI bindings), `result.outputRepaired`, `{ schema, promptSchema: false }`, a forced no-tools answer when `maxSteps` or `limits.maxSteps` runs out, and `outputError.kind: 'truncated'`. Unions, records, tuples and optional fields now work on strict endpoints. See [Structured output](docs/structured-output.md).
|
|
334
|
+
- Memory: `expose: { forget: true }`, `itemSchema` / `itemKey` upserts (new optional `MemoryProvider.upsert()`), `memoryKey()`, de-duplicated `add()`, and `migrateMemoryKeys()`. See [Memory](docs/memory.md#correcting-memory).
|
|
335
|
+
- MCP: `optional: true`, `cwd`, `stderr`, `connectTimeoutMs`, `timeoutMs`, `tools: { include, exclude }`, per-call `approval({ args })`, `onToolsChanged` and `McpConnections.reconnect()`. See [MCP](docs/mcp.md).
|
|
336
|
+
- Observability: `invoke_agent` spans carry the request model and token totals (`agentUsageTotals: false` leaves them off), `legacyAttributes: false`, `parentSpanId` on `send()` / `stream()`, retry attributes on `chat` spans, sub-agent names on their spans, and compaction calls as counted `chat` spans. See [Observability](docs/observability.md).
|
|
337
|
+
- Tools and sub-agents: `defineTool({ timeoutMs })`, `subagentOptions: { background: false }`, `read_skill_file` for skill folders, `ToolDescriptor.transient`, `CommandRule` objects for the shell tool's `allow` / `deny`, and an optional `ShellProvider.shell` that tells the model which shell runs its commands.
|
|
338
|
+
- Compaction: `reserveOutputTokens` and `onCompaction` on the object form; `GenerateHookContext.generate()` for counted side calls.
|
|
339
|
+
- Evals: sub-agent tool calls in trajectory evals, `SendOptions.onEvent`, `repeat` / `passAt`, `{ agent }` for `setProviderInterceptor` callbacks, `lousho eval --timeout` and `defineEval({ timeoutMs })`, and `recordReplay({ rerecordHint })`. See [Evals](docs/evals.md).
|
|
340
|
+
- Channels and schedules: `githubChannel({ pullRequestOpened: true })`, `webhookChannel({ principal })`, exported `fireSchedule()`, `startSchedules({ keepAlive: true })` and an awaitable `stop()`, and `mountFetchChannels({ waitUntil })`.
|
|
341
|
+
- CLI: `lousho doctor --ping` and base URLs in its output, and `lousho --version`.
|
|
342
|
+
|
|
343
|
+
- `lousho build --target=node-server|docker` builds a `.ts`/`.js` agent module (the file `lousho dev src/agent.ts` serves: a default or `agent` export holding `createAgent()` options or a built agent). The `cloudflare-worker` target says it builds only a spec or an agent directory. The minimal and tools scaffolds export their options as `src/agent.ts`'s default export, so `lousho dev|chat|mcp|acp|build src/agent.ts` work on a new project, and every scaffold has a `build` script (and `tsup`); the minimal and tools ones also have `dev:ui` (`lousho dev src/agent.ts`).
|
|
344
|
+
- The Docker image `lousho build --target=docker` writes has a `HEALTHCHECK` on `GET /health`, and its `.dockerignore` keeps `.env`, `.env.*` and `*.tgz` out of the image.
|
|
345
|
+
- `lousho add`, `studio`, `eval` and `traces --help` describe every flag; `lousho help <command>` prints that command's help.
|
|
346
|
+
### Fixed
|
|
347
|
+
- Durability: concurrent commits on one session no longer lose a turn (new optional `SessionStore.saveIf()` compare-and-swap; `KVStore` stays best effort), and session objects re-read the store instead of caching the transcript. Concurrent `send({ sessionId })` calls in one process run in order. Parallel tool calls are checkpointed as each settles, so a crash no longer re-runs finished calls. A restarted process that resolves a session's approval commits the turn instead of dropping it. `session.clear()` deletes every turn's checkpoints and history, finished turns stay forkable, `session.fork()` works for 122-128 character ids, and sub-agent task ids are never reused.
|
|
348
|
+
- Stores: `fileStore` file names are case-safe on Windows and macOS (`Alice` and `alice` no longer share a file), long ids and device names are hashed, older long names are still read, writes are `fsync`ed and Windows `EPERM` / `EBUSY` is retried. A corrupt checkpoint fails with `LOUSHO_CHECKPOINT_CORRUPT` and can be discarded. `SqliteStore` waits 50 ms instead of freezing the event loop, retries locked writes without blocking (`LOUSHO_STORAGE_BUSY`) and `prune()` keeps paused turns. `KVStore` approval claims run a tool once in the common case (`ctx.approval.id` serves as an idempotency key). Image and file bytes survive every store. Taking over a stale file lock is serialized, and `KVCheckpointStore.list()` throws without a `list` binding instead of returning `[]`. `agent.approvals.get()` returns `undefined` for an id that cannot name an approval. `fileMemory` and `sqliteMemory` keep every item with several writers.
|
|
349
|
+
- OAuth: concurrent `ctx.getToken()` calls share one refresh, and a refused refresh no longer deletes a newer token.
|
|
350
|
+
- Approvals and memory: a run resumed after an approval or a crash keeps its memory tools and the scope it paused with; memory stays on after a handoff; recall errors skip the slot with a warning; invalid scopes fail with `LOUSHO_MEMORY_INVALID`; keyword recall matches whole words; `<memory` tags in item text are escaped; `recall_<name>` returns scores and metadata.
|
|
351
|
+
- Providers on `ai` 4, 6 and 7: tool errors are no longer wrapped twice on `ai` 7, malformed or truncated tool-call JSON reaches the model as a repairable `LOUSHO_TOOL_ARGS_INVALID` error (light repairs are tried first; `tool.start` has `rawArgs`), prefixed tool names (`functions.read_file`) resolve, unknown tools suggest the closest name, duplicate tool-call ids both run, a completion sent to a streaming request is replayed, mid-stream and `finish_reason: "error"` failures become retryable 502s, a stream that fails before any output is retried, and unset `temperature` / `maxTokens` are left out. `withFallback()` keeps its state per call, so concurrent calls no longer misreport fallbacks or book cost under the wrong model, and `withRateLimit()` over `withRetry()` streams reports the final attempt and holds its slot until the retried stream ends.
|
|
352
|
+
- Provider errors: local runtimes' context overflows are `context-length-exceeded`, refused connections name the URL, status and message are read together, nested OpenRouter reasons are shown, a long `Retry-After` fails at once instead of sleeping, negative hints are ignored, and compacted errors keep their category through `createAgent()`. A missing provider package fails with `LOUSHO_PEER_MISSING`, and the routes send its `npm install` command to the client.
|
|
353
|
+
- Provider features: `reasoning` reaches more models and warns once when it is dropped; image MIME types are sniffed; `OpenRouterProvider` honours `baseURL` and `OllamaProvider` sends `headers` and `apiKey`; `supportsTools()` knows current models; registered providers work in model strings; retired fallback models are replaced; price lookup handles OpenRouter spellings; the forced answer at `maxSteps` works on Claude.
|
|
354
|
+
- Runs: abort no longer waits on a provider or tool that ignores the signal (`finishReason: 'aborted'`, `result.abortReason`; `mockModel` honours `signal`), aborted streams keep their text, a throwing event listener or trace exporter no longer fails the run, tool results with cycles, BigInts, Maps or Sets serialize, `send(result.messages)` does not repeat the system prompt, `send([])` throws `LOUSHO_CONFIG_INVALID`, a `finish_reason: tool_calls` reply without calls ends `'stop'`, a `load_skill` tool that clashes with `skills` throws, `maxSteps` must be a whole number >= 1, and handoffs work on local models and Anthropic (no mid-conversation system message).
|
|
355
|
+
- Structured output: the repair and forced-answer prompts stay out of transcripts, near-miss replies (prose, `<think>` blocks, fenced JSON) are accepted without a repair call, a reply that repeats the schema gets a clearer repair message, and unsupported root unions or zod 4 `z.date()` fields throw a `ConfigurationError` naming the fix. `defineTool()` rejects inputs no model can satisfy and accepts `z.preprocess` dates on zod 4.1+.
|
|
356
|
+
- Compaction: summarizing splits oversized histories, counts tool definitions and the output schema, warns when it falls back to a 128,000-token window for an unknown model, caps `protectedTokens` at half the threshold, retries the summarizer after a rejection, counts its call in usage and budgets, and reports `appliedStrategy`.
|
|
357
|
+
- Sub-agents and handoffs: dynamic sub-agent config functions get the lead's `sessionId`, `metadata` and `principal`; a failed sub-agent's spend counts; truncated answers say so; agent-directory and Worker `subagents/` run as native sub-agents; a handoff target's `toolSearch` applies.
|
|
358
|
+
- Schedules: a fixed-hour cron job in a spring-forward gap runs once after the gap instead of being skipped.
|
|
359
|
+
- Flows: a failed `parallel` branch aborts its siblings, `forEach` variables are per iteration, unnamed steps get unique ids, and `resume()` refuses a changed flow (`LOUSHO_FLOW_CHANGED`).
|
|
360
|
+
- MCP: `Not connected` counts as a dropped connection and reconnects, `tools/list` follows every page, dropped servers report `'failed'` with their stderr, calls honour abort, text-only results are sent once, tool names are made valid for every provider, object schemas with a sibling `oneOf` keep their shape, `agent.ready()` reconnects after `close()`, and `serveMcp()` over HTTP cancels the run a client cancels or abandons (matched by its new `Mcp-Session-Id`) and rejects its pending approvals. `serveAcp` keeps serving after a malformed JSON line.
|
|
361
|
+
- Channels: redelivered webhooks run once (Telegram, GitHub, Slack), concurrent decisions on one approval decide it once (`409 LOUSHO_APPROVAL_CONFLICT`, also on the routes), `webhookChannel` authenticates on Fetch hosts and its pause replies carry the prompt, `githubChannel` ignores `/approve` for unknown approvals, and channel `metadata` reaches the run. `POST <base>/ui` answers 400 for an invalid id, and `auth: ''` is `LOUSHO_AUTH_CONFIG_INVALID` on `createRouteHandler()` and `serveFetch()`.
|
|
362
|
+
- UI bindings (React, Vue, Svelte): remote mode keeps one session across turns, a stream that ends early becomes an error, each step starts a new paragraph, abnormal endings set `status: 'error'`, React gains `reset()`, server error messages are kept, Svelte no longer aborts on a transient subscribe, `formatUsage()` shows small costs, and `parseEventStream()` joins multi-line `data:` fields.
|
|
363
|
+
- Tracing and OpenTelemetry: open spans are bounded without losing parents, queued background sub-agents stay in the lead's trace, background sub-agent cost rolls up, and `execute_tool` spans carry the error message.
|
|
364
|
+
- Evals: replay matches calls by request, sub-agent cassettes are named after the agent, cassette names include a hash of the case label, re-recording unchanged exchanges leaves no diff, tool order no longer breaks replay, reasoning and full usage are recorded, judge scores parse more formats (the last `SCORE:` wins) and judges see the user's input at temperature 0, `lousho eval` gives live cases 10 minutes and its reports are never empty when vitest fails.
|
|
365
|
+
- CLI: `lousho dev` reloads CommonJS tool files and recreated folders, tool files no longer print `MODULE_TYPELESS_PACKAGE_JSON`, TypeScript syntax that type stripping cannot erase gets an error suggesting `--experimental-transform-types`, every agent command loads `tools/*.ts` of an installed project (one ESM build, shared brands, `instanceof` across ESM and CJS copies), `lousho build` reports bundling failures as `LOUSHO_DEPLOY_FAILED`, warns about missing provider packages, merges an existing `wrangler.toml` (new default `compatibility_date` `2026-09-01`), no longer needs unused provider packages for a Worker and prints where it wrote, Docker images run as `node`, `lousho doctor` and `lousho mcp` accept agent directories, `lousho add` checks flags and target directories up front and falls back to GitHub when the default registry is down, `lousho init` templates and Windows instructions are fixed, `init` says when the provider's API key is missing and git-ignores an `--sdk-path` tarball, and help texts and messages are clearer.
|
|
366
|
+
- Agent directories: every `createAgent()` override passed to `loadAgentDir()` is forwarded, `mock/<name>` models work from every entry point, and import failures throw `LOUSHO_AGENT_DIR_INVALID` with an install hint. `loadSkills()` skips a loose `README.md`.
|
|
367
|
+
- Workspace tools: `grep` refuses catastrophic regexes and runs with a time budget, `edit_file` refuses non-UTF-8 files instead of corrupting them, and `tool_search` ranks synonyms and word families.
|
|
368
|
+
- Agent Forge: agents are read from and written to `.lousho/agents/`, narrow screens and keyboard and screen-reader use work, invalid names and imports show errors instead of a blank page, finished runs report `done`, and several drawing and status glitches are fixed.
|
|
369
|
+
- Packaging and types: ESM consumers get `.d.mts` declarations (plus `typesVersions` for `node10`), declarations type-check with `skipLibCheck: false` on zod 3 and 4, the pi peers are caret ranges, `llms-full.txt` no longer ships and the Worker entry has no source maps.
|
|
370
|
+
- Docs: corrections throughout (sessions vs runs under one id, schedules, nextjs approvals, memory, `engines.node`), the snippet check now also runs every snippet that builds its own mock provider and checks fences indented in lists, and the ops-pipeline example works as described.
|
|
371
|
+
- A checkpointed session turn that stalled past its run lease and then failed for good no longer stamps its `lastError` (`retryable: false`) on the checkpoint of the process that took the turn over, which bricked that turn forever with `LOUSHO_SESSION_TURN_FAILED`: the session now holds the turn's run lease itself (handed to the run as `HELD_RUN_LEASE`) and `recordFailure()` writes through `RunLease.save`, whose fenced save writes nothing once the lease is lost.
|
|
372
|
+
- `agent.send(msg, { sessionId })` and a fresh `agent.session({ id })` no longer run beside a session turn that waits on an approval when its `<id>.paused` marker was never written (a crash or a send between the executor's awaiting-approval checkpoint and `markPaused()`): `pausedSessionTurn` now also scans the store's `<id>.turn-*` checkpoints for `status: 'awaiting-approval'` when the marker is absent, on stores that implement `list()`.
|
|
373
|
+
- A streamed tool call whose deltas repeat its `id` while omitting `index` (the OpenAI-compatible / Gemini-proxy servers the tool-call id rewrite exists for) stays one call: each repeat no longer mints a `call_1_2`, `call_1_3`, … id that split the arguments JSON across phantom calls none of which parses; the call's slot in the delta's `tool_calls` stands in for the missing `index`.
|
|
374
|
+
- A non-streamed request whose body carries a nested `"stream": true` (a `providerOptions` passthrough, tool arguments, gateway metadata) no longer has its JSON reply rewritten into an SSE stream the consumer cannot parse: whether a request asked for streaming is read from the parsed top-level `stream` field instead of a regex over the raw body.
|
|
375
|
+
- Session routes: two concurrent first-touch turns on one unowned session no longer both pass the owner check - the claim is atomic now, so the loser is refused (`403 LOUSHO_SESSION_FORBIDDEN`) instead of streaming a turn inside the winner's session; a claim lost while a turn ran clears what it stored and ends the stream on the same coded error.
|
|
376
|
+
- Approve-with-edits on a sub-agent's *grouped* approval validates the edited args against the tool's schema like a top-level or single sub-agent decision does: before, `snapshot.subagent` stored `decision.args` raw, so an invalid edit (e.g. a string where `z.number()` wants a number) ran the tool with garbage args; it now throws `LOUSHO_TOOL_ARGS_INVALID` and leaves the approval pending.
|
|
377
|
+
- Aborting a run during a tool call no longer hangs `send()` / `stream()` / `approvals.resolve()` forever on a tool that ignores its `abortSignal`: the tool batch and the approved-tool wait of `resumeAfterApproval()` are raced against the run's signal the way the model call already was, the run ends `finishReason: 'aborted'` with a 'cancelled' result for calls still running past a short grace window (a result that lands within it is still kept), and the tool keeps running in the background.
|
|
378
|
+
- A run that re-pauses on several approvals after a resume persists every pending approval id in its checkpoint: `markAwaitingApproval()` wrote only `approvalId`, so durable discovery (`pausedApprovalIds`, `agent.approvals.list()`) named 1 of N approvals; it now carries the group's `approvalIds` like `markGroupPending()` does.
|
|
379
|
+
- The `inbox-triage` registry kit loads under `lousho dev`, `chat`, `acp`, `mcp` and `doctor`: its relative imports carry the `.ts` extension Node's type stripping needs (only bundlers resolved them). A test now installs every registry kit and loads it in a plain `node` process, as the CLI does. An extensionless relative import in an agent directory gets a hint naming the specifier to write (`'./lib/store.ts'`), and on a Node without built-in type stripping (before 22.18) the `.ts` load error says Node 22.19+ is required.
|
|
380
|
+
- `OPENAI_BASE_URL` (and `ANTHROPIC_BASE_URL`) is used with `@ai-sdk/openai` 1.x (`@ai-sdk/anthropic` 1.x), the line that pairs with `ai` 4: the provider passes it when no `baseURL` is configured. Before, `model: 'openai/...'` called api.openai.com while `lousho doctor --ping` reported the custom endpoint.
|
|
381
|
+
- `PropagatingToolError`, `McpToolError` and `WorkspaceError` pass `instanceof` across the ESM and CJS copies of the SDK, so a `.cjs` tool's "do not retry" error stops the run under the ESM `lousho` CLI instead of being handed to the model (and retried).
|
|
382
|
+
- `lousho dev` re-evaluates the local helper files an edited tool or agent module imports (the `?t=` cache-bust is passed on to relative imports), instead of logging "reloaded" while serving the stale helper; editor temp files no longer show in the "changed:" list.
|
|
383
|
+
- `lousho eval`: a case that vitest fails after the case finished (it ran past its time limit) is reported as that case failing, not as a pass plus an extra `vitest` row; a run that records no case at all (a mistyped `--tag`) exits 1 like a glob that matches nothing.
|
|
384
|
+
- The yarn scaffold's `yarn test` works: the templates list `vite`, vitest's peer, which yarn 1 does not install. The README's "Next 3 commands" lists three, and the tools template's README counts its three tools.
|
|
385
|
+
- CLI messages: a module that exports no agent names the command that loaded it (`lousho chat:`, not `lousho dev:`); `lousho mcp` without a path says a spec, directory or module is accepted, and `--port`/`--host` without `--http` are a usage error instead of ignored; `lousho version <arg>` and `init --sdk-path <missing>` exit 2; the unknown-provider error lists `mock`.
|
|
386
|
+
|
|
387
|
+
## [1.0.0-alpha.19] - 2026-10-08
|
|
388
|
+
|
|
389
|
+
Everything published from 1.0.0-alpha.15 (2026-10-06) through 1.0.0-alpha.19
|
|
390
|
+
(alpha.17 on 2026-10-07, alpha.18 and alpha.19 on 2026-10-08). Most of it was
|
|
391
|
+
prepared on `main` under the version number 1.0.0-rc.0, which was never
|
|
392
|
+
published. Upgrading from an earlier `1.0.0-alpha.*` release? Read the
|
|
393
|
+
[upgrade guide](docs/upgrading.md): imports moved to subpaths, dead and
|
|
394
|
+
superseded APIs were removed, and the patch guardrails are now patch checks.
|
|
395
|
+
What 1.0 considers public is defined there (every `exports` entry and the
|
|
396
|
+
checked-in `api/` reports).
|
|
397
|
+
|
|
398
|
+
### Breaking
|
|
399
|
+
- Flows, the Jira / GitHub / Slack / email tools, encryption, `StorageService` and the validators moved out of the package root. Import them from their subpath: `FlowBuilder`, `FlowExecutor`, `validateFlow` and every flow type (`AgentFlow`, `EditorStep`, `FlowExecutionEvent`,...) from `@lousho/build-ai-agent/flows`; `createJiraTools`, `createGitHubTools`, `createSlackTool`, `slackTool`, `postSlackAlert`, `createEmailTool` and their types from `@lousho/build-ai-agent/integrations`; `EncryptionUtils`, `DTOEncryptionFilter`, `DecryptionError`, `sha256`, `generatePassword`, `StorageService`, `StorageServiceApprovalStore`, `LocalStorageCheckpointStore` and the validators (`validateWithSchema`, `safeValidate`, `isValidEmail`,...) from `@lousho/build-ai-agent/utils`. The integrations are no longer in `@lousho/build-ai-agent/tools` and the flow types are no longer in `@lousho/build-ai-agent/types`. Nothing else changed: the classes and functions are the same. For file-backed stores with `createAgent()`, prefer `fileStore(dir)`.
|
|
400
|
+
- Removed `AgentType`, `AgentTypeDescriptor`, `AgentConfig.agentType`, `AgentBuilder.setType()` and the agent-type registry (`agentTypesRegistry`, `getAgentTypeDescriptor`, `getAllAgentTypeDescriptors`, `isValidAgentType`, `validateAgentConfig`, `validateAgentTools`). They had no runtime effect since 1.0.0-alpha: delete the `setType(...)` call and the `agentType` field. Also removed, because nothing implemented or used them: the repository interfaces (`IRepository`, `IAgentRepository`, `ISessionRepository`, `IResultRepository`, `SessionData`, `ResultData`, `SDKRepositories`), `DataLoadingStatus`, `PaginationParams`, `PaginatedResponse`, `DeepPartial`, `Timestamped`, `IdEntity`, `AgentExecutionOptions`, `AgentExecutionResult`, `AgentDefinition` and `ToolSetting`. If your code used one of these types, copy its declaration into your project. A checkpoint saved with `agentType` still loads; the field is ignored.
|
|
401
|
+
- Removed `ExecuteOptions.onEvent` and the `ExecutionEvent` / `ExecutionEventType` types, deprecated in an earlier alpha. Pass `onAgentEvent` to `AgentExecutor.execute()` / `stream()` / `resumeAfterApproval()` instead (with `createAgent()`, `onEvent` already receives `AgentEvent`s and is unchanged). The mapping from the old event names to `AgentEvent` types is in docs/streaming.md#listening-without-iterating ("Migrating from `onEvent` / `ExecutionEvent`"). The one-time "onEvent is deprecated" warning is gone with it.
|
|
402
|
+
- Removed `createDelegateTool()`, `DelegateAgentOptions`, `DelegateAgentResult` and `DelegationDepthExceededError`. Use the `subagents` option instead, on `createAgent()` or `AgentExecutor.execute()`: create the child with `createAgent({ provider, instructions, description })` and pass `subagents: { billing }`. The model gets one `task` tool for every sub-agent, with the same inheritance (hooks, permissions, abort, usage roll-up) and a depth limit (`maxSubagentDepth`, default `1`). A run at the depth limit is not offered the `task` tool, so a delegation cycle can no longer start; `DelegationDepthExceededError` is gone. See docs/sub-agents.md.
|
|
403
|
+
- The executor API moved out of the package root to `@lousho/build-ai-agent/executor`: `AgentExecutor`, `AgentBuilder`, `ExecuteOptions`, `resumeAfterApproval`, `streamResumeAfterApproval`, `resumeRequest`, `ResumeExecuteOptions`, `ResumeRequest`, `ToolRegistry` and `globalToolRegistry`. Change `import { AgentExecutor, AgentBuilder } from '@lousho/build-ai-agent'` to `import { AgentExecutor, AgentBuilder } from '@lousho/build-ai-agent/executor'`. `ToolRegistry` is also still in `@lousho/build-ai-agent/tools`. The `@lousho/build-ai-agent/core` subpath is removed: import `AgentBuilder` from `@lousho/build-ai-agent/executor`. `createAgent()`, `ExecutionResult` and every type `createAgent()` uses stay in the root. To move off the executor API entirely, see docs/migrating-to-create-agent.md.
|
|
404
|
+
- The patch guardrails are now called patch checks, so "guardrail" only means the input/output/tool guardrails of `createAgent({ guardrails })`. Rename: `runGuardrails` to `runPatchChecks`, `Guardrail` to `PatchCheck`, `GuardrailResult` to `PatchCheckResult`, `ProposedAction` to `ProposedPatch`, `RunGuardrailsResult` to `RunPatchChecksResult`, `runGuardrailSafely` to `runPatchCheckSafely`, `secretScanGuardrail` to `secretScanCheck`, `createDiffSizeGuardrail` to `createDiffSizeCheck`, `createCommandGuardrail` to `createCommandCheck` (options type `CommandCheckOptions`), `createTestRunGuardrail` to `createTestRunCheck`, `createLintGuardrail` to `createLintCheck`. Arguments, results and behavior are unchanged. `SECRET_PATTERNS` keeps its name.
|
|
405
|
+
|
|
406
|
+
### Security
|
|
407
|
+
- **Behavior change:** MCP tools now ask for approval by default (`approval: 'always'`). A server's own `readOnlyHint` / `destructiveHint` no longer switch approval off or let a tool run in plan mode unless its entry opts in with `approval: 'annotations'` (loaded descriptors then carry `metadata.mcp.annotationsTrusted: false`). An MCP tool whose `<server>__<tool>` name equals a local tool's is refused with `LOUSHO_CONFIG_INVALID` instead of silently replacing it and its approval gate. Set `approval: 'annotations'` on servers you trust to keep the old behavior. See [MCP](docs/mcp.md#approval-for-mcp-tools).
|
|
408
|
+
- `openApiTools()` can refuse private destinations: with the new option `privateAddresses: 'refuse'` (default `'allow'`, unchanged), every operation request and the document fetch, each redirect hop included, fails when its host is or resolves to a loopback, link-local, private or reserved address. Host names go through the pinned DNS lookup `http_request` and `web_fetch` use (one resolution per connection, every address checked, the socket connects to the checked address), so DNS rebinding cannot reach a private address; IP literals in every form `URL` normalizes (decimal, octal, hex, IPv4-mapped IPv6) and `localhost` / `*.localhost` are refused when the tools are made. `allowPrivate` lists hosts that may be private. `'refuse'` cannot be combined with a custom `fetch` (it throws a `ConfigurationError`: a custom fetch's connections cannot be pinned) and needs Node.js (`undici`, `node:dns`); where undici cannot load, making the tools throws a `ConfigurationError`. See docs/openapi-tools.md#security-rules.
|
|
409
|
+
- The registry permission manifest is now enforced when an agent directory loads and runs. `loadAgentDir` re-hashes every file the `lousho-registry.json` receipt lists; an item with edited or missing files is reported `unattested` (a warning naming the item and files, and `manifest.registry` on `resolveAgentDir()`, which also gains the new `Attestation`, `RegistryItemStatus` and `RegistryStatus` types) without refusing the load. Every tool a receipt item owns then runs inside the manifest accepted at install: an `exec: true` or `needsApproval: true` item's tools - and any unattested item's tools - always wait for approval (a tool's own `deny` still denies); `fetch` while the tool runs is limited to the declared `network` hosts; `process.env` shows only the declared `env` names; and a `requiresSandbox` tool gets a narrowed adapter (`run()` refused without `exec`, `writeFile()` without `filesystem: "write"`, a command env of the non-secret base plus the declared variables only). The envelope stays as narrow as the last accepted manifest until `lousho add <name> --overwrite` attests the current files. Limits, unchanged: the guards intercept `fetch` and `process.env`, not `http`/`net`/`axios`, the filesystem, module-scope captures or a container's own network policy; the manifest is still not a sandbox. See docs/registry.md#permission-manifest.
|
|
410
|
+
|
|
411
|
+
### Changed
|
|
412
|
+
- The published package is smaller: ~873 files / ~13.5 MiB unpacked / ~3.65 MiB packed, down from ~902 / ~20.8 MiB / ~5.6 MiB. Test-only files that still shipped (`*.test-d.ts` type tests, `*.testkit.ts` helpers, `__snapshots__/` and the test-replay `__cassettes__/` directories) and the Agent Forge server source map no longer ship, and the SDK's own source maps no longer embed `sourcesContent` — their `sources` still resolve to the shipped `src/`, so `--enable-source-maps` stack traces keep working. `pack-smoke` now also runs `build:studio`, so it measures the tarball that is actually published, and it fails when a file `lousho build` or `lousho studio` needs is missing.
|
|
413
|
+
- Test files are type-checked (`npm run typecheck:tests`, in CI); `pack-smoke` also checks the published declarations with `skipLibCheck: false`.
|
|
414
|
+
|
|
415
|
+
### Added
|
|
416
|
+
- Agent directories: new `agent.*` config keys `permissionMode`, `permissions` (a serializable rule form: `tool` is a name, a list or - in a code config - a `RegExp`; `when` narrows the rule with a record of argument names to matchers - a regular expression string or an operator object (`eq`/`ne`, `lt`/`lte`/`gt`/`gte` on `Number(arg)`, `matches`), or a predicate in a code config; `action` is `allow` / `ask` / `deny`), `compaction`, `limits`, plus `hooks` and `approve`, each a path (relative to the directory) to a file default-exporting the hook(s) / the approver function, or the value inline in a code config. `instructions/<family>.md` appends to `instructions.md` when the resolved model id contains `<family>` (e.g. `openai`, `anthropic`). `lousho build --target=node-server` (and `--target=docker`) bundles a root `hooks.*` / `approve.*` file next to the config and resolves the compiled `.js` sibling; the Cloudflare Worker target carries `permissionMode`, `permissions`, `compaction` and `limits` over and accepts `hooks` / `approve` from a code config (a file path is refused, a Worker cannot import it). See [Agent directories](docs/agent-directories.md).
|
|
417
|
+
- Registry: a new item `type` `kit` is a whole agent directory - its `files` may sit at any (still safe) relative path - so `lousho add <kit> --dir <target>` installs `agent.*`, `instructions.md`, tools, skills and sub-agents in one item, under one permission manifest, and records it in `lousho-registry.json` like any other item. The default registry's first kit is `coding-kit`, a port of the `examples/coding-harness` baseline: workspace tools with checkpoint rewind, an allow-listed shell, permission rules, a loop guard and output cap as hooks, a compaction setting, a `fix-failing-test` skill and a read-only `explorer` sub-agent (manifest: `exec`, `fs-write`; install with `lousho add coding-kit --dir <dir> --yes --allow exec,fs-write`). See [Registry](docs/registry.md).
|
|
418
|
+
- The `pi` provider: `createAgent({ model: 'pi/<pi-provider>/<model>' })` routes calls through `@earendil-works/pi-ai` (`1.0.3`, a new optional peer - `npm install @earendil-works/pi-ai@1.0.3`), the engine `pi-coding-agent` runs on, so pi's whole catalog (OpenRouter, Anthropic, OpenAI, Google,...) is usable natively; e.g. `pi/openrouter/openai/gpt-4o-mini` is OpenRouter's `openai/gpt-4o-mini` (nested slashes are kept: the segment after `pi/` is the pi provider, the rest the model id). Messages (including reasoning blocks, tool calls/results and image parts), tool `parameters` (raw JSON Schema, zod 3/4 or Standard Schema), streamed chunks and usage/cost map to the same surface the `ai`-SDK providers expose, and pi's own retries are pinned off so `retry`/`fallbackModels` stay the only retry layer. Credentials are per nested provider: pi reads each one's own env var (`pi/openrouter/...` reads `OPENROUTER_API_KEY`), and a missing one fails the call with `LOUSHO_PROVIDER_MISSING_API_KEY` naming it; a missing package fails with `MissingPeerDependencyError` naming `npm install @earendil-works/pi-ai@1.0.3`. New export `PiProvider` (+ `PiProviderConfig`, whose `models` injects custom/faux catalog entries). `pi` is Node-only: the `cloudflare-worker` target refuses `pi/...` specs. See docs/providers.md#the-pi-provider.
|
|
419
|
+
- `piAgent()` ("pi-coder"): a Pi coding-agent session (`@earendil-works/pi-coding-agent`, a new optional peer) usable in-process as a `createAgent({ subagents })` entry - `subagents: { coder: piAgent({ cwd, model, description, permissions }) }`. Each `task` call is one Pi session under the task's `sessionId`, so `taskId` resumes and approval resumes reopen the same JSONL transcript even across a restart. `permissions` (the same `PermissionRule`s as `createAgent()`) gate every Pi tool call through a `tool_call` extension handler: `deny` refuses it, `ask` pauses the lead run durably for approval when the caller can pause and refuses it otherwise; approving re-issues that exact call once (Pi has no run-blocked-call API, so a decision prompt asks the model to repeat it and the gate lets that one call through), rejecting sends the note as the next turn. Pi usage rolls into the lead's `result.usage`, and aborts propagate to the Pi session. Node-only: the package is loaded lazily, so a consumer without it still typechecks and `run()` fails with `LOUSHO_PEER_MISSING`. Also new: `defineRemoteSubagent(impl)` wraps any `RemoteSubagent` implementation (same contract as `remoteAgent()`), and `SubagentApprovalPause` is exported for such adapters.
|
|
420
|
+
- Agent directories: three more `agent.*` config keys wire the approval surface. `approvalTtlMs` is `createAgent`'s run-level default for how long a pause for approval stays decidable (milliseconds); a `permissions` `ask` rule may set `ttlMs`, bounding that rule's pauses and winning over `approvalTtlMs` (both positive finite numbers, rejected otherwise). `store` declares a durable store so paused approvals survive restarts ("draft today, approve tomorrow"): `{ "dir": "./.lousho" }` (resolved against the agent directory, staying inside it) becomes a `fileStore()` - sessions, checkpoints and approvals as JSON files, with `historyLimit` / `tokenKey` also declarable; a code config may set an `AgentStore` instance instead. `store` is Node-only: the Cloudflare Worker target rejects it. `loadAgentDir(dir, { approve: null })` / `{ hooks: null }` strip the directory's wiring (the file the config names is not even imported), where `undefined` keeps it. The generated node-server / docker server keeps a config-declared `store` unless `LOUSHO_STORE` is set. See [Agent directories](docs/agent-directories.md).
|
|
421
|
+
- Agent directories: `"engine": "pi"` in a `subagents/<name>/` config makes that directory a `piAgent()` coding sub-agent instead of a nested `createAgent()` one - it lands in the parent's `subagents` map (the `task` tool) and its sessions run in the parent's directory, the workspace both share. Its `model` is the usual `pi/<provider>/<model>` id and its `permissions` gate the Pi session's own tool calls; the remaining config keys are ignored (Pi carries its own prompt and tools). `loadAgentDir()`'s new `piAgent` override injects `PiAgentOptions` into every such sub-agent (a faux `modelRuntime` in tests, a `sessionDir` for durable approvals); the directory always wins `cwd`, `name` and `description`. Cloudflare Worker targets reject `engine` - Pi needs the Node runtime. The default registry's second kit, `coding-pi`, is `coding-kit` on the full Pi stack: pi-provider lead, dir-declared pi coder, same safety rails (install `--allow exec,fs-write,network,env`). See [Agent directories](docs/agent-directories.md#sub-agents).
|
|
422
|
+
- The `approve` callback may return `'defer'` to abstain: the call stays pending (listed by `agent.approvals.list()`, resolvable by `agent.approvals.resolve()` / `streamResolve()` / `answer()`) and the run surfaces the pause - `send()` resolves with `finishReason: 'awaiting-approval'`, as if the callback had never been asked - so an approver can decide the calls it trusts and hand the rest to a human. Note: `'defer'` is now reserved; an approval note that is exactly `'defer'` defers instead of approving. See [Deciding in code](docs/approvals.md#deciding-in-code).
|
|
423
|
+
- Tool-call hook contexts carry `resumedAfterApproval`: `true` only when `preToolCall` / `postToolCall` fire for the post-approval re-run of a call the run paused on (`agent.approvals.resolve()`, `resumeAfterApproval()`). Stateful hooks (loop guards, audit counters) can skip the re-fire with `if (ctx.resumedAfterApproval) return;`. See [Hooks](docs/hooks.md).
|
|
424
|
+
- New `ToolDescriptor.transient` marks a tool bound per run (a memory slot's `remember_*` / `recall_*` tools, bound to the run's scope keys): it is left out of the agent fingerprint, so an approval-resumed run - whose registry intentionally does not rebind them - no longer reports `tools removed` agent drift on every resolve.
|
|
425
|
+
- Approvals can expire. `createAgent({ approvalTtlMs })` (validated: positive finite) sets a default deadline for every pause, `send()`/`stream()` take a per-call override, a `permissions` `ask` rule may set `ttlMs` (wins over the run default), and `ExecuteOptions.approvalTtlMs` covers `AgentExecutor.execute()`; sub-agents inherit the lead's TTL unless they set their own. A pending approval past `expiresAt` resolves as denied with `'approval expired'` - enforced at decision time, so a durable-store resume (a restart, another process) can never approve a stale pause - and `list()` keeps expired entries (marked by `expiresAt`) so `resolve()` still continues the run. `approval.requested` events and `UIPendingApproval` carry `expiresAt` for UI countdowns; expiry writes an audited `deny` decision to `onPermissionDecision`. See [Approvals](docs/approvals.md).
|
|
426
|
+
- `llmCritique(config)`: like `llmJudge` but returns `{ score, feedback }` - one judge call, a `SCORE:`/`FEEDBACK:` prompt, graceful degradation on malformed output - so evaluator-optimizer loops get actionable revision guidance, not just a number. See [Evals](docs/evals.md).
|
|
427
|
+
- Testing helpers: `testToolContext(overrides?)` (`@lousho/build-ai-agent/testing`) builds a valid `ToolExecutionContext` for direct `tool.execute()` calls - `getToken`/`requireAuth` fail loudly unless stubbed - and `toolResultText(message)` (root export) unwraps a `tool` message's JSON-encoded content to its result text.
|
|
428
|
+
- Channels: `ChannelInbound.metadata` now reaches the run as `SendOptions.metadata` (memory scopes and `when` contexts can key on channel-supplied data; it is unauthenticated, unlike `principal`), and `webhookChannel({ principal: (body, req) =>... })` can derive the caller principal from a verified body/header - so a webhook intake (PagerDuty, Grafana) attributes the run to the alerting system. See [Channels](docs/channels.md).
|
|
429
|
+
- New registry kits and examples replicating the most-built agent archetypes: `inbox-triage` (channel intake → classify → draft → approval-gated send, per-sender memory, `@hourly` sweep; install `lousho add inbox-triage --allow env,fs-write`) and `incident-response` (webhook alert → diagnose → risk-tiered approvals → remediate → verify → audit timeline; install `--allow env,fs-write`); examples `deep-research` (orchestrator-workers fan-out with cited synthesis), `support-desk` (triage → `handoff()` to specialist agents), `evaluator-loop` (writer ↔ `llmCritique` revision loop) and `data-analyst` (text-to-SQL with read-only enforcement). Each runs offline on mock models and accepts `OPENROUTER_API_KEY` for a live pass.
|
|
430
|
+
- `claudeProject(dir)`: loads a repository's Claude-Code-conventional context into options that spread into `createAgent()` — `CLAUDE.md`/`AGENTS.md` + `.claude/rules/*.md` become `instructions`, `.claude/skills/*/SKILL.md` load through `loadSkills()` unchanged, `.claude/agents/*.md` frontmatter becomes `subagents` (frontmatter `model` selects the sub-agent's model; `provider`/`model` options are the fallback), and `.claude/scratchpad/` gets a `scratchpad` note tool (name-validated `*.md` writes). Nothing in the directory executes: markdown is read as data, and `.claude/commands`, `settings*.json` and `mcp.json` are listed on `manifest.ignored` rather than loaded. See [Claude Code projects](docs/claude-projects.md).
|
|
431
|
+
- `decide()`: typed answers from the OpenAI Decisions API (`POST /v1/decisions`, public beta — `gpt-6-luna` only) — `predicate` questions return a probability, `choice` returns a value plus per-option `probabilities` and `confidence`, `score` returns a rubric's probability-weighted level index, and any question may come back a `refusal`. Input is a string or messages with `input_text`/`input_image` (inline base64) parts; `apiKey` (default `OPENAI_API_KEY`), `baseURL`, `timeoutMs`, `signal` and `fetch` are overridable. It is the fast path for typed decisions (route/gate/score) — no agent turn and no generated prose — and does not go through providers or OpenRouter. See [Decisions](docs/decisions.md).
|
|
432
|
+
- Four new examples covering the agentic-patterns catalog: `waves` (Workers · Aggregate · Verify · Extend — bounded parallel workers, deterministic merge, a typed `{ verdict, gaps }` verifier, extension waves seeded by its gaps), `phase-pipeline` (discuss → spec → plan → implement → proof gate → report on `FlowBuilder`/`FlowExecutor`, with the implement↔check retry ladder as a `oneOf` chain), `coding-agent-workflows` (a coding agent routed by a typed decision — `output` schema `{ route, confidence, reason }`, a confidence floor escalating low-confidence calls to the careful path), and `connectors` (MCP connectors à la Claude — runs fully offline against a mock `serveMcp` stdio server exposing `docs_*`/`slack_*` tools; README maps Google Docs, Slack, GitHub and Notion). Plus `hostinger-deploy`: build an agent with `lousho build --target=docker`, ship it to a Hostinger VPS over SSH and smoke-test `/health` + bearer auth + approval pause/resume. See [Prompting and context techniques](docs/prompting-techniques.md), [Connectors](docs/connectors.md) and [Hostinger VPS](docs/hostinger-vps.md).
|
|
433
|
+
- `lousho add` has a default registry: without `--registry` or `LOUSHO_REGISTRY` it reads `https://registry.lousho.com/index.json` - the `registry/dist/` folder of this repository served as static JSON - instead of failing with "no registry configured; there is no hosted registry yet", so `lousho add --list` works out of the box. It carries five starter items: the tools `open-meteo-weather` (current weather by city through Open-Meteo, no API key) and `github-issues` (list and create issues through `api.github.com`; creating asks for approval), the skills `changelog` and `code-review`, and the `generic-webhook` channel (HMAC-SHA256 signature verification from `WEBHOOK_SECRET`). `--registry` and `LOUSHO_REGISTRY` still override it, and the new value `none` (in either place) disables the registry entirely with the old `LOUSHO_CONFIG_INVALID` "no registry configured" error, for offline or locked-down use. The items live in `registry/` as an `item.json` plus their files per folder; `npm run registry:build` validates each against the registry schemas, the path rules and the M7a code/manifest check, and writes the committed `registry/dist/` (`npm run registry:check` fails when it is stale and runs in CI). See docs/registry.md.
|
|
434
|
+
|
|
435
|
+
### Fixed
|
|
436
|
+
- A channel button click (Slack, Discord), inline-keyboard tap (Telegram), Adaptive Card press (Teams) or `/approve` command (GitHub) after a restart is now bound to the conversation's session before the approval is resolved, so its continuation is appended to the session transcript again. The decision names the conversation it came from (`inbound`); with checkpointed sessions (`mountChannels` `store` with `checkpoints`, e.g. a `SqliteStore`) it is accepted only when that session's pending turn waits on exactly that approval id and kind - a decision for another conversation, a replay of a decided one and a decision of the wrong kind are refused (`LOUSHO_APPROVAL_NOT_FOUND`) before the approval is touched, and `onDecision` audits only decisions that pass, so a replayed click cannot run the tool twice and the thread's next message continues the same session. Without a checkpoint store nothing can be checked, so the approval store alone decides, as before.
|
|
437
|
+
- A channel's function `approvers` works after a restart: `ctx.approval(id)` asked only this process's `agent.approvals.list()`, so a pause the restarted process did not make looked the same as a denial and the function failed closed. `ApprovalStore` gains an optional `load(id)` - `resolve()` without removing the record; every shipped store implements it, and a custom store without it keeps compiling and behaves as before - and the new `agent.approvals.get(id)` reads through it. `ctx.approval` falls back to that, so the function sees the same request (`toolName`, `input`, `sessionId`, `principal`) it saw before the restart and fails closed only when no store knows the id (a process-local approval store still forgets its pauses at a restart).
|
|
438
|
+
- A hand-written Cloudflare Worker can now bundle `createAgent`. The package root also exports file stores, the agent-directory loader, MCP over stdio and sandboxes, so bundling it for `platform: 'browser'` failed with `Could not resolve "async_hooks"` (and `crypto`, `fs/promises`, `http`, `net`, `stream`) - only the generated Worker ever bundled, because `lousho build --target=cloudflare-worker` redirects those imports to shims itself. `@lousho/build-ai-agent/worker` is a new subpath exporting `createAgent` (and its config types), `memoryStore`, the KV stores (`KVStore`, `KVCheckpointStore`, `CHECKPOINT_KV_BINDING`), the schedule helpers (`defineSchedule`, `handleScheduled`), `serveFetch` (the `/chat` API as a fetch handler), the providers a Worker can run and the whole Worker-safe subset agent-directory code may import. The SDK's own build produces it through the same Node-builtin/sandbox/QuickJS shims the adapter applies (plus new `ollama-ai-provider` and `@modelcontextprotocol/sdk` ones, which an external specifier would leave unresolvable and `mcpServers` therefore fails on first connect - use `lousho build` for MCP on Workers), so the published entry carries no `node:` import for a user's own bundler to fail on; `ai`, `zod`, `@opentelemetry/api` and the `@ai-sdk/*` provider packages stay external specifiers resolved from the user's install. Guarded by `src/deploy/worker.test.ts`; see docs/cloudflare-workers.md#writing-your-own-worker.
|
|
439
|
+
- The published `.d.ts` files no longer import the types of optional peers: `SubprocessSandboxOptions.dockerOptions` is now the structural `DockerConnectionOptions` (same fields as dockerode's `DockerOptions`, passed through unchanged) and `McpClientLike` is the structural subset of the MCP SDK's `Client` it already was (`listTools` / `callTool`). Before, `dist/index.d.ts` imported `dockerode` and the shared schema chunk imported `@modelcontextprotocol/sdk`, so a consumer compiling with `skipLibCheck: false` without those optional peers installed got TS2307 inside the SDK's own declarations. A dockerode `DockerOptions` or MCP `Client` value still satisfies both types.
|
|
440
|
+
|
|
441
|
+
## [1.0.0-alpha.18] - 2026-10-08
|
|
442
|
+
|
|
443
|
+
See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 through 1.0.0-alpha.19.
|
|
444
|
+
|
|
445
|
+
## [1.0.0-alpha.17] - 2026-10-07
|
|
446
|
+
|
|
447
|
+
See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 through 1.0.0-alpha.19.
|
|
448
|
+
|
|
449
|
+
## [1.0.0-alpha.15] - 2026-10-06
|
|
450
|
+
|
|
451
|
+
See the 1.0.0-alpha.19 section, which covers everything from 1.0.0-alpha.15 through 1.0.0-alpha.19.
|
|
452
|
+
|
|
453
|
+
## [1.0.0-alpha.9] - 2026-10-04
|
|
454
|
+
|
|
455
|
+
### Security
|
|
456
|
+
- `http_request` is no longer open to DNS rebinding. It resolved a host name once to check it against the private-address list and then let `fetch` resolve it again to connect, so a name that answered a public address to the check and `127.0.0.1` (or a cloud metadata or intranet address) to the connection reached the private address; the docs and a comment in the Worker runtime described the check as rebinding-safe. Now the one resolution is the check: the in-process path connects through an undici `Agent` whose `connect.lookup` checks every address and hands the socket the address it checked (each redirect hop included), and the sandboxed path (`sandboxExecute`, which agents use) resolves and checks the host in the agent's process and has the sandboxed process connect to that address (node:http/https with a fixed lookup; the host name still goes in `Host` and TLS SNI). The private-address list is now the shared one (below), which adds `0.0.0.0/8`, `100.64.0.0/10`, `192.0.0.0/24`, `198.18.0.0/15`, multicast and reserved `224.0.0.0/3`, `::`, `ff00::/8`, NAT64 `64:ff9b::/96` and 6to4 `2002::/16` to what `http_request` refused. Behavior changes: a host name that cannot be resolved now fails with the resolver's error instead of being passed to `fetch`; `undici` is loaded on the first `http_request` request (not only with `validateSSL: false`). New option `createHttpTool({ allowPrivate })` for hosts that may resolve to private addresses. Cloudflare Workers have no DNS hook, so neither tool exists in the Worker build; docs/deployment.md now says what a Worker can and cannot check.
|
|
457
|
+
- A run's principal is fixed for the whole run: an approval resolved by another caller, a restart, `agent.resume()` or a session's pending turn continue as the caller that started the run, and the decider is recorded as the approver (`ctx.approval.by`), never swapped in as the run's principal, so a later caller cannot use an earlier caller's grant by approving their pending call. The principal comes only from route auth or a channel's verified sender; the approvals route and channels never read it from a body field. It is frozen before a tool, a policy or a hook sees it. Remote sub-agents are not told who the caller is.
|
|
458
|
+
|
|
459
|
+
### Changed
|
|
460
|
+
- Behaviour and types: a tool whose `execute` returns an async iterator that is also async iterable (what an `async function*` returns) is now iterated: its last yielded value is the result, instead of the iterator object itself (which used to reach the model as `{}`). `AgentEvent` gains `tool.partial` (`ToolPartialEvent`), `isToolEvent()` narrows to it too, `UIToolCall` gains `partial?`, the `tool-output-available` UI chunk type gains `preliminary?: true`, and `DefinedTool` gains a third, defaulted type parameter (what `execute` resolves to). Migration: a `switch` over `event.type` meant to be exhaustive needs a `'tool.partial'` case; code using the result of `isToolEvent()` as start/done/error only should check `event.type`; a tool that means to return an async generator as a value should wrap it (`{ items: generator }`) - a value that is only async iterable, such as a `ReadableStream`, is still returned as it is.
|
|
461
|
+
- Types only: `AgentEvent` gains the `handoff` event (`HandoffEvent`) and `ExecutionResult` the optional `agentName` (set on every result the executor returns: the agent of the final reply). Migration: a `switch` over `event.type` meant to be exhaustive needs a `handoff` case; a test comparing a whole `ExecutionResult` with `toEqual()` now sees `agentName`. Nothing changes at runtime for an agent without `handoffs`.
|
|
462
|
+
- BREAKING (types only): `McpServerStatus` gains `'needs-auth'`, and `AgentOAuth` (the type of `agent.oauth`) gains `mcpSignInUrl(server)`. Migration: an exhaustive `switch` over `connectMcp(...).status()` values needs a `'needs-auth'` case (only servers with `oauth` ever report it); an object written by hand to satisfy `AgentOAuth` (a test double, say) needs an `mcpSignInUrl` method. Runtime behaviour without `oauth` is unchanged.
|
|
463
|
+
- BREAKING (types only): `ToolExecutionContext` gains the methods `getToken(provider)` and `requireAuth(provider)`, and `ApprovalKind` gains `'sign-in'`. Migration: code that builds a tool's execute context by hand (for example to call `tool.execute(args, { toolCallId, messages })` in a unit test) must add the two methods or cast (`as ToolExecutionContext`); a `switch` over `PendingApproval.kind` / `approval.requested`'s `kind` that is meant to be exhaustive needs a `'sign-in'` case. Contexts the SDK builds have them already; nothing changes at runtime.
|
|
464
|
+
- BREAKING: `send()` and `AgentExecutor.execute()` with a listener (`createAgent({ onEvent })`, `onAgentEvent`, or the deprecated `onEvent` option) now stream model calls: a step's text arrives as several `text.delta` events, as in `stream()`, instead of one `text.delta` per step after the step finished. What a listener receives otherwise does not change: the same event types in the same order, exactly one `text.done` per step with the step's whole text, the same `step.done` usage and `run.done`, the same `ExecutionResult` (text, tool calls, usage, steps). A provider without `stream()`, or whose `supportsStreaming(model)` is `false`, still gives one `text.delta` per step. Output guardrails on `send()` still check only the final reply (a `stream()` you iterate checks every step's text, as before). `send()` without a listener still calls `provider.generate()`. Hooks' `ctx.emit` is set whenever the run has listeners (it was already; the docs said "only when streamed"). Migration: concatenate `text.delta`, or listen to `text.done` for whole steps; `execute({ streamModelCalls: false })` (new option, default `true`) restores whole steps.
|
|
465
|
+
- `MockLLMProvider` / `createMockProvider()`: `stream()` now streams the same step `generate()` returns: the same tool calls (it streamed none, so a streamed mock run never called a tool), finish reason and usage, and text chunks that add up to exactly the generated text (the old chunks ended with an extra space: `'This is a mock response. '`). Runs on the mock provider with a listener (Agent Forge's, for one) therefore give the same results as before, and `stream()` runs on it now call tools as `send()` does.
|
|
466
|
+
- Agent Forge: runs now stream their model calls, so the run WebSocket (`WS /agents/:id/stream`) carries several `text.delta` events per step; the chat, logs and trace views are unchanged (they use `text.done` and the reconciled messages). Stop is checked between streamed chunks and after the last one, as it was after each `generate()` call.
|
|
467
|
+
- CI peer matrix: the `typecheck-ai7` and `typecheck-zod4` jobs are replaced by one `peers` job with five entries, each installed for real on top of the default install and run through `tsc`, `test:types`, both builds and `npx vitest run`: `ai4-zod4`, `ai6-zod3`, `ai6-zod4`, `ai7-zod3`, `ai7-zod4`. The two zod 4 entries on `ai` 6/7 also install `ollama-ai-provider-v2` (3.x / 4.x), and the new `src/providers/ollamaV2.contract.test.ts` runs `OllamaProvider` against the real package and a local fake Ollama server (`generate()`, `stream()`, a tool-call turn through `createAgent().send()` and `.stream()`). The `ai-v6` dev alias replaces the hand-made `ai` 6 stand-in in `aiMajorPeers.test.ts`.
|
|
468
|
+
- `lousho init --provider ollama` now scaffolds `ai@^7.0.0` with `ollama-ai-provider-v2@^4.0.0` and `zod@^4.0.0` (it was `ai@^4.3.19` with `ollama-ai-provider@^1.2.0` and zod 3). Existing projects are not touched; to stay on the old pairing keep `ai@^4.3.19` and `ollama-ai-provider@^1.2.0`.
|
|
469
|
+
- A run paused inside a sub-agent compares the sub-agent with its current definition on resume, under the lead's `onAgentDrift`. Behaviour change: with `onAgentDrift: 'error'`, `agent.approvals.resolve()` (and `resumeAfterApproval()`) now also rejects with `LOUSHO_AGENT_DRIFT` when a paused sub-agent's instructions, model or tools changed; before, only tool and provider default-model changes of a sub-agent were seen, and always as a warning. This holds at any depth (a sub-agent of a sub-agent uses the lead's mode). With `'warn'` (still the default) the `agent.drift` event carries `subagent`. A rejected resume puts the lead's approval record and its `'awaiting-approval'` checkpoint back, so fixing the sub-agent and resolving again finishes the run; before, a sub-agent's missing-tool error became an error result of the `task` call and the run continued. Migration: none for `'warn'` / `'ignore'`; with `'error'`, resolve approvals paused before a sub-agent changed with the old definition, or use `'warn'`. See docs/durable-execution.md#resuming-with-a-changed-agent.
|
|
470
|
+
- `lousho add` enforces the permission manifest at install. Behaviour change: an item whose code does not match its manifest is now refused with the new code `LOUSHO_REGISTRY_MANIFEST_MISMATCH`, and nothing is written. Before writing, every JavaScript / TypeScript file of the item is scanned (comments stripped) for `child_process` / `execa` / `shelljs` / `createShellTool` / `SubprocessSandbox` (needs `exec: true`), `fs` imports and `createFsTools` (needs `filesystem: "read"`), file-writing calls (needs `"write"`), `fetch(` and network modules (needs a non-empty `network`), `http(s)://` hosts in strings (each must match a `network` entry) and `process.env.NAME` reads (each must be in `env`); `eval(`, `new Function(`, a computed `process.env[...]` and a non-literal `import()` / `require()` are always refused. Each finding is listed as `<file>:<line>: <what> (<fix>)`. `network` entries must now be host patterns and `env` entries variable names (`LOUSHO_REGISTRY_INVALID` otherwise). With `--yes`, an item asking for `exec: true`, `filesystem: "write"`, a `network` or an `env` is refused unless each is named in the new `--allow exec,fs-write,network,env` flag; the interactive prompt lists them, and the printed manifest marks them `[elevated:...]`. After writing, `lousho add` records the item (type, registry, time, permissions, each file's sha256) in `<agent-dir>/lousho-registry.json`. Migration: registry authors declare what their code uses; scripts that run `lousho add --yes` add `--allow` with the permissions they accept. It is a static check, not a sandbox; see docs/registry.md#permission-manifest. The example item in docs/registry.md now uses `input` (not `inputSchema`).
|
|
471
|
+
- Behaviour change: continuing an unfinished checkpointed run (`send(message, { sessionId, principal })`, `AgentExecutor.execute({ sessionId, checkpointStore, principal })`) with a principal other than the one it was saved with now throws `LOUSHO_CONFIG_INVALID` instead of running the new input; without a principal (`agent.resume()`, a session's pending turn) the run continues as the saved caller. Migration: resume such a run without `principal`, or start a new session. `onPermissionDecision` is called with a second argument, `{ principal }` (callbacks that take one argument are unaffected). Checkpoints, approval snapshots and pending approvals now store the run's principal (claims included): treat those stores as personal data. Checkpoints and snapshots written before this release resume with no principal; no migration.
|
|
472
|
+
|
|
473
|
+
### Added
|
|
474
|
+
- `lousho dev` and `lousho chat` take `--traces` (or `--traces=<dir>`) to write local traces with `fileTraceExporter()`: `.lousho/traces` by default, the directory `lousho traces` reads, for spec files, agent directories and modules (`specToAgent()` now also accepts an `exporter` option). It is opt-in because trace files hold prompt and tool content even with `captureContent: false` (only `redactContent: true` removes it); the command prints where it writes. `lousho init` projects already ignore `.lousho/`.
|
|
475
|
+
- Code mode: `createAgent({ codeMode: true | CodeModeOptions })` adds a `run_code` tool. The model writes the body of an async JavaScript function that calls the agent's tools as `await tools.<name>(args)`, loops and combines their results, and returns one JSON value; `run_code` returns `{ result, logs, toolCalls }` and only that enters the transcript. Scripts run in a QuickJS WebAssembly isolate from the new optional peer `quickjs-emscripten` (`^0.32.0`, listed by `lousho doctor`; without it the first run of a `codeMode` agent fails with `MissingPeerDependencyError`): a fresh runtime per script, no `require`, `import`, `process`, `fetch`, timers or host objects, values passed as JSON copies, and the limits `timeoutMs` (30,000, whole script including awaited tool calls), `memoryLimitBytes` (64 MiB), `maxToolCalls` (50) and `maxOutputChars` (20,000, return value plus logs). `tools` picks the callable tools (default: all but `run_code`, `ask_question`, sub-agent tools, `tool_search`, `load_skill` and deferred tools); `exclusive: true` takes them off the model's tool list. Every inner call (id `<run_code id>:<n>`) goes through the same gate as a direct call: validation, hooks, permission rules and modes (plan mode refuses non-read-only inner calls; `run_code` itself is read-only), tool guardrails (a block stops the run), `needsApproval`, sandbox routing and the principal. A call that would pause the run (approval, sign-in, a sub-agent's approval) throws inside the script instead and the run does not pause; `run_code` itself can be paused for approval by a rule and runs after the decision. A failed script's error lists its first five tool calls with their (clipped) results. New export: type `CodeModeOptions`. Types: `parentToolCallId?` on the `tool.start`, `tool.partial`, `tool.done` and `tool.error` events (set on inner calls; their `tool.done` / `tool.error` comes before `run_code`'s); inner calls get `execute_tool` spans under the `run_code` span with the attribute `lousho.tool.parent_call_id`. No migration needed: nothing changes without `codeMode`. See docs/code-mode.md.
|
|
476
|
+
- Streaming partial tool results: a tool's `execute` may be an `async function*`. Every `yield` is a complete snapshot of its output, streamed as the new `tool.partial` event (`{ toolCallId, toolName, output, index }`, `index` from 0) between the call's `tool.start` and `tool.done`; the last yield is the result the model gets (and `tool.done`'s `result`), a `return` value is ignored and a generator that yields nothing has the result `undefined`. Snapshots are not sent to the model, not added to the transcript, not checkpointed and not recorded in trace spans; hooks see only the result; tokens from `ctx.getToken()` are redacted in snapshots like in the result. The main loop, a streamed resume after an approval (`agent.approvals.streamResolve()`) and a flow's tool-call node iterate the generator; aborting the run stops reading it and calls its `return()`; `toolConcurrency` counts the whole generator run. A call that pauses for a sign-in mid-stream runs again from the start (index from 0); partial output is lost on a crash. `defineTool` types it: `ToolOutput<typeof tool>` is the yielded type (new helper type `ToolResultOf<R>`). The UI reducer (`useLoushoAgent()` and the Vue / Svelte bindings) keeps the latest snapshot as `toolCalls[i].partial`, and `toUIMessageStream()` sends snapshots as the AI SDK's preliminary tool output (`tool-output-available` with `preliminary: true`). See docs/tools.md#streaming-partial-results.
|
|
477
|
+
- Tool search: tools marked `deferLoading` (`defineTool({ deferLoading: true })`, or every tool of an MCP server with `mcpServers.<name>.deferLoading: true`, also in spec files and agent directories) are withheld from the model, which finds them with a built-in `tool_search` tool (`{ query }` -> `{ loaded: [{ name, description }], more }`); the tools it finds are sent from the next step on, and `tool_search` stops being offered once everything is loaded. New option `toolSearch` on `createAgent()` and `AgentExecutor.execute()`: `{ thresholdPercent? (default 0.1 of the context window; 0 always defers), maxResults? (5), contextWindow?, search? }`, or `false` to send every tool upfront. Below the threshold nothing is deferred and no `tool_search` exists. Which tools are loaded is read from the transcript at each model call, so loading survives crash resume, approvals, forks and session turns; compaction that prunes a `tool_search` result unloads its tools; a handoff target or a sub-agent never inherits the lead's loaded tools. Skills, memory, `task`, `ask_question` and handoff tools are never deferred. New exports: `ToolSearchOptions`, `ToolSearchResult`, `rankToolsByKeywords()`. Types: `ToolDescriptor.deferLoading`, `DefineToolOptions.deferLoading`, `McpStdioServerSpec` / `McpHttpServerSpec.deferLoading`, `LoadMcpToolsOptions.deferLoading`, `SubagentSpec.toolSearch`, `ToolMetadata.mcp.server`. Behaviour change: tools loaded by `loadMcpTools()` / `connectMcp()` now carry their server name in `metadata.mcp.server`; a test comparing an MCP tool's whole `metadata` with `toEqual()` needs that key. A user tool named `tool_search` is refused (`LOUSHO_CONFIG_INVALID`) only when deferral is active. See docs/tool-search.md.
|
|
478
|
+
- Handoffs: `createAgent({ handoffs: [billing, handoff(refunds, options)] })` lets an agent hand the whole conversation to another `createAgent()` agent (with a `name` and a `description`), offered to the model as one `transfer_to_<name>` tool each. The handoff call is not run as a tool: once the other calls of its step are done, the run goes on in the same `send()` / `stream()` as the target, with its instructions (replacing the system prompt), model, tools, skills, sub-agents, reasoning and own handoffs, the lead's guardrails and permission rules before its own, on the transcript the handoff's `inputFilter` returns (`handoffFilters.removeToolCalls`, `handoffFilters.lastUserMessage`, or your own). Hooks, `limits` and spend, `maxSteps` (counted across agents), the signal, the approval, checkpoint and token stores, listeners, `output`, the permission mode and the principal stay the run's. `handoff()` options: `toolName`, `description`, `input` (validated; bad arguments get a tool error and no handoff), `inputFilter`, `onHandoff`, `isEnabled` (a `PerRun<boolean>`). Only the first handoff call of a turn is honored; `maxHandoffs` (default 5) caps one run. The transcript marks each handoff with `metadata.handoff = { from, to }`; a session's next turn, `agent.resume()`, `session.resume()` and `agent.approvals.resolve()` continue as the agent it last handed off to (`agent.send()` without a session starts at the lead), and the resume drift check compares with that agent. Approvals remembered with `once()` are dropped at a handoff. A `transfer_to_*` call passes the run's gate like a tool call: `permissions` rules apply to it (`deny` gives the model a `kind: 'denied'` tool error and no handoff happens; `ask` pauses the run for approval and the decided call then hands off or reports the rejection), pre/post tool hooks may deny it or rewrite its arguments, and its decision is audited to `onPermissionDecision` / `permission.decision`. Each handoff appends a routing system note (`metadata.handoff`) carrying the validated call arguments to the transcript the target reads; `handoffFilters.removeToolCalls` keeps it and a custom `inputFilter` may drop or replace it. `createAgent()` warns once when a reachable handoff target was created with run-level options (`approve`, `approvalStore`, `permissionMode`, `approvalTtlMs`, `store`) - a run keeps the entry agent's. New `handoff` event (`from`, `to`, `toolCallId`) and `ExecutionResult.agentName`; `ExecuteOptions.handoffs` / `maxHandoffs` for the executor API. Remote agents cannot be handoff targets. See docs/handoffs.md.
|
|
479
|
+
- OAuth for HTTP MCP servers, per the MCP authorization spec: an `mcpServers` HTTP entry takes `oauth: { redirectUri, clientId?, clientSecret?, scopes?, clientName? }` (new type `McpOAuthOptions`; refused on stdio entries by the spec schema). The MCP SDK's client does discovery (RFC 9728 protected resource metadata, RFC 8414 authorization server metadata), dynamic client registration (RFC 7591) when no `clientId` is given, PKCE and resource indicators (RFC 8707), and refreshes an expired token; tokens are kept in the agent's encrypted token store as provider `mcp-<server>`, owned by the app. An operator signs the agent in once with the new `agent.oauth.mcpSignInUrl(server)`; N9b's `agent.oauth.complete()` and `GET /oauth/callback` finish it. Without a usable token the server's status is the new `'needs-auth'` and connecting fails with the new error code `LOUSHO_MCP_AUTH_REQUIRED` (a tool call refused mid-run is a tool error with that code; nothing pauses). `connectMcp(servers, { tokens })` takes the token store. Security: `oauth` needs an https `url` (http only for localhost, 127.0.0.1 and [::1]); an `Authorization` header together with `oauth` is refused; with `oauth`, `headers` go only to the MCP server's origin, never to the authorization server; a stored grant is only used for the `url` it was obtained for (a changed `url` needs a new sign-in); no token, code, verifier or client secret appears in errors, logs, `status()`, events or results. Out of scope for now: per-user MCP grants, a `lousho mcp login` command, OAuth over stdio. See docs/oauth.md, "MCP servers with OAuth".
|
|
480
|
+
- Parallel input guardrails: `IoGuardrail.runInParallel` (default `false`) makes an `input` guardrail run while the first model call is already in flight, so a slow, model-backed check no longer delays the answer by its whole latency; `promptInjectionGuardrail({ runInParallel })` and `moderationGuardrail({ runInParallel })` take it as an option. The blocking input guardrails run first as before (rewrites included), then the parallel ones start together on that input. Until all have passed, the call's `text.delta`, `reasoning.*` and provider-run tool events are held in order (on the iterator and `onAgentEvent` alike), its tool calls do not run, and `onLLMResponse`, post-generate hooks, output guardrails, `text.done` and the checkpoint after the response wait. A trip aborts the call (its signal only, not the run's), drops the held events and ends the run with `finishReason: 'guardrail'`, `result.guardrail` and `guardrail.tripped` (or `GuardrailError` under `onTripped: 'throw'`), with nothing of the call in the transcript or a checkpoint; the call's usage counts in `result.usage` only when the provider reported it. A parallel check cannot rewrite (`action: 'rewrite'` blocks, the reason prefixed `runInParallel guardrails cannot rewrite: `), and a check that throws fails the run. Ignored on `output` and `tools` guardrails. No change for guardrails without the flag. See docs/guardrails.md#input-and-output-guardrails.
|
|
481
|
+
- OAuth sign-in for tools: `defineOAuthProvider({ name, displayName?, credentialOwner?, authorizationUrl, tokenUrl, clientId, clientSecret?, clientAuth?, scopes?, redirectUri, authorizationParams?, fetch? })`, and in a tool `ctx.getToken(provider)` and `ctx.requireAuth(provider)`. Without a usable token the run pauses durably like an approval (`finishReason: 'awaiting-approval'`, `kind: 'sign-in'`, `signIn: { provider, displayName?, url }` on the pending approval and the `approval.requested` event); the authorization URL uses PKCE S256 and a single-use 256-bit `state` that expires after 10 minutes, and the verifier is kept only in the encrypted pending record. The new route `GET /oauth/callback` (`lousho dev`, the node server, the Worker) and `GET <basePath>/oauth/callback` (`createRouteHandler`) is not behind route auth (a browser redirect carries no API token): it calls the new `agent.oauth.complete({ state, code?, error? })`, which stores the token for the paused run's principal and answers a small HTML page (400 for an unknown, used or expired state). Approving the pause (`agent.approvals.resolve({ id, approved: true })`, or the approvals route) runs the tool call again; before the callback it throws `LOUSHO_SIGNIN_PENDING` (HTTP 409) and the run stays paused. Tokens within 60 s of expiry are refreshed; `requireAuth()` deletes a refused token and pauses. A user credential needs a principal (`LOUSHO_OAUTH_PRINCIPAL_REQUIRED`); an app credential (`credentialOwner: 'app'`) is never asked of a chat user (`LOUSHO_OAUTH_APP_SIGNIN_REQUIRED`) and is connected by the operator with `agent.oauth.signInUrl(provider)`. The `approve` callback is never asked about a sign-in. Slack sends the link with `chat.postEphemeral` and Discord as an ephemeral follow-up, both without buttons; GitHub never posts it in a public thread; behind `createDeployedServer()` and `lousho dev` the callback continues the paused channel turn. The UI state (`pendingApproval.signIn`), the `data-lousho-approval` part, `lousho dev`'s page, `lousho chat` and `lousho acp` show the link. A tool result that contains a token handed out during that call is redacted to `[REDACTED]` with a warning. New error codes `LOUSHO_OAUTH_PRINCIPAL_REQUIRED`, `LOUSHO_OAUTH_APP_SIGNIN_REQUIRED`, `LOUSHO_OAUTH_STORE_MISSING`, `LOUSHO_OAUTH_STATE_INVALID`, `LOUSHO_SIGNIN_PENDING`, `LOUSHO_OAUTH_TOKEN_EXCHANGE_FAILED`. See [OAuth](docs/oauth.md#tools-that-need-sign-in).
|
|
482
|
+
- Hosted tools on Anthropic and OpenRouter: `webSearch()` and `codeInterpreter()` now also run on the Anthropic provider (`ai` 6 with `@ai-sdk/anthropic` 3, `ai` 7 with `@ai-sdk/anthropic` 4): `webSearch()` is the newest `tools.webSearch_*()` the installed package exports (options `maxUses`, `allowedDomains`, `blockedDomains`, `userLocation`), `codeInterpreter()` the newest `tools.codeExecution_*()`; events and `usage.hostedToolCalls` keep the neutral name `code_interpreter`. `fileSearch()` on Anthropic, and `codeInterpreter()` / `fileSearch()` on OpenRouter, are `LOUSHO_HOSTED_TOOL_UNSUPPORTED`. `webSearch()` works with every `openrouter/...` model on any `ai` major: the provider adds OpenRouter's `openrouter:web_search` server tool to the request body (after the function tools, composing with the `reasoning` field; `maxUses`, `allowedDomains`, `blockedDomains` and `searchContextSize` map to its parameters) and reports one hosted call per step that searched, read from the response: the cited urls from `url_citation` annotations as `sources`, and `result: { requests }` when OpenRouter reports `usage.server_tool_use.web_search_requests`. OpenRouter bills each search on top of tokens (not in `costUsd`). `supportsHostedTool()` on both providers reflects this. The per-provider table in docs/hosted-tools.md is complete, and a replayable cassette from a live OpenRouter search is in the test suite (`npm run test:live` re-records it with `OPENROUTER_API_KEY`).
|
|
483
|
+
- Hosted provider tools: `webSearch()`, `codeInterpreter()`, `fileSearch()` and `hostedTool(name, aiSdkTool)` go in `createAgent({ tools })` next to local tools (array or record form; in the record form the key must be the tool's name), and the provider runs them inside the model request. OpenAI maps them to `openai.tools.webSearch()` / `.codeInterpreter()` / `.fileSearch()` on `ai` 6 with `@ai-sdk/openai` 3 and `ai` 7 with `@ai-sdk/openai` 4 (options it does not take, such as `maxUses`, are dropped with one warning); `hostedTool()` passes any AI SDK provider tool through on `ai` 6 or 7, including on `fromAiSdk()` models. Provider-executed calls are never handed to the executor as local tool calls (on `ai` 6/7 they used to come back as calls of an unknown tool): they are reported as `tool.start` / `tool.done` / `tool.error` with the new optional field `executedBy: 'provider'` (as they stream in, or after a non-streamed call in call order; results capped at 20,000 characters of JSON), kept on the step's assistant message as `metadata.hostedToolCalls` (with url sources; not sent back to the model), counted in `usage.hostedToolCalls` and `run.done` usage per tool name (fees not in `costUsd`), and named on the `chat` span as `lousho.hosted_tool_calls`. A step whose only calls the provider ran ends the run even when the provider reports `tool-calls`. No permission rule, guardrail, approval or hook runs for them; instead, in permission mode `'plan'` only the read-only `webSearch()` and `fileSearch()` are sent to the provider (`codeInterpreter()` and `hostedTool()` are left out of the request, read before every model call), and every other mode sends them all. New: `ExecuteOptions.hostedTools`, `GenerateOptions.hostedTools`, `GenerateResult.hostedToolCalls`, `HostedToolCall`, stream chunks `hosted-tool-call` / `hosted-tool-result`, optional `LLMProvider.supportsHostedTool(type)`, `mockModel` turns with `hostedToolCalls`, cassettes that keep hosted calls, error code `LOUSHO_HOSTED_TOOL_UNSUPPORTED` (ai 4, Ollama, a provider without `supportsHostedTool()`, or an `@ai-sdk/openai` without the tool). Sub-agents do not inherit the lead's hosted tools; the agent fingerprint includes them. Anthropic and OpenRouter mappings follow in N1b (below). See docs/hosted-tools.md.
|
|
484
|
+
- `session.history()` and `session.fork({ fromStep, id?, patch? })` on `agent.session()` sessions. `history()` lists the steps of the committed transcript (one per model response: `step` numbered from 1 across the session, `turn`, `messageIndex`, `text`, and `toolCalls` with `args` and `result`). `fork()` saves the transcript up to and including a step (`0` keeps nothing), optionally with one tool result replaced (`patch.toolResult`), under a new id in the same store (default `<id>-fork-<n>`) and returns a session of the same agent and options (with the permission mode the session has at that moment); the original is not changed. The spend recorded for `limits` up to that point carries over. A turn waiting on an approval or a question, or an interrupted checkpointed turn, stays with the source session, so it is resolved once; workspace files are not rewound. Works on every shipped store (memory, `fileStore()`, `SqliteStore`, `KVStore`), with one contract suite run against each. New types `SessionHistoryStep`, `SessionForkOptions` and `SessionSpawner`; new error codes `LOUSHO_SESSION_STEP_NOT_FOUND`, `LOUSHO_SESSION_EXISTS` and `LOUSHO_SESSION_FORK_UNSUPPORTED`; `AgentSession`'s constructor takes an optional fourth argument, the function that creates its forks. See [Forking a session](docs/sessions.md#forking-a-session).
|
|
485
|
+
- Permission modes: `createAgent({ permissionMode })`, `send()` / `stream()` `{ permissionMode }`, `agent.session({ permissionMode })` and `session.setPermissionMode()` put an agent in `'plan'` (only read-only tools run; everything else is refused with `kind: 'denied'`, also when an `allow` rule matched, and a run that starts in plan mode gets one system-prompt paragraph saying so), `'acceptEdits'` (file edits that would ask run without asking) or `'dontAsk'` (a call that would ask is refused; nothing pauses). The modes are presets applied after hooks, permission rules, tool guardrails and `needsApproval`, so no mode turns a deny into a run. A function mode, and a session's mode, are read at every tool call, so a switch applies from the next call, also mid-turn and in a paused turn continued by `agent.approvals.resolve()`. Sub-agents run under the lead's mode unless it is `'default'`; a remote sub-agent is refused in plan mode. Audit: under a mode other than `'default'` every call is audited, `PermissionDecisionEntry.mode` says when the mode changed the outcome, and the new `onPermissionModeChange` records each switch. New: `PermissionMode` and `PermissionModeChange` types, `defineTool({ editsFiles })` (`metadata.editsFiles`, set on `write_file` and `edit_file`), `readOnlyHint: true` on `load_skill`, `recall_<name>`, `agent_status` and `agent_await`. The mode is not saved in checkpoints. Example: `examples/plan-mode`. See [Permission modes](docs/permission-modes.md).
|
|
486
|
+
- Semantic recall: a memory slot can recall by meaning. New `EmbeddingProvider` (`{ id, embed(texts) }`) and `aiSdkEmbedder(model, { id?, maxBatch? })`, which wraps any AI SDK embedding model through `embedMany` from the installed `ai` (4, 6 or 7; `ai` is loaded on first use, no new dependency), and two providers that rank by cosine similarity: `inMemoryVectorMemory({ embedder, maxItems?, minScore? })` from the root and `sqliteVectorMemory(store, options)` from `@lousho/build-ai-agent/sqlite` (a new `memory_vectors` table, added to an existing database file on open; `memory_items` and `sqliteMemory()` are unchanged). With a query, `list()` returns the scope key's items by score (floor `minScore`, default 0.2; score in `metadata.score`); without one it lists the newest first. Items embedded by another embedder id are skipped when ranking until `provider.reindex(scopeKey?)` re-embeds them. A `MemoryProvider` may set `ranking: 'relevance'`, which makes the `recall_<name>` tool say "by meaning, most relevant first". `hashEmbedder({ dimensions? })` from `@lousho/build-ai-agent/testing` is a deterministic offline embedder for tests. `describeMemoryProviderContract` takes `{ query: 'filter' | 'rank' }`. docs/memory.md has a new `## Semantic recall` section at the end.
|
|
487
|
+
- `teamsChannel({ appId, appPassword, tenantId?, name?, fetch?, approvers?, onError? })`: an agent behind an Azure Bot in Microsoft Teams, mounted like the other channels (`POST <basePath>/teams`). The inbound Bot Framework token is verified with the auth module (`oidc()`: RS256, the key set of the fixed Bot Framework OpenID metadata, issuer `https://api.botframework.com`, audience `appId`, 5-minute tolerance) before the body is parsed, and its `serviceurl` claim must equal the activity's `serviceUrl`; replies go only to that service URL (https). Personal chats always reach the agent, group chats and channels only on an `@mention` (stripped from the text); one session per conversation; Markdown replies split at 25,000 characters; tool approvals are Adaptive Cards with Approve and Deny buttons (`approvers` as on the other channels; the card updates to "Approved by..." and names its conversation, so a copied or replayed reference decides nothing); `ask_question` is answered by the next message, also after a restart with durable stores. No `node:*` import. Telegram and GitHub now set the sender as the run's `principal` too (`authenticator: 'telegram'` with the user id, `'github'` with the login), as Slack and Discord do. See docs/channels.md#microsoft-teams.
|
|
488
|
+
- Cloudflare Worker target builds agent directories (config, instructions, TypeScript tools, skills): `npx lousho build./my-agent --target=cloudflare-worker`. The build reads the directory on Node and generates `agent.module.ts` (static imports of `tools/*` and of an `agent.ts` config; `instructions.md`, a JSON/YAML config and the skills embedded as JSON), and the Worker builds the agent with the rules of `resolveAgentDir()` (the config check and the tool-export rules now live in Node-free modules both use). The config's `model` must name a Worker provider, or `agent.ts` sets a `provider` instance. `subagents/`, `schedules/`, `channels/`, `memory/`, `projectInstructions` and an unsupported provider are rejected with `LOUSHO_DEPLOY_FAILED`, naming what to remove. In a Worker build, an agent file's `@lousho/build-ai-agent` import resolves to a Worker-safe subset (`defineTool`, `defineSkill`, the Worker providers, `fromAiSdk`, errors,...); another name fails the build with the list. The leak check now names the file that imported a Node builtin (`"node:fs" imported by my-agent/tools/files.ts`) instead of failing with esbuild's "Could not resolve". Tools run in the Worker's isolate with its rights: see docs/cloudflare-workers.md#build-and-deploy.
|
|
489
|
+
- Workspace rewind: `createFsTools(fs, { checkpoints })` with a new `WorkspaceCheckpoints` backs up every file `write_file` and `edit_file` change (content and, where the provider has the new optional `FsProvider.getMode` / `chmod`, permission bits), grouped by session and turn. `checkpoints.list({ sessionId })` shows the changed paths per turn; `checkpoints.rewind(toTurn, { sessionId, dryRun, force })` restores every file changed in that turn or later, deletes files the agent created, and skips files changed since by something else (`'changed-since'`), files over `maxFileBytes` (`'too-large'`) and paths that are no longer files. Every path is checked before anything is written, and restores go through the provider, so `NodeWorkspace` keeps them inside its root. Retention: the newest `maxTurns` turns per session (default 20), the old content only for a turn's first write of a path, rewound turns dropped, `clear({ sessionId })`. Stores: `MemoryWorkspaceCheckpointStore` (default) and `FileWorkspaceCheckpointStore(dir)` (one JSON file per session, atomic writes). `NodeWorkspace` gains `getMode()` and `chmod()`. Without `checkpoints` the tools behave as before. See [Workspace tools](docs/workspace-tools.md#the-tools).
|
|
490
|
+
- Route auth and principals: a new subpath, `@lousho/build-ai-agent/auth`, with `jwt()`, `oidc()`, `basic()`, `apiToken()`, `anonymous()` and `routeAuth()`. A route takes an ordered list of entries: the first one that returns a `Principal` accepts the request, `null` skips to the next, `AuthError(401 | 403)` stops; when every entry skips the answer is a generic `401` with one `WWW-Authenticate` header per challenge (`Bearer`, `Basic realm="..."`), and the body never says which check failed. JWTs are verified with Web Crypto only (no new dependency, no `node:` import, so the helpers run on Workers and edge routes): `alg: none`, algorithms outside the configured list, a `crit` header and algorithm confusion (an HS token checked against a public key) are refused; `exp` is required, `nbf` / `iat` honored, `iss` / `aud` checked, with a clock tolerance of 60 s (at most 300); JWKS key sets are cached for 10 minutes and refetched at most once per 30 s, and a key-set URL is never taken from a token. `oidc()` uses the configured issuer's discovery document and requires it to name the same issuer. `basic()` and `apiToken()` compare in constant time. `createRouteHandler({ auth })` and `createDeployedServer(agent, { auth })` take the list (a token string and a boolean function work as before); the node server appends `apiToken(LOUSHO_API_TOKEN)` when that variable is set; an agent directory's `auth.ts` is bundled by `lousho build --target=node-server` / `docker` and guards the built server (`resolveAgentDir()` returns it as `auth`, the manifest has `auth: true`). The accepted principal reaches the run: `send()` / `stream()` / session turns take `principal`, and `RunConfigContext` (the `model` / `instructions` / `tools` functions) and `MemoryScopeContext` (memory scopes) carry it; Slack and Discord channels set the sender as the principal. `Principal` is also exported from the root. Bad helper options throw the new error code `LOUSHO_AUTH_CONFIG_INVALID` when the helper is created. `createRouteHandler()` without `auth` logs one warning when `NODE_ENV` is `production`. Route auth does not check session ownership, the principal does not reach tools or approval policies yet, and the Cloudflare Worker target keeps the `LOUSHO_API_TOKEN` token only. See [Route auth and principals](docs/auth.md).
|
|
491
|
+
- OAuth token storage: `AgentStore` gains a fourth optional part, `tokens` (`OAuthTokenStore`: `get` / `set` / `delete` / `list` per provider and credential owner, single-use pending sign-ins with `putPending` / `takePending`, and `getClient` / `setClient` for dynamically registered clients). Owners are `{ owner: 'app' }` or `{ owner: 'user', principalId, issuer? }`; `tokenStoreKey(provider, owner)` is the stable, percent-encoded record key (`<provider>|app`, `<provider>|user|<issuer>|<principalId>`). `memoryStore()` keeps tokens in process memory; `fileStore(dir)`, `SqliteStore` and `KVStore` encrypt every record with AES-256-GCM (Web Crypto, a fresh 12-byte IV per write, the record key as additional authenticated data) under a 32-byte key the application supplies as `tokenKey` or `LOUSHO_TOKEN_KEY`; there is no default key. Several keys (newest first, or comma-separated in the variable) rotate: writes use the first, reads try each. New error codes `LOUSHO_TOKEN_KEY_MISSING` (first write, or a read of an existing record, without a key) and `LOUSHO_TOKEN_DECRYPT_FAILED` (wrong key or a changed record; names the provider, never the token). `list()` returns metadata only. `generateTokenKey()` makes a key. SQLite: schema migration 4 adds the `oauth_tokens` and `oauth_pending` tables, and `prune()` also deletes expired pending sign-ins (its result gains `oauthPending`). KV: keys under `<prefix>oauth/`, pending sign-ins written with an `expirationTtl`; `KVBinding` gains an optional `list()` (used only by `tokens.list()`), and `KVListOptions` / `KVListResult` are exported from `/kv`. The generated Cloudflare Worker passes its `LOUSHO_TOKEN_KEY` secret to `KVStore`. Nothing signs in yet; the sign-in flow follows. New page [OAuth](docs/oauth.md); new `## OAuth` section at the end of docs/errors.md.
|
|
492
|
+
- `githubChannel({ webhookSecret, botName, token?, app?, botLogin?, name?, apiUrl?, fetch?, triggers?, approvers?, onError? })`: an agent that answers GitHub issue, pull-request and review comments. `@<botName>` in a comment starts a turn and the reply is a new comment in the same thread (a review thread gets a reply under it; text over 60,000 characters is split); one session per issue or pull request and one per review thread, and later comments in a thread with a session are follow-ups. `X-Hub-Signature-256` is verified over the raw body in constant time before the body is parsed (401 otherwise); the webhook is acknowledged at once. Comments by bots, by the channel's own account and every comment the channel posts (a hidden marker) are ignored, and so are edits, deletions and other events. Replies are posted with `token` (a personal access token, or a function) or as a GitHub App (`app: { appId, privateKey }`: a PKCS#1 or PKCS#8 key signs an RS256 JWT with Web Crypto, exchanged for an installation token that is cached per installation until 5 minutes before it expires; `src/channels/githubAppAuth.ts`, not exported). Approvals are comments: the prompt asks for `/approve <id>` or `/deny <id>` (first line only, notes below it), and by default only a commenter whose `author_association` is `OWNER`, `MEMBER` or `COLLABORATOR` may decide, read from the command comment itself; `approvers` (logins, or a function that sees the association in `user.roles`) overrides it, and `triggers` (logins, or a function) restricts who may start a turn or answer a question (default: everyone who can comment, so restrict it on a public repository). A comment is untrusted input to the agent; see the security note in docs/channels.md. An `ask_question` is answered by the next comment in the thread and survives a restart given durable stores. Errors name the call and the HTTP status, never the token. The `LOUSHO_CHANNEL_INVALID` hint, docs/agent-directories.md and docs/errors.md now list `githubChannel()`. New section `## GitHub` at the end of docs/channels.md.
|
|
493
|
+
- Agent Forge keeps the traces of its runs: every run is written with `fileTraceExporter()` to `.lousho/agents/<agent id>/traces` (the files `lousho traces` reads), and the Trace tab gets a list of the agent's past runs (time, duration, model calls, tokens, cost, status) with a "Live" entry while a run is active; choosing one opens its spans in the waterfall, with span kind and error status. New server routes `GET /agents/:id/traces?limit=N` and `GET /agents/:id/traces/:traceId`; the server reads only inside the agent's trace folder. The live `span` WebSocket messages now carry `kind` and `status` (optional fields of `SpanEvent`). See [Agent Forge](docs/agent-forge.md).
|
|
494
|
+
- A background sub-agent that needs approval pauses the lead when the lead awaits it (`agent_await`), and resumes with `approvals.resolve()` / `resume()`. The lead's result is `awaiting-approval` with the child's call (`subagentPath: ['<agent>']`) in `agent.approvals.list()`; the decision runs or rejects the child's call, the child finishes, and the continued lead gets its answer as the `agent_await` result. A child that pauses again, or several awaited tasks that are all paused, pause the lead one approval at a time. The child's usage is added to the lead's, and its conversation is saved under its `taskId`. The paused tasks are kept on the lead's approval record (`SubagentSuspension.background`, plain JSON), never in the tool arguments the model sees. Without `agent_await` nothing changes: the task reports `awaiting-approval` and is not resumable. The `task` and `agent_status` tool descriptions tell the model that a task awaiting approval continues only when it calls `agent_await` on it. Docs: docs/sub-agents.md ("When the lead run ends" and "Continuing a task").
|
|
495
|
+
- Guardrail starter set: `piiGuardrail()` (email, phone, credit card with a Luhn check, IBAN with its mod-97 check, US SSN, IPv4; blocks, or rewrites each match to `[<type>]`), `secretsGuardrail()` (private keys; Anthropic, OpenRouter, OpenAI, AWS, GitHub, Slack, Google and Stripe live keys; JWTs; `Authorization: Bearer` tokens; `extraPatterns`; rewrites to `[secret]` by default), `promptInjectionGuardrail()` (phrase, role-marker and Unicode-tag heuristics, plus an optional model check that must answer `SAFE` or `INJECTION: <reason>`) and `moderationGuardrail({ model })` (one model call that answers `NONE` or the categories that apply: `hate`, `harassment`, `self-harm`, `sexual`, `sexual-minors`, `violence`, `illicit`). The PII, secret and injection checks are heuristic pattern checks: they catch common cases and miss others, and are not a security boundary. Model-backed checks fail closed on a reply they cannot read. A guardrail result can now carry `info?: GuardrailTripInfo` (`pii`, `secret`, `prompt-injection`, `moderation` or `custom`), copied to `result.guardrail`, `guardrail.tripped` and `guardrail.rewrote`; it holds types, labels and offsets, never the matched text. Agent spec files can name them in `policy.guardrails` as `pii`, `secrets`, `prompt-injection` and `moderation`. New exported types `GuardrailTripInfo`, `PiiType`, `ModerationCategory`. Docs: [Guardrails](docs/guardrails.md#input-and-output-guardrails), [Configuration](docs/configuration.md#policy-policy), [Stream events](docs/stream-events.md).
|
|
496
|
+
- `telegramChannel({ botToken, secretToken, botUsername?, name?, fetch?, approvers?, onError? })`: an agent behind a Telegram bot, mounted like the Slack and Discord channels (`POST &channels&telegram`). The webhook secret token header (`X-Telegram-Bot-Api-Secret-Token`) is compared in constant time (401 otherwise); updates are acknowledged with `200` and the turn runs after. A private chat is one session per chat; in a group only `&ask`, `&ask@<botUsername>`, an `@<botUsername>` mention or a reply to the bot wakes it, and a forum topic is its own session. Replies are plain text, split at 4096 characters. Tool approvals are an inline keyboard (Approve & Deny) that only `approvers` (default: the user who started the turn) can tap; the tap names its chat, so it works after a restart. An `ask_question` is sent with `force_reply`; a pending one survives a restart given durable stores, like Slack and Discord. The bot token is never put in an error message or log line. `src&channels&channelSupport.ts` gains the internal `secretsEqual()` (constant-time compare with Web Crypto). The `LOUSHO_CHANNEL_INVALID` hint, docs&agent-directories.md and docs&errors.md now list `discordChannel()` and `telegramChannel()`. New section `## Telegram` at the end of docs&channels.md (attachments are not read yet).&r
|
|
497
|
+
- `todo.updated` stream event and `useTodos()` (React, Vue) / `loushoTodos()` (Svelte): every successful `todo_write` emits `todo.updated { todos, counts, toolCallId }` right after its `tool.done` (sub-agents too, with `subagent`). The shared UI reducer keeps the list in a new `AgentUIState.todos` (carried across turns, cleared by `reset()`), `useLoushoAgent()` / `loushoAgent()` expose it, `useTodos(agent)` / `loushoTodos(agent)` add counts, the current item and progress (`todoView()` computes the same view framework-free), and `toUIMessageStream()` sends a `data-lousho-todos` part for the AI SDK UI. See [Todos](docs/react.md#todos).
|
|
498
|
+
- Cloudflare Worker target: the `openrouter` provider and the `http` tool (with a required `LOUSHO_HTTP_ALLOW` host allowlist). `openrouter` reads its key from the `OPENROUTER_API_KEY` binding and is `@ai-sdk/openai` pointed at OpenRouter, so its bundle passes the `node:` leak check like `openai`. The Worker's `http` is `http_request` with the Node tool's input, over the platform `fetch()`: since a Worker cannot see or pin where a host name resolves, it reaches only the host names (`api.github.com`) and `*.` wildcards (subdomains only) listed in the comma-separated `LOUSHO_HTTP_ALLOW` binding, refuses IP-address hosts even when listed and any scheme but `http:`/`https:`, checks every redirect hop the same way, and refuses every request when the binding is unset or empty. An invalid entry fails every request with an error naming it. `wrangler.toml` gets a commented `[vars]` `LOUSHO_HTTP_ALLOW` line when the spec lists `http`. `ollama` and `web-fetch` stay unsupported on Workers. Internally, the request, redirect and response logic of `http_request` moved to the Node-free `src/tools/built-in/httpCore.ts`, shared by both tools; the Node tool's behaviour is unchanged. See docs/cloudflare-workers.md#providers-and-tools.
|
|
499
|
+
- `fromAiSdk(model)`: any AI SDK `LanguageModel` (Google, Bedrock, Azure, Mistral, Gateway,...) as `createAgent({ provider })`. Options `name` (default: the model's `provider` field), `fileMediaTypes`, `replaysReasoning` and `maxRetries` (default 0); exported with `FromAiSdkOptions` from the package root. Throws `LOUSHO_CONFIG_INVALID` for a model id string, for a model built for another `ai` major than the installed one, and for a call with another model id than the wrapped one. See docs/providers.md#any-ai-sdk-model-fromaisdk.
|
|
500
|
+
- Two ready-made durable stores. `@lousho/build-ai-agent/kv` is a new subpath exporting `KVStore`, `KVCheckpointStore`, `CHECKPOINT_KV_BINDING` and the types `KVStoreOptions`, `KVBinding`, `KVPutOptions`, so a hand-written Cloudflare Worker can use `createAgent({ provider, store: new KVStore(env.AGENT_KV) })`; nothing in its import graph touches `node:*`, so it bundles without shims (`assertSessionId` moved to the Node-free `src/session/sessionId.ts` and is still exported where it was). `fileStore(dir, { historyLimit? })` (root export) is an `AgentStore` of plain JSON files: `sessions/<id>.json`, `checkpoints/<id>.json`, `checkpoint-history/<id>.json` and `approvals/<id>.json`, each written to a temp file and renamed into place, with no lock files and no `StorageService`; resolving an approval claims it with an exclusive create, so of two processes resolving one approval only one gets it. It replaces combining `FileSessionStore`, `LocalStorageCheckpointStore` and `StorageServiceApprovalStore` by hand. See docs/sessions.md#choosing-a-store and docs/deployment.md.
|
|
501
|
+
- `web_fetch` built-in tool: `webFetchTool` / `createWebFetchTool(options)`, and `web-fetch` in spec files. It `GET`s one public web page and returns `{ url, finalUrl, status, contentType, content, truncated }`: HTML converted to text by a small built-in converter (scripts and styles dropped, link URLs kept, entities decoded), JSON and `text/*` as sent, a note for other types; a 4xx/5xx is returned with its body. Loopback, private and link-local destinations are refused on every hop with the connection pinned to the checked address; redirects (10), bytes read (2 MiB), characters returned (50 000) and time (30 s) are capped; `allowedHosts` / `blockedHosts` are checked before DNS and `allowPrivate` exempts named hosts. Node only. See docs/tools.md#built-in-tools.
|
|
502
|
+
- `src/security/privateAddress.ts` (internal): `isPrivateAddress()` and `pinnedLookup()`, shared by the credential broker, `http_request` and `web_fetch`. The credential broker uses it instead of its own list, so it also refuses `192.0.0.0/24`, `198.18.0.0/15`, NAT64 and 6to4 addresses.
|
|
503
|
+
- Slack and Discord: a pending `ask_question` survives a restart. Given durable stores for sessions, checkpoints and approvals, the next message in the Slack thread (or the next `/ask` in the Discord channel) after a restart is still the answer: the turn continues, the reply is posted, and the question, the answer and the reply are appended to the session transcript (before, the answer became a new turn and the question was orphaned). A message in a conversation that waits on a tool approval still does not decide it. New `ChannelContext.pendingQuestion(sessionKey)` for custom channels: the id of the `ask_question` that key's session waits on, also one asked before a restart (it binds the approval to the session, so the continuation is recorded); handed out once until that answer has run. A channel's answer and click continuations now run one at a time with the session's turns. Supporting additions: `Checkpoint.approvalKind`, `PendingTurn.approvalKind` (`session.pending()`) and `SessionAwaitingApprovalError.approvalKind` are `'question'` for a turn paused on an `ask_question`. Slack thread replies that answer a pending question no longer need a saved transcript first (a first turn paused on a question with checkpoints has none yet). See docs/channels.md.
|
|
504
|
+
- Local traces and `lousho traces`: `createAgent({ exporter, captureContent })` traces every run of the agent (`send()`, `stream()`, session turns, `agent.resume()` and runs continued by `agent.approvals.resolve()`; sub-agents join the lead's trace). The new subpath `@lousho/build-ai-agent/traces` exports `fileTraceExporter({ dir })`, which writes each run as JSON Lines to `<dir>/<YYYY-MM-DD>/<traceId>.jsonl` (default `.lousho/traces`; one finished span per line; a write error warns once and never fails the run), and `listTraces()` / `readTrace()` to read them. `npx lousho traces [--dir D] [--limit N] [--json]` lists recent runs with duration, model and tool calls, tokens and cost; `npx lousho traces <id|prefix> [--json] [--content]` prints one run as a span tree with duration bars. Projects made by `lousho init` now ignore `.lousho/`. See docs/observability.md#local-traces.
|
|
505
|
+
- `openApiTools(document, options)`: an OpenAPI 3.0 / 3.1 document (object, JSON or YAML string, or https URL) becomes one typed tool per operation, exported from the root and `./tools`. Names come from `operationId` (or `<method>_<path>`); the input has path, query, header and cookie parameters plus an `application/json` `body`, with `$ref`s resolved; every HTTP response is returned as `{ status, statusText, body }`. GET, HEAD and OPTIONS run and other methods ask for approval by default (`approval`), with read-only / destructive annotations. Options: `baseUrl`, `include`, `exclude`, `prefix`, `headers`, `bearerToken`, `providedArguments` (removed from the model's schema), `timeoutMs`, `maxResponseChars`, `fetch`. The base URL must be https (http only for loopback), credentials never reach events or the transcript, and redirects are followed only on the base origin. Swagger 2.0, multipart bodies and remote `$ref`s are refused. `jsonSchemaToZod(schema, root?)` takes an optional root for `$ref` resolution. New page docs/openapi-tools.md.
|
|
506
|
+
- Built-in OpenAI, Anthropic and OpenRouter providers send PDF file parts on `ai` 6 and 7 (Anthropic also `text/plain`); `ai` 4 and Ollama keep the text note. A provider subclass opts more media types in with `fileMediaTypes()` (`acceptsFileParts = true` still sends every type). The text-note warning is now once per provider and media type and names the type and the `ai` major. Added `npm run test:live` (`vitest.live.config.ts`) for `*.live.test.ts` files, which the default suite excludes. See docs/providers.md#multimodal-input.
|
|
507
|
+
- Principals in tools, approval policies and permissions: the run's principal reaches a tool's `execute(args, ctx)` as `ctx.principal`, `needsApproval` functions (`ApprovalCheckContext.principal`), permission rules' `when(args, ctx)` (`PermissionContext.principal`), `preToolCall` / `postToolCall` hooks (`ToolCallHookContext.principal`), `onPermissionDecision`'s new second argument (`PermissionAuditContext`), pending approvals (`PendingApproval.principal`, seen by `approve` and by a channel's `approvers` function as `request.principal`) and in-process sub-agents (`task`, background tasks, `createDelegateTool()`; `remoteAgent()` does not forward it). New `ExecuteOptions.principal`, set by `createAgent()` for `send()`, `stream()`, session turns and channel turns. It is saved in checkpoints (`Checkpoint.principal`) and approval snapshots (`ExecutionSnapshot.principal`), so a static or dynamic run resumed after an approval or a crash acts for the same caller. `agent.approvals.resolve()` / `answer()` / `streamResolve()` / `streamAnswer()` take `{ principal }` (new `ResolveApprovalOptions`) for who decides, and the approved tool sees it as `ctx.approval.by`; `resumeAfterApproval()` takes it as `executeOptions.approver`. The approvals route (`POST /chat/:sessionId/approvals/:id`) passes the caller route auth accepted, and `mountChannels()` the clicking user (`{ id, type: 'user', authenticator: <channel name> }`) or, for a question answered by a message, its sender. `agent.fork()` keeps the source run's principal; `session.fork()` copies only the conversation. The principal is not added to events, spans or trace files. See [Principals in tools and approvals](docs/auth.md#principals-in-tools-and-approvals).
|
|
508
|
+
|
|
509
|
+
### Deprecated
|
|
510
|
+
- `SlackTriggerAdapter`, `CronTriggerAdapter` and `WebhookTriggerAdapter` (`@lousho/build-ai-agent/triggers`) are deprecated and will be removed in a future major version. Use `slackChannel()` for Slack (sessions per thread, approval buttons), `defineSchedule()` with `startSchedules()` for cron (or `schedules/` in an agent directory, or cron triggers in a spec file), and `webhookChannel()` for webhooks, each mounted with `mountChannels()`. `verifySlackSignature()`, the `WebhookAuth` helpers, `parseCronExpression()`, `TriggerAdapter` and `TriggerRegistry` are not deprecated. Their options types (`SlackTriggerAdapterOptions`, `CronTriggerAdapterOptions` with `CronIntervalOptions` and `CronExpressionOptions`, `WebhookTriggerAdapterOptions`) and `WebhookTriggerHandle` carry `@deprecated` too. Nothing changes at run time. See docs/triggers.md.
|
|
511
|
+
|
|
512
|
+
### Fixed
|
|
513
|
+
- `lousho` CLI surface: `lousho dev`/`lousho chat --traces` now writes traces for a `.ts`/`.js` module that exports an already-built `createAgent()` agent - the module's export could not take the flag's exporter after the fact, so nothing was written. `createAgent()` remembers the config it was built with (readable via the internal `createAgentConfigOf()`), and the loader rebuilds the agent with the flag's exporter (the module's own `exporter` is replaced while the flag is set; a module exporting an agent not made by `createAgent()` prints a warning and keeps its own). A spec path that does not exist (e.g. `lousho chat missing.yaml`) now fails with the coded `LOUSHO_SPEC_NOT_FOUND` instead of a raw ENOENT. `lousho add --dry-run` no longer fails when a target file already exists; it prints the file list with an "(exists - a real install needs --overwrite)" marker and exits 0 (the symlink checks still run). Top-level `lousho --help` lists the implemented flags again: `--no-schedules`/`--traces` on `dev` and `chat`, `eval`'s record/replay/drift/`--config` flags, `init`'s `--sdk-path`, and `build`'s positional form.
|
|
514
|
+
- A cassette failure keeps its type through `agent.send()`: a replay mismatch (or a missing cassette raised by the provider-interception seam that `lousho eval` installs) was compacted into a generic `CompactedLLMProviderError`, losing the cassette path, the call number and the re-record hint. `CassetteMismatchError` is now an `SDKError` with code `LOUSHO_CASSETTE_INVALID` (docs/errors.md already claimed cassette errors carry it), and the run loop rethrows cassette errors untouched instead of compacting them, so `send()` rejects with the `CassetteMismatchError` itself (`.cassette`, `.callNumber`). `setProviderInterceptor()` and the `ProviderInterceptor` type are now exported from `@lousho/build-ai-agent/testing`, the public entry for the model-boundary seam docs/evals.md describes.
|
|
515
|
+
- A tool call approved with `agent.approvals.resolve()` (or `resumeAfterApproval()`) now has its `execute_tool` span. It ran before the continued run started, outside any span, so OpenTelemetry and `fileTraceExporter()` showed the continued run's `invoke_agent` and `chat` spans but not the approved tool. The continued run's `invoke_agent` span now opens first and the approved call runs inside it: its `execute_tool` span is a child of it (a `run_code` call's inner calls and a sub-agent the tool starts are children of the tool span), with the same `captureContent` / `redactContent` rules as any tool span, and no span is made for a rejected call. A pause that happens again (sign-in) leaves the `invoke_agent` span with the tool span and no `chat`.
|
|
516
|
+
- A tool that returns `undefined` (or an async generator that yields nothing) no longer writes a `tool` message with `content: undefined` into the transcript. Its result is now `null` everywhere: the transcript and the next model request carry `"null"`, and `tool.done`, approval resumes, `run_code` and flows see `null`. Providers, session stores and checkpoints get a valid string.
|
|
517
|
+
- `recordReplay` matches a tool's parameter schema the same way under zod 3 and zod 4. A zod 4 schema was fingerprinted from its JSON Schema (with `$schema`, `required`, `additionalProperties`) while a zod 3 one, and so every recorded cassette, used a fingerprint of the schema itself, so cassettes recorded with zod 3 failed to replay with zod 4 installed (the CI `peers` jobs `ai4-zod4`, `ai6-zod4` and `ai7-zod4` failed on every replay test). Both now give the same fingerprint for the same schema; existing cassettes stay valid and need no re-recording, and a real change to a tool's parameters still fails the match.
|
|
518
|
+
- Parallel `task` calls now get their `taskId`s, and background tasks start, in call order: allocating an id awaited a SHA-256 digest whose completion order is not fixed, so with `maxConcurrent: 1` the second task could take the slot first (#296, the flaky "queues tasks beyond maxConcurrent" test).
|
|
519
|
+
- `SubprocessSandbox.run()` no longer fails with HTTP 404 "no such container" for a command that exits at once: the container is `AutoRemove`d, and `wait()` was only called after `start()`, so a fast command could be gone by then (10 of 40 parallel `true` runs failed on Docker Desktop). It now begins `wait({ condition: 'next-exit' })` before `start()`, and removes the container if `start()` itself fails.
|
|
520
|
+
- The `cloudflare-worker` build's Node-builtin leak check now accepts the runtime-guarded `loadNodeModule("node:module" | "node:dns")` probes that `@ai-sdk/provider-utils` 4 (installed with `ai` 6) uses, as it already did for `ai` 7's `loadBuiltinModule`; without it a Worker bundle built on `ai` 6 failed with "Node builtins leaked" (found by the new `ai6` matrix entries).
|
|
521
|
+
- `isAgentEvent()` is now exhaustive (the runtime type list is a `Record<AgentEventType, true>`, so the compiler rejects a missing type). It had no `agent.drift`, so remote UI clients (`parseEventStream()`, the React, Vue and Svelte bindings) silently dropped that event.
|
|
522
|
+
- A remote sub-agent's token usage is added to the lead's `result.usage`. It was dropped, so budgets and cost reports undercounted delegation to a `remoteAgent()`. The lead adds what the remote agent reports on its `run.done` event to its totals, to `usage.delegated` and to `byModel['remote:<name>']`, which keeps the remote's own `costUsd` (the lead's `costUsd` is `undefined` when the remote sends none). A remote run that pauses for an approval adds what it spent up to the pause, and the continuation adds only what it spent after it; background remote tasks roll up the same way. A deployment that sends no usage adds nothing. `RemoteRunOptions` has a new optional `onUsage(usage)` callback; `RemoteSubagent.run()` still returns `Promise<string>`, so hand-written ones compile unchanged. See docs/sub-agents.md#remote-sub-agents.
|
|
523
|
+
- `lousho studio` finds the Agent Forge build that ships inside the installed package when it is run from a project that does not have `apps/agent-forge` (it used to fail with "could not find apps/agent-forge" everywhere except the SDK repo). A project's own `apps/agent-forge` still wins, and `.lousho/` is still created in the directory you ran it from. `--dev` from an installed package still needs the TypeScript source and says so.
|
|
524
|
+
- Documentation corrections: install commands, CLI command lists, optional peers, durable execution and compaction descriptions, the Status section, reasoning and file-part notes in the providers guide, and the `KVStore` note in the deployment guide now match the code. Ticket ids are gone from user-facing prose.
|
|
525
|
+
|
|
526
|
+
### Tests
|
|
527
|
+
- Two more load-sensitive tests fixed. `SandboxShell.test.ts` "passes only set values and allowed host names, never planted secrets" starts two real `node -e` processes and timed out at the 5 s default in 1 of 20 parallel runs; it has a 30 s timeout now (pure slowness, no race: it passes in under a second alone). `backgroundSubagents.test.ts` "reports a sub-agent paused for approval as awaiting-approval" waited a fixed 10 ms for the child's pause and failed in 9 of 20 parallel runs with the task still `running`; the lead now asks `agent_status` again until the task has left `running`.
|
|
528
|
+
- `http.test.ts` "rejects near the configured timeout" no longer compares real elapsed time to a real timeout (flaky under load, #324): it drives the request timer with a fake clock and checks the request is still pending at 499 ms, rejects with "timed out after 500ms" at 500 ms, and that the connection is closed.
|
|
529
|
+
- `src/tools/workspace/checkpoints.node.test.ts` "rewinds three turns" has a 30 s timeout: it takes well under a second alone, but timed out at the 5 s default when eight copies of the suite ran at once. The rewind itself is correct; every failure was the timeout.
|
|
530
|
+
- Live-model cassettes recorded: real gpt-4o-mini runs through OpenRouter are now replayed in CI with no key, for the coding agent in the docs, the Telegram, GitHub and Teams channels, plan mode, `web_fetch`, streaming `send()`, the guardrail starter set, `fromAiSdk()` and PDF/PNG file parts, plus a recorded `lousho traces` fixture. The live files that never ran (`sendStreams`, `guardrailStarterSet`, `fileTraceExporter`, `remoteUsage`) were missing the `import '../providers'` that registers OpenRouter; fixed. New live tests (skipped without a key): ai 6 through OpenRouter and `aiSdkEmbedder()` over OpenRouter embeddings.
|
|
531
|
+
- `pack-smoke` allows a 16 MiB unpacked tarball (was 14 MiB): `main` was 45 KB under the old cap and permission modes went over it; see #316 for shrinking the package instead.
|
|
532
|
+
- Sandbox egress and the credential broker are tested against a real Docker Engine: a new CI workflow, "Docker Engine" (`.github/workflows/docker.yml`, on pull requests touching the sandbox, broker or docker deploy adapter, on demand and weekly), runs `npm run test:docker` on GitHub's `ubuntu-latest` runner (rootful Engine 28.0.4). `src/security/sandboxEgress.docker.test.ts` checks nine cases of `SubprocessSandbox({ network: { allow }, broker })` with `curlimages/curl`: an allowed HTTPS host answers through the broker, another host gets a 403, bypassing the proxy and raw IPs have no route, outside names do not resolve, the broker injects a credential the container never sees, a container outside the internal network cannot use the gateway listener, an aborted run and `close()` leave no container or network, and a reused non-internal network is refused. The docker deploy image test (`/health` and `/chat`) moved to `src/deploy/adapters/docker.docker.test.ts` and runs in the same job; it was skipped in CI before and passes there. No product defect was found. `*.docker.test.ts` files are excluded from `npm test` and `npm run test:coverage`; they skip without a daemon unless `LOUSHO_DOCKER_TESTS=1` is set (the workflow sets it), which makes a missing daemon fail. docs/workspace-tools.md says what the job checks; CONTRIBUTING.md mentions `npm run test:docker`.
|
|
533
|
+
- `src/execution/principal.test.ts` and `principal.test-d.ts`: the principal in tools (send, stream, session and channel turns), policies, rules, hooks and sub-agents (local, background, remote), pause and resume with another approver, a restart, a crash resume on an on-disk `SqliteStore`, old checkpoints and snapshots, forks, the approvals route and channel clicks; frozen in strict mode; absent from events and spans.
|
|
534
|
+
- Test files can be type-checked: `npm run typecheck:tests` runs `tsc` over `src/` including every `*.test.ts` through the new `tsconfig.tests.json`, and the test files under `src/execution`, `src/context`, `src/session`, `src/subagents`, `src/acp` and `src/testing` now pass it. The check is not in CI yet. One type change: `textOf()` also accepts a message or content whose parts are `readonly`, such as a `mockModel` request snapshot.
|
|
535
|
+
|
|
536
|
+
### Docs
|
|
537
|
+
- The `vendor/` model prefix note: every docs page whose snippets hardcode a `provider/model` string now says the prefix chooses the provider and that with OpenRouter the id is `openrouter/<vendor>/<model>` (e.g. `openrouter/openai/gpt-4o-mini`), so `createAgent()` does not throw `LOUSHO_PROVIDER_MISSING_API_KEY` when only `OPENROUTER_API_KEY` is set. Stale model ids refreshed: `anthropic/claude-3-5-haiku-latest` examples are now `anthropic/claude-haiku-4-5`, `'anthropic/claude-3-5-sonnet-latest'` in errors.md is `'anthropic/claude-sonnet-4-5'`, `google('gemini-2.0-flash')` is `google('gemini-2.5-flash')` and the OpenRouter fallback example uses `openrouter/meta-llama/llama-3.3-70b-instruct`. (`anthropic/claude-3-5-sonnet-latest` stays where it documents the `ANTHROPIC_API_KEY` env default, which is still that id in `src/providers/providerSpec.ts`.)
|
|
538
|
+
- Corrections in [Errors](docs/errors.md), [Troubleshooting](docs/troubleshooting.md) and [Testing](docs/testing.md): not every intentional error is an `SDKError` (the named `Error` subclasses — `PropagatingToolError`, `CassetteMismatchError`, `CronExpressionError`, `DecryptionError` — carry no `code`); the `Find a code by area` table now lists the `Auth` section and every code that has one; `LOUSHO_CASSETTE_INVALID` no longer claims to cover request mismatches (that is `CassetteMismatchError`), and its example uses `webhookChannel()` rather than a `webhookTrigger()` that does not exist; `createAgent()`'s default retry is documented as applying only to model strings and env-resolved models, not a `provider` instance; the file-parts entry now matches providers.md (PDFs and `text/plain` do reach providers on `ai` 6/7); `connectMcp().status()` lists `'needs-auth'`; the `mockModel` `usage` default is "none reported" (the run estimates it) rather than zero; and the request-assertion snippet notes where `temperature` comes from, since `createAgent()` has no `temperature` option.
|
|
539
|
+
- [Stream events](docs/stream-events.md) gains the missing `agent.drift` row (emitted on a resume under `onAgentDrift: 'warn'`, before the continuation's first step) and the fields the table understated: `executedBy: 'provider'` on `tool.start` / `tool.done` / `tool.error` (hosted tools), `replacedByHook` on `tool.done`, `hook` on `permission.decision`, `trigger: 'manual'` on `compaction.start` / `compaction.done`, and the full `usage` shape (`inputTokens` / `outputTokens`, `estimated`, `costUsd`, and `modelCalls` / `hostedToolCalls` on `run.done`). The ordering guarantees now say that a streamed approval continuation reports `agent.drift` and the decided call's `tool.start` / `tool.done` before its first `step.start`, so those events fall outside a step. [Runs](docs/runs.md) uses the canonical event names `tool.start` / `tool.done` / `run.done` instead of the deprecated `tool-call` / `tool-result` / `finish`, and [Testing](docs/testing.md) no longer claims a `mockModel` turn without `usage` reports zeros (it reports none).
|
|
540
|
+
- Long-tail corrections across the guides: `lousho eval --config`, `lousho init`'s `InitUsageError` and `-y`, `.cts` among the dev-server module extensions and streamed approval continuations in docs/cli.md; `lousho build --target=...` (not `lousho deploy`) and the public `createRouteHandler().handler` (not the internal `serveFetch()`) in docs/sub-agents.md; `description` as a `createAgent()` option and `createDeployedServer()` as build-internal in docs/agent-directories.md, docs/channels.md and docs/schedules.md; approval continuations on Workers emit `tool.start` / `tool.done`, `deploy-runtime-worker` is bundler-internal and `handleScheduled` needs a store in docs/cloudflare-workers.md; `schedule-<name>` sessions, `checkpoint-history/` and the late-appearing `approvals/` directory in docs/schedules.md and docs/sessions.md; `saveAttachment`/`readAttachment` signatures in docs/utilities.md; 256-bit OAuth state in docs/oauth.md; the `recall_<name>` description's `<name>` in docs/memory.md; `ctx.sessionId` in docs/tools.md; the `throw` flow node in docs/flows.md; the `triggers` spec field in docs/configuration.md; `principal` in a permission rule's `when` in docs/approvals.md; the `'not-a-file'` rewind skip and `NodeWorkspace`'s required `root` in docs/workspace-tools.md; a built-in channel's `name` setting its route in docs/channels.md; the `completed()` 'max-steps' message in docs/evals.md; `formatUsage`'s `$0.0000` in docs/models-and-cost.md; the completed registry static-check list and the leading letter/digit name rule in docs/registry.md; `exporter`, `captureContent` and `redactContent` as `createAgent()` options in docs/migrating-to-create-agent.md; `connectMcp().tools` as a `Record` that combines with array tools in docs/mcp.md; `HookRegistry.size()` as a method in docs/hooks.md; `*.example.com` matching subdomains only in docs/tools.md, docs/workspace-tools.md and docs/registry.md; `resumeAfterApproval()` named as an export, not an executor method, and the fork `divergedAt` comment corrected in docs/durable-execution.md.
|
|
541
|
+
- New page, [Permission modes](docs/permission-modes.md): the modes and their table, which tools are read-only, the `editsFiles` marker, the order of evaluation, switching, the audit log, sub-agents and resume. [Approvals](docs/approvals.md) "Permission policies" links to it; [Tools](docs/tools.md), [Stream events](docs/stream-events.md) and [Sub-agents](docs/sub-agents.md) ("What a sub-agent inherits") gained a row each.
|
|
542
|
+
- New page, [Route auth and principals](docs/auth.md): the ordered list and its three outcomes, each helper, 401 / 403 / `WWW-Authenticate`, which helpers run where (Node, Workers, edge), `createRouteHandler`, the node server and `auth.ts`, reading the principal in the run, and security notes (no session ownership check; `basic()` only over HTTPS). docs/errors.md gains a `## Auth` section at the end (`LOUSHO_AUTH_CONFIG_INVALID`). One-paragraph links from deployment.md (`### Auth`), nextjs.md (`## Routes`), memory.md (`## Scopes`), agent-directories.md (layout and deploy), api-overview.md and a "Route auth" row in the cloudflare-workers.md limits table; no heading of an existing page changed.
|
|
543
|
+
- New page, [Cloudflare Workers](docs/cloudflare-workers.md): a table of what the Worker target supports and what it does not comes first, then build and deploy, bindings, sessions, checkpoints and approvals in KV, scheduled runs, consistency, and bundle size and Node builtins. The Worker sections moved out of docs/deployment.md unchanged (`Bindings, sessions and the API on Workers`, `Cron triggers and handleScheduled` and `Durable execution (pause/resume) on Workers` are gone from it); deployment.md keeps a short `cloudflare-worker` summary that links to the page. Links to `deployment.md#cron-triggers-and-handlescheduled` moved to `cloudflare-workers.md#scheduled-runs`.
|
|
544
|
+
- Two dead heading links fixed: docs/executor-api.md now links to `tools.md#advanced-toolregistry`, and docs/troubleshooting.md links to `mcp.md#use-mcp-servers-in-an-agent` (the configuration anchor moved with the MCP section). No other link in `docs/` or the README points to a missing heading. docs/approvals.md lists `fileStore(dir)` among the approval stores.
|
|
545
|
+
- Long pages split; text moved unchanged. New pages: [Runs](docs/runs.md), [Models and cost](docs/models-and-cost.md), [Stream events](docs/stream-events.md), [Queued input and steering](docs/queue-and-steer.md). Moved anchors (old to new): `api-overview.md#finish-reasons`, `#cancellation` and `#parallel-tool-calls` to `runs.md` (same anchors); `api-overview.md#models-tokens-and-cost` to `models-and-cost.md#models-and-the-price-table`; `api-overview.md#usage-and-cost-of-a-run` to `models-and-cost.md#usage-and-cost-of-a-run`; `api-overview.md#flow-expressions` to `flows.md#flow-expressions`; `api-overview.md#todo-tools` to `tools.md#todo-tools`; `api-overview.md#tool-errors` to `tools.md#errors`; `api-overview.md#context-compaction` to `compaction.md`; `streaming.md#event-schema-version-1`, `#ordering-guarantees`, `#typescript` and `#versioning` to `stream-events.md` (same anchors); `streaming.md#queued-input` and `#steering` to `queue-and-steer.md` (same anchors); the `AgentExecutor.execute()` options table of `configuration.md` to `executor-api.md#options-of-agentexecutorexecute`. The error codes page gained a "Find a code by area" table; its headings are unchanged.
|
|
546
|
+
- The API overview, providers, structured output, tools, tracing, guardrails and testing guides teach `createAgent()` first: usage and multimodal examples use it, the testing tool example uses `defineTool()`, and every executor, `AgentBuilder` or `ToolRegistry` snippet sits under an "Advanced" heading that links to [the executor API](docs/executor-api.md). They say that `createAgent()` does not take `exporter`, `captureContent` or `sandbox` yet.
|
|
547
|
+
- New page, [Triggers](docs/triggers.md): the `@lousho/build-ai-agent/triggers` adapters, a table that says when to use triggers, channels or schedules, the `TriggerAdapter` interface (with a custom adapter and `TriggerRegistry`), and the webhook, Slack and cron sections that moved from the API overview, unchanged. The API overview now points to it; links to `api-overview.md#triggers`, `#webhook-authentication` and `#slack-request-signatures` moved to `triggers.md`. The missing-`auth` warning of `WebhookTriggerAdapter` and the missing-`signingSecret` warning of `SlackTriggerAdapter` now link to `docs/triggers.md`.
|
|
548
|
+
- New page docs/migrating-to-create-agent.md: from `AgentBuilder`, `AgentExecutor`, `ToolRegistry` and `resumeAfterApproval()` to `createAgent()`, with a before-and-after example, a mapping table, the `ExecuteOptions` that `createAgent()` does not take yet (with alternatives), the behavior differences and a step-by-step checklist. Linked from docs/executor-api.md and the README docs table.
|
|
549
|
+
- New page, [Hooks](docs/hooks.md): the four hook points, the outcomes of a `preToolCall` / `postToolCall` hook, four type-checked recipes (deny a tool, add a default argument, redact a result, inject context), ordering, and `HookRegistry`. The `HookRegistry` bullet and the `### Hook outcomes` section moved there from the API overview, which now points to it; links to `api-overview.md#hook-outcomes` moved to `hooks.md#hook-outcomes`.
|
|
550
|
+
- Approvals, sessions, streaming, sub-agents, compaction and skills guides show `createAgent()` first. Executor snippets (`AgentExecutor`, `resumeAfterApproval`, `streamResumeAfterApproval`, `InputQueue`, `HookRegistry`) moved under headings named "Advanced: the executor API" (links to docs/executor-api.md); `approvals` and `streaming` renamed a section to that name. `createAgent({ hooks: [createCompactionHook(...)] })` is the compaction example. Removed a statement from the sub-agents guide that `createAgent()` takes no `approvalStore` (it does, and `agent.approvals` resolves sub-agent pauses).
|
|
551
|
+
- Stale statements removed: ticket ids in the Agent Forge page, the "removed next minor" promise on `AgentType` in the API overview, and the installation note that read as if every user needs Node 22.19 for the `http` tool (it comes from the `undici@8` dependency, which is loaded only for `validateSSL: false` requests). The `ProposedAction` doc comment no longer refers to an unlanded ticket.
|
|
552
|
+
- New page docs/mcp.md, "MCP (Model Context Protocol)": using MCP servers in an agent, `connectMcp()`, approval for MCP tools, `loadMcpTools()`, serving an agent with `serveMcp()` and `lousho mcp`, and a Limits list. These sections moved out of docs/configuration.md, which keeps the `mcpServers` spec field and links to the new page; links from approvals, CLI, tools, API overview and the README now point to `mcp.md`.
|
|
553
|
+
- `docs/durable-execution.md` and `docs/workspace-tools.md` lead with `createAgent()` (`send()` with a `sessionId`, `agent.resume()`, `agent.approvals.resolve()`, `agent.fork()`) instead of `AgentExecutor`. The executor snippets (`execute()` with a `checkpointStore`, `resumeAfterApproval()`, `AgentExecutor.fork()`, the `ToolRegistry` setup) moved under "Advanced: the executor API". In durable-execution the heading "What happens when you call `execute()` again with the same `sessionId`" is now "What happens when you send again with the same `sessionId`".
|
|
554
|
+
- New page docs/troubleshooting.md: start from the symptom (setup, runs that end without an answer, tools, providers, sandbox and MCP), with cause, fix and a link for each. README docs table, `docs/installation.md` and `docs/errors.md` link to it.
|
|
555
|
+
- New guide, [Build a coding agent](docs/build-a-coding-agent.md): a terminal coding agent in six steps (workspace tools confined to the project, approval before writes and risky commands, a streamed UI that handles the approval pause, a session for follow-up questions, offline tests with `MemoryWorkspace` and `mockModel`), with an offline test of the same agent (`docs/buildACodingAgent.test.ts`; its replay of a recorded real-model run is skipped until the cassette `docs/__cassettes__/build-a-coding-agent.json` is recorded).
|
|
556
|
+
- The Quick Start is rewritten around `createAgent()`: hello world, a tool, streaming, a session, an approval, an offline test with `mockModel`, a custom provider and spec files, each runnable with no API key (the first excepted). The `AgentBuilder` + `AgentExecutor` section moved to a new page, docs/executor-api.md (with the `ToolRegistry` note); the Quick Start no longer shows either API.
|
|
557
|
+
- `create-lousho-agent` has a README (its npm page was empty) and `license`, `homepage` and `repository` fields; they appear on npm with its next release. `lousho init --help` no longer says `--sdk-path` is needed until the package is on npm.
|
|
558
|
+
- docs/auth.md gains a section at its end, `## Principals in tools and approvals` (with `### In a tool`, `### Approval policies and permission rules`, `### Who approved`, `### Pauses, crashes and forks`, `### Sub-agents`, `### What is stored and logged`); its "Reading the principal in the run" and "Security notes" text is updated. One-sentence links, no new headings: docs/approvals.md (`needsApproval` context, `approve`), docs/tools.md (`ctx.principal` and `ctx.approval` rows), docs/permission-modes.md (the audit log) and docs/channels.md (`approvers`).
|
|
559
|
+
- UI and server examples corrected: docs/ai-sdk-ui.md's client snippet used `useChat({ api: '/api/chat' })`, the `ai` 4 signature, which is invalid for the documented `ai` 6/7 combos — it now uses `useChat({ transport: new DefaultChatTransport({ api }) })` and a note says which `ai` majors are supported (`ai` 4 keeps `useChat({ api })` and returns `input` / `handleInputChange` / `handleSubmit`; `ai` 5 is not in the peer range). docs/nextjs.md's `useChat` snippet has the same version note. The route handlers of ai-sdk-ui.md and nextjs.md opened `agent.session({ id })` per request with no `store`, so a second turn forgot the first: both now pass `store: memoryStore()` and say to use a durable store on serverless; docs/react.md's server section says the same for a per-request session. The `data-lousho-approval` part no longer reads as if `kind` / `question` only mark an `ask_question`: a tool waiting on an OAuth sign-in carries `kind: 'sign-in'` and `signIn: { provider, displayName?, url }`; docs/react.md's `pendingApproval` row says the same.
|
|
560
|
+
|
|
561
|
+
## [1.0.0-alpha.8] - 2026-10-02
|
|
562
|
+
|
|
563
|
+
### Changed
|
|
564
|
+
- No `any` left in the SDK's types (LOU-D16): `npm run lint` now fails on any ESLint warning, and `no-explicit-any`, `no-unused-vars` and `ban-ts-comment` are errors. Runtime behavior is unchanged; some exported types are stricter, which can be a compile error where code read an `any` value without checking it. What changed, and how to migrate:
|
|
565
|
+
- Values the SDK cannot know are `unknown` (narrow them, or cast to the shape you expect): `FlowExecutionResult.output` and `.variables`, `FlowExecutionContext.variables` / `session` / `memory`, `GenerateResult.rawResponse`, `StreamChunk.toolResult.result`, extra keys of `LLMProviderConfig`, `AgentConfig.expectedResult` / `events` / `metadata`, `AgentExecutionResult.result`, `ResultData.result`, `SessionData.data`, `JiraTicket.customFields`, the extra keys of `formatZodError()`'s result, `ToolConfiguration.options`, `ToolSetting.options`, `FlowToolSetting.options`, `ToolNode.toolOptions`, `UIComponentNode.componentProps`, `FlowChunkEvent.issues` / `input` / `toolResults[].args` / `componentProps`, and `ToolDefinition.function.parameters`.
|
|
566
|
+
- `FlowExecutionEvent` is a union discriminated on `type`, with `data` typed per event type (new `FlowExecutionEventOf<T>` and `FlowExecutionEventDataMap`): check `event.type` before reading `event.data` (`if (event.type === 'tool-call') event.data?.tool`).
|
|
567
|
+
- `AgentConfig.settings` (and `AgentBuilder.setSettings()`) is the new `AgentSettings` (`{ model?: string; [key: string]: unknown }`).
|
|
568
|
+
- `StorageService.readPlainJSONAttachment<T>()` (and `IStorageService`'s) defaults `T` to `unknown`: pass the type you stored, `readPlainJSONAttachment<MyRecord>(key)`.
|
|
569
|
+
- `OpenRouterProvider.getModelInfo()` returns the new `OpenRouterModel` (`{ id: string; [key: string]: unknown }`), `null` or `undefined`.
|
|
570
|
+
- `FlowBuilder.addAgent()` / `setAgents()` take `FlowAgentDefinition`; `injectVariables()` and `applyInputTransformation()` take a `FlowDefinitionNode` (`injectVariables()` returns the type it was given); `validateFlowInput()` takes `Record<string, unknown>`; `validateAgentTools()` takes `Record<string, { tool?: unknown }>`; `createDynamicZodSchemaForInputs()` returns `z.ZodObject<Record<string, z.ZodTypeAny>>`.
|
|
571
|
+
- Still accepted as before: `needsApproval` predicates typed for a tool's own arguments, any argument to `formatAxiosError()` and `hasRequiredKeys()`, and anything as `writePlainJSONAttachment()` data. The deprecated `ExecutionEvent` keeps `toolResult.result: any`.
|
|
572
|
+
- Eval cassettes hook in at the model boundary (LOU-D46.2): `lousho eval --record / --replay / --drift` no longer reassigns `AgentExecutor.execute` at runtime. The run loop routes every model call through a small provider-interception seam (`setProviderInterceptor()` / `interceptProvider()` in `src/providers/interception.ts`; a no-op unless an interceptor is installed), and the eval runner installs one that answers with the per-case `recordReplay()` wrapper. Cassette files, keying, `--drift` output and JUnit reports are unchanged, so existing cassettes keep replaying. Streamed runs and sub-agents inside an eval case are covered by tests.
|
|
573
|
+
- More errors carry SDK error codes (LOU-D2.2): the plain `Error`s thrown by `src/execution` (`historyLimit`, `toolConcurrency`, a second iteration of an `AgentRun`), the tool registry, the built-in tools, `serveMcp()`, `NodeWorkspace` and the `lousho init` / `studio` / chat helpers are now `SDKError`s with an existing code (`LOUSHO_CONFIG_INVALID` via `ConfigurationError`, `LOUSHO_RUN_ALREADY_ITERATED`, `LOUSHO_APPROVAL_NOT_FOUND`, `LOUSHO_PROVIDER_UNKNOWN`, `LOUSHO_TOOL_EXECUTION_FAILED`). Messages are unchanged; `error.code`, `error.hint` and a `[code] hint (docs)` line (not on the built-in tools' run-time failures, whose message is the model-visible tool result) are added. Code that matched `error.constructor === Error` or `error.name === 'Error'` needs `error instanceof SDKError`.
|
|
574
|
+
- The remaining user-reachable plain `Error`s carry SDK error codes (LOU-D2.3): agent directories, flows, memory, skills, storage (SQLite), triggers, deploy, evals, record/replay cassettes, `defineTool()`, sub-agent options, channels, sandbox/credential-broker options and the `llm` registry now throw an `SDKError` (message text unchanged, plus the `[code] hint (docs)` line, except for tool results and messages tests assert, which stay as written). New codes: `LOUSHO_TOOL_ARGS_INVALID` (what `ToolArgumentsValidationError` carries now), `LOUSHO_AGENT_DIR_INVALID`, `LOUSHO_SKILL_INVALID`, `LOUSHO_FLOW_INVALID`, `LOUSHO_STORAGE_FAILED`, `LOUSHO_TRIGGER_INVALID`, `LOUSHO_DEPLOY_FAILED`, `LOUSHO_EVALS_INVALID`, `LOUSHO_TEST_FAILED`, `LOUSHO_CASSETTE_INVALID` and `LOUSHO_CHANNEL_REQUEST_FAILED`, all in `docs/errors.md`. Code that matched these errors by `error.constructor === Error` or `error.name === 'Error'` must check `instanceof SDKError` / `error.code` instead. `ToolExecutionError` takes an optional fourth `code` argument. A new test (`src/utils/plainErrors.test.ts`) fails when new SDK code throws a plain `Error`; the 5 that remain are listed there with the reason.
|
|
575
|
+
- The last two allowlisted plain `Error`s are `SDKError`s (LOU-D16): OpenRouter's model-catalog fetch (`LOUSHO_PROVIDER_REQUEST_FAILED`; `getModels()` / `getModelInfo()` still catch it and fall back) and the UI runner's failed `POST` to a remote agent (`LOUSHO_REMOTE_REQUEST_FAILED`, as used by `useLoushoAgent()` and the Vue/Svelte bindings). Both keep their message text exactly (no help line appended), so the UI's error event shows the same message, now with `name: 'SDKError'` instead of `'Error'`. `SDKError` itself moved to `src/utils/sdkError.ts` (re-exported from the same places as before) so the browser entries can throw it without importing `ai`. The allowlist in `src/utils/plainErrors.test.ts` is down to the 3 intended sites.
|
|
576
|
+
- zod 3 or zod 4 (LOU-D29): the `zod` peer is now `^3.25.76 || ^4.0.0`. Schemas of either major (including `zod/v4` on zod 3.25 and `zod/v3` on zod 4) work in `defineTool({ input })`, structured `output` and argument validation: zod 4 schemas are sent to the model as `z.toJSONSchema` output (also to `ai` v4, whose own converter only reads zod 3), zod 3 schemas through the `ai` SDK's converter as before, and both are parsed with their own `safeParse`. `defineTool` also takes any Standard Schema that exposes `~standard.jsonSchema`; tool arguments validate with any Standard Schema's `~standard.validate`. The SDK's own schemas (AgentSpec, registry documents, built-in tools, MCP tool conversion) run on whichever major is installed, and issue messages read the same on both (zod 4's `Invalid input: expected string, received undefined` is reported as `Required`). `lousho init` keeps zod 3 for the `ai` 4 scaffold (Ollama), whose packages peer on zod 3. Ollama on `ai` 6/7 (`ollama-ai-provider-v2`, which peers on zod 4) is no longer blocked by the SDK's zod peer. CI runs the type check, build and suite on zod 4 too. Migration: `DefineToolOptions`, `DefinedTool` and `ToolDescriptor.inputSchema` are typed with the new exported `StandardSchemaV1` (parsed type: `InferSchemaOutput`) instead of zod 3's `ZodTypeAny`; code that called zod methods on `descriptor.inputSchema` needs a cast. The `task` tool's error for an unknown sub-agent now reads `Unknown sub-agent "x". Expected one of: 'a', 'b'`. `createAgent({ output })` is still typed with the installed major's `ZodType`.
|
|
577
|
+
- Slack and Discord approvals are starter-only by default (LOU-P5.2, behaviour change): without `approvers`, only the user who started the turn (the Slack message author, the Discord command's user) can approve or deny a tool call; before, anyone who could see the message could. Migration: pass `approvers: () => true` for the old behaviour, or a list of user ids / a function for a team. Approval button values (Slack) and custom ids (Discord) now carry the starter (`<starter>:<approval id>`), so buttons posted before the upgrade carry no starter and only an `approvers` list or function can approve them.
|
|
578
|
+
- Package entries share one copy of each module (LOU-D42): tsup code splitting is on, so `@lousho/build-ai-agent`, `/hooks`, `/tools`, `/mcp` and the rest import shared chunks instead of each bundling their own copy. `HookRegistry`, `SDKError` and module-level registries are now the same object across entries (they were duplicated, so `instanceof` and singletons could disagree), and `dist/` shrinks from about 25 MB to 8.8 MB. `instanceof SDKError` and `instanceof HookRegistry` also hold between the ESM and CJS copies a mixed-format process loads (a `Symbol.for` brand). See [Installation](docs/installation.md#entry-points-share-code-esm-and-cjs).
|
|
579
|
+
- A crash resume of a dynamic agent restores the run's config (LOU-V15.2): checkpoints now carry `runConfig` (the `ctx` and model the run was resolved with, as approval snapshots already did), and `agent.resume(id)` / `session.resume()` / `send(…, { sessionId })` on an unfinished run use the saved model and re-resolve `tools` and `instructions` with the saved `ctx`, instead of `input: []` and no `metadata`; a model chosen from `metadata` no longer changes mid-run. Static agents are unchanged.
|
|
580
|
+
- Resumed approved calls must run with the approved arguments (LOU-X3.2, behaviour change): after the pre-tool hooks ran on the resumed call, the arguments are compared (deep, key order ignored) with the approved ones whether a hook returned `{ input }` or changed `ctx.args` in place, and a difference is refused with the existing `kind: 'validation'` error ("approved with different input"); a hook that rebuilds the same arguments in another key order is no longer refused. **Migration:** a hook that rewrites arguments (a redactor, a normalizer) must also run on the original run, so the approval record already holds the rewritten input; a hook added only after the pause, that changes the arguments, now refuses the call. A call whose tool no longer exists after an approval (snapshots with an agent fingerprint) now rejects with `LOUSHO_RESUME_TOOL_MISSING` instead of giving the model a `not-found` tool result; older snapshots keep the old behaviour.
|
|
581
|
+
- Cancelled and settled tool calls use the shared tool-error shape (LOU-U14.2): a tool call cancelled before it ran (the run was aborted or steered) and a sub-agent call dropped because the lead run was already pausing for another approval now reach the model as `{ error: 'ToolNotRunError', toolName, message, kind: 'not-run' }` with the transcript message flagged `isError`. Before, the result was `{ error: '<text>' }` (and the cancelled one was not flagged `isError`); the text is now under `message`.
|
|
582
|
+
- A `permissions` `allow` (or `ask`) rule no longer overrides a tool's own `needsApproval` deny (LOU-X8 follow-up, behaviour change): the tool's `needsApproval` is now called for those calls too, and its `'deny'` / `{ deny }` denies the call; an `allow` still skips the tool's ask. A `deny` rule still wins without calling `needsApproval`. Migration: a tool that should run under an `allow` rule must not deny it from `needsApproval`. See docs/approvals.md.
|
|
583
|
+
- `agent.session()` passes the agent's `createAgent({ compaction })` to the session (LOU-W8 follow-up), so `session.compact()` uses the agent's strategy and sizes unless `agent.session({ compaction })` sets its own.
|
|
584
|
+
- The `cloudflare-worker` target builds with `ai` v7 installed (LOU-D28c). With `ai` v7 (`@ai-sdk/openai`/`@ai-sdk/anthropic` v4), `lousho build --target=cloudflare-worker` failed with "Node builtins leaked" (`node:module`, `node:dns`, `node:diagnostics_channel`, `node:async_hooks`). Those are not imports: `ai` v7 and `@ai-sdk/provider-utils` v5 probe them at run time through `process.getBuiltinModule()`, only when running on Node, so the bundle never loads them on Workers. The build's leak check now exempts exactly those four ids, and only as the argument of a `getBuiltinModule`/`loadBuiltinModule` call; any other `node:` reference still fails the build. No `nodejs_compat` flag is needed. The 16 Worker bundle tests that LOU-D28b skipped on `ai` v7 now run on both majors. The bundle is about 3.4 MB raw on v7 (1.7 MB on v4), well under the 64 MiB limit.
|
|
585
|
+
- BREAKING: sandboxed and workspace commands get an allowlisted environment (LOU-X11). Commands run by `NodeWorkspace.exec()` and `SandboxShell` no longer see the host's `process.env`, so `env`/`printenv` cannot reveal API keys or cloud credentials. They get a small base a shell needs (`PATH`, `HOME`/`USERPROFILE`, `TMP`/`TEMP`/`TMPDIR`, `LANG`, `LC_*`, `TERM`, plus `SystemRoot`, `SystemDrive`, `ComSpec`, `PATHEXT`, `WINDIR` on Windows) and what you add. A container (`SubprocessSandbox`) gets only what you add, never the host env. `SandboxShell` gains `env` and `inheritEnv` options; `NodeWorkspaceOptions.inheritEnv` and `SandboxShellOptions.inheritEnv` accept `true`; `SandboxRunOptions` gains `inheritEnv` (`NoopSandbox` keeps passing the host env unless it is `false`, which `SandboxShell` sets). `SubprocessSandbox` gains `network: 'none' | 'default' | { allow: string[] }` (default `'none'`, as before); `{ allow }` is validated and kept on `sandbox.network` but runs with no network until the egress proxy of LOU-X12 enforces it (fail closed). **Migration:** pass what commands need, either values (`env: { NODE_ENV: 'test' }`) or host names to copy (`inheritEnv: ['CI', 'NODE_OPTIONS']`); to restore the old behavior, pass `inheritEnv: true`. See docs/workspace-tools.md.
|
|
586
|
+
- Internal (LOU-D28b): the test suite runs with `ai` v7 installed as `ai`. A test helper (`src/providers/aiMajor.testkit.ts`: `installedAiMajor`, `describeOnAiV4`, `itOnAiV4`) reads the installed major; tests whose behavior is the same on both majors are made major-agnostic, the 66 that only make sense on v4 (v4 message and call shapes, `MockLanguageModelV1`) or on a Worker bundle that still leaks `node:` built-ins on v7 (LOU-D28c) are skipped there. The `typecheck-ai7` CI job now also builds and runs `npx vitest run`. Test and CI change only; no product change.
|
|
587
|
+
- Internal (LOU-D28a): the SDK source now type-checks against `ai` v7 (with `@ai-sdk/openai`/`@ai-sdk/anthropic` v4) as well as `ai` v4. `AiSdkProvider` builds its `ai` v4 request with SDK-owned structural types instead of the v4-only `CoreMessage`/`CoreAssistantMessage`/`CoreToolMessage`/`Output` types and no longer calls `ai`'s `tool()` (an identity function on v4); the legacy `.tool` that `defineTool()`/`toolDescriptorFromSchema()` hand-build is cast once at that boundary. No runtime change on v4. A new CI job runs `tsc --noEmit` with `ai@7` installed. The `ai`/`@ai-sdk/*` peer ranges are unchanged for now (LOU-D28d).
|
|
588
|
+
- MCP tools without `readOnlyHint` now ask for approval by default (LOU-Z5, BREAKING for MCP users): `loadMcpTools()`, `connectMcp()` and `createAgent({ mcpServers })` set `needsApproval` from each tool's MCP annotations: `readOnlyHint: true` runs, `destructiveHint` true or absent (the MCP spec default) asks, `destructiveHint: false` runs. Choose per server with the new `approval: 'annotations' | 'always' | 'never' | ({ name, annotations }) => boolean` (`loadMcpTools(client, name, { approval })`, `connectMcp()` server entries, `createAgent({ mcpServers })` and `AgentSpec.mcpServers`, which validates it; a spec file takes the three strings). The descriptor keeps the raw annotations on the new `ToolDescriptor.metadata?.mcp.annotations` (types `McpToolAnnotations`, `ToolMetadata`, `McpApproval`) and uses the annotation `title` as `displayName`. Migration: pass `approval: 'never'` to restore the old behaviour (every MCP tool runs without asking). `createAgent()` agents pause with `finishReason: 'awaiting-approval'`, while a bare `AgentExecutor` without an `approvalStore` now throws for such tools. See docs/configuration.md#mcp-tool-approval-approval.
|
|
589
|
+
- Small built-in tools are defined with `defineTool` (LOU-D24, not breaking): `currentDateTool`, `dayNameTool`, `createEmailTool()`, `createHttpTool()`, `createSlackTool()`, `createDelegateTool()` and the descriptors `loadMcpTools()` builds no longer call `ai`'s `tool()`. They now carry canonical `inputSchema` / `execute` (the legacy `.tool` stays), and the built-ins have the tool names `current_date`, `day_name`, `send_email`, `http_request`, `slack_alert` and `delegate_to_<agent>`. Descriptions, schemas, approval and sandbox behaviour and the factory signatures are unchanged. MCP descriptors are built with the new internal `toolDescriptorFromSchema()`, which (unlike `defineTool`) accepts any server-provided name or empty description. `routeFetchThroughSandbox()` reads the canonical `execute` first.
|
|
590
|
+
- GitHub and Jira tools are defined with `defineTool` (LOU-D25, not breaking): the 28 `createGitHubTools()` tools and the 20 `createJiraTools()` tools no longer call `ai`'s `tool()`, so nothing under `src/tools` imports `ai` any more. They now carry canonical `inputSchema` / `execute` (the legacy `.tool` stays). Tool names, descriptions, schemas, the sandbox flags and the factory signatures are unchanged. The GitHub out-of-scope tools (branch/file writes, code search, issue CRUD, merge) are disabled through the canonical `execute` as well as `tool.execute`, so calling one still throws "out of scope" before any HTTP request. `JiraTools.register()` now also routes tools registered with `defineTool` through the sandbox seam.
|
|
591
|
+
- BREAKING (types only): `Message.content` is widened from `string` to `string | ContentPart[]` (LOU-V11). Messages you build keep working unchanged, but code that reads `message.content` as a string (`.length`, `.startsWith()`, a template literal) no longer type-checks. **Migration:** read `textOf(message)` instead, or narrow with `typeof message.content === 'string'`. A custom `LLMProvider` that forwards `content` should handle the parts or send `textOf(message)`.
|
|
592
|
+
- The execute context is our own type (LOU-D23, not breaking): the new exported `ToolExecutionContext` (`{ toolCallId, messages: readonly Message[], abortSignal?, sessionId?, onDelegatedUsage? }`, `Message` from `@lousho/build-ai-agent`) replaces the `ai` SDK's `ToolExecutionOptions` as the type of the second argument of `defineTool()`'s `execute`, `ToolDescriptor.execute`, `DefinedTool.execute` and the third argument of `sandboxExecute`, and is what `buildToolRunContext()` returns; the public types no longer import `ToolExecutionOptions` from `ai`. A function typed for `ai`'s options that only reads `toolCallId` or `abortSignal` stays assignable. `ToolRunContext` is now a deprecated alias of the optional fields of `ToolExecutionContext` (it already existed with `onDelegatedUsage` and `toolCallId`). `AgentExecutionOptions.messages` is typed `Message[]` instead of the `ai` SDK's `CoreMessage[]` (the field is unused by the SDK). The legacy `ToolDescriptor.tool` (`ai` v4 `Tool`) is unchanged. See docs/tools.md#execute-context.
|
|
593
|
+
- BREAKING (small): `SDKError.code` values are now the `LOUSHO_*` codes (LOU-D2): `AGENT_EXECUTION_ERROR` -> `LOUSHO_AGENT_EXECUTION_FAILED`, `TOOL_EXECUTION_ERROR` -> `LOUSHO_TOOL_EXECUTION_FAILED`, `LLM_PROVIDER_ERROR` -> `LOUSHO_PROVIDER_REQUEST_FAILED`, `SESSION_AWAITING_APPROVAL` -> `LOUSHO_SESSION_AWAITING_APPROVAL`, `FLOW_EXECUTION_ERROR` -> `LOUSHO_FLOW_EXECUTION_FAILED`, `CONFIGURATION_ERROR` -> `LOUSHO_CONFIG_INVALID`, `VALIDATION_ERROR` -> `LOUSHO_VALIDATION_FAILED`, `TIMEOUT_ERROR` -> `LOUSHO_OPERATION_TIMEOUT`, `RATE_LIMIT_ERROR` -> `LOUSHO_PROVIDER_RATE_LIMITED`; `code` is no longer optional (`new SDKError(message)` gets `LOUSHO_GENERIC_ERROR`). No class was renamed. The messages of `SDKError`s other than `ToolExecutionError`, `LLMProviderError`, `TimeoutError` and `RateLimitError` gain a second line with the code, hint and docs link, and `loadSpec()` now rejects a spec whose top-level field is a likely typo of a spec field (`LOUSHO_SPEC_UNKNOWN_FIELD`; other unknown fields are still ignored). **Migration:** compare `code` against the new values (or use `instanceof`); match messages with `toContain` / a regex, or compare `error.detail`, instead of exact equality.
|
|
594
|
+
- BREAKING (small): tool failures that did not go through the thrown-error path now use the shared shape (LOU-U14), so `error` is an error name instead of a message. Before: an unknown tool or a missing registry gave `{ error: "Tool 'x' not found" }` / `{ error: 'No tool registry available' }`; a rejected approval gave `{ error: 'Tool execution was rejected by the reviewer', note }`; a tool that threw after an approval gave `{ error: '<message>' }`; a call left without a result on resume gave `{ error: 'Tool call was not run: ...' }`. Now each is `{ error: '<ToolNotFoundError | ToolRejectedError | <thrown error name> | ToolNotRunError>', toolName, message: '<the old text>', kind, ... }`. A tool approved but missing from the registry on resume used to reject `resumeAfterApproval()` with `Tool 'x' not found in registry`; it now resolves with a `not-found` error result. The sandbox guard throws `SandboxRequiredError` (same message, still an `Error`). **Migration:** read `message` instead of `error` when you want the text, and branch on `kind`. `ToolArgumentsValidationError.toToolResult()` now also returns `kind: 'validation'`.
|
|
595
|
+
- Background sub-agents still running when the lead run ends are now cancelled (LOU-Y4.2) instead of running to completion on their own; a lead run that pauses for approval cancels them too (the resumed run starts with none). **Migration:** to keep them, have the lead collect them with `agent_await` before its final answer, or set `subagentOptions: { awaitBackgroundOnFinish: true }`.
|
|
596
|
+
- Heavy dependencies are optional peers (LOU-D40): `dockerode` (Docker sandboxing, `SubprocessSandbox`), `@modelcontextprotocol/sdk` (`serveMcp`, `lousho mcp`, MCP clients) and `prompts` (the interactive questions of `lousho init`) moved from `dependencies` to `peerDependencies`, marked optional in `peerDependenciesMeta`. They were already loaded lazily; now a project that uses none of them does not install them. Using one without its package fails that call with `MissingPeerDependencyError`, which now also carries `feature` and names the exact command (for example `npm install dockerode@^5.0.1`). `lousho doctor` lists the three with what they enable (a missing `dockerode` fails the check when the agent spec uses a sandboxed tool), and its version lookup no longer returns `undefined` for packages whose `exports` map hides `package.json`. **Migration:** install the peer if you use Docker sandboxing (`npm install dockerode`), MCP (`npm install @modelcontextprotocol/sdk`), or interactive `lousho init` (`npm install prompts`; `npm create lousho-agent` already does, and `lousho init --yes` needs nothing). See docs/installation.md.
|
|
597
|
+
- The `http` tool no longer loads `undici` for default requests (LOU-D40): with TLS verification on (the default) it uses the runtime's global `fetch`; `undici` (still a regular dependency) is loaded only for `validateSSL: false`, whose per-request TLS dispatcher the global `fetch` cannot express. `yaml` stays a dependency (agent specs and skills need a YAML parser).
|
|
598
|
+
- Retries no longer stack (LOU-V7.2): the built-in providers (`OpenAIProvider`, `AnthropicProvider`, `OllamaProvider`, `OpenRouterProvider`) now pass `maxRetries` to the `ai` SDK calls: `config.maxRetries`, or 2 (the `ai` SDK's default, so behaviour is unchanged) when unset. `createAgent()` builds the providers it resolves from strings with `maxRetries: 0` and retries in its `withRetry()` wrapper instead, so retries happen in one place (at most 3 calls by default, as before) and each one is observable. If you wrap a built-in provider in `withRetry()` / `resilientProvider()` yourself, build it with `maxRetries: 0`; note that a `maxRetries` you already set in a provider config is now honoured by the `ai` SDK.
|
|
599
|
+
- `AgentType` is optional and deprecated (LOU-D34, not breaking): `AgentBuilder.build()` no longer requires `setType()` and `AgentConfig.agentType` is now optional (`createAgent()` and `specToAgent()` agents carry no type). `AgentType`, `setType()` and the `agent-types` registry/validators are marked `@deprecated`: they have no runtime effect and will be removed in the next minor; they stay exported for now. Drop your `setType(...)` calls.
|
|
600
|
+
- BREAKING: `encrypt()` now generates a random salt per call instead of a hardcoded one. Ciphertext produced before this change cannot be decrypted with the new code and must be re-encrypted.
|
|
601
|
+
- BREAKING: The mock repositories (`MockAgentRepository`, `MockSessionRepository`, `MockResultRepository`, `MockMemoryRepository`, `MockAttachmentRepository` — previously re-exported from `src/data/mocks.ts` via the package root/`./data` subpath; this package has no `createMockRepositories` factory) are no longer exported from the package root. Import them from `@lousho/build-ai-agent/testing` instead.
|
|
602
|
+
- One session-API client behind `remoteAgent()` and `remoteTarget()` (LOU-D53): both now share an internal client (`src/server/sessionClient.ts`) for `POST <url>/chat`, the SSE read, bearer-token scrubbing and the error mapping, and use one pair of codes, `LOUSHO_REMOTE_REQUEST_FAILED` and `LOUSHO_REMOTE_UNAUTHORIZED`. The unreleased `LOUSHO_REMOTE_AGENT_FAILED` (LOU-Y7) is removed: a `remoteAgent()` failure now carries `LOUSHO_REMOTE_REQUEST_FAILED` (network, non-2xx, truncated stream, or a remote run that ends in an error, with "the remote run ended in an error" in the message) or `LOUSHO_REMOTE_UNAUTHORIZED` (401). No option or behavior of either function changed.
|
|
603
|
+
|
|
604
|
+
### Added
|
|
605
|
+
- Remote sub-agent approvals go through the lead run (LOU-Y7.3): a `remoteAgent()` task whose remote run pauses for a tool approval now pauses the lead run the way a local sub-agent does (it used to fail with `LOUSHO_SESSION_AWAITING_APPROVAL`): the lead's pending approval (`agent.approvals.list()`, channel buttons, the dev chat, ACP permission requests) has the remote tool's name and input and `subagentPath: [<remote agent>]`, and a remote `ask_question` arrives as a question. Deciding it on the lead (`resolve`, `streamResolve`, `answer`) posts the decision to the remote `POST <url>/chat/:sessionId/approvals/:id` and the continuation's final answer is the `task` result; a further pause pauses the lead again. The lead's approval snapshot stores the remote session id, the remote approval id, the `taskId` and the agent name (never the token), so a fresh lead process on the same store can decide it. Failures while deciding (401, other non-2xx including the remote's 404 for an approval no longer pending, network) are the `task` call's structured tool error with `LOUSHO_REMOTE_UNAUTHORIZED` / `LOUSHO_REMOTE_REQUEST_FAILED`. A remote agent with an `output` schema now returns its object as JSON with the footer (the V4.2 shape), read from `run.done`'s `object`. `RemoteSubagent.run()` takes `pausable` and `decision` (type `RemoteRunOptions`); the internal session client gains `resolveRemoteApproval()` and `SessionTurnSummary.object`. Only a lead run without an approval store keeps the old `LOUSHO_SESSION_AWAITING_APPROVAL` error. See docs/sub-agents.md#remote-approvals.
|
|
606
|
+
- Structured output for sessions and sub-agents, typed for either zod major (LOU-V4.2): `createAgent({ output })` and `ExecuteOptions.output` accept a zod 3 schema, a zod 4 schema (`zod/v4` on zod 3.25, or zod 4) or a Standard Schema that can produce JSON Schema, and `result.object` is inferred from each without a cast (`InferSchemaOutput`, as `defineTool`). `AgentSession` is generic (`AgentSession<TObject = unknown>`): `agent.session().send()` and `.stream()` results carry the typed `object`. A sub-agent with its own `output` returns its validated object as JSON (then the `taskId` footer) as the `task` and `agent_await` result; its `output-invalid` finish is a structured tool error. Sub-agents do not inherit the lead's `output`; `remoteAgent()` returns text only. The exported spec schemas (`agentSpecSchema`, ...) are typed as `SpecSchema<T>` / `SpecObjectSchema<T>`, so the published `schema-*.d.ts` no longer depends on zod 3 generics (`skipLibCheck: false` projects on zod 4). The Ollama missing-peer note now says `ollama-ai-provider-v2` needs zod 4 (`npm install zod@^4.0.0`). See docs/structured-output.md.
|
|
607
|
+
- Publish readiness (LOU-D49): `npm run pack-smoke` (scripts/pack-smoke.ts, a CI job) packs the SDK and `create-lousho-agent`, checks the tarball (no `.env`, tests or secret-looking strings; entry count and size caps), runs `npm publish --dry-run` for both (nothing is published), installs the tarballs plus peers from the registry into a fresh project and verifies ESM and CJS loads of every `exports` entry, a mock-model agent turn, the `lousho` bin (`--help`, `doctor`) and `tsc` with `moduleResolution` bundler and node16.
|
|
608
|
+
- One event system (LOU-D41): `AgentEvent` listeners for callers who do not iterate a run. `createAgent({ onEvent: (event: AgentEvent) => void })` and the new `ExecuteOptions.onAgentEvent` (also taken by `AgentExecutor.stream()` and `resumeAfterApproval()`) are called synchronously with every `AgentEvent` of the run, on `send()` / `execute()` as on `stream()`, sub-agents' events included: the same events in the same order as the stream yields (a non-streamed run generates each model step whole, so its text is one `text.delta` per step). A run with a listener also reports hook events, permission decisions, budgets and guardrails to it when it is not streamed. A non-streamed `resumeAfterApproval()` with a listener now reports the decided call (`run.start`, `tool.start`, `tool.done`) like a streamed one. See docs/streaming.md#listening-without-iterating.
|
|
609
|
+
- Reasoning (LOU-V13): new `reasoning` option on `createAgent()`, `send()` / `stream()` (per call, overriding the agent's), `ExecuteOptions` and `GenerateOptions`: `'none' | 'minimal' | 'low' | 'medium' | 'high'` or `{ effort?, budgetTokens?, summary?: 'auto' | 'none', force? }` (types `ReasoningOption`, `ReasoningEffort`, `ReasoningSettings`). The built-in providers send it through `providerOptions` on `ai` 4.3 and 6/7: OpenAI `reasoningEffort` (+ `reasoningSummary`), Anthropic `thinking: { type: 'enabled', budgetTokens }` (effort table 1024 / 2048 / 8192 / 24576 tokens), Ollama `think` (`ollama-ai-provider-v2` only; ignored with one warning on `ai` 4), and OpenRouter's unified `reasoning` body field (`effort` or `max_tokens`). It is only sent to model families known to accept it (`force: true` overrides); `'none'` sends nothing. New events `reasoning.start`, `reasoning.delta` (`text`) and `reasoning.done` (`text`, `tokens?`) in the `AgentEvent` union, emitted inside the step before its first text or tool call, from `ai` v4 `reasoning` parts and v6/v7 `reasoning-start/delta/end`; `ExecutionResult.reasoning` holds the run's reasoning text and `GenerateResult.reasoning` a call's blocks (new type `ReasoningBlock`); new stream chunk types `reasoning-delta` / `reasoning-end` (`StreamChunk.reasoning`). Reasoning is not written to the transcript as text; an assistant tool-call turn keeps Anthropic's signed or redacted thinking blocks in the new `Message.reasoning`, which the Anthropic provider sends back unmodified (it survives checkpoints and resumes). The UI reducer keeps `UIMessage.reasoning`, `toUIMessageStream()` emits `reasoning-start` / `reasoning-delta` / `reasoning-end`, `lousho acp` sends `agent_thought_chunk`, `lousho chat` prints reasoning dimmed, and `chat` spans carry `lousho.usage.reasoning_tokens`. Cassettes record the reasoning chunks. See docs/reasoning.md.
|
|
610
|
+
- Tools know their session (LOU-D23.2): `ToolExecutionContext.sessionId` is now set whenever the run has one: `AgentExecutor.execute({ sessionId })`, `send({ sessionId })`, every `agent.session()` turn (the session id; a checkpointed turn's own `<id>.turn-<n>`, as hooks see it), approval resumes (the paused run's), and sub-agents (`<parent session>/<task tool call id>`). A session turn's run now carries the session id even without a checkpoint store (no checkpoint is written), so hooks and the `invoke_agent` span's conversation id see it too.
|
|
611
|
+
- Hardened Slack and Discord channels (LOU-P5.2): `approvers` (a list of platform user ids, or `(user: { id, name?, roles? }, { toolName, input, sessionId }) => boolean | Promise<boolean>`) says who may click Approve / Deny (a refused click gets an ephemeral "not allowed" and the approval stays pending); `onError(error, { channel, stage, sessionId? })` on both channels, on `mountChannels()` and on `defineChannel()` receives failures after the acknowledgment (reply delivery, a failed turn, an approval continuation; default `console.error` with channel, stage, session and SDK error code, never a token) and a failed turn tells the user so; `mountChannels({ onDecision })` reports who decided; `parse` gets a third `ctx` argument (`ChannelContext`: pending approval, session id, `hasSession`) and a `{ decision }` may carry `inbound` and `approver`. Pending approvals now resolve from the click itself and the Slack 'active thread' check asks the session store, so both survive a restart. Slack: the clicked message is replaced with the outcome (buttons removed) and direct messages (`message.im`, scope `im:history`) are sessions keyed on the DM channel. See docs/channels.md.
|
|
612
|
+
- Resumable sub-agent tasks (LOU-Y6): every `task` result ends with a `taskId` (the footer is now `[sub-agent '<name>': N step(s), finish reason '<reason>', taskId 'task_1']`; a remote one gains `, taskId '...'` too), and `task` takes optional `taskId` and `mode: 'new' | 'resume' | 'fork'` (default `new` without a `taskId`, `resume` with one). `resume` continues that sub-agent with its transcript and the new prompt as the next user turn; `fork` starts a new task (new `taskId`) from a copy of it and leaves the original alone. Conversations are kept per lead session in the agent's `store.sessions` (new `SubagentOptions.sessions`, set by `createAgent({ store })`) for lead runs with a `sessionId` (checkpointed `send()` and session turns), under a key hashed from the lead session and the taskId, so they survive a restart and cannot be reached from another session; other runs keep them in memory for the run. Background tasks share the ids (a background task's id is now `task_<n>` counted with the synchronous ones): a finished one can be resumed, a running one is refused with the new `LOUSHO_SUBAGENT_TASK_BUSY`; an unknown, foreign or other sub-agent's id gets the new `LOUSHO_SUBAGENT_TASK_NOT_FOUND`, both as the structured tool error. An approval inside a resumed child pauses and resumes the lead as before (streamed or not), and a resumed child's usage counts toward the lead like a fresh call. `remoteAgent()` tasks resumed by `taskId` continue in the same remote session (LOU-Y7.2; `RemoteSubagent.run()` takes `sessionId` and `taskId`); remote approvals are still not proxied, but a task whose remote run paused can be continued once the approval was decided on the remote agent. The model-facing `task` description and the sub-agents prompt block say how to continue a task. See docs/sub-agents.md#continuing-a-task.
|
|
613
|
+
- Agent-directory and spec builds keep optional peers external, and the node server runs spec cron triggers (LOU-P8.3): `lousho build --target=node-server|docker` leaves every optional peer of the SDK (read from its `peerDependenciesMeta`) out of the bundle, replacing the hand-kept `dockerode` / `ollama-ai-provider-v2` list, so a build no longer needs peers the agent does not use; a spec's `{ type: 'cron' }` triggers now start on the built server (and are validated at build time), as on the Cloudflare Worker target. See [Deployment](docs/deployment.md#optional-peers-in-node-and-docker-builds).
|
|
614
|
+
- Agent fingerprint on resume (LOU-W9.2): every checkpoint and approval snapshot now carries `agentFingerprint` (new type `AgentFingerprint`: the model id, each tool's name with a hash of its JSON input schema, a hash of the instructions, and a SHA-256 prefix over them, via Web Crypto; functions are ignored), and a resume compares it with the resuming agent's before any model call or tool runs. New option `onAgentDrift: 'warn' | 'error' | 'ignore'` (type `AgentDriftMode`) on `createAgent()`, `AgentExecutor.execute()` and `resumeAfterApproval()`: `'warn'` (default) emits the new `agent.drift` event (`AgentDriftEvent`: `model?`, `toolsAdded`, `toolsRemoved`, `toolsChanged`, `instructions`) and a `console.warn`, `'error'` rejects with the new `LOUSHO_AGENT_DRIFT` and leaves the checkpoint (or the pending approval) untouched. A pending tool call whose tool no longer exists always rejects with the new `LOUSHO_RESUME_TOOL_MISSING`. Works for crash resume, `session.resume()` and approval resume, streamed or not; old checkpoints and snapshots have no fingerprint and resume unchecked. A sub-agent's nested approval snapshot carries its own fingerprint, but only the top-level agent is compared. `resumeAfterApproval()`'s options take `currentAgent` (set by `createAgent()`) so instructions can be compared. See docs/durable-execution.md#resuming-with-a-changed-agent.
|
|
615
|
+
- Docker sandbox network allowlists are enforced through the credential broker (LOU-X12.2): `new SubprocessSandbox({ network: { allow }, broker })` creates (or reuses, by `networkName`) an internal Docker network (`Internal: true`, label `com.lousho.sandbox=egress`), has the broker listen on its gateway address for that subnet only, and starts each container on it with `HTTP_PROXY`/`HTTPS_PROXY` pointing there, so the broker (allowlist: its rule hosts plus `allow`) is the container's only route out. New `sandbox.close()` stops the listener and removes the network it created. New `broker.listen({ host, clients, allow? })` adds a listener on another address that drops peers outside the `clients` subnet; the broker still listens on loopback only by default. Enforced on Docker Engine 25.0.5+ for Linux with the agent on the same host; Docker Desktop, rootless Docker, a remote daemon, a non-internal reused network and older Engines are refused with the new code `LOUSHO_SANDBOX_EGRESS_UNSUPPORTED` (no container is started). Without a `broker`, `{ allow }` still means no network. See docs/workspace-tools.md.
|
|
616
|
+
- The Docker sandbox honors cancellation (LOU-U23): `SubprocessSandbox.run()` takes `signal` (`SandboxRunOptions`). When it aborts, the container is killed and force-removed (errors from an already stopped or removed container are ignored) and `run()` rejects with an `AbortError`, like `NoopSandbox`; an already-aborted signal starts no container; a timeout uses the same cleanup. `SandboxShell` passes the tool's abort signal down, so a cancelled `shell` call stops its container (it used to run until the timeout) and reports `aborted: true`. See docs/workspace-tools.md.
|
|
617
|
+
- Schedules on Cloudflare Workers (LOU-P9): the `cloudflare-worker` target writes the spec's `{ type: 'cron', cron, input, name?, timezone? }` triggers (deduplicated) to `wrangler.toml` as `[triggers] crons = [...]`, and the generated Worker exports `scheduled(controller, env, ctx)`, which runs every trigger whose expression equals `controller.cron` as an agent turn inside `ctx.waitUntil()`, in session `schedule:<name>` (visible in the KV store when bound). A failing trigger is logged with `console.error` (name and error code) and never stops the others. The build fails with `LOUSHO_SCHEDULE_INVALID` naming the trigger for what Cloudflare would not fire: a `timezone` (crons are UTC), a seconds field or `@daily` shortcut, a numeric day-of-week (Cloudflare counts 1 as Sunday). New `handleScheduled(agent, schedules, controller, ctx)` (root and `deploy-runtime-worker`) wires `defineSchedule` schedules to a hand-written Worker's `scheduled()`. `startSchedules` shares its fire step with it. See docs/schedules.md#on-cloudflare-workers.
|
|
618
|
+
- `ai` v6 and v7 as peers (LOU-D28d): `peerDependencies` now accept `ai` `^4.3.19 || ^6.0.0 || ^7.0.0` and `@ai-sdk/openai` / `@ai-sdk/anthropic` `^0.0.42 || ^1.0.0 || ^3.0.0 || ^4.0.0` (still optional), plus the new optional peer `ollama-ai-provider-v2` `^2.0.0 || ^3.0.0 || ^4.0.0`, so a project on the current `ai` major installs without peer conflicts. Pairings: `ai` 4 with `@ai-sdk/*` 0.0.x/1.x and `ollama-ai-provider` 1.x; `ai` 6 with `@ai-sdk/*` 3.x and `ollama-ai-provider-v2` 2.x/3.x; `ai` 7 with `@ai-sdk/*` 4.x and `ollama-ai-provider-v2` 4.x. A missing provider package's `MissingPeerDependencyError.installCommand` names the version for the installed `ai` major (`npm install @ai-sdk/openai@^4.0.0` on `ai` 7; unchanged on `ai` 4). `OllamaProvider` loads `ollama-ai-provider-v2` on `ai` 6/7; that package needs zod 4, which the SDK does not support yet, so its install hint says so and Ollama stays on `ai` 4 for now. `lousho doctor` checks provider packages against the installed `ai` major and flags a mismatched pair (e.g. `ai` 7 with `@ai-sdk/openai` 1.x) with the version to install; a missing or unsupported `ai` gets `npm install ai@^7.0.0`. `lousho init` / `npm create lousho-agent` scaffold `ai@^7.0.0` with `@ai-sdk/openai@^4.0.0` / `@ai-sdk/anthropic@^4.0.0` for OpenAI and Anthropic, and keep `ai@^4.3.19` for Ollama (zod 4) and OpenRouter (`@ai-sdk/openai` 2+ defaults to the Responses API, not yet verified there). The hint of `resolveProvider()`'s rare synchronous missing-peer error lists the command for each `ai` major. See [Installation](docs/installation.md#provider-packages).
|
|
619
|
+
- `lousho add` (LOU-D50): `lousho add <name> [--registry <url-or-path>] [--dir <agent-dir>] [--yes] [--overwrite] [--dry-run]` installs a tool, skill, channel, schedule or memory slot from a static JSON registry (an index plus one document per item that carries the file contents) into an agent directory as source you own; `lousho add --list` prints the index. The registry comes from `--registry` or `LOUSHO_REGISTRY` (there is no hosted registry yet). It prints the item's permission manifest (hosts, env vars, filesystem, exec, whether its tools ask for approval) and the files first and asks for confirmation (`--yes` skips it; a non-interactive stdin without `--yes` refuses; `--dry-run` writes nothing). Paths must be relative, inside the folder of the item's type and inside the agent directory (no `..`, absolute paths, drive letters, backslashes or symlink escapes), files and items have size caps, existing files need `--overwrite`, fetches time out and only `http(s)` and local paths are read; nothing from a registry is executed and `dependencies` are only printed as an `npm install` line. New error codes `LOUSHO_REGISTRY_UNREACHABLE`, `LOUSHO_REGISTRY_ITEM_NOT_FOUND`, `LOUSHO_REGISTRY_INVALID`, `LOUSHO_REGISTRY_UNSAFE_PATH`, `LOUSHO_REGISTRY_FILE_EXISTS`. See docs/registry.md.
|
|
620
|
+
- Checkpoint history for `KVCheckpointStore` and Agent Forge's file store (LOU-D43.2): `KVCheckpointStore` (and `KVStore`, with the new `historyLimit` option) keeps the bounded per-session history `memoryStore()` keeps, as one index key (`<prefix><sessionId>#history`) and one key per entry (`<prefix><sessionId>#history/<id>`) next to the unchanged latest-checkpoint key, so `agent.fork()`, `compareTrajectories()` and time travel work for Worker-deployed agents; a store written before this change still loads. A save now makes one `get` and two `put`s more (`historyLimit: 0` turns the history off), and two concurrent saves of one session can lose an index update (KV has no transactions). Agent Forge's `FileCheckpointStore` reads a partially written history file as empty and writes its files atomically. The history contract suite now runs against `KVCheckpointStore` too. See docs/durable-execution.md#checkpoint-history.
|
|
621
|
+
- Hook outcomes (LOU-X3): a `preToolCall` hook may return `{ deny: reason }` (the call does not run; the model gets the same `kind: 'denied'` tool error as a `needsApproval` deny, streams see `tool.error`, and `onPermissionDecision` / `permission.decision` record `decision: 'deny'` with the new `hook` field and `reason`), `{ result: value }` (the call does not run; `value` is its result) or `{ input: args }` (the call runs with `args`, validated against the tool's schema again; a mismatch is a `kind: 'validation'` tool error naming the hook). A `postToolCall` hook may return `{ result: value }` to replace the result the model sees. Pre-hooks run in registration order: the first deny or result skips the later ones, inputs chain. A replaced result is marked `replacedByHook` on the `tool.done` event, the `ToolCallOutcome` / `tool-result` event and the transcript's `tool` message `metadata`. Hooks still run first (after argument validation, before permission rules, tool guardrails and `needsApproval`), so the approval sees the hook's input; on a resumed approved call the pre-hooks run again and an `{ input }` that differs from the approved input is refused. Sub-agents inherit the outcomes. Hooks that return nothing type-check and behave as before; `HookRegistry.runPreToolCall()` now resolves to a `PreToolCallDecision` and `runPostToolCall()` to the name of the replacing hook (new types `PreToolCallOutcome`, `PostToolCallOutcome`, `PreToolCallDecision`). A hook that throws still rejects the run. See docs/api-overview.md#hook-outcomes.
|
|
622
|
+
- `lousho acp` (LOU-Z6): `lousho acp <spec.yaml|spec.json|agent-dir|agent.ts> [--model provider/model]` serves an agent over the Agent Client Protocol (v1) on stdio, so Zed and other ACP editors can run it as an external agent; stdout carries only JSON-RPC lines (the agent's `console.log` goes to stderr). It implements `initialize`, `session/new`, `session/prompt` (text and `resource_link` blocks) and `session/cancel`, streams `session/update`s (`agent_message_chunk`, `tool_call`, `tool_call_update`), asks `session/request_permission` (Allow / Reject) when a tool needs approval and streams the continuation, and maps finish reasons to stop reasons (`max-steps` to `max_turn_requests`, `budget-exceeded` / `length` to `max_tokens`, `guardrail` to `refusal`, a cancel to `cancelled`). One ACP session is one SDK session. An `ask_question` pause ends the turn with the question as the agent's message; the next prompt answers it. A failed run answers a JSON-RPC error whose `data.code` is the SDK error code. The protocol core is exported as `serveAcp(agent, { input, write, store? })` (new type `ServeAcpOptions`), transport-independent. Not supported yet: `session/load`, the client's `fs/*` and `terminal/*` methods, images, audio and embedded resources, `plan` and thought chunks. See docs/acp.md.
|
|
623
|
+
- Agent directories deploy and serve with their schedules and channels (LOU-P8.2): `lousho build <agent-dir> --target=node-server` (and `docker`; the path may also be `--agent`) builds a directory into a server whose entry calls `resolveAgentDir()` and `createDeployedServer(agent, { schedules, channels })`, so the deployed process starts the `schedules/` and mounts the `channels/` under `/channels` and logs which it found. The code files are pre-bundled by the existing tsup step into `dist/agent/**.js` (one ESM build sharing one SDK copy; `dist/` is marked ESM) and the rest of the directory is copied, so nothing needs a TypeScript loader at run time; spec builds are unchanged. `lousho dev <agent-dir>` mounts the directory's channels and starts its schedules, swapping both on hot reload (old schedules stopped first); `--no-schedules` starts none. The deploy runtime also exports `createAgent`, `resolveAgentDir` and `storeFromEnv`. The Cloudflare Worker target is unchanged (P9). See docs/agent-directories.md#deploy-it-with-lousho-build.
|
|
624
|
+
- Route handler (LOU-P4): `createRouteHandler(agent, { basePath?, auth?, uiMessageStream? })` returns `{ GET, POST, handler }`, Fetch handlers (`(request: Request) => Promise<Response>`) that serve the deployed session API under `basePath` (default `/api/agent`; trailing slash accepted, other paths 404) from a Next.js App Router `app/api/agent/[[...path]]/route.ts`, SvelteKit `+server.ts`, Hono or Bun.serve, with no framework or `node:*` import. `auth` is a bearer token or a `(request) => boolean | Promise<boolean>` (default none: a public route needs one; `GET /health` stays open). `useLoushoAgent({ url, approvalsUrl })` works against it (`POST <basePath>` with `{ input, sessionId? }`, `POST <basePath>/approvals/:id`), and `uiMessageStream: true` adds `POST <basePath>/ui` for `useChat`. `hasBearerToken` in `src/server/fetchRoutes.ts` is now exported for it. See docs/nextjs.md.
|
|
625
|
+
- AI SDK UI stream (LOU-P1): `toUIMessageStream(run)` maps a run's events to the Vercel AI SDK's UI message stream chunks (`start`, `start-step`, `text-start` / `text-delta` / `text-end`, `tool-input-start` / `tool-input-available` / `tool-output-available` / `tool-output-error`, `finish-step`, `error`, `finish` with the AI SDK finish reason), and `toUIMessageStreamResponse(run, init?)` returns the SSE `Response` (`data: [DONE]`, header `x-vercel-ai-ui-message-stream: v1`) for a route handler, so `useChat` can render a Lousho run. An approval pause or `ask_question` is a `data-lousho-approval` part (`approvalId`, `toolCallId`, `toolName`, `input`, `kind?`, `question?`); usage and the run's own finish reason are in the `finish` chunk's `messageMetadata`. `fromUIMessages(messages, { lastUserOnly? })` converts the `UIMessage[]` `useChat` posts (text, image and file parts; others ignored) to an `AgentInput`. Own structural chunk types (`LoushoUIMessageChunk`, `UIMessageLike`, ...), no `ai` import and no `node:*`, so it also runs on Workers; exported from the package root (a dedicated subpath can follow). See docs/ai-sdk-ui.md.
|
|
626
|
+
- Approval policies (LOU-X8): a `needsApproval` function may return (or resolve to) `'approve'`, `'deny'`, `'ask'` or `{ deny: reason }` besides a boolean (`true` is `'ask'`, `false` is `'approve'`; new type `ApprovalOutcome`), and gets a second argument `{ toolName, toolCallId, sessionId?, messages }` (new type `ApprovalCheckContext`). A deny does not pause: the call is not run, the model gets a `kind: 'denied'` tool error with the reason, streams see `tool.error`, and `onPermissionDecision` / `permission.decision` record `decision: 'deny'` with the new `reason` field (no `rule`); the audit entry is now reported once the call is decided (after tool guardrails) instead of before them. New helpers `always()`, `never()` and `once({ per?: 'tool' | 'args' })` (with type `ApprovalPolicy`): `once()` asks the first time a tool is called in a session and runs later calls once a human approved one (a rejection is not remembered; `per: 'args'` keys on the arguments' hash). The memory is the approved call's `tool` message (`metadata.approval`, set by `resumeAfterApproval()` and its streamed form), so it persists with sessions, checkpoints and approval snapshots and survives a resume in another process. A `permissions` `deny` rule wins; an `allow` or `ask` rule replaces the tool's ask but not its deny (see Changed); the tool's outcome applies when no rule matches. Existing boolean functions type-check and behave as before; a test asserting `needsApproval` was called with exactly one argument now sees the context too. See docs/approvals.md#approve-deny-or-ask.
|
|
627
|
+
- Approval continuations stream live (LOU-D32.2): `POST /chat/:sessionId/approvals/:id` (node server, Cloudflare Worker, `lousho dev`) now streams the continuation from `agent.approvals.streamResolve()` / `streamAnswer()` instead of replaying it after the turn ends; `createAgentRunner`'s `approve()` / `reject()` / `answer()` (React, Vue, Svelte) read that SSE stream event by event (an `ApprovalOutcome` JSON from an older server still works), and `lousho chat` prints the continuation as it streams. The UI reducer maps a streamed `tool.error` named `ToolRejectedError` to a `rejected` tool call. A second pause arrives as `approval.requested`. The approvals route has always answered with SSE, so no client opt-in was added.
|
|
628
|
+
- `session.compact()` and `session.clear()` (LOU-W8): `await session.compact({ strategy?, protectedTokens?, contextWindow?, signal? })` compacts the session's transcript now, whatever its size, with the given strategy, else `agent.session({ compaction })` (new option, the value of `createAgent({ compaction })`), else by pruning old tool results; it saves the result through the session's store and resolves to `{ messagesBefore, messagesAfter, tokensBefore, tokensAfter, strategy, error? }`. `clear()` (which already existed and forgot the conversation) now emits `context.cleared` (new `ContextClearedEvent` in the `AgentEvent` union) and rejects with `LOUSHO_SESSION_AWAITING_APPROVAL` when a durable turn waits on an approval; it keeps the session id. New `session.on(listener)` receives `compaction.start` / `compaction.done` (which gain an optional `trigger: 'manual'`) and `context.cleared`. Both reject with the new `LOUSHO_SESSION_BUSY` while a turn is in flight; `compact()` also with `LOUSHO_SESSION_TURN_PENDING` for an interrupted durable turn. `lousho chat` gains `/compact` and `/clear`. See docs/compaction.md#compacting-a-session.
|
|
629
|
+
- Discord channel (LOU-P6): `discordChannel({ publicKey, applicationId, botToken?, name?, fetch? })` serves Discord's HTTP Interactions endpoint through `mountChannels()` (Web Crypto and `fetch` only, Worker-safe, no gateway, no new dependency). It verifies `X-Signature-Ed25519` / `X-Signature-Timestamp` (Ed25519 over `timestamp + body`, 401 otherwise) and answers `PING` with `PONG`. A slash command (`/ask prompt:<text>`) is acknowledged at once with a deferred response; its session is keyed on guild + channel (+ thread), and the reply edits the original response, with text over 2000 characters continued in follow-up messages. A tool approval is posted with Approve / Deny buttons and resolved by the click; an `ask_question` is answered by the next `/ask` in the channel. See docs/channels.md#discord.
|
|
630
|
+
- Streaming resume after an approval (LOU-V14): `streamResumeAfterApproval(decision, approvalStore, toolRegistry, provider, options?, checkpointStore?)` takes `resumeAfterApproval()`'s arguments and returns an `AgentRun` whose `result` is what `resumeAfterApproval()` returns. Its events are `run.start`, the decided call's `tool.start` / `tool.done` (`tool.error` for a rejection), then the continuation's events as in a fresh `stream()` run; a further pause ends it with `approval.requested`. Abort, `enqueue()` and `steer()` work as on any run, and the approval can be resolved in another process. `createAgent()` agents get `agent.approvals.streamResolve(decision, { signal })` and `streamAnswer({ id, answer }, { signal })` (a pause made in a session continues in it, `run.done` after the transcript is saved; a dynamic agent keeps the model the paused run used). Also new: the `ResumeRequest` type and `resumeRequest(request)` (`resumeAfterApproval()` with one object argument). Additive: `resolve()`, `answer()` and `resumeAfterApproval()` are unchanged. See docs/streaming.md#streaming-after-an-approval.
|
|
631
|
+
- Credential broker (LOU-X12): `createCredentialBroker({ rules, allow?, allowPrivate?, port?, host?, pathFormScheme? })` starts a host-side HTTP forward proxy (Node-only, on an ephemeral `127.0.0.1` port by default) and resolves to `{ url, env, baseUrl(host), close() }`. `rules` map a host (exact or `*.suffix`) to headers to inject, as strings or per-request functions, so a command the model runs can call an authenticated API without the token entering its environment: pass `broker.env` (`HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`, both cases) to `NodeWorkspace` or `SandboxShell`'s `env`. Hosts outside the rules and `allow` get a 403 without a connection; hosts resolving to loopback, link-local or private addresses are refused unless in `allowPrivate`; a client `Authorization` header for a brokered host is replaced; hop-by-hop headers are dropped. Injection covers plain-HTTP requests and the `/__broker/<host>/<path>` form (forwarded to `https://<host>/<path>`, returned by `baseUrl(host)`); HTTPS `CONNECT` tunnels are allowlist-checked and passed through untouched (no TLS interception). `SubprocessSandbox`'s `network: { allow }` still fails closed: routing containers through the broker is LOU-X12.2. New types `CredentialBroker`, `CredentialBrokerOptions`, `BrokerHeaderValue`. See docs/workspace-tools.md#credential-broker.
|
|
632
|
+
- Memory in agent directories (LOU-W6.3): `memory/*.ts|js|mjs` files default-export a memory slot (`defineMemory()`, or the same options without a `name`; name = file name unless set). Unlike schedules and channels they are part of the agent: `loadAgentDir()` passes them to `createAgent({ memory })`, `resolveAgentDir()` lists them in `manifest.memory`, and a `memory` override is merged by name (the override wins a clash). A bad file fails with the new `LOUSHO_MEMORY_INVALID` naming the file. A directory without `memory/` is unchanged. See docs/agent-directories.md#memory.
|
|
633
|
+
- Channels in agent directories (LOU-P7.2): `channels/*.ts|js|mjs` files default-export a channel (`defineChannel()` or a built-in factory; name = file name unless set); `resolveAgentDir()` returns them as `channels` and `manifest.channels`, and a file that exports no channel fails with the new `LOUSHO_CHANNEL_INVALID` naming the file. `createDeployedServer(agent, { channels })` mounts them under `/channels` next to the chat routes. `loadSchedules()` and `loadChannels()` share one loader (schedule files that do not export a schedule now throw an `SDKError` with `LOUSHO_SCHEDULE_INVALID`, same message). `lousho dev` and the Cloudflare Worker target do not mount them yet. See docs/agent-directories.md#channels.
|
|
634
|
+
- `serveMcp` annotations (LOU-Z5.2): `defineTool({ annotations })` (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `title`; stored as `metadata.mcp.annotations`) is sent in `tools/list` verbatim, and a tool with `needsApproval` is advertised `readOnlyHint: false, destructiveHint: true` (never read-only). An unannotated tool sends none. A Lousho agent consuming the server therefore runs read-only tools without an approval pause, pauses for `needsApproval` ones and keeps asking for unannotated ones. The built-in `read_file`, `list_dir`, `glob`, `grep`, `todo_read`, `current_date` and `day_name` tools are marked read-only. See docs/configuration.md#annotations.
|
|
635
|
+
- Svelte store (LOU-P3): new `@lousho/build-ai-agent/svelte` subpath with `loushoAgent(source, options?)`. It returns a Svelte store (`subscribe(run) => unsubscribe`, so `$agent` works in Svelte 4 and 5) whose value is the same state as the React hook and the Vue composable (`messages`, `status`, `pendingApproval`, `error`, `usage`, `lastEvent`), plus `send()`, `stop()`, `approve()`, `reject()`, `answer()` and `reset()`. The run in flight is aborted by `stop()` and when the last subscriber unsubscribes. The store contract is implemented by hand: the entry imports nothing from `svelte`, so there is no new peer dependency. See docs/svelte.md.
|
|
636
|
+
- Per-run dynamic config (LOU-V15): `createAgent()`'s `model`, `instructions` / `prompt` and `tools` each also take a function of the run, `(ctx) => value | Promise<value>` (new types `PerRun<T>` and `RunConfigContext` = `{ sessionId?, input, metadata? }`), resolved once at the start of each run and of each session turn, before the first model call. `fallbackModels`, `retry`, `projectInstructions`, memory, MCP tools, sub-agents, permissions, approvals and guardrails apply to the resolved values; a dynamic agent used as a sub-agent resolves with the task prompt as `input`. `session.send()` / `session.stream()` gain the `metadata` option (`send()` / `stream()` already had it, and now pass it to these functions too). A function that throws fails the run with the new `LOUSHO_CONFIG_RESOLVER_FAILED` (`field` names the option, `cause` is the error); a session keeps its transcript. A run paused for approval stores its `ctx` and resolved model in the approval snapshot (`agent.metadata.loushoRunConfig`): resuming it uses that model and re-resolves the tools with the same `ctx`; a crash resume from a checkpoint resolves with `input: []`. Static options behave and type as before. `SessionRunner` / `SessionStreamRunner` take an optional fourth `call` argument (new type `SessionTurnCall`). See docs/api-overview.md#dynamic-config.
|
|
637
|
+
- Vue composable (LOU-P2): new `@lousho/build-ai-agent/vue` subpath with `useLoushoAgent(source, options?)` for Vue 3. It takes the same sources and options as the React hook (either may be a `ref`, read when a turn starts) and returns `messages`, `status`, `pendingApproval`, `error`, `usage` and `lastEvent` as read-only refs, plus `send()`, `stop()`, `approve()`, `reject()`, `answer()` and a new `reset()` (aborts, forgets the in-process session, clears the chat). The run in flight is aborted by `stop()` and when the owning scope is disposed (`onScopeDispose`: component unmount, `effectScope().stop()`). `vue` (`^3`) is an optional peer dependency, loaded by no other entry. To share the logic, the reducer and the stream parser moved from `src/react/` to a framework-neutral `src/ui/`, together with the run logic extracted from the React hook (`createAgentRunner()`); the `./react` exports are unchanged, and the reducer gains an additive `ui.reset` action. See docs/vue.md.
|
|
638
|
+
- One flag parser for the `lousho` CLI (LOU-U21): every command (`dev`, `chat`, `eval`, `build`, `mcp`, `doctor`, `studio`; `init` already did) parses its flags with `node:util` `parseArgs` in strict mode through `src/cli/args.ts`. `--flag value` and `--flag=value` work everywhere, `--` ends the flags, `-h` / `--help` prints the usage and exits 0, and an unknown flag, a flag without its value or an extra argument fails with `LOUSHO_CONFIG_INVALID` and the command's usage as the hint. **Behaviour change (migration):** `dev`, `studio`, `mcp`, `build` and `doctor` used to ignore unknown flags, and a flag missing its value could take the next flag or a prefix match (`--portx 1` read as `--port`); they now fail with exit 1 and the usage line, so remove typos from scripts. A value that starts with `-` must be written `--flag=-x` (a bare `--flag -x` is a missing value); `--port` must be an integer from 0 to 65535 for `dev`, `studio` and `mcp`; `dev`, `mcp` and `doctor` reject a second path; `studio --prod --dev` is rejected instead of preferring `--prod`. `lousho dev` and `lousho studio` parsing moved from `bin/lousho.js` into `src/cli/dev.ts` / `src/cli/studio.ts` (`parseDevArgs`, `runDev`, `parseStudioArgs`, `runStudio`). See docs/cli.md.
|
|
639
|
+
- `sqliteMemory(store, { maxItems? })` (LOU-W6.2, exported from `@lousho/build-ai-agent/sqlite`) keeps `defineMemory` slots in the agent's `SqliteStore` file: a new `memory_items` table (migration 3, one JSON array per scope key, with the same serialization, `maxItems` and query rules as `fileMemory`). An existing database gains the table on open and keeps its sessions and checkpoints. The memory provider contract tests now run against the in-memory, file and SQLite providers. See docs/memory.md#sqlite.
|
|
640
|
+
- Slack channel (LOU-P5): `slackChannel({ signingSecret, botToken, name?, fetch? })` (new export, with types `SlackChannelOptions`, `SlackChannelEvent`, `SlackThread`) serves a Slack app's Events API and interactivity requests through `mountChannels()`. It verifies the `v0` request signature with Web Crypto (no `node:*` import; the timestamp and header checks are shared with `verifySlackSignature()`), answers the `url_verification` challenge, and acknowledges every request before the turn runs. Each Slack thread is one session (team + channel + thread root `ts`): a mention starts or continues it, and later messages in a thread the bot was mentioned in continue it. Retries (`X-Slack-Retry-Num`) and bot messages are skipped. Replies go to the thread with `chat.postMessage` over `fetch`; a tool approval is posted as Approve / Deny buttons whose click resumes the session, and an `ask_question` as text answered by the next thread message. The channel contract gains two additive hooks for this: `parse(req, respond)` may call `respond` to answer the request before the turn runs, and may return `{ decision }` (new type `ChannelDecision`; `ChannelApprovalDecision` now lives in `defineChannel.ts`, same public export) to resolve a pause instead of starting a turn. See docs/channels.md#slack.
|
|
641
|
+
- Remote sub-agents (LOU-Y7): `remoteAgent({ url, auth?, name?, description?, headers?, fetch? })` (new export, with types `RemoteAgentOptions` and `RemoteSubagent`) makes a deployed agent usable wherever a local sub-agent is: in `createAgent({ subagents })` records and `SubagentCatalog.resolve()`, delegated to by the `task` tool and by `background: true` tasks. Each task opens a fresh session on the remote agent over `POST <url>/chat` (bearer `auth`: a string or a function, called per task), reads the SSE stream to the end and returns the remote agent's final text; the lead run's abort signal aborts the request. Failures (network, 401 or other non-2xx, malformed stream, remote run error) are structured tool errors with the new code `LOUSHO_REMOTE_AGENT_FAILED`, and the token never appears in errors or events; a remote run that pauses for approval fails with `LOUSHO_SESSION_AWAITING_APPROVAL` naming the remote session (proxying approvals is a follow-up). Worker-safe (`fetch` only). See docs/sub-agents.md#remote-sub-agents.
|
|
642
|
+
- Eval against a deployed agent (LOU-D47): `lousho eval --url <baseUrl> [--token <bearer>]` (token also from `LOUSHO_EVAL_TOKEN`) runs every case of the trajectory eval files against a deployment (node server or Cloudflare Worker) instead of the in-process agent: one remote session per case over `POST /chat { sessionId, input }`, the SSE stream read to `run.done` and turned into the usual result (tool calls, reply, finish reason, steps, usage), so checks, scorers, `t.judge()`, the summary and `--junit` work unchanged. A check that needs data the stream lacks (`maxTokens` without usage) fails and names what is missing. Programmatic equivalent: `defineEval({ target: remoteTarget({ url, auth?, fetch? }) })` (new `remoteTarget()`, `EvalTarget`; `agent` is now optional when a `target` is given, and `AgentSource` also accepts any `{ send() }`). `--url` with `--record` / `--replay` / `--drift` is rejected with `LOUSHO_CONFIG_CONFLICTING_OPTIONS`; an unreachable deployment, a non-2xx answer or a truncated stream fails the case with the new `LOUSHO_REMOTE_REQUEST_FAILED`, and 401 with `LOUSHO_REMOTE_UNAUTHORIZED`. The token never reaches output or reports. See docs/evals.md#run-evals-against-a-deployment.
|
|
643
|
+
- Schedules (LOU-P8): `defineSchedule({ cron, timezone?, name?, prompt | run })` declares a cron schedule (a `prompt` sent to the agent as a new turn, or `run({ agent, firedAt, name })`); an invalid cron expression, or not exactly one of `prompt`/`run`, throws the new `LOUSHO_SCHEDULE_INVALID` at definition time. An agent directory's `schedules/*.ts|js|mjs` files default-export one each (name = file name unless set); `resolveAgentDir()` returns them as `schedules` and `manifest.schedules`. `startSchedules(agent, schedules, { now?, setTimer?, onError? })` runs them with injectable clock and timers, isolates errors, skips a fire while the previous one runs and returns `stop()`; `createDeployedServer(agent, { schedules })` starts them on listen and stops them on close. The Cloudflare Worker target is not covered yet. See docs/schedules.md.
|
|
644
|
+
- Channels (LOU-P7): `defineChannel({ name, verify?, parse, reply, onApproval?, sessionId?, stream? })` describes one surface: `verify` authenticates a request (`false` or `{ ok: false }` answers 401), `parse` maps it to `{ sessionKey, input, metadata?, replyTo }` (or `null` to acknowledge it without a turn), `reply` delivers the agent's reply (with `stream: true`, also partial text as the model writes) and `onApproval` renders a pause (default: a text prompt through `reply`). `mountChannels(agent, channels, { store, basePath })` returns a framework-free `(req, res)` handler, in the style of the `/chat` routes, that serves `POST <basePath>/<name>` (verify -> parse -> a turn in session `` `${name}:${sessionKey}` `` -> reply; turns of one session run one at a time) and `POST <basePath>/<name>/approvals/<id>` (`{ approved, note? }` or `{ answer }`); `handler.resolveApproval()` decides a pause from code (e.g. a button callback) and replies through the same channel. Built in: `httpChannel()` (JSON `{ sessionKey, input }` in, `{ sessionId, text, finishReason, approval? }` out) and `webhookChannel({ secret })` (`WebhookTriggerAdapter`'s behaviour; the adapter now delegates its auth and parsing to it, unchanged for callers). Slack and Discord channels are LOU-P5 / LOU-P6. See docs/channels.md.
|
|
645
|
+
- Cloudflare Worker parity (LOU-D51): the `cloudflare-worker` target now serves the same API as the node server: `POST /chat { sessionId, input }` streamed as SSE, history per session, `GET /chat/:sessionId`, `POST /chat/:sessionId/approvals/:id` and `GET /health`; the deprecated `POST /chat { message, sessionId? }` still works and still checkpoints under `sessionId`. `LOUSHO_API_TOKEN` (a Worker secret: `wrangler secret put LOUSHO_API_TOKEN`) makes every route except `/health` require `Authorization: Bearer <token>` (constant-time compare). New `KVStore(kvBinding, { prefix?, ttl? })` (`src/deploy/kvStore.ts`) is an `AgentStore` on a Workers KV namespace: `sessions/<id>` (transcript JSON, bytes as `{ "$bytes" }`), `checkpoints/<id>` (`KVCheckpointStore`, which gains an optional TTL) and `approvals/<id>`, with optional per-kind TTLs; the Worker builds it from the existing `AGENT_CHECKPOINTS` binding, and keeps sessions in the isolate's memory without one. An approval paused on one request can be decided by another request on a new isolate. The routes are now Fetch-native (`src/server/fetchRoutes.ts`: `Request` in, `Response` and a `ReadableStream` SSE body out, bearer auth with Web Crypto); `src/server/chatRoutes.ts` adapts them to `node:http` for `lousho dev` and the node server, and `src/server/bearerAuth.ts` is gone. The built Worker runs a `createAgent()` agent, so the build swaps its Node-only imports for `src/deploy/shims/node.worker.ts`, and the `node:` leak check now also catches bare builtin specifiers; the bundle grows to about 1.7 MB. The generated `wrangler.toml` documents the KV binding and both secrets. The `wrangler dev` test now streams a session turn through workerd with a KV binding and a token. See docs/deployment.md#bindings-sessions-and-the-api-on-workers.
|
|
646
|
+
- Steering (LOU-V10): `run.steer(input)` on `AgentRun` (from `agent.stream()`, `session.stream()` and `AgentExecutor.stream()`), and `InputQueue.steer(input)` for non-streaming callers, redirect a running run to new user input. If a model call is in flight and has not emitted text or tool calls yet, it is aborted with a per-call signal (not the run's), its partial output is discarded, the input is appended as a user message and the model is called again (`applied: 'immediate'`; that step ends with `step.done` `'steered'` and counts toward `maxSteps`; a provider that ignores the signal is not waited for). Once the model has emitted output, the input waits for the next safe point like `enqueue()` (`'queued'`); after the run has finished, `applied` is `false`. Tool calls already running finish and keep their results; calls of that turn not started yet get the usual "cancelled before it ran" result. It returns `{ id, applied, joined }` (new type `SteerResult`; `joined` is a promise of whether the input reached the transcript). New event `input.steered` `{ id, text, mode: 'immediate' | 'queued' }` (`InputSteeredEvent`, additive, schema v1), followed by the existing `input.applied`. With checkpointing, the input is checkpointed when it is taken, before the model is called again, and the discarded partial turn is never checkpointed. `agent.session({ turnPolicy: 'steer' })` makes a `send()` / `stream()` made while a turn runs steer that turn (`'queue'` and `'wait'` are unchanged). `AgentRun` gains the required `steer()` member, so a hand-written `AgentRun` object must add it. See docs/streaming.md#steering and docs/sessions.md.
|
|
647
|
+
- `ai` v6/v7 `stream()` (LOU-D27): the built-in providers' `stream()` (and so `agent.stream()`) now works on `ai` v6 and v7 as well as v4, through the LOU-D26 compatibility layer (`streamCompat()`): the request is mapped as for `generate()`, and either major's `fullStream` is read into the same `StreamChunk`s: text deltas, tool calls (the whole call, or one assembled from `tool-input-start`/`-delta`/`-end` when the model sends none), and a `finish` chunk with the finish reason and usage, including cached and reasoning tokens. An `error` part now rejects the stream with its error on both majors (on v4 the step used to end with finish reason `error` and the partial text), an `abort` part with the signal's reason; reasoning deltas are dropped until a reasoning chunk type exists (LOU-V13). `StreamChunk`/`StreamResult` are unchanged. The protected `AiSdkProvider.convertMessages()` now returns our structural `AiSdkMessage[]` instead of `ai` v4's `CoreMessage[]` (which v6/v7 do not export) and is marked deprecated for LOU-D28; an override returning `CoreMessage[]` still compiles. The `ai`/`@ai-sdk/*` peer ranges still name v4 (LOU-D28). Tested on the real `ai` v7 (`MockLanguageModelV4`), with `agent.stream()` events asserted by one helper on v4 and v7. See docs/providers.md#vercel-ai-sdk-versions.
|
|
648
|
+
- `spec.policy` is enforced (LOU-X5): `specToAgent()` compiles the policy block into `createAgent()` options through the new `compilePolicy()`. `requiresApproval` (`true` or a list of tool names) becomes `permissions: [ask(...)]`; `guardrails` (`max-length`, `secret-scan`, `regex`, `deny-topics`, `llm-judge`, by name or `{ name, ...options, on }`) become input and output guardrails; `limits`, `askQuestion` and `compaction` pass through. `loadSpec()` validates the known fields (an unknown guardrail name fails with the names available and a did-you-mean; unknown policy keys are still kept for the generators), and `lousho doctor <spec>` prints one line per policy block and flags unknown guardrail names. **Behaviour change:** `requiresApproval: true` now actually pauses tool calls (`finishReason: 'awaiting-approval'`); before, a spec that set it ran its tools without asking, and a spec with a guardrail name outside the built-ins (for example `diff-size-cap`) now fails validation. See docs/configuration.md#policy-policy.
|
|
649
|
+
- `ai` v6/v7 `generate()` (LOU-D26): the built-in providers' `generate()` now works on `ai` v6 and v7 as well as v4, through a compatibility layer that detects the installed major (v5+ exports `stepCountIs`). On v6/v7 it sends messages as `ModelMessage`s (tool calls with `input`, tool results as `output`, `mediaType`), `maxTokens` as `maxOutputTokens`, one step as `stopWhen: stepCountIs(1)`, tools as `inputSchema` without `execute` and `responseFormat` as a JSON-mode `output`, and reads usage from `inputTokens`/`outputTokens`/`totalTokens` (cached and reasoning tokens from their details) into the same `GenerateResult`. `LLMProvider`, `GenerateOptions` and `GenerateResult` are unchanged, and v4 behaves as before. `stream()` on v6/v7 throws an error naming LOU-D27 until that ticket lands; the `ai` and `@ai-sdk/*` peer ranges still name v4 (LOU-D28). Tested against the real `ai` v7 (`MockLanguageModelV4`) next to the v4 contract tests. See docs/providers.md#vercel-ai-sdk-versions.
|
|
650
|
+
- Queued follow-up input (LOU-V9): `run.enqueue(input)` on `AgentRun` (from `agent.stream()`, `session.stream()` and `AgentExecutor.stream()`) adds user input to a run that is still going; it joins the transcript at the next safe point (after the current step's tool results, before the next model call) and the run continues as if the user had typed it, with one more step when it arrives during the final reply. It returns `{ id, applied }`: `applied` is `false` when the run had already finished (send the input as a new turn, e.g. `session.send()`), otherwise a promise of whether it was applied. New events `input.queued` `{ id, text }` and `input.applied` `{ id, step }` (additive, schema v1). Non-streaming callers pass an `InputQueue` (new export) as `ExecuteOptions.inputQueue` and call its `push(input)`. With checkpointing, a waiting input is saved right away at the end of the checkpoint (the LOU-U8 queued-input slot), so a crash does not lose it. `agent.session({ turnPolicy: 'queue' })` makes a `send()` / `stream()` made while a turn runs (or waits to start) join that turn instead of waiting for its own (`'wait'`, the default, keeps today's behaviour). `AgentRun` gains the required `enqueue()` member, so a hand-written `AgentRun` object must add it. Steering (aborting the in-flight model call to redirect) is the follow-up, LOU-V10. See docs/streaming.md#queued-input and docs/sessions.md.
|
|
651
|
+
- Input and output guardrails (LOU-X4): `guardrails?: { input?, output?, tools?, onTripped? }` on `createAgent()` and `ExecuteOptions` (new type `AgentGuardrails`). A guardrail is `{ name, check(ctx) }` (new type `IoGuardrail`; `ctx` is `{ kind: 'input' | 'output' | 'tool', text, messages, toolName?, args?, signal? }`) returning `{ ok: true }` or `{ ok: false, reason, action?: 'block' | 'rewrite', replacement? }`. Input guardrails run on each new user message before the first model call; output guardrails on the final reply (in a streamed run, on every step's text, before its `text.done`); tool guardrails on a call's arguments after the permission rules (skipped on `deny`) and before `needsApproval`. A block ends the run with the new `finishReason: 'guardrail'` and `result.guardrail` (`{ name, kind, reason, toolName? }`, new type `GuardrailTrip`), with no model call for an input block; `onTripped: 'throw'` rejects with the new `GuardrailError` (`LOUSHO_GUARDRAIL_TRIPPED`) instead. A rewrite replaces the text. `stream()` emits the new `guardrail.tripped` (before `run.done`) and `guardrail.rewrote` events. Sub-agents run their parent's guardrails, then their own. Built-ins: `maxLengthGuardrail({ maxChars })`, `regexGuardrail({ name, pattern?, action?, replacement? })` (defaults to the secret-scan patterns, now exported as `SECRET_PATTERNS`), `denyTopicsGuardrail({ topics })` and `llmJudgeGuardrail({ model, instruction })`. The patch guardrails (`runGuardrails()`, `secretScanGuardrail`, ...) are unchanged; the new types are named `IoGuardrail*` because `Guardrail` and `GuardrailResult` are theirs. `spec.policy.guardrails` in agent spec files is not wired yet (LOU-X5). See docs/guardrails.md#input-and-output-guardrails.
|
|
652
|
+
- Deployed `/chat` upgrade (LOU-D14): the `node-server` and `docker` targets (`dist/server.js`) now serve the `lousho dev` protocol: `POST /chat { sessionId, input }` streams the turn as SSE (`data: <AgentEvent JSON>`, then `event: done`) with history kept per session, `GET /chat/:sessionId`, `POST /chat/:sessionId/approvals/:id` (`{ approved, note? }` or `{ answer }`) and `GET /health`; the single-turn `POST /chat { message }` still works (now with a `Deprecation: true` header). The routes are one shared module, `src/server/chatRoutes.ts`, used by `lousho dev` and the deployed runtime. `LOUSHO_STORE` (`memory`, the default, or `sqlite:<path>`, Node >= 22) picks where sessions live. Bearer auth: set `LOUSHO_API_TOKEN` (or pass `{ auth: { token } }` as the new optional third argument of `adapter.scaffold()`; the variable wins) and every route except `/health` requires `Authorization: Bearer <token>` (constant-time compare, `401` JSON otherwise); treat it as required for anything not on localhost. The `docker` image is now based on `node:22-slim` (was `node:20-slim`) for `node:sqlite`. `specToAgent(spec, { store })` takes an optional `store`. The Cloudflare Worker target is unchanged (sessions over KV and SSE there are LOU-D51). See docs/deployment.md#http-api.
|
|
653
|
+
- `lousho chat` (LOU-D33): a terminal REPL for an agent. `lousho chat <spec|dir|module> [--model provider/model] [--session id] [--store sqlite:<file>]` loads the target like `lousho dev` and reads one turn per line through `agent.session({ id, store }).stream()`: text deltas as they arrive, each tool call as a dim `[tool_name] {args}` line followed by ` -> result`, `Approve <tool>(args)? [y/N]` for a tool that needs approval, a numbered prompt with free text for an `ask_question` call, and `[usage]` tokens and cost after each turn. Slash commands: `/new`, `/model <provider/model>` (rebuilds the agent, the session continues), `/history`, `/quit`. SDK errors print with their `[LOUSHO_...]` code and fix. The loop is `runChatRepl({ input, output, createAgent })` in `src/cli/chatRepl.ts` (testable with scripted lines and a `mockModel` agent); `loadTarget()` is now exported from `src/cli/devReload.ts`. See docs/cli.md#lousho-chat.
|
|
654
|
+
- Memory slots (LOU-W6): `defineMemory({ name, description?, scope, provider, recall?, expose? })` (new export) defines a named long-term memory, and `createAgent({ memory: [slot] })` wires it in. On the first model call of every run (`send()`, `stream()`, each session turn), a `preGenerate` hook recalls the slot's newest `recall.maxItems` (default 10) items into the system prompt as a `<memory name="...">` block (`recall.query: 'last-input'` passes the last user message as the provider query; `recall.onSessionStart: false` turns recall off). The model gets `remember_<name>({ text })` and `recall_<name>({ query?, limit? })` tools (`expose: { remember?, recall? }`, both on by default). `scope` is `'global'`, `'session'` (keyed by `agent.session({ id })`'s id or `send()`'s `sessionId`) or a function of `{ sessionId, metadata }`; the new `SendOptions.metadata` feeds it. Providers implement `MemoryProvider` (`list` / `add` / `remove`); built in are `inMemoryMemory()` and `fileMemory({ dir })` (one JSON file per scope key), both bounded by `maxItems` (default 1000, oldest dropped). See docs/memory.md.
|
|
655
|
+
- Budgets (LOU-V6): `limits?: { maxTokens?, maxInputTokens?, maxOutputTokens?, maxCostUsd?, maxDurationMs?, maxSteps?, onExceeded? }` on `createAgent()` and `ExecuteOptions` (new type `RunLimits`). Limits are checked before every model call (so after every tool batch) and after a model call that asks for tools, and `maxDurationMs` aborts an in-flight model or tool call through the run's signal. A tripped limit ends the run with the new `finishReason: 'budget-exceeded'` and `result.budget: { limit, value, max, scope }` (`BudgetExceeded`); tool calls of that step get a cancelled result and the run is checkpointed as finished, like `'max-steps'`. `stream()` emits the new typed `budget.exceeded` event (`BudgetExceededEvent`) before `run.done` (additive, the event schema version stays 1). `onExceeded: 'throw'` rejects with the new `BudgetExceededError` (`LOUSHO_BUDGET_EXCEEDED`, no longer reserved) instead. `limits.maxSteps` is an alias of `maxSteps` (the stricter wins; alone it replaces the default of 10). Sub-agents' usage counts toward their lead's budget. `agent.session({ id, limits })` applies limits across a session's turns: what the turns spent is saved as `metadata.sessionUsage` on the transcript's last message (new types `BudgetSpent`, `SessionBudget`, `SessionTurnOptions`; `ExecuteOptions.sessionBudget` carries it to the run), so `maxCostUsd` per session holds across processes. See docs/configuration.md#budgets.
|
|
656
|
+
- Stateful streaming dev chat (LOU-D32): `lousho dev` serves one session per browser tab. `POST /chat` with `{ sessionId, input }` streams the turn as SSE (`data: <AgentEvent JSON>` per event, then `event: done`) through `agent.session({ id, store }).stream(input)` over an in-process `memoryStore()` (or the agent's own `store`, when a module's `createAgent()` options or `overrides` set one); `GET /chat/:sessionId` returns the transcript and the pending approval; `POST /chat/:sessionId/approvals/:id` with `{ approved, note? }` or `{ answer }` decides an approval or answers a question and streams the continuation (rebuilt from the continued run's result, since `agent.approvals.resolve()` is not streamed). The chat page renders text deltas, tool calls with arguments and results, Approve / Reject and question buttons, errors and `run.done` usage and cost, with a "New session" button and the id kept in `sessionStorage`. Sessions survive a hot reload and continue on the new agent. The `{ message }` body of `POST /chat` still works and is deprecated (no session, JSON `ExecutionResult`, `Deprecation: true` header). See docs/cli.md#lousho-dev and docs/streaming.md.
|
|
657
|
+
- Agent Forge History tab (LOU-D45.2): the bottom drawer's new **History** tab lists the loaded agent's run steps (step, status, finish reason, tool calls, tokens, cost). "Edit and replay from here" appends a user message or edits one of the step's tool results, forks the run at that step through `POST /runs/:id/fork`, and shows the fork next to the original as a side-by-side trajectory with the diverging turn and the drift entries highlighted, refreshed when the fork finishes. See docs/agent-forge.md#the-history-tab.
|
|
658
|
+
- `lousho dev` for agent directories and TS agents, with hot reload (LOU-D31): `lousho dev <path>` takes a spec file (as before), an agent directory (`loadAgentDir()`) or a `.ts`/`.js` module whose default (or `agent`) export is a `SimpleAgent` or a `createAgent()` options object, detected by path type and extension (a bad path, extension or export fails with `LOUSHO_CONFIG_INVALID` / `LOUSHO_SPEC_UNSUPPORTED_FORMAT`). It watches the directory tree, or the module and its relative imports, and rebuilds the agent about 100 ms after a change with a `?t=<time>` cache-busted import, so edited tools reload; the new agent is built and `ready()` before it replaces the old one, which is then `close()`d. A failed rebuild keeps the last good agent and reports the error in the console, in a banner on the chat page and in the new `GET /dev/status` (`{ kind, path, reloads, error }`). `startDevServer(path, port?, host?, { overrides?, debounceMs? })` gains the options argument. Spec-file behaviour is unchanged; the chat is not streamed or per-tab (LOU-D32). See docs/cli.md#lousho-dev and docs/agent-directories.md#run-it-with-lousho-dev.
|
|
659
|
+
- Multimodal input through the public API (LOU-V12): `agent.send()` / `agent.stream()`, `session.send()` / `stream()`, `t.send()` in evals and `useLoushoAgent().send()` take the new exported `AgentInput` (`string | ContentPart[] | Message[]`): a string or parts become one user message, a `Message[]` is passed through (in a session it is appended to the transcript). Checkpoints, `agent.resume()` and queued input hold the parts like any message. The React user bubble shows the text with an `[image]` / `[file]` marker per non-text part. `SqliteStore` sessions and checkpoints save `Uint8Array` parts as `{ "$bytes": "<base64>" }`, as `FileSessionStore` does. Finishes LOU-V11 (its note that `agent.send()` / `session.send()` take no parts is out of date). See docs/providers.md#multimodal-input.
|
|
660
|
+
- Built-in `ask_question` tool (LOU-X9): `createAgent({ askQuestion: true })` (off by default) or `askQuestionTool()` gives the agent `ask_question({ question, options?, allowFreeText? })`. A call pauses the run through the approval mechanism, so it waits durably like any approval (sessions, `memoryStore()` / `SqliteStore`, a restart between question and answer). The pending record gains optional `kind: 'question'` and `question: { text, options?, allowFreeText? }` (new types `ApprovalKind`, `ApprovalQuestion`; `describeApproval()` and `ASK_QUESTION_TOOL_NAME` are exported), and so do the `approval.requested` event and the React hook's `pendingApproval`. The new `agent.approvals.answer({ id, answer })` (the same as `resolve({ id, approved: true, note: answer })`) continues the run with `{ answer, option? }` as the tool result (`option`: index of the matching option); `approved: false` gives the model a `kind: 'rejected'` error saying the user declined to answer. `useLoushoAgent()` gets `answer(text)`. The `approve` callback may now return a string (`ApproveToolCall` returns `boolean | string`), which approves with that note, so code can answer questions. A tool run after an approval sees the decision's note as `ctx.approval.note` (new optional `ToolExecutionContext.approval`). See docs/approvals.md "Asking the user a question".
|
|
661
|
+
- Agent Forge time travel API (LOU-D45): Agent Forge's `FileCheckpointStore` keeps the bounded per-session checkpoint history (`history()`, `historyLimit` option, `delete({ keepHistory })`) with the same semantics as `LocalStorageCheckpointStore`, checked by the SDK's history contract suite. The runtime control server gains `GET /runs/:id/history` (each step's status, save time, finish reason, tool calls, tokens and cost), `POST /runs/:id/fork` (`{ fromStep, patch?: { toolResult?, appendInput?, businessState? } }`, forks with `AgentExecutor.fork()` and starts the fork as a run of its own) and `GET /runs/compare?a=&b=` (`compareTrajectories()`). Fixed on the way: Agent Forge's chat transcript compiles against multimodal `Message.content` (LOU-V11) again, keeping each message's text. See docs/agent-forge.md#time-travel.
|
|
662
|
+
- Fork and replay (LOU-D44): `AgentExecutor.fork({ sessionId, fromStep, newSessionId?, checkpointStore, patch? })` takes the newest entry of `fromStep` in the session's checkpoint history, applies `patch` (`messages(messages)`, `businessState`, `toolResult: { toolCallId, result }` replaced in place so the transcript stays provider-valid, `appendInput` queued as a user message) and saves it as the `'in-progress'` checkpoint of `newSessionId` (default `<sessionId>.fork-<n>`), leaving the original session untouched; it returns `{ sessionId, step, checkpoint }`, and `execute({ sessionId, checkpointStore, input: [] })` continues the fork. A missing step throws the new `LOUSHO_CHECKPOINT_NOT_FOUND` code. `createAgent()` agents gain `agent.fork(sessionId, { fromStep, newSessionId?, patch? })` over `store.checkpoints` (resume with `agent.resume()`). `compareTrajectories(a, b)` compares two checkpoints or transcripts step by step: each run's steps (text, tool calls and results), the first diverging step and the `lousho eval --drift` diff. New types `ForkOptions`, `ForkPatch`, `ForkResult`, `TrajectoryComparison`, `TrajectoryStep` and `DriftEntry`. See docs/durable-execution.md#fork-and-replay.
|
|
663
|
+
- Multimodal message parts (LOU-V11): `Message.content` is now `string | ContentPart[]`, where a `ContentPart` is `{ type: 'text', text }`, `{ type: 'image', image, mimeType? }` (an `http(s)` URL, a `data:` URL or a `Uint8Array`) or `{ type: 'file', data, mimeType, filename? }` (new types `ContentPart`, `TextContentPart`, `ImageContentPart`, `FileContentPart`). The built-in providers map the parts of `user` messages to the `ai` v4 user-content parts: images go to the model with all four (the `ai` SDK downloads an image URL first for Anthropic and Ollama); file parts become a `[file <name> (<mimeType>) not sent]` text note with a one-time `console.warn`, since the pinned peers (`@ai-sdk/*` 0.0.x, `ollama-ai-provider`) cannot send files (a subclass sets `acceptsFileParts = true` to send them). `system`, `assistant` and `tool` messages are sent as their text. The new `textOf(message)` returns a message's text (the string, or its text parts joined), and is what `estimateTokens()` (each image or file part counts 1,000 tokens), compaction, `recordReplay()` fingerprints (cassette messages gain an optional `attachments` list: each image or file as its URL or a digest of its bytes), `mockModel()`, `MockLLMProvider`, OpenTelemetry content attributes and the React reducer (`ui.send` takes parts too) now read. `FileSessionStore` saves `Uint8Array`s as `{ "$bytes": "<base64>" }` and loads them back. Pass multimodal messages as `AgentExecutor.execute({ input: Message[] })`; `agent.send()` / `session.send()` taking parts is LOU-V12. See docs/providers.md#multimodal-input.
|
|
664
|
+
- Declarative permission policies (LOU-X2): `permissions?: PermissionRule[]` on `createAgent()`, `ExecuteOptions` and `resumeAfterApproval()` options. A rule is `{ tool: string | string[] | RegExp | '*', when?: (args, { toolName, toolCallId, sessionId }) => boolean | Promise<boolean>, action: 'allow' | 'deny' | 'ask', reason? }`; rules are checked in order for each tool call (after argument validation and `preToolCall` hooks, before `needsApproval`) and the first match decides: `allow` runs the call without approval even when `needsApproval` would ask, `deny` gives the model a tool error with the new `kind: 'denied'` (`ToolDeniedError`, plus `reason`) and streams `tool.error`, `ask` pauses for approval (or asks `approve`). No match keeps today's behaviour. New helpers `allow(tools)`, `deny(tools, reason?)`, `ask(tools)` and types `PermissionRule`, `PermissionAction`, `PermissionToolMatcher`, `PermissionContext`, `PermissionDecision`, `PermissionDecisionEntry`, `PermissionOptions`. Audit log: `onPermissionDecision(entry)` gets `{ toolName, toolCallId, decision: 'allow' | 'deny' | 'ask' | 'default', rule?: { index, reason? }, args?, at }` per call (`args` left out under `redactContent`), and streams get the same entry as the new typed `permission.decision` event (`PermissionDecisionEvent`; additive, the event schema version stays 1). Neither is produced unless `permissions` or `onPermissionDecision` is set. Sub-agents inherit the lead's rules (checked before their own) and its `onPermissionDecision`. See docs/approvals.md#permission-policies.
|
|
665
|
+
- Compaction events and the `compaction` option (LOU-W3.2): `agent.stream()`, `session.stream()` and `AgentExecutor.stream()` emit two new typed events whenever the compaction hook compacts a request: `compaction.start` (`strategy`, `tokensBefore`, `contextWindow`, `thresholdTokens`) before the work and `compaction.done` (`strategy`, `tokensBefore`, `tokensAfter`, `prunedToolCallIds`, `summary?`, `error?: { message }`) after it, inside the step and before the model call; `error` is set when the strategy failed and the hook fell back. Additive: the event schema version stays 1. `GenerateHookContext` gains the optional `emit(event)` (set in streamed runs only) that the hook uses. `createAgent({ compaction })` takes `true` (the prune hook with its defaults) or `{ strategy?, thresholdPercent?, contextWindow?, protectedTokens?, summarizer? }`, where `summarizer` (a `'provider/model'` string or an `LLMProvider`) selects `twoPhaseStrategy()` with that model; `summarizer` together with `strategy` is a `ConfigurationError`. `createAgent({ hooks })` (new) takes any `AgentHook[]`, run before the compaction hook. See docs/compaction.md.
|
|
666
|
+
- Connect MCP servers from config (LOU-Z4): `connectMcp(servers, { lazy?, onError?, logger? })` (`@lousho/build-ai-agent/mcp` and the root) connects stdio (`command`/`args`/`env`) and streamable HTTP (`url`/`headers`) servers, lists their tools as `<server>__<tool>` descriptors and returns `{ tools, close(), status() }`. Every server connects up front (listing tools needs a connection); `lazy` (default `true`) reconnects on the next tool call after `close()` or a dropped connection. `onError: 'skip'` leaves a failing server out with a warning instead of rejecting. `createAgent({ mcpServers })` connects them on the new `agent.ready()` or the first `send()` / `stream()` and adds their tools; the new `agent.close()` disconnects them (both are no-ops without `mcpServers`). `specToAgent()` now connects `spec.mcpServers` the same way (closes TODO(LOU-D20.2)); `agent.mcpServers` still lists them. `loadMcpTools()` accepts any `{ listTools, callTool }` (`McpClientLike`). See docs/configuration.md#connect-mcp-servers-mcpservers-connectmcp.
|
|
667
|
+
- Error codes with fixes (LOU-D2): every `SDKError` now has a stable `code` (`LOUSHO_<AREA>_<NAME>`, e.g. `LOUSHO_CONFIG_MISSING_PROVIDER`, `LOUSHO_PEER_MISSING`, `LOUSHO_SPEC_INVALID`, `LOUSHO_SESSION_AWAITING_APPROVAL`), a one-sentence `hint` and a `docs` link to its section of the new docs/errors.md; its message ends with a `[code] hint (docs)` line (`detail` is the message without it), and `toString()` always shows it. `ERROR_CODES` / `ErrorCode` (new exports) are the registry, and a test keeps it, the codes used in `src/` and docs/errors.md in sync. The plain `Error`s thrown by `createAgent()` (no model, `instructions` + `prompt`, `sessionId` without checkpoints), `resolveProvider()` (bad spec, unknown prefix, missing API key, missing peer), `AgentExecutor.execute()` / `stream()` (missing `provider` / `agent` / `input`, approval with no `approvalStore`), `resumeAfterApproval()` (unknown approval), sessions (invalid id, corrupt file, second iteration, no stream runner), `loadSpec()` and `specToAgent()` are now `ConfigurationError` / `ValidationError` / `SDKError` with codes; `MissingPeerDependencyError` extends `SDKError`. Existing message text is unchanged (the help line is appended). `loadSpec()` suggests the field a top-level typo meant (`unknown field 'promt' (did you mean 'prompt'?)`). See docs/errors.md.
|
|
668
|
+
- Checkpoint history (LOU-D43): `memoryStore()`, `SqliteStore` and `LocalStorageCheckpointStore` keep a bounded history per session: every `save()` also appends the record to a ring (`historyLimit` option, default 50, `0` keeps none; the oldest are dropped), and the new optional `CheckpointStore.history(sessionId, { limit? })` returns `{ step, savedAt, status, checkpoint }` entries newest first. `CheckpointStore.delete(sessionId, { keepHistory: true })` keeps the history (by default `delete()` clears it too), `getCheckpointHistory(store, sessionId)` reads it and returns `undefined` for a store without `history()`, and `SqliteStore` gains the `checkpoint_history` table (added when an existing database file is opened; `prune()` removes old entries). New types `CheckpointHistoryEntry`, `CheckpointHistoryOptions`, `CheckpointDeleteOptions` and `MemoryStoreOptions`. `KVCheckpointStore` and Agent Forge's file store keep no history yet (LOU-D43.2). This is the first step of time-travel from a past step. See docs/durable-execution.md#checkpoint-history.
|
|
669
|
+
- `lousho eval --record / --replay / --drift` (LOU-D46): `--record` runs every eval case against its agent's real provider through `recordReplay()` and writes one cassette per case next to the eval file (`__cassettes__/<eval-name>/<case>.json`, the normal cassette format); `--replay` runs every case from its cassette with no network, and a missing cassette fails the case with the `--record` command to run. Plain `lousho eval` with `CI` set replays the cases that have a cassette; without `CI` it is unchanged. `--drift` re-records into a temp directory and compares each case with its committed cassette (ordered tool names, JSON-normalized tool arguments, step count, finish reason; tokens with `--drift-usage`): the summary gets a `Drift:` table and each drifted case a soft `drift` assertion (a JUnit `<system-out>` note), or a gate failure with `--strict`. Existing eval files need no change: while a case runs, `AgentExecutor.execute()` wraps its provider for that case. `EvalResult` gains `cassettes`. Fixed on the way: trajectory and classic eval results now carry their `file` (it was read before the test ran and was always empty). See docs/evals.md#record-replay-and-drift.
|
|
670
|
+
- OpenTelemetry GenAI metrics and cost (LOU-D48): `createOtelTraceExporter()` (`@lousho/build-ai-agent/otel`) now also records the GenAI client metrics `gen_ai.client.token.usage` (histogram, `{token}`, one record per `gen_ai.token.type` `input`/`output`) and `gen_ai.client.operation.duration` (histogram, `s`, for every model call and tool call) through `@opentelemetry/api`'s metrics API: the global `MeterProvider`, or the new `meter` option; `metrics: false` turns them off. Without a registered `MeterProvider` they are discarded by OpenTelemetry's no-op meter. Spans gain `lousho.cost_usd` (estimated USD: per step on `chat` spans, cumulative on `invoke_agent` spans, absent when the price table does not know a model) and `lousho.usage.estimated` on `invoke_agent` spans (it was already on `chat` spans). Any `TraceExporter` sees the cumulative attributes: `withSpan()` sums the cost of finished child spans onto their parent. See docs/observability.md.
|
|
671
|
+
- One `store` option (LOU-D30): `createAgent({ store })` takes an `AgentStore` (`{ sessions?, checkpoints?, approvals? }`, new export) and wires all three from it. `SqliteStore` is one; the new `memoryStore()` returns in-memory sessions, checkpoints and approvals, and docs/sessions.md shows the file-based combination (`FileSessionStore`, `LocalStorageCheckpointStore`, `StorageServiceApprovalStore`). `agent.session({ id })` then keeps its transcript in `store.sessions` and checkpoints every turn in `store.checkpoints`; `store.approvals` is the default `approvalStore`. `SendOptions.sessionId` makes `send()` / `stream()` a durable run checkpointed in `store.checkpoints` (an error without one), so a one-shot durable job needs no `AgentExecutor`, and `agent.approvals.resolve()` keeps checkpointing it. The new `agent.resume(sessionId)` finishes an interrupted `send(..., { sessionId })` run, or else the session's pending turn (as `session.resume()`), and returns `null` when nothing is pending. A session's own `store` / `checkpointStore` and an explicit `approvalStore` still win, part by part. Without `store`, nothing changes.
|
|
672
|
+
- One tool-error shape (LOU-U14): every failed tool call reaches the model as `{ error, toolName, message, kind }` (message capped at 2,000 characters, transcript message flagged `isError`), whichever path failed: `kind` is `'execution'`, `'validation'` (plus `issues`), `'not-found'`, `'rejected'` (plus `note`), `'not-run'`, `'mcp'` or `'sandbox'`. New exports `toolErrorResult()`, `ToolErrorResult`, `ToolErrorKind` and `ToolErrorInput`; `McpToolError` and the sandbox guard's error (named `SandboxRequiredError`, not exported) carry a `toolErrorKind`. `kind` is additive on thrown-error and validation results. The paths that built their own shapes changed, see Changed. See docs/tools.md.
|
|
673
|
+
- Background sub-agents end with the lead run (LOU-Y4.2): when a run ends (any `finishReason`, or an error), its background sub-agents still queued or running are cancelled, and the run resolves once their runs have stopped, so none of their events reaches `onEvent` afterwards. `withSubagentOptions(subagents, { awaitBackgroundOnFinish: true })` waits for them instead (an aborted or failed lead still cancels them). The new `result.backgroundTasks` lists the run's background tasks as `{ taskId, agent, status, elapsedMs, ... }` (absent when it started none). `createAgent({ subagentOptions: { maxConcurrent, awaitBackgroundOnFinish } })` sets the same options (over any attached with `withSubagentOptions()`, without changing the `subagents` value). The executor hook behind it is public: `ExecuteOptions.onRunEnd({ result } | { error })` is called exactly once per `execute()` / `stream()` run, however it ends; an error it throws rejects the run. See docs/sub-agents.md.
|
|
674
|
+
- Durable sessions (LOU-W9): `agent.session({ id, store, checkpointStore })` (or a `store` that carries both, `{ sessions, checkpoints }`, such as a `SqliteStore`) runs each turn with `sessionId: '<id>.turn-<n>'` and that `CheckpointStore`, so it is checkpointed after every model response and tool result. `session.resume()` finishes a turn interrupted by a crash, a failed checkpoint write or a `PropagatingToolError` (also in a new process) without re-running recorded tool calls or model responses, adds it to the transcript as `send()` would and returns its result (`null` when nothing is pending); `session.pending()` reports an unfinished turn and `session.discardPending()` drops it. `send()` / `stream()` resume a pending turn before sending the new message. A turn paused for approval stays in its checkpoint until it finishes: `resume()` / `send()` throw `SessionAwaitingApprovalError`, and `agent.approvals.resolve()` continues it in the session. Sessions without a checkpoint store are unchanged. See docs/sessions.md.
|
|
675
|
+
- Structured output (LOU-V4): `createAgent({ output: zodSchema })` (and `ExecuteOptions.output`) makes the final reply a JSON object validated by the schema. `send()`, `run.result` and `agent.approvals.resolve()` resolve with it as `result.object`, typed `z.output<typeof schema>` from `send()` and `stream()` of `createAgent()` agents (`SimpleAgent`, `AgentRun` and `ExecutionResult` gain a type parameter that defaults to `unknown`); `result.text` keeps the raw JSON. The system prompt gets an `## Output format` section with the schema as JSON Schema, and each model call carries the new `GenerateOptions.responseFormat: { type: 'json', schema? }` hint, which the `ai`-SDK providers map to `experimental_output` JSON mode. The reply is parsed (a code fence is tolerated) and validated; when invalid, one repair step sends the model the issues (it counts against `maxSteps`), and a reply still invalid ends the run with the new `finishReason: 'output-invalid'`, no `object` and `outputError: { message, issues }`. `run.done` gains an optional `object` field (additive: the event schema version stays 1). See docs/structured-output.md.
|
|
676
|
+
- Background sub-agents (LOU-Y4): the `task` tool takes `background: true`, which starts the sub-agent and returns `{ taskId, status: 'running', agent }` at once. Agents with `subagents` also get `agent_status({ taskId? })` (`queued`/`running`/`done`/`failed`/`cancelled`/`awaiting-approval`, plus `elapsedMs`), `agent_await({ taskId | taskIds, timeoutMs? })` (the synchronous `task` result, or `status: 'timeout'`) and `agent_cancel({ taskId })`. `withSubagentOptions(subagents, { maxConcurrent })` bounds how many run at once per lead run (default 3; the rest queue). Background sub-agents inherit the lead run's runtime and `maxSubagentDepth`, and aborting the lead cancels them. Not yet: resuming one that paused for approval (it reports `awaiting-approval` with its `approvalId`). A user tool named `agent_status`, `agent_await` or `agent_cancel` now conflicts with `subagents`, like `task`. See docs/sub-agents.md.
|
|
677
|
+
- React hook (LOU-D15): new `@lousho/build-ai-agent/react` subpath with `useLoushoAgent(source, options?)`. `source` is `{ agent, sessionId? }` (in process, via `agent.stream()` or `agent.session({ id }).stream()`) or `{ url, headers?, fetch? }` (POSTs `{ input }` and reads the SSE or NDJSON event stream). It returns `messages` (text and tool calls with status, args and result), `status` (`idle`/`streaming`/`awaiting-approval`/`error`), `pendingApproval`, `error`, `usage`, `lastEvent`, `send()`, `stop()` (aborts; unmount aborts too), `approve(note?)` and `reject(note?)` (via `agent.approvals.resolve()` in process, or `POST ${approvalsUrl}/${id}` remotely). The framework-neutral `reduceAgentEvents()` reducer and `parseEventStream()` parser are exported from the same subpath. `react` (`^18 || ^19`) is an optional peer dependency. See docs/react.md.
|
|
678
|
+
- Summarizing compaction (LOU-W3): `summarizeStrategy({ model, protectedTokens?, prompt?, maxSummaryTokens? })` replaces the turns before the protected tail with one `[Conversation summary]` user message written by `model` (an `LLMProvider` or a `"provider/model"` spec), keeping system and pinned messages and each assistant turn together with its tool results; if the summary call fails it falls back to pruning and reports `error`. `twoPhaseStrategy()` (the recommended strategy) prunes first and summarizes only when still above the threshold. `pinMessage()` / `isPinned()` and the new optional `Message.metadata` (never sent to providers) mark messages no built-in strategy compacts. `CompactionStrategy.compact()` may now be async, so `compactMessages()` returns a Promise; `CompactionResult` / `CompactionInfo` gain `summary` and `error`, and `CompactionInput` gains `thresholdTokens` and `signal`. A strategy that throws no longer fails the run: the hook reports the `error` to `onCompaction`. See docs/compaction.md.
|
|
679
|
+
- `createAgent({ retry, fallbackModels })` (LOU-V7.2): `retry` (`withRetry()` options, or `false`) retries failed model calls and defaults to `{ maxRetries: 2 }` for models given as `provider/model` strings (or picked from the environment); a `provider` instance is wrapped only when `retry` is set. `fallbackModels` are `provider/model` strings tried in order once the primary model's retries are used up (`withFallback([withRetry(primary), withRetry(fallback), ...])`). `agent.stream()`, `session.stream()` and `AgentExecutor.stream()` report them as two new typed events, `provider.retry` (`attempt`, `maxRetries`, `delayMs`, `error: { message, category? }`, `provider`) and `provider.fallback` (`from`, `to`, `error: { message }`), for any `withRetry()` / `withFallback()` provider. Additive: the event schema version stays 1.
|
|
680
|
+
- Context compaction (LOU-W2): `createCompactionHook()` returns an `AgentHook` that, before a model call estimated above `thresholdPercent` (default 0.9) of the model's context window, replaces tool results older than the newest `protectedTokens` (default 40,000) with a `[pruned: <tool> result, N chars]` marker, in the run's transcript itself. `compactMessages()` compacts a conversation by hand; `CompactionStrategy` / `pruneToolResultsStrategy()` make the strategy pluggable. See docs/compaction.md.
|
|
681
|
+
- `AgentSpec.mcpServers` (LOU-D20): a validated map of MCP servers (stdio `command`/`args`/`env` or HTTP `url`/`headers`) in agent spec files. `loadSpec()` reports a bad entry with its name, `lousho doctor` reads the validated field instead of the raw file, and `specToAgent()` exposes the parsed servers as `agent.mcpServers` (connecting them is TODO(LOU-D20.2)). Existing specs are unaffected.
|
|
682
|
+
- `createAgent()` agents can pause for approval (LOU-D21): a `needsApproval` tool no longer fails the run with "requires approval but no approvalStore". The run pauses (`finishReason: 'awaiting-approval'`) in a per-agent `InMemoryApprovalStore` (or the new `approvalStore` option), and `agent.approvals.list()` / `agent.approvals.resolve({ id, approved, note? })` continue it, in its session if it paused in one. The `approve` option decides calls in code without pausing.
|
|
683
|
+
- Tools own their contract (LOU-D22): `ToolDescriptor` gains optional `inputSchema` (zod) and `execute(args, ctx)`, which are now the canonical fields. `defineTool()` sets both and no longer calls `ai`'s `tool()`. Argument validation, the schema sent to the model, and tool execution read them first and fall back to `tool.parameters` / `tool.execute`. `ToolDescriptor.tool` (the `ai` v4 `Tool`) is now legacy: it is still built by `defineTool()` and still accepted on hand-written descriptors this release, but new code should set `inputSchema` and `execute`.
|
|
684
|
+
- ✅ Complete Phase 5 migration: Security, Storage, Templates, and Utils modules
|
|
685
|
+
- ✅ Security module with encryption, hashing, and quota validation
|
|
686
|
+
- ✅ Storage service with file locking mechanism
|
|
687
|
+
- ✅ Template rendering engine with Jinja2-like syntax
|
|
688
|
+
- ✅ Comprehensive utility functions (errors, formatters, validators)
|
|
689
|
+
- ✅ File extraction and processing utilities
|
|
690
|
+
- ✅ JSON path navigation utilities
|
|
691
|
+
- ✅ Framework-agnostic architecture (zero framework dependencies)
|
|
692
|
+
|
|
693
|
+
### Fixed
|
|
694
|
+
- `lousho --help`, `-h` and `help` print the usage and exit 0 (they were "unknown command" with exit 1) (LOU-D49).
|
|
695
|
+
- Install truth (LOU-U20): the README, installation, CLI, quick start and Agent Forge docs, `lousho init --help` and the message after a failed `lousho init` install now say the package is not on npm yet and point to one section, "Installing before the first release" (docs/installation.md); `npm install github:LinuxDevil/agent-sdk` is documented as not working. Stale "planned / not yet" statements about agent-directory channels, `toolCallId` in sandboxed tools and the Agent Forge Settings tab are corrected.
|
|
696
|
+
- On `ai` 5+ the providers send images as `file` parts with an image `mediaType` (`image/*` when unknown) instead of the deprecated `image` part, which made `ai` 7 log a deprecation warning for every image. `ai` 4 still gets `image` parts.
|
|
697
|
+
- OpenRouter on `@ai-sdk/openai` 2+, and the Ollama base URL (LOU-D28f, behaviour fix): `OpenRouterProvider` now builds its model with `provider.chat(modelId)` (Chat Completions, the only API OpenRouter implements) instead of the bare call, which targets the Responses API from `@ai-sdk/openai` 2 on; no change on `ai` 4. `OllamaProvider` appends `/api` to a base URL that has no path (`http://host:11434` or `http://host:11434/`), the form `ollama-ai-provider` and `ollama-ai-provider-v2` expect, so a configured bare host no longer hits `/chat` outside `/api` (before, `getModels()` hit `<baseURL>/api/tags` but chat requests went to `<baseURL>/chat`); a URL ending in `/api` or with any other path is used as it is, and `getModels()` no longer doubles `/api` for a base URL that has it. `lousho init` (and `create-lousho-agent`) now scaffolds OpenRouter projects on `ai@^7.0.0` with `@ai-sdk/openai@^4.0.0`.
|
|
698
|
+
|
|
699
|
+
### Deprecated
|
|
700
|
+
- `ExecuteOptions.onEvent`, `ExecutionEvent` and `ExecutionEventType` (LOU-D41). `onEvent` keeps working: the executor now emits only `AgentEvent`s, and an adapter derives the old events from them in the old order (`start`, `text-complete`, `tool-call`, `tool-result`, `error`, `abort`, `finish`, sub-agents' tagged with `subagent`), with a one-time `console.warn`. Differences: `finish` now comes after the run's checkpoint is written (it was just before), and a run that fails outside a step (an input guardrail that throws, `onRunEnd` throwing) now gets an `error` event too. Migration: replace `onEvent` with `onAgentEvent` (or `createAgent({ onEvent })`); `start` -> `run.start`, `text-complete` -> `text.done` (step usage on `step.done`), `tool-call` -> `tool.start`, `tool-result` -> `tool.done` / `tool.error`, `abort` / `finish` -> `run.done` (`finishReason`), `error` -> `error` (`{ name, message }`). The full table is in docs/streaming.md#migrating-from-onevent--executionevent.
|
|
701
|
+
|
|
702
|
+
### Removed
|
|
703
|
+
- `ExecuteOptions.streaming` (LOU-D41): it was never read. Use `AgentExecutor.stream()` / `agent.stream()` to stream a run. Passing it in an object literal is now a type error; remove the property.
|
|
704
|
+
- `ToolDescriptor.injectStreamingController` and `defineTool({ injectStreamingController })` (LOU-D41.2, deprecated earlier in this release): no SDK path ever called it, so removing the property changes nothing at runtime. Passing it is now a type error; remove the property.
|
|
705
|
+
- `AgentExecutionOptions.streaming` (LOU-D41.2): it was never read (`AgentExecutionOptions` is a legacy type no SDK API takes). Remove the property; stream a run with `agent.stream()` / `AgentExecutor.stream()`.
|
|
706
|
+
- BREAKING: `validateTokenQuotas()` (`src/security/quotas.ts`) and the SaaS-era types `SaaSContext`, `QuotaConfig`, `UsageStats` and `QuotaValidationResult` (LOU-D36) no longer ship from the package root. Nothing in the SDK called them. No replacement; copy the file into your project if you relied on it.
|
|
707
|
+
- BREAKING: `ConfigManager` and `SDKConfig` (`src/core/ConfigManager.ts`) (LOU-D36) no longer ship from the package root or `./core` (which still exports `AgentBuilder`). Nothing in the SDK used them. No replacement; copy the file into your project if you relied on it.
|
|
708
|
+
- BREAKING: The formatters (LOU-D36) `getCurrentTS()`, `getTS()`, `formatDate()`, `safeJsonParse()`, `removeCodeBlocks()`, `findCodeBlocks()`, `checkApiKey()` and the types `CodeBlock`, `CodeBlockError`, `CodeBlockResult` (`src/utils/formatters.ts`) no longer ship from the package root. No replacement; copy the file into your project if you relied on it.
|
|
709
|
+
- BREAKING: The JSON path helpers `getObjectByPath()` and `setRecursiveNames()` (`src/utils/json-path.ts`) (LOU-D36) no longer ship from the package root. No replacement; copy the file into your project if you relied on it.
|
|
710
|
+
- BREAKING: The file extraction helpers `getMimeType()`, `getFileExtensionFromMimeType()`, `replaceBase64Content()`, `isBinaryData()`, `extractBase64Data()`, `createDataUri()` and `ProcessFilesParams` (`src/utils/file-extractor.ts`) (LOU-D36) no longer ship from the package root. No replacement; copy the file into your project if you relied on it.
|
|
711
|
+
- BREAKING: The flows-ai converters (LOU-D35) are gone: `convertToFlowDefinition()` and `convertFromFlowDefinition()` (`src/flows/converters.ts`) no longer ship from the package root or `./flows`. There is no replacement: the flows-ai engine they targeted was never a dependency of this package and `FlowExecutor` never used them. Run flows with `FlowExecutor` directly.
|
|
712
|
+
- BREAKING: `MemoryManager` and its types `MemoryManagerConfig`, `StoreMemoryOptions`, `RecallMemoryOptions` and `MemorySearchResult` (`src/execution/MemoryManager.ts`) (LOU-D38) no longer ship from the package root. Nothing in the SDK used them (`AgentExecutor`, `createAgent`, `resume` and the flows never called them; only their own test and the `execution/index.ts` re-export). Migrate to `defineMemory()` and `createAgent({ memory })` (memory slots, see docs/memory.md).
|
|
713
|
+
- BREAKING: `ContextBuilder`, `ContextBuilderOptions` and `ExecutionContext` (`src/execution/ContextBuilder.ts`) (LOU-D38) no longer ship from the package root. Nothing in the SDK used them; they truncated by message count only. Migrate to `createAgent({ compaction })` (token-aware compaction, see docs/compaction.md).
|
|
714
|
+
- BREAKING: The `retry()` family (`retry`, `retryOnError`, `retryWithTimeout`, `retryBatch`, `RetryableOperation`, `RetryOptions`, `RetryResult`; `src/execution/retry.ts`) (LOU-D38) no longer ships from the package root. Provider calls retry through the provider wrappers; nothing else in the SDK used it. Migrate to `createAgent({ retry, fallbackModels })`, or copy the file into your project for general-purpose retries.
|
|
715
|
+
- BREAKING: The `data/` module (LOU-D39) is gone: the classes `Agent`, `Session`, `Result`, `Memory`, `Attachment` and the repository interfaces `BaseRepository`, `AgentRepository`, `SessionRepository`, `ResultRepository`, `MemoryRepository`, `AttachmentRepository`, `RepositoryCollection`, `RepositoryFactory`, `PaginationOptions`, `PaginatedResult` no longer ship from the package root. They were donor-product data tables that nothing in the SDK used once `MemoryManager` was removed. No replacement: unused. Use `AgentConfig`, sessions (`agent.session()`) and `defineMemory()` for the SDK's own concepts.
|
|
716
|
+
- BREAKING: `@lousho/build-ai-agent/testing` exports test utilities only (LOU-D39). The in-memory repository mocks `MockAgentRepository`, `MockSessionRepository`, `MockResultRepository`, `MockMemoryRepository` and `MockAttachmentRepository` are removed with the `data/` module. No replacement: unused. `mockModel` and `recordReplay` are unchanged.
|
|
717
|
+
- BREAKING: The template engine (`TemplateManager`, `renderTemplate()` and the types `TemplateFilter`, `TemplateContext`, `TemplateOptions`, `ITemplateManager`; `src/templates`) (LOU-D37) no longer ships from the package root. Nothing in the SDK, `lousho init` or Agent Forge used it (the executor never rendered prompts through it; the scaffold templates are separate files under `src/cli/init`). No replacement: unused; use a template literal or your own renderer.
|
|
718
|
+
- `nanoid` is no longer a dependency (LOU-D35). Generated ids (agent, run, trace, memory ids) now come from `globalThis.crypto.randomUUID()` via an internal `newId()`, so they are UUIDs instead of 21-character nanoids. Nothing public depended on the old format.
|
|
719
|
+
- `finishReason: 'max-steps'` (LOU-U19): a run that exhausts `maxSteps` while the model still wanted to continue (last turn ended in tool calls) now resolves with `finishReason: 'max-steps'` instead of the stale last-turn reason (usually `'tool_calls'`), on `ExecutionResult`, the `finish` event and `run.done`. Resumed runs count `initialSteps` toward the budget. A run that finishes naturally within the budget is unchanged. Code that treated `'tool_calls'` as "hit maxSteps" should check `'max-steps'` instead (the eval `completed()` message and the MCP server's agent tool do).
|
|
720
|
+
|
|
721
|
+
### Docs
|
|
722
|
+
- README revamped into a short front page (LOU-D52); the details it dropped moved to `docs/` (new pages: tools, approvals, providers, cli, flows, guardrails, utilities).
|
|
723
|
+
|
|
724
|
+
### Renamed
|
|
725
|
+
- The project is now **lousho** (it was `loushy`), before the first npm release, so nothing was ever published under the old name. Everything that carried the name changed with it, and this changelog uses the new names throughout, including in older entries: the package `@lousho/build-ai-agent` (was `@loushy/build-ai-agent`), the `lousho` CLI (was `loushy`), `create-lousho-agent` (was `create-loushy-agent`), the exports `useLoushoAgent`, `loushoAgent`, `LoushoAgentSource`, `LoushoUIMessageChunk` and the other `Lousho*` types, every `LOUSHO_*` error code and environment variable (`LOUSHO_MODEL`, `LOUSHO_API_TOKEN`, `LOUSHO_STORE`, ...), the `.lousho/` directory, the `lousho.*` span attributes and the `data-lousho-approval` stream part. Migration for a checkout that used the old name: replace `loushy` with `lousho` (keeping the case) in imports, scripts, environment variables and config, and rename an existing `.loushy/` directory to `.lousho/`.
|
|
726
|
+
|
|
727
|
+
### Published
|
|
728
|
+
- `@lousho/build-ai-agent` and `create-lousho-agent` are on npm. The README and the installation, quick start, CLI and Agent Forge docs no longer say the package is unpublished; "Installing before the first release" is now "Installing from a local build" (docs/installation.md#installing-from-a-local-build) and covers trying an unreleased commit. The hint printed after a failed `lousho init` install no longer mentions a 404; it points to `--sdk-path`. The docs site moved to https://lousho.com.
|