zidane 9.1.1 → 9.1.3
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/dist/{acp-Di9WvMDF.js → acp-CeCavNlg.js} +7 -7
- package/dist/{acp-Di9WvMDF.js.map → acp-CeCavNlg.js.map} +1 -1
- package/dist/acp-cli.js +5 -5
- package/dist/acp.d.ts +2 -2
- package/dist/acp.js +1 -1
- package/dist/{agent-DJhpTS-B.js → agent-Dh_XZ7fj.js} +74 -19
- package/dist/agent-Dh_XZ7fj.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{auth-store-port-DFgf22oo.js → auth-store-port-BKpjlJ8a.js} +2 -2
- package/dist/{auth-store-port-DFgf22oo.js.map → auth-store-port-BKpjlJ8a.js.map} +1 -1
- package/dist/{auto-update-restart-BFB7Ek87.js → auto-update-restart-CQJtndMG.js} +277 -41
- package/dist/auto-update-restart-CQJtndMG.js.map +1 -0
- package/dist/{browser-BcoCCB_n.js → browser-BkdeKnxO.js} +56 -2
- package/dist/browser-BkdeKnxO.js.map +1 -0
- package/dist/{browser-entry-ypi8CyE-.d.ts → browser-entry-BCwruqNa.d.ts} +10 -10
- package/dist/{browser-entry-ypi8CyE-.d.ts.map → browser-entry-BCwruqNa.d.ts.map} +1 -1
- package/dist/channels.d.ts +5 -5
- package/dist/chat/browser.d.ts +4 -4
- package/dist/chat/browser.js +6 -6
- package/dist/chat/pure.d.ts +5 -5
- package/dist/chat/pure.js +8 -8
- package/dist/chat.d.ts +5 -5
- package/dist/chat.js +55 -55
- package/dist/chat.js.map +1 -1
- package/dist/{cli-shared-CabruH8Z.js → cli-shared-DeRHgYvl.js} +4 -4
- package/dist/{cli-shared-CabruH8Z.js.map → cli-shared-DeRHgYvl.js.map} +1 -1
- package/dist/cli-style-DBEm6CyT.js +105 -0
- package/dist/cli-style-DBEm6CyT.js.map +1 -0
- package/dist/{client-core-CIixwznM.d.ts → client-core-C_4m_oL6.d.ts} +4 -4
- package/dist/{client-core-CIixwznM.d.ts.map → client-core-C_4m_oL6.d.ts.map} +1 -1
- package/dist/{client-CCaIXWue.d.ts → client-ygTASGis.d.ts} +2 -2
- package/dist/{client-CCaIXWue.d.ts.map → client-ygTASGis.d.ts.map} +1 -1
- package/dist/codemode-worker.js +8 -5
- package/dist/codemode-worker.js.map +1 -1
- package/dist/codemode.d.ts +16 -0
- package/dist/codemode.d.ts.map +1 -1
- package/dist/codemode.js +2 -2
- package/dist/{connection-loop-QmWL-fJf.d.ts → connection-loop-D_pRONCr.d.ts} +4 -4
- package/dist/{connection-loop-QmWL-fJf.d.ts.map → connection-loop-D_pRONCr.d.ts.map} +1 -1
- package/dist/contexts/daytona.d.ts +3 -3
- package/dist/contexts/docker.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +2 -2
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/{contexts-C72b0mMa.js → contexts-DLZTSRHU.js} +17 -106
- package/dist/contexts-DLZTSRHU.js.map +1 -0
- package/dist/contexts.d.ts +4 -4
- package/dist/contexts.js +2 -2
- package/dist/{credentials-wwETW8rI.js → credentials-Bqg-qNBM.js} +2 -2
- package/dist/{credentials-wwETW8rI.js.map → credentials-Bqg-qNBM.js.map} +1 -1
- package/dist/{cwd-picker-CcekODCt.js → cwd-picker-BsHA6HKv.js} +4 -4
- package/dist/{cwd-picker-CcekODCt.js.map → cwd-picker-BsHA6HKv.js.map} +1 -1
- package/dist/daemon-cli.js +4 -3
- package/dist/daemon-cli.js.map +1 -1
- package/dist/{decisions-CXmK0nz9.js → decisions-hsw5XS2g.js} +3 -1
- package/dist/decisions-hsw5XS2g.js.map +1 -0
- package/dist/{discovery-DycXFxdA.js → discovery-CX7nbMCn.js} +4 -4
- package/dist/{discovery-DycXFxdA.js.map → discovery-CX7nbMCn.js.map} +1 -1
- package/dist/durable/sqlite.d.ts +2 -2
- package/dist/durable.d.ts +1 -1
- package/dist/{engine-VQ86XGdG.js → engine-CxBGgU94.js} +6 -6
- package/dist/{engine-VQ86XGdG.js.map → engine-CxBGgU94.js.map} +1 -1
- package/dist/{eval-Bod0I1W_.js → eval-CfGUDAkN.js} +3 -3
- package/dist/{eval-Bod0I1W_.js.map → eval-CfGUDAkN.js.map} +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/{executor-C7RQrkEq.js → executor-j_LFRBwS.js} +4 -4
- package/dist/{executor-C7RQrkEq.js.map → executor-j_LFRBwS.js.map} +1 -1
- package/dist/{extension-mark-BqKdEZwF.js → extension-mark-_rhkDWL5.js} +9 -6
- package/dist/extension-mark-_rhkDWL5.js.map +1 -0
- package/dist/extensions/testing.d.ts +1 -1
- package/dist/extensions/testing.js +2 -2
- package/dist/{extensions-BCx4QuGB.js → extensions-ADlhBQ_1.js} +8 -8
- package/dist/{extensions-BCx4QuGB.js.map → extensions-ADlhBQ_1.js.map} +1 -1
- package/dist/{extensions-client-D8tEAcMw.d.ts → extensions-client-D4Q2XRKV.d.ts} +31 -5
- package/dist/extensions-client-D4Q2XRKV.d.ts.map +1 -0
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +6 -6
- package/dist/{fold-rules-DvLK8pbf.d.ts → fold-rules-1DpDwG9h.d.ts} +2 -2
- package/dist/{fold-rules-DvLK8pbf.d.ts.map → fold-rules-1DpDwG9h.d.ts.map} +1 -1
- package/dist/{google-Crva32sk.js → google-B-Dhmzzf.js} +2 -2
- package/dist/{google-Crva32sk.js.map → google-B-Dhmzzf.js.map} +1 -1
- package/dist/{headless-CCm-jLLT.js → headless-CQm19BL0.js} +4 -4
- package/dist/{headless-CCm-jLLT.js.map → headless-CQm19BL0.js.map} +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/history-worker.js +1 -1
- package/dist/{host-wO_6GW0y.js → host-C61ntFBC.js} +276 -103
- package/dist/host-C61ntFBC.js.map +1 -0
- package/dist/{host-BHMs9jsN.js → host-DEoEzQal.js} +13 -4
- package/dist/host-DEoEzQal.js.map +1 -0
- package/dist/{host-scoped-settings-CrbcqOkP.js → host-scoped-settings-WBYaCork.js} +2 -2
- package/dist/{host-scoped-settings-CrbcqOkP.js.map → host-scoped-settings-WBYaCork.js.map} +1 -1
- package/dist/{host-signals-Jsjd_-4g.js → host-signals-Cb2TlGtN.js} +5 -5
- package/dist/{host-signals-Jsjd_-4g.js.map → host-signals-Cb2TlGtN.js.map} +1 -1
- package/dist/{host-version-CNAr9pov.js → host-version-BIx4CuBM.js} +2 -2
- package/dist/{host-version-CNAr9pov.js.map → host-version-BIx4CuBM.js.map} +1 -1
- package/dist/{index-C6nnKu32.d.ts → index-B0PLo9jn.d.ts} +4 -4
- package/dist/{index-C6nnKu32.d.ts.map → index-B0PLo9jn.d.ts.map} +1 -1
- package/dist/{index-Djnc8Peh.d.ts → index-De4x-s76.d.ts} +14 -14
- package/dist/{index-Djnc8Peh.d.ts.map → index-De4x-s76.d.ts.map} +1 -1
- package/dist/{index-CGbOWa7y.d.ts → index-WlE93Q6d.d.ts} +411 -3
- package/dist/index-WlE93Q6d.d.ts.map +1 -0
- package/dist/{index-CCf_WeTQ.d.ts → index-f-XWIoYo.d.ts} +2 -2
- package/dist/{index-CCf_WeTQ.d.ts.map → index-f-XWIoYo.d.ts.map} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +17 -17
- package/dist/ipc/browser-types.d.ts +304 -7
- package/dist/ipc/browser-types.d.ts.map +1 -1
- package/dist/ipc/browser.d.ts +4 -4
- package/dist/ipc/browser.js +2 -2
- package/dist/ipc/remote.d.ts +6 -6
- package/dist/ipc/remote.js +3 -3
- package/dist/{ipc-BzWhIybr.js → ipc-DiH_P-wr.js} +4 -4
- package/dist/{ipc-BzWhIybr.js.map → ipc-DiH_P-wr.js.map} +1 -1
- package/dist/ipc.d.ts +10 -10
- package/dist/ipc.js +8 -8
- package/dist/{json-Dj-YATkZ.js → json-BjClr43H.js} +2 -20
- package/dist/json-BjClr43H.js.map +1 -0
- package/dist/{links-CJG_rFvf.js → links-BbC_lOnn.js} +9 -9
- package/dist/{links-CJG_rFvf.js.map → links-BbC_lOnn.js.map} +1 -1
- package/dist/{loaded-entry-DTURJo_R.js → loaded-entry-C0vkCVNQ.js} +2 -2
- package/dist/{loaded-entry-DTURJo_R.js.map → loaded-entry-C0vkCVNQ.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/{media-type-BtA0cbfI.d.ts → media-type-ZSmAGHvg.d.ts} +188 -7
- package/dist/media-type-ZSmAGHvg.d.ts.map +1 -0
- package/dist/{memory-DuVGrzbl.js → memory-C3w8nO8f.js} +132 -31
- package/dist/memory-C3w8nO8f.js.map +1 -0
- package/dist/model-test-CAtJ76kM.js.map +1 -1
- package/dist/{move-session-kylExJ5H.js → move-session-D7ILECs-.js} +4 -4
- package/dist/{move-session-kylExJ5H.js.map → move-session-D7ILECs-.js.map} +1 -1
- package/dist/{nested-call-record-DlzPeTB7.js → nested-call-record-OayszZBQ.js} +4 -3
- package/dist/nested-call-record-OayszZBQ.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/{path-display-COVV71Yu.js → path-display-DRAEpKOU.js} +3 -3
- package/dist/{path-display-COVV71Yu.js.map → path-display-DRAEpKOU.js.map} +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/{prelude-CVKoUzV8.js → prelude-BwiSR1uA.js} +16 -5
- package/dist/prelude-BwiSR1uA.js.map +1 -0
- package/dist/{presets-CBt6rFfS.js → presets-CMY43nUP.js} +3 -3
- package/dist/{presets-CBt6rFfS.js.map → presets-CMY43nUP.js.map} +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/{protocol-CEVqBO-q.d.ts → protocol-C2lqKuMd.d.ts} +50 -7
- package/dist/protocol-C2lqKuMd.d.ts.map +1 -0
- package/dist/protocol-DOGyHgLQ.js.map +1 -1
- package/dist/protocol-NSllElLw.js.map +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.js +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/neuralwatt.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/{providers-Z0TaBp1O.js → providers-CpXV_uLE.js} +5 -5
- package/dist/providers-CpXV_uLE.js.map +1 -0
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/{readonly-db-D7Z-k0pQ.js → readonly-db-DrbT8tX6.js} +2 -2
- package/dist/{readonly-db-D7Z-k0pQ.js.map → readonly-db-DrbT8tX6.js.map} +1 -1
- package/dist/{registry-BKjeBnoy.js → registry-CJHvmvaP.js} +2 -2
- package/dist/{registry-BKjeBnoy.js.map → registry-CJHvmvaP.js.map} +1 -1
- package/dist/{registry-B6Ww76iW.js → registry-CN6JlZKj.js} +2 -2
- package/dist/{registry-B6Ww76iW.js.map → registry-CN6JlZKj.js.map} +1 -1
- package/dist/{registry-B9INJwJR.js → registry-CnnKaY9s.js} +324 -39
- package/dist/registry-CnnKaY9s.js.map +1 -0
- package/dist/{remote-hosts-BMWoHCWO.js → remote-hosts-DoO9CJUQ.js} +4 -4
- package/dist/{remote-hosts-BMWoHCWO.js.map → remote-hosts-DoO9CJUQ.js.map} +1 -1
- package/dist/{repo-url-D4Dr8NfZ.js → repo-url-DLjjuVk_.js} +169 -2
- package/dist/repo-url-DLjjuVk_.js.map +1 -0
- package/dist/restate.d.ts +2 -2
- package/dist/{sandbox-BwiPGKF7.d.ts → sandbox-B4OW6oNx.d.ts} +2 -2
- package/dist/{sandbox-BwiPGKF7.d.ts.map → sandbox-B4OW6oNx.d.ts.map} +1 -1
- package/dist/script-progress-CrZ507Qq.js +900 -0
- package/dist/script-progress-CrZ507Qq.js.map +1 -0
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/{setting-BQBSyUq0.js → setting-D_FpKFMw.js} +6 -6
- package/dist/{setting-BQBSyUq0.js.map → setting-D_FpKFMw.js.map} +1 -1
- package/dist/{settings-catalog-4IU2i_jG.js → settings-catalog-DEZlXP51.js} +14 -2
- package/dist/settings-catalog-DEZlXP51.js.map +1 -0
- package/dist/{settings-keys-iI_Y-7MC.js → settings-keys-CW2UgHgb.js} +3 -3
- package/dist/{settings-keys-iI_Y-7MC.js.map → settings-keys-CW2UgHgb.js.map} +1 -1
- package/dist/{skills-CFXRItJ6.js → skills-CEbosUlM.js} +2 -2
- package/dist/{skills-CFXRItJ6.js.map → skills-CEbosUlM.js.map} +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -1
- package/dist/{source-Bl1Z501r.js → source-3ZnEPOSa.js} +3 -2
- package/dist/source-3ZnEPOSa.js.map +1 -0
- package/dist/{sqlite-runtime-BZ2L4RaG.d.ts → sqlite-runtime-CS_LBaAN.d.ts} +2 -2
- package/dist/sqlite-runtime-CS_LBaAN.d.ts.map +1 -0
- package/dist/{src-BY_ND2xn.js → src-BmTXDta8.js} +2 -2
- package/dist/{src-BY_ND2xn.js.map → src-BmTXDta8.js.map} +1 -1
- package/dist/{store-C6gI7FfZ.js → store-ByCDaKgJ.js} +14 -3
- package/dist/store-ByCDaKgJ.js.map +1 -0
- package/dist/{system-freeze-m5o1sGIM.js → system-freeze-XgDPIwQS.js} +3 -3
- package/dist/{system-freeze-m5o1sGIM.js.map → system-freeze-XgDPIwQS.js.map} +1 -1
- package/dist/{todo-edits-CbukbjyV.js → todo-edits-Dyj32ds6.js} +4 -3
- package/dist/todo-edits-Dyj32ds6.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/{tools-CVTGhggj.js → tools-BsrGQ8K4.js} +2 -2
- package/dist/{tools-CVTGhggj.js.map → tools-BsrGQ8K4.js.map} +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +3 -3
- package/dist/{transcript-anchors-CiSi_UBn.js → transcript-anchors-DMjvyUjk.js} +3 -3
- package/dist/{transcript-anchors-CiSi_UBn.js.map → transcript-anchors-DMjvyUjk.js.map} +1 -1
- package/dist/{transcript-reconcile-jbSBjLq2.js → transcript-reconcile-DpQO85wl.js} +3 -3
- package/dist/{transcript-reconcile-jbSBjLq2.js.map → transcript-reconcile-DpQO85wl.js.map} +1 -1
- package/dist/tui.d.ts +9 -9
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +289 -38
- package/dist/tui.js.map +1 -1
- package/dist/{types-bDQagoiL.d.ts → types-2HrgolQz.d.ts} +20 -8
- package/dist/types-2HrgolQz.d.ts.map +1 -0
- package/dist/types-4q1bFG53.js +122 -0
- package/dist/types-4q1bFG53.js.map +1 -0
- package/dist/{types-DnRd9_cA.d.ts → types-SXqk6hLD.d.ts} +3 -3
- package/dist/{types-DnRd9_cA.d.ts.map → types-SXqk6hLD.d.ts.map} +1 -1
- package/dist/types-myxheQkw.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/{usage-summary-BfcXjiSM.js → usage-summary-BM7B3JdI.js} +2 -2
- package/dist/{usage-summary-BfcXjiSM.js.map → usage-summary-BM7B3JdI.js.map} +1 -1
- package/dist/{version-DqqqvHDP.js → version-szdW1Od9.js} +2 -2
- package/dist/{version-DqqqvHDP.js.map → version-szdW1Od9.js.map} +1 -1
- package/dist/{wait-task-BX4NcOOm.js → wait-task-BiI5UaZQ.js} +13 -3
- package/dist/wait-task-BiI5UaZQ.js.map +1 -0
- package/dist/{wire-CLm2rMK0.js → wire-C3l9jl9x.js} +2 -2
- package/dist/{wire-CLm2rMK0.js.map → wire-C3l9jl9x.js.map} +1 -1
- package/dist/{write-file-0E6TUJ9A.js → write-file-Dquu7MzB.js} +5 -3
- package/dist/write-file-Dquu7MzB.js.map +1 -0
- package/dist/{ws-DfGGYQ4W.js → ws-CEHi0CiB.js} +4 -4
- package/dist/{ws-DfGGYQ4W.js.map → ws-CEHi0CiB.js.map} +1 -1
- package/dist/{ws-client-CFMbhsNq.js → ws-client-t8XNWxcq.js} +2 -2
- package/dist/{ws-client-CFMbhsNq.js.map → ws-client-t8XNWxcq.js.map} +1 -1
- package/docs/CHAT.md +55 -0
- package/docs/CODEMODE.md +37 -0
- package/docs/EXTENSIONS.md +8 -6
- package/docs/RELEASE-NOTES-NEXT.md +48 -1
- package/docs/RELEASE.md +9 -1
- package/package.json +2 -3
- package/dist/agent-DJhpTS-B.js.map +0 -1
- package/dist/auto-update-restart-BFB7Ek87.js.map +0 -1
- package/dist/browser-BcoCCB_n.js.map +0 -1
- package/dist/contexts-C72b0mMa.js.map +0 -1
- package/dist/decisions-CXmK0nz9.js.map +0 -1
- package/dist/extension-mark-BqKdEZwF.js.map +0 -1
- package/dist/extensions-client-D8tEAcMw.d.ts.map +0 -1
- package/dist/host-BHMs9jsN.js.map +0 -1
- package/dist/host-wO_6GW0y.js.map +0 -1
- package/dist/index-CGbOWa7y.d.ts.map +0 -1
- package/dist/json-Dj-YATkZ.js.map +0 -1
- package/dist/media-type-BtA0cbfI.d.ts.map +0 -1
- package/dist/memory-DuVGrzbl.js.map +0 -1
- package/dist/nested-call-events-BYk8XvGX.js +0 -182
- package/dist/nested-call-events-BYk8XvGX.js.map +0 -1
- package/dist/nested-call-record-DlzPeTB7.js.map +0 -1
- package/dist/prelude-CVKoUzV8.js.map +0 -1
- package/dist/protocol-CEVqBO-q.d.ts.map +0 -1
- package/dist/providers-Z0TaBp1O.js.map +0 -1
- package/dist/registry-B9INJwJR.js.map +0 -1
- package/dist/repo-url-D4Dr8NfZ.js.map +0 -1
- package/dist/settings-catalog-4IU2i_jG.js.map +0 -1
- package/dist/source-Bl1Z501r.js.map +0 -1
- package/dist/sqlite-runtime-BZ2L4RaG.d.ts.map +0 -1
- package/dist/store-C6gI7FfZ.js.map +0 -1
- package/dist/todo-edits-CbukbjyV.js.map +0 -1
- package/dist/types-DTMEhKJO.js +0 -14
- package/dist/types-DTMEhKJO.js.map +0 -1
- package/dist/types-bDQagoiL.d.ts.map +0 -1
- package/dist/wait-task-BX4NcOOm.js.map +0 -1
- package/dist/write-file-0E6TUJ9A.js.map +0 -1
- package/scripts/eval.ts +0 -179
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-freeze-m5o1sGIM.js","names":[],"sources":["../src/extension-authoring.ts","../src/system-freeze.ts"],"sourcesContent":["/**\n * Host glue for the \"edit / create extension\" and \"edit skill\" authoring\n * flows (TUI + GUI).\n *\n * Pure, renderer-neutral helpers consumed by the hosts — deliberately kept OUT\n * of `src/extensions/**` (the extension loader / registry core) so the\n * authoring UX can never entangle with discovery / registration. Imports only\n * the skills lib and node builtins.\n *\n * - {@link extensionScaffold} — the `index.ts` of a new extension;\n * {@link extensionScaffoldFiles} adds `ui.tsx` / `tui.tsx`, a test and\n * package files; {@link writeExtensionScaffold} writes them;\n * {@link createExtension} writes a new extension where zidane discovers it\n * (the TUI, desktop and daemon create flows; `zidane extension create`).\n * - {@link resolveExtensionDocsDir} — locate the shipped `docs/` directory.\n * - {@link buildZidaneExtensionSkill} — the inline extension docs skill.\n * - {@link buildZidaneSkillEditSkill} — the inline skill-editing skill.\n * - {@link withZidaneExtensionSkill} / {@link withZidaneSkillEditSkill}\n * — inject either into a `SkillsConfig`.\n * - {@link editSessionMetadata} — the ONE canonical \"open an editing session\n * with preset context\" seed both hosts stamp at session creation.\n * - {@link EXTENSION_EDIT_METADATA_KEY} / {@link SKILL_EDIT_METADATA_KEY} /\n * {@link ZIDANE_EXTENSION_SKILL_NAME} / {@link ZIDANE_SKILL_EDIT_SKILL_NAME}\n * — constants shared by both hosts.\n */\nimport type { SkillConfig, SkillsConfig } from './skills'\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { zidaneRangeFor } from './extensions/host-version'\nimport { defineSkill } from './skills'\n\n/**\n * Session-metadata key marking an \"edit / create extension\" session. The value\n * is an {@link ExtensionEditMarker}. Both hosts stamp it at session creation so\n * the agent build can scope the docs skill to exactly these sessions (and it\n * survives reload because metadata is persisted).\n */\nexport const EXTENSION_EDIT_METADATA_KEY = 'zidane.extensionEdit'\n\n/** Name of the inline docs skill auto-activated in extension edit sessions. */\nexport const ZIDANE_EXTENSION_SKILL_NAME = 'zidane-extension'\n\n/**\n * Session-metadata key marking an \"edit skill\" session — the skill-editing\n * counterpart of {@link EXTENSION_EDIT_METADATA_KEY}. The value is a\n * {@link SkillEditMarker}. Stamped at session creation by both hosts (via\n * {@link editSessionMetadata}); persisted metadata means it survives reload.\n */\nexport const SKILL_EDIT_METADATA_KEY = 'zidane.skillEdit'\n\n/** Name of the inline preset skill auto-activated in skill edit sessions. */\nexport const ZIDANE_SKILL_EDIT_SKILL_NAME = 'zidane-skill'\n\n/** Per-edit-session marker persisted on `session.metadata[EXTENSION_EDIT_METADATA_KEY]`. */\nexport interface ExtensionEditMarker {\n /** Absolute path to the extension's directory (the session's cwd). */\n dir: string\n}\n\n/** Per-edit-session marker persisted on `session.metadata[SKILL_EDIT_METADATA_KEY]`. */\nexport interface SkillEditMarker {\n /** Absolute path to the skill's directory (the session's cwd). */\n dir: string\n /** The skill's name (frontmatter-first, as discovered). */\n name: string\n}\n\n/**\n * What an editing session targets. `dir` becomes the session's cwd; `name`\n * feeds the preset wording (and the skill marker).\n */\nexport interface EditSessionTarget {\n /** Whether the session edits an extension or a skill. */\n kind: 'extension' | 'skill'\n /** Name of the extension or skill. */\n name: string\n /** Absolute path of the extension or skill directory. */\n dir: string\n}\n\n/**\n * THE canonical seed for \"open an editing session with preset context\" —\n * the session metadata both hosts stamp at creation time. The engines read\n * the marker back and inject + auto-activate the matching inline preset\n * skill ({@link buildZidaneExtensionSkill} / {@link buildZidaneSkillEditSkill}),\n * so the preset rides as real conversation context from turn one.\n *\n * @param target - The extension or skill the editing session targets.\n */\nexport function editSessionMetadata(target: EditSessionTarget): Record<string, unknown> {\n return target.kind === 'extension'\n ? { [EXTENSION_EDIT_METADATA_KEY]: { dir: target.dir } satisfies ExtensionEditMarker }\n : { [SKILL_EDIT_METADATA_KEY]: { dir: target.dir, name: target.name } satisfies SkillEditMarker }\n}\n\n/**\n * Parse a persisted {@link SkillEditMarker} back out of session metadata.\n * Tolerant: anything that isn't `{ dir: string, name: string }` → `null`\n * (older sessions, manual metadata edits, type drift).\n *\n * @param raw - Value read from `session.metadata`, of unknown shape.\n */\nexport function readSkillEditMarker(raw: unknown): SkillEditMarker | null {\n if (typeof raw !== 'object' || raw === null)\n return null\n const { dir, name } = raw as Record<string, unknown>\n if (typeof dir !== 'string' || dir.length === 0 || typeof name !== 'string' || name.length === 0)\n return null\n return { dir, name }\n}\n\n/** Which optional files a scaffold adds next to `index.ts`. */\nexport interface ExtensionScaffoldOptions {\n /** Add `ui.tsx` (desktop and web components) and a status slot drawn by it. */\n ui?: boolean\n /** Add `tui.tsx` (terminal components) and a status slot drawn by it. */\n tui?: boolean\n /** Add `index.test.ts`, a test on `createTestHost`. */\n test?: boolean\n /** Add `package.json`, `tsconfig.json` and `README.md`, so the folder is a publishable package. */\n package?: boolean\n}\n\n/**\n * The `index.ts` of a new extension: a setting and one command (palette\n * entry and slash command) that toasts it. With `ui` / `tui`, a status slot\n * whose `Status` component comes from the matching entry. The `id` MUST equal\n * the folder name, or discovery refuses the extension.\n *\n * @param name - Extension id. Must equal the extension's folder name.\n * @param opts - Whether the extension has a `ui` and / or a `tui` entry.\n */\nexport function extensionScaffold(name: string, opts: Pick<ExtensionScaffoldOptions, 'ui' | 'tui'> = {}): string {\n const withSlot = opts.ui === true || opts.tui === true\n const imports = withSlot ? 'defineExtension, Slot' : 'defineExtension'\n const slot = withSlot\n ? `\n // A statusbar item. The desktop and web apps draw \\`Status\\` from ui.tsx, the\n // terminal from tui.tsx; a host without the component draws \\`text\\`.\n ctx.add(Slot, { at: 'app.statusbar', text: '${name}', component: { component: 'Status', props: { label: '${name}' } } })\n`\n : ''\n return `import { ${imports} } from 'zidane'\n\n/**\n * ${name}: a zidane extension.\n *\n * Everything the extension adds is registered on \\`ctx\\` inside \\`setup\\`, by\n * domain: ctx.tools, ctx.hooks, ctx.commands, ctx.storage, ctx.ui, ctx.add(…)…\n * Every registration returns a Disposable and is withdrawn when the extension\n * unloads. Authoring reference: the \\`${ZIDANE_EXTENSION_SKILL_NAME}\\` skill, active\n * whenever you open this folder in zidane.\n */\nexport default defineExtension({\n apiVersion: 1,\n // Must equal the folder name.\n id: '${name}',\n version: '0.1.0',\n description: 'A zidane extension.',\n // Shown in Settings, even while the extension is off.\n settings: {\n items: {\n greeting: { type: 'string', label: 'Greeting', default: 'Hello' },\n },\n },\n setup(ctx) {\n // A palette entry and a \\`/${name}-hello\\` slash command in one registration.\n ctx.commands.register({\n id: 'hello',\n title: 'Say hello',\n slash: { name: '${name}-hello' },\n run: async () => {\n await ctx.ui.toast({ text: \\`\\${ctx.settings.get('greeting') ?? 'Hello'} from ${name}\\` })\n },\n })\n${slot} },\n})\n`\n}\n\n/** The `index.test.ts` of a new extension: loads it in a test host and runs its command. */\nfunction extensionTestScaffold(name: string): string {\n return `import { afterEach, expect, it } from 'bun:test'\nimport { createTestHost } from 'zidane/extensions/testing'\nimport extension from './index'\n\n// \\`createTestHost\\` runs the extension against in-memory ports: no terminal,\n// window or disk. Run with \\`zidane extension test\\`.\nlet host: Awaited<ReturnType<typeof createTestHost>> | undefined\nafterEach(async () => {\n await host?.dispose()\n host = undefined\n})\n\nit('loads and says hello', async () => {\n host = await createTestHost({ extensions: [extension], settings: { '${name}': { greeting: 'Hi' } } })\n expect(host.extensions().map(e => e.status)).toEqual(['active'])\n\n expect(await host.commands.run('${name}.hello')).toBe(true)\n expect(host.dialogs.shown.map(d => d.request)).toContainEqual(expect.objectContaining({ kind: 'toast', text: 'Hi from ${name}' }))\n})\n`\n}\n\nfunction uiScaffold(name: string): string {\n return `/**\n * ${name}'s components for the desktop and web apps. React and the kit are the\n * app's own (host modules): import them, never bundle your own copy. Reach\n * the backend through useRpc and useStore only.\n */\nimport { Meta, useExtension } from 'zidane-ui/extensions/kit'\n\n/** The statusbar item (\\`component: 'Status'\\` in index.ts). */\nexport function Status(props: { label?: string }) {\n const extension = useExtension()\n return <Meta title={extension.id}>{props.label ?? extension.id}</Meta>\n}\n`\n}\n\nfunction tuiScaffold(name: string): string {\n return `/** @jsxImportSource @opentui/react */\n/**\n * ${name}'s components for the terminal app. \\`react\\`, \\`@opentui/*\\` and\n * \\`zidane/tui\\` resolve to the app's live modules.\n */\nimport { Badge } from 'zidane/tui'\n\n/** The statusbar item (\\`component: 'Status'\\` in index.ts). */\nexport function Status(props: { label?: string }) {\n return <Badge tone=\"brand\">{props.label ?? '${name}'}</Badge>\n}\n`\n}\n\nfunction packageScaffold(name: string, opts: ExtensionScaffoldOptions): string {\n const files = ['index.ts', ...(opts.ui ? ['ui.tsx'] : []), ...(opts.tui ? ['tui.tsx'] : []), 'README.md']\n return `${JSON.stringify({\n name,\n version: '0.1.0',\n description: 'A zidane extension.',\n type: 'module',\n keywords: ['zidane', 'zidane-extension'],\n main: './index.ts',\n files,\n scripts: { 'test': 'zidane extension test', 'pack': 'zidane extension pack', 'publish:extension': 'zidane extension publish' },\n peerDependencies: { zidane: zidaneRangeFor() },\n }, null, 2)}\\n`\n}\n\nconst TSCONFIG_SCAFFOLD = `${JSON.stringify({\n compilerOptions: {\n target: 'ESNext',\n module: 'ESNext',\n moduleResolution: 'bundler',\n jsx: 'react-jsx',\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n allowImportingTsExtensions: true,\n },\n include: ['.'],\n}, null, 2)}\\n`\n\nfunction readmeScaffold(name: string): string {\n return `# ${name}\n\nA zidane extension.\n\n- \\`zidane extension test\\` runs the tests (\\`createTestHost\\` comes from \\`zidane/extensions/testing\\`).\n- \\`zidane extension pack\\` checks the definition, bundles \\`ui.tsx\\` and writes a folder (and, with \\`--tarball\\`, a tarball) ready to install.\n- \\`zidane extension publish\\` publishes to npm after the same checks.\n\nInstall from GitHub (\\`owner/repo\\`) or npm (\\`${name}\\`) in Settings → Extensions.\n`\n}\n\n/**\n * Every file of a new extension, by path relative to its folder.\n *\n * @param name - Extension id; the folder name.\n * @param opts - Which optional files to add.\n */\nexport function extensionScaffoldFiles(name: string, opts: ExtensionScaffoldOptions = {}): Record<string, string> {\n const files: Record<string, string> = { 'index.ts': extensionScaffold(name, opts) }\n if (opts.ui)\n files['ui.tsx'] = uiScaffold(name)\n if (opts.tui)\n files['tui.tsx'] = tuiScaffold(name)\n if (opts.test)\n files['index.test.ts'] = extensionTestScaffold(name)\n if (opts.package) {\n files['package.json'] = packageScaffold(name, opts)\n files['tsconfig.json'] = TSCONFIG_SCAFFOLD\n files['README.md'] = readmeScaffold(name)\n }\n return files\n}\n\n/**\n * Writes a new extension into `dir` (created; it must not exist yet).\n *\n * @param dir - The extension folder. Its name should equal `name`.\n * @param name - Extension id.\n * @param opts - Which optional files to add.\n * @returns The paths written, relative to `dir`.\n */\nexport function writeExtensionScaffold(dir: string, name: string, opts: ExtensionScaffoldOptions = {}): string[] {\n const error = validateExtensionName(name)\n if (error)\n throw new Error(error)\n if (existsSync(dir))\n throw new Error(`An extension already exists at ${dir}.`)\n mkdirSync(dir, { recursive: true })\n const files = extensionScaffoldFiles(name, opts)\n for (const [file, content] of Object.entries(files))\n writeFileSync(join(dir, file), content, 'utf8')\n return Object.keys(files)\n}\n\n/**\n * Id contract of an extension, which is also its folder name: lowercase\n * words of letters and digits joined by single hyphens, starting with a\n * letter (the loader's `EXTENSION_ID_PATTERN`).\n */\nconst EXTENSION_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/\n\n/**\n * Why a proposed extension name is refused, or `null` when it is fine.\n *\n * @param name - The name as typed (trimmed here).\n */\nexport function validateExtensionName(name: string): string | null {\n const trimmed = name.trim()\n if (trimmed.length === 0)\n return 'Extension name is required.'\n if (!EXTENSION_NAME_PATTERN.test(trimmed))\n return 'Use lowercase letters, numbers, and single hyphens, starting with a letter (e.g. my-extension).'\n return null\n}\n\n/** A new extension to scaffold. */\nexport interface CreateExtensionInput extends ExtensionScaffoldOptions {\n /** Lowercase words joined by hyphens, starting with a letter: the folder name and the extension `id`. */\n name: string\n /** `user` (default): `<userDir>/extensions/<name>`. `project`: `<checkout>/<prefix>/extensions/<name>` of `cwd`. */\n scope?: 'user' | 'project'\n /** The folder a project extension goes with. */\n cwd?: string\n}\n\n/** A scaffolded extension. */\nexport interface CreateExtensionResult {\n /** Its folder (the cwd of the session that writes it). */\n dir: string\n /** Its name (the folder's). */\n name: string\n /** A project extension: it stays unloaded until the project is trusted. */\n needsTrust?: boolean\n}\n\n/** Where {@link createExtension} writes. */\nexport interface CreateExtensionDeps {\n /** The folder `user` extensions are discovered under (`~/.zidane`). */\n userDir: string\n /** A cwd's checkout root (the working tree it sits in, or the folder itself): a project extension is a file of that checkout. */\n resolveCheckoutRoot: (cwd: string) => string\n}\n\n/**\n * The folder {@link createExtension} writes a new extension to.\n *\n * @param input - The name, scope and folder.\n * @param deps - Where user and project extensions go.\n */\nexport function extensionTargetDir(input: Pick<CreateExtensionInput, 'name' | 'scope' | 'cwd'>, deps: CreateExtensionDeps): string {\n const name = input.name.trim()\n // The prefix extension discovery reads (`ZIDANE_PREFIX`).\n return input.scope === 'project'\n ? join(deps.resolveCheckoutRoot(input.cwd ?? process.cwd()), process.env.ZIDANE_PREFIX?.trim() || '.zidane', 'extensions', name)\n : join(deps.userDir, 'extensions', name)\n}\n\n/**\n * Scaffolds a new extension folder: `index.ts`, plus the optional files\n * `input` asks for. Throws when the name is invalid or the folder exists.\n *\n * @param input - The name, scope, folder and optional files.\n * @param deps - Where user and project extensions go.\n */\nexport function createExtension(input: CreateExtensionInput, deps: CreateExtensionDeps): CreateExtensionResult {\n const name = (input.name ?? '').trim()\n const error = validateExtensionName(name)\n if (error)\n throw new Error(error)\n const project = input.scope === 'project'\n const dir = extensionTargetDir({ ...input, name }, deps)\n writeExtensionScaffold(dir, name, input)\n return { dir, name, ...(project ? { needsTrust: true } : {}) }\n}\n\n/**\n * Locate the shipped `docs/` directory (`docs/EXTENSIONS.md` + siblings).\n *\n * The docs ship via `package.json` `files`, so an installed consumer has\n * `<root>/node_modules/zidane/docs` and the repo itself has `<repo>/docs`.\n * Returns the first existing candidate, or `null` (the skill then falls back to\n * the literal `node_modules/zidane/docs` hint, which the model's file tools can\n * still attempt from cwd).\n *\n * @param cwd - Directory used for the `node_modules/zidane/docs` fallback lookup. Defaults to `process.cwd()`.\n */\nexport function resolveExtensionDocsDir(cwd: string = process.cwd()): string | null {\n const candidates: string[] = []\n\n // 1) The installed (or self-linked) zidane package, via its package.json\n // (exposed through `exports[\"./package.json\"]`).\n try {\n const req = createRequire(import.meta.url)\n candidates.push(join(dirname(req.resolve('zidane/package.json')), 'docs'))\n }\n catch {\n // No resolvable package (compiled binary / odd layout) — fall through.\n }\n\n // 2) Relative to this module: the bundled chunk in `dist/` -> `../docs`, or\n // walking up from `src/` when running from source.\n try {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 6; i++) {\n if (existsSync(join(dir, 'docs', 'EXTENSIONS.md'))) {\n candidates.push(join(dir, 'docs'))\n break\n }\n dir = dirname(dir)\n }\n }\n catch {\n // `import.meta.url` is virtual under `bun build --compile` — fall through.\n }\n\n // 3) cwd-relative — the project the author is actually editing.\n candidates.push(join(cwd, 'node_modules', 'zidane', 'docs'))\n\n for (const candidate of candidates) {\n if (existsSync(join(candidate, 'EXTENSIONS.md')))\n return candidate\n }\n return null\n}\n\n/** Entry-file probe order for an extension dir (mirrors the loader's discovery). */\nconst EXTENSION_ENTRY_FILES = ['index.ts', 'index.tsx', 'index.mjs', 'index.js'] as const\n\n/**\n * If `cwd` sits inside a zidane extension, return that extension's root dir,\n * else `null`. Mirrors the discovery layout\n * `<scope>/.{agents,zidane}/extensions/<name>/` — the root is the `<name>` dir\n * (parent `extensions`, grandparent `.agents` / `.zidane`) holding an entry\n * file. Walks up from `cwd`, so the nearest enclosing extension wins. Lets the\n * hosts surface the authoring skill for a plain `zidane` launched from an\n * extension folder, not just sessions opened through the edit/create flow.\n *\n * @param cwd - Directory to start the upward walk from. Defaults to `process.cwd()`.\n */\nexport function extensionDirForCwd(cwd: string = process.cwd()): string | null {\n let dir = resolve(cwd)\n // Bounded walk to the filesystem root (the `parent === dir` guard is the real\n // terminator; the cap is just belt-and-braces against pathological inputs).\n for (let i = 0; i < 64; i++) {\n const parent = dirname(dir)\n if (\n basename(parent) === 'extensions'\n && (basename(dirname(parent)) === '.agents' || basename(dirname(parent)) === '.zidane')\n && EXTENSION_ENTRY_FILES.some(f => existsSync(join(dir, f)))\n ) {\n return dir\n }\n if (parent === dir)\n break\n dir = parent\n }\n return null\n}\n\n/**\n * The authoring reference embedded into the skill body. Kept INLINE (not a\n * filesystem path) on purpose: a `bun build --compile` TUI binary has no\n * resolvable `node_modules/zidane/docs`, no real `import.meta.url`, and the\n * edited extension's own cwd has no `node_modules`, so a path reference dead-\n * ends there. The model gets the API verbatim on activation regardless of host.\n *\n * Mirrors the extension API (`src/extensions/api/*.ts`). `test/extension-authoring.test.ts`\n * fails when a ctx member is added there and not named here.\n */\nconst EXTENSION_AUTHORING_CHEATSHEET = `## The extension\n\nA zidane extension is a folder \\`<scope>/.{agents,zidane}/extensions/<id>/\\` whose \\`index.{ts,tsx,mjs,js}\\`\ndefault-exports a definition. It runs in-process in every host (terminal, desktop, web server, daemon).\n\n\\`\\`\\`ts\nimport { defineExtension } from 'zidane'\n\nexport default defineExtension({\n apiVersion: 1, // required\n id: 'my-ext', // MUST equal the folder name; lowercase words joined by '-', starting with a letter\n displayName: 'My ext', // optional, shown in Settings\n icon: { gui: 'i-ph-puzzle-piece', tui: '◆' }, // optional; settings sidebar icon (gui: a Phosphor icon class); default: a letter badge\n version: '0.1.0', // optional semver\n description: 'what it does',\n dependencies: { other: '^1.0.0' }, // load order + install hints; a missing one never fails the load\n settings: { items: { greeting: { type: 'string', label: 'Greeting', default: 'Hello' } } },\n ui: 'ui.tsx', // optional; default ui.tsx when present (desktop + web components)\n tui: 'tui.tsx', // optional; default tui.tsx when present (terminal components)\n async setup(ctx) {\n // register everything through ctx; may be async; may return a cleanup\n return () => {}\n },\n})\n\\`\\`\\`\n\nA plain object with the same fields works too (\\`import type { ExtensionDefinition }\\`, erased at runtime).\nEvery id the extension contributes is published under it: command \\`hello\\` → \\`my-ext.hello\\`, panel \\`board\\` → \\`my-ext/board\\`.\n\n## Lifetime\n\n- Every registration returns a Disposable (\\`{ dispose() }\\`); UI items return a Handle (\\`update(patch)\\`, \\`dispose()\\`, \\`current\\`).\n- Registrations work any time until the extension unloads (after awaits, from handlers). Unload withdraws them all, in reverse order.\n- ctx.scope() → a child ctx with its own lifetime (and signal): dispose it to withdraw what was registered through it (\"while connected\", \"while a setting is on\").\n- ctx.onCleanup(fn) runs fn at unload. ctx.signal aborts at unload.\n- Every callback is guarded: timeouts, errors attributed to the extension, an error budget that auto-disables a failing extension (reason shown in Settings), stall detection.\n\n## ctx core\n\n- ctx.id / ctx.version / ctx.dir (the folder; use it instead of import.meta.dirname, undefined on Node hosts) / ctx.source ('project' | 'user' | 'config' | 'inline' | 'builtin')\n- ctx.app → { name: 'zidane', version, host: 'tui' | 'desktop' | 'daemon' | 'server' | 'sdk', platform }\n- ctx.paths → cwd, projectRoot?, dataDir, cacheDir, configDir?, stateDir?\n- ctx.logger (attributed to the extension), ctx.signal, ctx.scope(), ctx.onCleanup(cleanup)\n- ctx.add(Token, item) → Handle (UI tokens below)\n- ctx.extensions.list() / ctx.extensions.get(id) → { id, displayName, icon?, version?, source, status: 'active' | 'disabled' | 'failed', error? }\n\n## Domains\n\nTools\n- ctx.tools.register(name, toolDef) — toolDef: { spec: { name, description, inputSchema }, execute(input, toolCtx), isConcurrencySafe?, replay? }.\n isConcurrencySafe: true (or a function of the input) = read-only, runs in parallel; default false (a write barrier).\n- ctx.tools.metadata(namespace, { contextRows?, summarize? }) — claim a tool-result metadata namespace (context panel rows).\n- ctx.tools.transform(editor => …) — edit the tool catalog per run: editor.list/get/origin/add/update/remove, editor.phase ('pre-run' | 'run'), editor.sessionId.\n- ctx.tools.reload() / ctx.tools.list()\n\nHooks\n- ctx.hooks.on(event, handler, { sessionId? }) — observe (read-only event): every AgentHooks key ('turn:after', 'tool:after', 'agent:done', 'child:*' …) plus host events 'run:settled' { sessionId, runId, ok, aborted, session: { turns, metadata } | null } (the session as the run left it, tool results included; null when it is gone) and 'session:event' { sessionId, kind: 'done' | 'error' | 'needs-attention' }.\n- ctx.hooks.intercept(name, (input, current) => next | undefined, { priority?, match? }) — decide; chained in load order (then priority, higher first); return undefined to keep \\`current\\`.\n Interceptors: 'run', 'behavior', 'prompt' (edit text/attachments/delivery, or { handled: true }), 'system' (string), 'context' (wire messages),\n 'tool.call' ('allow' | 'ask' | { block } | { patch } | { result }; match: tool names), 'tool.result' ({ result, isError, metadata? }: metadata adds namespaces to the stored result, e.g. { myExt: {...} }; input.metadata is read-only), 'validation',\n 'skills.activate' (names to activate), 'mcp.tools', 'provider' (wrap a Provider), 'retry', 'compaction', 'title', 'shell.env',\n 'http.request', 'http.response' (OpenAI-compatible, OpenAI, Anthropic but not Bedrock, Google, Vertex and googleCompatible routes; not the Codex WebSocket), 'tracing.redact' (synchronous), 'content-ref.resolve'.\n\nCommands and keys\n- ctx.commands.register({ id, title, description?, group?, keywords?, icon?, when?: 'always' | 'session' | 'idle', enabled?, palette?: boolean | { featured?, suggested? },\n bind?: 'mod+shift+t' | [...], scope?: 'global' | 'composer' | 'transcript' | 'sidebar' | 'modal' | 'panel:<id>' | 'screen:<id>', editable?, mode?, hint?,\n slash?: { name, aliases?, complete?(prefix), argumentHint? }, run?(invocation), steps? }) — one registration = palette entry + slash command + keybinding.\n invocation: { source: 'palette' | 'slash' | 'key' | 'menu' | 'api', sessionId?, args, signal } plus, from a slash, the step context (emit, runAgent, generateData, interaction…).\n From a keybinding, run returning false passes the key on. Users can rebind every key; Esc and ctrl+c are refused. title/description/enabled are Bindable.\n steps: the slash pipeline [{ run(stepCtx) }]; a step returns { data } to merge into stepCtx.data for later steps.\n- ctx.commands.provider({ id, group?, items({ query, sessionId?, busy }), run(itemId, { sessionId? }), subscribe?(refresh) }) — dynamic palette entries;\n run may return { kind: 'prompt' | 'insert', text } or { kind: 'notify', text, tone? }. Item ids are published as '<ext>/<id>'.\n- ctx.commands.run(id, { args?, sessionId? }) / ctx.commands.list() — any command, built-ins included.\n- ctx.keymap.mode.push(name, { priority?, sessionId? }) → Disposable (bindings with \\`mode\\` fire only while it is on top); ctx.keymap.mode.current();\n ctx.keymap.bindings(commandId) — effective keys after user overrides; ctx.keymap.list() → every command (built-in and extension) with bindings, scope, mode;\n ctx.keymap.active({ sessionId? }) → the mode stack, current last; ctx.keymap.dispatch(chord, { sessionId? }) → Promise<boolean> presses a chord in the client viewing the session (rejects NoUiError without one).\n\nCatalogs (agents, models, providers, MCP, skills). Each has register (where it makes sense), transform(editor) and reload(), list().\nTransforms are synchronous and replayed in load order whenever a registration changes or reload() is called: keep them pure, load data first, call reload() when it changes.\nThey may edit or remove built-ins; Settings marks the change and restores it when the extension is off.\n- ctx.agents.register(profile) / ctx.agents.subagents.register({ name, description?, system?, model?, thinking?, tools?, readonly? }) /\n ctx.agents.transform(editor: list/get/update/remove, default.get/set, subagents.{list,get,add,update,remove,override(name, def => def)}) / ctx.agents.reload() / ctx.agents.list()\n- ctx.models.transform(editor: list(providerId?)/get/update/remove, default.get/set) / ctx.models.default() / ctx.models.reload() / ctx.models.list()\n- ctx.providers.register(descriptor) / ctx.providers.transform(editor + models.set(id, list, { account? })/update/remove) / ctx.providers.reload() / ctx.providers.list()\n (with account: that account's own list, which pickers and ctx.models.list() use for it; [] gives it back the provider's list; transforms re-run when accounts connect or switch)\n- ctx.mcp.register(serverConfig) / ctx.mcp.transform(editor) / ctx.mcp.reload() / ctx.mcp.list() (servers: name, status, tools, url?, command?, toolPrefix — tool.call / tool.result see '<toolPrefix><tool>') /\n ctx.mcp.call(server, tool, args?, { timeoutMs?, signal? }) — call a tool outside a run\n- ctx.skills.register({ name, description, instructions, … }) / ctx.skills.transform(editor + autoinvoke(name, on)) / ctx.skills.reload() / ctx.skills.list()\n- ctx.plugins.load(dir | { dir, skills?, mcp?, namePrefix? }) → LoadedPlugin & Disposable — an Agent Plugin (relative to ctx.dir); check \\`ok\\` / \\`diagnostics\\`\n- ctx.instructions.add(text, { sessionId?, key? }) → Disposable — system-prompt text; no sessionId = every session; a key replaces an earlier entry\n\nSessions, generation, workspace, artifacts (reject on hosts without a session runtime)\n- ctx.sessions acts as the extension (what it creates is tagged with the id):\n create({ cwd, prompt?, title?, agentId?, providerKey?, modelId?, tools?, subscribe? }) → id, list({ where?, sort?, limit?, cursor?, archived? }) → { rows, cursor? } (a session query, below), read(id), send(id, prompt), steer(id, message), cancel(id),\n transcript(id, limit?), wait(id, timeoutSec?), events(query?), pending(id?), answer(requestId, response), approve(requestId, decision),\n get(id) → { id, title?, cwd, agentId?, providerId?, modelId?, thinking?, status }, remove(id), compact(id), switchModel(id, { providerId, modelId }),\n switchAgent(id, agentId), setThinking(id, level), submit(text, { sessionId?, delivery?: 'auto' | 'steer' | 'followUp' }) (as if typed: commands,\n interceptors, skills apply), synthetic(id, { role, text }) (the model sees it next turn), note(id, blocks) (display-only, persisted; blocks\n { text, detail?, custom?: { renderer, data } }, text is the markdown fallback).\n- ctx.sessions.facets.register({ key, compute(session) → JSON | undefined, on?: ['created' | 'settled' | 'metadata'] }) → Disposable — a value per session (5 s, 8 KiB) queries read\n as 'facet.<ext>.<key>'; works on every host. ctx.sessions.facets.refresh(sessionId?) asks the host to compute this extension's facets again.\n- Session query (spaces, sidebar filters, list): a condition { field, eq | ne | in | nin | gt | gte | lt | lte | contains | exists } or { all: [...] } | { any: [...] } | { not }.\n Fields: id, title, status, agentId, projectRoot, cwd, createdAt, updatedAt, archived, favorite, spaceId, providerKey, modelId, environment.kind, … or 'facet.<ext>.<key>[.<prop>]'.\n Times take epoch ms, 'now', '-7d' / '+12h' (s m h d w) or ISO dates. Sort: [{ by, dir? }] (missing values last). An invalid query throws SessionQueryError naming each problem.\n- ctx.generate.text({ prompt, system?, model?, signal? }) / ctx.generate.object({ prompt, schema, … }) (validated) / ctx.generate.decide(request) — outside any session, the user's provider resolution, usage counted.\n ctx.generate.image({ prompt, model?, n? (1-4), aspectRatio?, references?: [{ path } | { mimeType, data }], save?: true | { sessionId? } }) → { images: [{ mimeType, data, path? }] }: the generate_image tool's models; save writes under the session's folder and records file artifacts.\n On a host without model access text/object reject and decide.available is false (decide answers { ok: false, reason: 'unavailable' }): check decide.available first.\n- ctx.workspace.available() / exec(command, { cwd?, env?, timeout?, signal? }) / readFile / writeFile / listFiles / fileSize — in the session's execution\n context (sandbox-correct; host child_process targets the wrong filesystem under docker/e2b). Guard with await ctx.workspace.available().\n- ctx.artifacts.kinds.register({ kind: '<ext>.<kind>', label, detect?, validateMeta?, render?, resolve?, badge? }) / ctx.artifacts.list({ scope?, kind? }) /\n ctx.artifacts.find(kind, uri) / ctx.artifacts.record({ kind, uri, label, meta? }). Detail views: ctx.add(Renderer, { for: { artifact: kind } }).\n\nStorage (scoped to the extension)\n- ctx.storage.kv(scope: 'user' | 'project' | 'session', sessionId?) → { ready(), get (sync), set, delete, scan(prefix?), subscribe }. user and project load before setup, a\n session before each of its runs and / commands; any other read (a session read cold, e.g. from run:settled after a restart): await kv.ready() first.\n- ctx.storage.store(name, schema, initial, { scope, sessionId?, version?, migrate? }) → typed, reactive, synced to every window and client: get, set(value | fn), subscribe, select(pick)\n- ctx.storage.memory(key, init) — survives a reload of the extension, not a restart\n- ctx.storage.path(scope | 'cache' | 'data', ...segments) — a folder of the extension's own for files\nSchemas everywhere (stores, RPC, generate.object) are Standard Schema (zod, valibot, arktype; types inferred) or plain JSON Schema (typed unknown).\n\nSettings (one schema in the definition; shown even while the extension is off)\n- keys: a letter or _, then letters, digits, _, . or - ('baseUrl', 'record-mentions'); keep a key's name across versions to carry users' saved values over\n- types: boolean, number (min/max/step), string (secret → auth store, multiline, pattern, placeholder, validate), enum (options), model (multi),\n action (button, run({ get, set, signal }) → { message?, tone?, patch? }, status({ get }), scope?: 'chat' | 'approve' | 'admin' (default admin: a server client below it gets a disabled button)): settings are static, so an action has no ctx; get/set reach the extension's own settings\n- common fields: label, description?, section? (settings.sections), page? (settings.pages; a sectioned item takes its section's page), scopes? (['user', 'project'] default; 'session'), apply? ('live' | 'agent-rebuild' | 'restart')\n- schema: pages?: [{ id, label, description?, icon? }] (default: one page named after the extension), sections?: [{ id, label, description?, page? }], category?: { label, icon? } (a sidebar group of its own; else under Extensions). Settings search covers every row.\n- ctx.settings.get(key, { sessionId? }) (typed from the schema; session → project → user → config → default) / ctx.settings.onChange(key, fn) / ctx.settings.locked(key) (set by a declarative config entry)\n\nIntegrations (Settings shows Connect / Disconnect per account; secrets live in the auth store)\n- ctx.integrations.register({ id, name, description?, icon?, methods: [{ type: 'apiKey', id, label, placeholder?, help? } | { type: 'oauth', flow: 'pkce' | 'device', … } | { type: 'command', command, … } | { type: 'custom', id, label, connect: io => ({ credential, account?, label? }), refresh? }], beforeConnect?, verify?, account?, parseCommandOutput?, legacyCredentialKey? }) → handle (dispose, update({ name, description, icon, methods, beforeConnect }))\n- integrationMethodFromOAuthProvider(oauthProvider) → a custom method from a zidane/oauth provider flow\n- several connections per integration (one per account, the first is the default): ctx.integrations.connections(id) / ctx.integrations.connection(id, { account? }) /\n ctx.integrations.setDefault(id, account) / ctx.integrations.resolve(connection) → credential (OAuth refreshed) / ctx.integrations.connect.key(id, key) /\n ctx.integrations.connect.start(id, methodId) / ctx.integrations.disconnect(id, { account? }) / ctx.integrations.onChange(id, fn)\n- a provider that signs in with one: ctx.providers.register({ …, integration: '<id>' }) (pickers, \"Sign in\" and usage follow the integration)\n\nRPC and contracts\n- const Api = Rpc.define({ id: '<ext>' | '<ext>.<name>', methods: { m: { input?, output?, errors?: { name: schema }, scope?: 'chat' | 'approve' | 'admin' } }, events: { e: { schema } } })\n- ctx.rpc.register(Api, { m: async (input, call) => output | call.error('name', message, data) }) → registration.events.emit(name, data);\n call.caller is { kind: 'extension', id } | { kind: 'ui' } | { kind: 'client', clientId }\n- ctx.rpc.client(Api) → typed methods (reject with RpcCallError: a declared error type, or rpc.invalid-input / invalid-output / unavailable / aborted / internal / forbidden) and events.on/subscribe.\n Protocol clients call POST /rpc/<ext>/<method>, refused (rpc.forbidden) below the method's scope (default chat). Inputs and outputs are validated both ways.\n- Contract.define<T>('<ext>.<name>') / Registry.define<T>('<ext>.<name>'): ctx.contracts.provide(token, impl) / ctx.contracts.use(token) → Live accessor\n (get() → { status: 'pending' | 'active' | 'inactive' }, value(), subscribe) / ctx.contracts.add(registry, item) / ctx.contracts.list(registry) → Readable of every item\n\nDialogs and layout (ctx.ui); each goes to the client viewing \\`sessionId\\`, else the last active one; no client → a dialog rejects NoUiError, a toast or notify\nwaits for one (up to 20, 60 s each; a dropped toast resolves undefined); timeoutMs → DialogTimeoutError\n- ctx.ui.confirm({ title, message?, confirmLabel?, danger? }) / ctx.ui.prompt({ title, placeholder?, initial?, secret? }) / ctx.ui.select({ title, options, multi?, actions? }) /\n ctx.ui.form({ title, schema (settings shape), values? }) / ctx.ui.askUser(questionPayload) / ctx.ui.presentPlan({ title, plan, steps? }) /\n ctx.ui.toast({ text, tone?, actions? }) → the clicked action id / ctx.ui.notify({ title, body? }) (OS notification)\n- ctx.ui.panel.open(id) / ctx.ui.panel.close(id) / ctx.ui.panel.toggle(id) / ctx.ui.panel.state() / ctx.ui.navigate({ screen } | { session } | { newSession: true })\n- ctx.composer.insert(text) / ctx.composer.setText(text) / ctx.composer.attach(files) — on the client viewing the session\n- ctx.composer.completions.register({ id, trigger, label, suggest, parseReferences, … }) — popover completions on a trigger character ('/', '@', '#' are built in)\n- ctx.composer.autocomplete.register({ id, suggest({ text, cursor, cwd, signal }), deadlineMs? }) — ghost text, accepted with Tab\n- ctx.desktop — ONLY in the desktop app's main process (undefined elsewhere: guard it): ctx.desktop.electron<typeof import('electron')>(), ctx.desktop.windows.list(), ctx.desktop.windows.on('open' | 'close' | 'focus', fn)\n\n## UI tokens: ctx.add(Token, item) → Handle\n\nImport the tokens from 'zidane'. Items are data the hosts draw natively; any item may name a component\n(\\`component: { component: 'ExportName', props? }\\`) from ui.tsx (desktop + web) and tui.tsx (terminal); a host without it draws the data or \\`fallback\\`.\nFields marked Bindable take a value or a Readable (\\`store.select(fn)\\`): the item follows it.\n- Panel — side tab and/or project-home block: { id, label, icon?, scope?: 'session' | 'project' | 'both', surfaces?: ['side' | 'home'], home?, load(lc) → { sections, actions?, controls?, badge?, empty? }, subscribe?(emit), onAction?(actionId, payload, lc), component? }.\n Section items: row, markdown, kv, stats, heatmap, bars, custom ({ renderer, data, fallback }).\n- Slot — a small item in a named place: at: 'app.statusbar' | 'app.titlebar' (max 8) | 'session.header' | 'composer.above' (banner \\`lines\\`) | 'composer.below' | 'composer.badges' (needs sessionId; max 8) |\n 'sidebar.top' | 'sidebar.bottom' (full-width rows pinned above / below the session list, outside its scroll) | 'sidebar.footer' (chips in the footer strip) | 'transcript.turn-end' | 'home.block' | 'settings.section'. Fields: text, lines, count, icon, tone, color, loader, highlight, title, panel, command, order, timeoutMs, sessionId, component.\n- Screen — { id, title, presentation?: 'screen' | 'modal', component (required), fallback? (markdown) }; open with ctx.ui.navigate({ screen: id }).\n- Renderer — { for: { block } | { language } | { tool } | { artifact }, component, fallthrough? }: custom transcript blocks (note/emit \\`custom: { renderer }\\`, panel custom items), fenced code by language, tool result rows, artifact details.\n- SettingsPage — { id, title, icon?, order? (after the schema's pages, default 100), schema? | component? }: one more page among the extension's settings pages\n- Webview — { id, title, src: { url } | { file }, placement: 'panel' | 'screen', rpc?: [contract ids] }: sandboxed; the page posts { type: 'zidane:rpc', id, contract, method, input } to window.parent and gets 'zidane:rpc:result'.\n- Menu — native menu entry (desktop): { menu: 'app' | 'session' | 'tray', label, command, accelerator?, enabled? }\n- ThemeToken — a full Theme { id, label, colors, select, surfaces, syntax }; namespace the id\n- Reference — { id: '<ext>.<what>', patterns: [{ regex, flags?, key? }], surfaces?, resolve(key) → { label, tone?, status?, href?, prompt?, card? } }: ticket ids / URLs become chips\n- BadgeAction — { id, match?: { kinds?, artifactKinds?, references? }, actions(target), run(actionId, target, ctx) → { copy?, insert?, open?, reveal?, notice? } }\n- LinkHandler — { id, match: prefix | RegExp | (url) => boolean, open(url, { sessionId?, source }) → false passes it on, priority? }: opens links the extension understands before the app (desktop and web: Alt opens the default way; the TUI has no bypass and never routes sign-in links)\n- ContextSection — { placement: 'breakdown', contributor } | { placement: 'panel', section: { id, title, rows?, load?, actions? } }\n- Space — a sidebar view of sessions: { id (not 'default'), name, icon?, query, groupBy?: { field, labels?, order? }, sortBy?: [{ field, dir? }], newSession?: { agentId?, providerKey?, modelId?, effort? }, items? (SidebarRows may list in it), order? }\n- SidebarFilter — { id, label, icon?, kind: 'toggle' (query) | 'select' | 'multi' (options: [{ id, label, query }]), spaces?: 'all' | space ids }\n- SidebarSort — { id, label, sortBy: [{ field, dir? }] }; SidebarGroup — { id, label, groupBy: { field, labels?, order?: 'asc' | 'desc' | [keys] } }\n- SidebarDecoration — { id, where?, icon?, tone?, subtitle? (a string drawn as given, or { template: '{facet.<ext>.branch} · {modelId}' }), chips?: [{ text } | { template } | { facet }], component? } on matching session rows\n- SidebarRow — a row beside sessions: { id, space ('default', own id, '<ext>/<id>'), group?, title, subtitle?, icon?, tone?, status?, time?, badges?, actions?: [{ id, label, command? }], open?: { command } | { panel } | { screen } | { session }, children? (query) }\n\n## Sidebar (spaces, filters, facets)\n\n- A Space lists the sessions its query matches, the sessions started in it (tagged '<ext>/<id>', which then leave Default) and the Main session. ctx.sessions.create does not tag: make the query match your sessions, usually through a facet.\n- Data fields (name, label, query, options, sortBy, groupBy, newSession, a row's state and children, a decoration's content) are Bindable: pass store.select(...) and the apps follow it; it pushes only when the value changes. Ids, placement, actions, open and components change through handle.update.\n- Facets are how a query sees your data: ctx.sessions.facets.register({ key: 'botId', compute: s => map[s.id] ?? null }) → 'facet.<ext>.botId'. Disposing a facet deletes its stored values. compute runs on created / settled / metadata,\n possibly before create() resolves: after you store the new id, await ctx.sessions.facets.refresh(id). A throw or timeout keeps the last value; null / undefined clears it.\n- Filters, sorts and groupings are choices the user picks (nothing applies until then); a Space's groupBy / sortBy are its defaults.\n- SidebarRow lists only in 'default' or a Space with items: true (own items too); children: a query of the sessions nested under it.\n A row's command gets args = the row's own id ('bot-1', never '<ext>/bot-1'); parse it with an anchored regex (/^bot-/). A bare command id is your own ('chat' runs '<ext>.chat'); another extension's is '<ext>.<id>'.\n- Text is literal unless it is { template }: put user text (names, titles) in plain strings or { text } chips, or escapeTemplate(text) inside a template; a lone brace in a template throws.\n- Queries: in: [] matches nothing (nin: [] everything), so { field: 'agentId', in: ids } needs no empty case. projectName is the folder name. Sorts break ties by id.\n- Every query, sort, grouping and template is validated by ctx.add / update (UiQueryError, a SessionQueryError, naming the path from the item's field). Caps per extension: 32 spaces, 64 filters, 32 sorts, 32 groups, 64 decorations, 500 rows (RangeError past them). Templates print times as epoch ms.\n- ctx.sessions.list({ where?, sort?, limit?, cursor?, archived? }) → { rows, cursor? }: rows are { id, status ('idle'|'running'|'blocked'|'completed'|'error'), ...fields, facets, worker }. Pass the same where/sort/archived with each cursor.\n- Desktop and web show extension spaces only with Spaces on; the terminal app always.\n\n## UI entries\n\nui.tsx (desktop + web): React components bundled by the app (compileUiEntry); each behind an error boundary.\n- Import React and 'zidane-ui/extensions/kit' only as host modules; other files and packages are bundled. \\`zidane/*\\` values, Node, Electron, Bun and @opentui imports are refused (type-only imports are erased).\n- Kit: useExtension, useRpc(contractOrId), useStore(name, { scope?, sessionId?, initial }), useSettings() → { values, locked, set }; Raised, Tray, EmptyState, PanelEmpty, PanelLoading, LoadError,\n CodePanel, SectionLabel, Meta, MetaChip, CountBadge, Keycap, Markdown, HighlightedCode, SegmentSpinner, Tooltip, Menu/MenuRow/MenuSection, buttonClass/BTN_PRIMARY/BTN_SECONDARY, IconButton, CopyButton,\n FormSection, FormCard, SwitchRow, ToggleSwitch, SegmentedRow, SliderRow, Segmented, TabPill, INPUT_CLASS, ErrorNote.\ntui.tsx (terminal): OpenTUI React components imported in-process; \\`/** @jsxImportSource @opentui/react */\\` at the top; react, @opentui/* and zidane/tui resolve to the app's live modules.\n- Kit from 'zidane/tui': Modal, List, Form, Markdown, Badge, EmptyState, KeyHint, Spinner, useColors/useTheme/useSurfaces, useExtension, useRpc(contract), useStore(name, { scope? }), useSettings().\nBoth: same export names, so one \\`component\\` name draws in every host. Reach the backend only through RPC and stores. Renderer props: language { language, code }, tool { tool, callId, text }, artifact { artifact }, block { data }.\n\n## Testing\n\n\\`\\`\\`ts\nimport { createTestHost } from 'zidane/extensions/testing'\nconst host = await createTestHost({ extensions: [extension], settings: { 'my-ext': { greeting: 'Hi' } } })\nhost.extensions() // [{ id, status, error? }]\nhost.ctx('my-ext') // the ctx setup received\nawait host.fire('tool:after', payload) // observers, then waits for them\nawait host.intercept('tool.call', input, 'allow', { matchKey: 'shell' })\nawait host.rpc(Api).method(input) // as another extension\nawait host.commands.run('my-ext.hello', { args, sessionId }); host.commands.list()\nhost.dialogs.answer('confirm', true); host.dialogs.shown // scripted dialogs\nhost.ui.items('slot'); host.ui.client.calls // UI items; panel/navigate/composer/notify calls\nhost.registry.ui.loadPanel(id, { surface: 'side' }, cwd); host.registry.catalogs.tools.read({ phase: 'run' })\nhost.settings.put('my-ext', 'user', values); host.storage.snapshot({ extensionId, scope })\nawait host.facets({ id: 's1', metadata }, 'settled') // { 'my-ext.key': value } as a host computes them\nawait host.settled(); await host.dispose()\n\\`\\`\\`\nOptions: ports (sessions/generate/workspace/artifacts host ports, desktop), paths, host kind, budget, onLog. Pass { definition, dir } to set ctx.dir (the folder name must equal the id).\n\n## CLI\n\n- zidane extension create <name> [--ui] [--tui] [--no-test] [--dir <parent>] [--scope user|project] — scaffold index.ts, index.test.ts, package.json (+ ui.tsx / tui.tsx)\n- zidane extension test [dir] [-- bun test args] — runs the tests; maps zidane, zidane/extensions, zidane/extensions/testing when the folder has no node_modules/zidane\n- zidane extension pack [dir] [--out] [--tarball] [--no-setup] — validates the definition, runs setup in a test host, bundles ui.tsx, checks webview files and package.json \\`files\\`, writes <dir>/.pack/<id>\n- zidane extension publish [dir] [--dry-run] [--tag] [--access] — the pack checks, refuses private / version mismatches, then npm publish\n\n## Patterns\n\n- One file per area, \\`setup\\` wires them (see the kitchen-sink example in the zidane repo: examples/extension-kitchen-sink).\n- State in a store, UI bound to it: \\`ctx.add(Slot, { at: 'app.statusbar', text: counter.select(c => String(c)) })\\` — no manual update calls.\n- Something on only while a setting is on: build it in ctx.scope(), dispose the scope in ctx.settings.onChange.\n- Transforms that read settings or loaded data: call ctx.<catalog>.reload() when those change.\n- Long work: pass ctx.signal (or a scope's) to fetch/exec so unload cancels it.\n- Session-bound commands: \\`when: 'session'\\` and use invocation.sessionId; dialogs and composer calls take { sessionId }.\n- Share an API with other extensions through Rpc.define or Contract.define in a tokens file both import; Live accessors may be pending or inactive — read them where you use them.\n\n## Pitfalls\n\n- id ≠ folder name → discovery refuses the extension.\n- \\`./ui\\` resolves to ui.tsx before ui.ts: don't name a backend file ui.ts next to ui.tsx.\n- composer.badges slots need a sessionId. Dialogs reject NoUiError without a client: catch it in commands bound to keys.\n- ctx.desktop is undefined outside the desktop main process; ctx.sessions / generate / workspace / artifacts reject on hosts without them.\n- A ui entry can't import a file that imports 'zidane' values (Rpc.define): import its types only and useRpc('<id>') with a typed client.\n- On Node hosts (the desktop app) import.meta.dirname is undefined: use ctx.dir. Only zidane, zidane/extensions, zidane/extensions/testing, zidane/providers, zidane/providers/google and zidane/oauth are host-mapped (runtime imports of tokens like Panel work); other packages need the extension's own node_modules.\n- Transforms must be synchronous and pure; never await or write state in them.\n- Don't write \\`.ts\\` / \\`.js\\` data files into the extension folder: the desktop app reloads on source changes.\n\n## Loading, trust, distribution\n\n- Discovery order (first id wins): project .agents → project .zidane → ~/.agents → ~/.zidane. Project extensions load once the project is trusted (\"Load project extensions\").\n- /reload (and a saved source file in the desktop app) re-imports the module graph and re-runs setup; ctx.storage.memory survives it.\n- Install from Settings → Extensions: GitHub (owner/repo[#ref][:sub/dir]) or npm (pkg[@version]); or declaratively with an \\`extensions: [{ source, settings }]\\` config entry (pinned, with a lock file; its settings show locked).\n- Runtime deps go in package.json \\`dependencies\\` (installed without lifecycle scripts). Extensions are trusted on install: they run in-process, with no permission prompts.`\n\n// ---------------------------------------------------------------------------\n// Edit-session presets — the first line of each inline skill's instructions.\n// Kept next to each other so the two flows stay phrased consistently.\n// ---------------------------------------------------------------------------\n\n/** Preset preamble for \"edit / create extension\" sessions. */\nconst EXTENSION_EDIT_PRESET\n = 'You are authoring / editing a zidane extension. Authoritative API reference:'\n\n/** Preset preamble for \"edit skill\" sessions — same spirit, skill-flavored. */\nfunction skillEditPreset(marker: SkillEditMarker): string {\n return `We are going to work on ${marker.name}, in ${marker.dir} directory, `\n + 'next messages will be requests at modifying and improving the skill or '\n + 'understanding how it works better.'\n}\n\n/**\n * The inline `zidane-extension` skill. Carries the authoring reference IN its\n * body ({@link EXTENSION_AUTHORING_CHEATSHEET}) so it works in every host —\n * incl. compiled binaries where the on-disk docs aren't resolvable. The shipped\n * `EXTENSIONS.md` (prose long-form) is appended as a \"see also\" only when found\n * on disk. `source: 'inline'` (the `defineSkill` default) survives the\n * project-trust gate.\n *\n * @param opts - Options. `cwd` is the directory used to locate the shipped docs. Defaults to `process.cwd()`.\n */\nexport function buildZidaneExtensionSkill(opts: { cwd?: string } = {}): SkillConfig {\n const docsDir = resolveExtensionDocsDir(opts.cwd)\n const seeAlso = docsDir\n ? `\\n\\n---\\nHow the extension system works inside: ${docsDir}/EXTENSIONS.md. Where it differs, the API above wins.`\n : '\\n\\n---\\nProse docs, when present: node_modules/zidane/docs/EXTENSIONS.md '\n + '(often absent in compiled binaries; the API above is authoritative).'\n return defineSkill({\n name: ZIDANE_EXTENSION_SKILL_NAME,\n description:\n 'Authoring or editing a zidane extension (a module calling defineExtension, under '\n + '.zidane/extensions/** or .agents/extensions/**). Carries the extension authoring reference.',\n instructions: `${EXTENSION_EDIT_PRESET}\\n\\n${EXTENSION_AUTHORING_CHEATSHEET}${seeAlso}`,\n })\n}\n\n/**\n * Inject the `zidane-extension` skill into an already-merged {@link SkillsConfig}.\n *\n * Call this on the FINAL merged skills object (after folding extension / preset\n * skills): `composePresets` is last-wins on `catalog`, so injecting earlier\n * would be silently dropped whenever an extension also contributes skills.\n *\n * Adds the skill to the catalog and, when an `enabled` allowlist is in force,\n * force-includes the name so the allowlist filter can't hide it (mirrors how\n * extension-contributed skill names are force-included). When skills are fully\n * disabled (`enabled === false`), the edit session still gets just this skill.\n *\n * @param skills - Merged skills configuration to extend.\n * @param opts - Options. `cwd` is the directory used to locate the shipped docs. Defaults to `process.cwd()`.\n */\nexport function withZidaneExtensionSkill(skills: SkillsConfig, opts: { cwd?: string } = {}): SkillsConfig {\n return injectInlineSkill(skills, buildZidaneExtensionSkill(opts))\n}\n\n/**\n * Compact skill-authoring reference embedded into the skill-edit skill body.\n * Inline for the same reason as {@link EXTENSION_AUTHORING_CHEATSHEET}: works\n * in compiled binaries where on-disk docs don't resolve.\n */\nconst SKILL_AUTHORING_CHEATSHEET = `## SKILL.md anatomy\n\nA skill is a directory holding a \\`SKILL.md\\` (YAML frontmatter + markdown body)\nper the Agent Skills standard (agentskills.io/specification):\n\n\\`\\`\\`md\n---\nname: my-skill # 1-64 chars, lowercase alphanumeric + hyphens; SHOULD match the directory name\ndescription: what it does AND when to use it (drives activation — 1-1024 chars)\nlicense: MIT # optional\ncompatibility: … # optional environment requirements\nallowed-tools: # optional — restricts the agent to these tools while active\n - read_file\nmetadata: # optional flat string map; zidane-specific keys use the \\`zidane.\\` prefix\n some-key: value\n---\nThe body is the skill's instructions — injected into context when the skill\nis activated. Keep it token-lean: move depth into references/.\n\\`\\`\\`\n\n## Bundled resources (all optional, discovered automatically)\n- \\`references/\\` — docs loaded on demand via \\`skills_read\\`\n- \\`scripts/\\` — executables run via \\`skills_run_script\\` (honor shebangs)\n- \\`assets/\\` — templates / static files\n\n## Guidance\n- The description is the activation trigger — write it for the ROUTER (\"use when …\"), the body for the ACTIVATED agent.\n- Prefer small bodies + lazy references over one giant SKILL.md.\n- Shell interpolation (exclamation mark + backtick-quoted command) in the body runs at activation — use sparingly; hosts may gate or disable it.\n- Validate names: mismatched frontmatter name vs directory loads with a warning diagnostic.`\n\n/**\n * The inline skill auto-activated in \"edit skill\" sessions. Body = the\n * session preset (which skill, which directory, what the next messages will\n * be about) + a compact SKILL.md authoring reference — the skill-editing\n * counterpart of {@link buildZidaneExtensionSkill}.\n *\n * @param marker - Marker naming the skill's directory and name.\n * @param opts - Options. `cwd` is the directory used to locate the shipped docs. Defaults to `process.cwd()`.\n */\nexport function buildZidaneSkillEditSkill(marker: SkillEditMarker, opts: { cwd?: string } = {}): SkillConfig {\n const docsDir = resolveExtensionDocsDir(opts.cwd)\n const seeAlso = docsDir && existsSync(join(docsDir, 'SKILL.md'))\n ? `\\n\\n---\\nFull prose reference on disk: ${join(docsDir, 'SKILL.md')}`\n : ''\n return defineSkill({\n name: ZIDANE_SKILL_EDIT_SKILL_NAME,\n description:\n `Editing or improving the \"${marker.name}\" agent skill (its SKILL.md, references, `\n + 'scripts, and assets). Carries the skill-authoring reference for this edit session.',\n instructions: `${skillEditPreset(marker)}\\n\\n${SKILL_AUTHORING_CHEATSHEET}${seeAlso}`,\n })\n}\n\n/**\n * Inject the skill-edit preset skill into an already-merged {@link SkillsConfig}.\n * Same contract as {@link withZidaneExtensionSkill} — call on the FINAL merged\n * skills object.\n *\n * @param skills - Merged skills configuration to extend.\n * @param marker - Marker naming the skill's directory and name.\n * @param opts - Options. `cwd` is the directory used to locate the shipped docs. Defaults to `process.cwd()`.\n */\nexport function withZidaneSkillEditSkill(\n skills: SkillsConfig,\n marker: SkillEditMarker,\n opts: { cwd?: string } = {},\n): SkillsConfig {\n return injectInlineSkill(skills, buildZidaneSkillEditSkill(marker, opts))\n}\n\n/**\n * Shared injector: add `skill` to the catalog and, when an `enabled`\n * allowlist is in force, force-include its name so the filter can't hide it\n * (mirrors how extension-contributed skill names are force-included). When\n * skills are fully disabled (`enabled === false`), the edit session still\n * gets just this skill.\n */\nfunction injectInlineSkill(skills: SkillsConfig, skill: SkillConfig): SkillsConfig {\n const catalog = [...(skills.catalog ?? []), skill]\n const enabled = skills.enabled\n const nextEnabled: SkillsConfig['enabled']\n = enabled === undefined\n ? undefined\n : Array.isArray(enabled)\n ? (enabled.includes(skill.name) ? enabled : [...enabled, skill.name])\n : [skill.name]\n return {\n ...skills,\n catalog,\n ...(nextEnabled !== undefined ? { enabled: nextEnabled } : {}),\n }\n}\n","/**\n * Building blocks for hosts that freeze a conversation's system prompt (see\n * `AgentRunOptions.systemFreeze`): the out-of-band snapshot turn, lookup of\n * the newest snapshot for a prompt key, section digests, and the notes\n * announcing changes to the sections the SDK appends to every run's prompt\n * (skills catalog, searchable tools catalog, MCP server instructions).\n *\n * Which other sections a host tracks, and when it clears a snapshot, is the\n * host's policy.\n */\n\nimport type { FrozenPromptSections, SessionTurn, SystemNote, SystemPromptSnapshot } from './types'\nimport { createHash } from 'node:crypto'\n\n/** The newest snapshot for a prompt key, with its prompt text and the index of the turn holding it. */\nexport interface FrozenSystemPrompt {\n /** The frozen system prompt. */\n text: string\n /** The snapshot's bookkeeping. */\n snapshot: SystemPromptSnapshot\n /** Index of the snapshot turn in the session's turns. */\n index: number\n}\n\n/** A note announcing one changed section, with the text the model reads. */\nexport type SectionChangeNote = SystemNote & { text: string }\n\n/** The SDK-appended sections, in announcement order. */\nexport const FROZEN_PROMPT_SECTION_KINDS = ['skills', 'tools', 'mcp'] as const\n\n/**\n * Short stable digest of a section's text.\n *\n * @param text - Section text.\n */\nexport function sectionDigest(text: string): string {\n return createHash('sha256').update(text).digest('hex').slice(0, 16)\n}\n\n/**\n * Digest of every section present, by section kind.\n *\n * @param sections - Section texts by kind; `undefined` entries are skipped.\n */\nexport function sectionDigests(sections: Readonly<Record<string, string | undefined>>): Record<string, string> {\n const digests: Record<string, string> = {}\n for (const [kind, text] of Object.entries(sections)) {\n if (text !== undefined)\n digests[kind] = sectionDigest(text)\n }\n return digests\n}\n\n/**\n * The out-of-band `role: 'system'` turn recording a frozen prompt, or\n * clearing the one recorded for `key` (every key when omitted).\n *\n * @param input - Turn id and time, plus the prompt and its snapshot, or `cleared` with an optional key.\n */\nexport function systemSnapshotTurn(input: { id: string, createdAt: number } & ({ cleared: true, key?: string } | { system: string, snapshot: Omit<SystemPromptSnapshot, 'v' | 'cleared'> })): SessionTurn {\n if ('cleared' in input) {\n return {\n id: input.id,\n role: 'system',\n createdAt: input.createdAt,\n content: [],\n systemSnapshot: { v: 1, ...(input.key !== undefined ? { key: input.key } : {}), cleared: true, digests: {} },\n }\n }\n return {\n id: input.id,\n role: 'system',\n createdAt: input.createdAt,\n content: [{ type: 'text', text: input.system }],\n systemSnapshot: { v: 1, ...input.snapshot },\n }\n}\n\n/**\n * The newest frozen prompt recorded for `key`. A cleared snapshot for the\n * same key, or one with no key, ends the search with `undefined`; snapshots\n * for other keys are skipped.\n *\n * @param turns - Session turns, oldest first.\n * @param key - The prompt key the snapshot was recorded under; omitted matches keyless snapshots.\n */\nexport function latestSystemSnapshot(turns: readonly SessionTurn[], key?: string): FrozenSystemPrompt | undefined {\n for (let index = turns.length - 1; index >= 0; index--) {\n const turn = turns[index]!\n const snapshot = turn.systemSnapshot\n if (turn.role !== 'system' || !snapshot)\n continue\n if (snapshot.cleared) {\n if (snapshot.key === undefined || snapshot.key === key)\n return undefined\n continue\n }\n if (snapshot.key !== key)\n continue\n const block = turn.content.find(b => b.type === 'text')\n return block?.type === 'text' && block.text ? { text: block.text, snapshot, index } : undefined\n }\n return undefined\n}\n\n/**\n * Digest of a section as the model was last told it: the newest note of\n * `kind` carrying a digest after the snapshot, else the snapshot's digest.\n *\n * @param frozen - The frozen prompt.\n * @param turns - Session turns, oldest first.\n * @param kind - Section kind.\n */\nexport function announcedDigest(frozen: FrozenSystemPrompt, turns: readonly SessionTurn[], kind: string): string | undefined {\n for (let t = turns.length - 1; t > frozen.index; t--) {\n const content = turns[t]!.content\n for (let b = content.length - 1; b >= 0; b--) {\n const block = content[b]!\n if (block.type === 'text' && block.systemNote?.kind === kind && block.systemNote.digest !== undefined)\n return block.systemNote.digest\n }\n }\n return frozen.snapshot.digests[kind]\n}\n\n/**\n * Notes for each SDK-appended section whose live text differs from what the\n * model was last told (see {@link announcedDigest}). A section the model was\n * told about that is now absent is announced as emptied; tools changes list\n * the entries added and removed. Deterministic.\n *\n * @param input - The frozen prompt, the session turns and this run's sections.\n * @param input.frozen - The frozen prompt.\n * @param input.turns - Session turns, oldest first.\n * @param input.sections - The sections the SDK appended to this run's fresh prompt.\n */\nexport function catalogChangeNotes(input: { frozen: FrozenSystemPrompt, turns: readonly SessionTurn[], sections: FrozenPromptSections }): SectionChangeNote[] {\n const { frozen, turns, sections } = input\n const notes: SectionChangeNote[] = []\n for (const kind of FROZEN_PROMPT_SECTION_KINDS) {\n const text = sections[kind] ?? ''\n const digest = sectionDigest(text)\n const told = announcedDigest(frozen, turns, kind)\n if (told === digest || (told === undefined && text === ''))\n continue\n notes.push({ kind, digest, ...sectionNote(kind, text, frozen, turns) })\n }\n return notes\n}\n\nfunction sectionNote(kind: (typeof FROZEN_PROMPT_SECTION_KINDS)[number], text: string, frozen: FrozenSystemPrompt, turns: readonly SessionTurn[]): { label: string, text: string } {\n switch (kind) {\n case 'skills':\n return text\n ? { label: 'Skills catalog changed', text: `The skills catalog changed. It now reads:\\n\\n${text}` }\n : { label: 'No skills available', text: 'The skills catalog is now empty: no skills are available.' }\n case 'tools':\n return toolsNote(text, frozenToolsBlock(frozen.text), noteTexts(frozen, turns, 'tools'))\n case 'mcp':\n return text\n ? { label: 'MCP server instructions changed', text: `The MCP server instructions changed. They now read:\\n\\n${text}` }\n : { label: 'MCP server instructions removed', text: 'The MCP server instructions were removed. The ones given earlier no longer apply.' }\n }\n}\n\n/** Texts of the notes of `kind` after the snapshot, oldest first. */\nfunction noteTexts(frozen: FrozenSystemPrompt, turns: readonly SessionTurn[], kind: string): string[] {\n const texts: string[] = []\n for (const turn of turns.slice(frozen.index + 1)) {\n for (const block of turn.content) {\n if (block.type === 'text' && block.systemNote?.kind === kind)\n texts.push(block.text)\n }\n }\n return texts\n}\n\n/** The last `<searchable_tools>` block of a frozen prompt. */\nfunction frozenToolsBlock(system: string): string | undefined {\n const start = system.lastIndexOf('<searchable_tools>')\n const end = start < 0 ? -1 : system.indexOf('</searchable_tools>', start)\n return end < 0 ? undefined : system.slice(start, end + '</searchable_tools>'.length)\n}\n\n/** Tool names listed in a `<searchable_tools>` block, either catalog detail. */\nfunction catalogEntries(block: string | undefined): Set<string> {\n const names = new Set<string>()\n for (const match of (block ?? '').matchAll(/<tool name=\"([^\"]*)\">/g))\n names.add(match[1]!)\n for (const match of (block ?? '').matchAll(/<(?:server name=\"[^\"]*\"|tools)>([^<\\n]+)<\\/(?:server|tools)>/g)) {\n for (const name of match[1]!.split(', '))\n names.add(name)\n }\n return names\n}\n\nconst ADDED = 'Added: '\nconst REMOVED = 'Removed: '\n\n/** The catalog entries added and removed since the frozen catalog and the tools notes after it. */\nfunction toolsNote(text: string, frozen: string | undefined, history: readonly string[]): { label: string, text: string } {\n let told = catalogEntries(frozen)\n for (const note of history) {\n if (note.includes('<searchable_tools>')) {\n told = catalogEntries(note)\n continue\n }\n for (const line of note.split('\\n')) {\n if (line.startsWith(ADDED))\n line.slice(ADDED.length).split(', ').forEach(name => told.add(name))\n else if (line.startsWith(REMOVED))\n line.slice(REMOVED.length).split(', ').forEach(name => told.delete(name))\n }\n }\n const now = catalogEntries(text)\n const added = [...now].filter(name => !told.has(name))\n const removed = [...told].filter(name => !now.has(name))\n if (added.length + removed.length === 0)\n return { label: 'Searchable tools catalog changed', text: `The searchable tools catalog changed. It now reads:\\n\\n${text}` }\n return {\n label: `Searchable tools: +${added.length} / -${removed.length}`,\n text: [\n 'The searchable tools catalog changed.',\n ...(added.length > 0 ? [`${ADDED}${added.join(', ')}`] : []),\n ...(removed.length > 0 ? [`${REMOVED}${removed.join(', ')}`] : []),\n ].join('\\n'),\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"system-freeze-XgDPIwQS.js","names":[],"sources":["../src/extension-authoring.ts","../src/system-freeze.ts"],"sourcesContent":["/**\n * Host glue for the \"edit / create extension\" and \"edit skill\" authoring\n * flows (TUI + GUI).\n *\n * Pure, renderer-neutral helpers consumed by the hosts — deliberately kept OUT\n * of `src/extensions/**` (the extension loader / registry core) so the\n * authoring UX can never entangle with discovery / registration. Imports only\n * the skills lib and node builtins.\n *\n * - {@link extensionScaffold} — the `index.ts` of a new extension;\n * {@link extensionScaffoldFiles} adds `ui.tsx` / `tui.tsx`, a test and\n * package files; {@link writeExtensionScaffold} writes them;\n * {@link createExtension} writes a new extension where zidane discovers it\n * (the TUI, desktop and daemon create flows; `zidane extension create`).\n * - {@link resolveExtensionDocsDir} — locate the shipped `docs/` directory.\n * - {@link buildZidaneExtensionSkill} — the inline extension docs skill.\n * - {@link buildZidaneSkillEditSkill} — the inline skill-editing skill.\n * - {@link withZidaneExtensionSkill} / {@link withZidaneSkillEditSkill}\n * — inject either into a `SkillsConfig`.\n * - {@link editSessionMetadata} — the ONE canonical \"open an editing session\n * with preset context\" seed both hosts stamp at session creation.\n * - {@link EXTENSION_EDIT_METADATA_KEY} / {@link SKILL_EDIT_METADATA_KEY} /\n * {@link ZIDANE_EXTENSION_SKILL_NAME} / {@link ZIDANE_SKILL_EDIT_SKILL_NAME}\n * — constants shared by both hosts.\n */\nimport type { SkillConfig, SkillsConfig } from './skills'\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { zidaneRangeFor } from './extensions/host-version'\nimport { defineSkill } from './skills'\n\n/**\n * Session-metadata key marking an \"edit / create extension\" session. The value\n * is an {@link ExtensionEditMarker}. Both hosts stamp it at session creation so\n * the agent build can scope the docs skill to exactly these sessions (and it\n * survives reload because metadata is persisted).\n */\nexport const EXTENSION_EDIT_METADATA_KEY = 'zidane.extensionEdit'\n\n/** Name of the inline docs skill auto-activated in extension edit sessions. */\nexport const ZIDANE_EXTENSION_SKILL_NAME = 'zidane-extension'\n\n/**\n * Session-metadata key marking an \"edit skill\" session — the skill-editing\n * counterpart of {@link EXTENSION_EDIT_METADATA_KEY}. The value is a\n * {@link SkillEditMarker}. Stamped at session creation by both hosts (via\n * {@link editSessionMetadata}); persisted metadata means it survives reload.\n */\nexport const SKILL_EDIT_METADATA_KEY = 'zidane.skillEdit'\n\n/** Name of the inline preset skill auto-activated in skill edit sessions. */\nexport const ZIDANE_SKILL_EDIT_SKILL_NAME = 'zidane-skill'\n\n/** Per-edit-session marker persisted on `session.metadata[EXTENSION_EDIT_METADATA_KEY]`. */\nexport interface ExtensionEditMarker {\n /** Absolute path to the extension's directory (the session's cwd). */\n dir: string\n}\n\n/** Per-edit-session marker persisted on `session.metadata[SKILL_EDIT_METADATA_KEY]`. */\nexport interface SkillEditMarker {\n /** Absolute path to the skill's directory (the session's cwd). */\n dir: string\n /** The skill's name (frontmatter-first, as discovered). */\n name: string\n}\n\n/**\n * What an editing session targets. `dir` becomes the session's cwd; `name`\n * feeds the preset wording (and the skill marker).\n */\nexport interface EditSessionTarget {\n /** Whether the session edits an extension or a skill. */\n kind: 'extension' | 'skill'\n /** Name of the extension or skill. */\n name: string\n /** Absolute path of the extension or skill directory. */\n dir: string\n}\n\n/**\n * THE canonical seed for \"open an editing session with preset context\" —\n * the session metadata both hosts stamp at creation time. The engines read\n * the marker back and inject + auto-activate the matching inline preset\n * skill ({@link buildZidaneExtensionSkill} / {@link buildZidaneSkillEditSkill}),\n * so the preset rides as real conversation context from turn one.\n *\n * @param target - The extension or skill the editing session targets.\n */\nexport function editSessionMetadata(target: EditSessionTarget): Record<string, unknown> {\n return target.kind === 'extension'\n ? { [EXTENSION_EDIT_METADATA_KEY]: { dir: target.dir } satisfies ExtensionEditMarker }\n : { [SKILL_EDIT_METADATA_KEY]: { dir: target.dir, name: target.name } satisfies SkillEditMarker }\n}\n\n/**\n * Parse a persisted {@link SkillEditMarker} back out of session metadata.\n * Tolerant: anything that isn't `{ dir: string, name: string }` → `null`\n * (older sessions, manual metadata edits, type drift).\n *\n * @param raw - Value read from `session.metadata`, of unknown shape.\n */\nexport function readSkillEditMarker(raw: unknown): SkillEditMarker | null {\n if (typeof raw !== 'object' || raw === null)\n return null\n const { dir, name } = raw as Record<string, unknown>\n if (typeof dir !== 'string' || dir.length === 0 || typeof name !== 'string' || name.length === 0)\n return null\n return { dir, name }\n}\n\n/** Which optional files a scaffold adds next to `index.ts`. */\nexport interface ExtensionScaffoldOptions {\n /** Add `ui.tsx` (desktop and web components) and a status slot drawn by it. */\n ui?: boolean\n /** Add `tui.tsx` (terminal components) and a status slot drawn by it. */\n tui?: boolean\n /** Add `index.test.ts`, a test on `createTestHost`. */\n test?: boolean\n /** Add `package.json`, `tsconfig.json` and `README.md`, so the folder is a publishable package. */\n package?: boolean\n}\n\n/**\n * The `index.ts` of a new extension: a setting and one command (palette\n * entry and slash command) that toasts it. With `ui` / `tui`, a status slot\n * whose `Status` component comes from the matching entry. The `id` MUST equal\n * the folder name, or discovery refuses the extension.\n *\n * @param name - Extension id. Must equal the extension's folder name.\n * @param opts - Whether the extension has a `ui` and / or a `tui` entry.\n */\nexport function extensionScaffold(name: string, opts: Pick<ExtensionScaffoldOptions, 'ui' | 'tui'> = {}): string {\n const withSlot = opts.ui === true || opts.tui === true\n const imports = withSlot ? 'defineExtension, Slot' : 'defineExtension'\n const slot = withSlot\n ? `\n // A statusbar item. The desktop and web apps draw \\`Status\\` from ui.tsx, the\n // terminal from tui.tsx; a host without the component draws \\`text\\`.\n ctx.add(Slot, { at: 'app.statusbar', text: '${name}', component: { component: 'Status', props: { label: '${name}' } } })\n`\n : ''\n return `import { ${imports} } from 'zidane'\n\n/**\n * ${name}: a zidane extension.\n *\n * Everything the extension adds is registered on \\`ctx\\` inside \\`setup\\`, by\n * domain: ctx.tools, ctx.hooks, ctx.commands, ctx.storage, ctx.ui, ctx.add(…)…\n * Every registration returns a Disposable and is withdrawn when the extension\n * unloads. Authoring reference: the \\`${ZIDANE_EXTENSION_SKILL_NAME}\\` skill, active\n * whenever you open this folder in zidane.\n */\nexport default defineExtension({\n apiVersion: 1,\n // Must equal the folder name.\n id: '${name}',\n version: '0.1.0',\n description: 'A zidane extension.',\n // Shown in Settings, even while the extension is off.\n settings: {\n items: {\n greeting: { type: 'string', label: 'Greeting', default: 'Hello' },\n },\n },\n setup(ctx) {\n // A palette entry and a \\`/${name}-hello\\` slash command in one registration.\n ctx.commands.register({\n id: 'hello',\n title: 'Say hello',\n slash: { name: '${name}-hello' },\n run: async () => {\n await ctx.ui.toast({ text: \\`\\${ctx.settings.get('greeting') ?? 'Hello'} from ${name}\\` })\n },\n })\n${slot} },\n})\n`\n}\n\n/** The `index.test.ts` of a new extension: loads it in a test host and runs its command. */\nfunction extensionTestScaffold(name: string): string {\n return `import { afterEach, expect, it } from 'bun:test'\nimport { createTestHost } from 'zidane/extensions/testing'\nimport extension from './index'\n\n// \\`createTestHost\\` runs the extension against in-memory ports: no terminal,\n// window or disk. Run with \\`zidane extension test\\`.\nlet host: Awaited<ReturnType<typeof createTestHost>> | undefined\nafterEach(async () => {\n await host?.dispose()\n host = undefined\n})\n\nit('loads and says hello', async () => {\n host = await createTestHost({ extensions: [extension], settings: { '${name}': { greeting: 'Hi' } } })\n expect(host.extensions().map(e => e.status)).toEqual(['active'])\n\n expect(await host.commands.run('${name}.hello')).toBe(true)\n expect(host.dialogs.shown.map(d => d.request)).toContainEqual(expect.objectContaining({ kind: 'toast', text: 'Hi from ${name}' }))\n})\n`\n}\n\nfunction uiScaffold(name: string): string {\n return `/**\n * ${name}'s components for the desktop and web apps. React and the kit are the\n * app's own (host modules): import them, never bundle your own copy. Reach\n * the backend through useRpc and useStore only.\n */\nimport { Meta, useExtension } from 'zidane-ui/extensions/kit'\n\n/** The statusbar item (\\`component: 'Status'\\` in index.ts). */\nexport function Status(props: { label?: string }) {\n const extension = useExtension()\n return <Meta title={extension.id}>{props.label ?? extension.id}</Meta>\n}\n`\n}\n\nfunction tuiScaffold(name: string): string {\n return `/** @jsxImportSource @opentui/react */\n/**\n * ${name}'s components for the terminal app. \\`react\\`, \\`@opentui/*\\` and\n * \\`zidane/tui\\` resolve to the app's live modules.\n */\nimport { Badge } from 'zidane/tui'\n\n/** The statusbar item (\\`component: 'Status'\\` in index.ts). */\nexport function Status(props: { label?: string }) {\n return <Badge tone=\"brand\">{props.label ?? '${name}'}</Badge>\n}\n`\n}\n\nfunction packageScaffold(name: string, opts: ExtensionScaffoldOptions): string {\n const files = ['index.ts', ...(opts.ui ? ['ui.tsx'] : []), ...(opts.tui ? ['tui.tsx'] : []), 'README.md']\n return `${JSON.stringify({\n name,\n version: '0.1.0',\n description: 'A zidane extension.',\n type: 'module',\n keywords: ['zidane', 'zidane-extension'],\n main: './index.ts',\n files,\n scripts: { 'test': 'zidane extension test', 'pack': 'zidane extension pack', 'publish:extension': 'zidane extension publish' },\n peerDependencies: { zidane: zidaneRangeFor() },\n }, null, 2)}\\n`\n}\n\nconst TSCONFIG_SCAFFOLD = `${JSON.stringify({\n compilerOptions: {\n target: 'ESNext',\n module: 'ESNext',\n moduleResolution: 'bundler',\n jsx: 'react-jsx',\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n allowImportingTsExtensions: true,\n },\n include: ['.'],\n}, null, 2)}\\n`\n\nfunction readmeScaffold(name: string): string {\n return `# ${name}\n\nA zidane extension.\n\n- \\`zidane extension test\\` runs the tests (\\`createTestHost\\` comes from \\`zidane/extensions/testing\\`).\n- \\`zidane extension pack\\` checks the definition, bundles \\`ui.tsx\\` and writes a folder (and, with \\`--tarball\\`, a tarball) ready to install.\n- \\`zidane extension publish\\` publishes to npm after the same checks.\n\nInstall from GitHub (\\`owner/repo\\`) or npm (\\`${name}\\`) in Settings → Extensions.\n`\n}\n\n/**\n * Every file of a new extension, by path relative to its folder.\n *\n * @param name - Extension id; the folder name.\n * @param opts - Which optional files to add.\n */\nexport function extensionScaffoldFiles(name: string, opts: ExtensionScaffoldOptions = {}): Record<string, string> {\n const files: Record<string, string> = { 'index.ts': extensionScaffold(name, opts) }\n if (opts.ui)\n files['ui.tsx'] = uiScaffold(name)\n if (opts.tui)\n files['tui.tsx'] = tuiScaffold(name)\n if (opts.test)\n files['index.test.ts'] = extensionTestScaffold(name)\n if (opts.package) {\n files['package.json'] = packageScaffold(name, opts)\n files['tsconfig.json'] = TSCONFIG_SCAFFOLD\n files['README.md'] = readmeScaffold(name)\n }\n return files\n}\n\n/**\n * Writes a new extension into `dir` (created; it must not exist yet).\n *\n * @param dir - The extension folder. Its name should equal `name`.\n * @param name - Extension id.\n * @param opts - Which optional files to add.\n * @returns The paths written, relative to `dir`.\n */\nexport function writeExtensionScaffold(dir: string, name: string, opts: ExtensionScaffoldOptions = {}): string[] {\n const error = validateExtensionName(name)\n if (error)\n throw new Error(error)\n if (existsSync(dir))\n throw new Error(`An extension already exists at ${dir}.`)\n mkdirSync(dir, { recursive: true })\n const files = extensionScaffoldFiles(name, opts)\n for (const [file, content] of Object.entries(files))\n writeFileSync(join(dir, file), content, 'utf8')\n return Object.keys(files)\n}\n\n/**\n * Id contract of an extension, which is also its folder name: lowercase\n * words of letters and digits joined by single hyphens, starting with a\n * letter (the loader's `EXTENSION_ID_PATTERN`).\n */\nconst EXTENSION_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/\n\n/**\n * Why a proposed extension name is refused, or `null` when it is fine.\n *\n * @param name - The name as typed (trimmed here).\n */\nexport function validateExtensionName(name: string): string | null {\n const trimmed = name.trim()\n if (trimmed.length === 0)\n return 'Extension name is required.'\n if (!EXTENSION_NAME_PATTERN.test(trimmed))\n return 'Use lowercase letters, numbers, and single hyphens, starting with a letter (e.g. my-extension).'\n return null\n}\n\n/** A new extension to scaffold. */\nexport interface CreateExtensionInput extends ExtensionScaffoldOptions {\n /** Lowercase words joined by hyphens, starting with a letter: the folder name and the extension `id`. */\n name: string\n /** `user` (default): `<userDir>/extensions/<name>`. `project`: `<checkout>/<prefix>/extensions/<name>` of `cwd`. */\n scope?: 'user' | 'project'\n /** The folder a project extension goes with. */\n cwd?: string\n}\n\n/** A scaffolded extension. */\nexport interface CreateExtensionResult {\n /** Its folder (the cwd of the session that writes it). */\n dir: string\n /** Its name (the folder's). */\n name: string\n /** A project extension: it stays unloaded until the project is trusted. */\n needsTrust?: boolean\n}\n\n/** Where {@link createExtension} writes. */\nexport interface CreateExtensionDeps {\n /** The folder `user` extensions are discovered under (`~/.zidane`). */\n userDir: string\n /** A cwd's checkout root (the working tree it sits in, or the folder itself): a project extension is a file of that checkout. */\n resolveCheckoutRoot: (cwd: string) => string\n}\n\n/**\n * The folder {@link createExtension} writes a new extension to.\n *\n * @param input - The name, scope and folder.\n * @param deps - Where user and project extensions go.\n */\nexport function extensionTargetDir(input: Pick<CreateExtensionInput, 'name' | 'scope' | 'cwd'>, deps: CreateExtensionDeps): string {\n const name = input.name.trim()\n // The prefix extension discovery reads (`ZIDANE_PREFIX`).\n return input.scope === 'project'\n ? join(deps.resolveCheckoutRoot(input.cwd ?? process.cwd()), process.env.ZIDANE_PREFIX?.trim() || '.zidane', 'extensions', name)\n : join(deps.userDir, 'extensions', name)\n}\n\n/**\n * Scaffolds a new extension folder: `index.ts`, plus the optional files\n * `input` asks for. Throws when the name is invalid or the folder exists.\n *\n * @param input - The name, scope, folder and optional files.\n * @param deps - Where user and project extensions go.\n */\nexport function createExtension(input: CreateExtensionInput, deps: CreateExtensionDeps): CreateExtensionResult {\n const name = (input.name ?? '').trim()\n const error = validateExtensionName(name)\n if (error)\n throw new Error(error)\n const project = input.scope === 'project'\n const dir = extensionTargetDir({ ...input, name }, deps)\n writeExtensionScaffold(dir, name, input)\n return { dir, name, ...(project ? { needsTrust: true } : {}) }\n}\n\n/**\n * Locate the shipped `docs/` directory (`docs/EXTENSIONS.md` + siblings).\n *\n * The docs ship via `package.json` `files`, so an installed consumer has\n * `<root>/node_modules/zidane/docs` and the repo itself has `<repo>/docs`.\n * Returns the first existing candidate, or `null` (the skill then falls back to\n * the literal `node_modules/zidane/docs` hint, which the model's file tools can\n * still attempt from cwd).\n *\n * @param cwd - Directory used for the `node_modules/zidane/docs` fallback lookup. Defaults to `process.cwd()`.\n */\nexport function resolveExtensionDocsDir(cwd: string = process.cwd()): string | null {\n const candidates: string[] = []\n\n // 1) The installed (or self-linked) zidane package, via its package.json\n // (exposed through `exports[\"./package.json\"]`).\n try {\n const req = createRequire(import.meta.url)\n candidates.push(join(dirname(req.resolve('zidane/package.json')), 'docs'))\n }\n catch {\n // No resolvable package (compiled binary / odd layout) — fall through.\n }\n\n // 2) Relative to this module: the bundled chunk in `dist/` -> `../docs`, or\n // walking up from `src/` when running from source.\n try {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 6; i++) {\n if (existsSync(join(dir, 'docs', 'EXTENSIONS.md'))) {\n candidates.push(join(dir, 'docs'))\n break\n }\n dir = dirname(dir)\n }\n }\n catch {\n // `import.meta.url` is virtual under `bun build --compile` — fall through.\n }\n\n // 3) cwd-relative — the project the author is actually editing.\n candidates.push(join(cwd, 'node_modules', 'zidane', 'docs'))\n\n for (const candidate of candidates) {\n if (existsSync(join(candidate, 'EXTENSIONS.md')))\n return candidate\n }\n return null\n}\n\n/** Entry-file probe order for an extension dir (mirrors the loader's discovery). */\nconst EXTENSION_ENTRY_FILES = ['index.ts', 'index.tsx', 'index.mjs', 'index.js'] as const\n\n/**\n * If `cwd` sits inside a zidane extension, return that extension's root dir,\n * else `null`. Mirrors the discovery layout\n * `<scope>/.{agents,zidane}/extensions/<name>/` — the root is the `<name>` dir\n * (parent `extensions`, grandparent `.agents` / `.zidane`) holding an entry\n * file. Walks up from `cwd`, so the nearest enclosing extension wins. Lets the\n * hosts surface the authoring skill for a plain `zidane` launched from an\n * extension folder, not just sessions opened through the edit/create flow.\n *\n * @param cwd - Directory to start the upward walk from. Defaults to `process.cwd()`.\n */\nexport function extensionDirForCwd(cwd: string = process.cwd()): string | null {\n let dir = resolve(cwd)\n // Bounded walk to the filesystem root (the `parent === dir` guard is the real\n // terminator; the cap is just belt-and-braces against pathological inputs).\n for (let i = 0; i < 64; i++) {\n const parent = dirname(dir)\n if (\n basename(parent) === 'extensions'\n && (basename(dirname(parent)) === '.agents' || basename(dirname(parent)) === '.zidane')\n && EXTENSION_ENTRY_FILES.some(f => existsSync(join(dir, f)))\n ) {\n return dir\n }\n if (parent === dir)\n break\n dir = parent\n }\n return null\n}\n\n/**\n * The authoring reference embedded into the skill body. Kept INLINE (not a\n * filesystem path) on purpose: a `bun build --compile` TUI binary has no\n * resolvable `node_modules/zidane/docs`, no real `import.meta.url`, and the\n * edited extension's own cwd has no `node_modules`, so a path reference dead-\n * ends there. The model gets the API verbatim on activation regardless of host.\n *\n * Mirrors the extension API (`src/extensions/api/*.ts`). `test/extension-authoring.test.ts`\n * fails when a ctx member is added there and not named here.\n */\nconst EXTENSION_AUTHORING_CHEATSHEET = `## The extension\n\nA zidane extension is a folder \\`<scope>/.{agents,zidane}/extensions/<id>/\\` whose \\`index.{ts,tsx,mjs,js}\\`\ndefault-exports a definition. It runs in-process in every host (terminal, desktop, web server, daemon).\n\n\\`\\`\\`ts\nimport { defineExtension } from 'zidane'\n\nexport default defineExtension({\n apiVersion: 1, // required\n id: 'my-ext', // MUST equal the folder name; lowercase words joined by '-', starting with a letter\n displayName: 'My ext', // optional, shown in Settings\n icon: { gui: 'i-ph-puzzle-piece', tui: '◆' }, // optional; settings sidebar icon (gui: a Phosphor icon class); default: a letter badge\n version: '0.1.0', // optional semver\n description: 'what it does',\n dependencies: { other: '^1.0.0' }, // load order + install hints; a missing one never fails the load\n settings: { items: { greeting: { type: 'string', label: 'Greeting', default: 'Hello' } } },\n ui: 'ui.tsx', // optional; default ui.tsx when present (desktop + web components)\n tui: 'tui.tsx', // optional; default tui.tsx when present (terminal components)\n async setup(ctx) {\n // register everything through ctx; may be async; may return a cleanup\n return () => {}\n },\n})\n\\`\\`\\`\n\nA plain object with the same fields works too (\\`import type { ExtensionDefinition }\\`, erased at runtime).\nEvery id the extension contributes is published under it: command \\`hello\\` → \\`my-ext.hello\\`, panel \\`board\\` → \\`my-ext/board\\`.\n\n## Lifetime\n\n- Every registration returns a Disposable (\\`{ dispose() }\\`); UI items return a Handle (\\`update(patch)\\`, \\`dispose()\\`, \\`current\\`).\n- Registrations work any time until the extension unloads (after awaits, from handlers). Unload withdraws them all, in reverse order.\n- ctx.scope() → a child ctx with its own lifetime (and signal): dispose it to withdraw what was registered through it (\"while connected\", \"while a setting is on\").\n- ctx.onCleanup(fn) runs fn at unload. ctx.signal aborts at unload.\n- Every callback is guarded: timeouts, errors attributed to the extension, an error budget that auto-disables a failing extension (reason shown in Settings), stall detection.\n\n## ctx core\n\n- ctx.id / ctx.version / ctx.dir (the folder; use it instead of import.meta.dirname, undefined on Node hosts) / ctx.source ('project' | 'user' | 'config' | 'inline' | 'builtin')\n- ctx.app → { name: 'zidane', version, host: 'tui' | 'desktop' | 'daemon' | 'server' | 'sdk', platform }\n- ctx.paths → cwd, projectRoot?, dataDir, cacheDir, configDir?, stateDir?\n- ctx.logger (attributed to the extension), ctx.signal, ctx.scope(), ctx.onCleanup(cleanup)\n- ctx.add(Token, item) → Handle (UI tokens below)\n- ctx.extensions.list() / ctx.extensions.get(id) → { id, displayName, icon?, version?, source, status: 'active' | 'disabled' | 'failed', error? }\n\n## Domains\n\nTools\n- ctx.tools.register(name, toolDef) — toolDef: { spec: { name, description, inputSchema }, execute(input, toolCtx), isConcurrencySafe?, replay? }.\n isConcurrencySafe: true (or a function of the input) = read-only, runs in parallel; default false (a write barrier).\n- ctx.tools.metadata(namespace, { contextRows?, summarize? }) — claim a tool-result metadata namespace (context panel rows).\n- ctx.tools.transform(editor => …) — edit the tool catalog per run: editor.list/get/origin/add/update/remove, editor.phase ('pre-run' | 'run'), editor.sessionId.\n- ctx.tools.reload() / ctx.tools.list()\n\nHooks\n- ctx.hooks.on(event, handler, { sessionId? }) — observe (read-only event): every AgentHooks key ('turn:after', 'tool:after', 'agent:done', 'child:*' …) plus host events 'run:settled' { sessionId, runId, ok, aborted, session: { turns, metadata } | null } (the session as the run left it, tool results included; null when it is gone) and 'session:event' { sessionId, kind: 'done' | 'error' | 'needs-attention' }.\n- ctx.hooks.intercept(name, (input, current) => next | undefined, { priority?, match? }) — decide; chained in load order (then priority, higher first); return undefined to keep \\`current\\`.\n Interceptors: 'run', 'behavior', 'prompt' (edit text/attachments/delivery, or { handled: true }), 'system' (string), 'context' (wire messages),\n 'tool.call' ('allow' | 'ask' | { block } | { patch } | { result }; match: tool names), 'tool.result' ({ result, isError, metadata? }: metadata adds namespaces to the stored result, e.g. { myExt: {...} }; input.metadata is read-only), 'validation',\n 'skills.activate' (names to activate), 'mcp.tools', 'provider' (wrap a Provider), 'retry', 'compaction', 'title', 'shell.env',\n 'http.request', 'http.response' (OpenAI-compatible, OpenAI, Anthropic but not Bedrock, Google, Vertex and googleCompatible routes; not the Codex WebSocket), 'tracing.redact' (synchronous), 'content-ref.resolve'.\n\nCommands and keys\n- ctx.commands.register({ id, title, description?, group?, keywords?, icon?, when?: 'always' | 'session' | 'idle', enabled?, palette?: boolean | { featured?, suggested? },\n bind?: 'mod+shift+t' | [...], scope?: 'global' | 'composer' | 'transcript' | 'sidebar' | 'modal' | 'panel:<id>' | 'screen:<id>', editable?, mode?, hint?,\n slash?: { name, aliases?, complete?(prefix), argumentHint? }, run?(invocation), steps? }) — one registration = palette entry + slash command + keybinding.\n invocation: { source: 'palette' | 'slash' | 'key' | 'menu' | 'api', sessionId?, args, signal } plus, from a slash, the step context (emit, runAgent, generateData, interaction…).\n From a keybinding, run returning false passes the key on. Users can rebind every key; Esc and ctrl+c are refused. title/description/enabled are Bindable.\n steps: the slash pipeline [{ run(stepCtx) }]; a step returns { data } to merge into stepCtx.data for later steps.\n- ctx.commands.provider({ id, group?, items({ query, sessionId?, busy }), run(itemId, { sessionId? }), subscribe?(refresh) }) — dynamic palette entries;\n run may return { kind: 'prompt' | 'insert', text } or { kind: 'notify', text, tone? }. Item ids are published as '<ext>/<id>'.\n- ctx.commands.run(id, { args?, sessionId? }) / ctx.commands.list() — any command, built-ins included.\n- ctx.keymap.mode.push(name, { priority?, sessionId? }) → Disposable (bindings with \\`mode\\` fire only while it is on top); ctx.keymap.mode.current();\n ctx.keymap.bindings(commandId) — effective keys after user overrides; ctx.keymap.list() → every command (built-in and extension) with bindings, scope, mode;\n ctx.keymap.active({ sessionId? }) → the mode stack, current last; ctx.keymap.dispatch(chord, { sessionId? }) → Promise<boolean> presses a chord in the client viewing the session (rejects NoUiError without one).\n\nCatalogs (agents, models, providers, MCP, skills). Each has register (where it makes sense), transform(editor) and reload(), list().\nTransforms are synchronous and replayed in load order whenever a registration changes or reload() is called: keep them pure, load data first, call reload() when it changes.\nThey may edit or remove built-ins; Settings marks the change and restores it when the extension is off.\n- ctx.agents.register(profile) / ctx.agents.subagents.register({ name, description?, system?, model?, thinking?, tools?, readonly? }) /\n ctx.agents.transform(editor: list/get/update/remove, default.get/set, subagents.{list,get,add,update,remove,override(name, def => def)}) / ctx.agents.reload() / ctx.agents.list()\n- ctx.models.transform(editor: list(providerId?)/get/update/remove, default.get/set) / ctx.models.default() / ctx.models.reload() / ctx.models.list()\n- ctx.providers.register(descriptor) / ctx.providers.transform(editor + models.set(id, list, { account? })/update/remove) / ctx.providers.reload() / ctx.providers.list()\n (with account: that account's own list, which pickers and ctx.models.list() use for it; [] gives it back the provider's list; transforms re-run when accounts connect or switch)\n- ctx.mcp.register(serverConfig) / ctx.mcp.transform(editor) / ctx.mcp.reload() / ctx.mcp.list() (servers: name, status, tools, url?, command?, toolPrefix — tool.call / tool.result see '<toolPrefix><tool>') /\n ctx.mcp.call(server, tool, args?, { timeoutMs?, signal? }) — call a tool outside a run\n- ctx.skills.register({ name, description, instructions, … }) / ctx.skills.transform(editor + autoinvoke(name, on)) / ctx.skills.reload() / ctx.skills.list()\n- ctx.plugins.load(dir | { dir, skills?, mcp?, namePrefix? }) → LoadedPlugin & Disposable — an Agent Plugin (relative to ctx.dir); check \\`ok\\` / \\`diagnostics\\`\n- ctx.instructions.add(text, { sessionId?, key? }) → Disposable — system-prompt text; no sessionId = every session; a key replaces an earlier entry\n\nSessions, generation, workspace, artifacts (reject on hosts without a session runtime)\n- ctx.sessions acts as the extension (what it creates is tagged with the id):\n create({ cwd, prompt?, title?, agentId?, providerKey?, modelId?, tools?, subscribe? }) → id, list({ where?, sort?, limit?, cursor?, archived? }) → { rows, cursor? } (a session query, below), read(id), send(id, prompt), steer(id, message), cancel(id),\n transcript(id, limit?), wait(id, timeoutSec?), events(query?), pending(id?), answer(requestId, response), approve(requestId, decision),\n get(id) → { id, title?, cwd, agentId?, providerId?, modelId?, thinking?, status }, remove(id), compact(id), switchModel(id, { providerId, modelId }),\n switchAgent(id, agentId), setThinking(id, level), submit(text, { sessionId?, delivery?: 'auto' | 'steer' | 'followUp' }) (as if typed: commands,\n interceptors, skills apply), synthetic(id, { role, text }) (the model sees it next turn), note(id, blocks) (display-only, persisted; blocks\n { text, detail?, custom?: { renderer, data } }, text is the markdown fallback).\n- ctx.sessions.facets.register({ key, compute(session) → JSON | undefined, on?: ['created' | 'settled' | 'metadata'] }) → Disposable — a value per session (5 s, 8 KiB) queries read\n as 'facet.<ext>.<key>'; works on every host. ctx.sessions.facets.refresh(sessionId?) asks the host to compute this extension's facets again.\n- Session query (spaces, sidebar filters, list): a condition { field, eq | ne | in | nin | gt | gte | lt | lte | contains | exists } or { all: [...] } | { any: [...] } | { not }.\n Fields: id, title, status, agentId, projectRoot, cwd, createdAt, updatedAt, archived, favorite, spaceId, providerKey, modelId, environment.kind, … or 'facet.<ext>.<key>[.<prop>]'.\n Times take epoch ms, 'now', '-7d' / '+12h' (s m h d w) or ISO dates. Sort: [{ by, dir? }] (missing values last). An invalid query throws SessionQueryError naming each problem.\n- ctx.generate.text({ prompt, system?, model?, signal? }) / ctx.generate.object({ prompt, schema, … }) (validated) / ctx.generate.decide(request) — outside any session, the user's provider resolution, usage counted.\n ctx.generate.image({ prompt, model?, n? (1-4), aspectRatio?, references?: [{ path } | { mimeType, data }], save?: true | { sessionId? } }) → { images: [{ mimeType, data, path? }] }: the generate_image tool's models; save writes under the session's folder and records file artifacts.\n On a host without model access text/object reject and decide.available is false (decide answers { ok: false, reason: 'unavailable' }): check decide.available first.\n- ctx.workspace.available() / exec(command, { cwd?, env?, timeout?, signal? }) / readFile / writeFile / listFiles / fileSize — in the session's execution\n context (sandbox-correct; host child_process targets the wrong filesystem under docker/e2b). Guard with await ctx.workspace.available().\n- ctx.artifacts.kinds.register({ kind: '<ext>.<kind>', label, detect?, validateMeta?, render?, resolve?, badge? }) / ctx.artifacts.list({ scope?, kind? }) /\n ctx.artifacts.find(kind, uri) / ctx.artifacts.record({ kind, uri, label, meta? }). Detail views: ctx.add(Renderer, { for: { artifact: kind } }).\n\nStorage (scoped to the extension)\n- ctx.storage.kv(scope: 'user' | 'project' | 'session', sessionId?) → { ready(), get (sync), set, delete, scan(prefix?), subscribe }. user and project load before setup, a\n session before each of its runs and / commands; any other read (a session read cold, e.g. from run:settled after a restart): await kv.ready() first.\n- ctx.storage.store(name, schema, initial, { scope, sessionId?, version?, migrate? }) → typed, reactive, synced to every window and client: get, set(value | fn), subscribe, select(pick)\n- ctx.storage.memory(key, init) — survives a reload of the extension, not a restart\n- ctx.storage.path(scope | 'cache' | 'data', ...segments) — a folder of the extension's own for files\nSchemas everywhere (stores, RPC, generate.object) are Standard Schema (zod, valibot, arktype; types inferred) or plain JSON Schema (typed unknown).\n\nSettings (one schema in the definition; shown even while the extension is off)\n- keys: a letter or _, then letters, digits, _, . or - ('baseUrl', 'record-mentions'); keep a key's name across versions to carry users' saved values over\n- types: boolean, number (min/max/step), string (secret → auth store, multiline, pattern, placeholder, validate), enum (options), model (multi),\n action (button, run({ get, set, signal }) → { message?, tone?, patch? }, status({ get }), scope?: 'chat' | 'approve' | 'admin' (default admin: a server client below it gets a disabled button)): settings are static, so an action has no ctx; get/set reach the extension's own settings\n- common fields: label, description?, section? (settings.sections), page? (settings.pages; a sectioned item takes its section's page), scopes? (['user', 'project'] default; 'session'), apply? ('live' | 'agent-rebuild' | 'restart')\n- schema: pages?: [{ id, label, description?, icon? }] (default: one page named after the extension), sections?: [{ id, label, description?, page? }], category?: { label, icon? } (a sidebar group of its own; else under Extensions). Settings search covers every row.\n- ctx.settings.get(key, { sessionId? }) (typed from the schema; session → project → user → config → default) / ctx.settings.onChange(key, fn) / ctx.settings.locked(key) (set by a declarative config entry)\n\nIntegrations (Settings shows Connect / Disconnect per account; secrets live in the auth store)\n- ctx.integrations.register({ id, name, description?, icon?, methods: [{ type: 'apiKey', id, label, placeholder?, help? } | { type: 'oauth', flow: 'pkce' | 'device', … } | { type: 'command', command, … } | { type: 'custom', id, label, connect: io => ({ credential, account?, label? }), refresh? }], beforeConnect?, verify?, account?, parseCommandOutput?, legacyCredentialKey? }) → handle (dispose, update({ name, description, icon, methods, beforeConnect }))\n- integrationMethodFromOAuthProvider(oauthProvider) → a custom method from a zidane/oauth provider flow\n- several connections per integration (one per account, the first is the default): ctx.integrations.connections(id) / ctx.integrations.connection(id, { account? }) /\n ctx.integrations.setDefault(id, account) / ctx.integrations.resolve(connection) → credential (OAuth refreshed) / ctx.integrations.connect.key(id, key) /\n ctx.integrations.connect.start(id, methodId) / ctx.integrations.disconnect(id, { account? }) / ctx.integrations.onChange(id, fn)\n- a provider that signs in with one: ctx.providers.register({ …, integration: '<id>' }) (pickers, \"Sign in\" and usage follow the integration)\n\nRPC and contracts\n- const Api = Rpc.define({ id: '<ext>' | '<ext>.<name>', methods: { m: { input?, output?, errors?: { name: schema }, scope?: 'chat' | 'approve' | 'admin' } }, events: { e: { schema } } })\n- ctx.rpc.register(Api, { m: async (input, call) => output | call.error('name', message, data) }) → registration.events.emit(name, data);\n call.caller is { kind: 'extension', id } | { kind: 'ui' } | { kind: 'client', clientId }\n- ctx.rpc.client(Api) → typed methods (reject with RpcCallError: a declared error type, or rpc.invalid-input / invalid-output / unavailable / aborted / internal / forbidden) and events.on/subscribe.\n Protocol clients call POST /rpc/<ext>/<method>, refused (rpc.forbidden) below the method's scope (default chat). Inputs and outputs are validated both ways.\n- Contract.define<T>('<ext>.<name>') / Registry.define<T>('<ext>.<name>'): ctx.contracts.provide(token, impl) / ctx.contracts.use(token) → Live accessor\n (get() → { status: 'pending' | 'active' | 'inactive' }, value(), subscribe) / ctx.contracts.add(registry, item) / ctx.contracts.list(registry) → Readable of every item\n\nDialogs and layout (ctx.ui); each goes to the client viewing \\`sessionId\\`, else the last active one; no client → a dialog rejects NoUiError, a toast or notify\nwaits for one (up to 20, 60 s each; a dropped toast resolves undefined); timeoutMs → DialogTimeoutError\n- ctx.ui.confirm({ title, message?, confirmLabel?, danger? }) / ctx.ui.prompt({ title, placeholder?, initial?, secret? }) / ctx.ui.select({ title, options, multi?, actions? }) /\n ctx.ui.form({ title, schema (settings shape), values? }) / ctx.ui.askUser(questionPayload) / ctx.ui.presentPlan({ title, plan, steps? }) /\n ctx.ui.toast({ text, tone?, actions? }) → the clicked action id / ctx.ui.notify({ title, body? }) (OS notification)\n- ctx.ui.panel.open(id) / ctx.ui.panel.close(id) / ctx.ui.panel.toggle(id) / ctx.ui.panel.state() / ctx.ui.navigate({ screen } | { session } | { newSession: true })\n- ctx.composer.insert(text) / ctx.composer.setText(text) / ctx.composer.attach(files) — on the client viewing the session\n- ctx.composer.completions.register({ id, trigger, label, suggest, parseReferences, … }) — popover completions on a trigger character ('/', '@', '#' are built in)\n- ctx.composer.autocomplete.register({ id, suggest({ text, cursor, cwd, signal }), deadlineMs? }) — ghost text, accepted with Tab\n- ctx.desktop — ONLY in the desktop app's main process (undefined elsewhere: guard it): ctx.desktop.electron<typeof import('electron')>(), ctx.desktop.windows.list(), ctx.desktop.windows.on('open' | 'close' | 'focus', fn)\n\n## UI tokens: ctx.add(Token, item) → Handle\n\nImport the tokens from 'zidane'. Items are data the hosts draw natively; any item may name a component\n(\\`component: { component: 'ExportName', props? }\\`) from ui.tsx (desktop + web) and tui.tsx (terminal); a host without it draws the data or \\`fallback\\`.\nFields marked Bindable take a value or a Readable (\\`store.select(fn)\\`): the item follows it.\n- Panel — side tab and/or project-home block: { id, label, icon?, scope?: 'session' | 'project' | 'both', surfaces?: ['side' | 'home'], home?, load(lc) → { sections, actions?, controls?, badge?, empty? }, subscribe?(emit), onAction?(actionId, payload, lc), component? }.\n Section items: row, markdown, kv, stats, heatmap, bars, custom ({ renderer, data, fallback }).\n- Slot — a small item in a named place: at: 'app.statusbar' | 'app.titlebar' (max 8) | 'session.header' | 'composer.above' (banner \\`lines\\`) | 'composer.below' | 'composer.badges' (needs sessionId; max 8) |\n 'sidebar.top' | 'sidebar.bottom' (full-width rows pinned above / below the session list, outside its scroll) | 'sidebar.footer' (chips in the footer strip) | 'transcript.turn-end' | 'home.block' | 'settings.section'. Fields: text, lines, count, icon, tone, color, loader, highlight, title, panel, command, order, timeoutMs, sessionId, component.\n- Screen — { id, title, presentation?: 'screen' | 'modal', component (required), fallback? (markdown) }; open with ctx.ui.navigate({ screen: id }).\n- Renderer — { for: { block } | { language } | { tool } | { artifact }, component, fallthrough? }: custom transcript blocks (note/emit \\`custom: { renderer }\\`, panel custom items), fenced code by language, tool result rows, artifact details.\n- SettingsPage — { id, title, icon?, order? (after the schema's pages, default 100), schema? | component? }: one more page among the extension's settings pages\n- Webview — { id, title, src: { url } | { file }, placement: 'panel' | 'screen', rpc?: [contract ids] }: sandboxed; the page posts { type: 'zidane:rpc', id, contract, method, input } to window.parent and gets 'zidane:rpc:result'.\n- Menu — native menu entry (desktop): { menu: 'app' | 'session' | 'tray', label, command, accelerator?, enabled? }\n- ThemeToken — a full Theme { id, label, colors, select, surfaces, syntax }; namespace the id\n- Reference — { id: '<ext>.<what>', patterns: [{ regex, flags?, key? }], surfaces?, resolve(key) → { label, tone?, status?, href?, prompt?, card? } }: ticket ids / URLs become chips\n- BadgeAction — { id, match?: { kinds?, artifactKinds?, references? }, actions(target), run(actionId, target, ctx) → { copy?, insert?, open?, reveal?, notice? } }\n- LinkHandler — { id, match: prefix | RegExp | (url) => boolean, open(url, { sessionId?, source }) → false passes it on, priority? }: opens links the extension understands before the app (desktop and web: Alt opens the default way; the TUI has no bypass and never routes sign-in links)\n- ContextSection — { placement: 'breakdown', contributor } | { placement: 'panel', section: { id, title, rows?, load?, actions? } }\n- Space — a sidebar view of sessions: { id (not 'default'), name, icon?, query, groupBy?: { field, labels?, order? }, sortBy?: [{ field, dir? }], newSession?: { agentId?, providerKey?, modelId?, effort? }, items? (SidebarRows may list in it), order? }\n- SidebarFilter — { id, label, icon?, kind: 'toggle' (query) | 'select' | 'multi' (options: [{ id, label, query }]), spaces?: 'all' | space ids }\n- SidebarSort — { id, label, sortBy: [{ field, dir? }] }; SidebarGroup — { id, label, groupBy: { field, labels?, order?: 'asc' | 'desc' | [keys] } }\n- SidebarDecoration — { id, where?, icon?, tone?, subtitle? (a string drawn as given, or { template: '{facet.<ext>.branch} · {modelId}' }), chips?: [{ text } | { template } | { facet }], component? } on matching session rows\n- SidebarRow — a row beside sessions: { id, space ('default', own id, '<ext>/<id>'), group?, title, subtitle?, icon?, tone?, status?, time?, badges?, actions?: [{ id, label, command? }], open?: { command } | { panel } | { screen } | { session }, children? (query) }\n\n## Sidebar (spaces, filters, facets)\n\n- A Space lists the sessions its query matches, the sessions started in it (tagged '<ext>/<id>', which then leave Default) and the Main session. ctx.sessions.create does not tag: make the query match your sessions, usually through a facet.\n- Data fields (name, label, query, options, sortBy, groupBy, newSession, a row's state and children, a decoration's content) are Bindable: pass store.select(...) and the apps follow it; it pushes only when the value changes. Ids, placement, actions, open and components change through handle.update.\n- Facets are how a query sees your data: ctx.sessions.facets.register({ key: 'botId', compute: s => map[s.id] ?? null }) → 'facet.<ext>.botId'. Disposing a facet deletes its stored values. compute runs on created / settled / metadata,\n possibly before create() resolves: after you store the new id, await ctx.sessions.facets.refresh(id). A throw or timeout keeps the last value; null / undefined clears it.\n- Filters, sorts and groupings are choices the user picks (nothing applies until then); a Space's groupBy / sortBy are its defaults.\n- SidebarRow lists only in 'default' or a Space with items: true (own items too); children: a query of the sessions nested under it.\n A row's command gets args = the row's own id ('bot-1', never '<ext>/bot-1'); parse it with an anchored regex (/^bot-/). A bare command id is your own ('chat' runs '<ext>.chat'); another extension's is '<ext>.<id>'.\n- Text is literal unless it is { template }: put user text (names, titles) in plain strings or { text } chips, or escapeTemplate(text) inside a template; a lone brace in a template throws.\n- Queries: in: [] matches nothing (nin: [] everything), so { field: 'agentId', in: ids } needs no empty case. projectName is the folder name. Sorts break ties by id.\n- Every query, sort, grouping and template is validated by ctx.add / update (UiQueryError, a SessionQueryError, naming the path from the item's field). Caps per extension: 32 spaces, 64 filters, 32 sorts, 32 groups, 64 decorations, 500 rows (RangeError past them). Templates print times as epoch ms.\n- ctx.sessions.list({ where?, sort?, limit?, cursor?, archived? }) → { rows, cursor? }: rows are { id, status ('idle'|'running'|'blocked'|'completed'|'error'), ...fields, facets, worker }. Pass the same where/sort/archived with each cursor.\n- Desktop and web show extension spaces only with Spaces on; the terminal app always.\n\n## UI entries\n\nui.tsx (desktop + web): React components bundled by the app (compileUiEntry); each behind an error boundary.\n- Import React and 'zidane-ui/extensions/kit' only as host modules; other files and packages are bundled. \\`zidane/*\\` values, Node, Electron, Bun and @opentui imports are refused (type-only imports are erased).\n- Kit: useExtension, useRpc(contractOrId), useStore(name, { scope?, sessionId?, initial }), useSettings() → { values, locked, set }; Raised, Tray, EmptyState, PanelEmpty, PanelLoading, LoadError,\n CodePanel, SectionLabel, Meta, MetaChip, CountBadge, Keycap, Markdown, HighlightedCode, SegmentSpinner, Tooltip, Menu/MenuRow/MenuSection, buttonClass/BTN_PRIMARY/BTN_SECONDARY, IconButton, CopyButton,\n FormSection, FormCard, SwitchRow, ToggleSwitch, SegmentedRow, SliderRow, Segmented, TabPill, INPUT_CLASS, ErrorNote.\ntui.tsx (terminal): OpenTUI React components imported in-process; \\`/** @jsxImportSource @opentui/react */\\` at the top; react, @opentui/* and zidane/tui resolve to the app's live modules.\n- Kit from 'zidane/tui': Modal, List, Form, Markdown, Badge, EmptyState, KeyHint, Spinner, useColors/useTheme/useSurfaces, useExtension, useRpc(contract), useStore(name, { scope? }), useSettings().\nBoth: same export names, so one \\`component\\` name draws in every host. Reach the backend only through RPC and stores. Renderer props: language { language, code }, tool { tool, callId, text }, artifact { artifact }, block { data }.\n\n## Testing\n\n\\`\\`\\`ts\nimport { createTestHost } from 'zidane/extensions/testing'\nconst host = await createTestHost({ extensions: [extension], settings: { 'my-ext': { greeting: 'Hi' } } })\nhost.extensions() // [{ id, status, error? }]\nhost.ctx('my-ext') // the ctx setup received\nawait host.fire('tool:after', payload) // observers, then waits for them\nawait host.intercept('tool.call', input, 'allow', { matchKey: 'shell' })\nawait host.rpc(Api).method(input) // as another extension\nawait host.commands.run('my-ext.hello', { args, sessionId }); host.commands.list()\nhost.dialogs.answer('confirm', true); host.dialogs.shown // scripted dialogs\nhost.ui.items('slot'); host.ui.client.calls // UI items; panel/navigate/composer/notify calls\nhost.registry.ui.loadPanel(id, { surface: 'side' }, cwd); host.registry.catalogs.tools.read({ phase: 'run' })\nhost.settings.put('my-ext', 'user', values); host.storage.snapshot({ extensionId, scope })\nawait host.facets({ id: 's1', metadata }, 'settled') // { 'my-ext.key': value } as a host computes them\nawait host.settled(); await host.dispose()\n\\`\\`\\`\nOptions: ports (sessions/generate/workspace/artifacts host ports, desktop), paths, host kind, budget, onLog. Pass { definition, dir } to set ctx.dir (the folder name must equal the id).\n\n## CLI\n\n- zidane extension create <name> [--ui] [--tui] [--no-test] [--dir <parent>] [--scope user|project] — scaffold index.ts, index.test.ts, package.json (+ ui.tsx / tui.tsx)\n- zidane extension test [dir] [-- bun test args] — runs the tests; maps zidane, zidane/extensions, zidane/extensions/testing when the folder has no node_modules/zidane\n- zidane extension pack [dir] [--out] [--tarball] [--no-setup] — validates the definition, runs setup in a test host, bundles ui.tsx, checks webview files and package.json \\`files\\`, writes <dir>/.pack/<id>\n- zidane extension publish [dir] [--dry-run] [--tag] [--access] — the pack checks, refuses private / version mismatches, then npm publish\n\n## Patterns\n\n- One file per area, \\`setup\\` wires them (see the kitchen-sink example in the zidane repo: examples/extension-kitchen-sink).\n- State in a store, UI bound to it: \\`ctx.add(Slot, { at: 'app.statusbar', text: counter.select(c => String(c)) })\\` — no manual update calls.\n- Something on only while a setting is on: build it in ctx.scope(), dispose the scope in ctx.settings.onChange.\n- Transforms that read settings or loaded data: call ctx.<catalog>.reload() when those change.\n- Long work: pass ctx.signal (or a scope's) to fetch/exec so unload cancels it.\n- Session-bound commands: \\`when: 'session'\\` and use invocation.sessionId; dialogs and composer calls take { sessionId }.\n- Share an API with other extensions through Rpc.define or Contract.define in a tokens file both import; Live accessors may be pending or inactive — read them where you use them.\n\n## Pitfalls\n\n- id ≠ folder name → discovery refuses the extension.\n- \\`./ui\\` resolves to ui.tsx before ui.ts: don't name a backend file ui.ts next to ui.tsx.\n- composer.badges slots need a sessionId. Dialogs reject NoUiError without a client: catch it in commands bound to keys.\n- ctx.desktop is undefined outside the desktop main process; ctx.sessions / generate / workspace / artifacts reject on hosts without them.\n- A ui entry can't import a file that imports 'zidane' values (Rpc.define): import its types only and useRpc('<id>') with a typed client.\n- On Node hosts (the desktop app) import.meta.dirname is undefined: use ctx.dir. Only zidane, zidane/extensions, zidane/extensions/testing, zidane/providers, zidane/providers/google and zidane/oauth are host-mapped (runtime imports of tokens like Panel work); other packages need the extension's own node_modules.\n- Transforms must be synchronous and pure; never await or write state in them.\n- Don't write \\`.ts\\` / \\`.js\\` data files into the extension folder: the desktop app reloads on source changes.\n\n## Loading, trust, distribution\n\n- Discovery order (first id wins): project .agents → project .zidane → ~/.agents → ~/.zidane. Project extensions load once the project is trusted (\"Load project extensions\").\n- /reload (and a saved source file in the desktop app) re-imports the module graph and re-runs setup; ctx.storage.memory survives it.\n- Install from Settings → Extensions: GitHub (owner/repo[#ref][:sub/dir]) or npm (pkg[@version]); or declaratively with an \\`extensions: [{ source, settings }]\\` config entry (pinned, with a lock file; its settings show locked).\n- Runtime deps go in package.json \\`dependencies\\` (installed without lifecycle scripts). Extensions are trusted on install: they run in-process, with no permission prompts.`\n\n// ---------------------------------------------------------------------------\n// Edit-session presets — the first line of each inline skill's instructions.\n// Kept next to each other so the two flows stay phrased consistently.\n// ---------------------------------------------------------------------------\n\n/** Preset preamble for \"edit / create extension\" sessions. */\nconst EXTENSION_EDIT_PRESET\n = 'You are authoring / editing a zidane extension. Authoritative API reference:'\n\n/** Preset preamble for \"edit skill\" sessions — same spirit, skill-flavored. */\nfunction skillEditPreset(marker: SkillEditMarker): string {\n return `We are going to work on ${marker.name}, in ${marker.dir} directory, `\n + 'next messages will be requests at modifying and improving the skill or '\n + 'understanding how it works better.'\n}\n\n/**\n * The inline `zidane-extension` skill. Carries the authoring reference IN its\n * body ({@link EXTENSION_AUTHORING_CHEATSHEET}) so it works in every host —\n * incl. compiled binaries where the on-disk docs aren't resolvable. The shipped\n * `EXTENSIONS.md` (prose long-form) is appended as a \"see also\" only when found\n * on disk. `source: 'inline'` (the `defineSkill` default) survives the\n * project-trust gate.\n *\n * @param opts - Options. `cwd` is the directory used to locate the shipped docs. Defaults to `process.cwd()`.\n */\nexport function buildZidaneExtensionSkill(opts: { cwd?: string } = {}): SkillConfig {\n const docsDir = resolveExtensionDocsDir(opts.cwd)\n const seeAlso = docsDir\n ? `\\n\\n---\\nHow the extension system works inside: ${docsDir}/EXTENSIONS.md. Where it differs, the API above wins.`\n : '\\n\\n---\\nProse docs, when present: node_modules/zidane/docs/EXTENSIONS.md '\n + '(often absent in compiled binaries; the API above is authoritative).'\n return defineSkill({\n name: ZIDANE_EXTENSION_SKILL_NAME,\n description:\n 'Authoring or editing a zidane extension (a module calling defineExtension, under '\n + '.zidane/extensions/** or .agents/extensions/**). Carries the extension authoring reference.',\n instructions: `${EXTENSION_EDIT_PRESET}\\n\\n${EXTENSION_AUTHORING_CHEATSHEET}${seeAlso}`,\n })\n}\n\n/**\n * Inject the `zidane-extension` skill into an already-merged {@link SkillsConfig}.\n *\n * Call this on the FINAL merged skills object (after folding extension / preset\n * skills): `composePresets` is last-wins on `catalog`, so injecting earlier\n * would be silently dropped whenever an extension also contributes skills.\n *\n * Adds the skill to the catalog and, when an `enabled` allowlist is in force,\n * force-includes the name so the allowlist filter can't hide it (mirrors how\n * extension-contributed skill names are force-included). When skills are fully\n * disabled (`enabled === false`), the edit session still gets just this skill.\n *\n * @param skills - Merged skills configuration to extend.\n * @param opts - Options. `cwd` is the directory used to locate the shipped docs. Defaults to `process.cwd()`.\n */\nexport function withZidaneExtensionSkill(skills: SkillsConfig, opts: { cwd?: string } = {}): SkillsConfig {\n return injectInlineSkill(skills, buildZidaneExtensionSkill(opts))\n}\n\n/**\n * Compact skill-authoring reference embedded into the skill-edit skill body.\n * Inline for the same reason as {@link EXTENSION_AUTHORING_CHEATSHEET}: works\n * in compiled binaries where on-disk docs don't resolve.\n */\nconst SKILL_AUTHORING_CHEATSHEET = `## SKILL.md anatomy\n\nA skill is a directory holding a \\`SKILL.md\\` (YAML frontmatter + markdown body)\nper the Agent Skills standard (agentskills.io/specification):\n\n\\`\\`\\`md\n---\nname: my-skill # 1-64 chars, lowercase alphanumeric + hyphens; SHOULD match the directory name\ndescription: what it does AND when to use it (drives activation — 1-1024 chars)\nlicense: MIT # optional\ncompatibility: … # optional environment requirements\nallowed-tools: # optional — restricts the agent to these tools while active\n - read_file\nmetadata: # optional flat string map; zidane-specific keys use the \\`zidane.\\` prefix\n some-key: value\n---\nThe body is the skill's instructions — injected into context when the skill\nis activated. Keep it token-lean: move depth into references/.\n\\`\\`\\`\n\n## Bundled resources (all optional, discovered automatically)\n- \\`references/\\` — docs loaded on demand via \\`skills_read\\`\n- \\`scripts/\\` — executables run via \\`skills_run_script\\` (honor shebangs)\n- \\`assets/\\` — templates / static files\n\n## Guidance\n- The description is the activation trigger — write it for the ROUTER (\"use when …\"), the body for the ACTIVATED agent.\n- Prefer small bodies + lazy references over one giant SKILL.md.\n- Shell interpolation (exclamation mark + backtick-quoted command) in the body runs at activation — use sparingly; hosts may gate or disable it.\n- Validate names: mismatched frontmatter name vs directory loads with a warning diagnostic.`\n\n/**\n * The inline skill auto-activated in \"edit skill\" sessions. Body = the\n * session preset (which skill, which directory, what the next messages will\n * be about) + a compact SKILL.md authoring reference — the skill-editing\n * counterpart of {@link buildZidaneExtensionSkill}.\n *\n * @param marker - Marker naming the skill's directory and name.\n * @param opts - Options. `cwd` is the directory used to locate the shipped docs. Defaults to `process.cwd()`.\n */\nexport function buildZidaneSkillEditSkill(marker: SkillEditMarker, opts: { cwd?: string } = {}): SkillConfig {\n const docsDir = resolveExtensionDocsDir(opts.cwd)\n const seeAlso = docsDir && existsSync(join(docsDir, 'SKILL.md'))\n ? `\\n\\n---\\nFull prose reference on disk: ${join(docsDir, 'SKILL.md')}`\n : ''\n return defineSkill({\n name: ZIDANE_SKILL_EDIT_SKILL_NAME,\n description:\n `Editing or improving the \"${marker.name}\" agent skill (its SKILL.md, references, `\n + 'scripts, and assets). Carries the skill-authoring reference for this edit session.',\n instructions: `${skillEditPreset(marker)}\\n\\n${SKILL_AUTHORING_CHEATSHEET}${seeAlso}`,\n })\n}\n\n/**\n * Inject the skill-edit preset skill into an already-merged {@link SkillsConfig}.\n * Same contract as {@link withZidaneExtensionSkill} — call on the FINAL merged\n * skills object.\n *\n * @param skills - Merged skills configuration to extend.\n * @param marker - Marker naming the skill's directory and name.\n * @param opts - Options. `cwd` is the directory used to locate the shipped docs. Defaults to `process.cwd()`.\n */\nexport function withZidaneSkillEditSkill(\n skills: SkillsConfig,\n marker: SkillEditMarker,\n opts: { cwd?: string } = {},\n): SkillsConfig {\n return injectInlineSkill(skills, buildZidaneSkillEditSkill(marker, opts))\n}\n\n/**\n * Shared injector: add `skill` to the catalog and, when an `enabled`\n * allowlist is in force, force-include its name so the filter can't hide it\n * (mirrors how extension-contributed skill names are force-included). When\n * skills are fully disabled (`enabled === false`), the edit session still\n * gets just this skill.\n */\nfunction injectInlineSkill(skills: SkillsConfig, skill: SkillConfig): SkillsConfig {\n const catalog = [...(skills.catalog ?? []), skill]\n const enabled = skills.enabled\n const nextEnabled: SkillsConfig['enabled']\n = enabled === undefined\n ? undefined\n : Array.isArray(enabled)\n ? (enabled.includes(skill.name) ? enabled : [...enabled, skill.name])\n : [skill.name]\n return {\n ...skills,\n catalog,\n ...(nextEnabled !== undefined ? { enabled: nextEnabled } : {}),\n }\n}\n","/**\n * Building blocks for hosts that freeze a conversation's system prompt (see\n * `AgentRunOptions.systemFreeze`): the out-of-band snapshot turn, lookup of\n * the newest snapshot for a prompt key, section digests, and the notes\n * announcing changes to the sections the SDK appends to every run's prompt\n * (skills catalog, searchable tools catalog, MCP server instructions).\n *\n * Which other sections a host tracks, and when it clears a snapshot, is the\n * host's policy.\n */\n\nimport type { FrozenPromptSections, SessionTurn, SystemNote, SystemPromptSnapshot } from './types'\nimport { createHash } from 'node:crypto'\n\n/** The newest snapshot for a prompt key, with its prompt text and the index of the turn holding it. */\nexport interface FrozenSystemPrompt {\n /** The frozen system prompt. */\n text: string\n /** The snapshot's bookkeeping. */\n snapshot: SystemPromptSnapshot\n /** Index of the snapshot turn in the session's turns. */\n index: number\n}\n\n/** A note announcing one changed section, with the text the model reads. */\nexport type SectionChangeNote = SystemNote & { text: string }\n\n/** The SDK-appended sections, in announcement order. */\nexport const FROZEN_PROMPT_SECTION_KINDS = ['skills', 'tools', 'mcp'] as const\n\n/**\n * Short stable digest of a section's text.\n *\n * @param text - Section text.\n */\nexport function sectionDigest(text: string): string {\n return createHash('sha256').update(text).digest('hex').slice(0, 16)\n}\n\n/**\n * Digest of every section present, by section kind.\n *\n * @param sections - Section texts by kind; `undefined` entries are skipped.\n */\nexport function sectionDigests(sections: Readonly<Record<string, string | undefined>>): Record<string, string> {\n const digests: Record<string, string> = {}\n for (const [kind, text] of Object.entries(sections)) {\n if (text !== undefined)\n digests[kind] = sectionDigest(text)\n }\n return digests\n}\n\n/**\n * The out-of-band `role: 'system'` turn recording a frozen prompt, or\n * clearing the one recorded for `key` (every key when omitted).\n *\n * @param input - Turn id and time, plus the prompt and its snapshot, or `cleared` with an optional key.\n */\nexport function systemSnapshotTurn(input: { id: string, createdAt: number } & ({ cleared: true, key?: string } | { system: string, snapshot: Omit<SystemPromptSnapshot, 'v' | 'cleared'> })): SessionTurn {\n if ('cleared' in input) {\n return {\n id: input.id,\n role: 'system',\n createdAt: input.createdAt,\n content: [],\n systemSnapshot: { v: 1, ...(input.key !== undefined ? { key: input.key } : {}), cleared: true, digests: {} },\n }\n }\n return {\n id: input.id,\n role: 'system',\n createdAt: input.createdAt,\n content: [{ type: 'text', text: input.system }],\n systemSnapshot: { v: 1, ...input.snapshot },\n }\n}\n\n/**\n * The newest frozen prompt recorded for `key`. A cleared snapshot for the\n * same key, or one with no key, ends the search with `undefined`; snapshots\n * for other keys are skipped.\n *\n * @param turns - Session turns, oldest first.\n * @param key - The prompt key the snapshot was recorded under; omitted matches keyless snapshots.\n */\nexport function latestSystemSnapshot(turns: readonly SessionTurn[], key?: string): FrozenSystemPrompt | undefined {\n for (let index = turns.length - 1; index >= 0; index--) {\n const turn = turns[index]!\n const snapshot = turn.systemSnapshot\n if (turn.role !== 'system' || !snapshot)\n continue\n if (snapshot.cleared) {\n if (snapshot.key === undefined || snapshot.key === key)\n return undefined\n continue\n }\n if (snapshot.key !== key)\n continue\n const block = turn.content.find(b => b.type === 'text')\n return block?.type === 'text' && block.text ? { text: block.text, snapshot, index } : undefined\n }\n return undefined\n}\n\n/**\n * Digest of a section as the model was last told it: the newest note of\n * `kind` carrying a digest after the snapshot, else the snapshot's digest.\n *\n * @param frozen - The frozen prompt.\n * @param turns - Session turns, oldest first.\n * @param kind - Section kind.\n */\nexport function announcedDigest(frozen: FrozenSystemPrompt, turns: readonly SessionTurn[], kind: string): string | undefined {\n for (let t = turns.length - 1; t > frozen.index; t--) {\n const content = turns[t]!.content\n for (let b = content.length - 1; b >= 0; b--) {\n const block = content[b]!\n if (block.type === 'text' && block.systemNote?.kind === kind && block.systemNote.digest !== undefined)\n return block.systemNote.digest\n }\n }\n return frozen.snapshot.digests[kind]\n}\n\n/**\n * Notes for each SDK-appended section whose live text differs from what the\n * model was last told (see {@link announcedDigest}). A section the model was\n * told about that is now absent is announced as emptied; tools changes list\n * the entries added and removed. Deterministic.\n *\n * @param input - The frozen prompt, the session turns and this run's sections.\n * @param input.frozen - The frozen prompt.\n * @param input.turns - Session turns, oldest first.\n * @param input.sections - The sections the SDK appended to this run's fresh prompt.\n */\nexport function catalogChangeNotes(input: { frozen: FrozenSystemPrompt, turns: readonly SessionTurn[], sections: FrozenPromptSections }): SectionChangeNote[] {\n const { frozen, turns, sections } = input\n const notes: SectionChangeNote[] = []\n for (const kind of FROZEN_PROMPT_SECTION_KINDS) {\n const text = sections[kind] ?? ''\n const digest = sectionDigest(text)\n const told = announcedDigest(frozen, turns, kind)\n if (told === digest || (told === undefined && text === ''))\n continue\n notes.push({ kind, digest, ...sectionNote(kind, text, frozen, turns) })\n }\n return notes\n}\n\nfunction sectionNote(kind: (typeof FROZEN_PROMPT_SECTION_KINDS)[number], text: string, frozen: FrozenSystemPrompt, turns: readonly SessionTurn[]): { label: string, text: string } {\n switch (kind) {\n case 'skills':\n return text\n ? { label: 'Skills catalog changed', text: `The skills catalog changed. It now reads:\\n\\n${text}` }\n : { label: 'No skills available', text: 'The skills catalog is now empty: no skills are available.' }\n case 'tools':\n return toolsNote(text, frozenToolsBlock(frozen.text), noteTexts(frozen, turns, 'tools'))\n case 'mcp':\n return text\n ? { label: 'MCP server instructions changed', text: `The MCP server instructions changed. They now read:\\n\\n${text}` }\n : { label: 'MCP server instructions removed', text: 'The MCP server instructions were removed. The ones given earlier no longer apply.' }\n }\n}\n\n/** Texts of the notes of `kind` after the snapshot, oldest first. */\nfunction noteTexts(frozen: FrozenSystemPrompt, turns: readonly SessionTurn[], kind: string): string[] {\n const texts: string[] = []\n for (const turn of turns.slice(frozen.index + 1)) {\n for (const block of turn.content) {\n if (block.type === 'text' && block.systemNote?.kind === kind)\n texts.push(block.text)\n }\n }\n return texts\n}\n\n/** The last `<searchable_tools>` block of a frozen prompt. */\nfunction frozenToolsBlock(system: string): string | undefined {\n const start = system.lastIndexOf('<searchable_tools>')\n const end = start < 0 ? -1 : system.indexOf('</searchable_tools>', start)\n return end < 0 ? undefined : system.slice(start, end + '</searchable_tools>'.length)\n}\n\n/** Tool names listed in a `<searchable_tools>` block, either catalog detail. */\nfunction catalogEntries(block: string | undefined): Set<string> {\n const names = new Set<string>()\n for (const match of (block ?? '').matchAll(/<tool name=\"([^\"]*)\">/g))\n names.add(match[1]!)\n for (const match of (block ?? '').matchAll(/<(?:server name=\"[^\"]*\"|tools)>([^<\\n]+)<\\/(?:server|tools)>/g)) {\n for (const name of match[1]!.split(', '))\n names.add(name)\n }\n return names\n}\n\nconst ADDED = 'Added: '\nconst REMOVED = 'Removed: '\n\n/** The catalog entries added and removed since the frozen catalog and the tools notes after it. */\nfunction toolsNote(text: string, frozen: string | undefined, history: readonly string[]): { label: string, text: string } {\n let told = catalogEntries(frozen)\n for (const note of history) {\n if (note.includes('<searchable_tools>')) {\n told = catalogEntries(note)\n continue\n }\n for (const line of note.split('\\n')) {\n if (line.startsWith(ADDED))\n line.slice(ADDED.length).split(', ').forEach(name => told.add(name))\n else if (line.startsWith(REMOVED))\n line.slice(REMOVED.length).split(', ').forEach(name => told.delete(name))\n }\n }\n const now = catalogEntries(text)\n const added = [...now].filter(name => !told.has(name))\n const removed = [...told].filter(name => !now.has(name))\n if (added.length + removed.length === 0)\n return { label: 'Searchable tools catalog changed', text: `The searchable tools catalog changed. It now reads:\\n\\n${text}` }\n return {\n label: `Searchable tools: +${added.length} / -${removed.length}`,\n text: [\n 'The searchable tools catalog changed.',\n ...(added.length > 0 ? [`${ADDED}${added.join(', ')}`] : []),\n ...(removed.length > 0 ? [`${REMOVED}${removed.join(', ')}`] : []),\n ].join('\\n'),\n }\n}\n"],"mappings":"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"}
|
|
@@ -4,9 +4,9 @@ import { n as looksBinary } from "./binary-detect-Dtj5NGns.js";
|
|
|
4
4
|
import { h as spawnStartParts, i as describeSpawnEnd, p as previewLine } from "./format-YgaIcuQ5.js";
|
|
5
5
|
import { A as panelShowsIn, k as panelOnSurface, x as SessionQueryError } from "./references-DZ5qTYDl.js";
|
|
6
6
|
import { g as computeLineDiff } from "./session-title-text-CQvgo3yS.js";
|
|
7
|
-
import {
|
|
7
|
+
import { at as mayHaveStreamFailure, mt as parseMachineKind, ot as splitStreamFailure } from "./repo-url-DLjjuVk_.js";
|
|
8
8
|
import { n as LOCAL_HOST_KEY } from "./connection-BaHd3T-D.js";
|
|
9
|
-
import { I as spawnLaneResolver, i as descriptionLabel } from "./transcript-reconcile-
|
|
9
|
+
import { I as spawnLaneResolver, i as descriptionLabel } from "./transcript-reconcile-DpQO85wl.js";
|
|
10
10
|
//#region src/chat/session-query/group.ts
|
|
11
11
|
const collator = new Intl.Collator("en", {
|
|
12
12
|
numeric: true,
|
|
@@ -1021,6 +1021,7 @@ function foldsInCompactShape(shape) {
|
|
|
1021
1021
|
case "spawn-start":
|
|
1022
1022
|
case "spawn-end":
|
|
1023
1023
|
case "spawn-queued":
|
|
1024
|
+
case "tool-progress":
|
|
1024
1025
|
case "command-detail": return true;
|
|
1025
1026
|
case "tool":
|
|
1026
1027
|
case "tool-result":
|
|
@@ -2411,4 +2412,4 @@ function buildTodoRetryMessage(item) {
|
|
|
2411
2412
|
//#endregion
|
|
2412
2413
|
export { machineWaiting as $, openProactive as A, parseSessionKey as At, validateMcpForm as B, groupRows as Bt, TERMINAL_PANEL_ID as C, selectableSources as Ct, cycleTab as D, SENDABLE_IMAGE_TYPES as Dt, closePanel as E, MAX_IMAGE_ATTACHMENT_BYTES as Et, togglePanel as F, createHostIndex as Ft, environmentLabel as G, MACHINE_NOTICE_LABEL as H, NOTICE_GLYPH as I, CONTINUE_PROMPT as It, lentServerMachines as J, isolationLabel as K, MCP_TRANSPORTS as L, flattenQuery as Lt, pinTab as M, AmbiguousHostIdError as Mt, reorderTabs as N, HostRemovedError as Nt, moveTab as O, planAttachment as Ot, resolveActiveTab as P, UnknownHostIdError as Pt, machineVersionDiffers as Q, parseKeyValueLines as R, viewQuery as Rt, SIDE_PANEL_ID as S, parentProviderKey as St, buildPanelCatalog as T, MAX_FILE_ATTACHMENT_BYTES as Tt, clockOrDateText as U, MACHINE_LINK_STATE_TEXT as V, clockText as W, machineDirListing as X, machineBlockedReason as Y, machineSetupCommand as Z, DIFFS_PANEL_ID as _, toolOverview as _t, editTodo as a, instructionPreviewLines as at, INITIAL_PANEL_STATE as b, indexOfProviderRow as bt, EMPTY_RATE as c, heatmapThresholds as ct, streamRateValue as d, noteLabel as dt, offlineSinceText as et, createRunStopMarker as f, planFold as ft, BUILTIN_PANEL_TABS as g, summarizeTools as gt, BROWSER_PANEL_ID as h, messagePreview as ht, cycleTodoStatus as i, instructionOriginBadge as it, pickTab as j, sessionKey as jt, openForAttention as k, SESSION_KEY_SEPARATOR as kt, MAX_PLAUSIBLE_RATE as l, foldSummary as lt, ARTIFACTS_PANEL_ID as m, commandGist as mt, buildTodoRetryMessage as n, INSTRUCTION_PREVIEW_LINE_CAP as nt, nextTodoId as o, daysEndingAt as ot, tidyPromptText as p, thoughtLabel as pt, lastSeenText as q, clearTodos as r, buildVisibleInstructionRows as rt, removeTodo as s, heatmapLevel as st, addTodo as t, orderByMachine as tt, stepStreamRate as u, foldsInCompactShape as ut, FILES_PANEL_ID as v, toolPhrase as vt, TODOS_PANEL_ID as w, ATTACHMENT_SNIFF_BYTES as wt, QUEUE_PANEL_ID as x, isExpandableProvider as xt, INBOX_PANEL_ID as y, buildVisibleAuthRows as yt, splitCommandLine as z, groupKeysOf as zt };
|
|
2413
2414
|
|
|
2414
|
-
//# sourceMappingURL=todo-edits-
|
|
2415
|
+
//# sourceMappingURL=todo-edits-Dyj32ds6.js.map
|