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,632 @@
1
+ /**
2
+ * dsh-omp-provider (SDK face) — OMP provider plugin for DeepSeek Harness.
3
+ *
4
+ * Replaces the built-in agent loop with an `AgentFactory` that hosts sessions in
5
+ * ONE bun sidecar embedding the OMP core (SDK transport: `LazyOmpRpc` →
6
+ * `OmpSdkClient` over `sidecar/main.ts`) and bridges its event stream into the
7
+ * Dash Agent/Session contracts.
8
+ *
9
+ * Difference from the RPC line (`@pgmi-builds/agent-omp`): the runtime is NOT on
10
+ * the operator's machine. This package declares no coding-agent/bun dependency;
11
+ * the durable runtime (bun + the pinned package + this package's sidecar) is
12
+ * fetched into the world home on the FIRST real dispatch. Nothing here fetches
13
+ * or spawns before that: this provider is constructed by the world plugin (which
14
+ * itself only reports a missing runtime), and every passive path — cold list,
15
+ * replay, model catalog — answers from disk.
16
+ *
17
+ * Mirrors `@deepseek-ai/dsh-agent-loop`'s creation transaction
18
+ * (prepare → setup → publish) so the session + agent publish as one ordered
19
+ * lifecycle.
20
+ *
21
+ * Phase 3 adds resume:
22
+ * - `createAgent` records the Dash→OMP session identity in `OmpSessionIndex`
23
+ * (OMP owns the transcript, Dash owns the live identity).
24
+ * - `resume` re-attaches the OMP session file through the sidecar (`--resume`)
25
+ * and restores the persisted Dash session log (`sessionPersistence.prepare`),
26
+ * so the Web UI's cold listing/history/follow-up path (`session.list`,
27
+ * `session.history`, `session.prompt` → `agents.resume`) reopens the exact
28
+ * transcript it rendered before the restart. The OMP transcript is replayed
29
+ * (`get_messages`) only when no Dash log was persisted.
30
+ */
31
+ import { realpathSync, statSync } from "node:fs";
32
+ import { Service } from "@deepseek-ai/cordis";
33
+ import { homedir } from "node:os";
34
+ import { join } from "node:path";
35
+ import { SessionPreparation, SessionLogOffset } from "@deepseek-ai/dsh-session";
36
+ import { createOmpSdkTransport } from "./transport.js";
37
+ import { OmpAgent } from "./agent.js";
38
+ import { OmpLlmAdapter } from "./adapter.js";
39
+ import { registerOmpPreset } from "./agent-preset-omp.js";
40
+ import { resolveEntryById } from "./pairing.js";
41
+ import { readOmpDefaultModelFromConfig, sessionHeaderId } from "./omp-store.js";
42
+ import { ompModelRoles, ompSetModelRoles } from "./omp-cli.js";
43
+ import { loadOmpModels, ompProviderIds, refreshOmpModelsCli } from "./models.js";
44
+ import { OMP_NATIVE_HOME, STORAGE_RECONCILE_INTERVAL_MS } from "./knobs.js";
45
+ import { defaultPermissionPreset, envApprovalMode, resolveSpawnPermission } from "./permission.js";
46
+ import { closeBridgeStore, getBridgeStore, initBridgeStore } from "./store/index.js";
47
+ import { prepareIndex, upsertCreated } from "./store/reconcile.js";
48
+ import { installMobileBootScript } from "./mobile-boot.js";
49
+ import { installOmpDiscovery } from "./discovery.js";
50
+ /** Realpath of a recorded cwd, accepted only when it names an existing directory. */
51
+ function validatedCwd(cwd) {
52
+ if (cwd === undefined)
53
+ return undefined;
54
+ try {
55
+ const canonical = realpathSync(cwd);
56
+ return statSync(canonical).isDirectory() ? canonical : undefined;
57
+ }
58
+ catch {
59
+ return undefined;
60
+ }
61
+ }
62
+ /** Diagnostic trace (OMP_TRACE=1 on the dsh process enables stderr tracing). */
63
+ const trace = (...parts) => {
64
+ if (process.env.OMP_TRACE === "1")
65
+ process.stderr.write(`[omp-sdk-provider ${Date.now() % 1_000_000}] ${parts.join(" ")}\n`);
66
+ };
67
+ /**
68
+ * Whether any live OMP session currently holds the SDK transport's child.
69
+ *
70
+ * The reconcile tick's catalog read is a real `omp models --json` subprocess,
71
+ * so this gate is load-bearing (AGENTS.md §五·五: the periodic tick never
72
+ * materializes a runtime). For this line the periodic path does NOT touch the
73
+ * sidecar at all — the data plane is the CLI — but the tick must still pull
74
+ * zero children, and the SDK wrapper's `spawned` is the honest fact to gate on.
75
+ * Sessions count only once they actually spawned: a draft session holds a
76
+ * `LazyOmpRpc` with no child (and no fetched runtime) yet.
77
+ * Exported beside the plugin (the cordis surface itself is unchanged) so the
78
+ * no-sidecar guard can pin this gate's truth table behaviourally — a shape
79
+ * regex alone cannot see an empty input set.
80
+ */
81
+ export function liveOmpSessionExists(sessions) {
82
+ for (const rpc of sessions) {
83
+ if (rpc.spawned)
84
+ return true;
85
+ }
86
+ return false;
87
+ }
88
+ export class OmpProvider extends Service {
89
+ /**
90
+ * `agentDefaultModel` is load-bearing, not decorative: the harness owns the
91
+ * "default for new sessions" selection there (`@deepseek-ai/dsh-agent-default-model`,
92
+ * mounted by dsh-base with a hard-coded `deepseek-official/deepseek-flash`),
93
+ * and this bridge is the only thing that feeds it OMP's configured default.
94
+ * Without the inject the context cannot resolve the service, every
95
+ * `saveSelection` in #syncModelDefaultTick silently no-ops, and a fresh
96
+ * session inherits the unserved native default → `session/model-unavailable`.
97
+ */
98
+ static inject = ["agents", "sessions", "llm", "agentDefaultModel", "settings"];
99
+ /** Plain holder — prevents Cordis re-tracing the factory's ctx through a caller shadow. */
100
+ runtime;
101
+ /**
102
+ * The SDK transport (one sidecar pool per provider = per dsh process). Built
103
+ * here, but CONSTRUCTION-FREE: the pool is inert until the first session's
104
+ * first prompt reaches `OmpSdkSidecar#start()`.
105
+ */
106
+ transport;
107
+ /** Live SDK-backed agents by dash id, for avoidance hand-off. */
108
+ heldAgents = new Map();
109
+ /**
110
+ * Live transport wrappers by dash id — the reconcile tick's liveness gate
111
+ * (AGENTS.md §五·五): a draft session counts as live only once its
112
+ * `spawned` flips, which is exactly when the runtime fetch + sidecar start
113
+ * have happened. Entries are dropped with the handle.
114
+ */
115
+ liveSessions = new Set();
116
+ /**
117
+ * Persist the default selection into the settings section.
118
+ *
119
+ * `ctx.agentDefaultModel.saveSelection()` cannot do it: the harness service
120
+ * installs the section from an `inject(["settings"], …)` callback but then
121
+ * writes through `this.ctx.get("settings")?.replace(…)` on its UNSCOPED
122
+ * context, where `settings` is not injected — so the optional chain swallows
123
+ * the lookup and the call is a silent no-op (verified: it returns undefined
124
+ * and the settings file is untouched). We therefore write the installed
125
+ * section directly through our own injected `settings` service; the harness
126
+ * still reads it back through `installSection`'s fold, so behaviour is
127
+ * unchanged for every other consumer.
128
+ */
129
+ async #saveDefaultSelection(selection) {
130
+ const settings = this.runtime.ctx.get("settings");
131
+ await settings?.replace?.("agent-default-model", {
132
+ provider: selection.provider,
133
+ model: selection.model,
134
+ });
135
+ }
136
+ /** Shadow of OMP's `modelRoles.default` (`provider/model`) seen last tick. */
137
+ #ompDefaultKey = "";
138
+ /** Shadow of DSH's agentDefaultModel selection (`provider/model`) seen last tick. */
139
+ #dshSelectionKey = "";
140
+ constructor(ctx) {
141
+ super(ctx, "ompProvider");
142
+ this.runtime = { ctx };
143
+ // NOTE: constructing the transport fetches nothing and spawns nothing —
144
+ // it only wires the pool the first dispatch will drive.
145
+ this.transport = createOmpSdkTransport();
146
+ ctx.effect(() => ctx.agents.setFactory(this), "ompProvider.setFactory()");
147
+ // Native-home ruling (2026-09-17): the sidecar runs on the operator's
148
+ // native OMP home (`~/.omp`; $OMP_HOME overrides in tests) — no app-home
149
+ // bootstrap, no config seeding. The world DSH home only holds DSH state,
150
+ // and the world home holds the lazily fetched SDK runtime.
151
+ this.#registerModelCatalog();
152
+ // Boot the centralized index BEFORE any service reads session state: warm
153
+ // pass + migration, so the first landing sees real titles/models (D5.1).
154
+ initBridgeStore();
155
+ void this.#syncModelDefaultTick();
156
+ // dsh-shape-session-log: the native jsonl backend (re-enabled in the patch)
157
+ // is the sole `sessionPersistence` — the plugin no longer provides a union
158
+ // persistence. Session list/page/replay come from native components; agent-omp
159
+ // is only the producer (writes the dsh session log on live turns).
160
+ // 0.1.7: roster/selection/document RPC + the `agentPreset` session
161
+ // projection all live in the host AgentPresetRegistry (mounted by the
162
+ // dsh-web-app bundle's `agent-preset-registry` row; the adapter patch pins
163
+ // its `default` to the OMP preset). The bridge only registers its one
164
+ // fixed preset into the registry — no service of its own anymore.
165
+ registerOmpPreset(ctx);
166
+ ctx.effect(() => () => closeBridgeStore(), "ompProvider.storeClose()");
167
+ this.#reconcileWorkspaces();
168
+ // Mobile page-config boot script (host half): sets `window.__AGENT_OMP_MOBILE__`
169
+ // + the iOS zoom-guard section. Fail-open — a missing webServer leaves the
170
+ // feature dormant, and an injection failure is logged, never thrown.
171
+ installMobileBootScript(ctx);
172
+ // OMP skills + slash commands into the host's `skills`/`commands` seams.
173
+ installOmpDiscovery(ctx);
174
+ }
175
+ /**
176
+ * Two-way default-model sync, evaluated once per reconcile tick (and once
177
+ * at boot): OMP's `modelRoles.default` (CLI) ↔ DSH's `agentDefaultModel`
178
+ * selection. TUI semantics on both sides — a default write survives
179
+ * sessions. Direction is decided by shadow keys: whichever side moved
180
+ * since the last tick wins, and an echo (same value round-tripping back)
181
+ * is a no-op.
182
+ */
183
+ async #syncModelDefaultTick() {
184
+ const service = this.runtime.ctx.get("agentDefaultModel");
185
+ // Source of truth is OMP's config.yml ON DISK, never the live transport:
186
+ // the `omp` child is cold at boot and this tick must publish OMP's default
187
+ // to `ctx.agentDefaultModel` immediately (archived default-model contract).
188
+ const ompParsed = readOmpDefaultModelFromConfig();
189
+ const ompKey = ompParsed === undefined ? undefined : `${ompParsed.provider}/${ompParsed.model}`;
190
+ const dsh = service?.currentSelection?.();
191
+ const dshKey = dsh === undefined ? undefined : `${dsh.provider}/${dsh.model}`;
192
+ try {
193
+ if (ompKey !== undefined && ompKey !== this.#ompDefaultKey) {
194
+ // OMP side moved (TUI default switch or config edit): carry it into
195
+ // the store and the DSH default so new Web sessions inherit it.
196
+ this.#ompDefaultKey = ompKey;
197
+ if (ompParsed !== undefined) {
198
+ getBridgeStore()?.setOmpDefaultModel(ompParsed.provider, ompParsed.model);
199
+ await this.#saveDefaultSelection(ompParsed);
200
+ this.#dshSelectionKey = ompKey;
201
+ this.runtime.ctx.logger.info(`omp-provider: default model ← OMP: ${ompKey}`);
202
+ }
203
+ }
204
+ else if (dshKey !== undefined && dshKey !== this.#dshSelectionKey) {
205
+ // DSH side moved (Web UI selector switch): propagate into OMP's
206
+ // modelRoles.default through the CLI so TUI sessions inherit it.
207
+ this.#dshSelectionKey = dshKey;
208
+ if (dsh !== undefined && dshKey !== this.#ompDefaultKey) {
209
+ // Guard: never write a selection OMP cannot serve into the operator's
210
+ // native config (e.g. dash's built-in `deepseek-official`, which no
211
+ // adapter serves) — that would poison `~/.omp/agent/config.yml`.
212
+ const served = loadOmpModels().some((candidate) => candidate.provider === dsh.provider && candidate.id === dsh.model);
213
+ if (!served) {
214
+ this.runtime.ctx.logger.warn(`omp-provider: refusing to write an unserved selection → OMP: ${dshKey}`);
215
+ return;
216
+ }
217
+ const next = { ...((await ompModelRoles()) ?? {}), default: `${dsh.provider}/${dsh.model}` };
218
+ if (await ompSetModelRoles(next)) {
219
+ this.#ompDefaultKey = dshKey;
220
+ this.runtime.ctx.logger.info(`omp-provider: default model → OMP: ${dshKey}`);
221
+ }
222
+ else {
223
+ this.runtime.ctx.logger.warn(`omp-provider: default model → OMP write failed: ${dshKey}`);
224
+ }
225
+ }
226
+ }
227
+ }
228
+ catch (error) {
229
+ this.runtime.ctx.logger.warn(`omp-provider: default model sync failed: ${String(error)}`);
230
+ }
231
+ }
232
+ /**
233
+ * Reconcile the Web UI workspace sidebar against the scanned OMP store.
234
+ *
235
+ * The upstream workspace registry (`dsh-workspace`) only auto-groups by cwd
236
+ * on its FIRST boot (`bootstrap`); every session appearing later is grouped
237
+ * solely by `workspace.attachSession`, which the apiproxy calls only during
238
+ * `create`. TUI-born OMP sessions never pass through `create`, so without
239
+ * this pass they land in the "Ungrouped" bucket even though their cwd is
240
+ * durable in the transcript. Attach each scanned session by its DASH-facing
241
+ * id (dev_0.0.3 §11: OMP ids never cross the bridge boundary) to the
242
+ * workspace owning its canonical cwd — idempotent, fail-soft, every boot.
243
+ */
244
+ #reconcileWorkspaces() {
245
+ this.runtime.ctx.inject(["workspaceRegistry"], (wctx) => {
246
+ const registry = wctx.get("workspaceRegistry");
247
+ if (registry === undefined)
248
+ return;
249
+ const run = () => {
250
+ const store = getBridgeStore();
251
+ if (store !== undefined)
252
+ prepareIndex(store);
253
+ void this.#syncModelDefaultTick();
254
+ // AGENTS.md §五·五 "tick 永不 spawn": the catalog refresh runs a real
255
+ // `omp models --json` subprocess, so it may only fire while an OMP
256
+ // session is live. An idle daemon's tick pulls ZERO children; the
257
+ // on-disk model cache (models.ts) keeps serving the boot catalog.
258
+ if (liveOmpSessionExists(this.liveSessions))
259
+ void refreshOmpModelsCli();
260
+ void this.#attachScannedSessions(registry);
261
+ };
262
+ run();
263
+ // The registry groups by cwd only at ITS boot, so TUI-side sessions
264
+ // created later (the store gains files at any time) would sit in
265
+ // "Ungrouped" forever. Re-attach periodically: idempotent, fail-soft,
266
+ // and the scan is a stat pass over the store.
267
+ const timer = setInterval(run, STORAGE_RECONCILE_INTERVAL_MS);
268
+ timer.unref();
269
+ wctx.effect(() => () => clearInterval(timer), "ompProvider.reconcileTimer()");
270
+ });
271
+ }
272
+ /** One idempotent attach pass: every scanned session joins the workspace owning its canonical cwd. */
273
+ async #attachScannedSessions(registry) {
274
+ try {
275
+ const excluded = new Set();
276
+ for (const home of [
277
+ OMP_NATIVE_HOME,
278
+ process.env.DSH_HOME ?? join(homedir(), ".omp", "dsh"),
279
+ ]) {
280
+ try {
281
+ excluded.add(realpathSync(home));
282
+ }
283
+ catch {
284
+ // Unresolvable home: nothing to exclude.
285
+ }
286
+ }
287
+ const groups = new Map();
288
+ const store = getBridgeStore();
289
+ if (store === undefined)
290
+ return;
291
+ for (const row of store.list()) {
292
+ const cwd = validatedCwd(row.cwd ?? undefined);
293
+ if (cwd === undefined || excluded.has(cwd))
294
+ continue;
295
+ const id = row.dsh_session_id;
296
+ const ids = groups.get(cwd);
297
+ if (ids === undefined)
298
+ groups.set(cwd, [id]);
299
+ else if (!ids.includes(id))
300
+ ids.push(id);
301
+ }
302
+ for (const [path, ids] of groups) {
303
+ const workspace = (await registry.resolveByPath(path)) ?? (await registry.create(path));
304
+ for (const id of ids) {
305
+ if (!workspace.sessionIds.includes(id))
306
+ await workspace.attachSession(id);
307
+ }
308
+ }
309
+ }
310
+ catch (error) {
311
+ this.runtime.ctx.logger.warn(`omp-provider: workspace reconciliation failed: ${String(error)}`);
312
+ }
313
+ }
314
+ /**
315
+ * Register the OMP-backed LLM adapter so the browser model selector (an RPC
316
+ * round-trip through the apiproxy reading `ctx.llm.*`) advertises OMP's real
317
+ * providers and models. The registration is tied to the LLM runtime's own
318
+ * lifecycle, mirroring `llm-deepseek`/`llm-pi-ai` (which the profile disables).
319
+ */
320
+ #registerModelCatalog() {
321
+ const llm = this.runtime.ctx.get("llm");
322
+ if (llm === undefined)
323
+ return;
324
+ const providers = ompProviderIds();
325
+ if (providers.length === 0) {
326
+ this.runtime.ctx.logger.warn("omp-provider: no OMP models discovered; the model selector will be empty");
327
+ return;
328
+ }
329
+ llm.registerAdapter(providers, new OmpLlmAdapter());
330
+ }
331
+ async createAgent(ownerCtx, options) {
332
+ const loopCtx = this.runtime.ctx;
333
+ const id = options.sessionId;
334
+ const meta = options.meta ?? {};
335
+ const cwd = meta.cwd;
336
+ // Fork (the apiproxy's `session.fork`) seeds a copied-turn prefix but OMP
337
+ // has no native fork: the child spawned below would start with an EMPTY
338
+ // context while the UI renders the copied history, and after a restart
339
+ // even that history would vanish (OMP's transcript holds only post-fork
340
+ // traffic). Fail the fork cleanly at the factory boundary instead of
341
+ // splitting the two surfaces. `parentSession` in create metadata is set
342
+ // only by the fork path (subagents go through ctx.subagents instead).
343
+ if (meta.parentSession !== undefined) {
344
+ throw new Error(`cannot fork session "${meta.parentSession}" onto the OMP provider: OMP has no native session fork`);
345
+ }
346
+ // Approval policy is pinned at OMP launch — but it is NOT frozen here.
347
+ // The Dash session is prepared (and `pinInitialPermission`-stamped) only
348
+ // inside setupAndPublish, AFTER this wrapper exists, and the new-chat
349
+ // `/permission` pick arrives as a later `permission/preset`; the child
350
+ // materializes on the first prompt. The launch flag is therefore resolved
351
+ // at spawn time from the session's own events
352
+ // (`resolveSpawnPermission`), with the composition default as the fallback
353
+ // and OMP_APPROVAL_MODE (headless runs) as the override.
354
+ // Backend takes ZERO part in the UI's browser-local new-session draft:
355
+ // no sidecar child, no SDK runtime fetch, no transcript, no index row, no
356
+ // session events — the session announces blank, exactly like the native
357
+ // agent-loop factory's in-process agent. The child (and the runtime it runs
358
+ // on) materializes on the first prompt (`LazyOmpRpc` → `OmpSdkSidecar` →
359
+ // `ensureSdkRuntime`); `adoptSpawnedChild` writes the index row and the
360
+ // supervisor's held baseline at that moment, so a draft abandoned before
361
+ // the first prompt costs and leaves NOTHING server-side.
362
+ const preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
363
+ ...(options.seed === undefined ? {} : { seed: options.seed }),
364
+ ...(meta === undefined ? {} : { meta }),
365
+ }));
366
+ const session = preparation.session;
367
+ const spawnPermission = () => resolveSpawnPermission(session.snapshotEvents(), defaultPermissionPreset(loopCtx), envApprovalMode());
368
+ const rpc = this.transport.create({
369
+ spawnArgs: () => ["--approval-mode", spawnPermission().approvalMode],
370
+ cwd,
371
+ });
372
+ // The prepared header is the single source of truth for the row the
373
+ // first-spawn adoption writes: v2 folds header identity across
374
+ // live/listed/loaded observations, so created_at/cwd must be copied from
375
+ // it verbatim (an independent Date.now() would SOURCE_CONFLICT the id).
376
+ const header = preparation.session.header;
377
+ // dsh-shape-session-log (producer role): open the native dsh session log
378
+ // write handle now. The jsonl backend routes live `session/event` appends
379
+ // into this handle by session id; we own the handle and close it on teardown
380
+ // (mirrors agent-loop's createStoredSession).
381
+ const persistence = loopCtx.get("sessionPersistence");
382
+ const stored = persistence === undefined
383
+ ? undefined
384
+ : { handle: await persistence.create(header, { inheritedEventCount: preparation.session.inheritedEventCount }) };
385
+ rpc.onSpawned((client) => {
386
+ void adoptSpawnedChild(id, header, client, spawnPermission().preset);
387
+ });
388
+ // Nothing can fail from the child here (none exists yet); the publish
389
+ // transaction below unwinds its own half-published state on failure — its
390
+ // catch calls rpc.close(), a no-op until the first prompt spawns.
391
+ const handle = await setupAndPublish(loopCtx, ownerCtx, id, options.agentOptions ?? {}, options.setup, session, rpc, options.parentAgent, "startup", { enterSession: true, preparation, stored });
392
+ return this.registerHeld(id, "", handle, rpc);
393
+ }
394
+ async resume(ownerCtx, options) {
395
+ const loopCtx = this.runtime.ctx;
396
+ const id = options.resumeSessionId;
397
+ // dsh-shape-session-log (dual storage): the dsh session log is the replay
398
+ // of record; the OMP app-home session file is the live runtime's OWN
399
+ // context. Resume opens BOTH — never feed the old dsh transcript back to
400
+ // OMP: OMP re-opens its own file and only receives the NEW user prompt.
401
+ const persistence = loopCtx.get("sessionPersistence");
402
+ if (persistence === undefined) {
403
+ throw new Error(`cannot resume session "${id}": session persistence is not configured`);
404
+ }
405
+ // dsh side: take write ownership of the dsh log and read its full seed.
406
+ const sig = options.signal === undefined ? undefined : { signal: options.signal };
407
+ let stored;
408
+ let coldRead;
409
+ try {
410
+ stored = await persistence.open(id, "write", sig);
411
+ coldRead = await stored.read(0, undefined, sig);
412
+ }
413
+ catch (error) {
414
+ throw new Error(`cannot resume session "${id}": dsh session log is unreadable (${String(error)})`);
415
+ }
416
+ // OMP side: locate the app-home session file via the bridge-store pairing
417
+ // recorded at first spawn (adoptSpawnedChild stores the SDK-reported file).
418
+ // A BLANK draft has no OMP counterpart and no context to resume: the host
419
+ // resumes any persisted id (createOrAdopt), and the UI's new-chat persists
420
+ // an empty session first — so start a FRESH OMP session for it instead of
421
+ // failing. A session with real content has no OMP context we may feed the
422
+ // runtime (never replay the dsh transcript into OMP): fail loudly.
423
+ const record = resolveEntryById(id);
424
+ trace(`resume id=${id} record=${record === undefined ? "MISSING" : record.ompSessionFile}`);
425
+ const hasContent = coldRead.events.some((event) => event.type === "turn/start" || event.type === "user/message" || event.type === "assistant/message");
426
+ if (record === undefined && hasContent) {
427
+ throw new Error(`cannot resume session "${id}": no OMP session is recorded for this Dash session id`);
428
+ }
429
+ const freshOmp = record === undefined;
430
+ const ompFile = record?.ompSessionFile;
431
+ const spawnCwd = validatedCwd(record?.cwd) ?? validatedCwd(stored.header.cwd);
432
+ // Approval mode: the session's own last `permission/preset` (= what the
433
+ // `/permission` pick wrote, or the event synthesized at create), with the
434
+ // bridge-index row as the fallback for logs that predate the event. Resolved
435
+ // AT SPAWN so a pick made after this resume but before the first prompt
436
+ // still reaches the child; OMP_APPROVAL_MODE (headless) wins.
437
+ // OMP live runtime — ALWAYS LAZY (lazy-transport ruling, both branches).
438
+ // Opening/replaying a persisted session in the WebUI promotes it through
439
+ // history.follow → resume; a read-only visit must NOT materialize the OMP
440
+ // child. `LazyOmpRpc` spawns on the FIRST dispatch (prompt/followUp/steer/
441
+ // ensureStarted in #startTurn); passive queries synthesize without a child.
442
+ // Blank draft: no app-home file exists yet — the child adopts a fresh OMP
443
+ // session on first prompt (adoptSpawnedChild then writes the mapping).
444
+ // With content: the child re-attaches its OWN app-home file via --resume
445
+ // (never the dsh transcript); the mapping already exists in the bridge
446
+ // store, so no adoption hook is needed.
447
+ // Both branches are LAZY and both resolve their launch flag at spawn; both
448
+ // also defer the SDK runtime fetch (which rides the same first dispatch).
449
+ let rpc;
450
+ try {
451
+ // Fresh attach: seed the dsh session from the dsh log (native replay),
452
+ // header identity folded from the stored header verbatim.
453
+ const preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
454
+ seed: [...coldRead.events],
455
+ meta: structuredClone(stored.header),
456
+ inheritedEventCount: stored.inheritedEventCount,
457
+ eventState: coldRead.eventState,
458
+ }));
459
+ const rowPreset = () => getBridgeStore()?.byDshId(String(id))?.permission_preset ?? undefined;
460
+ trace(`resume id=${id} seeded from dsh session log (${coldRead.events.length} events), approval=resolve-at-spawn (row fallback ${rowPreset() ?? "none"})${freshOmp ? " fresh-omp" : ""}`);
461
+ const spawnPermission = () => resolveSpawnPermission(preparation.session.snapshotEvents(), rowPreset(), envApprovalMode());
462
+ if (ompFile === undefined) {
463
+ const lazy = this.transport.create({
464
+ spawnArgs: () => ["--approval-mode", spawnPermission().approvalMode],
465
+ cwd: spawnCwd,
466
+ });
467
+ lazy.onSpawned((client) => { void adoptSpawnedChild(id, stored.header, client, spawnPermission().preset); });
468
+ rpc = lazy;
469
+ }
470
+ else {
471
+ rpc = this.transport.create({
472
+ spawnArgs: () => ["--approval-mode", spawnPermission().approvalMode, "--resume", ompFile],
473
+ cwd: spawnCwd,
474
+ });
475
+ }
476
+ const handle = await setupAndPublish(loopCtx, ownerCtx, id, options.agentOptions ?? {}, options.setup, preparation.session, rpc, options.parentAgent, "resume", { enterSession: true, preparation, stored: { handle: stored, written: coldRead.events.length } });
477
+ return this.registerHeld(id, ompFile ?? "", handle, rpc);
478
+ }
479
+ catch (error) {
480
+ trace(`resume ${id} THREW: ${String(error)} | stack=${error instanceof Error ? error.stack?.slice(0, 400) : "n/a"}`);
481
+ await stored.close().catch(() => { });
482
+ rpc?.close();
483
+ throw error;
484
+ }
485
+ }
486
+ /**
487
+ * Track a published RPC-backed agent, register its transport wrapper with the
488
+ * tick's liveness gate, and hand the session to the supervisor as held. Arrow
489
+ * FIELD (not a `#` method): the provider is exposed through a Cordis tracing
490
+ * proxy, and hard-private methods fail their brand check on the proxy receiver
491
+ * (the same reason setupAndPublish is module-level).
492
+ */
493
+ registerHeld = (id, file, handle, rpc) => {
494
+ const wrapped = {
495
+ agent: handle.agent,
496
+ dispose: async () => {
497
+ this.heldAgents.delete(String(id));
498
+ this.liveSessions.delete(rpc);
499
+ await handle.dispose();
500
+ },
501
+ };
502
+ this.heldAgents.set(String(id), wrapped);
503
+ // Populate the tick's liveness gate: without this the gate is a constant
504
+ // false and the catalog refresh never runs (dead-input regression).
505
+ this.liveSessions.add(rpc);
506
+ return wrapped;
507
+ };
508
+ }
509
+ /**
510
+ * Shared creation/resume transaction: build the agent over the prepared
511
+ * session, run unpublished setup, and publish both in order. Kept as a
512
+ * module-level function (not a private method) because Cordis exposes the
513
+ * provider through a tracing proxy, which breaks hard-private (`#`) receivers.
514
+ */
515
+ /**
516
+ * First-spawn adoption for a lazily created session: the OMP child exists only
517
+ * from the first prompt on, so the bridge index row and the supervisor's held
518
+ * baseline are written HERE instead of at create time. The row copies the
519
+ * prepared header's created_at/cwd verbatim — v2 folds header identity across
520
+ * live/listed/loaded observations, and an independent timestamp would make
521
+ * session/query throw SOURCE_CONFLICT for the id. Fail-soft: an adoption
522
+ * failure degrades that session's metadata (the next reconcile pass retries
523
+ * against the transcript), never the prompt itself.
524
+ */
525
+ async function adoptSpawnedChild(id, header, client, preset) {
526
+ try {
527
+ const state = await client.getState();
528
+ const file = state.sessionFile;
529
+ if (file === undefined || file === "")
530
+ return;
531
+ const store = getBridgeStore();
532
+ const ompId = state.sessionId ?? sessionHeaderId(file);
533
+ if (store !== undefined && ompId !== undefined) {
534
+ upsertCreated(store, {
535
+ ompSessionId: ompId,
536
+ sessionFile: file,
537
+ dshSessionId: String(id),
538
+ createdAt: header.createdAt,
539
+ cwd: header.cwd,
540
+ ...(preset === undefined ? {} : { preset }),
541
+ });
542
+ }
543
+ trace(`adoptSpawnedChild id=${id} file=${file}`);
544
+ }
545
+ catch (error) {
546
+ trace(`adoptSpawnedChild id=${id} failed: ${String(error)}`);
547
+ }
548
+ }
549
+ async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, session, rpc, parentAgent, source, opts) {
550
+ let detachSession;
551
+ let detachAgent;
552
+ let agent;
553
+ // Late-bound teardown the agent triggers itself after its idle window
554
+ // expires (see OmpAgent's idle exit) — defined only after publication.
555
+ let idleExit;
556
+ try {
557
+ // Build the agent shim over the session and the live RPC client. For a
558
+ // promoted shadow the session is already entered+announced; enterSession
559
+ // is false and the projection is reused (seq-continuous promotion).
560
+ agent = new OmpAgent(loopCtx, id, agentOptions, session, rpc, () => idleExit?.());
561
+ // The live system prompt is NOT stamped here anymore: the eager append
562
+ // turned the UI's new-session draft into a non-blank session and broke
563
+ // the composer. It is committed on the first turn by OmpAgent's
564
+ // #bootstrapSessionIdentity, after the lazy child spawns — surface node 0
565
+ // ahead of turn/start, the native loop's step() ordering. A replayed or
566
+ // resumed session seeds its own system/message from the transcript.
567
+ // Composition-only setup on the unpublished agent scope.
568
+ const commit = await setup?.(agent.ctx, agent);
569
+ // Publish: enter both session and agent, announce in order, then signal
570
+ // session-start. The commit runs immediately before publication.
571
+ commit?.commit();
572
+ // dsh-shape-session-log (producer role): flush events appended BEFORE
573
+ // publication that never emit through `session/event` (constructor seeds
574
+ // and the restore path's `session/end-seed`). Without this the jsonl
575
+ // writer would see a gap (expected N, got N+1) and halt the drain.
576
+ // Mirrors agent-loop's `appendUnstoredSuffix`.
577
+ if (opts.stored !== undefined) {
578
+ const written = opts.stored.written ?? 0;
579
+ const suffix = session.snapshotEvents(SessionLogOffset(written));
580
+ if (suffix.length > 0)
581
+ await opts.stored.handle.append(suffix);
582
+ opts.stored.written = written + suffix.length;
583
+ }
584
+ if (opts.enterSession) {
585
+ detachSession = agent.ctx.sessions.enter(session);
586
+ agent.ctx.sessions.announce(session);
587
+ }
588
+ detachAgent = loopCtx.agents.enter(agent, parentAgent);
589
+ // 0.1.6: announce(agent, source) is required, async, and emits agent/created
590
+ // itself — the manual agent/session-start emission is gone upstream.
591
+ await loopCtx.agents.announce(agent, source);
592
+ let disposed = false;
593
+ let unfollowOwner;
594
+ const dispose = async () => {
595
+ if (disposed)
596
+ return;
597
+ disposed = true;
598
+ trace(`dispose() called for agent ${id} (source=${source}) stack=${new Error().stack?.split("\n").slice(1, 4).join(" <- ")}`);
599
+ unfollowOwner?.();
600
+ await agent?.dispose();
601
+ await opts.stored?.handle.close().catch(() => { });
602
+ detachAgent?.();
603
+ detachSession?.();
604
+ };
605
+ idleExit = () => void dispose();
606
+ // Follow the owner: a caller-fiber unload tears this agent down.
607
+ unfollowOwner = ownerCtx.effect(() => () => {
608
+ void dispose();
609
+ }, `ompProvider.lifecycle(${id})`);
610
+ return { agent, dispose };
611
+ }
612
+ catch (error) {
613
+ // Unwind the half-published transaction. A failure between `enter` and
614
+ // the announcements (e.g. a persistence listener rejecting the session)
615
+ // must not leave a live-but-dead entry the API resolver would serve.
616
+ trace(`setupAndPublish ${id} FAILED: ${String(error)}`);
617
+ detachAgent?.();
618
+ detachSession?.();
619
+ void agent?.dispose().catch(() => { });
620
+ rpc.close();
621
+ throw error;
622
+ }
623
+ finally {
624
+ // Release the preparation's per-id reservation on every path (mirrors
625
+ // the reference loop's unconditional dispose): the session it seeded is
626
+ // already published above, so a same-process re-resume of this id can
627
+ // prepare again instead of colliding with a leaked reservation. Promotion
628
+ // has no preparation (the shadow's projection persists across the call).
629
+ opts.preparation?.[Symbol.dispose]();
630
+ }
631
+ }
632
+ export default OmpProvider;
@@ -0,0 +1,34 @@
1
+ /** Bridge-index tick: workspace attach + model-default sync + model refresh. */
2
+ export declare const STORAGE_RECONCILE_INTERVAL_MS: number;
3
+ /**
4
+ * Slash-command discovery refresh: the disk scan (omp+claude roots) re-mounts
5
+ * the host `commands` registrations on this cadence — boot once, then hourly.
6
+ * Pure disk reads, never a sidecar spawn. `0` disables the refresh.
7
+ */
8
+ export declare const OMP_DISCOVERY_REFRESH_INTERVAL_MS: number;
9
+ /**
10
+ * The OMP home — the SDK runtime app home (`agentDir`): runtime data
11
+ * (agent.db / models.db / sessions) and config live here. NATIVE by default
12
+ * (user ruling 2026-09-17, pi precedent):
13
+ *
14
+ * OMP_HOME = $OMP_HOME (tests / adapter-level isolation) ?? ~/.omp
15
+ *
16
+ * The installed OMP app keeps its own home; the adapter never redirects it at
17
+ * spawn and never seeds config into it. `<dshHome>/agents/omp` remains the
18
+ * world's DSH home (its sessions/storages/settings) — it is NOT the app home
19
+ * anymore. The old nested derivation (`dshHomePath("agents", "omp", ".omp")`)
20
+ * and the config copy-in (`ensureOmpAppHome`) are retired with the same
21
+ * ruling; `OMP_HOME` stays overridable per the 2026-09-15 adapter-level
22
+ * freedom (optional isolation, never the default).
23
+ */
24
+ export declare const OMP_HOME: string;
25
+ /**
26
+ * The operator's NATIVE OMP home (`~/.omp`): the TUI/CLI installation owning
27
+ * `agent/config.yml`, `agent/models.yml` and the native session store. Since
28
+ * the 2026-09-17 native-home ruling this is ALSO the default runtime app home
29
+ * ({@link OMP_HOME}); the knob stays as a late env override seam (tests may
30
+ * pin disk-scan roots via `$OMP_NATIVE_HOME` independently of `$OMP_HOME`).
31
+ */
32
+ export declare function resolveOmpNativeHome(): string;
33
+ /** Boot-time snapshot of {@link resolveOmpNativeHome}. */
34
+ export declare const OMP_NATIVE_HOME: string;