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,50 @@
1
+ /**
2
+ * Env-tunable knobs.
3
+ *
4
+ * dsh-shape-session-log retired the union-era supervisor cadence and replay
5
+ * cache knobs along with the supervisor and the union persistence.
6
+ */
7
+ import { homedir } from "node:os";
8
+ import { join } from "node:path";
9
+ function parseMs(raw, fallback) {
10
+ if (raw === undefined || raw.trim() === "")
11
+ return fallback;
12
+ const value = Number(raw);
13
+ return Number.isFinite(value) && value >= 0 ? value : fallback;
14
+ }
15
+ /** Bridge-index tick: workspace attach + model-default sync + model refresh. */
16
+ export const STORAGE_RECONCILE_INTERVAL_MS = parseMs(process.env.OMP_STORAGE_RECONCILE_INTERVAL_MS, 30_000);
17
+ /**
18
+ * Slash-command discovery refresh: the disk scan (omp+claude roots) re-mounts
19
+ * the host `commands` registrations on this cadence — boot once, then hourly.
20
+ * Pure disk reads, never a sidecar spawn. `0` disables the refresh.
21
+ */
22
+ export const OMP_DISCOVERY_REFRESH_INTERVAL_MS = parseMs(process.env.OMP_DISCOVERY_REFRESH_INTERVAL_MS, 3_600_000);
23
+ /**
24
+ * The OMP home — the SDK runtime app home (`agentDir`): runtime data
25
+ * (agent.db / models.db / sessions) and config live here. NATIVE by default
26
+ * (user ruling 2026-09-17, pi precedent):
27
+ *
28
+ * OMP_HOME = $OMP_HOME (tests / adapter-level isolation) ?? ~/.omp
29
+ *
30
+ * The installed OMP app keeps its own home; the adapter never redirects it at
31
+ * spawn and never seeds config into it. `<dshHome>/agents/omp` remains the
32
+ * world's DSH home (its sessions/storages/settings) — it is NOT the app home
33
+ * anymore. The old nested derivation (`dshHomePath("agents", "omp", ".omp")`)
34
+ * and the config copy-in (`ensureOmpAppHome`) are retired with the same
35
+ * ruling; `OMP_HOME` stays overridable per the 2026-09-15 adapter-level
36
+ * freedom (optional isolation, never the default).
37
+ */
38
+ export const OMP_HOME = process.env.OMP_HOME ?? resolveOmpNativeHome();
39
+ /**
40
+ * The operator's NATIVE OMP home (`~/.omp`): the TUI/CLI installation owning
41
+ * `agent/config.yml`, `agent/models.yml` and the native session store. Since
42
+ * the 2026-09-17 native-home ruling this is ALSO the default runtime app home
43
+ * ({@link OMP_HOME}); the knob stays as a late env override seam (tests may
44
+ * pin disk-scan roots via `$OMP_NATIVE_HOME` independently of `$OMP_HOME`).
45
+ */
46
+ export function resolveOmpNativeHome() {
47
+ return process.env.OMP_NATIVE_HOME ?? join(homedir(), ".omp");
48
+ }
49
+ /** Boot-time snapshot of {@link resolveOmpNativeHome}. */
50
+ export const OMP_NATIVE_HOME = resolveOmpNativeHome();
@@ -0,0 +1,98 @@
1
+ /**
2
+ * LazyOmpRpc — the create-time placeholder that makes a freshly created Dash
3
+ * session cost NOTHING on the backend (the "backend takes zero part in the
4
+ * UI's draft" contract):
5
+ *
6
+ * - constructing one spawns no OMP child, writes no transcript, no index
7
+ * row, and no session events — the session announces blank, exactly like
8
+ * the native agent-loop factory's in-process agent;
9
+ * - the shared-sidecar session materializes on the FIRST dispatch path
10
+ * (`prompt` / `followUp` / `steer` / `ensureStarted`);
11
+ * - passive queries (`getState` / `getSubagents` / `getSessionStats` /
12
+ * `getSystemPrompt` / `abort` / `sendRaw`) answer synthetically without
13
+ * spawning, so idle-exit revalidation, `whenIdle`, and avoidance checks
14
+ * can never conjure a child;
15
+ * - `close()` on a never-spawned wrapper is a no-op; a close racing an
16
+ * in-flight spawn disposes the child the moment it lands.
17
+ *
18
+ * A model selection made before the child exists (`setModel` from the pre-turn
19
+ * sync) is parked and applied right after the spawn, ahead of the prompt.
20
+ */
21
+ import type { OmpSessionStats, OmpState, RpcEvent } from "./rpc-types.js";
22
+ import { OmpSdkClient, type OmpSdkSidecarPool } from "./sdk-client.js";
23
+ /**
24
+ * The client surface `OmpAgent` drives. `OmpSdkClient` satisfies this
25
+ * structurally (plus `ensureStarted`/`spawned`, which are no-ops/trues there);
26
+ * `LazyOmpRpc` implements it with deferred spawning.
27
+ */
28
+ export interface OmpAgentRpc {
29
+ on(listener: (event: RpcEvent) => void): () => void;
30
+ onTitle(listener: (title: string) => void): () => void;
31
+ onFailure(listener: (error: Error) => void): () => void;
32
+ prompt(message: string): Promise<unknown>;
33
+ followUp(message: string): Promise<unknown>;
34
+ steer(message: string): Promise<unknown>;
35
+ compact(instructions?: string): Promise<unknown>;
36
+ contextUsage(): Promise<{
37
+ tokens?: number;
38
+ percent?: number;
39
+ } | undefined>;
40
+ abort(): Promise<unknown>;
41
+ /** Forces the child to exist (first prompt). Never rejects for being live. */
42
+ ensureStarted(): Promise<void>;
43
+ /** Whether the child exists — passive paths key off this, never spawn. */
44
+ readonly spawned: boolean;
45
+ getState(): Promise<OmpState>;
46
+ getSubagents(): Promise<unknown[]>;
47
+ getSessionStats(): Promise<OmpSessionStats>;
48
+ getSystemPrompt(): Promise<string | undefined>;
49
+ setModel(provider: string, modelId: string): Promise<unknown>;
50
+ sendRaw(command: Record<string, unknown>): void;
51
+ close(): void;
52
+ }
53
+ export declare class LazyOmpRpc implements OmpAgentRpc {
54
+ #private;
55
+ /**
56
+ * @param spawnArgs - the session's argv (`--approval-mode …`, optional
57
+ * `--resume …`), or a thunk called AT SPAWN. Use the thunk whenever a
58
+ * launch fact can still change between construction and the first prompt —
59
+ * the new-chat permission pick does, and so does the lazily fetched SDK
60
+ * runtime the spawn depends on.
61
+ * @param cwd - the session cwd.
62
+ * @param pool - the sidecar pool (default: the process-wide one).
63
+ */
64
+ constructor(spawnArgs: string[] | (() => string[]), cwd?: string, pool?: OmpSdkSidecarPool);
65
+ /**
66
+ * Bridge hook: runs once when the child first exists (the caller writes the
67
+ * index row + supervisor held-baseline there — the create-time counterparts
68
+ * were deliberately skipped). Registered before or after the spawn; both
69
+ * orders fire exactly once.
70
+ */
71
+ onSpawned(handler: (client: OmpSdkClient) => void): void;
72
+ get spawned(): boolean;
73
+ on(listener: (event: RpcEvent) => void): () => void;
74
+ /**
75
+ * Title frames must survive the deferred spawn: OMP generates the title from
76
+ * the first prompt, which is exactly the moment this RPC materializes, so a
77
+ * listener registered before then would otherwise miss the only frame.
78
+ */
79
+ onTitle(listener: (title: string) => void): () => void;
80
+ onFailure(listener: (error: Error) => void): () => void;
81
+ prompt(message: string): Promise<unknown>;
82
+ followUp(message: string): Promise<unknown>;
83
+ steer(message: string): Promise<unknown>;
84
+ compact(instructions?: string): Promise<unknown>;
85
+ contextUsage(): Promise<{
86
+ tokens?: number;
87
+ percent?: number;
88
+ } | undefined>;
89
+ abort(): Promise<unknown>;
90
+ ensureStarted(): Promise<void>;
91
+ getState(): Promise<OmpState>;
92
+ getSubagents(): Promise<unknown[]>;
93
+ getSessionStats(): Promise<OmpSessionStats>;
94
+ getSystemPrompt(): Promise<string | undefined>;
95
+ setModel(provider: string, modelId: string): Promise<unknown>;
96
+ sendRaw(command: Record<string, unknown>): void;
97
+ close(): void;
98
+ }
@@ -0,0 +1,178 @@
1
+ import { defaultOmpSdkSidecarPool } from "./sdk-client.js";
2
+ /** Synthetic state for a not-yet-spawned child: quiescent by definition. */
3
+ const IDLE_STATE = { isStreaming: false };
4
+ export class LazyOmpRpc {
5
+ #spawnArgs;
6
+ #cwd;
7
+ /**
8
+ * The sidecar pool this wrapper rides (the process-wide default unless a test
9
+ * or a second world injects its own). Every session of one pool shares ONE
10
+ * bun child — that singleton IS the reason the SDK row exists.
11
+ */
12
+ #pool;
13
+ #client;
14
+ #spawn;
15
+ #closed = false;
16
+ #eventListeners = new Set();
17
+ #titleListeners = new Set();
18
+ #failureListeners = new Set();
19
+ #pendingModel;
20
+ #onSpawned;
21
+ /**
22
+ * @param spawnArgs - the session's argv (`--approval-mode …`, optional
23
+ * `--resume …`), or a thunk called AT SPAWN. Use the thunk whenever a
24
+ * launch fact can still change between construction and the first prompt —
25
+ * the new-chat permission pick does, and so does the lazily fetched SDK
26
+ * runtime the spawn depends on.
27
+ * @param cwd - the session cwd.
28
+ * @param pool - the sidecar pool (default: the process-wide one).
29
+ */
30
+ constructor(spawnArgs, cwd, pool) {
31
+ this.#spawnArgs = spawnArgs;
32
+ this.#cwd = cwd;
33
+ this.#pool = pool ?? defaultOmpSdkSidecarPool();
34
+ }
35
+ /**
36
+ * Bridge hook: runs once when the child first exists (the caller writes the
37
+ * index row + supervisor held-baseline there — the create-time counterparts
38
+ * were deliberately skipped). Registered before or after the spawn; both
39
+ * orders fire exactly once.
40
+ */
41
+ onSpawned(handler) {
42
+ this.#onSpawned = handler;
43
+ if (this.#client !== undefined)
44
+ handler(this.#client);
45
+ }
46
+ get spawned() {
47
+ return this.#client !== undefined;
48
+ }
49
+ /** Spawn once; concurrent and later callers share the same child. */
50
+ #ensure() {
51
+ if (this.#client !== undefined)
52
+ return Promise.resolve(this.#client);
53
+ if (this.#spawn === undefined) {
54
+ const args = typeof this.#spawnArgs === "function" ? this.#spawnArgs() : this.#spawnArgs;
55
+ this.#spawn = this.#pool.spawnSession(args, this.#cwd).then((client) => {
56
+ // A close() that raced the in-flight spawn wins: dispose immediately.
57
+ if (this.#closed) {
58
+ client.close();
59
+ return client;
60
+ }
61
+ this.#client = client;
62
+ for (const listener of this.#eventListeners)
63
+ client.on(listener);
64
+ for (const listener of this.#titleListeners)
65
+ client.onTitle(listener);
66
+ for (const listener of this.#failureListeners)
67
+ client.onFailure(listener);
68
+ this.#onSpawned?.(client);
69
+ return client;
70
+ }).catch((error) => {
71
+ // Allow a later dispatch to retry the spawn.
72
+ this.#spawn = undefined;
73
+ throw error;
74
+ });
75
+ }
76
+ return this.#spawn;
77
+ }
78
+ on(listener) {
79
+ this.#eventListeners.add(listener);
80
+ if (this.#client !== undefined)
81
+ this.#client.on(listener);
82
+ return () => {
83
+ this.#eventListeners.delete(listener);
84
+ };
85
+ }
86
+ /**
87
+ * Title frames must survive the deferred spawn: OMP generates the title from
88
+ * the first prompt, which is exactly the moment this RPC materializes, so a
89
+ * listener registered before then would otherwise miss the only frame.
90
+ */
91
+ onTitle(listener) {
92
+ this.#titleListeners.add(listener);
93
+ if (this.#client !== undefined)
94
+ this.#client.onTitle(listener);
95
+ return () => {
96
+ this.#titleListeners.delete(listener);
97
+ };
98
+ }
99
+ onFailure(listener) {
100
+ this.#failureListeners.add(listener);
101
+ if (this.#client !== undefined)
102
+ this.#client.onFailure(listener);
103
+ return () => {
104
+ this.#failureListeners.delete(listener);
105
+ };
106
+ }
107
+ async prompt(message) {
108
+ const client = await this.#ensure();
109
+ // Apply a pre-spawn model selection now, ahead of the prompt (the sidecar
110
+ // records it as the session's model_change, like a pre-turn set_model).
111
+ const pending = this.#pendingModel;
112
+ if (pending !== undefined) {
113
+ this.#pendingModel = undefined;
114
+ await client.setModel(pending.provider, pending.modelId).catch(() => { });
115
+ }
116
+ return client.prompt(message);
117
+ }
118
+ async followUp(message) {
119
+ return (await this.#ensure()).followUp(message);
120
+ }
121
+ async steer(message) {
122
+ return (await this.#ensure()).steer(message);
123
+ }
124
+ async compact(instructions) {
125
+ return (await this.#ensure()).compact(instructions);
126
+ }
127
+ async contextUsage() {
128
+ if (this.#client === undefined)
129
+ return undefined;
130
+ return this.#client.contextUsage();
131
+ }
132
+ async abort() {
133
+ if (this.#client === undefined)
134
+ return undefined;
135
+ return this.#client.abort();
136
+ }
137
+ async ensureStarted() {
138
+ await this.#ensure();
139
+ }
140
+ async getState() {
141
+ if (this.#client === undefined)
142
+ return IDLE_STATE;
143
+ return this.#client.getState();
144
+ }
145
+ async getSubagents() {
146
+ if (this.#client === undefined)
147
+ return [];
148
+ return this.#client.getSubagents();
149
+ }
150
+ async getSessionStats() {
151
+ if (this.#client === undefined)
152
+ return {};
153
+ return this.#client.getSessionStats();
154
+ }
155
+ async getSystemPrompt() {
156
+ if (this.#client === undefined)
157
+ return undefined;
158
+ return this.#client.getSystemPrompt();
159
+ }
160
+ async setModel(provider, modelId) {
161
+ if (this.#client === undefined) {
162
+ // Park it: applied by prompt() immediately after the spawn.
163
+ this.#pendingModel = { provider, modelId };
164
+ return undefined;
165
+ }
166
+ return this.#client.setModel(provider, modelId);
167
+ }
168
+ sendRaw(command) {
169
+ if (this.#client !== undefined)
170
+ this.#client.sendRaw(command);
171
+ }
172
+ close() {
173
+ this.#closed = true;
174
+ const client = this.#client;
175
+ this.#client = undefined;
176
+ client?.close();
177
+ }
178
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * iOS focus auto-zoom suppression — pure decision core (host half, change
3
+ * `2026-09-03-ios-focus-zoom-suppression`).
4
+ *
5
+ * iOS Safari auto-zooms the page (~115–120%) whenever ANY focusable control
6
+ * with a computed font-size below 16px receives focus; DSH's input surfaces
7
+ * are all 13–14px, so every focus on an iPhone trips it. Solution B (user
8
+ * ruling, 2026-09-03): rewrite the viewport meta early — appending
9
+ * `maximum-scale=1, user-scalable=no` — which suppresses the focus zoom on
10
+ * iOS 10+ WITHOUT disabling pinch zoom (Discourse PR #30877 empirics).
11
+ *
12
+ * v0.2.5 refinement (change `2026-09-03-zoomguard-standalone-font-floor`):
13
+ * that empiric holds only IN-BROWSER. Standalone (Add to Home Screen) iOS
14
+ * HONORS `user-scalable=no` — the meta rewrite kills pinch there, and the
15
+ * user's primary usage IS the PWA (real-device datum, 2026-09-03). The guard
16
+ * therefore forks by display mode: browser keeps the meta rewrite untouched;
17
+ * standalone never touches the meta at all and instead injects a 16px
18
+ * font-floor stylesheet (the font-size mechanism kills the focus zoom while
19
+ * pinch stays fully engine-available).
20
+ *
21
+ * The five exported predicates here are the entire decision surface:
22
+ * everything else (state machine, resize re-evaluation, stock-meta
23
+ * reconciliation) lives in the boot script section {@link buildZoomGuardSection}
24
+ * emits. That section embeds these functions via `Function.prototype.toString`
25
+ * — the unit-tested source IS the shipped source, byte for byte — which
26
+ * constrains their shape:
27
+ *
28
+ * - **ES5 syntax only** (the inline head script targets ancient parsers):
29
+ * `var`, function declarations, string concatenation; no arrows, no
30
+ * let/const, no template literals, no destructuring.
31
+ * - **Fully self-contained bodies**: no module-scope identifiers, no helper
32
+ * calls, no shared constants. A bundler may rename the declaration, but a
33
+ * renamed free reference would be a `ReferenceError` on the page. The
34
+ * eval-style tests in `test/web-trust.spec.ts` execute the real generated
35
+ * script against a stub DOM, so any such leak fails the suite.
36
+ *
37
+ * @module agent-omp/mobile/zoom-guard
38
+ */
39
+ /** The two zoom tokens the guard merges into the stock viewport content. */
40
+ export declare const ZOOM_GUARD_TOKENS: Readonly<Record<string, string>>;
41
+ /**
42
+ * Whether a UA string + touch-capability pair is an iOS-class browser
43
+ * (design D2, mirroring Discourse `capabilities.isIOS`): iPhone/iPod/iPad,
44
+ * plus iPadOS 13+ desktop-Safari masquerade (`Macintosh` UA with a
45
+ * multi-touch digitizer). Deliberately NOT `pointer:coarse` — Android Chrome
46
+ * never focus-zooms and `maximum-scale` has historical side effects there,
47
+ * so the gate is tightened to the iOS family. UA-shape drift degrades
48
+ * benignly: a novel UA simply reads as non-iOS and no rewriting happens.
49
+ *
50
+ * @param ua - `navigator.userAgent`.
51
+ * @param maxTouchPoints - `navigator.maxTouchPoints` (0 when absent).
52
+ * @returns `true` for iOS-class browsers.
53
+ */
54
+ export declare function isIOSClassUA(ua: string, maxTouchPoints: number): boolean;
55
+ /**
56
+ * Token-level merge of viewport directives into a viewport meta `content`
57
+ * string (design D4): existing tokens keep their positions, a token whose key
58
+ * (compared case-insensitively) is in `tokens` is REPLACED by the configured
59
+ * key=value, and configured keys not present are APPENDED in insertion order.
60
+ * Idempotent by construction — merging always re-derives from the input, so
61
+ * repeated evaluation cannot accumulate duplicates.
62
+ *
63
+ * mergeViewportTokens('width=device-width, initial-scale=1', T)
64
+ * → 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'
65
+ * mergeViewportTokens('width=device-width, maximum-scale=5', T)
66
+ * → 'width=device-width, maximum-scale=1, user-scalable=no' // same-key override, position kept
67
+ *
68
+ * @param content - the stock (or stock-equivalent) content string.
69
+ * @param tokens - the directives to merge, keyed lowercase.
70
+ * @returns the merged content string.
71
+ */
72
+ export declare function mergeViewportTokens(content: string, tokens: Record<string, string>): string;
73
+ /**
74
+ * The double gate (design D2/D3 + the `zoomGuard` config): rewrite applies
75
+ * only on an iOS-class browser AND a narrow viewport AND a non-`off`
76
+ * `zoomGuard`. An absent payload (or absent key) means the `'meta'` default —
77
+ * the same default-on posture as the mobile leg itself — so only an explicit
78
+ * `'off'` disarms the guard. Unknown non-`off` values read as `'meta'`
79
+ * (schema validation makes them unreachable in production; benign here).
80
+ *
81
+ * @param config - the `__AGENT_OMP_MOBILE__` payload (only `zoomGuard` is read).
82
+ * @param isIOS - {@link isIOSClassUA} verdict for this page load.
83
+ * @param isNarrow - `matchMedia('(max-width: breakpoint-0.02px)')` verdict.
84
+ * @returns `true` when the viewport rewrite should be applied.
85
+ */
86
+ export declare function shouldApplyZoomGuard(config: {
87
+ zoomGuard?: string;
88
+ } | undefined, isIOS: boolean, isNarrow: boolean): boolean;
89
+ /**
90
+ * Standalone display-mode verdict (design D4 of change
91
+ * `2026-09-03-zoomguard-standalone-font-floor`): standalone PWAs HONOR
92
+ * `user-scalable=no` (the real-device pinch regression that motivated the
93
+ * display-mode split), so they must never enter the meta-rewrite path. Two
94
+ * sources, strict-`true` OR: the `(display-mode: standalone)` media query
95
+ * (primary) and legacy `navigator.standalone` (older iOS Safari exposes only
96
+ * the latter). Anything else — `undefined` (engine without the source),
97
+ * `false`, or truthy junk — reads as NOT standalone.
98
+ *
99
+ * @param mqMatchesStandalone - `matchMedia('(display-mode: standalone)').matches`.
100
+ * @param navStandalone - `navigator.standalone === true`.
101
+ * @returns `true` in standalone display mode (either source).
102
+ */
103
+ export declare function isStandaloneDisplay(mqMatchesStandalone: boolean | undefined, navStandalone: boolean | undefined): boolean;
104
+ /**
105
+ * The 16px font floor stylesheet (design D3): iOS auto-zooms on focus into
106
+ * ANY control whose computed font-size is below 16px, so in standalone mode
107
+ * — where the meta rewrite is off the table (pinch must stay
108
+ * engine-available) — the floor IS the suppression. Scoped by the same
109
+ * breakpoint band the meta leg uses and derived the same way
110
+ * (`breakpoint - 0.02`: 768 → 767.98, 900 → 899.98); `!important` beats the
111
+ * 13/14px CSS module rules and the body inline var chain;
112
+ * `[contenteditable="true"]` matches only the editable state (React
113
+ * serializes `contentEditable={bool}` to the literal attribute value
114
+ * "true"/"false").
115
+ *
116
+ * @param breakpointPx - the configured `mobile.breakpoint` (raw — this
117
+ * function derives the `-0.02` band itself, do NOT pre-subtract).
118
+ * @returns the complete stylesheet text.
119
+ */
120
+ export declare function buildFontFloorCss(breakpointPx: number): string;
121
+ /**
122
+ * Build the boot script's zoomGuard section: the five decision predicates
123
+ * embedded via `toString()` plus the DOM state machine that applies them.
124
+ * The machine implements design D3/D4 with one discovered refinement —
125
+ *
126
+ * **The standalone fork (2026-09-03, change
127
+ * `2026-09-03-zoomguard-standalone-font-floor`).** Real-device datum:
128
+ * standalone (Add to Home Screen) iOS HONORS `user-scalable=no`, so the
129
+ * meta rewrite that is benign in-browser (pinch stays engine-controlled)
130
+ * kills pinch in the PWA — the user's primary surface. The section
131
+ * therefore forks right after the iOS gate, BEFORE any meta machinery: a
132
+ * one-shot startup verdict (display-mode never flips mid-session) via the
133
+ * `(display-mode: standalone)` MQ or legacy `navigator.standalone` (D4).
134
+ * Standalone → inject the `ios-zoom-font-floor` style element into the
135
+ * head (16px floor, media-query-scoped to the same breakpoint band, D3)
136
+ * and RETURN — no provisional meta, no reconcile observer, no ladder
137
+ * timer, no listeners, byte-identical stock meta (D1). The width dimension
138
+ * lives inside the stylesheet's media query, so the injection itself is
139
+ * NOT width-gated: a wide standalone load still gets the element; the rule
140
+ * simply does not match above the breakpoint. Browser mode runs the v0.2.4
141
+ * machinery unchanged.
142
+ * **Why reconciliation exists.** The webserver splices head injections
143
+ * immediately after the opening `<head>` tag, i.e. BEFORE the stock
144
+ * `<meta name="viewport">` in upstream's `index.html`. At boot-script time
145
+ * the stock meta is therefore NOT yet parsed, so the "meta missing"
146
+ * branch is the NORMAL path on the served shell, not an edge case. A
147
+ * provisional meta is created right away (the timing requirement: the
148
+ * suppressive tokens are live before any application bundle can focus an
149
+ * input), and a MutationObserver watches for the parser to insert the stock
150
+ * meta; the moment it appears, the guard records the true stock content,
151
+ * rewrites the STOCK meta (token merge, D4's actual semantics — restore
152
+ * later writes these exact bytes back), and removes the provisional one —
153
+ * leaving a single-meta document instead of relying on engines'
154
+ * unspecified multi-meta merge behavior. Engines without MutationObserver
155
+ * keep the provisional meta for the page's lifetime (degraded but still
156
+ * suppressive); the observer is never started once the stock meta was
157
+ * found synchronously.
158
+ *
159
+ * **Why the re-evaluation ladder exists (2026-09-03 rework, lead CDP
160
+ * finding on the 4999 instance).** The boot script runs during head parsing,
161
+ * BEFORE the engine applies the stock viewport meta — the layout viewport at
162
+ * first evaluation is the pre-meta engine default (980px observed), so
163
+ * `mq.matches` reads false on an initial narrow load, and the narrowing that
164
+ * follows meta application does NOT reliably dispatch `resize` to the page
165
+ * during load (headless Chrome: never). A ~10ms poll ladder therefore
166
+ * re-evaluates until the guard IS applied, `document.readyState` reaches
167
+ * `'complete'`, or ~2s elapse — the first layout always precedes deferred
168
+ * scripts, so the ladder lands the guard before any application bundle can
169
+ * focus an input. Steady-state ticks are idempotent no-ops; a wide load
170
+ * idles to the readyState stop and leaves no timer behind (a pending tick
171
+ * is also cancelled the moment the guard lands). MediaQuery `change` events
172
+ * (with the legacy `addListener` fallback) form a second re-evaluation
173
+ * channel alongside window `resize`.
174
+ *
175
+ * State machine (ES5, zero dependencies, synchronous first evaluation):
176
+ * `meta` is the element under management (provisional or reconciled stock),
177
+ * `stock` its recorded stock content, `mine` whether we created it,
178
+ * `applied` the last applied gate verdict. `apply(on)` is idempotent per
179
+ * state (no-op when the verdict is unchanged); merges always re-derive
180
+ * from the recorded stock, never from the live content, so resize storms
181
+ * cannot accumulate tokens. Non-iOS pages never wire any listener.
182
+ *
183
+ * Emitted only when the mobile payload exists and `zoomGuard !== 'off'`.
184
+ *
185
+ * @returns the inline section text (truthy `var` declarations + IIFE).
186
+ */
187
+ export declare function buildZoomGuardSection(): string;