super-dsh 0.1.6 → 0.1.10-e

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 (260) hide show
  1. package/README.md +25 -3
  2. package/agent-agy/cordis.patch.yml +7 -4
  3. package/agent-agy/dist/agent.d.ts +12 -11
  4. package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
  5. package/agent-agy/dist/agy-auth-gateway.js +233 -0
  6. package/agent-agy/dist/agy-auth.d.ts +153 -0
  7. package/agent-agy/dist/agy-auth.js +179 -0
  8. package/agent-agy/dist/agy-cli-client.d.ts +39 -9
  9. package/agent-agy/dist/agy-cli-client.js +82 -18
  10. package/agent-agy/dist/agy-store.d.ts +8 -0
  11. package/agent-agy/dist/index.d.ts +46 -16
  12. package/agent-agy/dist/index.js +82 -56
  13. package/agent-agy/dist/models.d.ts +70 -0
  14. package/agent-agy/dist/models.js +102 -3
  15. package/agent-agy/package.json +7 -5
  16. package/agent-agy-sdk/cordis.patch.yml +58 -0
  17. package/agent-agy-sdk/dist/adapter.d.ts +15 -0
  18. package/agent-agy-sdk/dist/adapter.js +42 -0
  19. package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
  20. package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
  21. package/agent-agy-sdk/dist/agent.d.ts +138 -0
  22. package/agent-agy-sdk/dist/agent.js +494 -0
  23. package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
  24. package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
  25. package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
  26. package/agent-agy-sdk/dist/agy-sessions.js +107 -0
  27. package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
  28. package/agent-agy-sdk/dist/agy-store.js +83 -0
  29. package/agent-agy-sdk/dist/inbox.d.ts +26 -0
  30. package/agent-agy-sdk/dist/inbox.js +53 -0
  31. package/agent-agy-sdk/dist/index.d.ts +93 -0
  32. package/agent-agy-sdk/dist/index.js +476 -0
  33. package/agent-agy-sdk/dist/models.d.ts +41 -0
  34. package/agent-agy-sdk/dist/models.js +74 -0
  35. package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
  36. package/agent-agy-sdk/dist/sdk-venv.js +173 -0
  37. package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
  38. package/agent-agy-sdk/dist/world-plugin.js +82 -0
  39. package/agent-agy-sdk/package.json +64 -0
  40. package/agent-claude/dist/adapter.js +106 -31
  41. package/agent-claude/dist/agent.js +154 -17
  42. package/agent-claude/dist/claude-client.js +84 -43
  43. package/agent-claude/dist/claude-launch.js +101 -0
  44. package/agent-claude/dist/claude-native-config.js +169 -0
  45. package/agent-claude/dist/claude-provider-gateway.js +340 -0
  46. package/agent-claude/dist/claude-providers.js +391 -0
  47. package/agent-claude/dist/claude-route.js +152 -0
  48. package/agent-claude/dist/index.js +214 -37
  49. package/agent-claude/dist/models.js +14 -0
  50. package/agent-claude/dist/sdk-runtime.js +320 -0
  51. package/agent-claude/package.json +6 -4
  52. package/agent-codex/README.md +141 -36
  53. package/agent-codex/cordis.patch.yml +14 -10
  54. package/agent-codex/dist/adapter.js +64 -28
  55. package/agent-codex/dist/agent.js +255 -81
  56. package/agent-codex/dist/app-server-approvals.js +165 -0
  57. package/agent-codex/dist/app-server-client.js +723 -0
  58. package/agent-codex/dist/app-server-events.js +432 -0
  59. package/agent-codex/dist/app-server-protocol.js +95 -0
  60. package/agent-codex/dist/app-server-wire.js +299 -0
  61. package/agent-codex/dist/codex-launch.js +78 -0
  62. package/agent-codex/dist/codex-native-config.js +392 -0
  63. package/agent-codex/dist/codex-provider-gateway.js +402 -0
  64. package/agent-codex/dist/codex-providers.js +376 -0
  65. package/agent-codex/dist/codex-route.js +86 -0
  66. package/agent-codex/dist/index.js +256 -115
  67. package/agent-codex/dist/permission.js +75 -1
  68. package/agent-codex/package.json +1 -2
  69. package/agent-codex-sdk/README.md +79 -0
  70. package/agent-codex-sdk/cordis.patch.yml +81 -0
  71. package/agent-codex-sdk/dist/adapter.js +91 -0
  72. package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
  73. package/agent-codex-sdk/dist/agent.js +1085 -0
  74. package/agent-codex-sdk/dist/codex-native-config.js +392 -0
  75. package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
  76. package/agent-codex-sdk/dist/codex-providers.js +376 -0
  77. package/agent-codex-sdk/dist/codex-route.js +86 -0
  78. package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
  79. package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
  80. package/agent-codex-sdk/dist/codex-store.js +180 -0
  81. package/agent-codex-sdk/dist/inbox.js +53 -0
  82. package/agent-codex-sdk/dist/index.js +739 -0
  83. package/agent-codex-sdk/dist/permission.js +71 -0
  84. package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
  85. package/agent-codex-sdk/dist/session-map.js +167 -0
  86. package/agent-codex-sdk/dist/world-plugin.js +93 -0
  87. package/agent-codex-sdk/package.json +79 -0
  88. package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
  89. package/agent-omp/README.md +72 -55
  90. package/agent-omp/dist/agent.js +36 -11
  91. package/agent-omp/dist/index.js +92 -52
  92. package/agent-omp/dist/lazy-rpc.js +11 -4
  93. package/agent-omp/dist/omp-cli.js +114 -97
  94. package/agent-omp/dist/omp-store.js +12 -4
  95. package/agent-omp/dist/permission.js +70 -5
  96. package/agent-omp/dist/rpc-client.js +409 -0
  97. package/agent-omp/dist/world-plugin.js +36 -3
  98. package/agent-omp/package.json +2 -6
  99. package/agent-omp-sdk/README.md +103 -0
  100. package/agent-omp-sdk/cordis.patch.yml +108 -0
  101. package/agent-omp-sdk/dist/adapter.d.ts +21 -0
  102. package/agent-omp-sdk/dist/adapter.js +36 -0
  103. package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
  104. package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
  105. package/agent-omp-sdk/dist/agent.d.ts +64 -0
  106. package/agent-omp-sdk/dist/agent.js +1178 -0
  107. package/agent-omp-sdk/dist/discovery.d.ts +57 -0
  108. package/agent-omp-sdk/dist/discovery.js +116 -0
  109. package/agent-omp-sdk/dist/inbox.d.ts +28 -0
  110. package/agent-omp-sdk/dist/inbox.js +53 -0
  111. package/agent-omp-sdk/dist/index.d.ts +60 -0
  112. package/agent-omp-sdk/dist/index.js +632 -0
  113. package/agent-omp-sdk/dist/knobs.d.ts +34 -0
  114. package/agent-omp-sdk/dist/knobs.js +50 -0
  115. package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
  116. package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
  117. package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
  118. package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
  119. package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
  120. package/agent-omp-sdk/dist/mobile-boot.js +38 -0
  121. package/agent-omp-sdk/dist/models.d.ts +39 -0
  122. package/agent-omp-sdk/dist/models.js +299 -0
  123. package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
  124. package/agent-omp-sdk/dist/omp-cli.js +164 -0
  125. package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
  126. package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
  127. package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
  128. package/agent-omp-sdk/dist/omp-executable.js +30 -0
  129. package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
  130. package/agent-omp-sdk/dist/omp-store.js +526 -0
  131. package/agent-omp-sdk/dist/pairing.d.ts +7 -0
  132. package/agent-omp-sdk/dist/pairing.js +45 -0
  133. package/agent-omp-sdk/dist/permission.d.ts +138 -0
  134. package/agent-omp-sdk/dist/permission.js +161 -0
  135. package/agent-omp-sdk/dist/protocol.d.ts +189 -0
  136. package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
  137. package/agent-omp-sdk/dist/rpc-types.js +1 -0
  138. package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
  139. package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
  140. package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
  141. package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
  142. package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
  143. package/agent-omp-sdk/dist/sidecar-client.js +197 -0
  144. package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
  145. package/agent-omp-sdk/dist/store/adapter.js +47 -0
  146. package/agent-omp-sdk/dist/store/db.d.ts +70 -0
  147. package/agent-omp-sdk/dist/store/db.js +210 -0
  148. package/agent-omp-sdk/dist/store/index.d.ts +23 -0
  149. package/agent-omp-sdk/dist/store/index.js +59 -0
  150. package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
  151. package/agent-omp-sdk/dist/store/reconcile.js +51 -0
  152. package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
  153. package/agent-omp-sdk/dist/store/schema.js +40 -0
  154. package/agent-omp-sdk/dist/transport.d.ts +49 -0
  155. package/agent-omp-sdk/dist/transport.js +35 -0
  156. package/agent-omp-sdk/dist/world-home.d.ts +4 -0
  157. package/agent-omp-sdk/dist/world-home.js +23 -0
  158. package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
  159. package/agent-omp-sdk/dist/world-plugin.js +137 -0
  160. package/agent-omp-sdk/package.json +87 -0
  161. package/client/index.js +1883 -287
  162. package/cordis.patch.yml +80 -0
  163. package/package.json +23 -9
  164. package/superd-hub/dist/a2a-delivery.d.ts +189 -0
  165. package/superd-hub/dist/a2a-delivery.js +240 -0
  166. package/superd-hub/dist/a2a-host.d.ts +76 -0
  167. package/superd-hub/dist/a2a-host.js +145 -0
  168. package/superd-hub/dist/a2a-ingest.d.ts +31 -0
  169. package/superd-hub/dist/a2a-ingest.js +636 -0
  170. package/superd-hub/dist/a2a-log.d.ts +17 -0
  171. package/superd-hub/dist/a2a-log.js +42 -0
  172. package/superd-hub/dist/a2a-row.d.ts +65 -0
  173. package/superd-hub/dist/a2a-row.js +162 -0
  174. package/superd-hub/dist/a2a-service.d.ts +357 -0
  175. package/superd-hub/dist/a2a-service.js +980 -0
  176. package/superd-hub/dist/a2a-wire.d.ts +220 -0
  177. package/superd-hub/dist/a2a-wire.js +305 -0
  178. package/superd-hub/dist/agent-ids.d.ts +10 -0
  179. package/superd-hub/dist/agent-ids.js +68 -0
  180. package/superd-hub/dist/agent-roster.d.ts +29 -18
  181. package/superd-hub/dist/agent-roster.js +54 -15
  182. package/superd-hub/dist/agent-status.d.ts +119 -0
  183. package/superd-hub/dist/agent-status.js +386 -0
  184. package/superd-hub/dist/client/agent-settings.d.ts +210 -0
  185. package/superd-hub/dist/client/agent-settings.js +300 -0
  186. package/superd-hub/dist/client/mesh-api.d.ts +134 -0
  187. package/superd-hub/dist/client/mesh-api.js +219 -0
  188. package/superd-hub/dist/client/mesh-groups.d.ts +63 -0
  189. package/superd-hub/dist/client/mesh-groups.js +66 -0
  190. package/superd-hub/dist/client/roster.d.ts +48 -0
  191. package/superd-hub/dist/client/roster.js +102 -0
  192. package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
  193. package/superd-hub/dist/client/runtime-settings-api.js +137 -0
  194. package/superd-hub/dist/client/switch.d.ts +165 -0
  195. package/superd-hub/dist/client/switch.js +295 -0
  196. package/superd-hub/dist/exec-pty-channel.d.ts +58 -0
  197. package/superd-hub/dist/exec-pty-channel.js +162 -0
  198. package/superd-hub/dist/exec-pty-framing.d.ts +32 -0
  199. package/superd-hub/dist/exec-pty-framing.js +81 -0
  200. package/superd-hub/dist/exec-pty-rpc.d.ts +21 -0
  201. package/superd-hub/dist/exec-pty-rpc.js +128 -0
  202. package/superd-hub/dist/exec-pty-session.d.ts +76 -0
  203. package/superd-hub/dist/exec-pty-session.js +234 -0
  204. package/superd-hub/dist/exec.d.ts +56 -0
  205. package/superd-hub/dist/exec.js +225 -0
  206. package/superd-hub/dist/gateway.d.ts +39 -2
  207. package/superd-hub/dist/gateway.js +62 -3
  208. package/superd-hub/dist/hub-tool.d.ts +174 -0
  209. package/superd-hub/dist/hub-tool.js +464 -0
  210. package/superd-hub/dist/index.d.ts +15 -1
  211. package/superd-hub/dist/index.js +16 -0
  212. package/superd-hub/dist/machines.d.ts +45 -0
  213. package/superd-hub/dist/machines.js +125 -0
  214. package/superd-hub/dist/mesh-federate.d.ts +138 -0
  215. package/superd-hub/dist/mesh-federate.js +223 -0
  216. package/superd-hub/dist/mesh-gateway.d.ts +67 -0
  217. package/superd-hub/dist/mesh-gateway.js +408 -0
  218. package/superd-hub/dist/mesh-link.d.ts +274 -0
  219. package/superd-hub/dist/mesh-link.js +441 -0
  220. package/superd-hub/dist/mesh-rpc.d.ts +123 -0
  221. package/superd-hub/dist/mesh-rpc.js +250 -0
  222. package/superd-hub/dist/mux-client.d.ts +71 -0
  223. package/superd-hub/dist/mux-client.js +281 -0
  224. package/superd-hub/dist/pack-version.d.ts +2 -0
  225. package/superd-hub/dist/pack-version.js +30 -0
  226. package/superd-hub/dist/roster.d.ts +6 -0
  227. package/superd-hub/dist/rpc.d.ts +18 -2
  228. package/superd-hub/dist/rpc.js +34 -4
  229. package/superd-hub/dist/spawn-world.js +3 -1
  230. package/superd-hub/dist/switch-core.d.ts +62 -25
  231. package/superd-hub/dist/switch-core.js +53 -30
  232. package/superd-hub/dist/system-head.d.ts +22 -12
  233. package/superd-hub/dist/system-head.js +100 -84
  234. package/superd-hub/dist/wire-endpoints.d.ts +264 -0
  235. package/superd-hub/dist/wire-endpoints.js +180 -0
  236. package/superd-hub/lib/client/index.js +1883 -287
  237. package/superd-hub/package.json +11 -2
  238. package/agent-agy/dist/agent-preset-projection.d.ts +0 -31
  239. package/agent-agy/dist/agent-preset-projection.js +0 -33
  240. package/agent-claude/dist/agent-preset-projection.js +0 -30
  241. package/agent-codex/dist/agent-preset-projection.js +0 -37
  242. package/agent-codex/dist/app-home.js +0 -65
  243. package/agent-codex/dist/models.js +0 -152
  244. package/agent-codex/dist/spike.js +0 -46
  245. package/agent-hermes/dist/agent-preset-projection.js +0 -34
  246. package/agent-omp/dist/agent-preset-projection.js +0 -36
  247. package/agent-omp/dist/app-home.js +0 -86
  248. package/agent-omp/dist/sidecar-client.js +0 -195
  249. package/agent-pi/dist/agent-preset-projection.js +0 -29
  250. package/superd-hub/dist/client-shim.d.ts +0 -28
  251. package/superd-hub/dist/client-shim.js +0 -251
  252. package/superd-hub/dist/index-pass.d.ts +0 -22
  253. package/superd-hub/dist/index-pass.js +0 -47
  254. package/superd-hub/dist/labels.d.ts +0 -45
  255. package/superd-hub/dist/labels.js +0 -83
  256. /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
  257. /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
  258. /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
  259. /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
  260. /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
@@ -0,0 +1,526 @@
1
+ /**
2
+ * Read-only scanner over OMP's native session store
3
+ * (`$OMP_HOME/agent/sessions/<cwd-dashed>/<timestamp>_<uuid>.jsonl`).
4
+ *
5
+ * OMP owns the transcript format; this module extracts only what the Dash
6
+ * surfaces need per session — identity, cwd, creation time, and a display
7
+ * title — by reading a bounded HEAD of each JSONL file (never the full
8
+ * transcript). Parsed entries are memoized per file keyed on (size, mtime),
9
+ * so repeated `list()` calls re-stat but re-parse only changed files.
10
+ */
11
+ import { closeSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, statSync } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import { basename, dirname, join } from "node:path";
14
+ import { OMP_NATIVE_HOME, resolveOmpNativeHome } from "./knobs.js";
15
+ /** Root of OMP's native session store. */
16
+ export const OMP_SESSIONS_ROOT = process.env.OMP_SESSIONS_ROOT ?? join(OMP_NATIVE_HOME, "agent", "sessions");
17
+ /** How many head bytes of one session file the scanner will read. */
18
+ const HEAD_BUDGET_BYTES = 256 * 1024;
19
+ /** Title fallback budget (characters of the first user message). */
20
+ const FALLBACK_TITLE_CHARS = 80;
21
+ /** Memoized parse results, keyed by session file path. */
22
+ const entryCache = new Map();
23
+ /** Epoch ms from a `<YYYY>-<MM>-<DD>T<HH>-<MM>-<SS>-<mmm>Z` filename prefix. */
24
+ function createdAtFromFileName(name) {
25
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z_/.exec(name);
26
+ if (match === null)
27
+ return undefined;
28
+ const [year, month, day, hour, minute, second, ms] = match.slice(1).map(Number);
29
+ const epoch = Date.UTC(year, month - 1, day, hour, minute, second, ms);
30
+ return Number.isSafeInteger(epoch) ? epoch : undefined;
31
+ }
32
+ /** One normalized display-title fallback from a first user message. */
33
+ function fallbackTitle(text) {
34
+ const firstLine = text.split("\n", 1)[0] ?? "";
35
+ const collapsed = firstLine.replaceAll(/\s+/gu, " ").trim();
36
+ return collapsed.length === 0 ? undefined : collapsed.slice(0, FALLBACK_TITLE_CHARS);
37
+ }
38
+ /**
39
+ * Read one OMP session transcript in a single pass, extracting both the
40
+ * `message` records and the `model_change` records.
41
+ */
42
+ export function readOmpTranscript(path) {
43
+ const messages = [];
44
+ const modelChanges = [];
45
+ let buffer;
46
+ try {
47
+ buffer = readFileSync(path, "utf8");
48
+ }
49
+ catch {
50
+ return { messages, modelChanges };
51
+ }
52
+ for (const line of buffer.split("\n")) {
53
+ if (line === "")
54
+ continue;
55
+ let record;
56
+ try {
57
+ record = JSON.parse(line);
58
+ }
59
+ catch {
60
+ continue;
61
+ }
62
+ if (record === null || typeof record !== "object")
63
+ continue;
64
+ const rec = record;
65
+ if (rec["type"] === "message") {
66
+ const message = rec["message"];
67
+ if (message === null || typeof message !== "object")
68
+ continue;
69
+ const msg = message;
70
+ const role = msg["role"];
71
+ if (role !== "user" && role !== "assistant" && role !== "toolResult")
72
+ continue;
73
+ messages.push({ ...msg, role });
74
+ }
75
+ else if (rec["type"] === "model_change") {
76
+ const model = rec["model"];
77
+ if (typeof model !== "string" || model === "")
78
+ continue;
79
+ modelChanges.push({ model, role: typeof rec["role"] === "string" ? rec["role"] : undefined });
80
+ }
81
+ }
82
+ return { messages, modelChanges };
83
+ }
84
+ /**
85
+ * Legacy fallback: the last assistant message that names a provider/model
86
+ * pair. Used only for transcripts without any `model_change` record; newer
87
+ * sessions derive their resume model from {@link lastRestorableModel}.
88
+ */
89
+ export function lastModelCall(messages) {
90
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
91
+ const message = messages[index];
92
+ if (message.role !== "assistant")
93
+ continue;
94
+ const provider = typeof message.provider === "string" ? message.provider : "";
95
+ const model = typeof message.model === "string" ? message.model : "";
96
+ if (provider !== "" && model !== "")
97
+ return { provider, model };
98
+ }
99
+ return undefined;
100
+ }
101
+ /** Split a `provider/model[:variant]` selector into provider/model, dropping the variant suffix. */
102
+ export function parseSelector(selector) {
103
+ if (selector === undefined)
104
+ return undefined;
105
+ const slash = selector.indexOf("/");
106
+ if (slash <= 0)
107
+ return undefined;
108
+ let model = selector.slice(slash + 1);
109
+ const colon = model.indexOf(":");
110
+ if (colon > 0)
111
+ model = model.slice(0, colon);
112
+ return { provider: selector.slice(0, slash), model };
113
+ }
114
+ /**
115
+ * The model OMP restores on `--resume`: the last non-fallback `model_change`.
116
+ * Mirrors OMP's `getRestorableSessionModels` (`session-context.ts`): a
117
+ * fallback switch is recorded under `role:"fallback"` (EPHEMERAL) and never
118
+ * restores — the last such switch falls back to the role's primary.
119
+ */
120
+ export function lastRestorableModel(modelChanges) {
121
+ const models = {};
122
+ let lastRole;
123
+ for (const change of modelChanges) {
124
+ const role = change.role ?? "default";
125
+ models[role] = change.model;
126
+ lastRole = change.role;
127
+ }
128
+ const selector = lastRole === undefined || lastRole === "default" || lastRole === "fallback"
129
+ ? models["default"]
130
+ : (models[lastRole] ?? models["default"]);
131
+ return parseSelector(selector);
132
+ }
133
+ /**
134
+ * Path to OMP's per-user agent config (`<OMP_NATIVE_HOME>/agent/config.yml`).
135
+ *
136
+ * This is the OMP layer's own config, distinct from dsh's settings layer:
137
+ * upstream dsh keeps a home-level singleton settings document
138
+ * (`$DSH_HOME/settings.yaml`); the profile only composes bundles and has no
139
+ * per-profile settings isolation of its own. This bridge never reads that
140
+ * settings.yaml — OMP data lives under OMP_NATIVE_HOME, and the bridge's own
141
+ * index under `$DSH_HOME/bridge-store.sqlite` (see store/index.ts).
142
+ */
143
+ function ompConfigPath() {
144
+ // Per-call resolution (not the frozen knob const) so tests can inject the
145
+ // native root via OMP_NATIVE_HOME at any time.
146
+ return join(resolveOmpNativeHome(), "agent", "config.yml");
147
+ }
148
+ /**
149
+ * Read the whole `modelRoles` map (role → "provider/model[:effort]") from
150
+ * OMP's config.yml. Returns undefined when the block is absent/empty or the
151
+ * file is unreadable. A deliberately minimal YAML scan — the bridge has no
152
+ * YAML dependency; one indent level of `key: value` lines inside the block.
153
+ */
154
+ export function readOmpModelRolesFromConfig() {
155
+ let text;
156
+ try {
157
+ text = readFileSync(ompConfigPath(), "utf8");
158
+ }
159
+ catch {
160
+ return undefined;
161
+ }
162
+ const roles = {};
163
+ let inModelRoles = false;
164
+ for (const line of text.split("\n")) {
165
+ if (/^modelRoles:\s*$/.test(line)) {
166
+ inModelRoles = true;
167
+ continue;
168
+ }
169
+ if (!inModelRoles)
170
+ continue;
171
+ if (line !== "" && !/^\s/.test(line))
172
+ break; // left the modelRoles block
173
+ const match = /^\s*([A-Za-z0-9_-]+):\s*(\S+)\s*$/.exec(line);
174
+ if (match !== null)
175
+ roles[match[1]] = match[2];
176
+ }
177
+ return Object.keys(roles).length > 0 ? roles : undefined;
178
+ }
179
+ /**
180
+ * Read the configured default role model (`modelRoles.default`) from OMP's
181
+ * config.yml. Returns the provider/model pair (variant suffix dropped), or
182
+ * undefined when unset or unreadable. Delegates to the whole-map reader.
183
+ */
184
+ export function readOmpDefaultModelFromConfig() {
185
+ const def = readOmpModelRolesFromConfig()?.default;
186
+ return def === undefined ? undefined : parseSelector(def);
187
+ }
188
+ /**
189
+ * Derive the working directory a session ran in from its location in OMP's
190
+ * store: the parent directory name is the cwd with every "/" flattened to
191
+ * "-" (leading "/" → leading "-"). Literal dashes in path segments are
192
+ * ambiguous, so segments are re-joined greedily longest-first against the
193
+ * filesystem and the candidate is accepted only as an existing directory
194
+ * (realpath'd). This is the trusted resume cwd — the omp-sessions.json
195
+ * mapping's verbatim `cwd` is user-writable and never consulted.
196
+ */
197
+ export function cwdFromSessionFile(path) {
198
+ const dashed = basename(dirname(path));
199
+ if (!dashed.startsWith("-"))
200
+ return undefined;
201
+ const tokens = dashed.slice(1).split("-").filter((token) => token !== "");
202
+ if (tokens.length === 0)
203
+ return undefined;
204
+ // OMP flattens the cwd's "/" separators to "-"; home-relative cwds lose
205
+ // their "~/" prefix entirely ("-workspaces-x" ≡ ~/workspaces/x), absolute
206
+ // ones keep the leading slash ("-tmp-x" ≡ /tmp/x). Try the absolute base
207
+ // first, then home; literal dashes in segment names are ambiguous, so each
208
+ // walk consumes the LONGEST run of remaining tokens that names an existing
209
+ // directory (dash-joined), which resolves the common cases exactly.
210
+ for (const base of ["/", homedir()]) {
211
+ let current = base === "/" ? "" : base;
212
+ let consumed = 0;
213
+ let complete = true;
214
+ while (consumed < tokens.length) {
215
+ let stepped = false;
216
+ for (let take = tokens.length - consumed; take >= 1; take--) {
217
+ const candidate = `${current}/${tokens.slice(consumed, consumed + take).join("-")}`;
218
+ try {
219
+ if (!statSync(candidate).isDirectory())
220
+ continue;
221
+ }
222
+ catch {
223
+ continue;
224
+ }
225
+ current = candidate;
226
+ consumed += take;
227
+ stepped = true;
228
+ break;
229
+ }
230
+ if (!stepped) {
231
+ complete = false;
232
+ break;
233
+ }
234
+ }
235
+ if (complete) {
236
+ try {
237
+ return realpathSync(current);
238
+ }
239
+ catch {
240
+ // try the next base
241
+ }
242
+ }
243
+ }
244
+ return undefined;
245
+ }
246
+ /** Parse the interesting head records of one OMP session file. */
247
+ function scanHead(path) {
248
+ const out = {};
249
+ let buffer = "";
250
+ let remaining = HEAD_BUDGET_BYTES;
251
+ const chunks = [];
252
+ const fd = openSync(path, "r");
253
+ try {
254
+ while (remaining > 0) {
255
+ const chunk = Buffer.allocUnsafe(Math.min(remaining, 64 * 1024));
256
+ const read = readSync(fd, chunk, 0, chunk.length, null);
257
+ if (read === 0)
258
+ break;
259
+ chunks.push(chunk.subarray(0, read));
260
+ remaining -= read;
261
+ }
262
+ }
263
+ finally {
264
+ closeSync(fd);
265
+ }
266
+ buffer = Buffer.concat(chunks).toString("utf8");
267
+ for (const line of buffer.split("\n")) {
268
+ let record;
269
+ try {
270
+ record = JSON.parse(line);
271
+ }
272
+ catch {
273
+ continue;
274
+ }
275
+ if (record === null || typeof record !== "object")
276
+ continue;
277
+ const rec = record;
278
+ if (rec["type"] === "title" || rec["type"] === "title_change") {
279
+ // Latest wins: OMP renames titles over a session's life (replans), and
280
+ // the scan must surface the name the TUI shows today, not the first.
281
+ const title = rec["title"];
282
+ if (typeof title === "string" && title.trim().length > 0)
283
+ out.title = title.trim();
284
+ }
285
+ else if (rec["type"] === "session" && out.ompSessionId === undefined) {
286
+ const id = rec["id"];
287
+ const timestamp = rec["timestamp"];
288
+ const cwd = rec["cwd"];
289
+ if (typeof id === "string" && id.length > 0)
290
+ out.ompSessionId = id;
291
+ if (typeof timestamp === "string") {
292
+ const epoch = Date.parse(timestamp);
293
+ if (Number.isSafeInteger(epoch))
294
+ out.createdAt = epoch;
295
+ }
296
+ if (typeof cwd === "string" && cwd.length > 0)
297
+ out.cwd = cwd;
298
+ }
299
+ else if (rec["type"] === "message" && out.firstUserText === undefined) {
300
+ const message = rec["message"];
301
+ if (message === null || typeof message !== "object")
302
+ continue;
303
+ const msg = message;
304
+ if (msg["role"] !== "user" || (msg["attribution"] !== undefined && msg["attribution"] !== "user"))
305
+ continue;
306
+ const content = msg["content"];
307
+ if (!Array.isArray(content))
308
+ continue;
309
+ for (const block of content) {
310
+ if (block === null || typeof block !== "object")
311
+ continue;
312
+ const b = block;
313
+ if (b["type"] === "text" && typeof b["text"] === "string" && b["text"].trim().length > 0) {
314
+ out.firstUserText = b["text"];
315
+ break;
316
+ }
317
+ }
318
+ }
319
+ }
320
+ return out;
321
+ }
322
+ /** Read one session file's header id (the `session` record's `id`), no full scan. */
323
+ export function sessionHeaderId(path) {
324
+ return scanHead(path).ompSessionId;
325
+ }
326
+ /**
327
+
328
+ /**
329
+ * Scan OMP's native store (PURE DISK — sidecar-free). Returns sessions keyed
330
+ * by OMP session id; files without a parsable `session` header record are
331
+ * skipped (corrupt/foreign).
332
+ *
333
+ * The FILE LIST always comes from a fresh directory walk; per-session metadata
334
+ * (id/cwd/title/createdAt) comes from the self-written {@link scanHead},
335
+ * memoized by size+mtime in `entryCache`. The old SDK `sessions.listAll` fast
336
+ * path is GONE (sidecar-decoupling): the only consumer left is pairing.ts's
337
+ * store-absent fallback (unit tests).
338
+ */
339
+ export function scanOmpSessions() {
340
+ const sessions = new Map();
341
+ let workspaceDirs;
342
+ try {
343
+ workspaceDirs = readdirSync(OMP_SESSIONS_ROOT, { withFileTypes: true })
344
+ .filter((entry) => entry.isDirectory())
345
+ .map((entry) => entry.name);
346
+ }
347
+ catch {
348
+ return sessions;
349
+ }
350
+ for (const dirName of workspaceDirs) {
351
+ let files;
352
+ try {
353
+ files = readdirSync(join(OMP_SESSIONS_ROOT, dirName));
354
+ }
355
+ catch {
356
+ continue;
357
+ }
358
+ for (const fileName of files) {
359
+ if (!fileName.endsWith(".jsonl"))
360
+ continue;
361
+ const path = join(OMP_SESSIONS_ROOT, dirName, fileName);
362
+ let size;
363
+ let mtimeMs;
364
+ try {
365
+ const stats = statSync(path);
366
+ size = stats.size;
367
+ mtimeMs = stats.mtimeMs;
368
+ }
369
+ catch {
370
+ entryCache.delete(path);
371
+ continue;
372
+ }
373
+ let cached = entryCache.get(path);
374
+ if (cached === undefined || cached.size !== size || cached.mtimeMs !== mtimeMs) {
375
+ const head = scanHead(path);
376
+ const parsed = head.ompSessionId === undefined
377
+ ? undefined
378
+ : {
379
+ ompSessionId: head.ompSessionId,
380
+ ompSessionFile: path,
381
+ size,
382
+ mtimeMs,
383
+ ...(head.cwd === undefined ? {} : { cwd: head.cwd }),
384
+ createdAt: head.createdAt ?? createdAtFromFileName(fileName) ?? 0,
385
+ ...(head.title !== undefined
386
+ ? { title: head.title }
387
+ : head.firstUserText !== undefined
388
+ ? { title: fallbackTitle(head.firstUserText) }
389
+ : {}),
390
+ revision: `omp:${size}:${mtimeMs}`,
391
+ };
392
+ cached = { size, mtimeMs, entry: parsed };
393
+ entryCache.set(path, cached);
394
+ }
395
+ const entry = cached.entry;
396
+ if (entry !== undefined && !sessions.has(entry.ompSessionId)) {
397
+ sessions.set(entry.ompSessionId, entry);
398
+ }
399
+ }
400
+ }
401
+ return sessions;
402
+ }
403
+ /** Parent pid of a process, from `/proc/<pid>/stat` (undefined when unreadable). */
404
+ function parentPid(pid) {
405
+ try {
406
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
407
+ // The comm field may contain spaces and parens; fields resume after the LAST ')'.
408
+ const tail = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
409
+ const ppid = Number.parseInt(tail[1] ?? "", 10);
410
+ return Number.isNaN(ppid) ? undefined : ppid;
411
+ }
412
+ catch {
413
+ return undefined;
414
+ }
415
+ }
416
+ /** Whether `pid` sits anywhere below `ancestor` in the process tree. */
417
+ function isDescendantOf(pid, ancestor) {
418
+ let current = pid;
419
+ for (let hop = 0; hop < 32; hop += 1) {
420
+ if (current === ancestor)
421
+ return true;
422
+ const parent = parentPid(current);
423
+ if (parent === undefined || parent <= 1)
424
+ return false;
425
+ current = parent;
426
+ }
427
+ return false;
428
+ }
429
+ /**
430
+ * One /proc pass over every foreign process's write-mode file descriptors,
431
+ * returning `canonical path → first foreign writer pid`. Shared by every
432
+ * session in one tick so the daemon never re-scans /proc per file.
433
+ *
434
+ * OMP has no session-level exclusivity: neither the TUI nor `--mode rpc`
435
+ * locks its transcript, so two live writers interleave appends silently
436
+ * (verified: a TUI `omp` and a bridge `omp --mode rpc --resume` coexisted on
437
+ * one file with plain `w` descriptors and no advisory locks). The intended
438
+ * enforcement is to refuse resuming a transcript another process already writes
439
+ * (the TUI case), closing the split-brain at the only layer that can see it —
440
+ * but see {@link foreignWriterPid}: that hold is NOT wired in this round.
441
+ * Own descendants (a just-torn-down bridge child whose fds linger) are
442
+ * exempt to keep teardown races from wedging re-resume.
443
+ */
444
+ export function scanForeignWriters() {
445
+ const holders = new Map();
446
+ let procEntries;
447
+ try {
448
+ procEntries = readdirSync("/proc");
449
+ }
450
+ catch {
451
+ return holders;
452
+ }
453
+ const self = process.pid;
454
+ for (const entry of procEntries) {
455
+ if (!/^[0-9]+$/.test(entry))
456
+ continue;
457
+ const pid = Number.parseInt(entry, 10);
458
+ if (pid === self || isDescendantOf(pid, self))
459
+ continue;
460
+ let fds;
461
+ try {
462
+ fds = readdirSync(`/proc/${entry}/fd`);
463
+ }
464
+ catch {
465
+ continue;
466
+ }
467
+ for (const fd of fds) {
468
+ let target;
469
+ try {
470
+ target = readlinkSync(`/proc/${entry}/fd/${fd}`);
471
+ }
472
+ catch {
473
+ continue;
474
+ }
475
+ // Non-path targets (socket:[…], pipe:[…], anon_inode:[…]) can never be a session file.
476
+ if (!target.startsWith("/"))
477
+ continue;
478
+ let flags;
479
+ try {
480
+ const info = readFileSync(`/proc/${entry}/fdinfo/${fd}`, "utf8");
481
+ const flagLine = info.split("\n").find((line) => line.startsWith("flags:"));
482
+ const raw = flagLine?.split(":")[1]?.trim();
483
+ flags = raw === undefined ? undefined : Number.parseInt(raw, 8);
484
+ }
485
+ catch {
486
+ continue;
487
+ }
488
+ if (flags === undefined || Number.isNaN(flags))
489
+ continue;
490
+ if ((flags & 0b11) === 0)
491
+ continue; // neither O_WRONLY nor O_RDWR
492
+ let canonical;
493
+ try {
494
+ canonical = realpathSync(target);
495
+ }
496
+ catch {
497
+ continue;
498
+ }
499
+ if (!holders.has(canonical))
500
+ holders.set(canonical, pid);
501
+ }
502
+ }
503
+ return holders;
504
+ }
505
+ /**
506
+ * Pid of a FOREIGN process holding `file` open for writing, if any.
507
+ * Convenience wrapper over {@link scanForeignWriters} for the single-file
508
+ * resume gate.
509
+ *
510
+ * UNWIRED (2026-10-03): this helper has no caller — the `resume` path never
511
+ * invokes it, so nothing currently refuses a transcript another process (the
512
+ * TUI) is writing. Spec §3.4 of `docs/specs/omp-rpc-transport/spec.md`
513
+ * registers that accepted exposure and the planned follow-up: a fail-loud
514
+ * `foreignWriterPid(<sessionFile>)` check wired into `resume`, to be decided
515
+ * by the operator. Helper kept exported for that follow-up.
516
+ */
517
+ export function foreignWriterPid(file) {
518
+ let canonical;
519
+ try {
520
+ canonical = realpathSync(file);
521
+ }
522
+ catch {
523
+ return undefined;
524
+ }
525
+ return scanForeignWriters().get(canonical);
526
+ }
@@ -0,0 +1,7 @@
1
+ import { type OmpNativeSession } from "./omp-store.js";
2
+ /**
3
+ * Resolve an id from the Dash side back to its session entry. Indexed lookup
4
+ * (byDshId / byOmpId) — never a full scan. Falls back to the scanner only
5
+ * when the store is absent (unit tests running the supervisor in isolation).
6
+ */
7
+ export declare function resolveEntryById(id: string): OmpNativeSession | undefined;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Dash↔OMP session id pairing, backed by the centralized index (DB).
3
+ *
4
+ * The index is the sole pairing authority (D3): bridge-created sessions carry
5
+ * their real Dash id (migrated from webui.json or written at createAgent);
6
+ * every other OMP session gets a deterministic derived id (`session-` + the
7
+ * OMP uuidv7, whose version nibble 7 ≠ 4 keeps it collision-free against
8
+ * apiproxy-minted ids). All id translation is now an O(1) indexed lookup —
9
+ * no webui.json read/write, no full-store scan.
10
+ */
11
+ import { getBridgeStore } from "./store/index.js";
12
+ import { scanOmpSessions } from "./omp-store.js";
13
+ /** The Dash-format prefix apiproxy mints; derived ids reuse it verbatim. */
14
+ const DASH_PREFIX = "session-";
15
+ /** Reconstruct a scanned-entry-shaped view from an index row. */
16
+ function rowToEntry(row) {
17
+ return {
18
+ ompSessionId: row.omp_session_id,
19
+ ompSessionFile: row.session_file,
20
+ ...(row.cwd === null ? {} : { cwd: row.cwd }),
21
+ createdAt: row.created_at,
22
+ ...(row.title === null ? {} : { title: row.title }),
23
+ size: row.transcript_size,
24
+ mtimeMs: row.last_modified_at,
25
+ revision: `omp:${row.transcript_size}:${row.last_modified_at}`,
26
+ };
27
+ }
28
+ /**
29
+ * Resolve an id from the Dash side back to its session entry. Indexed lookup
30
+ * (byDshId / byOmpId) — never a full scan. Falls back to the scanner only
31
+ * when the store is absent (unit tests running the supervisor in isolation).
32
+ */
33
+ export function resolveEntryById(id) {
34
+ const store = getBridgeStore();
35
+ if (store !== undefined) {
36
+ const row = id.startsWith(DASH_PREFIX) ? store.byDshId(id) : store.byOmpId(id);
37
+ return row === undefined ? undefined : rowToEntry(row);
38
+ }
39
+ if (id.startsWith(DASH_PREFIX)) {
40
+ const stripped = id.slice(DASH_PREFIX.length);
41
+ const direct = scanOmpSessions().get(stripped);
42
+ return direct;
43
+ }
44
+ return scanOmpSessions().get(id);
45
+ }