@spinabot/brigade 1.7.0 → 1.9.0

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 (257) hide show
  1. package/README.md +19 -0
  2. package/dist/agents/agent-loop.d.ts +13 -0
  3. package/dist/agents/agent-loop.d.ts.map +1 -1
  4. package/dist/agents/agent-loop.js +5 -0
  5. package/dist/agents/agent-loop.js.map +1 -1
  6. package/dist/agents/channels/access-control/group-tool-policy.d.ts +73 -0
  7. package/dist/agents/channels/access-control/group-tool-policy.d.ts.map +1 -0
  8. package/dist/agents/channels/access-control/group-tool-policy.js +193 -0
  9. package/dist/agents/channels/access-control/group-tool-policy.js.map +1 -0
  10. package/dist/agents/channels/access-control/index.d.ts +1 -0
  11. package/dist/agents/channels/access-control/index.d.ts.map +1 -1
  12. package/dist/agents/channels/access-control/index.js +1 -0
  13. package/dist/agents/channels/access-control/index.js.map +1 -1
  14. package/dist/agents/channels/bluebubbles/account-config.d.ts +253 -0
  15. package/dist/agents/channels/bluebubbles/account-config.d.ts.map +1 -0
  16. package/dist/agents/channels/bluebubbles/account-config.js +486 -0
  17. package/dist/agents/channels/bluebubbles/account-config.js.map +1 -0
  18. package/dist/agents/channels/bluebubbles/account-registry.d.ts +33 -0
  19. package/dist/agents/channels/bluebubbles/account-registry.d.ts.map +1 -0
  20. package/dist/agents/channels/bluebubbles/account-registry.js +46 -0
  21. package/dist/agents/channels/bluebubbles/account-registry.js.map +1 -0
  22. package/dist/agents/channels/bluebubbles/adapter.d.ts +45 -0
  23. package/dist/agents/channels/bluebubbles/adapter.d.ts.map +1 -0
  24. package/dist/agents/channels/bluebubbles/adapter.js +366 -0
  25. package/dist/agents/channels/bluebubbles/adapter.js.map +1 -0
  26. package/dist/agents/channels/bluebubbles/catchup-cursor.d.ts +52 -0
  27. package/dist/agents/channels/bluebubbles/catchup-cursor.d.ts.map +1 -0
  28. package/dist/agents/channels/bluebubbles/catchup-cursor.js +118 -0
  29. package/dist/agents/channels/bluebubbles/catchup-cursor.js.map +1 -0
  30. package/dist/agents/channels/bluebubbles/catchup.d.ts +114 -0
  31. package/dist/agents/channels/bluebubbles/catchup.d.ts.map +1 -0
  32. package/dist/agents/channels/bluebubbles/catchup.js +220 -0
  33. package/dist/agents/channels/bluebubbles/catchup.js.map +1 -0
  34. package/dist/agents/channels/bluebubbles/chat.d.ts +75 -0
  35. package/dist/agents/channels/bluebubbles/chat.d.ts.map +1 -0
  36. package/dist/agents/channels/bluebubbles/chat.js +182 -0
  37. package/dist/agents/channels/bluebubbles/chat.js.map +1 -0
  38. package/dist/agents/channels/bluebubbles/connection.d.ts +95 -0
  39. package/dist/agents/channels/bluebubbles/connection.d.ts.map +1 -0
  40. package/dist/agents/channels/bluebubbles/connection.js +413 -0
  41. package/dist/agents/channels/bluebubbles/connection.js.map +1 -0
  42. package/dist/agents/channels/bluebubbles/contact-names.d.ts +79 -0
  43. package/dist/agents/channels/bluebubbles/contact-names.d.ts.map +1 -0
  44. package/dist/agents/channels/bluebubbles/contact-names.js +188 -0
  45. package/dist/agents/channels/bluebubbles/contact-names.js.map +1 -0
  46. package/dist/agents/channels/bluebubbles/debounce.d.ts +78 -0
  47. package/dist/agents/channels/bluebubbles/debounce.d.ts.map +1 -0
  48. package/dist/agents/channels/bluebubbles/debounce.js +173 -0
  49. package/dist/agents/channels/bluebubbles/debounce.js.map +1 -0
  50. package/dist/agents/channels/bluebubbles/dedupe.d.ts +25 -0
  51. package/dist/agents/channels/bluebubbles/dedupe.d.ts.map +1 -0
  52. package/dist/agents/channels/bluebubbles/dedupe.js +35 -0
  53. package/dist/agents/channels/bluebubbles/dedupe.js.map +1 -0
  54. package/dist/agents/channels/bluebubbles/effects.d.ts +17 -0
  55. package/dist/agents/channels/bluebubbles/effects.d.ts.map +1 -0
  56. package/dist/agents/channels/bluebubbles/effects.js +57 -0
  57. package/dist/agents/channels/bluebubbles/effects.js.map +1 -0
  58. package/dist/agents/channels/bluebubbles/history.d.ts +56 -0
  59. package/dist/agents/channels/bluebubbles/history.d.ts.map +1 -0
  60. package/dist/agents/channels/bluebubbles/history.js +140 -0
  61. package/dist/agents/channels/bluebubbles/history.js.map +1 -0
  62. package/dist/agents/channels/bluebubbles/index.d.ts +18 -0
  63. package/dist/agents/channels/bluebubbles/index.d.ts.map +1 -0
  64. package/dist/agents/channels/bluebubbles/index.js +18 -0
  65. package/dist/agents/channels/bluebubbles/index.js.map +1 -0
  66. package/dist/agents/channels/bluebubbles/media.d.ts +104 -0
  67. package/dist/agents/channels/bluebubbles/media.d.ts.map +1 -0
  68. package/dist/agents/channels/bluebubbles/media.js +222 -0
  69. package/dist/agents/channels/bluebubbles/media.js.map +1 -0
  70. package/dist/agents/channels/bluebubbles/messaging.d.ts +13 -0
  71. package/dist/agents/channels/bluebubbles/messaging.d.ts.map +1 -0
  72. package/dist/agents/channels/bluebubbles/messaging.js +36 -0
  73. package/dist/agents/channels/bluebubbles/messaging.js.map +1 -0
  74. package/dist/agents/channels/bluebubbles/module.d.ts +24 -0
  75. package/dist/agents/channels/bluebubbles/module.d.ts.map +1 -0
  76. package/dist/agents/channels/bluebubbles/module.js +52 -0
  77. package/dist/agents/channels/bluebubbles/module.js.map +1 -0
  78. package/dist/agents/channels/bluebubbles/normalize.d.ts +111 -0
  79. package/dist/agents/channels/bluebubbles/normalize.d.ts.map +1 -0
  80. package/dist/agents/channels/bluebubbles/normalize.js +239 -0
  81. package/dist/agents/channels/bluebubbles/normalize.js.map +1 -0
  82. package/dist/agents/channels/bluebubbles/plugin.d.ts +46 -0
  83. package/dist/agents/channels/bluebubbles/plugin.d.ts.map +1 -0
  84. package/dist/agents/channels/bluebubbles/plugin.js +242 -0
  85. package/dist/agents/channels/bluebubbles/plugin.js.map +1 -0
  86. package/dist/agents/channels/bluebubbles/probe.d.ts +79 -0
  87. package/dist/agents/channels/bluebubbles/probe.d.ts.map +1 -0
  88. package/dist/agents/channels/bluebubbles/probe.js +138 -0
  89. package/dist/agents/channels/bluebubbles/probe.js.map +1 -0
  90. package/dist/agents/channels/bluebubbles/reactions.d.ts +46 -0
  91. package/dist/agents/channels/bluebubbles/reactions.d.ts.map +1 -0
  92. package/dist/agents/channels/bluebubbles/reactions.js +207 -0
  93. package/dist/agents/channels/bluebubbles/reactions.js.map +1 -0
  94. package/dist/agents/channels/bluebubbles/send.d.ts +132 -0
  95. package/dist/agents/channels/bluebubbles/send.d.ts.map +1 -0
  96. package/dist/agents/channels/bluebubbles/send.js +327 -0
  97. package/dist/agents/channels/bluebubbles/send.js.map +1 -0
  98. package/dist/agents/channels/bluebubbles/status-issues.d.ts +57 -0
  99. package/dist/agents/channels/bluebubbles/status-issues.d.ts.map +1 -0
  100. package/dist/agents/channels/bluebubbles/status-issues.js +93 -0
  101. package/dist/agents/channels/bluebubbles/status-issues.js.map +1 -0
  102. package/dist/agents/channels/bluebubbles/types.d.ts +69 -0
  103. package/dist/agents/channels/bluebubbles/types.d.ts.map +1 -0
  104. package/dist/agents/channels/bluebubbles/types.js +118 -0
  105. package/dist/agents/channels/bluebubbles/types.js.map +1 -0
  106. package/dist/agents/channels/bluebubbles/webhook.d.ts +97 -0
  107. package/dist/agents/channels/bluebubbles/webhook.d.ts.map +1 -0
  108. package/dist/agents/channels/bluebubbles/webhook.js +249 -0
  109. package/dist/agents/channels/bluebubbles/webhook.js.map +1 -0
  110. package/dist/agents/channels/bundled-channel-metas.d.ts +13 -0
  111. package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
  112. package/dist/agents/channels/bundled-channel-metas.js +33 -0
  113. package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
  114. package/dist/agents/channels/imessage/account-config.d.ts +178 -0
  115. package/dist/agents/channels/imessage/account-config.d.ts.map +1 -0
  116. package/dist/agents/channels/imessage/account-config.js +371 -0
  117. package/dist/agents/channels/imessage/account-config.js.map +1 -0
  118. package/dist/agents/channels/imessage/adapter.d.ts +36 -0
  119. package/dist/agents/channels/imessage/adapter.d.ts.map +1 -0
  120. package/dist/agents/channels/imessage/adapter.js +286 -0
  121. package/dist/agents/channels/imessage/adapter.js.map +1 -0
  122. package/dist/agents/channels/imessage/client.d.ts +99 -0
  123. package/dist/agents/channels/imessage/client.d.ts.map +1 -0
  124. package/dist/agents/channels/imessage/client.js +231 -0
  125. package/dist/agents/channels/imessage/client.js.map +1 -0
  126. package/dist/agents/channels/imessage/connection.d.ts +101 -0
  127. package/dist/agents/channels/imessage/connection.d.ts.map +1 -0
  128. package/dist/agents/channels/imessage/connection.js +367 -0
  129. package/dist/agents/channels/imessage/connection.js.map +1 -0
  130. package/dist/agents/channels/imessage/format.d.ts +45 -0
  131. package/dist/agents/channels/imessage/format.d.ts.map +1 -0
  132. package/dist/agents/channels/imessage/format.js +170 -0
  133. package/dist/agents/channels/imessage/format.js.map +1 -0
  134. package/dist/agents/channels/imessage/history.d.ts +49 -0
  135. package/dist/agents/channels/imessage/history.d.ts.map +1 -0
  136. package/dist/agents/channels/imessage/history.js +74 -0
  137. package/dist/agents/channels/imessage/history.js.map +1 -0
  138. package/dist/agents/channels/imessage/index.d.ts +25 -0
  139. package/dist/agents/channels/imessage/index.d.ts.map +1 -0
  140. package/dist/agents/channels/imessage/index.js +25 -0
  141. package/dist/agents/channels/imessage/index.js.map +1 -0
  142. package/dist/agents/channels/imessage/media.d.ts +92 -0
  143. package/dist/agents/channels/imessage/media.d.ts.map +1 -0
  144. package/dist/agents/channels/imessage/media.js +196 -0
  145. package/dist/agents/channels/imessage/media.js.map +1 -0
  146. package/dist/agents/channels/imessage/messaging.d.ts +14 -0
  147. package/dist/agents/channels/imessage/messaging.d.ts.map +1 -0
  148. package/dist/agents/channels/imessage/messaging.js +37 -0
  149. package/dist/agents/channels/imessage/messaging.js.map +1 -0
  150. package/dist/agents/channels/imessage/module.d.ts +16 -0
  151. package/dist/agents/channels/imessage/module.d.ts.map +1 -0
  152. package/dist/agents/channels/imessage/module.js +23 -0
  153. package/dist/agents/channels/imessage/module.js.map +1 -0
  154. package/dist/agents/channels/imessage/monitor.d.ts +207 -0
  155. package/dist/agents/channels/imessage/monitor.d.ts.map +1 -0
  156. package/dist/agents/channels/imessage/monitor.js +504 -0
  157. package/dist/agents/channels/imessage/monitor.js.map +1 -0
  158. package/dist/agents/channels/imessage/plugin.d.ts +53 -0
  159. package/dist/agents/channels/imessage/plugin.d.ts.map +1 -0
  160. package/dist/agents/channels/imessage/plugin.js +215 -0
  161. package/dist/agents/channels/imessage/plugin.js.map +1 -0
  162. package/dist/agents/channels/imessage/probe.d.ts +68 -0
  163. package/dist/agents/channels/imessage/probe.d.ts.map +1 -0
  164. package/dist/agents/channels/imessage/probe.js +134 -0
  165. package/dist/agents/channels/imessage/probe.js.map +1 -0
  166. package/dist/agents/channels/imessage/remote-attachments.d.ts +61 -0
  167. package/dist/agents/channels/imessage/remote-attachments.d.ts.map +1 -0
  168. package/dist/agents/channels/imessage/remote-attachments.js +131 -0
  169. package/dist/agents/channels/imessage/remote-attachments.js.map +1 -0
  170. package/dist/agents/channels/imessage/send.d.ts +61 -0
  171. package/dist/agents/channels/imessage/send.d.ts.map +1 -0
  172. package/dist/agents/channels/imessage/send.js +108 -0
  173. package/dist/agents/channels/imessage/send.js.map +1 -0
  174. package/dist/agents/channels/imessage/targets.d.ts +91 -0
  175. package/dist/agents/channels/imessage/targets.d.ts.map +1 -0
  176. package/dist/agents/channels/imessage/targets.js +245 -0
  177. package/dist/agents/channels/imessage/targets.js.map +1 -0
  178. package/dist/agents/channels/imessage/watch-error.d.ts +23 -0
  179. package/dist/agents/channels/imessage/watch-error.d.ts.map +1 -0
  180. package/dist/agents/channels/imessage/watch-error.js +69 -0
  181. package/dist/agents/channels/imessage/watch-error.js.map +1 -0
  182. package/dist/agents/channels/inbound-pipeline.d.ts +11 -0
  183. package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
  184. package/dist/agents/channels/inbound-pipeline.js +20 -1
  185. package/dist/agents/channels/inbound-pipeline.js.map +1 -1
  186. package/dist/agents/channels/manager.d.ts +9 -0
  187. package/dist/agents/channels/manager.d.ts.map +1 -1
  188. package/dist/agents/channels/manager.js.map +1 -1
  189. package/dist/agents/channels/sdk.d.ts +4 -0
  190. package/dist/agents/channels/sdk.d.ts.map +1 -1
  191. package/dist/agents/channels/sdk.js +4 -0
  192. package/dist/agents/channels/sdk.js.map +1 -1
  193. package/dist/agents/extensions/modules/index.d.ts.map +1 -1
  194. package/dist/agents/extensions/modules/index.js +12 -0
  195. package/dist/agents/extensions/modules/index.js.map +1 -1
  196. package/dist/agents/session-wiring.d.ts +31 -0
  197. package/dist/agents/session-wiring.d.ts.map +1 -1
  198. package/dist/agents/session-wiring.js +45 -2
  199. package/dist/agents/session-wiring.js.map +1 -1
  200. package/dist/agents/tools/bluebubbles-action-tool.d.ts +66 -0
  201. package/dist/agents/tools/bluebubbles-action-tool.d.ts.map +1 -0
  202. package/dist/agents/tools/bluebubbles-action-tool.js +234 -0
  203. package/dist/agents/tools/bluebubbles-action-tool.js.map +1 -0
  204. package/dist/agents/tools/registry.d.ts.map +1 -1
  205. package/dist/agents/tools/registry.js +18 -0
  206. package/dist/agents/tools/registry.js.map +1 -1
  207. package/dist/buildstamp.json +1 -1
  208. package/dist/cli/commands/expose.d.ts +40 -0
  209. package/dist/cli/commands/expose.d.ts.map +1 -0
  210. package/dist/cli/commands/expose.js +200 -0
  211. package/dist/cli/commands/expose.js.map +1 -0
  212. package/dist/cli/program/build-program.d.ts.map +1 -1
  213. package/dist/cli/program/build-program.js +61 -0
  214. package/dist/cli/program/build-program.js.map +1 -1
  215. package/dist/config/io.d.ts +41 -0
  216. package/dist/config/io.d.ts.map +1 -1
  217. package/dist/config/io.js.map +1 -1
  218. package/dist/core/server.d.ts.map +1 -1
  219. package/dist/core/server.js +48 -2
  220. package/dist/core/server.js.map +1 -1
  221. package/dist/core/tunnel/auth-proxy.d.ts +55 -0
  222. package/dist/core/tunnel/auth-proxy.d.ts.map +1 -0
  223. package/dist/core/tunnel/auth-proxy.js +179 -0
  224. package/dist/core/tunnel/auth-proxy.js.map +1 -0
  225. package/dist/core/tunnel/manager.d.ts +42 -0
  226. package/dist/core/tunnel/manager.d.ts.map +1 -0
  227. package/dist/core/tunnel/manager.js +102 -0
  228. package/dist/core/tunnel/manager.js.map +1 -0
  229. package/dist/core/tunnel/providers/bore.d.ts +18 -0
  230. package/dist/core/tunnel/providers/bore.d.ts.map +1 -0
  231. package/dist/core/tunnel/providers/bore.js +117 -0
  232. package/dist/core/tunnel/providers/bore.js.map +1 -0
  233. package/dist/core/tunnel/providers/cloudflared.d.ts +24 -0
  234. package/dist/core/tunnel/providers/cloudflared.d.ts.map +1 -0
  235. package/dist/core/tunnel/providers/cloudflared.js +179 -0
  236. package/dist/core/tunnel/providers/cloudflared.js.map +1 -0
  237. package/dist/core/tunnel/providers/custom.d.ts +21 -0
  238. package/dist/core/tunnel/providers/custom.d.ts.map +1 -0
  239. package/dist/core/tunnel/providers/custom.js +124 -0
  240. package/dist/core/tunnel/providers/custom.js.map +1 -0
  241. package/dist/core/tunnel/registry.d.ts +15 -0
  242. package/dist/core/tunnel/registry.d.ts.map +1 -0
  243. package/dist/core/tunnel/registry.js +26 -0
  244. package/dist/core/tunnel/registry.js.map +1 -0
  245. package/dist/core/tunnel/state.d.ts +39 -0
  246. package/dist/core/tunnel/state.d.ts.map +1 -0
  247. package/dist/core/tunnel/state.js +57 -0
  248. package/dist/core/tunnel/state.js.map +1 -0
  249. package/dist/core/tunnel/types.d.ts +61 -0
  250. package/dist/core/tunnel/types.d.ts.map +1 -0
  251. package/dist/core/tunnel/types.js +20 -0
  252. package/dist/core/tunnel/types.js.map +1 -0
  253. package/dist/infra/net/fetch-guard.d.ts +24 -2
  254. package/dist/infra/net/fetch-guard.d.ts.map +1 -1
  255. package/dist/infra/net/fetch-guard.js +78 -31
  256. package/dist/infra/net/fetch-guard.js.map +1 -1
  257. package/package.json +5 -1
@@ -0,0 +1,215 @@
1
+ /**
2
+ * iMessage `ChannelPlugin` — the multi-ACCOUNT contract surface.
3
+ *
4
+ * Mirrors `discord/plugin.ts`: wraps `createIMessageAdapter()` (the
5
+ * per-connection implementation) with the lifecycle adapters the
6
+ * `ChannelPluginManager` consumes, so an operator can run MORE THAN ONE iMessage
7
+ * bridge at once (e.g. two Macs / two chat.db files) via:
8
+ *
9
+ * channels.imessage = {
10
+ * enabled: true,
11
+ * accounts: [
12
+ * { id: "personal", dbPath: "~/Library/Messages/chat.db" },
13
+ * { id: "work", cliPath: "/opt/imsg/bin/imsg" },
14
+ * ],
15
+ * }
16
+ *
17
+ * Per-account state lives in a `Map<accountId, AccountRuntime>` in this closure —
18
+ * one `imsg rpc` subprocess per account. Inbound dispatch reuses the shared
19
+ * `runChannelInboundPipeline` so the multi-account path carries the identical
20
+ * ACL + debounce + abort surface as the legacy single-adapter manager.
21
+ *
22
+ * The legacy single-account `createIMessageAdapter` STEPS ASIDE when >1 account
23
+ * is configured (its `isConfigured` returns false for the default account), so
24
+ * the two paths never double-start a subprocess. iMessage has no inbound HTTP
25
+ * route (the `imsg rpc` notification stream is the only inbound transport).
26
+ */
27
+ // Channel SDK barrel — the single import surface for the multi-account
28
+ // `ChannelPlugin` contract + the shared inbound pipeline + the gateway boot args.
29
+ import { buildBundledCommands, createInboundPipelineContext, createSubsystemLogger, runChannelInboundPipeline, IMESSAGE_CHANNEL_META, } from "../sdk.js";
30
+ import { listIMessageAccountIds, resolveIMessageAccount, resolveIMessageCliPath, resolveIMessageDbPath, resolveIMessageDefaultTo, resolveIMessageProbeTimeoutMs, IMESSAGE_CHANNEL_ID, IMESSAGE_DEFAULT_ACCOUNT_ID, } from "./account-config.js";
31
+ import { createIMessageAdapter, IMESSAGE_CAPABILITIES } from "./adapter.js";
32
+ import { imessageMessagingAdapter } from "./messaging.js";
33
+ import { probeIMessage } from "./probe.js";
34
+ const log = createSubsystemLogger("channels/imessage/plugin");
35
+ const IMESSAGE_META = IMESSAGE_CHANNEL_META;
36
+ /** Construct the plugin instance, capturing per-account runtime state in closure. */
37
+ export function createIMessagePlugin(deps) {
38
+ const accountRuntimes = new Map();
39
+ const startAccount = async (ctx) => {
40
+ const accountId = ctx.accountId || IMESSAGE_DEFAULT_ACCOUNT_ID;
41
+ const existing = accountRuntimes.get(accountId);
42
+ if (existing) {
43
+ try {
44
+ await existing.adapter.stop();
45
+ }
46
+ catch {
47
+ /* best-effort */
48
+ }
49
+ try {
50
+ existing.abort.abort("restart");
51
+ }
52
+ catch {
53
+ /* best-effort */
54
+ }
55
+ accountRuntimes.delete(accountId);
56
+ }
57
+ const cfg = deps.loadConfig();
58
+ const factory = deps.adapterFactory ?? defaultIMessageAdapterFactory;
59
+ const adapter = factory({ accountId });
60
+ const accountAbort = new AbortController();
61
+ const parent = ctx.signal;
62
+ if (parent) {
63
+ if (parent.aborted)
64
+ accountAbort.abort();
65
+ else
66
+ parent.addEventListener("abort", () => accountAbort.abort(), { once: true });
67
+ }
68
+ const pipelineRunTurn = (turn) => deps.runTurn(turn);
69
+ const commandMap = new Map();
70
+ for (const c of buildBundledCommands(adapter))
71
+ commandMap.set(c.name.toLowerCase(), c);
72
+ const pipeline = createInboundPipelineContext({
73
+ adapter,
74
+ config: cfg,
75
+ agentId: deps.defaultAgentId,
76
+ runTurn: pipelineRunTurn,
77
+ commandMap,
78
+ parentAbort: accountAbort.signal,
79
+ });
80
+ const startCtx = {
81
+ signal: accountAbort.signal,
82
+ log: (msg, meta) => log.info(`[${accountId}] ${msg}`, meta),
83
+ onInbound: async (msg) => {
84
+ pipeline.config = deps.loadConfig();
85
+ const stamped = msg.accountId ? msg : { ...msg, accountId };
86
+ await runChannelInboundPipeline(pipeline, stamped);
87
+ },
88
+ };
89
+ try {
90
+ await adapter.start(startCtx);
91
+ accountRuntimes.set(accountId, { adapter, pipeline, abort: accountAbort });
92
+ log.info("imessage account started", { accountId });
93
+ }
94
+ catch (err) {
95
+ log.warn("imessage account failed to start", {
96
+ accountId,
97
+ error: err instanceof Error ? err.message : String(err),
98
+ });
99
+ throw err;
100
+ }
101
+ };
102
+ const stopAccount = async (ctx) => {
103
+ const runtime = accountRuntimes.get(ctx.accountId);
104
+ if (!runtime)
105
+ return;
106
+ accountRuntimes.delete(ctx.accountId);
107
+ try {
108
+ runtime.abort.abort("stop-requested");
109
+ }
110
+ catch {
111
+ /* best-effort */
112
+ }
113
+ for (const slot of runtime.pipeline.pendingDispatches.values())
114
+ clearTimeout(slot.timer);
115
+ runtime.pipeline.pendingDispatches.clear();
116
+ try {
117
+ await runtime.adapter.stop();
118
+ }
119
+ catch (err) {
120
+ log.warn("imessage account stop threw", {
121
+ accountId: ctx.accountId,
122
+ error: err instanceof Error ? err.message : String(err),
123
+ });
124
+ }
125
+ };
126
+ const logoutAccount = async (ctx) => {
127
+ try {
128
+ await stopAccount(ctx);
129
+ return { ok: true };
130
+ }
131
+ catch (err) {
132
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
133
+ }
134
+ };
135
+ return {
136
+ id: IMESSAGE_CHANNEL_ID,
137
+ meta: IMESSAGE_META,
138
+ capabilities: IMESSAGE_CAPABILITIES,
139
+ startedAccountIds: () => [...accountRuntimes.keys()],
140
+ getAdapter: (accountId) => accountRuntimes.get(accountId)?.adapter,
141
+ probeAccount: async (accountId, cfg) => {
142
+ return probeIMessage({
143
+ cliPath: resolveIMessageCliPath(cfg, accountId),
144
+ ...(resolveIMessageDbPath(cfg, accountId) ? { dbPath: resolveIMessageDbPath(cfg, accountId) } : {}),
145
+ timeoutMs: resolveIMessageProbeTimeoutMs(cfg, accountId),
146
+ });
147
+ },
148
+ config: {
149
+ listAccountIds: (cfg) => listIMessageAccountIds(cfg),
150
+ resolveAccount: (cfg, accountId) => resolveIMessageAccount(cfg, accountId ?? undefined),
151
+ defaultAccountId: () => IMESSAGE_DEFAULT_ACCOUNT_ID,
152
+ isEnabled: (account) => account.enabled,
153
+ },
154
+ gateway: {
155
+ startAccount,
156
+ stopAccount,
157
+ logoutAccount,
158
+ },
159
+ outbound: {
160
+ sendText: async (params) => {
161
+ const accountId = params.target.accountId || IMESSAGE_DEFAULT_ACCOUNT_ID;
162
+ const runtime = accountRuntimes.get(accountId);
163
+ if (!runtime)
164
+ return { ok: false, error: `imessage account "${accountId}" is not running` };
165
+ // Fall back to the configured default recipient when no target was given.
166
+ const to = params.target.to?.trim() || resolveIMessageDefaultTo(deps.loadConfig(), accountId);
167
+ if (!to)
168
+ return { ok: false, error: `imessage send needs a recipient (no target and no defaultTo set)` };
169
+ try {
170
+ const sent = await runtime.adapter.sendText(to, params.text, { accountId });
171
+ return {
172
+ ok: true,
173
+ ...(sent && typeof sent === "object" && sent.messageId !== undefined ? { messageId: sent.messageId } : {}),
174
+ };
175
+ }
176
+ catch (err) {
177
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
178
+ }
179
+ },
180
+ sendMedia: async (params) => {
181
+ const accountId = params.target.accountId || IMESSAGE_DEFAULT_ACCOUNT_ID;
182
+ const runtime = accountRuntimes.get(accountId);
183
+ if (!runtime || !runtime.adapter.sendMedia) {
184
+ return { ok: false, error: `imessage account "${accountId}" cannot send media right now` };
185
+ }
186
+ const to = params.target.to?.trim() || resolveIMessageDefaultTo(deps.loadConfig(), accountId);
187
+ if (!to)
188
+ return { ok: false, error: `imessage send needs a recipient (no target and no defaultTo set)` };
189
+ try {
190
+ await runtime.adapter.sendMedia(to, {
191
+ kind: params.mediaType ?? "document",
192
+ path: params.mediaUrl,
193
+ ...(params.caption !== undefined ? { caption: params.caption } : {}),
194
+ });
195
+ return { ok: true };
196
+ }
197
+ catch (err) {
198
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
199
+ }
200
+ },
201
+ },
202
+ // Outbound addressing: parse/normalize a loose `to` into a concrete target.
203
+ messaging: imessageMessagingAdapter,
204
+ secrets: {
205
+ // iMessage has no token secret; the cliPath isn't a secret-ref, so no
206
+ // secret-target entries. Declared empty for surface parity.
207
+ secretTargetRegistryEntries: [],
208
+ },
209
+ };
210
+ }
211
+ /** Default adapter factory — threads the per-account scope. */
212
+ function defaultIMessageAdapterFactory(args) {
213
+ return createIMessageAdapter({ accountId: args.accountId });
214
+ }
215
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../../src/agents/channels/imessage/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * iMessage status / doctor probe.
3
+ *
4
+ * iMessage has no remote API to ping — the only way to answer "is this channel
5
+ * actually working?" is to drive the local `imsg` binary. The probe is a
6
+ * three-stage gate:
7
+ * 1. the binary exists / is runnable (`imsg rpc --help` succeeds);
8
+ * 2. the binary supports the `rpc` subcommand (an old build prints
9
+ * "unknown command 'rpc'" → a FATAL, non-retriable error);
10
+ * 3. a live `chats.list {limit:1}` round-trip over the RPC stream succeeds.
11
+ *
12
+ * The RPC client factory is INJECTABLE (`opts.createClient`) — that is the test
13
+ * seam: a unit test passes a fake to exercise the success / failure branches
14
+ * with NO real `imsg` binary. Likewise the `runHelp` probe of `imsg rpc --help`
15
+ * is injectable. Never throws — a failure surfaces as `{ ok: false, error }` so
16
+ * `brigade channels status` / `brigade doctor` degrade gracefully.
17
+ */
18
+ import { type IMessageRpcLike } from "./client.js";
19
+ /** Structured probe result. `ok` true ⇒ binary present + rpc works + reachable. */
20
+ export interface IMessageProbeResult {
21
+ ok: boolean;
22
+ /** Operator-facing error line when `ok` is false. */
23
+ error?: string;
24
+ /** True when the failure is non-recoverable without operator action (old binary). */
25
+ fatal?: boolean;
26
+ /** Round-trip time in ms. */
27
+ elapsedMs: number;
28
+ }
29
+ /** Outcome of the `imsg rpc --help` support probe. */
30
+ interface RpcSupportResult {
31
+ supported: boolean;
32
+ error?: string;
33
+ fatal?: boolean;
34
+ }
35
+ export interface IMessageProbeArgs {
36
+ /** `imsg` binary path. Defaults to `"imsg"`. */
37
+ cliPath?: string;
38
+ /** Optional chat.db override. */
39
+ dbPath?: string;
40
+ /** Probe timeout in ms (default 10s). */
41
+ timeoutMs?: number;
42
+ /**
43
+ * TEST SEAM: run `imsg rpc --help` and return its combined output + exit code.
44
+ * Production spawns the binary; tests inject a fake.
45
+ */
46
+ runHelp?: (cliPath: string, timeoutMs: number) => Promise<{
47
+ stdout: string;
48
+ stderr: string;
49
+ code: number | null;
50
+ }>;
51
+ /**
52
+ * TEST SEAM: build the RPC client used for the live `chats.list` call.
53
+ * Production constructs the real one; tests inject a fake.
54
+ */
55
+ createClient?: (args: {
56
+ cliPath: string;
57
+ dbPath?: string;
58
+ }) => Promise<IMessageRpcLike>;
59
+ }
60
+ /** Probe whether the binary supports the `rpc` subcommand. */
61
+ export declare function probeRpcSupport(args: IMessageProbeArgs): Promise<RpcSupportResult>;
62
+ /**
63
+ * Run the full iMessage probe (binary + rpc support + live `chats.list`).
64
+ * Never throws.
65
+ */
66
+ export declare function probeIMessage(args?: IMessageProbeArgs): Promise<IMessageProbeResult>;
67
+ export {};
68
+ //# sourceMappingURL=probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/imessage/probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE5E,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,sDAAsD;AACtD,UAAU,gBAAgB;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IACjC,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACnH;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;CACxF;AA8CD,8DAA8D;AAC9D,wBAAsB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwBxF;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgC9F"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * iMessage status / doctor probe.
3
+ *
4
+ * iMessage has no remote API to ping — the only way to answer "is this channel
5
+ * actually working?" is to drive the local `imsg` binary. The probe is a
6
+ * three-stage gate:
7
+ * 1. the binary exists / is runnable (`imsg rpc --help` succeeds);
8
+ * 2. the binary supports the `rpc` subcommand (an old build prints
9
+ * "unknown command 'rpc'" → a FATAL, non-retriable error);
10
+ * 3. a live `chats.list {limit:1}` round-trip over the RPC stream succeeds.
11
+ *
12
+ * The RPC client factory is INJECTABLE (`opts.createClient`) — that is the test
13
+ * seam: a unit test passes a fake to exercise the success / failure branches
14
+ * with NO real `imsg` binary. Likewise the `runHelp` probe of `imsg rpc --help`
15
+ * is injectable. Never throws — a failure surfaces as `{ ok: false, error }` so
16
+ * `brigade channels status` / `brigade doctor` degrade gracefully.
17
+ */
18
+ import { spawn } from "node:child_process";
19
+ import { DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS } from "./account-config.js";
20
+ import { createIMessageRpcClient } from "./client.js";
21
+ /** Run `imsg rpc --help` with a timeout, capturing combined output + exit code. */
22
+ function defaultRunHelp(cliPath, timeoutMs) {
23
+ return new Promise((resolve) => {
24
+ let stdout = "";
25
+ let stderr = "";
26
+ let settled = false;
27
+ const done = (code) => {
28
+ if (settled)
29
+ return;
30
+ settled = true;
31
+ resolve({ stdout, stderr, code });
32
+ };
33
+ let child;
34
+ try {
35
+ child = spawn(cliPath, ["rpc", "--help"], { stdio: ["ignore", "pipe", "pipe"] });
36
+ }
37
+ catch (err) {
38
+ resolve({ stdout: "", stderr: err instanceof Error ? err.message : String(err), code: 1 });
39
+ return;
40
+ }
41
+ const timer = setTimeout(() => {
42
+ try {
43
+ child.kill("SIGTERM");
44
+ }
45
+ catch {
46
+ /* best-effort */
47
+ }
48
+ done(null);
49
+ }, timeoutMs);
50
+ if (typeof timer.unref === "function")
51
+ timer.unref();
52
+ child.stdout?.on("data", (c) => {
53
+ stdout += c.toString("utf8");
54
+ });
55
+ child.stderr?.on("data", (c) => {
56
+ stderr += c.toString("utf8");
57
+ });
58
+ child.on("error", (err) => {
59
+ stderr += err instanceof Error ? err.message : String(err);
60
+ done(1);
61
+ });
62
+ child.on("close", (code) => {
63
+ clearTimeout(timer);
64
+ done(code);
65
+ });
66
+ });
67
+ }
68
+ /** Probe whether the binary supports the `rpc` subcommand. */
69
+ export async function probeRpcSupport(args) {
70
+ const cliPath = args.cliPath?.trim() || "imsg";
71
+ const timeoutMs = args.timeoutMs ?? DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS;
72
+ const runHelp = args.runHelp ?? defaultRunHelp;
73
+ let result;
74
+ try {
75
+ result = await runHelp(cliPath, timeoutMs);
76
+ }
77
+ catch (err) {
78
+ return { supported: false, error: err instanceof Error ? err.message : String(err) };
79
+ }
80
+ const combined = `${result.stdout}\n${result.stderr}`.trim();
81
+ const normalized = combined.toLowerCase();
82
+ if (normalized.includes("unknown command") && normalized.includes("rpc")) {
83
+ return {
84
+ supported: false,
85
+ fatal: true,
86
+ error: 'imsg CLI does not support the "rpc" subcommand (update imsg)',
87
+ };
88
+ }
89
+ if (result.code === 0)
90
+ return { supported: true };
91
+ return {
92
+ supported: false,
93
+ error: combined || `imsg rpc --help failed (code ${result.code ?? "unknown"})`,
94
+ };
95
+ }
96
+ /**
97
+ * Run the full iMessage probe (binary + rpc support + live `chats.list`).
98
+ * Never throws.
99
+ */
100
+ export async function probeIMessage(args = {}) {
101
+ const started = Date.now();
102
+ const cliPath = args.cliPath?.trim() || "imsg";
103
+ const dbPath = args.dbPath?.trim() || undefined;
104
+ const timeoutMs = args.timeoutMs ?? DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS;
105
+ const support = await probeRpcSupport({ cliPath, timeoutMs, ...(args.runHelp ? { runHelp: args.runHelp } : {}) });
106
+ if (!support.supported) {
107
+ return {
108
+ ok: false,
109
+ error: support.error ?? "imsg rpc unavailable",
110
+ elapsedMs: Date.now() - started,
111
+ ...(support.fatal ? { fatal: true } : {}),
112
+ };
113
+ }
114
+ let client;
115
+ try {
116
+ client = args.createClient
117
+ ? await args.createClient({ cliPath, ...(dbPath ? { dbPath } : {}) })
118
+ : await createIMessageRpcClient({ cliPath, ...(dbPath ? { dbPath } : {}) });
119
+ }
120
+ catch (err) {
121
+ return { ok: false, error: err instanceof Error ? err.message : String(err), elapsedMs: Date.now() - started };
122
+ }
123
+ try {
124
+ await client.request("chats.list", { limit: 1 }, { timeoutMs });
125
+ return { ok: true, elapsedMs: Date.now() - started };
126
+ }
127
+ catch (err) {
128
+ return { ok: false, error: err instanceof Error ? err.message : String(err), elapsedMs: Date.now() - started };
129
+ }
130
+ finally {
131
+ await client.stop();
132
+ }
133
+ }
134
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../../src/agents/channels/imessage/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * iMessage remote-host attachment fetch.
3
+ *
4
+ * The `imsg` bridge can run on a DIFFERENT Mac than the gateway — operators wrap
5
+ * `cliPath` in an SSH script like `exec ssh -T user@mac-mini imsg "$@"`. In that
6
+ * setup an inbound attachment's `original_path` is a path on the REMOTE machine,
7
+ * so resolving it against the local filesystem fails. This module:
8
+ *
9
+ * 1. `normalizeScpRemoteHost` — safety-validate a `user@host` / `host` token so
10
+ * it can NEVER inject an option/argument into the `scp` command line.
11
+ * 2. `detectRemoteHostFromCliPath` — parse an SSH-wrapper script at `cliPath`
12
+ * to auto-detect the remote host (so `remoteHost` need not be set by hand).
13
+ * 3. `scpCopyRemoteAttachment` — copy a remote attachment to a local temp file
14
+ * (validated against the configured remote roots) before it is resolved.
15
+ *
16
+ * `scp`/`readFile`/`spawn` are all INJECTABLE so the unit tests exercise the
17
+ * remote path with NO real ssh/scp and NO real filesystem. Ported from the
18
+ * upstream iMessage monitor-provider remote-host support + `scp-host.ts`.
19
+ */
20
+ /**
21
+ * Safety-validate + normalise an SCP/SSH remote host. Returns the canonical
22
+ * `user@host` / `host` string, or `undefined` when the value is unsafe (contains
23
+ * whitespace/control chars, looks like an option `-…`, embeds a path separator,
24
+ * has a malformed `user@host`, etc.). Ported from `normalizeScpRemoteHost`.
25
+ */
26
+ export declare function normalizeScpRemoteHost(value: string | null | undefined): string | undefined;
27
+ /** TEST SEAM: read the wrapper-script file (defaults to fs `readFile`). */
28
+ export type ReadFileLike = (filePath: string) => Promise<string>;
29
+ /**
30
+ * Try to detect a remote host from an SSH-wrapper script at `cliPath`, e.g.:
31
+ * exec ssh -T user@192.168.64.3 /opt/homebrew/bin/imsg "$@"
32
+ * exec ssh -T mac-mini imsg "$@"
33
+ * Returns the `user@host` / `host` portion (unvalidated — the caller normalises)
34
+ * or undefined when the file is unreadable / has no ssh invocation. Never throws.
35
+ */
36
+ export declare function detectRemoteHostFromCliPath(cliPath: string, readFileImpl?: ReadFileLike): Promise<string | undefined>;
37
+ /** TEST SEAM: run `scp <remote> <local>` (defaults to spawning real `scp`). */
38
+ export type ScpRunner = (args: {
39
+ remoteHost: string;
40
+ remotePath: string;
41
+ localPath: string;
42
+ }) => Promise<void>;
43
+ /** TEST SEAM: make a temp dir (defaults to fs `mkdtemp` under the OS temp dir). */
44
+ export type MkdtempLike = (prefix: string) => Promise<string>;
45
+ /** Options for {@link scpCopyRemoteAttachment}. */
46
+ export interface ScpCopyArgs {
47
+ /** Validated remote host (`user@host` / `host`). */
48
+ remoteHost: string;
49
+ /** Remote attachment path (already checked against the allowed remote roots). */
50
+ remotePath: string;
51
+ /** TEST SEAM: scp runner. */
52
+ scpRunner?: ScpRunner;
53
+ /** TEST SEAM: temp-dir factory. */
54
+ mkdtempImpl?: MkdtempLike;
55
+ }
56
+ /**
57
+ * Copy a remote attachment to a local temp file and return the local path.
58
+ * Throws on scp failure (the caller treats it as "attachment unavailable").
59
+ */
60
+ export declare function scpCopyRemoteAttachment(args: ScpCopyArgs): Promise<string>;
61
+ //# sourceMappingURL=remote-attachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-attachments.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/imessage/remote-attachments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAqBH;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAwB3F;AAID,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAUjE;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAChD,OAAO,EAAE,MAAM,EACf,YAAY,GAAE,YAA8B,GAC1C,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAY7B;AAID,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAqB/G,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAI9D,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC3B,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,mCAAmC;IACnC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAOhF"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * iMessage remote-host attachment fetch.
3
+ *
4
+ * The `imsg` bridge can run on a DIFFERENT Mac than the gateway — operators wrap
5
+ * `cliPath` in an SSH script like `exec ssh -T user@mac-mini imsg "$@"`. In that
6
+ * setup an inbound attachment's `original_path` is a path on the REMOTE machine,
7
+ * so resolving it against the local filesystem fails. This module:
8
+ *
9
+ * 1. `normalizeScpRemoteHost` — safety-validate a `user@host` / `host` token so
10
+ * it can NEVER inject an option/argument into the `scp` command line.
11
+ * 2. `detectRemoteHostFromCliPath` — parse an SSH-wrapper script at `cliPath`
12
+ * to auto-detect the remote host (so `remoteHost` need not be set by hand).
13
+ * 3. `scpCopyRemoteAttachment` — copy a remote attachment to a local temp file
14
+ * (validated against the configured remote roots) before it is resolved.
15
+ *
16
+ * `scp`/`readFile`/`spawn` are all INJECTABLE so the unit tests exercise the
17
+ * remote path with NO real ssh/scp and NO real filesystem. Ported from the
18
+ * upstream iMessage monitor-provider remote-host support + `scp-host.ts`.
19
+ */
20
+ import { spawn } from "node:child_process";
21
+ import { mkdtemp, readFile } from "node:fs/promises";
22
+ import os from "node:os";
23
+ import path from "node:path";
24
+ /* ───────────────────────── host safety ───────────────────────── */
25
+ const SSH_TOKEN = /^[A-Za-z0-9._-]+$/;
26
+ const BRACKETED_IPV6 = /^\[[0-9A-Fa-f:.%]+\]$/;
27
+ const WHITESPACE = /\s/;
28
+ function hasControlOrWhitespace(value) {
29
+ for (const char of value) {
30
+ const code = char.charCodeAt(0);
31
+ if (code <= 0x1f || code === 0x7f || WHITESPACE.test(char))
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ /**
37
+ * Safety-validate + normalise an SCP/SSH remote host. Returns the canonical
38
+ * `user@host` / `host` string, or `undefined` when the value is unsafe (contains
39
+ * whitespace/control chars, looks like an option `-…`, embeds a path separator,
40
+ * has a malformed `user@host`, etc.). Ported from `normalizeScpRemoteHost`.
41
+ */
42
+ export function normalizeScpRemoteHost(value) {
43
+ const trimmed = (value ?? "").trim();
44
+ if (!trimmed)
45
+ return undefined;
46
+ if (hasControlOrWhitespace(trimmed))
47
+ return undefined;
48
+ if (trimmed.startsWith("-") || trimmed.includes("/") || trimmed.includes("\\"))
49
+ return undefined;
50
+ const firstAt = trimmed.indexOf("@");
51
+ const lastAt = trimmed.lastIndexOf("@");
52
+ let user;
53
+ let host = trimmed;
54
+ if (firstAt !== -1) {
55
+ if (firstAt !== lastAt || firstAt === 0 || firstAt === trimmed.length - 1)
56
+ return undefined;
57
+ user = trimmed.slice(0, firstAt);
58
+ host = trimmed.slice(firstAt + 1);
59
+ if (!SSH_TOKEN.test(user))
60
+ return undefined;
61
+ }
62
+ if (!host || host.startsWith("-") || host.includes("@"))
63
+ return undefined;
64
+ if (host.includes(":") && !BRACKETED_IPV6.test(host))
65
+ return undefined;
66
+ if (!SSH_TOKEN.test(host) && !BRACKETED_IPV6.test(host))
67
+ return undefined;
68
+ return user ? `${user}@${host}` : host;
69
+ }
70
+ const defaultReadFile = (filePath) => readFile(filePath, "utf8");
71
+ /** Expand a leading `~` to the user's home dir. */
72
+ function expandHome(p) {
73
+ if (p.startsWith("~"))
74
+ return p.replace(/^~/, os.homedir());
75
+ return p;
76
+ }
77
+ /**
78
+ * Try to detect a remote host from an SSH-wrapper script at `cliPath`, e.g.:
79
+ * exec ssh -T user@192.168.64.3 /opt/homebrew/bin/imsg "$@"
80
+ * exec ssh -T mac-mini imsg "$@"
81
+ * Returns the `user@host` / `host` portion (unvalidated — the caller normalises)
82
+ * or undefined when the file is unreadable / has no ssh invocation. Never throws.
83
+ */
84
+ export async function detectRemoteHostFromCliPath(cliPath, readFileImpl = defaultReadFile) {
85
+ try {
86
+ const content = await readFileImpl(expandHome(cliPath));
87
+ // `user@host` form first (e.g. brigade@192.168.64.3).
88
+ const userHostMatch = content.match(/\bssh\b[^\n]*?\s+([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+)/);
89
+ if (userHostMatch)
90
+ return userHostMatch[1];
91
+ // Fallback: host-only token immediately before the imsg command.
92
+ const hostOnlyMatch = content.match(/\bssh\b[^\n]*?\s+([a-zA-Z][a-zA-Z0-9._-]*)\s+\S*\bimsg\b/);
93
+ return hostOnlyMatch?.[1];
94
+ }
95
+ catch {
96
+ return undefined;
97
+ }
98
+ }
99
+ /** Spawn a real `scp` and resolve on exit 0, reject otherwise. */
100
+ const defaultScpRunner = ({ remoteHost, remotePath, localPath }) => new Promise((resolve, reject) => {
101
+ // `-T` disables pseudo-terminal allocation; `--` ends option parsing so a
102
+ // crafted path can't be read as a flag. The host is already host-validated.
103
+ const child = spawn("scp", ["-T", "--", `${remoteHost}:${remotePath}`, localPath], {
104
+ stdio: ["ignore", "ignore", "pipe"],
105
+ });
106
+ let stderr = "";
107
+ child.stderr?.on("data", (c) => {
108
+ stderr += c.toString("utf8");
109
+ });
110
+ child.on("error", (err) => reject(err instanceof Error ? err : new Error(String(err))));
111
+ child.on("close", (code) => {
112
+ if (code === 0)
113
+ resolve();
114
+ else
115
+ reject(new Error(`scp exited (code ${code ?? "unknown"})${stderr ? `: ${stderr.trim()}` : ""}`));
116
+ });
117
+ });
118
+ const defaultMkdtemp = (prefix) => mkdtemp(prefix);
119
+ /**
120
+ * Copy a remote attachment to a local temp file and return the local path.
121
+ * Throws on scp failure (the caller treats it as "attachment unavailable").
122
+ */
123
+ export async function scpCopyRemoteAttachment(args) {
124
+ const runner = args.scpRunner ?? defaultScpRunner;
125
+ const makeTemp = args.mkdtempImpl ?? defaultMkdtemp;
126
+ const dir = await makeTemp(path.join(os.tmpdir(), "brigade-imsg-"));
127
+ const localPath = path.join(dir, path.basename(args.remotePath) || "attachment");
128
+ await runner({ remoteHost: args.remoteHost, remotePath: args.remotePath, localPath });
129
+ return localPath;
130
+ }
131
+ //# sourceMappingURL=remote-attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-attachments.js","sourceRoot":"","sources":["../../../../src/agents/channels/imessage/remote-attachments.ts"],"names":[],"mappings":"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"}