@sentropic/h2a-runtime 0.81.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 (253) hide show
  1. package/dist/account-pool.d.ts +168 -0
  2. package/dist/account-pool.d.ts.map +1 -0
  3. package/dist/account-pool.js +385 -0
  4. package/dist/account-pool.js.map +1 -0
  5. package/dist/agents-projection.d.ts +56 -0
  6. package/dist/agents-projection.d.ts.map +1 -0
  7. package/dist/agents-projection.js +82 -0
  8. package/dist/agents-projection.js.map +1 -0
  9. package/dist/attach.d.ts +75 -0
  10. package/dist/attach.d.ts.map +1 -0
  11. package/dist/attach.js +432 -0
  12. package/dist/attach.js.map +1 -0
  13. package/dist/auth-bundle.d.ts +21 -0
  14. package/dist/auth-bundle.d.ts.map +1 -0
  15. package/dist/auth-bundle.js +112 -0
  16. package/dist/auth-bundle.js.map +1 -0
  17. package/dist/auth-diagnostics.d.ts +18 -0
  18. package/dist/auth-diagnostics.d.ts.map +1 -0
  19. package/dist/auth-diagnostics.js +23 -0
  20. package/dist/auth-diagnostics.js.map +1 -0
  21. package/dist/auth-login.d.ts +9 -0
  22. package/dist/auth-login.d.ts.map +1 -0
  23. package/dist/auth-login.js +21 -0
  24. package/dist/auth-login.js.map +1 -0
  25. package/dist/auth-refresh.d.ts +32 -0
  26. package/dist/auth-refresh.d.ts.map +1 -0
  27. package/dist/auth-refresh.js +83 -0
  28. package/dist/auth-refresh.js.map +1 -0
  29. package/dist/auth-tools.d.ts +55 -0
  30. package/dist/auth-tools.d.ts.map +1 -0
  31. package/dist/auth-tools.js +163 -0
  32. package/dist/auth-tools.js.map +1 -0
  33. package/dist/browser-bridge-local.d.ts +27 -0
  34. package/dist/browser-bridge-local.d.ts.map +1 -0
  35. package/dist/browser-bridge-local.js +51 -0
  36. package/dist/browser-bridge-local.js.map +1 -0
  37. package/dist/browser.d.ts +53 -0
  38. package/dist/browser.d.ts.map +1 -0
  39. package/dist/browser.js +61 -0
  40. package/dist/browser.js.map +1 -0
  41. package/dist/conductor-launch.d.ts +179 -0
  42. package/dist/conductor-launch.d.ts.map +1 -0
  43. package/dist/conductor-launch.js +414 -0
  44. package/dist/conductor-launch.js.map +1 -0
  45. package/dist/config.d.ts +182 -0
  46. package/dist/config.d.ts.map +1 -0
  47. package/dist/config.js +334 -0
  48. package/dist/config.js.map +1 -0
  49. package/dist/conv-guard.d.ts +76 -0
  50. package/dist/conv-guard.d.ts.map +1 -0
  51. package/dist/conv-guard.js +131 -0
  52. package/dist/conv-guard.js.map +1 -0
  53. package/dist/convsync.d.ts +24 -0
  54. package/dist/convsync.d.ts.map +1 -0
  55. package/dist/convsync.js +108 -0
  56. package/dist/convsync.js.map +1 -0
  57. package/dist/cred-health.d.ts +96 -0
  58. package/dist/cred-health.d.ts.map +1 -0
  59. package/dist/cred-health.js +170 -0
  60. package/dist/cred-health.js.map +1 -0
  61. package/dist/delegate.d.ts +406 -0
  62. package/dist/delegate.d.ts.map +1 -0
  63. package/dist/delegate.js +739 -0
  64. package/dist/delegate.js.map +1 -0
  65. package/dist/enroll.d.ts +91 -0
  66. package/dist/enroll.d.ts.map +1 -0
  67. package/dist/enroll.js +248 -0
  68. package/dist/enroll.js.map +1 -0
  69. package/dist/fanout.d.ts +63 -0
  70. package/dist/fanout.d.ts.map +1 -0
  71. package/dist/fanout.js +115 -0
  72. package/dist/fanout.js.map +1 -0
  73. package/dist/forward.d.ts +66 -0
  74. package/dist/forward.d.ts.map +1 -0
  75. package/dist/forward.js +141 -0
  76. package/dist/forward.js.map +1 -0
  77. package/dist/gitdiff.d.ts +31 -0
  78. package/dist/gitdiff.d.ts.map +1 -0
  79. package/dist/gitdiff.js +133 -0
  80. package/dist/gitdiff.js.map +1 -0
  81. package/dist/h2a-bridge.d.ts +102 -0
  82. package/dist/h2a-bridge.d.ts.map +1 -0
  83. package/dist/h2a-bridge.js +333 -0
  84. package/dist/h2a-bridge.js.map +1 -0
  85. package/dist/h2a-jobs.d.ts +235 -0
  86. package/dist/h2a-jobs.d.ts.map +1 -0
  87. package/dist/h2a-jobs.js +388 -0
  88. package/dist/h2a-jobs.js.map +1 -0
  89. package/dist/h2a-ping.d.ts +38 -0
  90. package/dist/h2a-ping.d.ts.map +1 -0
  91. package/dist/h2a-ping.js +58 -0
  92. package/dist/h2a-ping.js.map +1 -0
  93. package/dist/index.d.ts +98 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +5887 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/interactive-throttle.d.ts +164 -0
  98. package/dist/interactive-throttle.d.ts.map +1 -0
  99. package/dist/interactive-throttle.js +194 -0
  100. package/dist/interactive-throttle.js.map +1 -0
  101. package/dist/lineage-client.d.ts +77 -0
  102. package/dist/lineage-client.d.ts.map +1 -0
  103. package/dist/lineage-client.js +117 -0
  104. package/dist/lineage-client.js.map +1 -0
  105. package/dist/lineage-lease.d.ts +152 -0
  106. package/dist/lineage-lease.d.ts.map +1 -0
  107. package/dist/lineage-lease.js +303 -0
  108. package/dist/lineage-lease.js.map +1 -0
  109. package/dist/llm-gateway-runtime/accounts.d.ts +48 -0
  110. package/dist/llm-gateway-runtime/accounts.d.ts.map +1 -0
  111. package/dist/llm-gateway-runtime/accounts.js +226 -0
  112. package/dist/llm-gateway-runtime/accounts.js.map +1 -0
  113. package/dist/llm-gateway-runtime/index.d.ts +4 -0
  114. package/dist/llm-gateway-runtime/index.d.ts.map +1 -0
  115. package/dist/llm-gateway-runtime/index.js +58 -0
  116. package/dist/llm-gateway-runtime/index.js.map +1 -0
  117. package/dist/llm-gateway-runtime/model-catalog.d.ts +36 -0
  118. package/dist/llm-gateway-runtime/model-catalog.d.ts.map +1 -0
  119. package/dist/llm-gateway-runtime/model-catalog.js +146 -0
  120. package/dist/llm-gateway-runtime/model-catalog.js.map +1 -0
  121. package/dist/llm-gateway-runtime/proxy-anthropic.d.ts +3 -0
  122. package/dist/llm-gateway-runtime/proxy-anthropic.d.ts.map +1 -0
  123. package/dist/llm-gateway-runtime/proxy-anthropic.js +139 -0
  124. package/dist/llm-gateway-runtime/proxy-anthropic.js.map +1 -0
  125. package/dist/llm-gateway-runtime/proxy-openai.d.ts +117 -0
  126. package/dist/llm-gateway-runtime/proxy-openai.d.ts.map +1 -0
  127. package/dist/llm-gateway-runtime/proxy-openai.js +1132 -0
  128. package/dist/llm-gateway-runtime/proxy-openai.js.map +1 -0
  129. package/dist/llm-gateway-runtime/session-ledger.d.ts +32 -0
  130. package/dist/llm-gateway-runtime/session-ledger.d.ts.map +1 -0
  131. package/dist/llm-gateway-runtime/session-ledger.js +62 -0
  132. package/dist/llm-gateway-runtime/session-ledger.js.map +1 -0
  133. package/dist/llm-gateway-runtime/sticky.d.ts +40 -0
  134. package/dist/llm-gateway-runtime/sticky.d.ts.map +1 -0
  135. package/dist/llm-gateway-runtime/sticky.js +254 -0
  136. package/dist/llm-gateway-runtime/sticky.js.map +1 -0
  137. package/dist/llm-mesh.d.ts +102 -0
  138. package/dist/llm-mesh.d.ts.map +1 -0
  139. package/dist/llm-mesh.js +433 -0
  140. package/dist/llm-mesh.js.map +1 -0
  141. package/dist/migrate-candidates.d.ts +35 -0
  142. package/dist/migrate-candidates.d.ts.map +1 -0
  143. package/dist/migrate-candidates.js +128 -0
  144. package/dist/migrate-candidates.js.map +1 -0
  145. package/dist/migrate.d.ts +125 -0
  146. package/dist/migrate.d.ts.map +1 -0
  147. package/dist/migrate.js +561 -0
  148. package/dist/migrate.js.map +1 -0
  149. package/dist/plugin-manifest.d.ts +143 -0
  150. package/dist/plugin-manifest.d.ts.map +1 -0
  151. package/dist/plugin-manifest.js +233 -0
  152. package/dist/plugin-manifest.js.map +1 -0
  153. package/dist/plugin.d.ts +274 -0
  154. package/dist/plugin.d.ts.map +1 -0
  155. package/dist/plugin.js +919 -0
  156. package/dist/plugin.js.map +1 -0
  157. package/dist/pod-liveness.d.ts +51 -0
  158. package/dist/pod-liveness.d.ts.map +1 -0
  159. package/dist/pod-liveness.js +80 -0
  160. package/dist/pod-liveness.js.map +1 -0
  161. package/dist/profile-menu.d.ts +7 -0
  162. package/dist/profile-menu.d.ts.map +1 -0
  163. package/dist/profile-menu.js +52 -0
  164. package/dist/profile-menu.js.map +1 -0
  165. package/dist/profiles.d.ts +23 -0
  166. package/dist/profiles.d.ts.map +1 -0
  167. package/dist/profiles.js +66 -0
  168. package/dist/profiles.js.map +1 -0
  169. package/dist/protocol-local.d.ts +27 -0
  170. package/dist/protocol-local.d.ts.map +1 -0
  171. package/dist/protocol-local.js +12 -0
  172. package/dist/protocol-local.js.map +1 -0
  173. package/dist/pty.d.ts +26 -0
  174. package/dist/pty.d.ts.map +1 -0
  175. package/dist/pty.js +45 -0
  176. package/dist/pty.js.map +1 -0
  177. package/dist/readiness.d.ts +38 -0
  178. package/dist/readiness.d.ts.map +1 -0
  179. package/dist/readiness.js +169 -0
  180. package/dist/readiness.js.map +1 -0
  181. package/dist/registry.d.ts +258 -0
  182. package/dist/registry.d.ts.map +1 -0
  183. package/dist/registry.js +570 -0
  184. package/dist/registry.js.map +1 -0
  185. package/dist/relaunch.d.ts +52 -0
  186. package/dist/relaunch.d.ts.map +1 -0
  187. package/dist/relaunch.js +76 -0
  188. package/dist/relaunch.js.map +1 -0
  189. package/dist/restore.d.ts +165 -0
  190. package/dist/restore.d.ts.map +1 -0
  191. package/dist/restore.js +530 -0
  192. package/dist/restore.js.map +1 -0
  193. package/dist/run.d.ts +29 -0
  194. package/dist/run.d.ts.map +1 -0
  195. package/dist/run.js +206 -0
  196. package/dist/run.js.map +1 -0
  197. package/dist/secrets.d.ts +25 -0
  198. package/dist/secrets.d.ts.map +1 -0
  199. package/dist/secrets.js +98 -0
  200. package/dist/secrets.js.map +1 -0
  201. package/dist/session-restore.d.ts +26 -0
  202. package/dist/session-restore.d.ts.map +1 -0
  203. package/dist/session-restore.js +122 -0
  204. package/dist/session-restore.js.map +1 -0
  205. package/dist/skills-sync.d.ts +126 -0
  206. package/dist/skills-sync.d.ts.map +1 -0
  207. package/dist/skills-sync.js +255 -0
  208. package/dist/skills-sync.js.map +1 -0
  209. package/dist/smoke.d.ts +23 -0
  210. package/dist/smoke.d.ts.map +1 -0
  211. package/dist/smoke.js +138 -0
  212. package/dist/smoke.js.map +1 -0
  213. package/dist/soft-refresh.d.ts +107 -0
  214. package/dist/soft-refresh.d.ts.map +1 -0
  215. package/dist/soft-refresh.js +426 -0
  216. package/dist/soft-refresh.js.map +1 -0
  217. package/dist/sync-status.d.ts +22 -0
  218. package/dist/sync-status.d.ts.map +1 -0
  219. package/dist/sync-status.js +41 -0
  220. package/dist/sync-status.js.map +1 -0
  221. package/dist/sync.d.ts +77 -0
  222. package/dist/sync.d.ts.map +1 -0
  223. package/dist/sync.js +302 -0
  224. package/dist/sync.js.map +1 -0
  225. package/dist/throttle-signatures.d.ts +43 -0
  226. package/dist/throttle-signatures.d.ts.map +1 -0
  227. package/dist/throttle-signatures.js +116 -0
  228. package/dist/throttle-signatures.js.map +1 -0
  229. package/dist/tmux.d.ts +286 -0
  230. package/dist/tmux.d.ts.map +1 -0
  231. package/dist/tmux.js +970 -0
  232. package/dist/tmux.js.map +1 -0
  233. package/dist/tunnel.d.ts +25 -0
  234. package/dist/tunnel.d.ts.map +1 -0
  235. package/dist/tunnel.js +127 -0
  236. package/dist/tunnel.js.map +1 -0
  237. package/dist/workspace-merge.d.ts +17 -0
  238. package/dist/workspace-merge.d.ts.map +1 -0
  239. package/dist/workspace-merge.js +153 -0
  240. package/dist/workspace-merge.js.map +1 -0
  241. package/dist/workspace-sync-incremental.d.ts +64 -0
  242. package/dist/workspace-sync-incremental.d.ts.map +1 -0
  243. package/dist/workspace-sync-incremental.js +199 -0
  244. package/dist/workspace-sync-incremental.js.map +1 -0
  245. package/dist/workspace-sync.d.ts +3 -0
  246. package/dist/workspace-sync.d.ts.map +1 -0
  247. package/dist/workspace-sync.js +187 -0
  248. package/dist/workspace-sync.js.map +1 -0
  249. package/dist/workspace.d.ts +70 -0
  250. package/dist/workspace.d.ts.map +1 -0
  251. package/dist/workspace.js +173 -0
  252. package/dist/workspace.js.map +1 -0
  253. package/package.json +44 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Plugin/MCP DESIRED-STATE manifest + drift diff (remote supervise slice 3) —
3
+ * ADDITIVE, PURE. No IO, no clock. The watch loop / `plugin sync --check` call
4
+ * these to RENDER the desired set, HASH it (to gate a sidecar push exactly like
5
+ * CREDS_HASH_FILE), and DIFF it against what a Pod actually has installed.
6
+ *
7
+ * Why a manifest at all: MCP servers + plugin CLIs (track, h2a, harness,
8
+ * graphify, skills) baked/installed into Pods drift away from the LOCAL
9
+ * source-of-truth — a Pod restart loses globally-installed plugins, a re-pin
10
+ * bumps the local version, and nothing today DETECTS the gap (`plugin ls`
11
+ * prints `REMOTE ?`). The manifest is the canonical "what every Pod SHOULD have"
12
+ * record; its sha256 is the cheap drift signal (one `kubectl exec cat`), and the
13
+ * diff is the per-item report.
14
+ *
15
+ * BIGGEST REMAINING DRIFT SOURCE (DEFERRED — converge-on-session-start): a
16
+ * freshly (re)created Pod boots with NO plugins until the next `plugin sync` /
17
+ * watch pass reconciles it, so its CLI starts WITHOUT track/h2a/etc. Closing
18
+ * that needs the session-agent / orchestrator to run the sync BEFORE the CLI
19
+ * launches — a session-agent change that is OUT OF SCOPE this slice. It hooks in
20
+ * at the session-agent startup path (packages/session-agent, the pane that
21
+ * launches `<cli>`): before exec'ing the CLI, run the equivalent of
22
+ * `buildPodSyncScript` for each desired plugin + write this manifest sidecar.
23
+ * TODO(slice 4): wire converge-on-session-start there; until then the watch-pass
24
+ * reconcile (compareManifestHash → re-run buildPodSyncScript) is the safety net.
25
+ *
26
+ * NOTHING here is a NEW push path: `plugin sync` (no --check) still converges via
27
+ * the EXISTING buildPodSyncScript, untouched. This module only adds a manifest
28
+ * artifact + a read-only drift report.
29
+ */
30
+ import type { PluginEntry } from "./config.js";
31
+ /** One plugin package the Pods should carry (pkg + pinned version). */
32
+ export type ManifestPlugin = {
33
+ readonly pkg: string;
34
+ readonly version: string;
35
+ };
36
+ /** One MCP server the Pods should have registered (name + bin/script ref). */
37
+ export type ManifestMcp = {
38
+ readonly name: string;
39
+ readonly bin: string;
40
+ };
41
+ /**
42
+ * The desired-state manifest: the canonical set of plugins + MCP servers (+
43
+ * optional tracked skills) every live Pod should converge to. Field order /
44
+ * array order are NORMALIZED by `renderManifest` so the JSON — and therefore the
45
+ * hash — is independent of config key order.
46
+ */
47
+ export type PluginManifest = {
48
+ readonly plugins: ReadonlyArray<ManifestPlugin>;
49
+ readonly mcp: ReadonlyArray<ManifestMcp>;
50
+ /** Tracked skills, if any are recorded as desired state (none today; reserved). */
51
+ readonly skills?: ReadonlyArray<string>;
52
+ };
53
+ /**
54
+ * Render the CANONICAL desired-state manifest from the configured plugins.
55
+ * Deterministic: plugins sorted by pkg, MCP servers sorted by name, skills
56
+ * sorted — so two configs that differ only in array/key ORDER render the SAME
57
+ * manifest (and hash). PURE. `skills` is emitted only when a non-empty tracked
58
+ * list is passed (none is tracked in config today — reserved for slice 4).
59
+ */
60
+ export declare function renderManifest(plugins: ReadonlyArray<PluginEntry>, skills?: ReadonlyArray<string>): PluginManifest;
61
+ /**
62
+ * The CANONICAL JSON string of a manifest: keys in a FIXED order (plugins, mcp,
63
+ * skills) and every object's keys emitted in a fixed order, so the serialization
64
+ * is byte-stable regardless of how the manifest object was built. This is what
65
+ * gets written to `~/.remote-manifest.json` in the Pod AND what is hashed.
66
+ */
67
+ export declare function canonicalManifestJson(manifest: PluginManifest): string;
68
+ /**
69
+ * sha256 (hex) of the canonical manifest JSON — the cheap drift signal. Same
70
+ * input → same hash; key-order / array-order independent (renderManifest
71
+ * normalizes order, canonicalManifestJson fixes serialization). Mirrors
72
+ * `hashAuthBundle` in soft-refresh.ts: the Pod records this hex in
73
+ * `~/.remote-manifest.sha256`, and a pass compares local vs Pod to gate the
74
+ * sidecar push + trigger a reconcile.
75
+ */
76
+ export declare function manifestHash(manifest: PluginManifest): string;
77
+ /**
78
+ * What a Pod actually has installed, as probed by the thin pod-state reader:
79
+ * - `pluginVersions`: pkg -> installed version (from `npm ls -g --json`); a pkg
80
+ * absent from the map means it is NOT installed globally in the Pod.
81
+ * - `mcpRegistered`: the set of MCP server NAMES the Pod has registered (read
82
+ * from the claude/codex/agy config files the existing sync writes).
83
+ */
84
+ export type PodPluginState = {
85
+ readonly pluginVersions: Readonly<Record<string, string>>;
86
+ readonly mcpRegistered: ReadonlyArray<string>;
87
+ };
88
+ /** Drift status for one desired item in one Pod. */
89
+ export type DriftStatus = "ok" | "version-drift" | "missing" | "mcp-unregistered";
90
+ /** One row of the drift report: a desired item's status in a Pod. */
91
+ export type DriftRow = {
92
+ readonly pod: string;
93
+ /** "plugin:<pkg>" or "mcp:<name>". */
94
+ readonly item: string;
95
+ readonly status: DriftStatus;
96
+ /** Human detail (e.g. "local@1.2.0 vs pod@1.1.0"); empty for ok. */
97
+ readonly detail: string;
98
+ };
99
+ /**
100
+ * DIFF the desired manifest against one Pod's actual state. PURE. One row per
101
+ * desired plugin + one per desired MCP server:
102
+ * - plugin MISSING → pkg not installed globally in the Pod.
103
+ * - plugin VERSION-DRIFT → installed but at a different version than desired.
104
+ * - plugin OK → installed at the desired version.
105
+ * - mcp MCP-UNREGISTERED → desired MCP name not in the Pod's registered set.
106
+ * - mcp OK → registered.
107
+ * Rows preserve the manifest's (already-canonical) order, plugins before mcp.
108
+ */
109
+ export declare function diffManifest(manifest: PluginManifest, pod: string, state: PodPluginState): DriftRow[];
110
+ /** A row is drift when its status is anything other than `ok`. */
111
+ export declare function isDrift(row: DriftRow): boolean;
112
+ /**
113
+ * The process exit code for a `--check` run: 1 when ANY row across all Pods is
114
+ * drift, else 0. PURE — the command stays a thin printer around this. An empty
115
+ * report (no Pods / no desired items) is NOT drift → exit 0.
116
+ */
117
+ export declare function checkExitCode(rows: ReadonlyArray<DriftRow>): number;
118
+ /**
119
+ * Parse `npm ls -g --json` output into pkg -> installed version, keeping ONLY
120
+ * the packages we care about (`wanted`). npm's JSON is
121
+ * `{ dependencies: { "<pkg>": { version, ... }, ... } }`; a missing/!
122
+ * unparseable blob yields an empty map (treated downstream as "nothing
123
+ * installed" → all desired plugins report missing, which is the safe drift
124
+ * signal). PURE, exported for tests. A pkg present but without a string version
125
+ * is skipped (can't assert a version → reported missing, never a false "ok").
126
+ */
127
+ export declare function parseNpmLsVersions(json: string, wanted: ReadonlyArray<string>): Record<string, string>;
128
+ /**
129
+ * Parse the set of registered MCP server NAMES from the Pod's MCP config blobs.
130
+ * The existing sync writes either a Claude-style JSON (`{"mcpServers": {...}}` —
131
+ * ~/.claude.json and agy's mcp_config.json) or codex TOML
132
+ * (`[mcp_servers.<name>]` sections). We read whatever blobs are present and
133
+ * union their server names. PURE, exported for tests. A blob that is empty /
134
+ * unparseable contributes nothing (never throws — an unreadable config is just
135
+ * "no servers from there", surfacing as mcp-unregistered, the safe drift call).
136
+ */
137
+ export declare function parseRegisteredMcpServers(blobs: {
138
+ /** ~/.claude.json and/or agy mcp_config.json bodies (Claude-style JSON). */
139
+ readonly json?: ReadonlyArray<string>;
140
+ /** ~/.codex/config.toml body. */
141
+ readonly toml?: string;
142
+ }): string[];
143
+ //# sourceMappingURL=plugin-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-manifest.d.ts","sourceRoot":"","sources":["../src/plugin-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C,uEAAuE;AACvE,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhF,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAChD,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACzC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACzC,CAAC;AAaF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,EACnC,MAAM,GAAE,aAAa,CAAC,MAAM,CAAM,GACjC,cAAc,CAmBhB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAStE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAE7D;AAMD;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC/C,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,WAAW,GACnB,IAAI,GACJ,eAAe,GACf,SAAS,GACT,kBAAkB,CAAC;AAEvB,qEAAqE;AACrE,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,cAAc,EACxB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,cAAc,GACpB,QAAQ,EAAE,CAoCZ;AAED,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,MAAM,CAEnE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,iCAAiC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,EAAE,CA2BX"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Plugin/MCP DESIRED-STATE manifest + drift diff (remote supervise slice 3) —
3
+ * ADDITIVE, PURE. No IO, no clock. The watch loop / `plugin sync --check` call
4
+ * these to RENDER the desired set, HASH it (to gate a sidecar push exactly like
5
+ * CREDS_HASH_FILE), and DIFF it against what a Pod actually has installed.
6
+ *
7
+ * Why a manifest at all: MCP servers + plugin CLIs (track, h2a, harness,
8
+ * graphify, skills) baked/installed into Pods drift away from the LOCAL
9
+ * source-of-truth — a Pod restart loses globally-installed plugins, a re-pin
10
+ * bumps the local version, and nothing today DETECTS the gap (`plugin ls`
11
+ * prints `REMOTE ?`). The manifest is the canonical "what every Pod SHOULD have"
12
+ * record; its sha256 is the cheap drift signal (one `kubectl exec cat`), and the
13
+ * diff is the per-item report.
14
+ *
15
+ * BIGGEST REMAINING DRIFT SOURCE (DEFERRED — converge-on-session-start): a
16
+ * freshly (re)created Pod boots with NO plugins until the next `plugin sync` /
17
+ * watch pass reconciles it, so its CLI starts WITHOUT track/h2a/etc. Closing
18
+ * that needs the session-agent / orchestrator to run the sync BEFORE the CLI
19
+ * launches — a session-agent change that is OUT OF SCOPE this slice. It hooks in
20
+ * at the session-agent startup path (packages/session-agent, the pane that
21
+ * launches `<cli>`): before exec'ing the CLI, run the equivalent of
22
+ * `buildPodSyncScript` for each desired plugin + write this manifest sidecar.
23
+ * TODO(slice 4): wire converge-on-session-start there; until then the watch-pass
24
+ * reconcile (compareManifestHash → re-run buildPodSyncScript) is the safety net.
25
+ *
26
+ * NOTHING here is a NEW push path: `plugin sync` (no --check) still converges via
27
+ * the EXISTING buildPodSyncScript, untouched. This module only adds a manifest
28
+ * artifact + a read-only drift report.
29
+ */
30
+ import { createHash } from "node:crypto";
31
+ /**
32
+ * The MCP "bin" reference recorded in the manifest. We use the package-relative
33
+ * script path (`scriptRel`, e.g. "dist/mcp.js") when known — it is the stable,
34
+ * Pod-portable identity (the realpath differs per npm-global root). When a
35
+ * plugin entry has no scriptRel recorded we fall back to the server name so the
36
+ * manifest still names the server (a plain presence check downstream).
37
+ */
38
+ function manifestMcpBin(mcp) {
39
+ return mcp.scriptRel ?? mcp.name;
40
+ }
41
+ /**
42
+ * Render the CANONICAL desired-state manifest from the configured plugins.
43
+ * Deterministic: plugins sorted by pkg, MCP servers sorted by name, skills
44
+ * sorted — so two configs that differ only in array/key ORDER render the SAME
45
+ * manifest (and hash). PURE. `skills` is emitted only when a non-empty tracked
46
+ * list is passed (none is tracked in config today — reserved for slice 4).
47
+ */
48
+ export function renderManifest(plugins, skills = []) {
49
+ const manifestPlugins = [...plugins]
50
+ .map((p) => ({ pkg: p.pkg, version: p.version }))
51
+ .sort((a, b) => (a.pkg < b.pkg ? -1 : a.pkg > b.pkg ? 1 : 0));
52
+ const mcp = [];
53
+ for (const p of plugins) {
54
+ for (const m of p.mcp) {
55
+ mcp.push({ name: m.name, bin: manifestMcpBin(m) });
56
+ }
57
+ }
58
+ mcp.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
59
+ const manifest = { plugins: manifestPlugins, mcp };
60
+ const uniqueSkills = [...new Set(skills)].sort();
61
+ if (uniqueSkills.length > 0) {
62
+ return { ...manifest, skills: uniqueSkills };
63
+ }
64
+ return manifest;
65
+ }
66
+ /**
67
+ * The CANONICAL JSON string of a manifest: keys in a FIXED order (plugins, mcp,
68
+ * skills) and every object's keys emitted in a fixed order, so the serialization
69
+ * is byte-stable regardless of how the manifest object was built. This is what
70
+ * gets written to `~/.remote-manifest.json` in the Pod AND what is hashed.
71
+ */
72
+ export function canonicalManifestJson(manifest) {
73
+ const canonical = {
74
+ plugins: manifest.plugins.map((p) => ({ pkg: p.pkg, version: p.version })),
75
+ mcp: manifest.mcp.map((m) => ({ name: m.name, bin: m.bin })),
76
+ };
77
+ if (manifest.skills && manifest.skills.length > 0) {
78
+ canonical.skills = [...manifest.skills];
79
+ }
80
+ return JSON.stringify(canonical);
81
+ }
82
+ /**
83
+ * sha256 (hex) of the canonical manifest JSON — the cheap drift signal. Same
84
+ * input → same hash; key-order / array-order independent (renderManifest
85
+ * normalizes order, canonicalManifestJson fixes serialization). Mirrors
86
+ * `hashAuthBundle` in soft-refresh.ts: the Pod records this hex in
87
+ * `~/.remote-manifest.sha256`, and a pass compares local vs Pod to gate the
88
+ * sidecar push + trigger a reconcile.
89
+ */
90
+ export function manifestHash(manifest) {
91
+ return createHash("sha256").update(canonicalManifestJson(manifest)).digest("hex");
92
+ }
93
+ /**
94
+ * DIFF the desired manifest against one Pod's actual state. PURE. One row per
95
+ * desired plugin + one per desired MCP server:
96
+ * - plugin MISSING → pkg not installed globally in the Pod.
97
+ * - plugin VERSION-DRIFT → installed but at a different version than desired.
98
+ * - plugin OK → installed at the desired version.
99
+ * - mcp MCP-UNREGISTERED → desired MCP name not in the Pod's registered set.
100
+ * - mcp OK → registered.
101
+ * Rows preserve the manifest's (already-canonical) order, plugins before mcp.
102
+ */
103
+ export function diffManifest(manifest, pod, state) {
104
+ const rows = [];
105
+ for (const p of manifest.plugins) {
106
+ const installed = state.pluginVersions[p.pkg];
107
+ if (installed === undefined) {
108
+ rows.push({
109
+ pod,
110
+ item: `plugin:${p.pkg}`,
111
+ status: "missing",
112
+ detail: `not installed (desired ${p.version})`,
113
+ });
114
+ }
115
+ else if (installed !== p.version) {
116
+ rows.push({
117
+ pod,
118
+ item: `plugin:${p.pkg}`,
119
+ status: "version-drift",
120
+ detail: `local@${p.version} vs pod@${installed}`,
121
+ });
122
+ }
123
+ else {
124
+ rows.push({ pod, item: `plugin:${p.pkg}`, status: "ok", detail: "" });
125
+ }
126
+ }
127
+ const registered = new Set(state.mcpRegistered);
128
+ for (const m of manifest.mcp) {
129
+ if (registered.has(m.name)) {
130
+ rows.push({ pod, item: `mcp:${m.name}`, status: "ok", detail: "" });
131
+ }
132
+ else {
133
+ rows.push({
134
+ pod,
135
+ item: `mcp:${m.name}`,
136
+ status: "mcp-unregistered",
137
+ detail: "not registered in the pod's MCP config",
138
+ });
139
+ }
140
+ }
141
+ return rows;
142
+ }
143
+ /** A row is drift when its status is anything other than `ok`. */
144
+ export function isDrift(row) {
145
+ return row.status !== "ok";
146
+ }
147
+ /**
148
+ * The process exit code for a `--check` run: 1 when ANY row across all Pods is
149
+ * drift, else 0. PURE — the command stays a thin printer around this. An empty
150
+ * report (no Pods / no desired items) is NOT drift → exit 0.
151
+ */
152
+ export function checkExitCode(rows) {
153
+ return rows.some(isDrift) ? 1 : 0;
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // Pod-state probe PARSERS (pure — the executors that shell out stay thin).
157
+ // ---------------------------------------------------------------------------
158
+ /**
159
+ * Parse `npm ls -g --json` output into pkg -> installed version, keeping ONLY
160
+ * the packages we care about (`wanted`). npm's JSON is
161
+ * `{ dependencies: { "<pkg>": { version, ... }, ... } }`; a missing/!
162
+ * unparseable blob yields an empty map (treated downstream as "nothing
163
+ * installed" → all desired plugins report missing, which is the safe drift
164
+ * signal). PURE, exported for tests. A pkg present but without a string version
165
+ * is skipped (can't assert a version → reported missing, never a false "ok").
166
+ */
167
+ export function parseNpmLsVersions(json, wanted) {
168
+ const want = new Set(wanted);
169
+ const out = {};
170
+ let parsed;
171
+ try {
172
+ parsed = JSON.parse(json);
173
+ }
174
+ catch {
175
+ return out;
176
+ }
177
+ if (!parsed || typeof parsed !== "object")
178
+ return out;
179
+ const deps = parsed.dependencies;
180
+ if (!deps || typeof deps !== "object")
181
+ return out;
182
+ for (const [pkg, info] of Object.entries(deps)) {
183
+ if (!want.has(pkg))
184
+ continue;
185
+ if (info && typeof info === "object") {
186
+ const v = info.version;
187
+ if (typeof v === "string")
188
+ out[pkg] = v;
189
+ }
190
+ }
191
+ return out;
192
+ }
193
+ /**
194
+ * Parse the set of registered MCP server NAMES from the Pod's MCP config blobs.
195
+ * The existing sync writes either a Claude-style JSON (`{"mcpServers": {...}}` —
196
+ * ~/.claude.json and agy's mcp_config.json) or codex TOML
197
+ * (`[mcp_servers.<name>]` sections). We read whatever blobs are present and
198
+ * union their server names. PURE, exported for tests. A blob that is empty /
199
+ * unparseable contributes nothing (never throws — an unreadable config is just
200
+ * "no servers from there", surfacing as mcp-unregistered, the safe drift call).
201
+ */
202
+ export function parseRegisteredMcpServers(blobs) {
203
+ const names = new Set();
204
+ for (const body of blobs.json ?? []) {
205
+ if (!body || !body.trim())
206
+ continue;
207
+ let parsed;
208
+ try {
209
+ parsed = JSON.parse(body);
210
+ }
211
+ catch {
212
+ continue;
213
+ }
214
+ if (!parsed || typeof parsed !== "object")
215
+ continue;
216
+ const servers = parsed.mcpServers;
217
+ if (servers && typeof servers === "object" && !Array.isArray(servers)) {
218
+ for (const name of Object.keys(servers)) {
219
+ names.add(name);
220
+ }
221
+ }
222
+ }
223
+ if (blobs.toml) {
224
+ // `[mcp_servers.<name>]` section headers — name is SAFE_NAME ([A-Za-z0-9_-]).
225
+ const re = /^\s*\[mcp_servers\.([A-Za-z0-9_-]+)\]\s*$/gm;
226
+ let m;
227
+ while ((m = re.exec(blobs.toml)) !== null) {
228
+ names.add(m[1]);
229
+ }
230
+ }
231
+ return [...names].sort();
232
+ }
233
+ //# sourceMappingURL=plugin-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-manifest.js","sourceRoot":"","sources":["../src/plugin-manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,274 @@
1
+ /**
2
+ * `remote plugin` — install npm "plugin" packages (a CLI + an MCP server, e.g.
3
+ * @sentropic/track shipping bins `track` and `track-mcp`) for every agent CLI
4
+ * (claude, codex, agy), both LOCALLY (npm i -g + MCP registration) and inside
5
+ * live REMOTE session Pods (`remote plugin sync`: kubectl exec → npm i -g +
6
+ * per-profile MCP registration).
7
+ *
8
+ * agy (Antigravity CLI) has NO `agy mcp` subcommand: MCP servers are declared
9
+ * in ~/.gemini/config/mcp_config.json, a Claude-style `{"mcpServers": {…}}`
10
+ * JSON file (the agy changelog 1.0.3 calls this the "migrated" path; the old
11
+ * ~/.gemini/antigravity/mcp_config.json is legacy). We merge idempotently and
12
+ * keep a one-shot `.bak.<epoch>` backup the first time we touch a non-empty
13
+ * file.
14
+ *
15
+ * KNOWN PITFALL — broken entrypoint guard through the npm-global symlink:
16
+ * some packages (track@0.2.0) guard their entry script with a
17
+ * "was-I-run-directly?" check that compares argv[1] with the module path;
18
+ * invoked through the npm-global bin SYMLINK the two differ and the guard
19
+ * never fires (the CLI/MCP silently does nothing). So MCP servers are ALWAYS
20
+ * registered as `node <realpathSync(script)>` — never the bare bin name.
21
+ *
22
+ * Baking plugins into the session image is a separate TODO that belongs in
23
+ * packages/session-agent/Dockerfile (left untouched here): until then a Pod
24
+ * restart loses globally-installed plugins and `remote plugin sync` must be
25
+ * re-run.
26
+ *
27
+ * DRIFT (remote supervise slice 3, ADDITIVE): a desired-state manifest
28
+ * (plugin-manifest.ts) + a sidecar pushed to each Pod (~/.remote-manifest.json /
29
+ * ~/.remote-manifest.sha256, gated EXACTLY like CREDS_HASH_FILE), a read-only
30
+ * `plugin sync --check` drift report, and a watch-pass reconcile that re-runs
31
+ * the EXISTING buildPodSyncScript on a hash mismatch (an extra TRIGGER, not a
32
+ * new push path). The plain `plugin add`/`plugin sync` push is UNCHANGED.
33
+ *
34
+ * TODO(slice 4 — converge-on-session-start, the BIGGEST remaining drift source):
35
+ * a freshly (re)created Pod boots with NO plugins until the next sync/watch pass,
36
+ * so its CLI launches WITHOUT track/h2a/etc. Closing that needs the session-agent
37
+ * startup path (packages/session-agent, the pane that launches `<cli>`) to run
38
+ * the sync + write the manifest sidecar BEFORE exec'ing the CLI — OUT OF SCOPE
39
+ * here (do not touch session-agent this slice). The watch-pass reconcile is the
40
+ * interim safety net.
41
+ */
42
+ import { type PluginEntry, type PluginInstall, type TunnelConfig } from "./config.js";
43
+ import { type DriftRow, type PluginManifest, type PodPluginState } from "./plugin-manifest.js";
44
+ /**
45
+ * The shell install command for a plugin + a one-line label. Pure, exported for
46
+ * tests. npm (default): `npm install -g <pkg>@<ver>`. curl: pipe an https
47
+ * installer (`curl -fsSL <url> | bash`) — the url is single-quoted and must be
48
+ * a clean https URL. script: an arbitrary shell line straight from the user's
49
+ * own config (no extra guard — it is the user's command). Same command runs
50
+ * locally (pluginAdd) and in the Pod (buildPodSyncScript).
51
+ */
52
+ export declare function buildInstallCommand(plugin: PluginEntry): {
53
+ cmd: string;
54
+ label: string;
55
+ };
56
+ /** Split `pkg[@version]` (scope-aware: the leading @ is not a version sep). */
57
+ export declare function splitNpmSpec(spec: string): {
58
+ pkg: string;
59
+ version?: string;
60
+ };
61
+ export type McpRequest = {
62
+ name: string;
63
+ bin: string;
64
+ };
65
+ /** Parse one `--mcp <name>=<bin>` spec. */
66
+ export declare function parseMcpSpec(spec: string): McpRequest;
67
+ export declare function parseMcpSpecs(specs: readonly string[]): McpRequest[];
68
+ /**
69
+ * Heuristic when no --mcp is given: every bin ending in `-mcp` is an MCP
70
+ * server named after the bin minus the suffix (track-mcp → track).
71
+ */
72
+ export declare function detectMcpBins(bins: Readonly<Record<string, string>>): McpRequest[];
73
+ /** Normalize a package.json `bin` field (string or map) to name -> rel path. */
74
+ export declare function normalizeBins(pkgName: string, bin: unknown): Record<string, string>;
75
+ /** The `[mcp_servers.<name>]` TOML section for ~/.codex/config.toml. */
76
+ export declare function codexMcpServerBlock(name: string, command: string, args: readonly string[]): string;
77
+ /**
78
+ * Idempotently upsert the `[mcp_servers.<name>]` section in a config.toml
79
+ * body: an existing section (up to the next `[…]` header) is replaced in
80
+ * place, otherwise the block is appended. Applying twice is a no-op.
81
+ */
82
+ export declare function upsertCodexMcpServer(toml: string, name: string, command: string, args: readonly string[]): string;
83
+ /** Idempotent `mcpServers.<name>` merge for a ~/.claude.json body. */
84
+ export declare function mergeClaudeMcpServers(json: string, name: string, command: string, args: readonly string[]): string;
85
+ /**
86
+ * Idempotent `mcpServers.<name>` merge for agy's
87
+ * ~/.gemini/config/mcp_config.json (same Claude-style shape — the agy binary
88
+ * schema accepts command/args/env/url per server; we only emit command+args).
89
+ */
90
+ export declare function mergeAgyMcpServers(json: string, name: string, command: string, args: readonly string[]): string;
91
+ /**
92
+ * Pure write plan for agy's mcp_config.json: the merged body, whether it
93
+ * differs from `before`, and whether a `.bak.<epoch>` backup is owed (only on
94
+ * the FIRST real modification of a non-empty file — an empty/absent file has
95
+ * nothing worth backing up, and an unchanged file is never rewritten).
96
+ */
97
+ export declare function planAgyMcpConfigUpdate(before: string, name: string, command: string, args: readonly string[]): {
98
+ next: string;
99
+ changed: boolean;
100
+ needsBackup: boolean;
101
+ };
102
+ /**
103
+ * In-Pod ~/.claude.json merge, run as `node -e '<this>' <name> <scriptPath>`.
104
+ * Double quotes only — the snippet is single-quoted inside the bash script.
105
+ */
106
+ export declare const POD_CLAUDE_MERGE_JS: string;
107
+ /**
108
+ * In-Pod ~/.gemini/config/mcp_config.json merge, run as
109
+ * `node -e '<this>' <name> <scriptPath>`. Double quotes only — the snippet is
110
+ * single-quoted inside the bash script. Same shape as the claude merge; the
111
+ * config dir may not exist yet in a fresh Pod, hence mkdirSync.
112
+ */
113
+ export declare const POD_AGY_MERGE_JS: string;
114
+ /** claude / claude-code → claude; codex → codex; agy / antigravity → agy. */
115
+ export declare function mcpTargetForProfile(profile: string): "claude" | "codex" | "agy" | "todo";
116
+ /**
117
+ * Bash script run inside a session Pod (`kubectl exec … bash -lc`) by
118
+ * `remote plugin sync`: installs the plugin globally, recomputes each MCP
119
+ * script's realpath against the POD's npm global root (the local realpath is
120
+ * meaningless there), then registers the MCP server for the Pod's profile.
121
+ * Every step echoes one line — the CLI prints them as the per-session recap.
122
+ */
123
+ export declare function buildPodSyncScript(plugin: PluginEntry, profile: string): string;
124
+ /** HOME-relative manifest sidecar in the Pod (the desired-state JSON). */
125
+ export declare const MANIFEST_FILE = ".remote-manifest.json";
126
+ /** HOME-relative file recording the sha256 of the last pushed manifest. */
127
+ export declare const MANIFEST_HASH_FILE = ".remote-manifest.sha256";
128
+ /** Render the desired-state manifest for the configured plugins (no tracked skills today). */
129
+ export declare function desiredManifest(): PluginManifest;
130
+ /**
131
+ * Injectable IO seams for the manifest sidecar + reconcile so the gate is
132
+ * unit-testable WITHOUT shelling out to kubectl. Production wires the real
133
+ * execPod against a tunnel (`manifestPodIo`); tests pass a stateful fake Pod and
134
+ * assert that a converged Pod re-reports NO drift (no resync) next pass.
135
+ */
136
+ export type ManifestPodIo = {
137
+ /** Read the Pod's recorded manifest hash ("" when absent/unreadable). */
138
+ readHash: () => string;
139
+ /** Persist the manifest JSON + its hash in the Pod (the sidecar write). */
140
+ writeSidecar: (json: string, hash: string) => void;
141
+ /** Run one plugin's EXISTING buildPodSyncScript in the Pod; returns its stdout. */
142
+ runSync: (plugin: PluginEntry) => string;
143
+ };
144
+ /** Production wiring of `ManifestPodIo` against a live Pod (real kubectl exec). */
145
+ export declare function manifestPodIo(tunnel: TunnelConfig, pod: string, profile: string): ManifestPodIo;
146
+ /**
147
+ * Push the desired-state manifest into a Pod as ~/.remote-manifest.json +
148
+ * ~/.remote-manifest.sha256, GATED exactly like CREDS_HASH_FILE: read the Pod's
149
+ * recorded hash first and SKIP the write when it already matches (so a steady
150
+ * Pod is a single `cat`, no churn). Mirrors soft-refresh's hash-sidecar pattern.
151
+ * Returns whether the sidecar was (re)written. Best-effort: caller logs errors.
152
+ */
153
+ export declare function pushManifestSidecar(tunnel: TunnelConfig, pod: string, manifest: PluginManifest): {
154
+ wrote: boolean;
155
+ hash: string;
156
+ };
157
+ /** Sidecar push over an injected `ManifestPodIo` (the testable core). PURE-ish. */
158
+ export declare function pushManifestSidecarVia(io: ManifestPodIo, manifest: PluginManifest): {
159
+ wrote: boolean;
160
+ hash: string;
161
+ };
162
+ /** The Pod's recorded manifest hash, or "" when absent/unreadable. */
163
+ export declare function readPodManifestHash(tunnel: TunnelConfig, pod: string): string;
164
+ /**
165
+ * Injectable seams so the pod-state probe is unit-testable WITHOUT kubectl.
166
+ * `npmLs` runs the argv-safe `npm ls -g --json` and returns its stdout;
167
+ * `readMcpConfigs` returns the raw MCP config blobs the existing sync writes.
168
+ */
169
+ export type PodProbeDeps = {
170
+ /** Run `npm ls -g --json <pkgs…>` in the Pod, return stdout (may be non-zero exit). */
171
+ npmLs: (pkgs: ReadonlyArray<string>) => string;
172
+ /** Read the Pod's MCP config blobs: Claude-style JSON bodies + codex TOML. */
173
+ readMcpConfigs: () => {
174
+ json: string[];
175
+ toml: string;
176
+ };
177
+ };
178
+ /**
179
+ * Probe ONE Pod's actual plugin/MCP state into a `PodPluginState` (pure shape):
180
+ * installed versions for the desired pkgs (`npm ls -g --json` → parsed) + the
181
+ * registered MCP server names (claude/agy JSON + codex TOML → parsed). PURE-ish:
182
+ * all IO is injected via `deps` (tests never shell out). Exported for tests.
183
+ */
184
+ export declare function probePodPluginState(desiredPkgs: ReadonlyArray<string>, deps: PodProbeDeps): PodPluginState;
185
+ /**
186
+ * Watch-pass RECONCILE for ONE live Pod (an EXTRA trigger, NOT a new push path):
187
+ * compare the local desired-state hash with the Pod's recorded
188
+ * ~/.remote-manifest.sha256 (one `cat`); on a MISMATCH, re-run the EXISTING
189
+ * buildPodSyncScript for every plugin (the same mechanism `plugin sync` uses)
190
+ * and re-push the manifest sidecar. A match is a zero-work no-op. Mirrors the
191
+ * slice-2 probe→push advisory structure. Best-effort: a per-plugin failure is
192
+ * logged; the others proceed. Returns what happened. Used by the supervise pass.
193
+ */
194
+ export declare function reconcilePodManifest(tunnel: TunnelConfig, pod: string, profile: string, manifest: PluginManifest, plugins: ReadonlyArray<PluginEntry>, stderr?: {
195
+ write: (s: string) => unknown;
196
+ }): {
197
+ reconciled: boolean;
198
+ localHash: string;
199
+ podHash: string;
200
+ failures: number;
201
+ };
202
+ /**
203
+ * Reconcile core over an injected `ManifestPodIo` (the testable seam). PURE-ish:
204
+ * all Pod IO goes through `io`, so tests drive a stateful fake Pod and assert the
205
+ * gate — a matching hash is a zero-work no-op; an absent/mismatched hash resyncs
206
+ * ONCE then writes the sidecar so the NEXT pass converges (no re-sync).
207
+ *
208
+ * PERSISTENCE FIX (slice-3 regression): the sidecar is written via the SAME `io`
209
+ * (no second pod read, no `pushManifestSidecar` re-gate that could skip it) and
210
+ * is written even when some plugin syncs FAILED, so the Pod's recorded hash
211
+ * always converges to local after a reconcile — otherwise the next pass re-reads
212
+ * an absent hash and re-reports drift forever. (The dead-pod guard upstream keeps
213
+ * us off Evicted Pods where the write would throw and never persist at all.)
214
+ */
215
+ export declare function reconcilePodManifestVia(io: ManifestPodIo, pod: string, manifest: PluginManifest, plugins: ReadonlyArray<PluginEntry>, stderr?: {
216
+ write: (s: string) => unknown;
217
+ }): {
218
+ reconciled: boolean;
219
+ localHash: string;
220
+ podHash: string;
221
+ failures: number;
222
+ };
223
+ /**
224
+ * Production wiring used by the supervise/watch pass: reconcile manifest drift
225
+ * for ONE session via the configured tunnel. Thin — the decision + the reused
226
+ * buildPodSyncScript live in `reconcilePodManifest`. No-op (and no error) when
227
+ * no plugins are configured. Best-effort; never throws into the caller.
228
+ */
229
+ export declare function reconcileSessionPlugins(sessionId: string, profile: string, stderr?: NodeJS.WriteStream): {
230
+ reconciled: boolean;
231
+ };
232
+ /**
233
+ * `remote plugin add <npmPkg> [--mcp name=bin]...` — npm i -g, register the
234
+ * MCP server(s) with claude + codex + agy, persist in the config.
235
+ */
236
+ export declare function pluginAdd(npmSpec: string, mcpSpecs: readonly string[], stderr?: NodeJS.WriteStream): PluginEntry;
237
+ /**
238
+ * `remote plugin add <name> --curl <url>` / `--install "<shell>"` — register a
239
+ * NON-npm plugin (a tool installed by piping an https script, or an arbitrary
240
+ * shell command). No local install is run (unlike the npm path, which must read
241
+ * the package to detect MCP bins): the installer runs in each Pod on
242
+ * `remote plugin sync`. Validates the command up front so a bad URL fails now.
243
+ */
244
+ export declare function pluginAddInstaller(name: string, install: PluginInstall, stderr?: NodeJS.WriteStream): PluginEntry;
245
+ /**
246
+ * `remote plugin ls` — pkg / version / MCPs / where (local ok/missing, remote).
247
+ *
248
+ * The REMOTE column is now REAL per-Pod drift status (slice 3), not the old
249
+ * guess-`?`: when a `url` is passed (the command is connected to the control-
250
+ * plane) we probe each live Pod via the same drift mechanism `--check` uses and
251
+ * summarize each plugin's worst per-Pod status across all Pods
252
+ * (ok / version-drift / missing). Without a url (offline) we still print `?`
253
+ * with the documented hint — the `?` only remains when we genuinely can't reach
254
+ * the cluster, never as guesswork when we can.
255
+ */
256
+ export declare function pluginLs(stdout?: NodeJS.WriteStream, url?: string): Promise<void>;
257
+ /**
258
+ * `remote plugin sync` — for every live remote session: kubectl exec into the
259
+ * Pod, `npm i -g <pkg>@<version>`, then register the MCP servers for the
260
+ * Pod's profile. Prints a per-session recap. Needs the configured tunnel.
261
+ */
262
+ export declare function pluginSync(url: string, stderr?: NodeJS.WriteStream): Promise<void>;
263
+ /**
264
+ * `remote plugin sync --check` — a READ-ONLY drift report (converges NOTHING).
265
+ * For each live Pod it probes the actual installed state (npm ls -g --json for
266
+ * the desired pkgs + the registered MCP server names from the config files the
267
+ * existing sync writes) via the argv-safe pod exec, then the pure
268
+ * `diffManifest` rows it: ok / version-drift / missing / mcp-unregistered.
269
+ * Prints a table and EXITS 1 when ANY row is drift (via process.exitCode). This
270
+ * REPLACES the `plugin ls` `REMOTE ?` guesswork with REAL per-Pod status. Pure
271
+ * decisions live in plugin-manifest.ts; this is the thin probe + printer.
272
+ */
273
+ export declare function pluginSyncCheck(url: string, stdout?: NodeJS.WriteStream, stderr?: NodeJS.WriteStream): Promise<DriftRow[]>;
274
+ //# sourceMappingURL=plugin.d.ts.map