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,252 @@
1
+ /**
2
+ * agy-client.ts — drives the Python SDK bridge (bridge/agy_bridge.py) over
3
+ * JSONL stdio.
4
+ *
5
+ * Discipline (agent-adapter-dev-rules §14-i / §16a):
6
+ * - LAZY: no python child until the first real prompt. `start` config is
7
+ * buffered and flushed with the first `prompt` (one round trip).
8
+ * - Transient spawn failures retry in-place up to AGY_SPAWN_ATTEMPTS (3);
9
+ * prompt-level errors are surfaced, never auto-retried.
10
+ * - The agy CLI is never invoked; auth is transparent (api_key param or ADC
11
+ * environment picked up by the SDK itself).
12
+ */
13
+ import { spawn } from "node:child_process";
14
+ import { existsSync, readFileSync } from "node:fs";
15
+ import { dirname, join } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ const BRIDGE_RELATIVE = join("bridge", "agy_bridge.py");
18
+ export class AgyBridgeClient {
19
+ child;
20
+ buffer = "";
21
+ waiters = [];
22
+ pending = [];
23
+ opts;
24
+ spawnFailures = 0;
25
+ /** Thinking-delta sink (agent buffers these into a reasoning block). */
26
+ onThinkingDelta;
27
+ /** Tool lifecycle sink: tool_call / tool_result (agent writes DSH events). */
28
+ onToolEvent;
29
+ /** Bridge-protocol parity for the agent test harness (fake clients). */
30
+ setToolEventHandler(handler) { }
31
+ dead = false;
32
+ startedSent = false;
33
+ lastUsageValue = undefined;
34
+ /** Per-turn token usage from the bridge (undefined when the bridge skipped it). */
35
+ get lastUsage() {
36
+ return this.lastUsageValue;
37
+ }
38
+ /** Register the ask-mode approval handler (agent wires its approval service). */
39
+ setApprovalHandler(handler) {
40
+ this.opts.onApprovalRequest = handler;
41
+ }
42
+ conversationIdValue = "";
43
+ constructor(opts = {}) {
44
+ this.opts = opts;
45
+ }
46
+ get conversationId() {
47
+ return this.conversationIdValue;
48
+ }
49
+ resolveBridgePath() {
50
+ if (this.opts.bridgePath)
51
+ return this.opts.bridgePath;
52
+ // dist/agy-client.js → package root is dist/.. (works through the
53
+ // world-profile symlink too: node reports the real path here).
54
+ const here = dirname(fileURLToPath(import.meta.url));
55
+ for (const base of [here, dirname(here)]) {
56
+ const p = join(base, BRIDGE_RELATIVE);
57
+ if (existsSync(p))
58
+ return p;
59
+ }
60
+ throw new Error(`agy bridge script not found (${BRIDGE_RELATIVE})`);
61
+ }
62
+ /** Durable venv created by scripts/setup-venv.sh (pkg-root/.venv). */
63
+ resolvePackagedVenvPython() {
64
+ try {
65
+ const pkgRoot = join(dirname(this.resolveBridgePath()), "..");
66
+ const venv = join(pkgRoot, ".venv", "bin", "python");
67
+ return existsSync(venv) ? venv : undefined;
68
+ }
69
+ catch {
70
+ return undefined;
71
+ }
72
+ }
73
+ spawnBridge() {
74
+ if (this.child || this.dead)
75
+ return;
76
+ const attempts = this.opts.spawnAttempts ?? 3;
77
+ const pythonBin = this.opts.pythonBin
78
+ ?? this.resolvePackagedVenvPython()
79
+ ?? process.env.AGY_PYTHON
80
+ ?? "python3";
81
+ const bridgePath = this.resolveBridgePath();
82
+ for (let attempt = 1; attempt <= attempts; attempt++) {
83
+ try {
84
+ this.child = spawn(pythonBin, [bridgePath], {
85
+ stdio: ["pipe", "pipe", "pipe"],
86
+ });
87
+ break;
88
+ }
89
+ catch (err) {
90
+ this.opts.trace?.(`spawn attempt ${attempt} failed:`, err);
91
+ if (attempt === attempts)
92
+ throw err;
93
+ }
94
+ }
95
+ const child = this.child;
96
+ child.stdout.setEncoding("utf8");
97
+ child.stdout.on("data", (d) => this.consume(d));
98
+ child.stderr.setEncoding("utf8");
99
+ child.stderr.on("data", (d) => this.opts.trace?.("[bridge stderr]", d.trimEnd()));
100
+ child.on("exit", (code) => {
101
+ this.child = undefined;
102
+ this.opts.trace?.(`bridge exited code=${code}`);
103
+ this.dispatch({ event: "error", error: `bridge exited code=${code}`, recoverable: !this.dead });
104
+ });
105
+ }
106
+ consume(data) {
107
+ this.buffer += data;
108
+ let idx;
109
+ while ((idx = this.buffer.indexOf("\n")) >= 0) {
110
+ const line = this.buffer.slice(0, idx).trim();
111
+ this.buffer = this.buffer.slice(idx + 1);
112
+ if (!line)
113
+ continue;
114
+ try {
115
+ const ev = JSON.parse(line);
116
+ if (ev.event === "done" && ev.conversation_id)
117
+ this.conversationIdValue = ev.conversation_id;
118
+ this.dispatch(ev);
119
+ }
120
+ catch (err) {
121
+ this.opts.trace?.("bad bridge json:", line, err);
122
+ }
123
+ }
124
+ }
125
+ dispatch(ev) {
126
+ const waiter = this.waiters.shift();
127
+ if (waiter)
128
+ waiter(ev);
129
+ else
130
+ this.pending.push(ev);
131
+ }
132
+ nextEvent() {
133
+ const pending = this.pending.shift();
134
+ if (pending)
135
+ return Promise.resolve(pending);
136
+ return new Promise((resolve) => this.waiters.push(resolve));
137
+ }
138
+ send(obj) {
139
+ if (!this.child)
140
+ throw new Error("bridge not running");
141
+ this.child.stdin.write(JSON.stringify(obj) + "\n");
142
+ }
143
+ /** Wait for a specific event kind, surfacing `error` events as throws. */
144
+ async expect(kind) {
145
+ for (;;) {
146
+ const ev = await this.nextEvent();
147
+ if (ev.event === kind)
148
+ return ev;
149
+ if (ev.event === "error")
150
+ throw new Error(`agy bridge: ${ev.error}`);
151
+ }
152
+ }
153
+ /** Ping round trip (spawns the bridge if needed — use for liveness probes). */
154
+ async ping() {
155
+ this.spawnBridge();
156
+ this.send({ op: "ping" });
157
+ await this.expect("pong");
158
+ }
159
+ /**
160
+ * Run one turn. First call materializes the bridge child and sends the
161
+ * buffered `start` config followed by `prompt` (lazy spawn per §14-i).
162
+ * Yields streamed text chunks; resolves with the full turn text.
163
+ */
164
+ async *turn(text) {
165
+ this.spawnBridge();
166
+ if (!this.startedSent) {
167
+ this.send({
168
+ op: "start",
169
+ model: this.opts.model,
170
+ ...(this.opts.thinkingLevel === undefined ? {} : { thinking_level: this.opts.thinkingLevel }),
171
+ ...(this.opts.approvalMode === undefined ? {} : { approval_mode: this.opts.approvalMode }),
172
+ api_key: this.opts.apiKey,
173
+ project: this.opts.project,
174
+ location: this.opts.location,
175
+ workspaces: this.opts.workspaces,
176
+ save_dir: this.opts.saveDir,
177
+ });
178
+ await this.expect("started");
179
+ this.startedSent = true;
180
+ }
181
+ this.send({ op: "prompt", text, conversation_id: this.conversationIdValue || undefined });
182
+ let full = "";
183
+ this.lastUsageValue = undefined;
184
+ for (;;) {
185
+ const ev = await this.nextEvent();
186
+ if (ev.event === "chunk") {
187
+ full += ev.text;
188
+ yield ev.text;
189
+ }
190
+ else if (ev.event === "thinking") {
191
+ this.onThinkingDelta?.(ev.text);
192
+ }
193
+ else if (ev.event === "tool_call" || ev.event === "tool_result") {
194
+ this.onToolEvent?.(ev);
195
+ }
196
+ else if (ev.event === "tool") {
197
+ this.opts.trace?.("tool (legacy)", ev.name);
198
+ }
199
+ else if (ev.event === "usage") {
200
+ this.lastUsageValue = ev;
201
+ }
202
+ else if (ev.event === "approval_request") {
203
+ // ask mode only; fail closed when no handler / handler throws.
204
+ let allow = false;
205
+ try {
206
+ allow = this.opts.onApprovalRequest
207
+ ? await this.opts.onApprovalRequest({ id: ev.id, tool: ev.tool, args: ev.args })
208
+ : false;
209
+ }
210
+ catch (err) {
211
+ this.opts.trace?.("approval handler failed — deny:", err);
212
+ allow = false;
213
+ }
214
+ this.send({ op: "approval", id: ev.id, allow });
215
+ }
216
+ else if (ev.event === "done") {
217
+ return full;
218
+ }
219
+ else if (ev.event === "error") {
220
+ throw new Error(`agy bridge: ${ev.error}`);
221
+ }
222
+ }
223
+ }
224
+ async close() {
225
+ if (!this.child)
226
+ return;
227
+ this.dead = true;
228
+ try {
229
+ this.send({ op: "close" });
230
+ }
231
+ catch {
232
+ /* child already gone */
233
+ }
234
+ const child = this.child;
235
+ setTimeout(() => {
236
+ if (!child.killed)
237
+ child.kill("SIGKILL");
238
+ }, 2000).unref();
239
+ await new Promise((resolve) => {
240
+ child.on("exit", () => resolve());
241
+ });
242
+ this.child = undefined;
243
+ }
244
+ }
245
+ /** Convenience: read the venv python path recorded by scripts/setup-venv.sh. */
246
+ export function resolveVenvPython(worldHome) {
247
+ const p = join(worldHome, "agy-venv-python.txt");
248
+ if (!existsSync(p))
249
+ return undefined;
250
+ const v = readFileSync(p, "utf8").trim();
251
+ return v || undefined;
252
+ }
@@ -0,0 +1,24 @@
1
+ /** One persisted DSH → Antigravity conversation pairing. */
2
+ export interface AgySessionRecord {
3
+ /** The SDK conversation id; `null` until the first turn materializes it. */
4
+ readonly conversationId: string | null;
5
+ /** The cwd the bridge session was (or is to be) spawned in. */
6
+ readonly cwd: string;
7
+ /** Epoch ms of the DSH session's creation. */
8
+ readonly createdAt: number;
9
+ /** Record-only permission preset at create, when one was known. */
10
+ readonly preset: string | null;
11
+ }
12
+ export type AgySessionMap = Record<string, AgySessionRecord>;
13
+ /**
14
+ * The adapter's DSH-side world home. Form-A (boot home = DSH home root) nests
15
+ * `agents/agy`; Form-B (already `<root>/agents/agy`) passes through. Prod-guarded.
16
+ */
17
+ export declare function resolveAgyWorldHome(home?: string): string;
18
+ export declare function sessionMapPath(home?: string): string;
19
+ /** Read the map. Fail-soft: missing or malformed file reads as `{}`. */
20
+ export declare function readSessionMap(home?: string): AgySessionMap;
21
+ /** One session's record, or `undefined` when the id is unknown. */
22
+ export declare function sessionRecord(home: string | undefined, dshSessionId: string): AgySessionRecord | undefined;
23
+ /** Insert or merge one record, persisted atomically (temp file + rename). */
24
+ export declare function upsertSession(home: string | undefined, dshSessionId: string, patch: Partial<AgySessionRecord>): AgySessionRecord;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * DSH-session ↔ Antigravity-conversation identity map (the "mapping only"
3
+ * model, mirroring hermes-store.ts; 2026-09-18).
4
+ *
5
+ * The DSH session id is the authority. The SDK's `conversationId` only
6
+ * materializes after the first turn (first bridge prompt), so the pairing is
7
+ * persisted in the adapter's DSH-side app dir — `<worldHome>/dsh-sessions.json`
8
+ * (never the runtime's native `~/.gemini`). Mapping only: resume resolves the
9
+ * record or fails closed; native conversation storage is never scanned.
10
+ *
11
+ * Shape: `{ "<dshSessionId>": { conversationId, cwd, createdAt, preset } }` —
12
+ * `conversationId` is `null` until the first turn materializes it.
13
+ */
14
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { basename, dirname, join, resolve } from "node:path";
17
+ /** Prod homes that must never be resolved into (repo red line). */
18
+ const PROD_HOMES = [join(homedir(), ".dsh"), join(homedir(), ".superd")];
19
+ function assertNotProdHome(path, label) {
20
+ if (PROD_HOMES.includes(path) || PROD_HOMES.some((p) => path.startsWith(`${p}/`))) {
21
+ throw new Error(`agy-sessions: refusing prod home as ${label}: ${path}`);
22
+ }
23
+ }
24
+ /**
25
+ * The adapter's DSH-side world home. Form-A (boot home = DSH home root) nests
26
+ * `agents/agy`; Form-B (already `<root>/agents/agy`) passes through. Prod-guarded.
27
+ */
28
+ export function resolveAgyWorldHome(home) {
29
+ const base = resolve(home ?? process.env.DSH_HOME ?? join(process.cwd(), ".tests", "aw"));
30
+ const dir = basename(base) === "agy" && basename(dirname(base)) === "agents" ? base : join(base, "agents", "agy");
31
+ assertNotProdHome(dir, "agy world home");
32
+ return dir;
33
+ }
34
+ export function sessionMapPath(home) {
35
+ return join(resolveAgyWorldHome(home), "dsh-sessions.json");
36
+ }
37
+ /** Read the map. Fail-soft: missing or malformed file reads as `{}`. */
38
+ export function readSessionMap(home) {
39
+ let raw;
40
+ try {
41
+ raw = readFileSync(sessionMapPath(home), "utf8");
42
+ }
43
+ catch {
44
+ return {};
45
+ }
46
+ let parsed;
47
+ try {
48
+ parsed = JSON.parse(raw);
49
+ }
50
+ catch {
51
+ return {};
52
+ }
53
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
54
+ return {};
55
+ const out = {};
56
+ for (const [id, value] of Object.entries(parsed)) {
57
+ const record = coerceRecord(value);
58
+ if (record !== undefined)
59
+ out[id] = record;
60
+ }
61
+ return out;
62
+ }
63
+ function coerceRecord(value) {
64
+ if (value === null || typeof value !== "object" || Array.isArray(value))
65
+ return undefined;
66
+ const raw = value;
67
+ const cwd = raw["cwd"];
68
+ const createdAt = raw["createdAt"];
69
+ if (typeof cwd !== "string" || cwd === "")
70
+ return undefined;
71
+ const conversationId = raw["conversationId"];
72
+ const preset = raw["preset"];
73
+ return {
74
+ conversationId: typeof conversationId === "string" && conversationId !== "" ? conversationId : null,
75
+ cwd,
76
+ createdAt: typeof createdAt === "number" && Number.isFinite(createdAt) ? createdAt : 0,
77
+ preset: typeof preset === "string" && preset !== "" ? preset : null,
78
+ };
79
+ }
80
+ /** One session's record, or `undefined` when the id is unknown. */
81
+ export function sessionRecord(home, dshSessionId) {
82
+ return readSessionMap(home)[dshSessionId];
83
+ }
84
+ /** Insert or merge one record, persisted atomically (temp file + rename). */
85
+ export function upsertSession(home, dshSessionId, patch) {
86
+ const path = sessionMapPath(home);
87
+ const map = readSessionMap(home);
88
+ const previous = map[dshSessionId];
89
+ const next = {
90
+ conversationId: patch.conversationId !== undefined ? patch.conversationId : previous?.conversationId ?? null,
91
+ cwd: patch.cwd !== undefined ? patch.cwd : previous?.cwd ?? "",
92
+ createdAt: patch.createdAt !== undefined ? patch.createdAt : previous?.createdAt ?? Date.now(),
93
+ preset: patch.preset !== undefined ? patch.preset : previous?.preset ?? null,
94
+ };
95
+ map[dshSessionId] = next;
96
+ writeMapAtomic(path, map);
97
+ return next;
98
+ }
99
+ function writeMapAtomic(path, map) {
100
+ const dir = dirname(path);
101
+ mkdirSync(dir, { recursive: true });
102
+ const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
103
+ writeFileSync(temp, `${JSON.stringify(map, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
104
+ try {
105
+ renameSync(temp, path);
106
+ }
107
+ catch (error) {
108
+ try {
109
+ rmSync(temp, { force: true });
110
+ }
111
+ catch {
112
+ // The rename failure is primary.
113
+ }
114
+ throw error;
115
+ }
116
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * agy-store.ts — adapter-owned state + native-home read-only access.
3
+ *
4
+ * Home discipline (agent-adapter-dev-rules §5/§13/§16a):
5
+ * - Adapter state (Gemini API key, cached default model) lives in the WORLD
6
+ * DSH home: `<worldHome>/agy-adapter.json`. Never written into `~/.gemini`.
7
+ * - The native home (`~/.gemini`) is READ-ONLY for us: default model comes
8
+ * from `~/.gemini/antigravity-cli/settings.json` (`model` field). Paths are
9
+ * injectable so tests use fixtures instead of env knobs (prod path has no
10
+ * redirection).
11
+ */
12
+ export interface AgyAdapterState {
13
+ /** Gemini API key the user pasted through the onboarding session. */
14
+ apiKey?: string;
15
+ /** Cached default model slug (from native settings or catalog head). */
16
+ defaultModel?: string;
17
+ }
18
+ export declare function adapterStatePath(worldHome: string): string;
19
+ export declare function readAdapterState(worldHome: string): AgyAdapterState;
20
+ export declare function writeAdapterState(worldHome: string, state: AgyAdapterState): void;
21
+ /**
22
+ * ADC evidence probe: the SDK picks up Application Default Credentials on its
23
+ * own, so their mere existence satisfies auth (§16a transparency). Checks the
24
+ * two standard sources — the env-pointed service-account key and the gcloud
25
+ * user ADC file. Read-only; paths injectable for tests.
26
+ */
27
+ export declare function hasAdcEvidence(env: NodeJS.ProcessEnv, home: string): boolean;
28
+ /**
29
+ * Read the gcloud CLI default project (ADC companion): the SDK's Vertex
30
+ * endpoint needs an explicit project when no api_key is given. Parses
31
+ * `~/.config/gcloud/configurations/config_default` (read-only, fixture-friendly).
32
+ */
33
+ export declare function readGcloudProject(home: string): string | undefined;
34
+ /**
35
+ * Default model as the agy TUI would report it. Read-only probe of the
36
+ * native home; missing file / missing field → undefined (caller falls back
37
+ * to the static catalog head).
38
+ */
39
+ export declare function readNativeDefaultModel(nativeHome: string): string | undefined;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * agy-store.ts — adapter-owned state + native-home read-only access.
3
+ *
4
+ * Home discipline (agent-adapter-dev-rules §5/§13/§16a):
5
+ * - Adapter state (Gemini API key, cached default model) lives in the WORLD
6
+ * DSH home: `<worldHome>/agy-adapter.json`. Never written into `~/.gemini`.
7
+ * - The native home (`~/.gemini`) is READ-ONLY for us: default model comes
8
+ * from `~/.gemini/antigravity-cli/settings.json` (`model` field). Paths are
9
+ * injectable so tests use fixtures instead of env knobs (prod path has no
10
+ * redirection).
11
+ */
12
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ export function adapterStatePath(worldHome) {
15
+ return join(worldHome, "agy-adapter.json");
16
+ }
17
+ export function readAdapterState(worldHome) {
18
+ const p = adapterStatePath(worldHome);
19
+ if (!existsSync(p))
20
+ return {};
21
+ try {
22
+ const parsed = JSON.parse(readFileSync(p, "utf8"));
23
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
24
+ }
25
+ catch {
26
+ return {};
27
+ }
28
+ }
29
+ export function writeAdapterState(worldHome, state) {
30
+ writeFileSync(adapterStatePath(worldHome), JSON.stringify(state, null, 2) + "\n");
31
+ }
32
+ /**
33
+ * ADC evidence probe: the SDK picks up Application Default Credentials on its
34
+ * own, so their mere existence satisfies auth (§16a transparency). Checks the
35
+ * two standard sources — the env-pointed service-account key and the gcloud
36
+ * user ADC file. Read-only; paths injectable for tests.
37
+ */
38
+ export function hasAdcEvidence(env, home) {
39
+ const fromEnv = env.GOOGLE_APPLICATION_CREDENTIALS;
40
+ if (typeof fromEnv === "string" && fromEnv !== "" && existsSync(fromEnv))
41
+ return true;
42
+ return existsSync(join(home, ".config", "gcloud", "application_default_credentials.json"));
43
+ }
44
+ /**
45
+ * Read the gcloud CLI default project (ADC companion): the SDK's Vertex
46
+ * endpoint needs an explicit project when no api_key is given. Parses
47
+ * `~/.config/gcloud/configurations/config_default` (read-only, fixture-friendly).
48
+ */
49
+ export function readGcloudProject(home) {
50
+ const p = join(home, ".config", "gcloud", "configurations", "config_default");
51
+ if (!existsSync(p))
52
+ return undefined;
53
+ try {
54
+ for (const line of readFileSync(p, "utf8").split("\n")) {
55
+ const m = /^\s*project\s*=\s*(\S+)\s*$/.exec(line);
56
+ if (m)
57
+ return m[1];
58
+ }
59
+ }
60
+ catch {
61
+ /* fall through */
62
+ }
63
+ return undefined;
64
+ }
65
+ /**
66
+ * Default model as the agy TUI would report it. Read-only probe of the
67
+ * native home; missing file / missing field → undefined (caller falls back
68
+ * to the static catalog head).
69
+ */
70
+ export function readNativeDefaultModel(nativeHome) {
71
+ const p = join(nativeHome, "antigravity-cli", "settings.json");
72
+ if (!existsSync(p))
73
+ return undefined;
74
+ try {
75
+ const parsed = JSON.parse(readFileSync(p, "utf8"));
76
+ return typeof parsed.model === "string" && parsed.model.length > 0
77
+ ? parsed.model
78
+ : undefined;
79
+ }
80
+ catch {
81
+ return undefined;
82
+ }
83
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Minimal in-memory inbox for the Hermes agent shim.
3
+ *
4
+ * The 0.1.5 `dsh-agent` contract exposes the inbox as an interface
5
+ * (`Inbox` in `runtime-types`); the durable projection-backed implementation
6
+ * (`ReactLoopInbox`) lives inside the (disabled) `dsh-agent-loop`. Hermes owns
7
+ * its own transcript and the adapter delivers messages directly to the Hermes
8
+ * client (`HermesAgent.#deliver`), so this inbox is never queued into — it
9
+ * exists to satisfy the `Agent` interface's `inbox` surface with correct
10
+ * (empty) semantics: `nextTurn`/`nextStep` stay empty and every mutation is
11
+ * a faithful in-memory splice.
12
+ */
13
+ import type { Inbox as InboxContract, InboxTarget } from "@deepseek-ai/dsh-agent";
14
+ import type { UserMessage } from "@deepseek-ai/dsh-session";
15
+ import type { MessageId } from "@deepseek-ai/dsh-llm";
16
+ export declare class Inbox implements InboxContract {
17
+ #private;
18
+ get nextTurn(): readonly UserMessage[];
19
+ get nextStep(): readonly UserMessage[];
20
+ clear(): void;
21
+ append(target: InboxTarget, message: UserMessage): void;
22
+ prepend(target: InboxTarget, message: UserMessage): void;
23
+ replace(messageId: MessageId, newMessage: UserMessage): boolean;
24
+ remove(messageId: MessageId): boolean;
25
+ splice(target: InboxTarget, start: number, deleteCount: number, inserted: UserMessage[]): UserMessage[];
26
+ }
@@ -0,0 +1,53 @@
1
+ export class Inbox {
2
+ #nextTurn = [];
3
+ #nextStep = [];
4
+ get nextTurn() {
5
+ return this.#nextTurn;
6
+ }
7
+ get nextStep() {
8
+ return this.#nextStep;
9
+ }
10
+ clear() {
11
+ this.#nextTurn = [];
12
+ this.#nextStep = [];
13
+ }
14
+ append(target, message) {
15
+ this.#list(target).push(message);
16
+ }
17
+ prepend(target, message) {
18
+ this.#list(target).unshift(message);
19
+ }
20
+ replace(messageId, newMessage) {
21
+ const list = this.#find(messageId);
22
+ if (list === undefined)
23
+ return false;
24
+ const index = list.findIndex((message) => message.id === messageId);
25
+ if (index < 0)
26
+ return false;
27
+ list.splice(index, 1, newMessage);
28
+ return true;
29
+ }
30
+ remove(messageId) {
31
+ const list = this.#find(messageId);
32
+ if (list === undefined)
33
+ return false;
34
+ const index = list.findIndex((message) => message.id === messageId);
35
+ if (index < 0)
36
+ return false;
37
+ list.splice(index, 1);
38
+ return true;
39
+ }
40
+ splice(target, start, deleteCount, inserted) {
41
+ return this.#list(target).splice(start, deleteCount, ...inserted);
42
+ }
43
+ #list(target) {
44
+ return target === "next-turn" ? this.#nextTurn : this.#nextStep;
45
+ }
46
+ #find(messageId) {
47
+ if (this.#nextTurn.some((message) => message.id === messageId))
48
+ return this.#nextTurn;
49
+ if (this.#nextStep.some((message) => message.id === messageId))
50
+ return this.#nextStep;
51
+ return undefined;
52
+ }
53
+ }
@@ -0,0 +1,80 @@
1
+ import { Context, Service } from "@deepseek-ai/cordis";
2
+ import type { AgentFactory, AgentHandle, CreateAgentOptions, ResumeAgentOptions } from "@deepseek-ai/dsh-agent";
3
+ /** Test/override knobs for the bridge child (prod: all unset). */
4
+ export interface AgyProviderOptions {
5
+ bridgePath?: string;
6
+ pythonBin?: string;
7
+ spawnAttempts?: number;
8
+ /** Native home override for tests (prod: ~/.gemini, read-only). */
9
+ nativeHome?: string;
10
+ /** Vertex project/location override (ADC mode). Default: gcloud CLI config. */
11
+ project?: string;
12
+ location?: string;
13
+ /** Tool-approval posture surfaced to every session's bridge start frame. */
14
+ approvalMode?: "allow" | "ask";
15
+ /** danger-full-access → CLI --dangerously-skip-permissions (default: true, patch default preset). */
16
+ skipPermissions?: boolean;
17
+ /** agy binary override (CLI path tests). */
18
+ agyBin?: string;
19
+ /** Forward proxy for the geo-fenced Gemini API (CLI path; dev3 gost). */
20
+ proxy?: string;
21
+ /** HOME override for the CLI child (isolated key-mode home; dev knob). */
22
+ cliHome?: string;
23
+ }
24
+ export declare class AgyProvider extends Service implements AgentFactory {
25
+ static inject: string[];
26
+ /** Plain holder — prevents Cordis re-tracing the factory's ctx through a caller shadow. */
27
+ private readonly runtime;
28
+ /** The world's DSH home — anchors adapter-owned state (session map, api-key state). */
29
+ private readonly home;
30
+ private readonly opts;
31
+ private defaultModelKey;
32
+ constructor(ctx: Context, opts?: AgyProviderOptions);
33
+ /** The world's DSH home: boot-provided dshHomePath first, env last. */
34
+ private resolveHome;
35
+ /** Effective Gemini API key: adapter state first, then the environment. */
36
+ private resolveApiKey;
37
+ /**
38
+ * Auth evidence for the §17 onboarding gate: an API key (state or env) OR
39
+ * ADC evidence is enough — the SDK consumes either transparently, so a user
40
+ * with gcloud ADC must NOT be pushed into key onboarding.
41
+ */
42
+ private hasAuthEvidence;
43
+ /** Default model id: adapter state → native TUI default (slug-normalized) → catalog head. */
44
+ private resolveDefaultModelId;
45
+ /** Legacy slug-only default (default-model push keeps slug form). */
46
+ private resolveDefaultModel;
47
+ /** Static route: provider `agy` + the resolved default (or the explicit ask).
48
+ * The selected model id (`slug` or `slug@level`) rides in `options.model`
49
+ * so clientOptions can forward slug+thinkingLevel to the bridge start frame. */
50
+ private sessionAgentOptions;
51
+ private requirePersistence;
52
+ private agentRuntime;
53
+ /** Persist the Dash↔conversation pairing once the first turn materializes it. */
54
+ private readonly followConversationId;
55
+ private registerModelCatalog;
56
+ /** Push the resolved default model into the world's `agentDefaultModel`. */
57
+ private registerDefaultModel;
58
+ createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
59
+ resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
60
+ /**
61
+ * Live transport routing (2026-09-18 user ruling): gemini-api-key > oauth >
62
+ * adc. Key present → CLI stream (richest surface). ADC-only → SDK bridge.
63
+ * Neither → CLI as well (auth fails at first prompt; the §17 onboarding
64
+ * branch answers locally before any spawn).
65
+ */
66
+ private createLiveClient;
67
+ /**
68
+ * The effective permission preset this provider was mounted with. The
69
+ * cordis.patch.yml default is danger-full-access; a patch-level override
70
+ * would surface through the permission service config, which we read
71
+ * best-effort (fail → the patch default).
72
+ */
73
+ private currentPermissionPreset;
74
+ /** CLI transport options: key-first default is the cheapest live pair. */
75
+ private cliOptions;
76
+ /** Bridge child options: world cwd + overrides; the api key rides the start config. */
77
+ private clientOptions;
78
+ private createStoredSession;
79
+ }
80
+ export default AgyProvider;