@ken-jo/agent-connector 0.4.6 → 0.4.8

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.
Files changed (187) hide show
  1. package/dist/{action-EV63GAPY.js → action-RWPRIZIC.js} +10 -10
  2. package/dist/{amazon-q-COAL3764.js → amazon-q-5LJOB5L3.js} +6 -18
  3. package/dist/amazon-q-5LJOB5L3.js.map +1 -0
  4. package/dist/{amp-XJQ4TDAU.js → amp-PU4LFSY6.js} +16 -48
  5. package/dist/amp-PU4LFSY6.js.map +1 -0
  6. package/dist/{antigravity-AMKKKHS4.js → antigravity-DWW35HZO.js} +6 -5
  7. package/dist/{antigravity-cli-XERXEH5W.js → antigravity-cli-VTQ6DNWL.js} +6 -5
  8. package/dist/{antigravity-cli-XERXEH5W.js.map → antigravity-cli-VTQ6DNWL.js.map} +1 -1
  9. package/dist/{chunk-F5RX62T7.js → chunk-7QRUZAUG.js} +2 -2
  10. package/dist/{chunk-G5CQHVQG.js → chunk-7TVBFRVI.js} +12 -15
  11. package/dist/chunk-7TVBFRVI.js.map +1 -0
  12. package/dist/{chunk-QC5A3TFQ.js → chunk-A2T4Q7RM.js} +18 -18
  13. package/dist/{chunk-2X4EH2D7.js → chunk-CDVOBKSG.js} +4 -4
  14. package/dist/{chunk-RQPKL62M.js → chunk-CENEXP7K.js} +9 -38
  15. package/dist/chunk-CENEXP7K.js.map +1 -0
  16. package/dist/chunk-FBJQGRET.js +38 -0
  17. package/dist/chunk-FBJQGRET.js.map +1 -0
  18. package/dist/{chunk-WZA4CSGJ.js → chunk-H53QZQJI.js} +15 -2
  19. package/dist/chunk-H53QZQJI.js.map +1 -0
  20. package/dist/{chunk-DCK5FADX.js → chunk-HOE2MN36.js} +2 -2
  21. package/dist/{chunk-7L6ITC4T.js → chunk-IEGR4TP3.js} +22 -6
  22. package/dist/chunk-IEGR4TP3.js.map +1 -0
  23. package/dist/{chunk-CPLK7GTE.js → chunk-KG6T3YNJ.js} +353 -36
  24. package/dist/chunk-KG6T3YNJ.js.map +1 -0
  25. package/dist/{chunk-MZF2YCXX.js → chunk-MOW2RKHX.js} +2 -2
  26. package/dist/{chunk-EXLS7BSI.js → chunk-NLFPH4S2.js} +10 -2
  27. package/dist/chunk-NLFPH4S2.js.map +1 -0
  28. package/dist/{chunk-EROJDXMW.js → chunk-NYR55UT7.js} +2 -2
  29. package/dist/{chunk-SRVAA2VN.js → chunk-OZ5EXXQV.js} +2 -2
  30. package/dist/{chunk-I5MLE6S7.js → chunk-TO57WYBB.js} +15 -1
  31. package/dist/chunk-TO57WYBB.js.map +1 -0
  32. package/dist/chunk-UM5S7DKK.js +33 -0
  33. package/dist/chunk-UM5S7DKK.js.map +1 -0
  34. package/dist/{chunk-5G4EPJ4Y.js → chunk-VRKTVCYJ.js} +7 -7
  35. package/dist/{chunk-QOGMDOUX.js → chunk-WI6YSXOC.js} +3 -3
  36. package/dist/{chunk-BMD7ANKF.js → chunk-XQUUNAGC.js} +36 -36
  37. package/dist/{chunk-LQRAPXS4.js → chunk-YS2NYJ6R.js} +15 -49
  38. package/dist/chunk-YS2NYJ6R.js.map +1 -0
  39. package/dist/{chunk-H67V7DEL.js → chunk-YYISBXOM.js} +7 -7
  40. package/dist/{claude-code-T2LPFNC3.js → claude-code-64X6RR2S.js} +46 -133
  41. package/dist/claude-code-64X6RR2S.js.map +1 -0
  42. package/dist/cli/sdk.js +4 -4
  43. package/dist/cli.js +1 -1
  44. package/dist/{cline-WRYMNS7Z.js → cline-T7ZDJYZQ.js} +15 -47
  45. package/dist/cline-T7ZDJYZQ.js.map +1 -0
  46. package/dist/{codebuff-PODYPOOP.js → codebuff-PPPY3SJV.js} +9 -40
  47. package/dist/codebuff-PPPY3SJV.js.map +1 -0
  48. package/dist/{codex-O4G7D7KK.js → codex-EEVN43C2.js} +115 -172
  49. package/dist/codex-EEVN43C2.js.map +1 -0
  50. package/dist/{continue-K43SUHLQ.js → continue-VLLZO4PM.js} +12 -33
  51. package/dist/continue-VLLZO4PM.js.map +1 -0
  52. package/dist/{copilot-cli-Q3SSPT4V.js → copilot-cli-A7G5WB4N.js} +17 -64
  53. package/dist/copilot-cli-A7G5WB4N.js.map +1 -0
  54. package/dist/{crush-MBEH2R3I.js → crush-AH24GDDB.js} +17 -42
  55. package/dist/crush-AH24GDDB.js.map +1 -0
  56. package/dist/{cursor-TMBXDYR4.js → cursor-GJBSMXI6.js} +61 -183
  57. package/dist/cursor-GJBSMXI6.js.map +1 -0
  58. package/dist/{detect-AHJIUMBO.js → detect-YDCZNV4W.js} +4 -4
  59. package/dist/{doctor-G5MLCPAB.js → doctor-DGC7P3P7.js} +13 -12
  60. package/dist/{doctor-G5MLCPAB.js.map → doctor-DGC7P3P7.js.map} +1 -1
  61. package/dist/{droid-JC2NT6S4.js → droid-RBWCFKL3.js} +63 -144
  62. package/dist/droid-RBWCFKL3.js.map +1 -0
  63. package/dist/{gemini-cli-ILR7M5X3.js → gemini-cli-CFPFEJEA.js} +20 -63
  64. package/dist/gemini-cli-CFPFEJEA.js.map +1 -0
  65. package/dist/{goose-SFO5BBGW.js → goose-UBJVQFUE.js} +107 -183
  66. package/dist/goose-UBJVQFUE.js.map +1 -0
  67. package/dist/{hermes-3N7WN4WS.js → hermes-6FXGXLNL.js} +41 -83
  68. package/dist/hermes-6FXGXLNL.js.map +1 -0
  69. package/dist/{hook-76JBKTUX.js → hook-CDGSCASQ.js} +10 -10
  70. package/dist/index.js +2 -2
  71. package/dist/{install-R7252QFH.js → install-2D3UKEME.js} +13 -12
  72. package/dist/{install-R7252QFH.js.map → install-2D3UKEME.js.map} +1 -1
  73. package/dist/{jetbrains-copilot-NRZHI5PC.js → jetbrains-copilot-K26KMA3U.js} +12 -37
  74. package/dist/jetbrains-copilot-K26KMA3U.js.map +1 -0
  75. package/dist/{kilo-IX4T3OHJ.js → kilo-BKZJ26RM.js} +26 -90
  76. package/dist/kilo-BKZJ26RM.js.map +1 -0
  77. package/dist/{kilo-cli-2AYCXBQD.js → kilo-cli-6UVWCXQD.js} +17 -48
  78. package/dist/kilo-cli-6UVWCXQD.js.map +1 -0
  79. package/dist/{kimi-2YFK2GBA.js → kimi-QALYPSRR.js} +20 -31
  80. package/dist/kimi-QALYPSRR.js.map +1 -0
  81. package/dist/{kiro-YL6AWUYG.js → kiro-J5J42I5Z.js} +12 -31
  82. package/dist/kiro-J5J42I5Z.js.map +1 -0
  83. package/dist/{leaderboard-V6VWA5ZV.js → leaderboard-VZQXNWTX.js} +2 -2
  84. package/dist/{mimo-code-FTMGSKMQ.js → mimo-code-BEE622P2.js} +22 -87
  85. package/dist/mimo-code-BEE622P2.js.map +1 -0
  86. package/dist/{mux-5XZFB44C.js → mux-5HTNKK4K.js} +10 -54
  87. package/dist/mux-5HTNKK4K.js.map +1 -0
  88. package/dist/{nemoclaw-CQYXZVQN.js → nemoclaw-DNFIZFDF.js} +5 -5
  89. package/dist/{omp-2QMEY6HD.js → omp-I3UTRBNP.js} +16 -48
  90. package/dist/omp-I3UTRBNP.js.map +1 -0
  91. package/dist/{openclaw-UZC437RW.js → openclaw-QE2LMWIF.js} +5 -5
  92. package/dist/{opencode-L4BGFP25.js → opencode-ZFSUYJXI.js} +22 -88
  93. package/dist/opencode-ZFSUYJXI.js.map +1 -0
  94. package/dist/{package-DNLQTPNN.js → package-BSCCMW4E.js} +8 -8
  95. package/dist/{pi-PVVPVWKG.js → pi-H2KBBDDJ.js} +4 -4
  96. package/dist/{qwen-code-G4CXKUHR.js → qwen-code-5LE34FNE.js} +63 -152
  97. package/dist/qwen-code-5LE34FNE.js.map +1 -0
  98. package/dist/{roo-code-67TYJIQR.js → roo-code-XH4GCYIS.js} +15 -52
  99. package/dist/roo-code-XH4GCYIS.js.map +1 -0
  100. package/dist/runtime/index.js +8 -8
  101. package/dist/sdk/index.js +3 -3
  102. package/dist/sdk/test.js +5 -5
  103. package/dist/{serve-EOVWYBGZ.js → serve-MQNGPY2H.js} +10 -10
  104. package/dist/{status-VTD6XXLW.js → status-MRHVRDFY.js} +8 -7
  105. package/dist/{status-VTD6XXLW.js.map → status-MRHVRDFY.js.map} +1 -1
  106. package/dist/{statusline-3ET5YPQ5.js → statusline-IYWA75TR.js} +9 -9
  107. package/dist/{telemetry-QCTMKDBQ.js → telemetry-IONIOMLC.js} +5 -5
  108. package/dist/{trae-7CZZGOGI.js → trae-RVNRSV7X.js} +10 -39
  109. package/dist/trae-RVNRSV7X.js.map +1 -0
  110. package/dist/{uninstall-FL3SH2DT.js → uninstall-ESLSVCLE.js} +13 -12
  111. package/dist/{uninstall-FL3SH2DT.js.map → uninstall-ESLSVCLE.js.map} +1 -1
  112. package/dist/{upgrade-SLFG2FCT.js → upgrade-VICY5T64.js} +13 -12
  113. package/dist/{upgrade-SLFG2FCT.js.map → upgrade-VICY5T64.js.map} +1 -1
  114. package/dist/{usage-IRBLVUZM.js → usage-TCUWV2YZ.js} +2 -2
  115. package/dist/{usage-event-VFF77WYY.js → usage-event-CBZZP37Q.js} +9 -9
  116. package/dist/{vscode-copilot-MF2CDNMT.js → vscode-copilot-B2QBBHJW.js} +17 -65
  117. package/dist/vscode-copilot-B2QBBHJW.js.map +1 -0
  118. package/dist/{warp-VKG55TAT.js → warp-5R54ST3N.js} +7 -19
  119. package/dist/warp-5R54ST3N.js.map +1 -0
  120. package/dist/{windsurf-FU5G4SHF.js → windsurf-L32UEOLY.js} +6 -18
  121. package/dist/windsurf-L32UEOLY.js.map +1 -0
  122. package/dist/{zed-SFUHL3GS.js → zed-FDJHJZKD.js} +11 -21
  123. package/dist/zed-FDJHJZKD.js.map +1 -0
  124. package/package.json +1 -1
  125. package/dist/amazon-q-COAL3764.js.map +0 -1
  126. package/dist/amp-XJQ4TDAU.js.map +0 -1
  127. package/dist/chunk-7L6ITC4T.js.map +0 -1
  128. package/dist/chunk-CPLK7GTE.js.map +0 -1
  129. package/dist/chunk-EXLS7BSI.js.map +0 -1
  130. package/dist/chunk-G5CQHVQG.js.map +0 -1
  131. package/dist/chunk-I5MLE6S7.js.map +0 -1
  132. package/dist/chunk-LQRAPXS4.js.map +0 -1
  133. package/dist/chunk-RQPKL62M.js.map +0 -1
  134. package/dist/chunk-WZA4CSGJ.js.map +0 -1
  135. package/dist/claude-code-T2LPFNC3.js.map +0 -1
  136. package/dist/cline-WRYMNS7Z.js.map +0 -1
  137. package/dist/codebuff-PODYPOOP.js.map +0 -1
  138. package/dist/codex-O4G7D7KK.js.map +0 -1
  139. package/dist/continue-K43SUHLQ.js.map +0 -1
  140. package/dist/copilot-cli-Q3SSPT4V.js.map +0 -1
  141. package/dist/crush-MBEH2R3I.js.map +0 -1
  142. package/dist/cursor-TMBXDYR4.js.map +0 -1
  143. package/dist/droid-JC2NT6S4.js.map +0 -1
  144. package/dist/gemini-cli-ILR7M5X3.js.map +0 -1
  145. package/dist/goose-SFO5BBGW.js.map +0 -1
  146. package/dist/hermes-3N7WN4WS.js.map +0 -1
  147. package/dist/jetbrains-copilot-NRZHI5PC.js.map +0 -1
  148. package/dist/kilo-IX4T3OHJ.js.map +0 -1
  149. package/dist/kilo-cli-2AYCXBQD.js.map +0 -1
  150. package/dist/kimi-2YFK2GBA.js.map +0 -1
  151. package/dist/kiro-YL6AWUYG.js.map +0 -1
  152. package/dist/mimo-code-FTMGSKMQ.js.map +0 -1
  153. package/dist/mux-5XZFB44C.js.map +0 -1
  154. package/dist/omp-2QMEY6HD.js.map +0 -1
  155. package/dist/opencode-L4BGFP25.js.map +0 -1
  156. package/dist/qwen-code-G4CXKUHR.js.map +0 -1
  157. package/dist/roo-code-67TYJIQR.js.map +0 -1
  158. package/dist/trae-7CZZGOGI.js.map +0 -1
  159. package/dist/vscode-copilot-MF2CDNMT.js.map +0 -1
  160. package/dist/warp-VKG55TAT.js.map +0 -1
  161. package/dist/windsurf-FU5G4SHF.js.map +0 -1
  162. package/dist/zed-SFUHL3GS.js.map +0 -1
  163. /package/dist/{action-EV63GAPY.js.map → action-RWPRIZIC.js.map} +0 -0
  164. /package/dist/{antigravity-AMKKKHS4.js.map → antigravity-DWW35HZO.js.map} +0 -0
  165. /package/dist/{chunk-F5RX62T7.js.map → chunk-7QRUZAUG.js.map} +0 -0
  166. /package/dist/{chunk-QC5A3TFQ.js.map → chunk-A2T4Q7RM.js.map} +0 -0
  167. /package/dist/{chunk-2X4EH2D7.js.map → chunk-CDVOBKSG.js.map} +0 -0
  168. /package/dist/{chunk-DCK5FADX.js.map → chunk-HOE2MN36.js.map} +0 -0
  169. /package/dist/{chunk-MZF2YCXX.js.map → chunk-MOW2RKHX.js.map} +0 -0
  170. /package/dist/{chunk-EROJDXMW.js.map → chunk-NYR55UT7.js.map} +0 -0
  171. /package/dist/{chunk-SRVAA2VN.js.map → chunk-OZ5EXXQV.js.map} +0 -0
  172. /package/dist/{chunk-5G4EPJ4Y.js.map → chunk-VRKTVCYJ.js.map} +0 -0
  173. /package/dist/{chunk-QOGMDOUX.js.map → chunk-WI6YSXOC.js.map} +0 -0
  174. /package/dist/{chunk-BMD7ANKF.js.map → chunk-XQUUNAGC.js.map} +0 -0
  175. /package/dist/{chunk-H67V7DEL.js.map → chunk-YYISBXOM.js.map} +0 -0
  176. /package/dist/{detect-AHJIUMBO.js.map → detect-YDCZNV4W.js.map} +0 -0
  177. /package/dist/{hook-76JBKTUX.js.map → hook-CDGSCASQ.js.map} +0 -0
  178. /package/dist/{leaderboard-V6VWA5ZV.js.map → leaderboard-VZQXNWTX.js.map} +0 -0
  179. /package/dist/{nemoclaw-CQYXZVQN.js.map → nemoclaw-DNFIZFDF.js.map} +0 -0
  180. /package/dist/{openclaw-UZC437RW.js.map → openclaw-QE2LMWIF.js.map} +0 -0
  181. /package/dist/{package-DNLQTPNN.js.map → package-BSCCMW4E.js.map} +0 -0
  182. /package/dist/{pi-PVVPVWKG.js.map → pi-H2KBBDDJ.js.map} +0 -0
  183. /package/dist/{serve-EOVWYBGZ.js.map → serve-MQNGPY2H.js.map} +0 -0
  184. /package/dist/{statusline-3ET5YPQ5.js.map → statusline-IYWA75TR.js.map} +0 -0
  185. /package/dist/{telemetry-QCTMKDBQ.js.map → telemetry-IONIOMLC.js.map} +0 -0
  186. /package/dist/{usage-IRBLVUZM.js.map → usage-TCUWV2YZ.js.map} +0 -0
  187. /package/dist/{usage-event-VFF77WYY.js.map → usage-event-CBZZP37Q.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/claude-code/wire.ts"],"sourcesContent":["/**\n * adapters/claude-code/wire — Claude Code hook wire protocol.\n *\n * Claude Code pipes a JSON object to a hook command on stdin and reads a JSON\n * object (or empty) from stdout, interpreting the exit code as well. This module\n * captures the exact stdin shape and the canonical reply shape so the adapter's\n * parse/format methods are a single source of truth.\n *\n * Stdin (documented Claude Code hook input):\n * {\n * session_id, transcript_path, cwd, hook_event_name,\n * tool_name, tool_input, tool_response, // tool events\n * permission_suggestions, // PermissionRequest\n * tool_use_id, error, is_interrupt, duration_ms, // PostToolUseFailure\n * agent_id, agent_type, // SubagentStart/Stop\n * agent_transcript_path, last_assistant_message, // SubagentStop\n * source, // SessionStart\n * reason, // SessionEnd\n * prompt, // UserPromptSubmit\n * trigger, // PreCompact\n * stop_hook_active, // Stop / SubagentStop\n * message // Notification\n * }\n *\n * Stdout (canonical reply): a `hookSpecificOutput` object keyed by\n * `hookEventName`, carrying `permissionDecision` (allow|deny|ask) +\n * `permissionDecisionReason`, `additionalContext`, and (PreToolUse)\n * `updatedInput`; PermissionRequest instead takes a nested\n * `decision: { behavior: \"allow\"|\"deny\", ... }`; Stop-class blocks\n * (Stop / SubagentStop / UserPromptSubmit / PostToolUse) take the TOP-LEVEL\n * `{\"decision\":\"block\",\"reason\"}`. Exit 0 = proceed; the JSON refines the\n * decision.\n */\n\nimport type { SessionStartEvent } from \"../../core/types.js\";\n\n/** Canonical Claude Code hook event names (the values of `hook_event_name`). */\nexport const CLAUDE_HOOK_EVENTS = [\n \"PreToolUse\",\n \"PostToolUse\",\n \"PreCompact\",\n \"SessionStart\",\n \"SessionEnd\",\n \"UserPromptSubmit\",\n \"Stop\",\n \"Notification\",\n \"PermissionRequest\",\n \"PostToolUseFailure\",\n \"SubagentStart\",\n \"SubagentStop\",\n] as const;\n\nexport type ClaudeHookEvent = (typeof CLAUDE_HOOK_EVENTS)[number];\n\n/**\n * Raw Claude Code hook stdin payload. Every field is optional — a given event\n * only populates the subset relevant to it. `connector` is NOT a Claude field;\n * it is threaded in by the universal entrypoint so parseEvent can recover the\n * connector id (the home binary passes `--connector <id>` and may inject it).\n */\nexport interface ClaudeWireInput {\n session_id?: string;\n transcript_path?: string;\n cwd?: string;\n hook_event_name?: string;\n\n // tool events\n tool_name?: string;\n tool_input?: Record<string, unknown>;\n /** PostToolUse result payload (string or structured). */\n tool_response?: unknown;\n\n // PermissionRequest — permission-update entries the dialog would offer.\n // NOTE: the docs explicitly state PermissionRequest has NO tool_use_id.\n permission_suggestions?: unknown[];\n\n // PostToolUseFailure\n tool_use_id?: string;\n error?: string;\n is_interrupt?: boolean;\n duration_ms?: number;\n\n // SubagentStart / SubagentStop — agent_type is unreliable on SubagentStop\n // (the SDK may not populate it); treat both as optional everywhere.\n agent_id?: string;\n agent_type?: string;\n // SubagentStop — the subagent's OWN transcript (transcript_path stays the\n // parent session's) + the text of its final response.\n agent_transcript_path?: string;\n last_assistant_message?: string;\n\n // SessionStart\n source?: string;\n // SessionEnd\n reason?: string;\n // UserPromptSubmit\n prompt?: string;\n // PreCompact\n trigger?: string;\n // Stop / SubagentStop\n stop_hook_active?: boolean;\n // Notification\n message?: string;\n\n /** Injected by the entrypoint so the runtime knows which connector to dispatch. */\n connector?: string;\n}\n\n/**\n * Extract a stable session id from a Claude wire payload.\n * Priority mirrors the proven adapter: transcript UUID > session_id > \"\".\n * (The framework's normalized event uses \"\" when no id is available, per\n * core/types BaseEvent.sessionId docstring — no ppid fabrication here.)\n */\nexport function extractSessionId(input: ClaudeWireInput): string {\n if (typeof input.transcript_path === \"string\") {\n const m = input.transcript_path.match(/([a-f0-9-]{36})\\.jsonl$/);\n if (m && m[1]) return m[1];\n }\n if (typeof input.session_id === \"string\" && input.session_id !== \"\") {\n return input.session_id;\n }\n return \"\";\n}\n\n/** Coerce a Claude PostToolUse `tool_response` into a string for the normalized event. */\nexport function toolResponseToString(value: unknown): string | undefined {\n if (value == null) return undefined;\n if (typeof value === \"string\") return value;\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n/** Map a host's raw session-start \"source\" to the canonical SessionStart source. */\nexport function normalizeSessionSource(source: string | undefined): SessionStartEvent[\"source\"] {\n switch (source) {\n case \"compact\":\n return \"compact\";\n case \"resume\":\n return \"resume\";\n case \"clear\":\n return \"clear\";\n default:\n return \"startup\";\n }\n}\n"],"mappings":";;;AAkHO,SAAS,iBAAiB,OAAgC;AAC/D,MAAI,OAAO,MAAM,oBAAoB,UAAU;AAC7C,UAAM,IAAI,MAAM,gBAAgB,MAAM,yBAAyB;AAC/D,QAAI,KAAK,EAAE,CAAC,EAAG,QAAO,EAAE,CAAC;AAAA,EAC3B;AACA,MAAI,OAAO,MAAM,eAAe,YAAY,MAAM,eAAe,IAAI;AACnE,WAAO,MAAM;AAAA,EACf;AACA,SAAO;AACT;AAGO,SAAS,qBAAqB,OAAoC;AACvE,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO,OAAO,KAAK;AAAA,EACrB;AACF;AAGO,SAAS,uBAAuB,QAAyD;AAC9F,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  REGISTERED_PLATFORM_IDS
4
- } from "./chunk-BMD7ANKF.js";
4
+ } from "./chunk-XQUUNAGC.js";
5
5
  import {
6
6
  CONFIG_PATCH_SEGMENT_RE,
7
7
  configPatchNamespaceViolation,
@@ -495,4 +495,4 @@ export {
495
495
  defineConfigPatch,
496
496
  defineNativeHook
497
497
  };
498
- //# sourceMappingURL=chunk-DCK5FADX.js.map
498
+ //# sourceMappingURL=chunk-HOE2MN36.js.map
@@ -9,13 +9,16 @@ ${stringifyYaml(frontmatter)}---
9
9
  ${body}
10
10
  `;
11
11
  }
12
- function renderCommandMd(cmd) {
12
+ function renderCommandMd(cmd, opts = {}) {
13
+ const { includeToolsAndModel = true } = opts;
13
14
  const frontmatter = {};
14
15
  if (cmd.description !== void 0) frontmatter.description = cmd.description;
15
16
  if (cmd.argumentHint !== void 0) frontmatter["argument-hint"] = cmd.argumentHint;
16
- const allow = cmd.tools?.allow;
17
- if (allow && allow.length > 0) frontmatter["allowed-tools"] = allow.join(", ");
18
- if (cmd.model !== void 0) frontmatter.model = cmd.model;
17
+ if (includeToolsAndModel) {
18
+ const allow = cmd.tools?.allow;
19
+ if (allow && allow.length > 0) frontmatter["allowed-tools"] = allow.join(", ");
20
+ if (cmd.model !== void 0) frontmatter.model = cmd.model;
21
+ }
19
22
  if (cmd.extra) Object.assign(frontmatter, cmd.extra);
20
23
  return renderFrontmatterMd(frontmatter, cmd.prompt);
21
24
  }
@@ -44,11 +47,24 @@ function renderSubagentMd(agent) {
44
47
  if (agent.extra) Object.assign(frontmatter, agent.extra);
45
48
  return renderFrontmatterMd(frontmatter, agent.prompt);
46
49
  }
50
+ function renderOpenCodeSubagentMd(agent) {
51
+ const frontmatter = {
52
+ description: agent.description,
53
+ mode: "subagent"
54
+ };
55
+ if (agent.model !== void 0) frontmatter.model = agent.model;
56
+ if (agent.readonly === true) {
57
+ frontmatter.permission = { edit: "deny", bash: "deny" };
58
+ }
59
+ if (agent.extra) Object.assign(frontmatter, agent.extra);
60
+ return renderFrontmatterMd(frontmatter, agent.prompt);
61
+ }
47
62
 
48
63
  export {
49
64
  renderFrontmatterMd,
50
65
  renderCommandMd,
51
66
  renderSkillMd,
52
- renderSubagentMd
67
+ renderSubagentMd,
68
+ renderOpenCodeSubagentMd
53
69
  };
54
- //# sourceMappingURL=chunk-7L6ITC4T.js.map
70
+ //# sourceMappingURL=chunk-IEGR4TP3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/claude-code/render.ts"],"sourcesContent":["/**\n * adapters/claude-code/render — shared content-surface renderers.\n *\n * The single source of truth for how a connector's commands / skills / subagents\n * are serialized into Claude Code's native `md + YAML-frontmatter` documents.\n * BOTH the live claude-code adapter (which writes into ~/.claude or .claude when\n * installing) AND `core/package` (which emits a marketplace-installable plugin\n * bundle) import these so the two paths produce BYTE-IDENTICAL output — there is\n * no second, subtly-different copy to drift.\n *\n * The frontmatter serializer reuses the `yaml` package (the same serializer\n * core/yaml and BaseAdapter.renderFrontmatterMd use): a `---` fence, the YAML\n * block, a closing `---`, a blank line, then the markdown body and a trailing\n * newline.\n */\n\nimport { stringify as stringifyYaml } from \"yaml\";\n\nimport type { CommandDef, SkillDef, SubagentDef } from \"../../core/types.js\";\n\n/**\n * Render a YAML-frontmatter + markdown body document:\n * \"---\\n\" + <yaml> + \"---\\n\\n\" + <body> + \"\\n\".\n * Identical to BaseAdapter.renderFrontmatterMd (which now delegates here).\n */\nexport function renderFrontmatterMd(\n frontmatter: Record<string, unknown>,\n body: string,\n): string {\n return `---\\n${stringifyYaml(frontmatter)}---\\n\\n${body}\\n`;\n}\n\n/**\n * Render a command to md+frontmatter\n * (description, argument-hint, allowed-tools, model + verbatim extra).\n *\n * `opts.includeToolsAndModel` (default `true`) controls the allowed-tools and\n * model insertions. Hosts whose command frontmatter is only\n * (description, argument-hint + verbatim extra) — i.e. they have no native\n * allowed-tools/model keys — pass `{ includeToolsAndModel: false }` to suppress\n * both; the default reproduces the full shape byte-for-byte.\n */\nexport function renderCommandMd(\n cmd: CommandDef,\n opts: { includeToolsAndModel?: boolean } = {},\n): string {\n const { includeToolsAndModel = true } = opts;\n const frontmatter: Record<string, unknown> = {};\n if (cmd.description !== undefined) frontmatter.description = cmd.description;\n if (cmd.argumentHint !== undefined) frontmatter[\"argument-hint\"] = cmd.argumentHint;\n if (includeToolsAndModel) {\n const allow = cmd.tools?.allow;\n if (allow && allow.length > 0) frontmatter[\"allowed-tools\"] = allow.join(\", \");\n if (cmd.model !== undefined) frontmatter.model = cmd.model;\n }\n if (cmd.extra) Object.assign(frontmatter, cmd.extra);\n return renderFrontmatterMd(frontmatter, cmd.prompt);\n}\n\n/**\n * Render a skill's SKILL.md: frontmatter (name, description + optional model,\n * allowed-tools, disable-model-invocation, verbatim extra) + body.\n */\nexport function renderSkillMd(skill: SkillDef): string {\n const frontmatter: Record<string, unknown> = {\n name: skill.name,\n description: skill.description,\n };\n if (skill.model !== undefined) frontmatter.model = skill.model;\n const allow = skill.tools?.allow;\n if (allow && allow.length > 0) frontmatter[\"allowed-tools\"] = allow.join(\", \");\n if (skill.disableModelInvocation !== undefined) {\n frontmatter[\"disable-model-invocation\"] = skill.disableModelInvocation;\n }\n if (skill.extra) Object.assign(frontmatter, skill.extra);\n return renderFrontmatterMd(frontmatter, skill.body);\n}\n\n/** Render a subagent to md+frontmatter (name, description, tools, model) + prompt body. */\nexport function renderSubagentMd(agent: SubagentDef): string {\n const frontmatter: Record<string, unknown> = {\n name: agent.name,\n description: agent.description,\n };\n const allow = agent.tools?.allow;\n if (allow && allow.length > 0) frontmatter.tools = allow.join(\", \");\n if (agent.model !== undefined) frontmatter.model = agent.model;\n if (agent.extra) Object.assign(frontmatter, agent.extra);\n return renderFrontmatterMd(frontmatter, agent.prompt);\n}\n\n/**\n * Render a subagent in OpenCode's shape (shared by opencode/kilo/mimo-code):\n * frontmatter (description, mode:\"subagent\", model?, permission? + verbatim\n * extra) with the system prompt as the body. `name` is NOT a frontmatter\n * field — it comes from the filename. `permission` is derived from the coarse\n * `readonly` knob: a readonly agent gets a per-tool deny map (edit/bash) so it\n * cannot mutate the workspace.\n */\nexport function renderOpenCodeSubagentMd(agent: SubagentDef): string {\n const frontmatter: Record<string, unknown> = {\n description: agent.description,\n mode: \"subagent\",\n };\n if (agent.model !== undefined) frontmatter.model = agent.model;\n if (agent.readonly === true) {\n // Per-tool permission map: deny mutating tools for a readonly subagent.\n frontmatter.permission = { edit: \"deny\", bash: \"deny\" };\n }\n if (agent.extra) Object.assign(frontmatter, agent.extra);\n return renderFrontmatterMd(frontmatter, agent.prompt);\n}\n"],"mappings":";;;AAgBA,SAAS,aAAa,qBAAqB;AASpC,SAAS,oBACd,aACA,MACQ;AACR,SAAO;AAAA,EAAQ,cAAc,WAAW,CAAC;AAAA;AAAA,EAAU,IAAI;AAAA;AACzD;AAYO,SAAS,gBACd,KACA,OAA2C,CAAC,GACpC;AACR,QAAM,EAAE,uBAAuB,KAAK,IAAI;AACxC,QAAM,cAAuC,CAAC;AAC9C,MAAI,IAAI,gBAAgB,OAAW,aAAY,cAAc,IAAI;AACjE,MAAI,IAAI,iBAAiB,OAAW,aAAY,eAAe,IAAI,IAAI;AACvE,MAAI,sBAAsB;AACxB,UAAM,QAAQ,IAAI,OAAO;AACzB,QAAI,SAAS,MAAM,SAAS,EAAG,aAAY,eAAe,IAAI,MAAM,KAAK,IAAI;AAC7E,QAAI,IAAI,UAAU,OAAW,aAAY,QAAQ,IAAI;AAAA,EACvD;AACA,MAAI,IAAI,MAAO,QAAO,OAAO,aAAa,IAAI,KAAK;AACnD,SAAO,oBAAoB,aAAa,IAAI,MAAM;AACpD;AAMO,SAAS,cAAc,OAAyB;AACrD,QAAM,cAAuC;AAAA,IAC3C,MAAM,MAAM;AAAA,IACZ,aAAa,MAAM;AAAA,EACrB;AACA,MAAI,MAAM,UAAU,OAAW,aAAY,QAAQ,MAAM;AACzD,QAAM,QAAQ,MAAM,OAAO;AAC3B,MAAI,SAAS,MAAM,SAAS,EAAG,aAAY,eAAe,IAAI,MAAM,KAAK,IAAI;AAC7E,MAAI,MAAM,2BAA2B,QAAW;AAC9C,gBAAY,0BAA0B,IAAI,MAAM;AAAA,EAClD;AACA,MAAI,MAAM,MAAO,QAAO,OAAO,aAAa,MAAM,KAAK;AACvD,SAAO,oBAAoB,aAAa,MAAM,IAAI;AACpD;AAGO,SAAS,iBAAiB,OAA4B;AAC3D,QAAM,cAAuC;AAAA,IAC3C,MAAM,MAAM;AAAA,IACZ,aAAa,MAAM;AAAA,EACrB;AACA,QAAM,QAAQ,MAAM,OAAO;AAC3B,MAAI,SAAS,MAAM,SAAS,EAAG,aAAY,QAAQ,MAAM,KAAK,IAAI;AAClE,MAAI,MAAM,UAAU,OAAW,aAAY,QAAQ,MAAM;AACzD,MAAI,MAAM,MAAO,QAAO,OAAO,aAAa,MAAM,KAAK;AACvD,SAAO,oBAAoB,aAAa,MAAM,MAAM;AACtD;AAUO,SAAS,yBAAyB,OAA4B;AACnE,QAAM,cAAuC;AAAA,IAC3C,aAAa,MAAM;AAAA,IACnB,MAAM;AAAA,EACR;AACA,MAAI,MAAM,UAAU,OAAW,aAAY,QAAQ,MAAM;AACzD,MAAI,MAAM,aAAa,MAAM;AAE3B,gBAAY,aAAa,EAAE,MAAM,QAAQ,MAAM,OAAO;AAAA,EACxD;AACA,MAAI,MAAM,MAAO,QAAO,OAAO,aAAa,MAAM,KAAK;AACvD,SAAO,oBAAoB,aAAa,MAAM,MAAM;AACtD;","names":[]}
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  renderFrontmatterMd
4
- } from "./chunk-7L6ITC4T.js";
4
+ } from "./chunk-IEGR4TP3.js";
5
5
  import {
6
- buildHomeBinActionCommand
7
- } from "./chunk-I5MLE6S7.js";
6
+ buildHomeBinActionCommand,
7
+ buildHomeBinHookCommand
8
+ } from "./chunk-TO57WYBB.js";
8
9
  import {
9
10
  MEMORY_CONTENT_SOFT_BUDGET_BYTES,
10
11
  listManagedBlocks,
@@ -102,6 +103,88 @@ function parseJsonc(text) {
102
103
  return JSON.parse(stripJsonc(text));
103
104
  }
104
105
 
106
+ // src/core/object-map.ts
107
+ function isMalformedRootValue(value) {
108
+ return value != null && (typeof value !== "object" || Array.isArray(value));
109
+ }
110
+ function unparseableWarn(platform, configPath) {
111
+ return {
112
+ platform,
113
+ action: "warn",
114
+ path: configPath,
115
+ detail: `existing ${configPath} is not parseable; left untouched (back it up / fix it, then re-run)`
116
+ };
117
+ }
118
+ function malformedWarn(platform, rootKey, configPath) {
119
+ return {
120
+ platform,
121
+ action: "warn",
122
+ path: configPath,
123
+ detail: `existing "${rootKey}" in ${configPath} is not an object map; left untouched (fix it, then re-run)`
124
+ };
125
+ }
126
+ function upsertInObjectMap(o) {
127
+ const policy = o.policy ?? "warn-skip";
128
+ if (o.codec.isPresentButUnparseable(o.configPath)) {
129
+ return unparseableWarn(o.platform, o.configPath);
130
+ }
131
+ const cfg = o.codec.parse(o.configPath) ?? {};
132
+ if (isMalformedRootValue(cfg[o.rootKey])) {
133
+ if (policy === "warn-skip") return malformedWarn(o.platform, o.rootKey, o.configPath);
134
+ cfg[o.rootKey] = {};
135
+ }
136
+ const bucket = cfg[o.rootKey] ?? (cfg[o.rootKey] = {});
137
+ const before = JSON.stringify(bucket[o.entryId]);
138
+ const after = JSON.stringify(o.entry);
139
+ let action;
140
+ if (before === void 0) action = "create";
141
+ else if (before === after) action = "skip";
142
+ else action = "update";
143
+ if (action !== "skip") {
144
+ bucket[o.entryId] = o.entry;
145
+ o.codec.serialize(o.configPath, cfg, o.dryRun ?? false);
146
+ }
147
+ return {
148
+ platform: o.platform,
149
+ action,
150
+ path: o.configPath,
151
+ detail: `${o.rootKey}.${o.entryId}`
152
+ };
153
+ }
154
+ function removeFromObjectMap(o) {
155
+ const policy = o.policy ?? "warn-skip";
156
+ if (o.codec.isPresentButUnparseable(o.configPath)) {
157
+ return unparseableWarn(o.platform, o.configPath);
158
+ }
159
+ const cfg = o.codec.parse(o.configPath);
160
+ if (cfg && isMalformedRootValue(cfg[o.rootKey])) {
161
+ if (policy === "warn-skip") return malformedWarn(o.platform, o.rootKey, o.configPath);
162
+ return {
163
+ platform: o.platform,
164
+ action: "skip",
165
+ path: o.configPath,
166
+ detail: `${o.rootKey}.${o.entryId} absent`
167
+ };
168
+ }
169
+ const bucket = cfg?.[o.rootKey];
170
+ if (!cfg || !bucket || !(o.entryId in bucket)) {
171
+ return {
172
+ platform: o.platform,
173
+ action: "skip",
174
+ path: o.configPath,
175
+ detail: `${o.rootKey}.${o.entryId} absent`
176
+ };
177
+ }
178
+ delete bucket[o.entryId];
179
+ o.codec.serialize(o.configPath, cfg, o.dryRun ?? false);
180
+ return {
181
+ platform: o.platform,
182
+ action: "remove",
183
+ path: o.configPath,
184
+ detail: `${o.rootKey}.${o.entryId}`
185
+ };
186
+ }
187
+
105
188
  // src/adapters/base.ts
106
189
  import {
107
190
  copyFileSync,
@@ -114,6 +197,36 @@ import {
114
197
  } from "fs";
115
198
  import { homedir } from "os";
116
199
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "path";
200
+
201
+ // src/core/hook-array.ts
202
+ function upsertInArray(array, candidate, ownsEntry) {
203
+ const idx = array.findIndex(ownsEntry);
204
+ if (idx >= 0) {
205
+ if (JSON.stringify(array[idx]) === JSON.stringify(candidate)) {
206
+ return { array, action: "skip" };
207
+ }
208
+ const next = array.slice();
209
+ next[idx] = candidate;
210
+ return { array: next, action: "update" };
211
+ }
212
+ return { array: [...array, candidate], action: "create" };
213
+ }
214
+ function removeFromArray(array, ownsEntry, stripInner) {
215
+ if (!stripInner) {
216
+ const kept2 = array.filter((e) => !ownsEntry(e));
217
+ return { array: kept2, removed: array.length - kept2.length };
218
+ }
219
+ const kept = [];
220
+ let removed = 0;
221
+ for (const e of array) {
222
+ const r = stripInner(e);
223
+ removed += r.removed;
224
+ if (r.next !== null) kept.push(r.next);
225
+ }
226
+ return { array: kept, removed };
227
+ }
228
+
229
+ // src/adapters/base.ts
117
230
  var AGENTS_MD_USER_PATHS = {
118
231
  // codex resolves its own user path in-adapter ($CODEX_HOME with ~ expansion
119
232
  // + the AGENTS.override.md shadow probe) — deliberately no row here.
@@ -735,51 +848,253 @@ var BaseAdapter = class {
735
848
  `, "utf8");
736
849
  }
737
850
  /**
738
- * Upsert `entry` at `config[rootKey][serverId]` in a JSON file, creating the
739
- * file/object as needed. Returns a ChangeRecord describing create/update/skip.
740
- * Idempotent: an identical entry yields "skip".
851
+ * True when a PRESENT rootKey value is not a plain object map (the user
852
+ * hand-edited it to an array / string / number / boolean). Assigning a server
853
+ * entry into such a value would corrupt the file silently (a named property on
854
+ * an Array is dropped by JSON.stringify) or throw under strict mode (a property
855
+ * write / `in` operator against a primitive), so callers warn-and-skip. A
856
+ * `null`/`undefined` value is NOT malformed — it is the well-formed "absent"
857
+ * case the helpers create fresh.
741
858
  */
742
- upsertServerInJson(configPath, rootKey, serverId, entry, dryRun = false) {
743
- if (this.isPresentButUnparseable(configPath)) {
859
+ isMalformedRootValue(value) {
860
+ return isMalformedRootValue(value);
861
+ }
862
+ /**
863
+ * Warn-skip ChangeRecord when an existing JSON `hooks` config is present-but-
864
+ * malformed (root hand-edited to an array/primitive, or an existing event
865
+ * bucket is a non-array) — installing a hook into it would silently drop the
866
+ * entry (a named property on an Array is dropped by JSON.stringify) or throw
867
+ * under strict mode (a `.push`/property write against a primitive or non-array
868
+ * bucket). Returns null when the hooks config is well-formed, including the
869
+ * absent case the installers create fresh (proceed normally). Mirrors the
870
+ * server-root guard (isMalformedRootValue). All targeted hosts use the Claude-
871
+ * style `{ Event: Matcher[] }` shape, so every present event bucket must be an
872
+ * array; a non-array bucket is the same malformed class as a wrong-typed root.
873
+ */
874
+ malformedHookRootSkip(configPath, hooksRoot) {
875
+ if (hooksRoot == null) return null;
876
+ if (this.isMalformedRootValue(hooksRoot)) {
744
877
  return {
745
878
  platform: this.id,
746
879
  action: "warn",
747
880
  path: configPath,
748
- detail: `existing ${configPath} is not parseable; left untouched (back it up / fix it, then re-run)`
881
+ detail: `existing "hooks" in ${configPath} is not an object map; left untouched (fix it, then re-run)`
749
882
  };
750
883
  }
751
- const cfg = this.readJson(configPath) ?? {};
752
- const bucket = cfg[rootKey] ??= {};
753
- const before = JSON.stringify(bucket[serverId]);
754
- const after = JSON.stringify(entry);
755
- let action;
756
- if (before === void 0) action = "create";
757
- else if (before === after) action = "skip";
758
- else action = "update";
759
- if (action !== "skip") {
760
- bucket[serverId] = entry;
761
- this.writeJson(configPath, cfg, dryRun);
884
+ for (const v of Object.values(hooksRoot)) {
885
+ if (v !== void 0 && !Array.isArray(v)) {
886
+ return {
887
+ platform: this.id,
888
+ action: "warn",
889
+ path: configPath,
890
+ detail: `existing "hooks" in ${configPath} has a non-array event bucket; left untouched (fix it, then re-run)`
891
+ };
892
+ }
762
893
  }
763
- return { platform: this.id, action, path: configPath, detail: `${rootKey}.${serverId}` };
894
+ return null;
764
895
  }
765
- /** Remove `config[rootKey][serverId]` from a JSON file. */
766
- removeServerFromJson(configPath, rootKey, serverId, dryRun = false) {
767
- if (this.isPresentButUnparseable(configPath)) {
768
- return {
896
+ // ── Shared object-map hook-array merge engine ────────────────────────────
897
+ // installHooks/uninstallHooks for every json-stdio host that stores hooks as
898
+ // `{ <event>: HookEntry[] }` share ONE create/update/skip + strip/remove
899
+ // orchestration. The host supplies a {@link HookMergeDescriptor} carrying
900
+ // EVERY observable string and ownership predicate — the engine body hardcodes
901
+ // none of them, so per-host wording (skip/remove/absent/no-match details) and
902
+ // FLAT-vs-NESTED removal stay host-controlled and never drift to a lowest-
903
+ // common-denominator phrasing the suites (which assert by action) would miss.
904
+ // The write-gate is the `mutated` bool, matching the migrated host exactly.
905
+ /**
906
+ * Install: upsert each `{ event, matcher }` in `pending` into the object-map
907
+ * `hooks` root at `configPath`. Reproduces the canonical host's loop: read
908
+ * `?? {}`, warn-skip on a malformed `hooks` root, then per pending item render
909
+ * the entry, find the owned slot, and create/update/skip. Writes once at the
910
+ * end iff anything mutated.
911
+ *
912
+ * Hosts that fold their `nativeHooks` surface into `pending` (claude-code) pass
913
+ * no `native` arg. Hosts that keep the native surface SEPARATE — verbatim
914
+ * event keys, EXACT-command ownership, a distinct `(native)` detail — pass it
915
+ * as the optional `native` list (cursor); that pass runs after the normalized
916
+ * loop and before the single write. Omitting it is byte-identical to before.
917
+ */
918
+ /**
919
+ * Resolve the per-event hook bucket. With "coerce" policy a present-but-non-
920
+ * array bucket (hand-edited) is replaced by a fresh array; otherwise `??=` is
921
+ * used (the warn-skip path already rejected any non-array bucket via
922
+ * malformedHookRootSkip, so `??=` only ever fills the absent case).
923
+ */
924
+ coerceHookBucket(hooks, event, coerce) {
925
+ if (coerce) {
926
+ return Array.isArray(hooks[event]) ? hooks[event] : hooks[event] = [];
927
+ }
928
+ return hooks[event] ??= [];
929
+ }
930
+ upsertHookEntries(ctx, configPath, pending, descriptor, native) {
931
+ const file = this.readJson(configPath) ?? {};
932
+ const coerce = descriptor.malformedPolicy === "coerce";
933
+ let hooks;
934
+ if (coerce) {
935
+ const existing = file.hooks;
936
+ hooks = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : file.hooks = {};
937
+ } else {
938
+ const skip = this.malformedHookRootSkip(configPath, file.hooks);
939
+ if (skip) return [skip];
940
+ hooks = file.hooks ??= {};
941
+ }
942
+ const changes = [];
943
+ let mutated = false;
944
+ for (const { event, matcher } of pending) {
945
+ const hostEvent = descriptor.mapEvent ? descriptor.mapEvent(event) : event;
946
+ if (hostEvent === void 0) {
947
+ changes.push({
948
+ platform: this.id,
949
+ action: "warn",
950
+ path: configPath,
951
+ detail: descriptor.unmappedWarnDetail(event)
952
+ });
953
+ continue;
954
+ }
955
+ const command = buildHomeBinHookCommand(
956
+ ctx.homeBinPath,
957
+ this.id,
958
+ event,
959
+ ctx.connector.id
960
+ );
961
+ const entry = descriptor.renderEntry(hostEvent, matcher, command);
962
+ const bucket = this.coerceHookBucket(hooks, hostEvent, coerce);
963
+ const r = upsertInArray(
964
+ bucket,
965
+ entry,
966
+ (e) => descriptor.entryOwnsCommand(e, command)
967
+ );
968
+ if (r.action !== "skip") {
969
+ hooks[hostEvent] = r.array;
970
+ mutated = true;
971
+ }
972
+ changes.push({
769
973
  platform: this.id,
770
- action: "warn",
974
+ action: r.action,
771
975
  path: configPath,
772
- detail: `existing ${configPath} is not parseable; left untouched (back it up / fix it, then re-run)`
773
- };
976
+ detail: r.action === "skip" ? descriptor.skipDetail(hostEvent) : `hooks.${hostEvent}`
977
+ });
774
978
  }
775
- const cfg = this.readJson(configPath);
776
- const bucket = cfg?.[rootKey];
777
- if (!cfg || !bucket || !(serverId in bucket)) {
778
- return { platform: this.id, action: "skip", path: configPath, detail: `${rootKey}.${serverId} absent` };
979
+ for (const { event, matcher } of native ?? []) {
980
+ const command = buildHomeBinHookCommand(
981
+ ctx.homeBinPath,
982
+ this.id,
983
+ event,
984
+ ctx.connector.id
985
+ );
986
+ const entry = descriptor.renderEntry(event, matcher, command);
987
+ const bucket = this.coerceHookBucket(hooks, event, coerce);
988
+ const r = upsertInArray(
989
+ bucket,
990
+ entry,
991
+ (e) => descriptor.nativeOwnsCommand(e, command)
992
+ );
993
+ if (r.action !== "skip") {
994
+ hooks[event] = r.array;
995
+ mutated = true;
996
+ }
997
+ changes.push({
998
+ platform: this.id,
999
+ action: r.action,
1000
+ path: configPath,
1001
+ detail: r.action === "skip" ? descriptor.nativeSkipDetail(event) : descriptor.nativeMutateDetail(event)
1002
+ });
1003
+ }
1004
+ if (mutated) {
1005
+ descriptor.onMutate?.(file);
1006
+ this.writeJson(configPath, file, ctx.dryRun);
779
1007
  }
780
- delete bucket[serverId];
781
- this.writeJson(configPath, cfg, dryRun);
782
- return { platform: this.id, action: "remove", path: configPath, detail: `${rootKey}.${serverId}` };
1008
+ return changes;
1009
+ }
1010
+ /**
1011
+ * Uninstall: strip owned content from every event bucket in the object-map
1012
+ * `hooks` root at `configPath`. NESTED hosts pass `descriptor.stripInner`
1013
+ * (strip owned inner commands, drop emptied entries; `removed` = inner
1014
+ * commands); FLAT hosts omit it (drop whole owned entries). Reproduces the
1015
+ * canonical host: read (NO `?? {}`), `[skip]` (absentDetail) when no hooks
1016
+ * section, per-event remove → delete-or-replace bucket, write once iff
1017
+ * mutated, and a single `[skip]` (noMatchDetail) when nothing matched.
1018
+ */
1019
+ removeHookEntries(ctx, configPath, descriptor) {
1020
+ const file = this.readJson(configPath);
1021
+ const hooks = file?.hooks;
1022
+ if (!file || !hooks) {
1023
+ return [
1024
+ {
1025
+ platform: this.id,
1026
+ action: "skip",
1027
+ path: configPath,
1028
+ detail: descriptor.absentDetail
1029
+ }
1030
+ ];
1031
+ }
1032
+ const changes = [];
1033
+ let mutated = false;
1034
+ const stripInner = descriptor.stripInner?.(ctx);
1035
+ for (const event of descriptor.removeEventKeys ?? Object.keys(hooks)) {
1036
+ const bucket = hooks[event];
1037
+ if (!Array.isArray(bucket)) continue;
1038
+ const ownsEntry = descriptor.ownsEntryForRemove(ctx, event);
1039
+ const r = removeFromArray(bucket, ownsEntry, stripInner);
1040
+ if (r.removed > 0) {
1041
+ if (r.array.length > 0) hooks[event] = r.array;
1042
+ else delete hooks[event];
1043
+ changes.push({
1044
+ platform: this.id,
1045
+ action: "remove",
1046
+ path: configPath,
1047
+ detail: descriptor.removeDetail(event, r.removed)
1048
+ });
1049
+ mutated = true;
1050
+ }
1051
+ }
1052
+ if (mutated) this.writeJson(configPath, file, ctx.dryRun);
1053
+ if (changes.length === 0) {
1054
+ changes.push({
1055
+ platform: this.id,
1056
+ action: "skip",
1057
+ path: configPath,
1058
+ detail: descriptor.noMatchDetail
1059
+ });
1060
+ }
1061
+ return changes;
1062
+ }
1063
+ /** Bind BaseAdapter's JSON leaf IO (readJson/writeJson + the unparseable
1064
+ * overwrite guard) as a Codec for the object-map engine. */
1065
+ jsonObjectMapCodec() {
1066
+ return {
1067
+ parse: (path) => this.readJson(path),
1068
+ serialize: (path, data, dryRun) => this.writeJson(path, data, dryRun),
1069
+ isPresentButUnparseable: (path) => this.isPresentButUnparseable(path)
1070
+ };
1071
+ }
1072
+ /**
1073
+ * Upsert `entry` at `config[rootKey][serverId]` in a JSON file, creating the
1074
+ * file/object as needed. Returns a ChangeRecord describing create/update/skip.
1075
+ * Idempotent: an identical entry yields "skip".
1076
+ */
1077
+ upsertServerInJson(configPath, rootKey, serverId, entry, dryRun = false) {
1078
+ return upsertInObjectMap({
1079
+ codec: this.jsonObjectMapCodec(),
1080
+ rootKey,
1081
+ platform: this.id,
1082
+ configPath,
1083
+ entryId: serverId,
1084
+ entry,
1085
+ dryRun
1086
+ });
1087
+ }
1088
+ /** Remove `config[rootKey][serverId]` from a JSON file. */
1089
+ removeServerFromJson(configPath, rootKey, serverId, dryRun = false) {
1090
+ return removeFromObjectMap({
1091
+ codec: this.jsonObjectMapCodec(),
1092
+ rootKey,
1093
+ platform: this.id,
1094
+ configPath,
1095
+ entryId: serverId,
1096
+ dryRun
1097
+ });
783
1098
  }
784
1099
  // ── Backup ───────────────────────────────────────────────────────────────
785
1100
  backupSettings(ctx) {
@@ -829,6 +1144,8 @@ var BaseAdapter = class {
829
1144
 
830
1145
  export {
831
1146
  parseJsonc,
1147
+ upsertInObjectMap,
1148
+ removeFromObjectMap,
832
1149
  BaseAdapter
833
1150
  };
834
- //# sourceMappingURL=chunk-CPLK7GTE.js.map
1151
+ //# sourceMappingURL=chunk-KG6T3YNJ.js.map