super-dsh 0.1.0 → 0.1.2

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/agent-agy/bridge/agy_bridge.py +250 -0
  2. package/agent-agy/bridge/gemini_openai_server.mjs +98 -0
  3. package/agent-agy/cordis.patch.yml +49 -0
  4. package/agent-agy/dist/adapter.d.ts +15 -0
  5. package/agent-agy/dist/adapter.js +42 -0
  6. package/agent-agy/dist/agent-preset-agy.d.ts +47 -0
  7. package/agent-agy/dist/agent-preset-agy.js +169 -0
  8. package/agent-agy/dist/agent-preset-projection.d.ts +31 -0
  9. package/agent-agy/dist/agent-preset-projection.js +33 -0
  10. package/agent-agy/dist/agent.d.ts +137 -0
  11. package/agent-agy/dist/agent.js +493 -0
  12. package/agent-agy/dist/agy-cli-client.d.ts +137 -0
  13. package/agent-agy/dist/agy-cli-client.js +301 -0
  14. package/agent-agy/dist/agy-client.d.ts +160 -0
  15. package/agent-agy/dist/agy-client.js +252 -0
  16. package/agent-agy/dist/agy-sessions.d.ts +24 -0
  17. package/agent-agy/dist/agy-sessions.js +116 -0
  18. package/agent-agy/dist/agy-store.d.ts +39 -0
  19. package/agent-agy/dist/agy-store.js +83 -0
  20. package/agent-agy/dist/inbox.d.ts +26 -0
  21. package/agent-agy/dist/inbox.js +53 -0
  22. package/agent-agy/dist/index.d.ts +80 -0
  23. package/agent-agy/dist/index.js +505 -0
  24. package/agent-agy/dist/models.d.ts +41 -0
  25. package/agent-agy/dist/models.js +74 -0
  26. package/agent-agy/dist/world-plugin.d.ts +9 -0
  27. package/agent-agy/dist/world-plugin.js +97 -0
  28. package/agent-agy/package.json +64 -0
  29. package/agent-claude/cordis.patch.yml +42 -0
  30. package/agent-claude/dist/adapter.js +62 -0
  31. package/agent-claude/dist/agent-preset-claude.js +169 -0
  32. package/agent-claude/dist/agent-preset-projection.js +30 -0
  33. package/agent-claude/dist/agent.js +1271 -0
  34. package/agent-claude/dist/claude-client.js +357 -0
  35. package/agent-claude/dist/claude-events.js +223 -0
  36. package/agent-claude/dist/claude-home.js +29 -0
  37. package/agent-claude/dist/commands.js +182 -0
  38. package/agent-claude/dist/content.js +134 -0
  39. package/agent-claude/dist/inbox.js +53 -0
  40. package/agent-claude/dist/index.js +555 -0
  41. package/agent-claude/dist/input-queue.js +64 -0
  42. package/agent-claude/dist/interaction.js +271 -0
  43. package/agent-claude/dist/models.js +158 -0
  44. package/agent-claude/dist/permission.js +120 -0
  45. package/agent-claude/dist/session-id.js +20 -0
  46. package/agent-claude/dist/session-map.js +165 -0
  47. package/agent-claude/dist/world-plugin.js +14 -0
  48. package/agent-claude/package.json +74 -0
  49. package/agent-codex/README.md +74 -0
  50. package/agent-codex/cordis.patch.yml +77 -0
  51. package/agent-codex/dist/adapter.js +55 -0
  52. package/agent-codex/dist/agent-preset-codex.js +171 -0
  53. package/agent-codex/dist/agent-preset-projection.js +37 -0
  54. package/agent-codex/dist/agent.js +1056 -0
  55. package/agent-codex/dist/app-home.js +65 -0
  56. package/agent-codex/dist/codex-client.js +469 -0
  57. package/agent-codex/dist/codex-events.js +297 -0
  58. package/agent-codex/dist/codex-store.js +190 -0
  59. package/agent-codex/dist/inbox.js +53 -0
  60. package/agent-codex/dist/index.js +605 -0
  61. package/agent-codex/dist/models.js +152 -0
  62. package/agent-codex/dist/permission.js +71 -0
  63. package/agent-codex/dist/session-map.js +170 -0
  64. package/agent-codex/dist/spike.js +46 -0
  65. package/agent-codex/dist/world-plugin.js +96 -0
  66. package/agent-codex/package.json +78 -0
  67. package/agent-hermes/README.md +144 -0
  68. package/agent-hermes/cordis.patch.yml +83 -0
  69. package/agent-hermes/dist/adapter.js +83 -0
  70. package/agent-hermes/dist/agent-preset-hermes.js +171 -0
  71. package/agent-hermes/dist/agent-preset-projection.js +34 -0
  72. package/agent-hermes/dist/agent.js +1068 -0
  73. package/agent-hermes/dist/hermes-client.js +953 -0
  74. package/agent-hermes/dist/hermes-events.js +289 -0
  75. package/agent-hermes/dist/hermes-store.js +158 -0
  76. package/agent-hermes/dist/inbox.js +53 -0
  77. package/agent-hermes/dist/index.js +541 -0
  78. package/agent-hermes/dist/models.js +324 -0
  79. package/agent-hermes/dist/permission.js +60 -0
  80. package/agent-hermes/dist/world-plugin.js +101 -0
  81. package/agent-hermes/package.json +76 -0
  82. package/agent-hub/cordis.patch.yml +12 -0
  83. package/agent-hub/dist/agent-roster.d.ts +34 -0
  84. package/agent-hub/dist/agent-roster.js +27 -0
  85. package/agent-hub/dist/carrier.d.ts +43 -0
  86. package/agent-hub/dist/carrier.js +241 -0
  87. package/agent-hub/dist/client-shim.d.ts +28 -0
  88. package/agent-hub/dist/client-shim.js +251 -0
  89. package/agent-hub/dist/envelope.d.ts +54 -0
  90. package/agent-hub/dist/envelope.js +92 -0
  91. package/agent-hub/dist/gateway.d.ts +28 -0
  92. package/agent-hub/dist/gateway.js +18 -0
  93. package/agent-hub/dist/index-pass.d.ts +22 -0
  94. package/agent-hub/dist/index-pass.js +47 -0
  95. package/agent-hub/dist/index.d.ts +29 -0
  96. package/agent-hub/dist/index.js +29 -0
  97. package/agent-hub/dist/labels.d.ts +45 -0
  98. package/agent-hub/dist/labels.js +83 -0
  99. package/agent-hub/dist/ownership.d.ts +37 -0
  100. package/agent-hub/dist/ownership.js +54 -0
  101. package/agent-hub/dist/roster.d.ts +28 -0
  102. package/agent-hub/dist/roster.js +38 -0
  103. package/agent-hub/dist/rpc.d.ts +38 -0
  104. package/agent-hub/dist/rpc.js +52 -0
  105. package/agent-hub/dist/spawn-world.d.ts +46 -0
  106. package/agent-hub/dist/spawn-world.js +72 -0
  107. package/agent-hub/dist/targets.d.ts +33 -0
  108. package/agent-hub/dist/targets.js +30 -0
  109. package/agent-hub/dist/world-entry.d.ts +31 -0
  110. package/agent-hub/dist/world-entry.js +51 -0
  111. package/agent-hub/dist/world-host.d.ts +69 -0
  112. package/agent-hub/dist/world-host.js +44 -0
  113. package/agent-hub/dist/world-join.d.ts +16 -0
  114. package/agent-hub/dist/world-join.js +122 -0
  115. package/agent-hub/dist/world-mount.d.ts +52 -0
  116. package/agent-hub/dist/world-mount.js +52 -0
  117. package/agent-hub/dist/world-mux.d.ts +72 -0
  118. package/agent-hub/dist/world-mux.js +224 -0
  119. package/agent-hub/dist/world-provision.d.ts +20 -0
  120. package/agent-hub/dist/world-provision.js +122 -0
  121. package/agent-hub/dist/world-web-server.d.ts +162 -0
  122. package/agent-hub/dist/world-web-server.js +248 -0
  123. package/agent-hub/lib/client/index.js +242 -0
  124. package/agent-hub/package.json +65 -0
  125. package/agent-omp/README.md +77 -0
  126. package/agent-omp/cordis.patch.yml +92 -0
  127. package/agent-omp/dist/adapter.js +36 -0
  128. package/agent-omp/dist/agent-preset-omp.js +171 -0
  129. package/agent-omp/dist/agent-preset-projection.js +36 -0
  130. package/agent-omp/dist/agent.js +1145 -0
  131. package/agent-omp/dist/app-home.js +86 -0
  132. package/agent-omp/dist/discovery.js +116 -0
  133. package/agent-omp/dist/inbox.js +53 -0
  134. package/agent-omp/dist/index.js +574 -0
  135. package/agent-omp/dist/knobs.js +50 -0
  136. package/agent-omp/dist/lazy-rpc.js +161 -0
  137. package/agent-omp/dist/mobile/gesture.js +148 -0
  138. package/agent-omp/dist/mobile/zoom-guard.js +294 -0
  139. package/agent-omp/dist/mobile-boot.js +38 -0
  140. package/agent-omp/dist/models.js +299 -0
  141. package/agent-omp/dist/omp-cli.js +147 -0
  142. package/agent-omp/dist/omp-disk-discovery.js +178 -0
  143. package/agent-omp/dist/omp-store.js +518 -0
  144. package/agent-omp/dist/pairing.js +45 -0
  145. package/agent-omp/dist/permission.js +96 -0
  146. package/agent-omp/dist/protocol.js +13 -0
  147. package/agent-omp/dist/rpc-types.js +1 -0
  148. package/agent-omp/dist/sdk-client.js +378 -0
  149. package/agent-omp/dist/sidecar-client.js +195 -0
  150. package/agent-omp/dist/store/adapter.js +47 -0
  151. package/agent-omp/dist/store/db.js +210 -0
  152. package/agent-omp/dist/store/index.js +59 -0
  153. package/agent-omp/dist/store/reconcile.js +51 -0
  154. package/agent-omp/dist/store/schema.js +40 -0
  155. package/agent-omp/dist/world-plugin.js +96 -0
  156. package/agent-omp/package.json +97 -0
  157. package/agent-omp/sidecar/main.ts +481 -0
  158. package/agent-pi/README.md +67 -0
  159. package/agent-pi/cordis.patch.yml +80 -0
  160. package/agent-pi/dist/adapter.js +53 -0
  161. package/agent-pi/dist/agent-preset-pi.js +169 -0
  162. package/agent-pi/dist/agent-preset-projection.js +29 -0
  163. package/agent-pi/dist/agent.js +949 -0
  164. package/agent-pi/dist/inbox.js +53 -0
  165. package/agent-pi/dist/index.js +595 -0
  166. package/agent-pi/dist/knobs.js +24 -0
  167. package/agent-pi/dist/models.js +200 -0
  168. package/agent-pi/dist/permission.js +66 -0
  169. package/agent-pi/dist/pi-client.js +224 -0
  170. package/agent-pi/dist/pi-events.js +155 -0
  171. package/agent-pi/dist/pi-home.js +55 -0
  172. package/agent-pi/dist/safe-json.js +25 -0
  173. package/agent-pi/dist/session-map.js +119 -0
  174. package/agent-pi/dist/world-plugin.js +97 -0
  175. package/agent-pi/package.json +77 -0
  176. package/cordis.patch.yml +22 -19
  177. package/package.json +88 -25
  178. package/client.js +0 -4
@@ -0,0 +1,505 @@
1
+ /**
2
+ * agent-agy provider — Google Antigravity (Gemini) provider plugin for DeepSeek
3
+ * Harness (Agent Worlds, consumer-first line, 2026-09-18).
4
+ *
5
+ * An `AgentFactory` that drives an `AgyBridgeClient` (the Python google-
6
+ * antigravity SDK bridge child — localharness materializes inside it on the
7
+ * FIRST prompt, so create/resume spawn nothing). Auth is consumer-first:
8
+ * a Gemini API key persisted in the adapter's world-home state, or ADC via the
9
+ * environment (the SDK picks it up; we never touch the agy CLI — dev-rules §16a).
10
+ *
11
+ * Session identity ("mapping only"): the DSH session id is the authority,
12
+ * supplied by the harness. The SDK conversation id materializes after the
13
+ * first turn, so the pairing is persisted in `<worldHome>/dsh-sessions.json`
14
+ * (agy-sessions.ts). Resume resolves that record and fails CLOSED
15
+ * (CONVERSATION_NOT_FOUND) when no conversation is recorded. The DSH session
16
+ * log is the transcript authority, written through the upstream
17
+ * `sessionPersistence` service (hermes create/resume transaction shape).
18
+ */
19
+ import { realpathSync, statSync } from "node:fs";
20
+ import { join } from "node:path";
21
+ import { Service } from "@deepseek-ai/cordis";
22
+ import { emitAgentEvent, installModelSelection } from "@deepseek-ai/dsh-agent";
23
+ import { SessionLogOffset, SessionPreparation, interruptedTurnClosers, } from "@deepseek-ai/dsh-session";
24
+ import { AgyBridgeClient } from "./agy-client.js";
25
+ import { AgyCliClient } from "./agy-cli-client.js";
26
+ import { SingleAgyPresetRoster } from "./agent-preset-agy.js";
27
+ import { agyAgentPresetProjection } from "./agent-preset-projection.js";
28
+ import { readAdapterState, writeAdapterState, readNativeDefaultModel, hasAdcEvidence, readGcloudProject } from "./agy-store.js";
29
+ import { AgyLlmAdapter, AGY_PROVIDER_ID } from "./adapter.js";
30
+ import { AgyAgent } from "./agent.js";
31
+ import { sessionRecord, upsertSession } from "./agy-sessions.js";
32
+ import { AGY_MODEL_CATALOG, CLI_DEFAULT_MODEL, parseModelId, toModelSlug } from "./models.js";
33
+ /** Realpath of a recorded cwd, accepted only when it names an existing directory. */
34
+ /** Extract the session-selected model id (string form) from agent options. */
35
+ function agentOptionsModel(agentOptions) {
36
+ const m = agentOptions?.model;
37
+ return typeof m === "string" && m !== "" ? m : undefined;
38
+ }
39
+ function validatedCwd(cwd) {
40
+ if (cwd === undefined)
41
+ return undefined;
42
+ try {
43
+ const canonical = realpathSync(cwd);
44
+ return statSync(canonical).isDirectory() ? canonical : undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ /** Diagnostic trace (AGY_TRACE=1 on the dsh process enables stderr tracing). */
51
+ const trace = (...parts) => {
52
+ if (process.env.AGY_TRACE === "1")
53
+ process.stderr.write(`[agy-provider ${Date.now() % 1_000_000}] ${parts.join(" ")}\n`);
54
+ };
55
+ /** Blank-session doctrine (§14 i): conversation content = turns actually ran. */
56
+ function hasConversationContent(events) {
57
+ return events.some((event) => {
58
+ if (event.type === "turn/start" || event.type === "assistant/message")
59
+ return true;
60
+ if (event.type === "user/message") {
61
+ const data = event.data;
62
+ return data?.source?.kind === "user";
63
+ }
64
+ return false;
65
+ });
66
+ }
67
+ /** Open a read handle, pull the complete stored log, close. */
68
+ async function readStoredEvents(persistence, id, signal) {
69
+ const handle = await persistence.open(id, "read", signal === undefined ? {} : { signal });
70
+ try {
71
+ return (await handle.read(0, undefined, signal === undefined ? {} : { signal })).events;
72
+ }
73
+ finally {
74
+ await handle.close();
75
+ }
76
+ }
77
+ export class AgyProvider extends Service {
78
+ static inject = ["agents", "sessions", "llm", "agentDefaultModel", "settings"];
79
+ /** Plain holder — prevents Cordis re-tracing the factory's ctx through a caller shadow. */
80
+ runtime;
81
+ /** The world's DSH home — anchors adapter-owned state (session map, api-key state). */
82
+ home;
83
+ opts;
84
+ defaultModelKey = "";
85
+ constructor(ctx, opts = {}) {
86
+ super(ctx, "agyProvider");
87
+ this.runtime = { ctx };
88
+ this.opts = opts;
89
+ this.home = this.resolveHome();
90
+ trace(`home: worldHome=${this.home}`);
91
+ ctx.effect(() => ctx.agents.setFactory(this), "agyProvider.setFactory()");
92
+ // The roster must be visible to the API gateway's root-level remote
93
+ // enumeration; register it on this plugin's own (top-level) fiber.
94
+ new SingleAgyPresetRoster(ctx);
95
+ // Drive the `agentPreset` session projection ourselves.
96
+ ctx.inject(["sessionProjections"], (scoped) => {
97
+ scoped.sessionProjections.register(agyAgentPresetProjection);
98
+ });
99
+ this.registerModelCatalog();
100
+ this.registerDefaultModel();
101
+ }
102
+ /** The world's DSH home: boot-provided dshHomePath first, env last. */
103
+ resolveHome() {
104
+ const fromBoot = this.runtime.ctx.get("dshHomePath");
105
+ if (typeof fromBoot === "string" && fromBoot !== "")
106
+ return fromBoot;
107
+ if (typeof fromBoot === "function") {
108
+ const resolved = fromBoot();
109
+ if (typeof resolved === "string" && resolved !== "")
110
+ return resolved;
111
+ }
112
+ return process.env.DSH_HOME ?? join(process.cwd(), ".tests", "aw");
113
+ }
114
+ /** Effective Gemini API key: adapter state first, then the environment. */
115
+ resolveApiKey() {
116
+ const state = readAdapterState(this.home);
117
+ if (typeof state.apiKey === "string" && state.apiKey !== "")
118
+ return state.apiKey;
119
+ const env = process.env.GEMINI_API_KEY;
120
+ return typeof env === "string" && env !== "" ? env : undefined;
121
+ }
122
+ /**
123
+ * Auth evidence for the §17 onboarding gate: an API key (state or env) OR
124
+ * ADC evidence is enough — the SDK consumes either transparently, so a user
125
+ * with gcloud ADC must NOT be pushed into key onboarding.
126
+ */
127
+ hasAuthEvidence() {
128
+ if (this.resolveApiKey() !== undefined)
129
+ return true;
130
+ return hasAdcEvidence(process.env, process.env.HOME ?? "");
131
+ }
132
+ /** Default model id: adapter state → native TUI default (slug-normalized) → catalog head. */
133
+ resolveDefaultModelId() {
134
+ const state = readAdapterState(this.home);
135
+ if (typeof state.defaultModel === "string" && parseModelId(state.defaultModel) !== undefined)
136
+ return state.defaultModel;
137
+ const native = readNativeDefaultModel(this.opts.nativeHome ?? join(process.env.HOME ?? "", ".gemini"));
138
+ const slug = toModelSlug(native);
139
+ if (slug !== undefined)
140
+ return slug;
141
+ return AGY_MODEL_CATALOG[0].slug;
142
+ }
143
+ /** Legacy slug-only default (default-model push keeps slug form). */
144
+ resolveDefaultModel() {
145
+ const state = readAdapterState(this.home);
146
+ if (typeof state.defaultModel === "string" && state.defaultModel !== "")
147
+ return state.defaultModel;
148
+ const native = readNativeDefaultModel(this.opts.nativeHome ?? join(process.env.HOME ?? "", ".gemini"));
149
+ const slug = toModelSlug(native);
150
+ if (slug !== undefined)
151
+ return slug;
152
+ return AGY_MODEL_CATALOG[0].slug;
153
+ }
154
+ /** Static route: provider `agy` + the resolved default (or the explicit ask).
155
+ * The selected model id (`slug` or `slug@level`) rides in `options.model`
156
+ * so clientOptions can forward slug+thinkingLevel to the bridge start frame. */
157
+ sessionAgentOptions(requested) {
158
+ const requestedModel = typeof requested?.model === "string" ? requested.model : undefined;
159
+ const explicit = requestedModel !== undefined && parseModelId(requestedModel) !== undefined
160
+ ? requestedModel
161
+ : undefined;
162
+ const model = explicit ?? this.resolveDefaultModelId();
163
+ return { ...(requested ?? {}), provider: AGY_PROVIDER_ID, model };
164
+ }
165
+ requirePersistence(op) {
166
+ const persistence = this.runtime.ctx.get("sessionPersistence");
167
+ if (persistence === undefined) {
168
+ throw new Error(`cannot ${op} an Agy session: session persistence is not configured`);
169
+ }
170
+ return persistence;
171
+ }
172
+ agentRuntime(cwd) {
173
+ const provider = this;
174
+ return {
175
+ session: { cwd },
176
+ awaitingKey: !provider.hasAuthEvidence(),
177
+ onSaveApiKey: (key) => {
178
+ writeAdapterState(provider.home, { ...readAdapterState(provider.home), apiKey: key });
179
+ trace("onboarding: api key persisted to adapter state (world home)");
180
+ },
181
+ onTurnDone: undefined, // replaced per-session by followConversationId below
182
+ };
183
+ }
184
+ /** Persist the Dash↔conversation pairing once the first turn materializes it. */
185
+ followConversationId = (id, client) => {
186
+ const cid = client.conversationId;
187
+ if (cid === "")
188
+ return;
189
+ try {
190
+ upsertSession(this.home, String(id), { conversationId: cid });
191
+ trace(`conversation id recorded for ${String(id)}: ${cid}`);
192
+ }
193
+ catch (error) {
194
+ this.runtime.ctx.logger.warn(`agy-provider: failed to persist the conversation id for ${String(id)}: ${String(error)}`);
195
+ }
196
+ };
197
+ registerModelCatalog() {
198
+ const llm = this.runtime.ctx.get("llm");
199
+ if (llm === undefined)
200
+ return;
201
+ llm.registerAdapter([AGY_PROVIDER_ID], new AgyLlmAdapter());
202
+ }
203
+ /** Push the resolved default model into the world's `agentDefaultModel`. */
204
+ registerDefaultModel() {
205
+ this.runtime.ctx.inject(["agentDefaultModel", "settings"], (mCtx) => {
206
+ const service = mCtx.get("agentDefaultModel");
207
+ if (service === undefined)
208
+ return;
209
+ const target = { provider: AGY_PROVIDER_ID, model: this.resolveDefaultModel() };
210
+ const targetKey = `${target.provider}/${target.model}`;
211
+ const current = service.currentSelection?.();
212
+ const currentKey = current === undefined ? undefined : `${current.provider}/${current.model}`;
213
+ if (currentKey === targetKey || currentKey === this.defaultModelKey)
214
+ return;
215
+ trace(`default model: ${currentKey ?? "none"} → ${targetKey}`);
216
+ const settings = mCtx.get("settings");
217
+ void Promise.resolve(settings?.replace?.("agent-default-model", target))
218
+ .then(() => service.saveSelection?.(target))
219
+ .then(() => {
220
+ this.defaultModelKey = targetKey;
221
+ this.runtime.ctx.logger.info(`agy-provider: default model ← static catalog / native settings: ${targetKey}`);
222
+ })
223
+ .catch((error) => {
224
+ this.runtime.ctx.logger.warn(`agy-provider: default model save failed: ${String(error)}`);
225
+ });
226
+ });
227
+ }
228
+ async createAgent(ownerCtx, options) {
229
+ const loopCtx = this.runtime.ctx;
230
+ const id = options.sessionId;
231
+ const meta = options.meta ?? {};
232
+ const cwd = meta.cwd;
233
+ if (meta.parentSession !== undefined) {
234
+ throw new Error(`cannot fork session "${meta.parentSession}" onto the Agy provider: Antigravity has no native session fork`);
235
+ }
236
+ const persistence = this.requirePersistence("create");
237
+ const spawnCwd = validatedCwd(cwd) ?? process.cwd();
238
+ trace(`create id=${id} spawnCwd=${spawnCwd}`);
239
+ // Full-lazy (§14-i): construct the client (spawns NOTHING); the bridge
240
+ // child materializes on the first real prompt.
241
+ const client = this.createLiveClient(spawnCwd, agentOptionsModel(options.agentOptions), options.agentOptions?.reasoningEffort);
242
+ const preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
243
+ ...(options.seed === undefined ? {} : { seed: options.seed }),
244
+ ...(meta === undefined ? {} : { meta }),
245
+ ...(options.inheritedEventCount === undefined ? {} : { inheritedEventCount: options.inheritedEventCount }),
246
+ }));
247
+ let stored;
248
+ let handedOff = false;
249
+ try {
250
+ stored = await this.createStoredSession(persistence, preparation.session, options.signal);
251
+ upsertSession(this.home, String(id), { conversationId: null, cwd: spawnCwd, createdAt: Date.now(), preset: null });
252
+ handedOff = true;
253
+ const agentOptions = await this.sessionAgentOptions(options.agentOptions);
254
+ return await setupAndPublish(loopCtx, ownerCtx, id, agentOptions, options.setup, preparation.session, client, options.parentAgent, "startup", { enterSession: true, preparation }, stored, this.agentRuntime(spawnCwd), () => this.followConversationId(id, client));
255
+ }
256
+ catch (error) {
257
+ trace(`create ${id} THREW: ${String(error)}`);
258
+ void client.close();
259
+ if (!handedOff)
260
+ await stored?.handle.close().catch(() => { });
261
+ throw error;
262
+ }
263
+ finally {
264
+ if (!handedOff)
265
+ preparation[Symbol.dispose]();
266
+ }
267
+ }
268
+ async resume(ownerCtx, options) {
269
+ const loopCtx = this.runtime.ctx;
270
+ const id = options.resumeSessionId;
271
+ // Identity ("mapping only") with BLANK-SESSION TOLERANCE (2026-09-18,
272
+ // dev-rules §14 i — pi doctrine mirror): the Dash host eagerly resumes on
273
+ // view/model-change, so a never-materialized session (no record, or a
274
+ // record whose native conversation never produced a turn) resumes AS
275
+ // FRESH — zero processes until the first prompt. A MISSING record with
276
+ // conversation-bearing DSH log content stays a genuine orphan → fail loud.
277
+ const record = sessionRecord(this.home, String(id));
278
+ const blank = record === undefined || record.conversationId === null || record.conversationId === "";
279
+ trace(`resume id=${id} record=${record === undefined ? "MISSING" : record.conversationId ?? "NO-CONVERSATION"}${blank ? " (blank-tolerant)" : ""}`);
280
+ const persistence = this.requirePersistence("resume");
281
+ let orphanCheck;
282
+ if (record === undefined) {
283
+ orphanCheck = await readStoredEvents(persistence, id, options.signal);
284
+ if (hasConversationContent(orphanCheck)) {
285
+ const error = new Error(`cannot resume session "${id}": no Antigravity conversation is recorded for this Dash session id — start a new session`);
286
+ error.code = "CONVERSATION_NOT_FOUND";
287
+ throw error;
288
+ }
289
+ }
290
+ const spawnCwd = blank
291
+ ? (record !== undefined ? validatedCwd(record.cwd) ?? process.cwd() : process.cwd())
292
+ : (() => {
293
+ const verified = validatedCwd(record.cwd);
294
+ if (verified === undefined) {
295
+ throw new Error(`cannot resume session "${id}": its recorded working directory no longer exists`);
296
+ }
297
+ return verified;
298
+ })();
299
+ const client = this.createLiveClient(spawnCwd, agentOptionsModel(options.agentOptions), options.agentOptions?.reasoningEffort);
300
+ let handle;
301
+ let stored;
302
+ let preparation;
303
+ let handedOff = false;
304
+ try {
305
+ handle = await persistence.open(id, "write", options.signal === undefined ? {} : { signal: options.signal });
306
+ const cold = await handle.read(0, undefined, options.signal === undefined ? {} : { signal: options.signal });
307
+ const closers = interruptedTurnClosers(cold.events);
308
+ if (closers.length > 0)
309
+ await handle.append(closers);
310
+ preparation = SessionPreparation.create(loopCtx.sessions.prepare(id, {
311
+ seed: [...cold.events, ...closers],
312
+ meta: structuredClone(handle.header),
313
+ inheritedEventCount: handle.inheritedEventCount,
314
+ eventState: cold.eventState,
315
+ }));
316
+ stored = { handle, storedCount: cold.events.length + closers.length };
317
+ trace(`resume id=${id} seeded ${cold.events.length} events (+${closers.length} closers)`);
318
+ handle = undefined;
319
+ handedOff = true;
320
+ const agentOptions = await this.sessionAgentOptions(options.agentOptions);
321
+ return await setupAndPublish(loopCtx, ownerCtx, id, agentOptions, options.setup, preparation.session, client, options.parentAgent, "resume", { enterSession: true, preparation }, stored, this.agentRuntime(spawnCwd), () => this.followConversationId(id, client));
322
+ }
323
+ catch (error) {
324
+ trace(`resume ${id} THREW: ${String(error)}`);
325
+ void client.close();
326
+ if (!handedOff)
327
+ await handle?.close().catch(() => { });
328
+ throw error;
329
+ }
330
+ finally {
331
+ if (!handedOff)
332
+ preparation?.[Symbol.dispose]();
333
+ }
334
+ }
335
+ /**
336
+ * Live transport routing (2026-09-18 user ruling): gemini-api-key > oauth >
337
+ * adc. Key present → CLI stream (richest surface). ADC-only → SDK bridge.
338
+ * Neither → CLI as well (auth fails at first prompt; the §17 onboarding
339
+ * branch answers locally before any spawn).
340
+ */
341
+ createLiveClient(cwd, modelId, reasoningEffort) {
342
+ const apiKey = this.resolveApiKey();
343
+ if (apiKey === undefined && hasAdcEvidence(process.env, process.env.HOME ?? "")) {
344
+ return new AgyBridgeClient(this.clientOptions(cwd, modelId, reasoningEffort));
345
+ }
346
+ return new AgyCliClient(this.cliOptions(cwd, modelId, reasoningEffort));
347
+ }
348
+ /**
349
+ * The effective permission preset this provider was mounted with. The
350
+ * cordis.patch.yml default is danger-full-access; a patch-level override
351
+ * would surface through the permission service config, which we read
352
+ * best-effort (fail → the patch default).
353
+ */
354
+ currentPermissionPreset() {
355
+ try {
356
+ const permission = this.runtime.ctx.get("permission");
357
+ return permission?.config?.defaultPreset ?? "danger-full-access";
358
+ }
359
+ catch {
360
+ return "danger-full-access";
361
+ }
362
+ }
363
+ /** CLI transport options: key-first default is the cheapest live pair. */
364
+ cliOptions(cwd, modelId, reasoningEffort) {
365
+ const parsed = parseModelId(modelId ?? this.resolveDefaultModelId());
366
+ const slug = parsed?.slug ?? CLI_DEFAULT_MODEL.slug;
367
+ // Effort precedence: explicit session reasoningEffort (WebUI Effort
368
+ // selector → AgentOptions) > legacy @level id suffix > CLI cheap default.
369
+ const level = reasoningEffort ?? parsed?.thinkingLevel ?? CLI_DEFAULT_MODEL.thinkingLevel;
370
+ return {
371
+ apiKey: () => this.resolveApiKey(),
372
+ model: slug,
373
+ ...(level === undefined ? {} : { effort: level }),
374
+ cwd,
375
+ proxy: this.opts.proxy ?? process.env.AGY_PROXY,
376
+ // Preset → permission mapping (cordis.patch.yml default
377
+ // danger-full-access): headless has no interactive approval, so the
378
+ // full-access preset maps to --dangerously-skip-permissions; the
379
+ // tighter presets keep the CLI's request-review (soft-deny, honest).
380
+ skipPermissions: (this.opts.skipPermissions ?? (this.currentPermissionPreset() === "danger-full-access")),
381
+ agyBin: this.opts.agyBin,
382
+ nativeCliHome: this.opts.nativeHome,
383
+ home: this.opts.cliHome ?? process.env.AGY_CLI_HOME,
384
+ spawnAttempts: this.opts.spawnAttempts,
385
+ };
386
+ }
387
+ /** Bridge child options: world cwd + overrides; the api key rides the start config. */
388
+ clientOptions(cwd, modelId, reasoningEffort) {
389
+ // Auth plane (§16a): API key when present; otherwise the SDK's Vertex
390
+ // endpoint over ADC — which needs an explicit project, read from the
391
+ // gcloud CLI default. Without either, sessions gate into §17 onboarding.
392
+ const apiKey = this.resolveApiKey();
393
+ let project = this.opts.project;
394
+ let location = this.opts.location;
395
+ if (apiKey === undefined && project === undefined && hasAdcEvidence(process.env, process.env.HOME ?? "")) {
396
+ project = readGcloudProject(process.env.HOME ?? "");
397
+ location = location ?? "global";
398
+ }
399
+ // Session-selected model id wins over the provider default (previously a
400
+ // split-brain: the session showed one model, the bridge started another).
401
+ // `slug@level` decodes into model + thinking_level for the start frame.
402
+ const parsed = parseModelId(modelId ?? this.resolveDefaultModelId());
403
+ return {
404
+ apiKey,
405
+ project,
406
+ location,
407
+ model: parsed?.slug ?? this.resolveDefaultModelId(),
408
+ // TUI parity: the native default ships (Medium); without an explicit
409
+ // level the bridge sends none and thinking blocks rarely stream.
410
+ thinkingLevel: parsed?.thinkingLevel ?? "medium",
411
+ approvalMode: this.opts.approvalMode ?? "allow",
412
+ workspaces: [cwd],
413
+ bridgePath: this.opts.bridgePath,
414
+ pythonBin: this.opts.pythonBin,
415
+ spawnAttempts: this.opts.spawnAttempts,
416
+ };
417
+ }
418
+ async createStoredSession(persistence, session, signal) {
419
+ const handle = await persistence.create(session.header, {
420
+ inheritedEventCount: session.inheritedEventCount,
421
+ ...(signal === undefined ? {} : { signal }),
422
+ });
423
+ return { handle, storedCount: 0 };
424
+ }
425
+ }
426
+ /** Durable flush of the session's pre-publication suffix. */
427
+ async function appendUnstoredSuffix(stored, session) {
428
+ const suffix = session.snapshotEvents(SessionLogOffset(stored.storedCount));
429
+ if (suffix.length > 0)
430
+ await stored.handle.append(suffix);
431
+ stored.storedCount += suffix.length;
432
+ }
433
+ /** Shared create/resume transaction (module-level: Cordis tracing proxy). */
434
+ async function setupAndPublish(loopCtx, ownerCtx, id, agentOptions, setup, session, client, parentAgent, source, opts, stored, runtimeInfo, onTurnDone) {
435
+ const runtime = { ...runtimeInfo, onTurnDone };
436
+ let detachSession;
437
+ let detachAgent;
438
+ let agent;
439
+ let idleExit;
440
+ try {
441
+ agent = new AgyAgent(loopCtx, id, agentOptions, session, client, () => idleExit?.(), runtime);
442
+ // Pin THIS session to the agy route (static resolution — synchronous).
443
+ try {
444
+ const selection = { provider: agentOptions.provider ?? "agy", model: agentOptions.model ?? "" };
445
+ if (selection.model !== "") {
446
+ let picked = selection;
447
+ installModelSelection(agent.ctx, {
448
+ get current() { return picked; },
449
+ set current(next) { picked = next; },
450
+ assembled: undefined,
451
+ });
452
+ session.append("model/selection", selection);
453
+ trace(`session route pinned: ${selection.provider}/${selection.model}`);
454
+ }
455
+ }
456
+ catch (error) {
457
+ trace(`session route pin failed: ${String(error)}`);
458
+ }
459
+ const commit = await setup?.(agent.ctx, agent);
460
+ commit?.commit();
461
+ await appendUnstoredSuffix(stored, session);
462
+ if (opts.enterSession) {
463
+ detachSession = agent.ctx.sessions.enter(session);
464
+ agent.ctx.sessions.announce(session);
465
+ }
466
+ detachAgent = loopCtx.agents.enter(agent, parentAgent);
467
+ loopCtx.agents.announce(agent);
468
+ emitAgentEvent(loopCtx, agent, "agent/session-start", { source });
469
+ let disposed = false;
470
+ let unfollowOwner;
471
+ const dispose = async () => {
472
+ if (disposed)
473
+ return;
474
+ disposed = true;
475
+ unfollowOwner?.();
476
+ await agent?.dispose();
477
+ detachAgent?.();
478
+ detachSession?.();
479
+ await stored.handle.close();
480
+ };
481
+ idleExit = () => {
482
+ void dispose().catch((error) => {
483
+ loopCtx.logger.warn(`agy-provider: teardown of ${id} failed: ${String(error)}`);
484
+ });
485
+ };
486
+ unfollowOwner = ownerCtx.effect(() => () => {
487
+ void dispose().catch((error) => {
488
+ loopCtx.logger.warn(`agy-provider: owner-unload teardown of ${id} failed: ${String(error)}`);
489
+ });
490
+ }, `agyProvider.lifecycle(${id})`);
491
+ return { agent, dispose };
492
+ }
493
+ catch (error) {
494
+ trace(`setupAndPublish ${id} FAILED: ${String(error)}`);
495
+ detachAgent?.();
496
+ detachSession?.();
497
+ void agent?.dispose().catch(() => { });
498
+ void client.close();
499
+ throw error;
500
+ }
501
+ finally {
502
+ opts.preparation?.[Symbol.dispose]();
503
+ }
504
+ }
505
+ export default AgyProvider;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Static model catalog for the Gemini consumer surface. The google-antigravity
3
+ * SDK has no model-list API and the CLI catalog path is auth-gated, so the
4
+ * catalog is static AND endpoint-verified (2026-09-18 probes on both auth
5
+ * planes; non-listed slugs 404 / are retired).
6
+ *
7
+ * Model ids are PLAIN slugs — thinking effort is a per-route option surfaced
8
+ * through the adapter's reasoning metadata (WebUI Effort selector →
9
+ * AgentOptions.reasoningEffort), NOT extra catalog entries (user ruling
10
+ * 2026-09-18).
11
+ */
12
+ export interface AgyCatalogModel {
13
+ slug: string;
14
+ label: string;
15
+ /** Effort levels the CLI/SDK accept for this slug (absent = no effort param). */
16
+ efforts?: readonly string[];
17
+ defaultEffort?: string;
18
+ }
19
+ export declare const AGY_MODEL_CATALOG: readonly AgyCatalogModel[];
20
+ /** Selectable model ids (plain slugs). */
21
+ export declare function modelIds(): string[];
22
+ export declare function catalogSlugs(): string[];
23
+ /** Parse a selectable model id: plain slug, or legacy `slug@level`. */
24
+ export declare function parseModelId(id: string | undefined): {
25
+ slug: string;
26
+ thinkingLevel?: string;
27
+ } | undefined;
28
+ /** Efforts for a slug (undefined = the slug takes no effort parameter). */
29
+ export declare function effortsFor(slug: string): readonly string[] | undefined;
30
+ export declare function defaultEffortFor(slug: string): string | undefined;
31
+ /** Cheapest live pair on the CLI key path (probed 2026-09-18). CLI default. */
32
+ export declare const CLI_DEFAULT_MODEL: {
33
+ slug: string;
34
+ thinkingLevel: string;
35
+ };
36
+ /**
37
+ * Normalize a model reference to an SDK-facing slug. The native TUI settings
38
+ * store display names ("Gemini 3.7 Flash (Medium)"); slugs pass through;
39
+ * unknown display names → undefined (caller uses the catalog head).
40
+ */
41
+ export declare function toModelSlug(model: string | undefined): string | undefined;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Static model catalog for the Gemini consumer surface. The google-antigravity
3
+ * SDK has no model-list API and the CLI catalog path is auth-gated, so the
4
+ * catalog is static AND endpoint-verified (2026-09-18 probes on both auth
5
+ * planes; non-listed slugs 404 / are retired).
6
+ *
7
+ * Model ids are PLAIN slugs — thinking effort is a per-route option surfaced
8
+ * through the adapter's reasoning metadata (WebUI Effort selector →
9
+ * AgentOptions.reasoningEffort), NOT extra catalog entries (user ruling
10
+ * 2026-09-18).
11
+ */
12
+ export const AGY_MODEL_CATALOG = [
13
+ { slug: "gemini-3.7-flash", label: "Gemini 3.7 Flash", efforts: ["low", "medium", "high"], defaultEffort: "medium" },
14
+ { slug: "gemini-3.6-flash", label: "Gemini 3.6 Flash", efforts: ["low", "medium", "high"], defaultEffort: "low" },
15
+ { slug: "gemini-3.5-flash", label: "Gemini 3.5 Flash", efforts: ["low", "medium", "high"], defaultEffort: "medium" },
16
+ { slug: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
17
+ { slug: "gemini-2.5-flash", label: "Gemini 2.5 Flash" },
18
+ ];
19
+ /** Selectable model ids (plain slugs). */
20
+ export function modelIds() {
21
+ return AGY_MODEL_CATALOG.map((m) => m.slug);
22
+ }
23
+ export function catalogSlugs() {
24
+ return modelIds();
25
+ }
26
+ /** Parse a selectable model id: plain slug, or legacy `slug@level`. */
27
+ export function parseModelId(id) {
28
+ if (id === undefined)
29
+ return undefined;
30
+ const at = id.indexOf("@");
31
+ const slug = at < 0 ? id : id.slice(0, at);
32
+ if (!catalogSlugs().includes(slug))
33
+ return undefined;
34
+ if (at < 0)
35
+ return { slug };
36
+ const level = id.slice(at + 1);
37
+ const entry = AGY_MODEL_CATALOG.find((m) => m.slug === slug);
38
+ if (entry?.efforts && entry.efforts.includes(level))
39
+ return { slug, thinkingLevel: level };
40
+ return { slug };
41
+ }
42
+ /** Efforts for a slug (undefined = the slug takes no effort parameter). */
43
+ export function effortsFor(slug) {
44
+ return AGY_MODEL_CATALOG.find((m) => m.slug === slug)?.efforts;
45
+ }
46
+ export function defaultEffortFor(slug) {
47
+ return AGY_MODEL_CATALOG.find((m) => m.slug === slug)?.defaultEffort;
48
+ }
49
+ /** Cheapest live pair on the CLI key path (probed 2026-09-18). CLI default. */
50
+ export const CLI_DEFAULT_MODEL = {
51
+ slug: "gemini-3.6-flash",
52
+ thinkingLevel: "low",
53
+ };
54
+ /**
55
+ * Normalize a model reference to an SDK-facing slug. The native TUI settings
56
+ * store display names ("Gemini 3.7 Flash (Medium)"); slugs pass through;
57
+ * unknown display names → undefined (caller uses the catalog head).
58
+ */
59
+ export function toModelSlug(model) {
60
+ if (!model)
61
+ return undefined;
62
+ const v = model.trim();
63
+ if (!v)
64
+ return undefined;
65
+ if (/^[a-z0-9.-]+$/.test(v) && !v.includes(" "))
66
+ return v;
67
+ const base = v.replace(/\s*\([^)]*\)\s*$/, "").trim().toLowerCase();
68
+ const slug = base.replace(/\s+/g, "-");
69
+ if (catalogSlugs().includes(slug))
70
+ return slug;
71
+ if (/^gemini-[0-9]/.test(slug))
72
+ return slug;
73
+ return undefined;
74
+ }
@@ -0,0 +1,9 @@
1
+ export declare const name = "aw.agent-adapter-agy";
2
+ /** Structural slice of the ctx0 services the mount needs. */
3
+ interface HostContext {
4
+ provide(name: string, value: unknown): void;
5
+ inject(deps: string[], callback: (ctx: unknown) => void): unknown;
6
+ effect?(callback: () => () => void, label?: string): unknown;
7
+ }
8
+ export declare function apply(ctx: HostContext): void;
9
+ export {};