chainlesschain 0.162.176 → 0.162.177
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BDLu5Elz.js → AIOps-DT-VPWuN.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DH5us-CK.js → ActionButton-DyOreOVN.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BZ0u8IFG.js → Analytics-DslNsKux.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Duu8xR14.js → AppLayout-BNJJAYME.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Br2YBbh_.js → Artifacts-DoaAtEcY.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C3C6a482.js → Audit-DPG3WdJ9.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CzHvebcj.js → BackgroundAgents-C_JO6R5G.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DZNHYDFd.js → Backup-Cv3BooyX.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-VRiC4fIx.js → BaseInput-D2uQXWu-.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Cm08WWAD.js → Chat-B4Vczyf3.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DE6reP99.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-DtByk7d7.js → Checkbox-qTgk4VeP.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CphXXOfD.js → Codegen-DlLWcIzj.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Hz86TnLR.js → Col-DNO73qp7.js} +1 -1
- package/src/assets/web-panel/assets/{Community-HP03ltpD.js → Community-vRDNpc4j.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B_N1mqQz.js → Compact-CEWtIXni.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CVztRpi8.js → Compliance-KgYC9Fxi.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-wUVOyZih.js → Cowork-zj5wvkpT.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-DW5TLJKw.js → Cron-BLDoCozU.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CrAFGhWH.js → Crosschain-BYnff4VY.js} +1 -1
- package/src/assets/web-panel/assets/{DID-pA-oD8ke.js → DID-C75VFsVm.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CXrd5gAw.js → Dashboard-C4Gutni2.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-EweJcsek.js → Dropdown-B5dbO4Kx.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C9xVLPG4.js → EmailListRenderer-BDipZtHJ.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CDcEEqlu.js → FamilyGuardDashboard-DmIzt8jj.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DtZda9A_.js → Federation-D9QOS-uW.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CiYdcVz3.js → FormItemContext-B47HRwAm.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DA13uwA0.js → GenericCardRenderer-3R4mXmgV.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DzRhZD1O.js → Git-DrNXZf1C.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-D5RMgOSb.js → Governance-BGd7JR98.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BgrPfSNk.js → Inference-D-2he1ed.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DzjnD571.js → KnowledgeGraph-C0sHIbK5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BhWzVDtN.js → Logs-DiBQO-jL.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BDape9cx.js → MarkdownRenderer-DL_5eV2D.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DJtL5GV7.js → Marketplace-VR59OdrC.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CZcQygI7.js → McpTools-DrPzwSd3.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DUWSDUUu.js → Memory-CBgKALwQ.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BTwq2WKY.js → MobileBridge-pQ9Tn-Mn.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-mASB6jyj.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BiGugEQm.js → Mtc-DTMgE7HC.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-DY7a6gUe.js → MtcAudit-CwTnCtiL.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BQbDu_w7.js → Multisig-ngm6WU8f.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CM2wXTLv.js → NLProgramming-4YO4Qww9.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DI6gGs1v.js → Notes-DP1k45mF.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-BsZOLWAB.js → NotificationSettings-r2H8yPKb.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C48mu09U.js → OrderTableRenderer-CoM_Zw0J.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Cds0_tXr.js → Organization-DrO7jRWS.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-WunYMD87.js → Overflow-Cnwu4i4t.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Bo6j4DDv.js → P2P-zi-cDwA_.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Bh2XPD5f.js → PdhVaultBrowser-DBEryDv7.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-EMvMMC8N.js → Permissions-lqXK2C3x.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-AX3ypUDV.js → PersonalDataHub-b63T3HJM.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-BOx8HOgL.js → Pipeline-Kep5KAGW.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-tBeTVQVM.js → Privacy-CHdvOAh1.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CVYg1W4M.js → ProjectInit-BhKfdwe9.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Dvt5I7y1.js → ProjectSettings-Yavk9K8A.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Cc7FC9-e.js → Projects-CN8E_5Mq.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Ct8xqY-E.js → Providers-Bfym6SQn.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Cni8Ooja.js → QrScannerModal-CLSQcrlR.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BJVLKdxr.js → QuickAsk-VPa2WKQN.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DPG8Rbbm.js → Recommend-CGXR8jVU.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DsdR7dCt.js → RemoteSession-C_20YfMB.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Csg1YM-b.js → Reputation-awZh68UV.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D9DoFoEf.js → Row-Ctb4oiYN.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BsvKrXMS.js → RssFeed-D3sdjvUV.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DFUStfgQ.js → Search-C-zfA4kT.js} +1 -1
- package/src/assets/web-panel/assets/{Security-ILg1zxdK.js → Security-D9XlmHA3.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Dd41PCaY.js → Services-C3UKnr41.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Bzo1cN80.js → Skeleton-BQxagZfF.js} +1 -1
- package/src/assets/web-panel/assets/Skills-q6eb6KPZ.js +1 -0
- package/src/assets/web-panel/assets/{Sla-jl7UTiRY.js → Sla-DveENvek.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BePa-RF6.js → SpeechSettings-BFjXLNgF.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DiqqDKGZ.js → SyncSettings-BpHgbaP9.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DvepRb8-.js +1 -0
- package/src/assets/web-panel/assets/{Templates-JiDeI06L.js → Templates-DEiaoyve.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-hhgI6zwS.js → Tenant-ITg774nY.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-D3ZBcvnb.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-B8uB5Llk.js → TimelineRenderer-BsLU6JjU.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C3b_6r2z.js → Tokens-Cb5Pquc9.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-tIP7HKVe.js → Trigger-Dwfy-m9N.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-vjoc11vJ.js → Trust-CnlSRGG0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DUPBnGuH.js → UkeySign-Y4TlvKVo.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CBvhUY5T.js → VideoEditing-BpKLtTTO.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DUaQ0uA5.js → Wallet-LLBzIq8x.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DllSivjE.js → WebAuthn-BeSRiusP.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-xc8D3uO6.js → WorkflowEditor-BK_l1tXO.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BVicU7oA.js → chat-DYd-X7Wg.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DJzLmhLm.js → colors-C9uip71j.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BvYRfThE.js → compact-item-JBrfjUP6.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DGXxCBKR.js → createContext-D03fbuiq.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-B8LQ8leS.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-SBDJB1y4.js → hasIn-DLQOR6l-.js} +1 -1
- package/src/assets/web-panel/assets/{index-MjC4G09d.js → index-5YN7F5bO.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dm_77jgR.js → index-6GuLRpBQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtwqO5qo.js → index-B0X76LjX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRfiXB_p.js → index-B46YcgsV.js} +1 -1
- package/src/assets/web-panel/assets/{index-VMfwIH5G.js → index-BNCdRmWf.js} +1 -1
- package/src/assets/web-panel/assets/{index-hxTODbM1.js → index-BYv-LWdy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMarmfrv.js → index-Ba-Ps_AJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-xfluRkCT.js → index-BcBZwlnG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYqz4Wrr.js → index-BdH9l8wl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BO1itZaY.js → index-BfOs_bmg.js} +3 -3
- package/src/assets/web-panel/assets/index-Bh8BPtqR.js +1 -0
- package/src/assets/web-panel/assets/{index-Cf-O_g_8.js → index-BnQgRjsm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKyEivf8.js → index-BpYKLrUT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CoJAO1Lm.js → index-BuNFCZCx.js} +1 -1
- package/src/assets/web-panel/assets/{index-1SdDS6lR.js → index-ByHO3SqZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnjPdvR6.js → index-C2zmOsyD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf-ctERC.js → index-CChQBM1d.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCdRAgjy.js → index-CChaISMb.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crk5NTb0.js → index-CILVyK3b.js} +1 -1
- package/src/assets/web-panel/assets/{index-9gdI7uUW.js → index-CL0ixCxk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJZf9DhV.js → index-CS63r8h3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCqRS6da.js → index-CVZWiUE_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DonCnfMr.js → index-Ce0Bf1YF.js} +1 -1
- package/src/assets/web-panel/assets/index-CeVrHLzZ.js +1 -0
- package/src/assets/web-panel/assets/{index-CloCgBUR.js → index-Ck17i0OD.js} +1 -1
- package/src/assets/web-panel/assets/{index-wcCL-miV.js → index-Cpn19c9k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTrBGBJs.js → index-CqWk1FLG.js} +1 -1
- package/src/assets/web-panel/assets/{index-e4fXH_WO.js → index-CwoDpKzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvUTDYXX.js → index-D-1SqEpQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdGY9P8a.js → index-D4UY5Z60.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKrvvWm4.js → index-D8KvuDz0.js} +1 -1
- package/src/assets/web-panel/assets/{index-lpaGoTH3.js → index-DNMAAUVk.js} +1 -1
- package/src/assets/web-panel/assets/{index-YJkzy8ZF.js → index-DYLZY4uh.js} +1 -1
- package/src/assets/web-panel/assets/{index-FxZzRyE1.js → index-DaokIHt3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHVLJ8q0.js → index-Q7mmL6w4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cs2rmLFt.js → index-SO6lgMg0.js} +1 -1
- package/src/assets/web-panel/assets/{index-z1ZylsLq.js → index-iahNhZwm.js} +1 -1
- package/src/assets/web-panel/assets/{index-d5BVd-cL.js → index-l08afIbV.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7HimK0M.js → index-rIxxfdTN.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C1zO1KnY.js → initDefaultProps-CG9z1c6w.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CWLtEGmC.js → motion-B1Ky7Zff.js} +1 -1
- package/src/assets/web-panel/assets/{move-DzD_C3Vm.js → move-DME8X2P1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-D880PYJl.js → mtc-parser-cuJYSWB7.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CFR_z2ov.js → omit-UI_EE0uZ.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-srIhgrSC.js → pickAttrs-BIoE_Z_V.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-C1rXpibP.js → placementArrow-B48-9HKN.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C30gRcSS.js → responsiveObserve-D7x_GDgD.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CvtKiMfV.js → slide-DjzC5_7m.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DNHO7ITG.js → statusUtils-C6BIymhh.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Dt_Sl-g4.js → styleChecker-B4dGrdD9.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Cjev42Gh.js → useFlexGapSupport-Bken4aNE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Baxglj2V.js → useFs-BMlqECqv.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-B4NkcLaP.js → usePersonalDataHub-DLdrfmMa.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-mIRkdT7o.js → vnode-CKJQmxa0.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-HzI5DlH_.js → zoom-CaZunuau.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +22 -6
- package/src/commands/agent.js +3 -3
- package/src/commands/background-session.js +11 -4
- package/src/commands/batch.js +79 -27
- package/src/commands/changelog.js +15 -4
- package/src/commands/eval.js +21 -6
- package/src/commands/hook.js +2 -2
- package/src/commands/loop.js +9 -2
- package/src/commands/skill.js +2 -0
- package/src/commands/statusline.js +3 -3
- package/src/data/changelog.json +293 -51
- package/src/harness/background-task-manager.js +18 -6
- package/src/harness/background-task-worker.js +11 -18
- package/src/harness/mcp-client.js +9 -15
- package/src/harness/worktree-isolator.js +18 -21
- package/src/lib/agent-core.js +5 -0
- package/src/lib/agent-ipc-bus.js +12 -3
- package/src/lib/agent-team/team-worktree.js +28 -6
- package/src/lib/agent-worktree.js +20 -19
- package/src/lib/async-hook-supervisor.cjs +134 -24
- package/src/lib/async-hook-supervisor.js +47 -0
- package/src/lib/background-agent-supervisor.js +35 -12
- package/src/lib/background-task-manager.js +5 -1
- package/src/lib/changelog.js +57 -0
- package/src/lib/chrome-connector.js +6 -2
- package/src/lib/claude-code-bridge.js +24 -8
- package/src/lib/cli-anything-bridge.js +82 -25
- package/src/lib/doctor-checkup.js +18 -6
- package/src/lib/eval/tasks.js +25 -31
- package/src/lib/hook-runner.cjs +71 -36
- package/src/lib/hook-runner.js +55 -0
- package/src/lib/host-adb-bridge.js +18 -3
- package/src/lib/lsp/__tests__/benchmark.test.js +61 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +6 -1
- package/src/lib/lsp/benchmark.js +20 -4
- package/src/lib/lsp/lsp-client.js +6 -4
- package/src/lib/lsp/lsp-server-registry.js +0 -2
- package/src/lib/orchestrator.js +8 -2
- package/src/lib/packer/pack-update-applier.js +14 -2
- package/src/lib/packer/smoke-runner.js +18 -5
- package/src/lib/paths.js +1 -1
- package/src/lib/plugin-monitor-supervisor.js +20 -13
- package/src/lib/plugin-runtime/install.js +10 -3
- package/src/lib/repl-bang-memorize.js +10 -2
- package/src/lib/repl-goal.js +19 -2
- package/src/lib/settings-hook-events.cjs +13 -8
- package/src/lib/settings-hook-events.js +20 -0
- package/src/lib/skill-packs/generator.js +18 -9
- package/src/lib/skill-process-broker.js +71 -0
- package/src/lib/slash-commands.js +10 -2
- package/src/lib/status-line.cjs +28 -7
- package/src/lib/status-line.js +33 -0
- package/src/repl/agent-repl.js +48 -43
- package/src/runtime/agent-core.js +133 -49
- package/src/runtime/headless-runner.js +25 -10
- package/src/runtime/headless-stream.js +74 -4
- package/src/runtime/session-slash-commands.js +506 -0
- package/src/skills/video-editing/extractors/audio-extractor.js +13 -5
- package/src/skills/video-editing/extractors/video-extractor.js +13 -3
- package/src/skills/video-editing/media-process.js +20 -0
- package/src/skills/video-editing/render/audio-mix.js +13 -3
- package/src/skills/video-editing/render/ffmpeg-concat.js +7 -2
- package/src/skills/video-editing/render/ffmpeg-extract.js +7 -2
- package/src/workers/background-agent-worker.js +6 -2
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CGHYrugQ.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-NZvvfDME.js +0 -1
- package/src/assets/web-panel/assets/Skills-BDHh17P0.js +0 -1
- package/src/assets/web-panel/assets/Tasks-DYQ_6lxj.js +0 -1
- package/src/assets/web-panel/assets/Terminal-CjTZPa37.js +0 -3
- package/src/assets/web-panel/assets/devWarning-BT46Y_QI.js +0 -1
- package/src/assets/web-panel/assets/index-B7nuIywP.js +0 -1
- package/src/assets/web-panel/assets/index-ycwAfC-i.js +0 -1
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only, session-scoped slash commands for the stream-json protocol.
|
|
3
|
+
*
|
|
4
|
+
* These commands are handled by the live headless-stream process so their
|
|
5
|
+
* output describes this session (model, loaded memory, MCP connections, hooks,
|
|
6
|
+
* permissions and background tasks). They never become a user/model turn.
|
|
7
|
+
*
|
|
8
|
+
* The first protocol version is deliberately read-only. Mutating REPL forms
|
|
9
|
+
* such as `/agents new`, `/tasks kill` and `/permissions <mode>` fail with a
|
|
10
|
+
* stable error code instead of silently discarding their arguments.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const SESSION_SLASH_COMMANDS = Object.freeze([
|
|
14
|
+
"status",
|
|
15
|
+
"doctor",
|
|
16
|
+
"mcp",
|
|
17
|
+
"hooks",
|
|
18
|
+
"permissions",
|
|
19
|
+
"agents",
|
|
20
|
+
"tasks",
|
|
21
|
+
"memory",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const MAX_REQUEST_ID_LENGTH = 128;
|
|
25
|
+
const MAX_COMMAND_LENGTH = 128;
|
|
26
|
+
const MAX_ARGUMENT_LENGTH = 4096;
|
|
27
|
+
const MAX_OUTPUT_LENGTH = 256 * 1024;
|
|
28
|
+
const MAX_ERROR_LENGTH = 16 * 1024;
|
|
29
|
+
|
|
30
|
+
const hasOwn = (value, key) =>
|
|
31
|
+
Object.prototype.hasOwnProperty.call(value || {}, key);
|
|
32
|
+
|
|
33
|
+
function normalizeArgs(raw) {
|
|
34
|
+
if (raw == null) return { ok: true, args: "", text: "" };
|
|
35
|
+
if (typeof raw === "string") {
|
|
36
|
+
if (raw.length > MAX_ARGUMENT_LENGTH) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
args: "",
|
|
40
|
+
text: "",
|
|
41
|
+
error: `slash_command args exceed ${MAX_ARGUMENT_LENGTH} characters`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { ok: true, args: raw, text: raw.trim() };
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
args: raw,
|
|
49
|
+
text: "",
|
|
50
|
+
error: "slash_command args must be a string",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse a decoded stream-json object into the command shape consumed by the
|
|
56
|
+
* dispatcher. Returns null for non-slash events.
|
|
57
|
+
*/
|
|
58
|
+
export function parseSessionSlashCommandEvent(value) {
|
|
59
|
+
if (!value || typeof value !== "object" || value.type !== "slash_command") {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const errors = [];
|
|
64
|
+
const rawRequestId =
|
|
65
|
+
typeof value.request_id === "string" ? value.request_id : "";
|
|
66
|
+
const requestIdText = rawRequestId.trim();
|
|
67
|
+
const requestId =
|
|
68
|
+
requestIdText && rawRequestId.length <= MAX_REQUEST_ID_LENGTH
|
|
69
|
+
? rawRequestId
|
|
70
|
+
: null;
|
|
71
|
+
if (!requestId) {
|
|
72
|
+
errors.push(
|
|
73
|
+
rawRequestId.length > MAX_REQUEST_ID_LENGTH
|
|
74
|
+
? `slash_command request_id exceeds ${MAX_REQUEST_ID_LENGTH} characters`
|
|
75
|
+
: "slash_command requires a non-empty string request_id",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (typeof value.command !== "string" || !value.command.trim()) {
|
|
79
|
+
errors.push("slash_command requires a non-empty command");
|
|
80
|
+
return {
|
|
81
|
+
command: "",
|
|
82
|
+
args: hasOwn(value, "args") ? value.args : "",
|
|
83
|
+
argumentText: "",
|
|
84
|
+
requestId,
|
|
85
|
+
validationError: errors.join("; "),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const raw = value.command.trim().replace(/^\/+/, "");
|
|
90
|
+
if (raw.length > MAX_COMMAND_LENGTH) {
|
|
91
|
+
errors.push(
|
|
92
|
+
`slash_command command exceeds ${MAX_COMMAND_LENGTH} characters`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const boundedRaw = raw.slice(0, MAX_COMMAND_LENGTH);
|
|
96
|
+
const splitAt = boundedRaw.search(/\s/);
|
|
97
|
+
const commandText = splitAt < 0 ? boundedRaw : boundedRaw.slice(0, splitAt);
|
|
98
|
+
const inlineArgs =
|
|
99
|
+
raw.length > MAX_COMMAND_LENGTH || splitAt < 0
|
|
100
|
+
? ""
|
|
101
|
+
: boundedRaw.slice(splitAt + 1).trim();
|
|
102
|
+
if (!commandText) {
|
|
103
|
+
errors.push("slash_command requires a non-empty command name");
|
|
104
|
+
}
|
|
105
|
+
const normalized = normalizeArgs(hasOwn(value, "args") ? value.args : null);
|
|
106
|
+
if (!normalized.ok) errors.push(normalized.error);
|
|
107
|
+
const argumentText = [inlineArgs, normalized.text].filter(Boolean).join(" ");
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
command: commandText.toLowerCase(),
|
|
111
|
+
args: normalized.args,
|
|
112
|
+
argumentText,
|
|
113
|
+
requestId,
|
|
114
|
+
...(errors.length ? { validationError: errors.join("; ") } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function success(output, extra = {}) {
|
|
119
|
+
const text = String(output == null ? "" : output);
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
output:
|
|
123
|
+
text.length > MAX_OUTPUT_LENGTH
|
|
124
|
+
? `${text.slice(0, MAX_OUTPUT_LENGTH)}\n… (output truncated)`
|
|
125
|
+
: text,
|
|
126
|
+
...extra,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function failure(code, error, extra = {}) {
|
|
131
|
+
const text = String(error || "Slash command failed");
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
code,
|
|
135
|
+
error:
|
|
136
|
+
text.length > MAX_ERROR_LENGTH
|
|
137
|
+
? `${text.slice(0, MAX_ERROR_LENGTH)}…`
|
|
138
|
+
: text,
|
|
139
|
+
...extra,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function unsupportedArguments(command, event, usage) {
|
|
144
|
+
return failure(
|
|
145
|
+
"UNSUPPORTED_ARGUMENTS",
|
|
146
|
+
`/${command} does not support "${event.argumentText}" over stream-json. ${usage}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function mcpServerList(mcp) {
|
|
151
|
+
const client = mcp?.mcpClient;
|
|
152
|
+
if (client && typeof client.listServers === "function") {
|
|
153
|
+
const listed = client.listServers();
|
|
154
|
+
return Array.isArray(listed) ? listed : [];
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(mcp?.connected)) {
|
|
157
|
+
return mcp.connected.map((server) =>
|
|
158
|
+
typeof server === "string"
|
|
159
|
+
? { name: server, state: "connected" }
|
|
160
|
+
: server,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function runStatus(context, deps) {
|
|
167
|
+
if (context.event.argumentText) {
|
|
168
|
+
return unsupportedArguments(
|
|
169
|
+
"status",
|
|
170
|
+
context.event,
|
|
171
|
+
"Use /status without arguments.",
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const [{ VERSION }, versionModule, ideModule, statusModule] =
|
|
175
|
+
await Promise.all([
|
|
176
|
+
import("../constants.js"),
|
|
177
|
+
import("../lib/version-skew.js"),
|
|
178
|
+
import("../repl/ide-status.js"),
|
|
179
|
+
import("../repl/status-summary.js"),
|
|
180
|
+
]);
|
|
181
|
+
const servers = mcpServerList(context.mcp);
|
|
182
|
+
const ideToolNames = deps.ideToolNames || ideModule.ideToolNames;
|
|
183
|
+
const formatStatus = deps.formatStatus || statusModule.formatStatus;
|
|
184
|
+
const readDiskVersion = deps.readDiskVersion || versionModule.readDiskVersion;
|
|
185
|
+
const version = hasOwn(deps, "version") ? deps.version : VERSION;
|
|
186
|
+
const installedVersion = await readDiskVersion();
|
|
187
|
+
|
|
188
|
+
return success(
|
|
189
|
+
formatStatus({
|
|
190
|
+
version,
|
|
191
|
+
installedVersion,
|
|
192
|
+
node: process.version,
|
|
193
|
+
platform: `${process.platform}-${process.arch}`,
|
|
194
|
+
provider: context.provider,
|
|
195
|
+
model: context.model,
|
|
196
|
+
sessionId: context.sessionId,
|
|
197
|
+
messageCount:
|
|
198
|
+
context.messageCount ??
|
|
199
|
+
(Array.isArray(context.messages) ? context.messages.length : 0),
|
|
200
|
+
cwd: context.cwd,
|
|
201
|
+
extraRoots:
|
|
202
|
+
context.extraRoots ??
|
|
203
|
+
(Array.isArray(context.additionalDirectories)
|
|
204
|
+
? context.additionalDirectories.length
|
|
205
|
+
: 0),
|
|
206
|
+
ideConnected: ideToolNames(context.mcp).length > 0,
|
|
207
|
+
mcpServers: servers.length,
|
|
208
|
+
hookEvents:
|
|
209
|
+
context.settingsHooks &&
|
|
210
|
+
typeof context.settingsHooks === "object" &&
|
|
211
|
+
!Array.isArray(context.settingsHooks)
|
|
212
|
+
? Object.keys(context.settingsHooks).length
|
|
213
|
+
: 0,
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function runDoctor(context, deps) {
|
|
219
|
+
if (context.event.argumentText) {
|
|
220
|
+
return unsupportedArguments(
|
|
221
|
+
"doctor",
|
|
222
|
+
context.event,
|
|
223
|
+
"Use /doctor without arguments.",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const [configModule, doctorModule, ideModule] = await Promise.all([
|
|
227
|
+
import("../lib/config-manager.js"),
|
|
228
|
+
import("../repl/doctor-status.js"),
|
|
229
|
+
import("../repl/ide-status.js"),
|
|
230
|
+
]);
|
|
231
|
+
const loadConfig = deps.loadConfig || configModule.loadConfig;
|
|
232
|
+
const buildDoctorChecks =
|
|
233
|
+
deps.buildDoctorChecks || doctorModule.buildDoctorChecks;
|
|
234
|
+
const renderDoctor = deps.renderDoctor || doctorModule.renderDoctor;
|
|
235
|
+
const ideToolNames = deps.ideToolNames || ideModule.ideToolNames;
|
|
236
|
+
|
|
237
|
+
let config = {};
|
|
238
|
+
try {
|
|
239
|
+
config = (await loadConfig()) || {};
|
|
240
|
+
} catch {
|
|
241
|
+
// Match the REPL: configuration discovery is best-effort.
|
|
242
|
+
}
|
|
243
|
+
config = {
|
|
244
|
+
...config,
|
|
245
|
+
llm: {
|
|
246
|
+
...(config.llm || {}),
|
|
247
|
+
...(context.provider ? { provider: context.provider } : {}),
|
|
248
|
+
...(context.model ? { model: context.model } : {}),
|
|
249
|
+
...(context.apiKey ? { apiKey: context.apiKey } : {}),
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return success(
|
|
254
|
+
renderDoctor(
|
|
255
|
+
buildDoctorChecks({
|
|
256
|
+
config,
|
|
257
|
+
ideTools: ideToolNames(context.mcp),
|
|
258
|
+
mcpServers: mcpServerList(context.mcp),
|
|
259
|
+
permissionRules: context.permissionRules,
|
|
260
|
+
settingsHooks: context.settingsHooks,
|
|
261
|
+
}),
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function formatMcpServers(servers) {
|
|
267
|
+
const lines = [`MCP servers (${servers.length}):`];
|
|
268
|
+
if (servers.length === 0) {
|
|
269
|
+
lines.push(" (none connected)");
|
|
270
|
+
} else {
|
|
271
|
+
for (const server of servers) {
|
|
272
|
+
const name = server?.name || "?";
|
|
273
|
+
const state = server?.state || "unknown";
|
|
274
|
+
const counts = [
|
|
275
|
+
Number.isFinite(Number(server?.tools))
|
|
276
|
+
? `${Number(server.tools)} tools`
|
|
277
|
+
: null,
|
|
278
|
+
Number.isFinite(Number(server?.resources))
|
|
279
|
+
? `${Number(server.resources)} resources`
|
|
280
|
+
: null,
|
|
281
|
+
Number.isFinite(Number(server?.prompts))
|
|
282
|
+
? `${Number(server.prompts)} prompts`
|
|
283
|
+
: null,
|
|
284
|
+
].filter(Boolean);
|
|
285
|
+
lines.push(
|
|
286
|
+
` ${name}: ${state}${counts.length ? ` (${counts.join(", ")})` : ""}`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return lines.join("\n");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function runMcp(context, deps) {
|
|
294
|
+
if (context.event.argumentText) {
|
|
295
|
+
return unsupportedArguments(
|
|
296
|
+
"mcp",
|
|
297
|
+
context.event,
|
|
298
|
+
"Use /mcp without arguments.",
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
const { renderMcpSurface: defaultRenderer } =
|
|
302
|
+
await import("../repl/mcp-prompt.js");
|
|
303
|
+
const renderMcpSurface = deps.renderMcpSurface || defaultRenderer;
|
|
304
|
+
const client = context.mcp?.mcpClient || null;
|
|
305
|
+
const servers = mcpServerList(context.mcp);
|
|
306
|
+
return success(`${formatMcpServers(servers)}\n\n${renderMcpSurface(client)}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function runHooks(context, deps) {
|
|
310
|
+
if (context.event.argumentText) {
|
|
311
|
+
return unsupportedArguments(
|
|
312
|
+
"hooks",
|
|
313
|
+
context.event,
|
|
314
|
+
"Use /hooks without arguments.",
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const { formatSettingsHooks: defaultRenderer } =
|
|
318
|
+
await import("../repl/hooks-status.js");
|
|
319
|
+
const formatSettingsHooks = deps.formatSettingsHooks || defaultRenderer;
|
|
320
|
+
return success(formatSettingsHooks(context.settingsHooks));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function runPermissions(context, deps) {
|
|
324
|
+
if (context.event.argumentText) {
|
|
325
|
+
return failure(
|
|
326
|
+
"UNSUPPORTED_ARGUMENTS",
|
|
327
|
+
"Changing permission mode through /permissions is not supported over " +
|
|
328
|
+
"stream-json. Start a new session with --permission-mode <mode>; use " +
|
|
329
|
+
"/permissions without arguments to inspect the active rules.",
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
const { renderPermissions: defaultRenderer } =
|
|
333
|
+
await import("../repl/permissions-status.js");
|
|
334
|
+
const renderPermissions = deps.renderPermissions || defaultRenderer;
|
|
335
|
+
const output = renderPermissions(context.permissionRules, {
|
|
336
|
+
files: Array.isArray(context.settingsFiles) ? context.settingsFiles : [],
|
|
337
|
+
});
|
|
338
|
+
return success(
|
|
339
|
+
`${output}\n Current mode: ${context.permissionMode || "default"}`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function runTasks(context, deps) {
|
|
344
|
+
const arg = context.event.argumentText.trim();
|
|
345
|
+
if (arg && arg !== "list" && arg !== "ls") {
|
|
346
|
+
return failure(
|
|
347
|
+
"UNSUPPORTED_ARGUMENTS",
|
|
348
|
+
"Only read-only /tasks list is supported over stream-json. " +
|
|
349
|
+
"Use the terminal REPL for /tasks kill <id> or /tasks kill-all.",
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const [coreModule, tasksModule] = await Promise.all([
|
|
353
|
+
import("./agent-core.js"),
|
|
354
|
+
import("../repl/tasks-status.js"),
|
|
355
|
+
]);
|
|
356
|
+
const listBackgroundShellTasks =
|
|
357
|
+
deps.listBackgroundShellTasks || coreModule.listBackgroundShellTasks;
|
|
358
|
+
const formatBackgroundTasks =
|
|
359
|
+
deps.formatBackgroundTasks || tasksModule.formatBackgroundTasks;
|
|
360
|
+
return success(formatBackgroundTasks(await listBackgroundShellTasks()));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function validateReadOnlyAgentArgs(event) {
|
|
364
|
+
const text = event.argumentText.trim();
|
|
365
|
+
if (!text || text === "list" || text === "ls") {
|
|
366
|
+
return { ok: true, action: "list" };
|
|
367
|
+
}
|
|
368
|
+
if (/^new(?:\s|$)/.test(text)) {
|
|
369
|
+
return {
|
|
370
|
+
ok: false,
|
|
371
|
+
error:
|
|
372
|
+
"Creating agents is not supported over stream-json. " +
|
|
373
|
+
"Use /agents new from the terminal REPL.",
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const show = /^show\s+(\S+)$/.exec(text);
|
|
377
|
+
if (show) return { ok: true, action: "show", name: show[1] };
|
|
378
|
+
if (/^\S+$/.test(text)) {
|
|
379
|
+
return { ok: true, action: "show", name: text };
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
ok: false,
|
|
383
|
+
error:
|
|
384
|
+
"Only /agents list and /agents show <name> are supported over stream-json.",
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function runAgents(context, deps) {
|
|
389
|
+
const validated = validateReadOnlyAgentArgs(context.event);
|
|
390
|
+
if (!validated.ok) {
|
|
391
|
+
return failure("UNSUPPORTED_ARGUMENTS", validated.error);
|
|
392
|
+
}
|
|
393
|
+
const [statusModule, agentsModule, profilesModule] = await Promise.all([
|
|
394
|
+
import("../repl/agents-status.js"),
|
|
395
|
+
import("../lib/agents.js"),
|
|
396
|
+
import("../lib/sub-agent-profiles.js"),
|
|
397
|
+
]);
|
|
398
|
+
const parseAgentsCommand =
|
|
399
|
+
deps.parseAgentsCommand || statusModule.parseAgentsCommand;
|
|
400
|
+
const formatAgentsList =
|
|
401
|
+
deps.formatAgentsList || statusModule.formatAgentsList;
|
|
402
|
+
const formatAgentDetail =
|
|
403
|
+
deps.formatAgentDetail || statusModule.formatAgentDetail;
|
|
404
|
+
const discoverAgents = deps.discoverAgents || agentsModule.discoverAgents;
|
|
405
|
+
const getAgent = deps.getAgent || agentsModule.getAgent;
|
|
406
|
+
const listSubAgentProfiles =
|
|
407
|
+
deps.listSubAgentProfiles || profilesModule.listSubAgentProfiles;
|
|
408
|
+
|
|
409
|
+
// Keep the REPL parser as the source of truth after the read-only wire guard.
|
|
410
|
+
const parsed = parseAgentsCommand(
|
|
411
|
+
`/agents${context.event.argumentText ? ` ${context.event.argumentText}` : ""}`,
|
|
412
|
+
);
|
|
413
|
+
if (parsed.action === "list") {
|
|
414
|
+
return success(
|
|
415
|
+
formatAgentsList(
|
|
416
|
+
await discoverAgents(context.cwd),
|
|
417
|
+
await listSubAgentProfiles(),
|
|
418
|
+
),
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
if (parsed.action === "show" && parsed.name) {
|
|
422
|
+
const agent = await getAgent(parsed.name, context.cwd);
|
|
423
|
+
if (!agent) {
|
|
424
|
+
return failure("NOT_FOUND", `No such agent: ${parsed.name}`);
|
|
425
|
+
}
|
|
426
|
+
return success(formatAgentDetail(agent));
|
|
427
|
+
}
|
|
428
|
+
return failure(
|
|
429
|
+
"UNSUPPORTED_ARGUMENTS",
|
|
430
|
+
"Only /agents list and /agents show <name> are supported over stream-json.",
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function runMemory(context, deps) {
|
|
435
|
+
if (context.event.argumentText) {
|
|
436
|
+
return unsupportedArguments(
|
|
437
|
+
"memory",
|
|
438
|
+
context.event,
|
|
439
|
+
"Use /memory without arguments.",
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
const { renderMemoryFiles: defaultRenderer } =
|
|
443
|
+
await import("../repl/memory-status.js");
|
|
444
|
+
const renderMemoryFiles = deps.renderMemoryFiles || defaultRenderer;
|
|
445
|
+
// This must reflect what was captured when THIS session's system prompt was
|
|
446
|
+
// composed. Re-scanning disk here could claim a newly-created file was loaded
|
|
447
|
+
// even though the model has never seen it.
|
|
448
|
+
const loaded = context.loadedInstructions || { files: [], warnings: [] };
|
|
449
|
+
return success(
|
|
450
|
+
renderMemoryFiles(loaded, {
|
|
451
|
+
enabled: context.projectMemoryEnabled !== false,
|
|
452
|
+
}),
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Execute a parsed command against live session state.
|
|
458
|
+
*
|
|
459
|
+
* Returns a command-local result; callers decide how it is represented on the
|
|
460
|
+
* wire. This function never throws for user-facing command failures.
|
|
461
|
+
*/
|
|
462
|
+
export async function executeSessionSlashCommand(
|
|
463
|
+
event,
|
|
464
|
+
context = {},
|
|
465
|
+
deps = {},
|
|
466
|
+
) {
|
|
467
|
+
const command = String(event?.command || "").toLowerCase();
|
|
468
|
+
if (event?.validationError) {
|
|
469
|
+
return failure("INVALID_REQUEST", event.validationError);
|
|
470
|
+
}
|
|
471
|
+
if (!SESSION_SLASH_COMMANDS.includes(command)) {
|
|
472
|
+
return failure(
|
|
473
|
+
"UNKNOWN_COMMAND",
|
|
474
|
+
`Unknown session slash command "/${command || "?"}". Supported: ${SESSION_SLASH_COMMANDS.map((name) => `/${name}`).join(", ")}`,
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const scoped = { ...context, event };
|
|
479
|
+
try {
|
|
480
|
+
switch (command) {
|
|
481
|
+
case "status":
|
|
482
|
+
return await runStatus(scoped, deps);
|
|
483
|
+
case "doctor":
|
|
484
|
+
return await runDoctor(scoped, deps);
|
|
485
|
+
case "mcp":
|
|
486
|
+
return await runMcp(scoped, deps);
|
|
487
|
+
case "hooks":
|
|
488
|
+
return await runHooks(scoped, deps);
|
|
489
|
+
case "permissions":
|
|
490
|
+
return await runPermissions(scoped, deps);
|
|
491
|
+
case "agents":
|
|
492
|
+
return await runAgents(scoped, deps);
|
|
493
|
+
case "tasks":
|
|
494
|
+
return await runTasks(scoped, deps);
|
|
495
|
+
case "memory":
|
|
496
|
+
return await runMemory(scoped, deps);
|
|
497
|
+
default:
|
|
498
|
+
return failure("UNKNOWN_COMMAND", `Unknown command: /${command}`);
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
return failure(
|
|
502
|
+
"COMMAND_FAILED",
|
|
503
|
+
`/${command} failed: ${String(error?.message || error)}`,
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* - 简化模式:ffmpeg volumedetect + 等间距伪 beat (MVP)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { spawn } from "child_process";
|
|
15
14
|
import { promises as fs } from "fs";
|
|
16
15
|
import path from "path";
|
|
16
|
+
import { spawnMediaProcess } from "../media-process.js";
|
|
17
17
|
|
|
18
18
|
export async function extractSubtitle(
|
|
19
19
|
audioPath,
|
|
@@ -123,9 +123,12 @@ async function analyzeBeatsMadmom(audioPath, outputDir) {
|
|
|
123
123
|
"scripts",
|
|
124
124
|
"madmom-beats.py",
|
|
125
125
|
);
|
|
126
|
-
const proc =
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
const proc = spawnMediaProcess(
|
|
127
|
+
"python",
|
|
128
|
+
[script, audioPath],
|
|
129
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
130
|
+
"video-editing:beat-analysis",
|
|
131
|
+
);
|
|
129
132
|
let stdout = "";
|
|
130
133
|
let stderr = "";
|
|
131
134
|
proc.stdout.on("data", (d) => (stdout += d.toString("utf8")));
|
|
@@ -161,7 +164,12 @@ export async function getAudioDuration(audioPath) {
|
|
|
161
164
|
"-of",
|
|
162
165
|
"csv=p=0",
|
|
163
166
|
];
|
|
164
|
-
const proc =
|
|
167
|
+
const proc = spawnMediaProcess(
|
|
168
|
+
"ffprobe",
|
|
169
|
+
args,
|
|
170
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
171
|
+
"video-editing:audio-probe",
|
|
172
|
+
);
|
|
165
173
|
let stdout = "";
|
|
166
174
|
proc.stdout.on("data", (d) => (stdout += d));
|
|
167
175
|
proc.on("close", (code) => {
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* - scene.json — 场景切分 + caption
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { spawn } from "child_process";
|
|
10
9
|
import { promises as fs } from "fs";
|
|
11
10
|
import path from "path";
|
|
11
|
+
import { spawnMediaProcess } from "../media-process.js";
|
|
12
12
|
|
|
13
13
|
const DEFAULT_FPS = 2;
|
|
14
14
|
|
|
@@ -30,7 +30,12 @@ export async function extractFrames(
|
|
|
30
30
|
"2",
|
|
31
31
|
path.join(framesDir, "frame_%06d.png"),
|
|
32
32
|
];
|
|
33
|
-
const proc =
|
|
33
|
+
const proc = spawnMediaProcess(
|
|
34
|
+
"ffmpeg",
|
|
35
|
+
args,
|
|
36
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
37
|
+
"video-editing:frame-extract",
|
|
38
|
+
);
|
|
34
39
|
let stderr = "";
|
|
35
40
|
proc.stderr.on("data", (d) => (stderr += d));
|
|
36
41
|
proc.on("close", (code) => {
|
|
@@ -59,7 +64,12 @@ export async function detectScenes(
|
|
|
59
64
|
"null",
|
|
60
65
|
"-",
|
|
61
66
|
];
|
|
62
|
-
const proc =
|
|
67
|
+
const proc = spawnMediaProcess(
|
|
68
|
+
"ffmpeg",
|
|
69
|
+
args,
|
|
70
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
71
|
+
"video-editing:scene-detect",
|
|
72
|
+
);
|
|
63
73
|
let stderr = "";
|
|
64
74
|
proc.stderr.on("data", (d) => (stderr += d));
|
|
65
75
|
proc.on("close", async (code) => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import executionBroker from "../../lib/process-execution-broker/index.js";
|
|
2
|
+
|
|
3
|
+
export const _deps = {
|
|
4
|
+
spawn: (...args) => executionBroker.spawn(...args),
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function spawnMediaProcess(
|
|
8
|
+
file,
|
|
9
|
+
args,
|
|
10
|
+
options = {},
|
|
11
|
+
origin = "video-editing:process",
|
|
12
|
+
) {
|
|
13
|
+
return _deps.spawn(file, args, {
|
|
14
|
+
...options,
|
|
15
|
+
origin,
|
|
16
|
+
policy: "allow",
|
|
17
|
+
scope: "video-editing",
|
|
18
|
+
shell: false,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Phase 4 扩展: 对话区 ducking + 结尾 fade
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { spawnMediaProcess } from "../media-process.js";
|
|
9
9
|
|
|
10
10
|
export async function mixAudio(videoPath, audioPath, outputPath, options = {}) {
|
|
11
11
|
const bgmVolume = options.bgmVolume ?? 0.3;
|
|
@@ -38,7 +38,12 @@ export async function mixAudio(videoPath, audioPath, outputPath, options = {}) {
|
|
|
38
38
|
outputPath,
|
|
39
39
|
];
|
|
40
40
|
|
|
41
|
-
const proc =
|
|
41
|
+
const proc = spawnMediaProcess(
|
|
42
|
+
"ffmpeg",
|
|
43
|
+
args,
|
|
44
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
45
|
+
"video-editing:audio-mix",
|
|
46
|
+
);
|
|
42
47
|
let stderr = "";
|
|
43
48
|
proc.stderr.on("data", (d) => (stderr += d));
|
|
44
49
|
proc.on("close", (code) => {
|
|
@@ -70,7 +75,12 @@ export async function mixAudioWithDucking(
|
|
|
70
75
|
].join(";");
|
|
71
76
|
|
|
72
77
|
const args = buildFfmpegArgs(videoPath, audioPath, outputPath, filter);
|
|
73
|
-
const proc =
|
|
78
|
+
const proc = spawnMediaProcess(
|
|
79
|
+
"ffmpeg",
|
|
80
|
+
args,
|
|
81
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
82
|
+
"video-editing:audio-duck",
|
|
83
|
+
);
|
|
74
84
|
let stderr = "";
|
|
75
85
|
proc.stderr.on("data", (d) => (stderr += d.toString("utf8")));
|
|
76
86
|
proc.on("close", (code) => {
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* ffmpeg-concat.js — concat demuxer 拼接多个片段
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { spawn } from "child_process";
|
|
6
5
|
import { promises as fs } from "fs";
|
|
7
6
|
import path from "path";
|
|
7
|
+
import { spawnMediaProcess } from "../media-process.js";
|
|
8
8
|
|
|
9
9
|
export async function concatClips(clipPaths, workDir) {
|
|
10
10
|
if (clipPaths.length === 0) throw new Error("No clips to concatenate");
|
|
@@ -30,7 +30,12 @@ export async function concatClips(clipPaths, workDir) {
|
|
|
30
30
|
"copy",
|
|
31
31
|
outPath,
|
|
32
32
|
];
|
|
33
|
-
const proc =
|
|
33
|
+
const proc = spawnMediaProcess(
|
|
34
|
+
"ffmpeg",
|
|
35
|
+
args,
|
|
36
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
37
|
+
"video-editing:concat",
|
|
38
|
+
);
|
|
34
39
|
let stderr = "";
|
|
35
40
|
proc.stderr.on("data", (d) => (stderr += d));
|
|
36
41
|
proc.on("close", (code) => {
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* ffmpeg-extract.js — 按 shot_point 从视频中抽取片段
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { spawn } from "child_process";
|
|
6
5
|
import { promises as fs } from "fs";
|
|
7
6
|
import path from "path";
|
|
7
|
+
import { spawnMediaProcess } from "../media-process.js";
|
|
8
8
|
|
|
9
9
|
export async function extractClips(videoPath, shotPoints, workDir) {
|
|
10
10
|
const clipsDir = path.join(workDir, "clips");
|
|
@@ -48,7 +48,12 @@ function extractSingle(videoPath, startSec, duration, outFile) {
|
|
|
48
48
|
"make_zero",
|
|
49
49
|
outFile,
|
|
50
50
|
];
|
|
51
|
-
const proc =
|
|
51
|
+
const proc = spawnMediaProcess(
|
|
52
|
+
"ffmpeg",
|
|
53
|
+
args,
|
|
54
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
55
|
+
"video-editing:clip-extract",
|
|
56
|
+
);
|
|
52
57
|
let stderr = "";
|
|
53
58
|
proc.stderr.on("data", (d) => (stderr += d));
|
|
54
59
|
proc.on("close", (code) => {
|