super-dsh 0.1.9 → 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 (178) 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 +1204 -93
  162. package/cordis.patch.yml +64 -0
  163. package/package.json +17 -8
  164. package/superd-hub/dist/client/agent-settings.d.ts +210 -0
  165. package/superd-hub/dist/client/agent-settings.js +300 -0
  166. package/superd-hub/dist/client/roster.js +6 -0
  167. package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
  168. package/superd-hub/dist/client/runtime-settings-api.js +137 -0
  169. package/superd-hub/dist/client/switch.d.ts +165 -0
  170. package/superd-hub/dist/client/switch.js +295 -0
  171. package/superd-hub/lib/client/index.js +1204 -93
  172. package/agent-codex/dist/models.js +0 -152
  173. package/agent-omp/dist/sidecar-client.js +0 -195
  174. /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
  175. /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
  176. /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
  177. /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
  178. /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Node-side client for the omp-sdk bun sidecar.
3
+ *
4
+ * The sidecar is NOT shipped as a runtime: this package declares no `bun` and
5
+ * no coding-agent dependency. `start()` is therefore the ONE place the lazy
6
+ * runtime bootstrap is awaited ({@link ensureSdkRuntime}) — it downloads bun +
7
+ * the pinned coding-agent package into `<worldHome>/sdk-runtime/` and copies
8
+ * this package's `sidecar/main.ts` + compiled `dist/protocol.js` there, then
9
+ * the child is spawned with `<runtime>/bin/bun run <runtime>/sidecar/main.ts`.
10
+ *
11
+ * Because `start()` is only reached from the first real dispatch (via
12
+ * `LazyOmpRpc#ensure`), construction — and every passive path — stays at zero
13
+ * network, zero download and zero process.
14
+ */
15
+ import { spawn } from "node:child_process";
16
+ import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import { createSdkRuntimeDeps, ensureSdkRuntime, sdkRuntimeLayout } from "./sdk-runtime.js";
19
+ import { PROTOCOL_VERSION, } from "./protocol.js";
20
+ /** This package's root (`sidecar/`, `dist/`, `package.json` live here). */
21
+ const pkgDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
22
+ /** The default fetcher: the real bun download + `bun add` install + smoke. */
23
+ const defaultRuntimeFetch = (worldHome) => ensureSdkRuntime(worldHome, createSdkRuntimeDeps(pkgDir));
24
+ /** The default child spawn: the fetched `bin/bun run <runtime>/sidecar/main.ts`. */
25
+ const defaultSpawn = (command, args, options) => spawn(command, args, { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
26
+ export class OmpSdkSidecar {
27
+ options;
28
+ proc = null;
29
+ nextId = 1;
30
+ pending = new Map();
31
+ eventHandlers = new Set();
32
+ readyPromise = null;
33
+ buffer = "";
34
+ startError = null;
35
+ constructor(options) {
36
+ this.options = options;
37
+ }
38
+ get started() {
39
+ return this.proc !== null;
40
+ }
41
+ onEvent(handler) {
42
+ this.eventHandlers.add(handler);
43
+ return () => this.eventHandlers.delete(handler);
44
+ }
45
+ /**
46
+ * Fetch/reuse the durable runtime and spawn the sidecar on it.
47
+ *
48
+ * This is the FIRST and ONLY place the runtime bootstrap runs — the whole
49
+ * point of the opt-in SDK row is that nothing is downloaded until a real
50
+ * prompt materializes the transport.
51
+ */
52
+ async start() {
53
+ if (this.readyPromise)
54
+ return this.readyPromise;
55
+ const fetchRuntime = this.options.runtimeFetch ?? defaultRuntimeFetch;
56
+ this.readyPromise = (async () => {
57
+ // (1) durable runtime (idempotent: a complete matching one is free).
58
+ const runtime = await fetchRuntime(this.options.worldHome);
59
+ // (2) the sidecar source + compiled protocol live INSIDE the runtime dir,
60
+ // so bun resolves `@oh-my-pi/pi-coding-agent` from the runtime's
61
+ // node_modules and `../dist/protocol.js` from the copy next to it.
62
+ const layout = sdkRuntimeLayout(this.options.worldHome);
63
+ const cwd = this.options.cwd ?? layout.dir;
64
+ return await new Promise((resolve, reject) => {
65
+ const env = {
66
+ ...process.env,
67
+ ...this.options.env,
68
+ };
69
+ let proc;
70
+ try {
71
+ proc = (this.options.spawnProcess ?? defaultSpawn)(runtime.bunPath, ["run", layout.sidecarEntry], {
72
+ cwd,
73
+ env,
74
+ });
75
+ }
76
+ catch (err) {
77
+ reject(new Error(`failed to spawn bun sidecar: ${err.message}`));
78
+ return;
79
+ }
80
+ this.proc = proc;
81
+ const readyTimeoutMs = this.options.readyTimeoutMs ?? 60_000;
82
+ const timeout = setTimeout(() => reject(new Error(`sidecar ready timeout (${readyTimeoutMs}ms): ${this.startError ?? "no output"}`)), readyTimeoutMs);
83
+ const settle = (fn) => { clearTimeout(timeout); fn(); };
84
+ // A dead sidecar surfaces as EPIPE on stdin writes; keep a permanent
85
+ // no-op error sink so no window exists where the stream throws out of the
86
+ // event loop (per-call degradation is handled by #writeSafe).
87
+ proc.stdin.on("error", () => { });
88
+ proc.stdout.setEncoding("utf8");
89
+ proc.stdout.on("data", (chunk) => this.consume(chunk, (r) => settle(() => resolve(r))));
90
+ proc.stderr.setEncoding("utf8");
91
+ proc.stderr.on("data", (chunk) => {
92
+ this.startError = (this.startError ?? "") + chunk;
93
+ process.stderr.write(`[omp-sdk-sidecar] ${chunk}`);
94
+ });
95
+ proc.on("exit", (code, signal) => {
96
+ settle(() => reject(new Error(`sidecar exited before ready (${code ?? signal}): ${this.startError ?? ""}`)));
97
+ for (const [, p] of this.pending)
98
+ p.reject(new Error(`sidecar exited (${code ?? signal})`));
99
+ this.pending.clear();
100
+ this.proc = null;
101
+ });
102
+ });
103
+ })();
104
+ return this.readyPromise;
105
+ }
106
+ consume(chunk, onReady) {
107
+ this.buffer += chunk;
108
+ let nl;
109
+ while ((nl = this.buffer.indexOf("\n")) >= 0) {
110
+ const raw = this.buffer.slice(0, nl).trim();
111
+ this.buffer = this.buffer.slice(nl + 1);
112
+ if (!raw)
113
+ continue;
114
+ let frame;
115
+ try {
116
+ frame = JSON.parse(raw);
117
+ }
118
+ catch {
119
+ continue;
120
+ }
121
+ if (frame.event === "ready") {
122
+ onReady?.({ protocol: frame.payload?.protocol ?? PROTOCOL_VERSION });
123
+ continue;
124
+ }
125
+ if (typeof frame.id === "number" && ("ok" in frame)) {
126
+ const p = this.pending.get(frame.id);
127
+ if (p) {
128
+ this.pending.delete(frame.id);
129
+ if (frame.ok)
130
+ p.resolve(frame.result);
131
+ else
132
+ p.reject(new Error(frame.error));
133
+ }
134
+ continue;
135
+ }
136
+ if (frame.event) {
137
+ for (const h of this.eventHandlers)
138
+ h(frame);
139
+ }
140
+ }
141
+ }
142
+ call(method, params) {
143
+ if (!this.proc?.stdin)
144
+ return Promise.reject(new Error("sidecar not started"));
145
+ const id = this.nextId++;
146
+ const frame = { id, method, params };
147
+ return new Promise((resolve, reject) => {
148
+ this.pending.set(id, { resolve, reject });
149
+ this.#writeSafe(JSON.stringify(frame) + "\n", () => {
150
+ this.pending.delete(id);
151
+ reject(new Error(`sidecar write failed: ${method}`));
152
+ });
153
+ });
154
+ }
155
+ /** Write to the sidecar's stdin without ever throwing EPIPE out of the loop. */
156
+ #writeSafe(chunk, onLost) {
157
+ const stdin = this.proc?.stdin;
158
+ if (!stdin)
159
+ return onLost();
160
+ const fail = (err) => {
161
+ // EPIPE/ECONNRESET when the sidecar died between checks — a lost frame
162
+ // must degrade the caller, never crash the host process.
163
+ stdin.removeListener("error", fail);
164
+ try {
165
+ stdin.destroy();
166
+ }
167
+ catch { /* already gone */ }
168
+ onLost();
169
+ };
170
+ stdin.once("error", fail);
171
+ try {
172
+ stdin.write(chunk, () => stdin.removeListener("error", fail));
173
+ }
174
+ catch {
175
+ fail(new Error("write threw"));
176
+ }
177
+ }
178
+ notify(method, params) {
179
+ if (!this.proc?.stdin)
180
+ return;
181
+ this.#writeSafe(JSON.stringify({ method, params }) + "\n", () => { });
182
+ }
183
+ async stop() {
184
+ const proc = this.proc;
185
+ if (!proc)
186
+ return;
187
+ this.proc = null;
188
+ await new Promise((resolve) => {
189
+ proc.once("exit", () => resolve());
190
+ proc.stdin.end();
191
+ setTimeout(() => {
192
+ proc.kill("SIGTERM");
193
+ setTimeout(() => proc.kill("SIGKILL"), 3000);
194
+ }, 3000).unref?.();
195
+ });
196
+ }
197
+ }
@@ -0,0 +1,17 @@
1
+ /** A prepared statement: run / get / all with positional params. */
2
+ export interface SqliteStatement {
3
+ run(...params: unknown[]): {
4
+ changes: number | bigint;
5
+ lastInsertRowid: number | bigint;
6
+ };
7
+ get(...params: unknown[]): Record<string, unknown> | undefined;
8
+ all(...params: unknown[]): Record<string, unknown>[];
9
+ }
10
+ /** A SQLite connection, normalized across drivers. */
11
+ export interface SqliteDb {
12
+ exec(sql: string): void;
13
+ prepare(sql: string): SqliteStatement;
14
+ close(): void;
15
+ }
16
+ /** Open a SQLite connection on whichever builtin the runtime provides. */
17
+ export declare function openDb(path: string): SqliteDb;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * SQLite driver adapter — one thin interface over whichever builtin the
3
+ * runtime ships. Bun → `bun:sqlite`; Node (≥22.5) → `node:sqlite`. The store
4
+ * never cares which: it sees exec / prepare→(run|get|all) / close.
5
+ *
6
+ * Both builtins load through `createRequire` (never a static ESM import) so
7
+ * neither `bun:` nor `node:sqlite` is resolved at module load: under Node the
8
+ * `bun:` branch is dead code, under Bun the `node:sqlite` branch is dead code.
9
+ */
10
+ import { createRequire } from "node:module";
11
+ const nodeRequire = createRequire(import.meta.url);
12
+ function openNode(path) {
13
+ const { DatabaseSync: Driver } = nodeRequire("node:sqlite");
14
+ const db = new Driver(path);
15
+ return {
16
+ exec: (sql) => db.exec(sql),
17
+ prepare: (sql) => {
18
+ const statement = db.prepare(sql);
19
+ return {
20
+ run: (...params) => statement.run(...params),
21
+ get: (...params) => statement.get(...params),
22
+ all: (...params) => statement.all(...params),
23
+ };
24
+ },
25
+ close: () => db.close(),
26
+ };
27
+ }
28
+ function openBun(path) {
29
+ const { Database: Driver } = nodeRequire("bun:sqlite");
30
+ const db = new Driver(path, { create: true });
31
+ return {
32
+ exec: (sql) => db.exec(sql),
33
+ prepare: (sql) => {
34
+ const query = db.query(sql);
35
+ return {
36
+ run: (...params) => query.run(...params),
37
+ get: (...params) => query.get(...params),
38
+ all: (...params) => query.all(...params),
39
+ };
40
+ },
41
+ close: () => db.close(),
42
+ };
43
+ }
44
+ /** Open a SQLite connection on whichever builtin the runtime provides. */
45
+ export function openDb(path) {
46
+ return globalThis.Bun !== undefined ? openBun(path) : openNode(path);
47
+ }
@@ -0,0 +1,70 @@
1
+ /** One row of the `sessions` table (snake_case, matching SQL columns 1:1). */
2
+ export interface SessionRow {
3
+ omp_session_id: string;
4
+ dsh_session_id: string;
5
+ session_file: string;
6
+ cwd: string | null;
7
+ title: string | null;
8
+ created_at: number;
9
+ last_modified_at: number;
10
+ transcript_size: number;
11
+ model_provider: string | null;
12
+ model_id: string | null;
13
+ agent_preset: string | null;
14
+ permission_preset: string | null;
15
+ forked_from: string | null;
16
+ archived: number;
17
+ last_visited_at: number | null;
18
+ }
19
+ /** One row of the `workspaces` table. */
20
+ export interface WorkspaceRow {
21
+ workspace_id: string;
22
+ title: string | null;
23
+ manual_order: string | null;
24
+ }
25
+ /** The fallback workspace id for sessions whose cwd is missing/corrupt (D8). */
26
+ export declare const UNGROUPED_WORKSPACE = "ungrouped";
27
+ export declare class BridgeStore {
28
+ private readonly db;
29
+ private constructor();
30
+ /** Open (WAL + schema + integrity check). Throws on a corrupt/invalid database. */
31
+ static open(path: string): BridgeStore;
32
+ close(): void;
33
+ private all;
34
+ private get;
35
+ private run;
36
+ /** All session rows, default `last_modified_at DESC`, manual mode per-workspace order. */
37
+ list(): SessionRow[];
38
+ /** All workspace rows. */
39
+ workspaces(): WorkspaceRow[];
40
+ byDshId(dshSessionId: string): SessionRow | undefined;
41
+ byOmpId(ompSessionId: string): SessionRow | undefined;
42
+ byFile(sessionFile: string): SessionRow | undefined;
43
+ /** Full-row upsert, keyed by `omp_session_id`. */
44
+ upsert(row: SessionRow): void;
45
+ /** Drop every row whose session file is not in `keepFiles`. */
46
+ prune(keepFiles: Set<string>): void;
47
+ /** Migration only: overwrite the DSH pairing for one OMP session. */
48
+ setDshId(ompSessionId: string, dshSessionId: string): void;
49
+ setPermissionPreset(ompSessionId: string, preset: string | null): void;
50
+ setArchived(dshSessionId: string, archived: boolean): void;
51
+ setForkedFrom(dshSessionId: string, forkedFrom: string | null): void;
52
+ touchVisited(dshSessionId: string, at: number): void;
53
+ /** Persist a workspace's manual ordering (upsert the workspace row first). */
54
+ setWorkspaceOrder(workspaceId: string, order: string[]): void;
55
+ /** Ensure the `ungrouped` sentinel workspace row exists (D8). */
56
+ ensureUngroupedWorkspace(): void;
57
+ getUiState(key: string): string | undefined;
58
+ setUiState(key: string, value: string): void;
59
+ /** The tracked OMP default model (`modelRoles.default`), or undefined when unset. */
60
+ getOmpDefaultModel(): {
61
+ provider: string;
62
+ model: string;
63
+ } | undefined;
64
+ /** Persist the tracked OMP default model. */
65
+ setOmpDefaultModel(provider: string, model: string): void;
66
+ /** Event-driven model update after a live model switch (D5.3). */
67
+ updateModel(dshSessionId: string, provider: string | null, model: string | null): void;
68
+ /** Event-driven stat refresh on teardown/cold transition (D5.3). */
69
+ updateStat(dshSessionId: string, size: number, mtimeMs: number): void;
70
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * `BridgeStore` — the bridge's centralized session index over SQLite.
3
+ *
4
+ * Owns the schema, the query interface (list / byDshId / byOmpId / byFile /
5
+ * upsert / prune + UI setters), and the connection lifecycle (WAL, integrity
6
+ * check). The driver behind it is abstracted by `./adapter.js`; this class
7
+ * only talks to the normalized `SqliteDb` surface.
8
+ */
9
+ import { openDb } from "./adapter.js";
10
+ import { SCHEMA_SQL } from "./schema.js";
11
+ /** The fallback workspace id for sessions whose cwd is missing/corrupt (D8). */
12
+ export const UNGROUPED_WORKSPACE = "ungrouped";
13
+ const SESSIONS_COLUMNS = [
14
+ "omp_session_id",
15
+ "dsh_session_id",
16
+ "session_file",
17
+ "cwd",
18
+ "title",
19
+ "created_at",
20
+ "last_modified_at",
21
+ "transcript_size",
22
+ "model_provider",
23
+ "model_id",
24
+ "agent_preset",
25
+ "permission_preset",
26
+ "forked_from",
27
+ "archived",
28
+ "last_visited_at",
29
+ ];
30
+ const UPSERT_SQL = `INSERT INTO sessions (${SESSIONS_COLUMNS.join(", ")})
31
+ VALUES (${SESSIONS_COLUMNS.map(() => "?").join(", ")})
32
+ ON CONFLICT(omp_session_id) DO UPDATE SET
33
+ dsh_session_id = excluded.dsh_session_id,
34
+ session_file = excluded.session_file,
35
+ cwd = excluded.cwd,
36
+ title = excluded.title,
37
+ created_at = excluded.created_at,
38
+ last_modified_at = excluded.last_modified_at,
39
+ transcript_size = excluded.transcript_size,
40
+ model_provider = excluded.model_provider,
41
+ model_id = excluded.model_id,
42
+ agent_preset = excluded.agent_preset,
43
+ permission_preset = excluded.permission_preset,
44
+ forked_from = excluded.forked_from,
45
+ archived = excluded.archived,
46
+ last_visited_at = excluded.last_visited_at`;
47
+ /** Parse a stored JSON array, failing soft to `undefined`. */
48
+ function parseOrder(raw) {
49
+ if (raw === null)
50
+ return undefined;
51
+ try {
52
+ const value = JSON.parse(raw);
53
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : undefined;
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ /** Order a flat row list per workspace under manual mode (D9). */
60
+ function orderManual(rows, workspaces) {
61
+ const byCwd = new Map();
62
+ for (const row of rows) {
63
+ const key = row.cwd ?? UNGROUPED_WORKSPACE;
64
+ const group = byCwd.get(key);
65
+ if (group === undefined)
66
+ byCwd.set(key, [row]);
67
+ else
68
+ group.push(row);
69
+ }
70
+ const orderFor = new Map(workspaces.map((w) => [w.workspace_id, parseOrder(w.manual_order)]));
71
+ const ordered = [];
72
+ for (const [cwd, group] of byCwd) {
73
+ const manual = orderFor.get(cwd);
74
+ if (manual !== undefined && manual.length > 0) {
75
+ const rank = new Map(manual.map((id, index) => [id, index]));
76
+ group.sort((a, b) => {
77
+ const ai = rank.get(a.dsh_session_id) ?? Number.MAX_SAFE_INTEGER;
78
+ const bi = rank.get(b.dsh_session_id) ?? Number.MAX_SAFE_INTEGER;
79
+ return ai !== bi ? ai - bi : b.last_modified_at - a.last_modified_at;
80
+ });
81
+ }
82
+ else {
83
+ group.sort((a, b) => b.last_modified_at - a.last_modified_at);
84
+ }
85
+ ordered.push(...group);
86
+ }
87
+ return ordered;
88
+ }
89
+ export class BridgeStore {
90
+ db;
91
+ constructor(db) {
92
+ this.db = db;
93
+ }
94
+ /** Open (WAL + schema + integrity check). Throws on a corrupt/invalid database. */
95
+ static open(path) {
96
+ const db = openDb(path);
97
+ db.exec("PRAGMA journal_mode=WAL");
98
+ db.exec(SCHEMA_SQL);
99
+ const check = db.prepare("PRAGMA integrity_check").all();
100
+ const ok = check.length === 1 && check[0]?.["integrity_check"] === "ok";
101
+ if (!ok) {
102
+ db.close();
103
+ throw new Error(`bridge-store integrity check failed: ${JSON.stringify(check)}`);
104
+ }
105
+ return new BridgeStore(db);
106
+ }
107
+ close() {
108
+ this.db.close();
109
+ }
110
+ all(sql, ...params) {
111
+ return this.db.prepare(sql).all(...params);
112
+ }
113
+ get(sql, ...params) {
114
+ return this.db.prepare(sql).get(...params);
115
+ }
116
+ run(sql, ...params) {
117
+ this.db.prepare(sql).run(...params);
118
+ }
119
+ /** All session rows, default `last_modified_at DESC`, manual mode per-workspace order. */
120
+ list() {
121
+ const rows = this.all("SELECT * FROM sessions ORDER BY last_modified_at DESC");
122
+ if (this.getUiState("sort_mode") !== "manual")
123
+ return rows;
124
+ return orderManual(rows, this.workspaces());
125
+ }
126
+ /** All workspace rows. */
127
+ workspaces() {
128
+ return this.all("SELECT * FROM workspaces");
129
+ }
130
+ byDshId(dshSessionId) {
131
+ return this.get("SELECT * FROM sessions WHERE dsh_session_id = ?", dshSessionId);
132
+ }
133
+ byOmpId(ompSessionId) {
134
+ return this.get("SELECT * FROM sessions WHERE omp_session_id = ?", ompSessionId);
135
+ }
136
+ byFile(sessionFile) {
137
+ return this.get("SELECT * FROM sessions WHERE session_file = ?", sessionFile);
138
+ }
139
+ /** Full-row upsert, keyed by `omp_session_id`. */
140
+ upsert(row) {
141
+ this.run(UPSERT_SQL, row.omp_session_id, row.dsh_session_id, row.session_file, row.cwd, row.title, row.created_at, row.last_modified_at, row.transcript_size, row.model_provider, row.model_id, row.agent_preset, row.permission_preset, row.forked_from, row.archived, row.last_visited_at);
142
+ }
143
+ /** Drop every row whose session file is not in `keepFiles`. */
144
+ prune(keepFiles) {
145
+ const files = this.all("SELECT session_file FROM sessions");
146
+ const del = this.db.prepare("DELETE FROM sessions WHERE session_file = ?");
147
+ for (const { session_file } of files) {
148
+ if (!keepFiles.has(session_file))
149
+ del.run(session_file);
150
+ }
151
+ }
152
+ /** Migration only: overwrite the DSH pairing for one OMP session. */
153
+ setDshId(ompSessionId, dshSessionId) {
154
+ this.run("UPDATE sessions SET dsh_session_id = ? WHERE omp_session_id = ?", dshSessionId, ompSessionId);
155
+ }
156
+ setPermissionPreset(ompSessionId, preset) {
157
+ this.run("UPDATE sessions SET permission_preset = ? WHERE omp_session_id = ?", preset, ompSessionId);
158
+ }
159
+ setArchived(dshSessionId, archived) {
160
+ this.run("UPDATE sessions SET archived = ? WHERE dsh_session_id = ?", archived ? 1 : 0, dshSessionId);
161
+ }
162
+ setForkedFrom(dshSessionId, forkedFrom) {
163
+ this.run("UPDATE sessions SET forked_from = ? WHERE dsh_session_id = ?", forkedFrom, dshSessionId);
164
+ }
165
+ touchVisited(dshSessionId, at) {
166
+ this.run("UPDATE sessions SET last_visited_at = ? WHERE dsh_session_id = ?", at, dshSessionId);
167
+ }
168
+ /** Persist a workspace's manual ordering (upsert the workspace row first). */
169
+ setWorkspaceOrder(workspaceId, order) {
170
+ this.run("INSERT INTO workspaces (workspace_id, title, manual_order) VALUES (?, NULL, ?) ON CONFLICT(workspace_id) DO UPDATE SET manual_order = excluded.manual_order", workspaceId, JSON.stringify(order));
171
+ }
172
+ /** Ensure the `ungrouped` sentinel workspace row exists (D8). */
173
+ ensureUngroupedWorkspace() {
174
+ this.run("INSERT INTO workspaces (workspace_id, title, manual_order) VALUES (?, NULL, NULL) ON CONFLICT(workspace_id) DO NOTHING", UNGROUPED_WORKSPACE);
175
+ }
176
+ getUiState(key) {
177
+ return this.get("SELECT value FROM ui_state WHERE key = ?", key)?.value ?? undefined;
178
+ }
179
+ setUiState(key, value) {
180
+ this.run("INSERT INTO ui_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value", key, value);
181
+ }
182
+ /** The tracked OMP default model (`modelRoles.default`), or undefined when unset. */
183
+ getOmpDefaultModel() {
184
+ const raw = this.getUiState("omp_default_model");
185
+ if (raw === undefined)
186
+ return undefined;
187
+ try {
188
+ const parsed = JSON.parse(raw);
189
+ if (typeof parsed.provider === "string" && typeof parsed.model === "string") {
190
+ return { provider: parsed.provider, model: parsed.model };
191
+ }
192
+ }
193
+ catch {
194
+ return undefined;
195
+ }
196
+ return undefined;
197
+ }
198
+ /** Persist the tracked OMP default model. */
199
+ setOmpDefaultModel(provider, model) {
200
+ this.setUiState("omp_default_model", JSON.stringify({ provider, model }));
201
+ }
202
+ /** Event-driven model update after a live model switch (D5.3). */
203
+ updateModel(dshSessionId, provider, model) {
204
+ this.run("UPDATE sessions SET model_provider = ?, model_id = ? WHERE dsh_session_id = ?", provider, model, dshSessionId);
205
+ }
206
+ /** Event-driven stat refresh on teardown/cold transition (D5.3). */
207
+ updateStat(dshSessionId, size, mtimeMs) {
208
+ this.run("UPDATE sessions SET transcript_size = ?, last_modified_at = ? WHERE dsh_session_id = ?", size, mtimeMs, dshSessionId);
209
+ }
210
+ }
@@ -0,0 +1,23 @@
1
+ import { BridgeStore } from "./db.js";
2
+ /**
3
+ * Resolve the index path: `OMP_BRIDGE_DB` (absolute) or the profile's own
4
+ * state dir (`$DSH_HOME/bridge-store.sqlite`). Throws on a relative/invalid
5
+ * path, or one that falls inside OMP's native store (D7: OMP scans that dir
6
+ * and would treat the `.sqlite` as a foreign session file).
7
+ *
8
+ * `$DSH_HOME` also anchors dsh's home-level singleton settings document
9
+ * (`$DSH_HOME/settings.yaml`), which this bridge never reads; the profile
10
+ * isolates that document via its own `settings.path` patch (settings-under-
11
+ * profile), independent of this store.
12
+ */
13
+ export declare function resolveBridgeDbPath(): string;
14
+ /**
15
+ * Open the store, run the boot warm pass, then the one-time migration. The
16
+ * warm pass must complete before the bridge serves traffic (D5.1): the first
17
+ * landing already reads real titles/models from the index.
18
+ */
19
+ export declare function initBridgeStore(path?: string): BridgeStore;
20
+ /** The live store, or undefined when not initialized (tests, no DB). */
21
+ export declare function getBridgeStore(): BridgeStore | undefined;
22
+ /** Close and release the singleton (teardown). */
23
+ export declare function closeBridgeStore(): void;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Bridge store lifecycle: DB path resolution, open/close, and the module
3
+ * singleton the rest of the bridge reaches through {@link getBridgeStore}.
4
+ */
5
+ import { mkdirSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { OMP_SESSIONS_ROOT } from "../omp-store.js";
9
+ import { BridgeStore } from "./db.js";
10
+ import { prepareIndex } from "./reconcile.js";
11
+ let store;
12
+ /**
13
+ * Resolve the index path: `OMP_BRIDGE_DB` (absolute) or the profile's own
14
+ * state dir (`$DSH_HOME/bridge-store.sqlite`). Throws on a relative/invalid
15
+ * path, or one that falls inside OMP's native store (D7: OMP scans that dir
16
+ * and would treat the `.sqlite` as a foreign session file).
17
+ *
18
+ * `$DSH_HOME` also anchors dsh's home-level singleton settings document
19
+ * (`$DSH_HOME/settings.yaml`), which this bridge never reads; the profile
20
+ * isolates that document via its own `settings.path` patch (settings-under-
21
+ * profile), independent of this store.
22
+ */
23
+ export function resolveBridgeDbPath() {
24
+ const explicit = process.env.OMP_BRIDGE_DB;
25
+ if (explicit !== undefined && explicit !== "" && !explicit.startsWith("/")) {
26
+ throw new Error(`OMP_BRIDGE_DB must be an absolute path: ${explicit}`);
27
+ }
28
+ const path = resolve(explicit !== undefined && explicit !== ""
29
+ ? explicit
30
+ : join(process.env.DSH_HOME ?? join(homedir(), ".omp", "dsh"), "bridge-store.sqlite"));
31
+ const root = resolve(OMP_SESSIONS_ROOT);
32
+ if (path === root || path.startsWith(`${root}/`)) {
33
+ throw new Error(`bridge store must not live under OMP_SESSIONS_ROOT (${root}): ${path}`);
34
+ }
35
+ return path;
36
+ }
37
+ /**
38
+ * Open the store, run the boot warm pass, then the one-time migration. The
39
+ * warm pass must complete before the bridge serves traffic (D5.1): the first
40
+ * landing already reads real titles/models from the index.
41
+ */
42
+ export function initBridgeStore(path) {
43
+ if (store !== undefined)
44
+ return store;
45
+ const dbPath = path ?? resolveBridgeDbPath();
46
+ mkdirSync(dirname(dbPath), { recursive: true });
47
+ store = BridgeStore.open(dbPath);
48
+ prepareIndex(store);
49
+ return store;
50
+ }
51
+ /** The live store, or undefined when not initialized (tests, no DB). */
52
+ export function getBridgeStore() {
53
+ return store;
54
+ }
55
+ /** Close and release the singleton (teardown). */
56
+ export function closeBridgeStore() {
57
+ store?.close();
58
+ store = undefined;
59
+ }
@@ -0,0 +1,15 @@
1
+ import type { BridgeStore } from "./db.js";
2
+ /**
3
+ * Boot the index before anything reads session state: ensure the ungrouped
4
+ * fallback workspace exists. Idempotent.
5
+ */
6
+ export declare function prepareIndex(store: BridgeStore): void;
7
+ /** Event-driven INSERT at first spawn: record the identity mapping + preset. */
8
+ export declare function upsertCreated(store: BridgeStore, input: {
9
+ ompSessionId: string;
10
+ sessionFile: string;
11
+ dshSessionId: string;
12
+ cwd?: string;
13
+ preset?: string;
14
+ createdAt?: number;
15
+ }): void;