dsh-agy-link 0.2.8 โ 0.3.0
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.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -2
- package/dist/index.js +504 -133
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.9 (2026-08-19)
|
|
4
|
+
|
|
5
|
+
- **Tool activity moved out of the thinking panel into the reply body.**
|
|
6
|
+
User feedback on 0.2.8: tool annotations hidden inside the DSH thinking
|
|
7
|
+
fold were invisible and felt wrong. agy tools now render as visible
|
|
8
|
+
`๐ง [agy tool: name] args -> output` lines in the message body (they
|
|
9
|
+
cannot become native DSH tool cards: a finish:tool-calls would make the
|
|
10
|
+
DSH agent try to execute tools it does not own โ agy runs its own closed
|
|
11
|
+
tool loop). The thinking panel keeps only what is genuinely thinking
|
|
12
|
+
signal: `[agy thinking turn ยท N thinking tokens]` turn annotations
|
|
13
|
+
(agy print mode never streams thinking text) and terminal error notes.
|
|
14
|
+
|
|
3
15
|
## 0.2.8 (2026-08-19)
|
|
4
16
|
|
|
5
17
|
- **Fix: real agy 1.1.15 stream-json parsing.** The parser only understood
|
package/README.md
CHANGED
|
@@ -145,7 +145,8 @@ Bring **Google Antigravity models into DeepSeek Harness (DSH)** โ chat, thinki
|
|
|
145
145
|
| Capability | Description |
|
|
146
146
|
| ---- | ---- |
|
|
147
147
|
| ๐ **A model route, not a proxy** | Registers the `antigravity` provider in DSH's model config; pick any Antigravity model (Gemini / Claude / GPT-OSS) from the `/model` picker |
|
|
148
|
-
| ๐ **Full streaming** | Text, thinking (reasoning), and
|
|
148
|
+
| ๐ **Full streaming** | Text, thinking (reasoning), and token usage mapped onto DSH's native chunk protocol |
|
|
149
|
+
| ๐ **Native tool cards (v0.3)** | agy's tool activity renders with DSH's own tool-card UI โ terminal cards for `run_command`, inline diffs for file writes โ via the internal `agy_tool` mirror riding the real agent loop |
|
|
149
150
|
| ๐ **Session continuity** | Each DSH session binds to a native agy conversation (`--conversation`); multi-turn context rides agy history instead of re-sending everything |
|
|
150
151
|
| ๐ **Token usage** | Input/output/thinking/cache tokens surface in DSH usage accounting |
|
|
151
152
|
| โ๏ธ **Settings status** | DSH Settings โ Antigravity page shows agy connection/login/workspace/bindings/last-run state |
|
|
@@ -222,7 +223,7 @@ DSH-side tools and permissions are unaffected โ this only governs what the spa
|
|
|
222
223
|
|
|
223
224
|
## ๐งฉ How it works
|
|
224
225
|
|
|
225
|
-
One DSH
|
|
226
|
+
One DSH turn = one short-lived `agy -p --output-format stream-json` process. The NDJSON event stream is parsed, normalized, and recorded. Spans of that recording are mapped to DSH StreamChunks โ thinking โ reasoning blocks, text โ text blocks, result envelope โ usage + finish โ and each **completed agy tool step cuts the span** with a `tool-calls` finish addressed to the internal `agy_tool` mirror. DSH's agent loop dispatches the mirror (it instantly replays the recorded output), writes real `tool/call` + `tool/result` session events, and re-calls the provider to continue the run โ so tool activity renders with DSH's **native tool-card UI** (terminal cards, diffs, read/search icons) instead of text annotations. Conversation ids come from the stream itself, with a conversations-directory snapshot diff as fallback. **No reverse-engineered database scraping, no protobuf decoding, no token-file access** โ only the official unmodified agy binary is spawned.
|
|
226
227
|
|
|
227
228
|
## ๐ What it cannot do (honest list)
|
|
228
229
|
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
2
2
|
import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
|
|
4
|
+
import { CallId, LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
|
|
5
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
5
6
|
import { mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
|
-
import {
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
7
8
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
8
9
|
import { createServer } from "node:http";
|
|
9
|
-
import { randomBytes } from "node:crypto";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
//#region src/common/types.ts
|
|
12
12
|
const PROVIDER_ID = "antigravity";
|
|
@@ -178,7 +178,7 @@ function readJson(file) {
|
|
|
178
178
|
function readOverrides(file = overridesPath()) {
|
|
179
179
|
return readJson(file);
|
|
180
180
|
}
|
|
181
|
-
function asString(v) {
|
|
181
|
+
function asString$1(v) {
|
|
182
182
|
return typeof v === "string" ? v : void 0;
|
|
183
183
|
}
|
|
184
184
|
function asBool(v) {
|
|
@@ -207,11 +207,11 @@ function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
|
|
|
207
207
|
const cfg = {
|
|
208
208
|
...base,
|
|
209
209
|
enabled: asBool(get("enabled")) ?? base.enabled,
|
|
210
|
-
agyBin: asString(get("agyBin")) ?? base.agyBin,
|
|
210
|
+
agyBin: asString$1(get("agyBin")) ?? base.agyBin,
|
|
211
211
|
extraArgs: Array.isArray(get("extraArgs")) ? get("extraArgs").filter((x) => typeof x === "string") : base.extraArgs,
|
|
212
212
|
permissionMode: asMode(get("permissionMode")) ?? base.permissionMode,
|
|
213
|
-
defaultModel: asString(get("defaultModel")) ?? base.defaultModel,
|
|
214
|
-
defaultEffort: asString(get("defaultEffort")) ?? base.defaultEffort,
|
|
213
|
+
defaultModel: asString$1(get("defaultModel")) ?? base.defaultModel,
|
|
214
|
+
defaultEffort: asString$1(get("defaultEffort")) ?? base.defaultEffort,
|
|
215
215
|
timeoutMs: asNum(get("timeoutMs")) ?? base.timeoutMs,
|
|
216
216
|
maxConcurrent: asNum(get("maxConcurrent")) ?? base.maxConcurrent,
|
|
217
217
|
contextWindowDefault: asNum(get("contextWindowDefault")) ?? base.contextWindowDefault,
|
|
@@ -221,15 +221,15 @@ function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
|
|
|
221
221
|
modelsCacheTtlMs: asNum(get("modelsCacheTtlMs")) ?? base.modelsCacheTtlMs,
|
|
222
222
|
allowAuxiliary: asBool(get("allowAuxiliary")) ?? base.allowAuxiliary,
|
|
223
223
|
compactionMaxChars: asNum(get("compactionMaxChars")) ?? base.compactionMaxChars,
|
|
224
|
-
workspaceRoot: asString(get("workspaceRoot")) ?? base.workspaceRoot,
|
|
224
|
+
workspaceRoot: asString$1(get("workspaceRoot")) ?? base.workspaceRoot,
|
|
225
225
|
fallbackModels: Array.isArray(get("fallbackModels")) ? get("fallbackModels").filter((x) => !!x && typeof x === "object" && typeof x.id === "string") : base.fallbackModels,
|
|
226
226
|
askTool: asBool(get("askTool")) ?? base.askTool,
|
|
227
|
-
mediaDir: asString(get("mediaDir")) ?? base.mediaDir,
|
|
227
|
+
mediaDir: asString$1(get("mediaDir")) ?? base.mediaDir,
|
|
228
228
|
mediaTtlMs: asNum(get("mediaTtlMs")) ?? base.mediaTtlMs,
|
|
229
229
|
mediaMaxBytes: asNum(get("mediaMaxBytes")) ?? base.mediaMaxBytes,
|
|
230
230
|
mediaMaxImages: asNum(get("mediaMaxImages")) ?? base.mediaMaxImages,
|
|
231
231
|
mcpBridge: asBool(get("mcpBridge")) ?? base.mcpBridge,
|
|
232
|
-
mcpToolAllowlist: asString(get("mcpToolAllowlist")) ?? base.mcpToolAllowlist
|
|
232
|
+
mcpToolAllowlist: asString$1(get("mcpToolAllowlist")) ?? base.mcpToolAllowlist
|
|
233
233
|
};
|
|
234
234
|
if (env.DSH_AGY_ENABLED !== void 0) cfg.enabled = asBool(env.DSH_AGY_ENABLED) ?? cfg.enabled;
|
|
235
235
|
if (env.DSH_AGY_BIN) cfg.agyBin = env.DSH_AGY_BIN;
|
|
@@ -300,31 +300,158 @@ function diffConversations(before, dir = defaultConversationsDir()) {
|
|
|
300
300
|
};
|
|
301
301
|
}
|
|
302
302
|
//#endregion
|
|
303
|
-
//#region src/host/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
303
|
+
//#region src/host/recording.ts
|
|
304
|
+
const waiters = Symbol("waiters");
|
|
305
|
+
/** One agy run's append-only event log plus its settlement state. */
|
|
306
|
+
var RunRecording = class {
|
|
307
|
+
runId;
|
|
308
|
+
events = [];
|
|
309
|
+
settled = false;
|
|
310
|
+
failure = null;
|
|
311
|
+
resultConversationId = null;
|
|
312
|
+
[waiters] = /* @__PURE__ */ new Set();
|
|
313
|
+
constructor(runId = randomUUID()) {
|
|
314
|
+
this.runId = runId;
|
|
315
|
+
}
|
|
316
|
+
/** Append one pump event and wake every span consumer. */
|
|
317
|
+
append(ev) {
|
|
318
|
+
if (this.settled) return;
|
|
319
|
+
this.events.push(ev);
|
|
320
|
+
if (ev.kind === "result") this.resultConversationId = ev.conversationId ?? null;
|
|
321
|
+
this.wake();
|
|
322
|
+
}
|
|
323
|
+
/** Settle the recording: no further events will arrive. */
|
|
324
|
+
settle(failure) {
|
|
325
|
+
if (this.settled) return;
|
|
326
|
+
this.settled = true;
|
|
327
|
+
this.failure = failure;
|
|
328
|
+
this.wake();
|
|
329
|
+
}
|
|
330
|
+
wake() {
|
|
331
|
+
for (const w of this[waiters]) w();
|
|
332
|
+
this[waiters].clear();
|
|
333
|
+
}
|
|
334
|
+
get isSettled() {
|
|
335
|
+
return this.settled;
|
|
336
|
+
}
|
|
337
|
+
/** Terminal failure, when the run ended without a consumable result. */
|
|
338
|
+
get failureInfo() {
|
|
339
|
+
return this.failure;
|
|
340
|
+
}
|
|
341
|
+
/** Conversation id from the result envelope, once seen. */
|
|
342
|
+
get conversationId() {
|
|
343
|
+
return this.resultConversationId;
|
|
344
|
+
}
|
|
345
|
+
/** Whether a result event (ok or error-with-response) was recorded. */
|
|
346
|
+
get hasResult() {
|
|
347
|
+
return this.resultConversationId !== null || this.events.some((e) => e.kind === "result");
|
|
348
|
+
}
|
|
349
|
+
/** The last result envelope's finish-relevant projection, when one arrived. */
|
|
350
|
+
getResultEvent() {
|
|
351
|
+
for (let i = this.events.length - 1; i >= 0; i--) {
|
|
352
|
+
const ev = this.events[i];
|
|
353
|
+
if (ev !== void 0 && ev.kind === "result") return {
|
|
354
|
+
ok: ev.ok,
|
|
355
|
+
response: ev.response
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
311
359
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
let s;
|
|
320
|
-
try {
|
|
321
|
-
s = typeof output === "string" ? output : JSON.stringify(output);
|
|
322
|
-
} catch {
|
|
323
|
-
s = String(output);
|
|
360
|
+
/** Event at an absolute index (bounds-checked). */
|
|
361
|
+
eventAt(index) {
|
|
362
|
+
return this.events[index];
|
|
363
|
+
}
|
|
364
|
+
/** Number of recorded events so far. */
|
|
365
|
+
get length() {
|
|
366
|
+
return this.events.length;
|
|
324
367
|
}
|
|
325
|
-
|
|
326
|
-
|
|
368
|
+
/**
|
|
369
|
+
* Whether any assistant-visible text streamed before (not including) the
|
|
370
|
+
* given index. Carried into each span's mapper so the result-envelope
|
|
371
|
+
* fallback never duplicates text earlier spans already streamed.
|
|
372
|
+
*/
|
|
373
|
+
sawTextBefore(index) {
|
|
374
|
+
for (let i = 0; i < Math.min(index, this.events.length); i++) {
|
|
375
|
+
const ev = this.events[i];
|
|
376
|
+
if (ev !== void 0 && ev.kind === "step" && ev.stepKind === "text" && ev.text !== "") return true;
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Yield events at or after `from`, waiting for the pump while the run is
|
|
382
|
+
* live. The iterator ends once every recorded event was yielded AND the
|
|
383
|
+
* recording settled โ spans then inspect failureInfo to finish or fail.
|
|
384
|
+
*/
|
|
385
|
+
async *eventsFrom(from) {
|
|
386
|
+
let cursor = from;
|
|
387
|
+
for (;;) {
|
|
388
|
+
while (cursor < this.events.length) {
|
|
389
|
+
yield this.events[cursor];
|
|
390
|
+
cursor++;
|
|
391
|
+
}
|
|
392
|
+
if (this.settled) return;
|
|
393
|
+
await new Promise((resolve) => {
|
|
394
|
+
this[waiters].add(resolve);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Recorded tool step for a mirror-tool call: the event at `eventIndex` must
|
|
400
|
+
* be the completed tool step the callId was minted from.
|
|
401
|
+
*/
|
|
402
|
+
toolEventAt(eventIndex) {
|
|
403
|
+
const ev = this.events[eventIndex];
|
|
404
|
+
if (ev === void 0 || ev.kind !== "step" || ev.stepKind !== "tool" || !ev.tool) return null;
|
|
405
|
+
return ev.tool;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
/** Prefix every mirrored agy tool callId carries; continuation detection key. */
|
|
409
|
+
const AGY_CALL_PREFIX = "agytc-";
|
|
410
|
+
/** Mint the callId for one recorded tool event. */
|
|
411
|
+
function mirrorCallId(runId, eventIndex) {
|
|
412
|
+
return AGY_CALL_PREFIX + runId + "-" + String(eventIndex);
|
|
413
|
+
}
|
|
414
|
+
/** Parse a callId back into its run coordinates; null when not ours. */
|
|
415
|
+
function parseMirrorCallId(callId) {
|
|
416
|
+
if (!callId.startsWith("agytc-")) return null;
|
|
417
|
+
const rest = callId.slice(6);
|
|
418
|
+
const idx = rest.lastIndexOf("-");
|
|
419
|
+
if (idx <= 0) return null;
|
|
420
|
+
const runId = rest.slice(0, idx);
|
|
421
|
+
const n = Number(rest.slice(idx + 1));
|
|
422
|
+
if (!Number.isSafeInteger(n) || n < 0) return null;
|
|
423
|
+
return {
|
|
424
|
+
runId,
|
|
425
|
+
eventIndex: n
|
|
426
|
+
};
|
|
327
427
|
}
|
|
428
|
+
const MAX_RETAINED_RUNS = 8;
|
|
429
|
+
/** Bounded registry keeping the most recent runs for continuation spans. */
|
|
430
|
+
var RunRegistry = class {
|
|
431
|
+
runs = /* @__PURE__ */ new Map();
|
|
432
|
+
create() {
|
|
433
|
+
const rec = new RunRecording();
|
|
434
|
+
this.remember(rec);
|
|
435
|
+
return rec;
|
|
436
|
+
}
|
|
437
|
+
remember(rec) {
|
|
438
|
+
this.runs.set(rec.runId, rec);
|
|
439
|
+
while (this.runs.size > MAX_RETAINED_RUNS) {
|
|
440
|
+
const oldest = this.runs.keys().next().value;
|
|
441
|
+
if (oldest === void 0) break;
|
|
442
|
+
this.runs.delete(oldest);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
get(runId) {
|
|
446
|
+
return this.runs.get(runId);
|
|
447
|
+
}
|
|
448
|
+
/** Drop a settled run early (called when its final span finished stop). */
|
|
449
|
+
forget(runId) {
|
|
450
|
+
this.runs.delete(runId);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/host/mapper.ts
|
|
328
455
|
function usageFromRaw(raw) {
|
|
329
456
|
const usage = {
|
|
330
457
|
inputTokens: raw.input_tokens ?? 0,
|
|
@@ -348,12 +475,13 @@ var EventMapper = class {
|
|
|
348
475
|
openType = null;
|
|
349
476
|
openAcc = "";
|
|
350
477
|
emittedByKey = /* @__PURE__ */ new Map();
|
|
351
|
-
|
|
478
|
+
announcedTools = /* @__PURE__ */ new Set();
|
|
352
479
|
thinkingAnnounced = /* @__PURE__ */ new Set();
|
|
353
|
-
sawTextStep
|
|
480
|
+
sawTextStep;
|
|
354
481
|
finished = false;
|
|
355
|
-
constructor(opts
|
|
482
|
+
constructor(opts) {
|
|
356
483
|
this.opts = opts;
|
|
484
|
+
this.sawTextStep = opts.initialSawText === true;
|
|
357
485
|
}
|
|
358
486
|
/** Whether a terminal finish chunk has been emitted. */
|
|
359
487
|
get isFinished() {
|
|
@@ -403,7 +531,12 @@ var EventMapper = class {
|
|
|
403
531
|
text: delta
|
|
404
532
|
};
|
|
405
533
|
}
|
|
406
|
-
|
|
534
|
+
/**
|
|
535
|
+
* Map one event. `absIndex` is the event's position in the run recording;
|
|
536
|
+
* it mints the mirror callId and is what continuation detection parses
|
|
537
|
+
* back out of the DSH message list.
|
|
538
|
+
*/
|
|
539
|
+
*map(ev, absIndex) {
|
|
407
540
|
if (this.finished) return;
|
|
408
541
|
if (ev.kind === "init") return;
|
|
409
542
|
if (ev.kind === "garbage") return;
|
|
@@ -433,42 +566,64 @@ var EventMapper = class {
|
|
|
433
566
|
if (d) yield d;
|
|
434
567
|
return;
|
|
435
568
|
}
|
|
436
|
-
if (ev.stepKind === "thinking" || ev.stepKind === "
|
|
569
|
+
if (ev.stepKind === "thinking" || ev.stepKind === "subagent") {
|
|
437
570
|
yield* this.ensureBlock("reasoning");
|
|
438
571
|
if (ev.stepKind === "thinking") {
|
|
439
572
|
const delta = suffixDelta(this.emittedByKey.get(ev.stepKey) ?? "", ev.text);
|
|
440
573
|
this.emittedByKey.set(ev.stepKey, ev.text);
|
|
441
574
|
const d = this.appendDelta(delta);
|
|
442
575
|
if (d) yield d;
|
|
443
|
-
} else
|
|
444
|
-
const announceKey = ev.stepKey + ":a";
|
|
445
|
-
if (!this.toolPhaseSeen.has(announceKey)) {
|
|
446
|
-
this.toolPhaseSeen.add(announceKey);
|
|
447
|
-
const line = (this.opts.announce ?? defaultAnnounce)(ev.tool.name, ev.tool.args);
|
|
448
|
-
const d = this.appendDelta(line);
|
|
449
|
-
if (d) yield d;
|
|
450
|
-
}
|
|
451
|
-
const outKey = ev.stepKey + ":o";
|
|
452
|
-
const errKey = ev.stepKey + ":e";
|
|
453
|
-
if (ev.tool.error !== void 0 && !this.toolPhaseSeen.has(errKey)) {
|
|
454
|
-
this.toolPhaseSeen.add(errKey);
|
|
455
|
-
const d = this.appendDelta("[agy tool error: " + ev.tool.name + "] " + ev.tool.error + "\n");
|
|
456
|
-
if (d) yield d;
|
|
457
|
-
}
|
|
458
|
-
if (ev.tool.output !== void 0 && !this.toolPhaseSeen.has(outKey)) {
|
|
459
|
-
this.toolPhaseSeen.add(outKey);
|
|
460
|
-
const rendered = this.opts.toolOutput ? this.opts.toolOutput(ev.tool.name, ev.tool.args, ev.tool.output) : defaultOutput(ev.tool.output);
|
|
461
|
-
if (rendered !== null) {
|
|
462
|
-
const d = this.appendDelta(rendered);
|
|
463
|
-
if (d) yield d;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
} else if (ev.stepKind === "subagent") {
|
|
576
|
+
} else {
|
|
467
577
|
const d = this.appendDelta("[agy subagent] " + ev.text + "\n");
|
|
468
578
|
if (d) yield d;
|
|
469
579
|
}
|
|
470
580
|
return;
|
|
471
581
|
}
|
|
582
|
+
if (ev.stepKind === "tool" && ev.tool) {
|
|
583
|
+
if (!(ev.tool.output !== void 0 || ev.tool.error !== void 0)) return;
|
|
584
|
+
if (this.announcedTools.has(ev.stepKey)) return;
|
|
585
|
+
this.announcedTools.add(ev.stepKey);
|
|
586
|
+
if (!this.opts.cutOnTool) return;
|
|
587
|
+
const close = this.closeOpen();
|
|
588
|
+
if (close) yield close;
|
|
589
|
+
const idx = this.blockIdx;
|
|
590
|
+
const input = ev.tool.args === void 0 ? {} : { input: ev.tool.args };
|
|
591
|
+
const argumentsJson = JSON.stringify({
|
|
592
|
+
run: this.opts.runId,
|
|
593
|
+
step: absIndex,
|
|
594
|
+
tool: ev.tool.name,
|
|
595
|
+
...input
|
|
596
|
+
});
|
|
597
|
+
yield {
|
|
598
|
+
type: "block-start",
|
|
599
|
+
index: idx,
|
|
600
|
+
blockType: "tool-call"
|
|
601
|
+
};
|
|
602
|
+
yield {
|
|
603
|
+
type: "block-end",
|
|
604
|
+
index: idx,
|
|
605
|
+
block: {
|
|
606
|
+
type: "tool-call",
|
|
607
|
+
id: CallId(mirrorCallId(this.opts.runId, absIndex)),
|
|
608
|
+
name: "agy_tool",
|
|
609
|
+
arguments: argumentsJson
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
this.blockIdx++;
|
|
613
|
+
yield {
|
|
614
|
+
type: "usage",
|
|
615
|
+
usage: {
|
|
616
|
+
inputTokens: 0,
|
|
617
|
+
outputTokens: 0
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
yield {
|
|
621
|
+
type: "finish",
|
|
622
|
+
reason: { kind: "tool-calls" }
|
|
623
|
+
};
|
|
624
|
+
this.finished = true;
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
472
627
|
return;
|
|
473
628
|
}
|
|
474
629
|
if (!ev.ok) {
|
|
@@ -1548,6 +1703,32 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1548
1703
|
if (isAux && !cfg.allowAuxiliary) throw new LlmError("auxiliary calls are disabled for the antigravity route (allowAuxiliary: false)", Err.AUX_DISABLED);
|
|
1549
1704
|
const sessionKey = options.sessionId !== void 0 ? String(options.sessionId) : "";
|
|
1550
1705
|
const workspaceRoot = cfg.workspaceRoot !== "" ? cfg.workspaceRoot : this.deps.sessionCwd?.(sessionKey) || process.cwd();
|
|
1706
|
+
const continuation = detectContinuation(options.messages);
|
|
1707
|
+
if (continuation !== null) {
|
|
1708
|
+
const rec = this.deps.runs.get(continuation.runId);
|
|
1709
|
+
if (rec === void 0) {
|
|
1710
|
+
yield {
|
|
1711
|
+
type: "usage",
|
|
1712
|
+
usage: {
|
|
1713
|
+
inputTokens: 0,
|
|
1714
|
+
outputTokens: 0
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
yield {
|
|
1718
|
+
type: "finish",
|
|
1719
|
+
reason: {
|
|
1720
|
+
kind: "error",
|
|
1721
|
+
failure: {
|
|
1722
|
+
message: "agy run " + continuation.runId + " is no longer available (server restarted?) โ please resend your message",
|
|
1723
|
+
code: Err.AGY_ERROR
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
yield* this.driveSpan(rec, continuation.eventIndex + 1, true);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1551
1732
|
const catalog = this.deps.catalog.get();
|
|
1552
1733
|
const model = options.model;
|
|
1553
1734
|
const entry = findEntry(catalog, model);
|
|
@@ -1617,11 +1798,9 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1617
1798
|
if (prompt.trim() === "") throw new LlmError("request carries no user text or images to forward to agy", Err.AGY_ERROR);
|
|
1618
1799
|
} else if (prompt.trim() === "") throw new LlmError("request carries no user text to forward to agy", Err.AGY_ERROR);
|
|
1619
1800
|
const before = snapshotConversations();
|
|
1620
|
-
const
|
|
1621
|
-
const mapper = new EventMapper({ toolOutput: toolOutput ? (name, args, output) => toolOutput(name, args, output, workspaceRoot) : void 0 });
|
|
1801
|
+
const rec = this.deps.runs.create();
|
|
1622
1802
|
const parser = new StreamJsonParser();
|
|
1623
1803
|
this.deps.onParser?.(parser);
|
|
1624
|
-
const queue = new ChunkQueue();
|
|
1625
1804
|
let streamCid = null;
|
|
1626
1805
|
const args = this.buildArgs({
|
|
1627
1806
|
prompt,
|
|
@@ -1652,7 +1831,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1652
1831
|
for (const ev of parser.feed(line + "\n")) {
|
|
1653
1832
|
if (ev.kind === "init" && ev.conversationId) streamCid = ev.conversationId;
|
|
1654
1833
|
if (ev.kind === "result" && ev.conversationId !== "") streamCid = ev.conversationId;
|
|
1655
|
-
|
|
1834
|
+
rec.append(ev);
|
|
1656
1835
|
}
|
|
1657
1836
|
}
|
|
1658
1837
|
});
|
|
@@ -1665,10 +1844,12 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1665
1844
|
releaseOnce();
|
|
1666
1845
|
for (const ev of parser.flush()) {
|
|
1667
1846
|
if (ev.kind === "result" && ev.conversationId !== "") streamCid = ev.conversationId;
|
|
1668
|
-
|
|
1847
|
+
rec.append(ev);
|
|
1669
1848
|
}
|
|
1670
1849
|
const diffed = diffConversations(before).conversationId;
|
|
1671
1850
|
const conversationId = streamCid ?? diffed;
|
|
1851
|
+
const r = rec.getResultEvent();
|
|
1852
|
+
const consumable = r !== null && (r.ok || r.response !== "");
|
|
1672
1853
|
let failure = null;
|
|
1673
1854
|
if (outcome.aborted) failure = {
|
|
1674
1855
|
kind: "aborted",
|
|
@@ -1685,7 +1866,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1685
1866
|
code: Err.AUTH,
|
|
1686
1867
|
message: "agy is not signed in โ run /agy auth (or run agy once in a terminal) to login"
|
|
1687
1868
|
};
|
|
1688
|
-
else if (!
|
|
1869
|
+
else if (!consumable) {
|
|
1689
1870
|
if (outcome.code !== 0) failure = {
|
|
1690
1871
|
kind: "error",
|
|
1691
1872
|
code: Err.PROCESS_EXIT,
|
|
@@ -1702,7 +1883,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1702
1883
|
message: "agy produced no result event (" + parser.stats.garbage + " unparseable lines)"
|
|
1703
1884
|
};
|
|
1704
1885
|
}
|
|
1705
|
-
|
|
1886
|
+
rec.settle(failure);
|
|
1706
1887
|
if (!isAux && sessionKey !== "" && failure === null) {
|
|
1707
1888
|
const finalId = binding !== void 0 ? binding.conversationId : conversationId;
|
|
1708
1889
|
if (finalId) this.deps.store.set(sessionKey, {
|
|
@@ -1718,15 +1899,63 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1718
1899
|
durationMs: outcome.durationMs,
|
|
1719
1900
|
model
|
|
1720
1901
|
});
|
|
1721
|
-
queue.close();
|
|
1722
1902
|
})().catch((err) => {
|
|
1723
1903
|
releaseOnce();
|
|
1724
|
-
|
|
1725
|
-
|
|
1904
|
+
rec.settle({
|
|
1905
|
+
kind: "error",
|
|
1906
|
+
code: Err.PROCESS_EXIT,
|
|
1907
|
+
message: "internal error: " + brief(String(err))
|
|
1908
|
+
});
|
|
1726
1909
|
});
|
|
1910
|
+
yield* this.driveSpan(rec, 0, !isAux);
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Stream one span of a recording: map events from `from` until the mapper
|
|
1914
|
+
* finishes (tool-calls cut or result stop), then let the queue drain. When
|
|
1915
|
+
* the recording settles without a consumable result, surface its failure
|
|
1916
|
+
* as this span's terminal chunk โ the turn ends exactly like a native
|
|
1917
|
+
* provider error.
|
|
1918
|
+
*/
|
|
1919
|
+
async *driveSpan(rec, from, cutOnTool) {
|
|
1920
|
+
const queue = new ChunkQueue();
|
|
1921
|
+
(async () => {
|
|
1922
|
+
const mapper = new EventMapper({
|
|
1923
|
+
runId: rec.runId,
|
|
1924
|
+
cutOnTool,
|
|
1925
|
+
initialSawText: rec.sawTextBefore(from)
|
|
1926
|
+
});
|
|
1927
|
+
let i = from;
|
|
1928
|
+
try {
|
|
1929
|
+
for await (const ev of rec.eventsFrom(from)) {
|
|
1930
|
+
for (const ch of mapper.map(ev, i)) queue.push(ch);
|
|
1931
|
+
i++;
|
|
1932
|
+
if (mapper.isFinished) break;
|
|
1933
|
+
}
|
|
1934
|
+
if (!mapper.isFinished) {
|
|
1935
|
+
const f = rec.failureInfo;
|
|
1936
|
+
if (f !== null) for (const ch of mapper.emitFailure(f.kind, f.code, f.message)) queue.push(ch);
|
|
1937
|
+
else for (const ch of mapper.emitFailure("error", Err.INVALID_OUTPUT, "agy stream ended without a result event")) queue.push(ch);
|
|
1938
|
+
}
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
for (const ch of mapper.emitFailure("error", Err.PROCESS_EXIT, "internal error: " + brief(String(err)))) queue.push(ch);
|
|
1941
|
+
}
|
|
1942
|
+
queue.close();
|
|
1943
|
+
})();
|
|
1727
1944
|
yield* queue.drain();
|
|
1728
1945
|
}
|
|
1729
1946
|
};
|
|
1947
|
+
/**
|
|
1948
|
+
* Detect a continuation span: the request's LAST message is the tool result
|
|
1949
|
+
* of one of our mirrored agy tool calls. Its callId encodes the recording
|
|
1950
|
+
* run and the event index to resume after.
|
|
1951
|
+
*/
|
|
1952
|
+
function detectContinuation(messages) {
|
|
1953
|
+
const last = messages[messages.length - 1];
|
|
1954
|
+
if (last === void 0 || last.role !== "user") return null;
|
|
1955
|
+
const src = last.source;
|
|
1956
|
+
if (src === void 0 || src.kind !== "tool" || typeof src.callId !== "string") return null;
|
|
1957
|
+
return parseMirrorCallId(src.callId);
|
|
1958
|
+
}
|
|
1730
1959
|
//#endregion
|
|
1731
1960
|
//#region src/host/oneshot.ts
|
|
1732
1961
|
/** Per-file inline cap; larger files are truncated. */
|
|
@@ -2392,73 +2621,203 @@ function writeDoctorReport(deps) {
|
|
|
2392
2621
|
return file;
|
|
2393
2622
|
}
|
|
2394
2623
|
//#endregion
|
|
2395
|
-
//#region src/host/
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
"
|
|
2402
|
-
"
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
const o = args;
|
|
2408
|
-
for (const k of FILE_KEYS) {
|
|
2409
|
-
const v = o[k];
|
|
2410
|
-
if (typeof v === "string" && v !== "") return v;
|
|
2624
|
+
//#region src/host/mirror-tool.ts
|
|
2625
|
+
const MIRROR_TOOL_NAME = "agy_tool";
|
|
2626
|
+
/** agy serializes some tool args as a JSON string; presenters get an object. */
|
|
2627
|
+
function toolInput(args) {
|
|
2628
|
+
const raw = args.input;
|
|
2629
|
+
if (raw === void 0 || raw === null) return {};
|
|
2630
|
+
if (typeof raw === "object") return raw;
|
|
2631
|
+
if (typeof raw === "string") try {
|
|
2632
|
+
const parsed = JSON.parse(raw);
|
|
2633
|
+
return typeof parsed === "object" && parsed !== null ? parsed : { value: parsed };
|
|
2634
|
+
} catch {
|
|
2635
|
+
return { value: raw };
|
|
2411
2636
|
}
|
|
2412
|
-
return
|
|
2637
|
+
return { value: raw };
|
|
2413
2638
|
}
|
|
2414
|
-
function
|
|
2415
|
-
|
|
2416
|
-
return n.includes("write") || n.includes("edit") || n.includes("replace") || n.includes("str_replace");
|
|
2639
|
+
function asString(v) {
|
|
2640
|
+
return typeof v === "string" ? v : void 0;
|
|
2417
2641
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2642
|
+
/** Native pending-card projection for one mirrored agy tool call. */
|
|
2643
|
+
function presentMirrorCall(args) {
|
|
2644
|
+
const a = args;
|
|
2645
|
+
const name = typeof a?.tool === "string" ? a.tool : "";
|
|
2646
|
+
const input = toolInput(a);
|
|
2647
|
+
switch (name) {
|
|
2648
|
+
case "run_command":
|
|
2649
|
+
case "bash":
|
|
2650
|
+
case "execute_command": return {
|
|
2651
|
+
card: "terminal",
|
|
2652
|
+
title: asString(input.command) ?? asString(input.cmd) ?? JSON.stringify(input),
|
|
2653
|
+
...asString(input.description) !== void 0 ? { description: asString(input.description) } : {},
|
|
2654
|
+
...asString(input.cwd) !== void 0 ? { cwd: asString(input.cwd) } : {}
|
|
2655
|
+
};
|
|
2656
|
+
case "write_to_file":
|
|
2657
|
+
case "write_file":
|
|
2658
|
+
case "create_file": {
|
|
2659
|
+
const path = asString(input.path) ?? asString(input.file_path) ?? asString(input.filename) ?? "file";
|
|
2660
|
+
const content = asString(input.content) ?? "";
|
|
2661
|
+
return {
|
|
2662
|
+
card: "diff",
|
|
2663
|
+
title: "Write " + path,
|
|
2664
|
+
diffs: [{
|
|
2665
|
+
path,
|
|
2666
|
+
oldText: null,
|
|
2667
|
+
newText: content
|
|
2668
|
+
}],
|
|
2669
|
+
locations: [{ path }]
|
|
2670
|
+
};
|
|
2425
2671
|
}
|
|
2672
|
+
case "edit_file":
|
|
2673
|
+
case "replace_in_file":
|
|
2674
|
+
case "edit": {
|
|
2675
|
+
const path = asString(input.path) ?? asString(input.file_path) ?? "file";
|
|
2676
|
+
const newText = asString(input.new_string) ?? asString(input.newText) ?? asString(input.content) ?? "";
|
|
2677
|
+
const oldText = asString(input.old_string) ?? asString(input.oldText) ?? null;
|
|
2678
|
+
return {
|
|
2679
|
+
card: "diff",
|
|
2680
|
+
title: "Edit " + path,
|
|
2681
|
+
diffs: [{
|
|
2682
|
+
path,
|
|
2683
|
+
oldText,
|
|
2684
|
+
newText
|
|
2685
|
+
}],
|
|
2686
|
+
locations: [{ path }]
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
case "read_file":
|
|
2690
|
+
case "read":
|
|
2691
|
+
case "open_file": {
|
|
2692
|
+
const path = asString(input.path) ?? asString(input.file_path) ?? asString(input.filename) ?? "";
|
|
2693
|
+
const offset = typeof input.offset === "number" ? input.offset : void 0;
|
|
2694
|
+
return {
|
|
2695
|
+
card: "generic",
|
|
2696
|
+
title: "Read " + path,
|
|
2697
|
+
kind: "read",
|
|
2698
|
+
...path !== "" ? { locations: [{
|
|
2699
|
+
path,
|
|
2700
|
+
...offset !== void 0 ? { line: offset + 1 } : {}
|
|
2701
|
+
}] } : {}
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
case "find_by_name":
|
|
2705
|
+
case "glob":
|
|
2706
|
+
case "search_files":
|
|
2707
|
+
case "search":
|
|
2708
|
+
case "grep": {
|
|
2709
|
+
const q = asString(input.pattern) ?? asString(input.query) ?? asString(input.regex) ?? "";
|
|
2710
|
+
return {
|
|
2711
|
+
card: "generic",
|
|
2712
|
+
title: q !== "" ? "Search " + q : "Search",
|
|
2713
|
+
kind: "search"
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
default: return {
|
|
2717
|
+
card: "generic",
|
|
2718
|
+
title: name !== "" ? name : "agy tool",
|
|
2719
|
+
rawInput: input
|
|
2720
|
+
};
|
|
2426
2721
|
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2722
|
+
}
|
|
2723
|
+
/** Native completed-card projection for one mirrored agy tool call. */
|
|
2724
|
+
function presentMirrorResult(args, result) {
|
|
2725
|
+
const a = args;
|
|
2726
|
+
const name = typeof a?.tool === "string" ? a.tool : "";
|
|
2727
|
+
const text = resultText$1(result.content);
|
|
2728
|
+
switch (name) {
|
|
2729
|
+
case "run_command":
|
|
2730
|
+
case "bash":
|
|
2731
|
+
case "execute_command": return {
|
|
2732
|
+
card: "terminal",
|
|
2733
|
+
output: text
|
|
2734
|
+
};
|
|
2735
|
+
case "write_to_file":
|
|
2736
|
+
case "write_file":
|
|
2737
|
+
case "create_file":
|
|
2738
|
+
case "edit_file":
|
|
2739
|
+
case "replace_in_file":
|
|
2740
|
+
case "edit": {
|
|
2741
|
+
const call = presentMirrorCall(args);
|
|
2742
|
+
return {
|
|
2743
|
+
card: "diff",
|
|
2744
|
+
diffs: call !== void 0 && call.card === "diff" ? call.diffs : []
|
|
2745
|
+
};
|
|
2433
2746
|
}
|
|
2434
|
-
|
|
2747
|
+
default: return {
|
|
2748
|
+
card: "generic",
|
|
2749
|
+
content: [{
|
|
2750
|
+
type: "text",
|
|
2751
|
+
text: clip(text, 4e3)
|
|
2752
|
+
}]
|
|
2753
|
+
};
|
|
2435
2754
|
}
|
|
2436
|
-
return parts.length === 0 ? null : parts.join("\n") + "\n";
|
|
2437
2755
|
}
|
|
2438
|
-
function
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
"diff",
|
|
2444
|
-
"HEAD",
|
|
2445
|
-
"--",
|
|
2446
|
-
file
|
|
2447
|
-
], {
|
|
2448
|
-
encoding: "utf8",
|
|
2449
|
-
timeout: 5e3,
|
|
2450
|
-
maxBuffer: 1e6,
|
|
2451
|
-
stdio: [
|
|
2452
|
-
"ignore",
|
|
2453
|
-
"pipe",
|
|
2454
|
-
"ignore"
|
|
2455
|
-
]
|
|
2456
|
-
});
|
|
2457
|
-
if (out.trim() === "") return null;
|
|
2458
|
-
return out.split("\n").slice(0, 100).join("\n");
|
|
2459
|
-
} catch {
|
|
2460
|
-
return null;
|
|
2756
|
+
function resultText$1(content) {
|
|
2757
|
+
const parts = [];
|
|
2758
|
+
for (const b of Array.isArray(content) ? content : []) {
|
|
2759
|
+
const blk = b;
|
|
2760
|
+
if (blk && blk.type === "text" && typeof blk.text === "string") parts.push(blk.text);
|
|
2461
2761
|
}
|
|
2762
|
+
return parts.join("\n");
|
|
2763
|
+
}
|
|
2764
|
+
function clip(s, max) {
|
|
2765
|
+
return s.length > max ? s.slice(0, max) + "โฆ (+" + (s.length - max) + " chars)" : s;
|
|
2766
|
+
}
|
|
2767
|
+
function defineAgyMirrorTool(deps) {
|
|
2768
|
+
return defineTool({
|
|
2769
|
+
name: MIRROR_TOOL_NAME,
|
|
2770
|
+
description: "Internal to the dsh-agy-link bridge: replays one tool activity recorded from a Google Antigravity (agy) CLI run so it renders as a native tool card and rides the agent loop. Emitted automatically by the antigravity provider โ do not call it directly.",
|
|
2771
|
+
parameters: {
|
|
2772
|
+
run: {
|
|
2773
|
+
type: "string",
|
|
2774
|
+
required: true,
|
|
2775
|
+
description: "Recording run id."
|
|
2776
|
+
},
|
|
2777
|
+
step: {
|
|
2778
|
+
type: "number",
|
|
2779
|
+
required: true,
|
|
2780
|
+
description: "Recorded event index of the tool step."
|
|
2781
|
+
},
|
|
2782
|
+
tool: {
|
|
2783
|
+
type: "string",
|
|
2784
|
+
required: true,
|
|
2785
|
+
description: "agy tool name that ran."
|
|
2786
|
+
},
|
|
2787
|
+
input: {
|
|
2788
|
+
type: "json",
|
|
2789
|
+
description: "agy tool arguments as recorded."
|
|
2790
|
+
}
|
|
2791
|
+
},
|
|
2792
|
+
output: {
|
|
2793
|
+
schema: { type: "string" },
|
|
2794
|
+
render: (_args, value) => [{
|
|
2795
|
+
type: "text",
|
|
2796
|
+
text: value
|
|
2797
|
+
}]
|
|
2798
|
+
},
|
|
2799
|
+
presentCall: (args) => presentMirrorCall(args),
|
|
2800
|
+
presentResult: (args, result) => presentMirrorResult(args, result),
|
|
2801
|
+
async execute(args, exec) {
|
|
2802
|
+
exec.signal;
|
|
2803
|
+
const runId = typeof args.run === "string" ? args.run : "";
|
|
2804
|
+
const step = typeof args.step === "number" ? args.step : -1;
|
|
2805
|
+
typeof args.tool === "string" && args.tool;
|
|
2806
|
+
const rec = deps.runs.get(runId);
|
|
2807
|
+
if (rec === void 0) throw new Error("agy_tool: no recorded agy run \"" + runId + "\" โ this tool only replays bridge-recorded activity");
|
|
2808
|
+
const t = rec.toolEventAt(step);
|
|
2809
|
+
if (t === null) throw new Error("agy_tool: event " + step + " of run " + runId + " is not a completed tool step");
|
|
2810
|
+
if (t.error !== void 0) throw new Error("agy tool " + t.name + " failed: " + t.error);
|
|
2811
|
+
const out = t.output;
|
|
2812
|
+
if (out === void 0 || out === null) return "";
|
|
2813
|
+
if (typeof out === "string") return out;
|
|
2814
|
+
try {
|
|
2815
|
+
return JSON.stringify(out, null, 2);
|
|
2816
|
+
} catch {
|
|
2817
|
+
return String(out);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
});
|
|
2462
2821
|
}
|
|
2463
2822
|
//#endregion
|
|
2464
2823
|
//#region src/host/sessions.ts
|
|
@@ -2750,6 +3109,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2750
3109
|
};
|
|
2751
3110
|
}, getConfig().fallbackModels, 3e5);
|
|
2752
3111
|
const auth = new AuthHelper(bin);
|
|
3112
|
+
const runs = new RunRegistry();
|
|
2753
3113
|
const adapter = new AgyAdapter({
|
|
2754
3114
|
getConfig,
|
|
2755
3115
|
catalog,
|
|
@@ -2757,10 +3117,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2757
3117
|
bin,
|
|
2758
3118
|
acquire: () => semaphore.acquire(),
|
|
2759
3119
|
log,
|
|
2760
|
-
|
|
2761
|
-
const ws = getConfig().workspaceRoot;
|
|
2762
|
-
return renderToolActivity(name, args, output, cwd ?? (ws !== "" ? ws : process.cwd()));
|
|
2763
|
-
},
|
|
3120
|
+
runs,
|
|
2764
3121
|
sessionCwd: (sessionId) => {
|
|
2765
3122
|
return ctx.get("sessions")?.get(sessionId)?.header?.cwd;
|
|
2766
3123
|
},
|
|
@@ -2858,6 +3215,18 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2858
3215
|
}
|
|
2859
3216
|
};
|
|
2860
3217
|
syncAskTool();
|
|
3218
|
+
const mirrorToolDispose = { current: null };
|
|
3219
|
+
const syncMirrorTool = () => {
|
|
3220
|
+
const want = getConfig().enabled && bin() !== null;
|
|
3221
|
+
if (want && mirrorToolDispose.current === null && toolsSvc) {
|
|
3222
|
+
const reg = toolsSvc.register(defineAgyMirrorTool({ runs }));
|
|
3223
|
+
mirrorToolDispose.current = typeof reg === "function" ? reg : null;
|
|
3224
|
+
} else if (!want && mirrorToolDispose.current !== null) {
|
|
3225
|
+
mirrorToolDispose.current();
|
|
3226
|
+
mirrorToolDispose.current = null;
|
|
3227
|
+
}
|
|
3228
|
+
};
|
|
3229
|
+
syncMirrorTool();
|
|
2861
3230
|
const registerRoutes = (webServer) => {
|
|
2862
3231
|
const disposers = [];
|
|
2863
3232
|
const reg = (route) => {
|
|
@@ -2976,6 +3345,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2976
3345
|
}
|
|
2977
3346
|
setOverride(key, body.value);
|
|
2978
3347
|
syncAskTool();
|
|
3348
|
+
syncMirrorTool();
|
|
2979
3349
|
sendJson(res, 200, {
|
|
2980
3350
|
ok: true,
|
|
2981
3351
|
key,
|
|
@@ -3072,6 +3442,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
3072
3442
|
ctx.effect(() => {
|
|
3073
3443
|
auth.dispose();
|
|
3074
3444
|
if (askToolDispose.current !== null) askToolDispose.current();
|
|
3445
|
+
if (mirrorToolDispose.current !== null) mirrorToolDispose.current();
|
|
3075
3446
|
bridgeState.restore?.();
|
|
3076
3447
|
bridgeState.bridge?.close();
|
|
3077
3448
|
return () => void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Google Antigravity (agy CLI) models for DeepSeek Harness โ stream Gemini/Claude/GPT-OSS subscriptions into DSH with thinking, tool activity, token usage and in-GUI Google OAuth login.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|