dsh-agy-link 0.2.6 → 0.2.8
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 +47 -0
- package/README.md +17 -7
- package/cordis.patch.yml +1 -0
- package/dist/client.js +85 -67
- package/dist/index.js +274 -87
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.8 (2026-08-19)
|
|
4
|
+
|
|
5
|
+
- **Fix: real agy 1.1.15 stream-json parsing.** The parser only understood
|
|
6
|
+
flat, hypothesized event shapes; the live CLI nests every step payload
|
|
7
|
+
under a `step_update` envelope and uses a different vocabulary
|
|
8
|
+
(`agent_response` + `text_delta` fragments, `tool` with `tool_name` /
|
|
9
|
+
`tool_info` with `parameters` / `output` / `error`). As a result thinking
|
|
10
|
+
and tool activity were silently dropped and only the final result text
|
|
11
|
+
survived. Both shapes are now parsed (legacy aliases kept).
|
|
12
|
+
- **New: visible thinking turns + tool activity.** agy does not stream
|
|
13
|
+
thinking text in print mode (only `thinking_tokens` usage), so each
|
|
14
|
+
thinking-only `agent_response` turn surfaces as an annotated reasoning
|
|
15
|
+
block (`[agy thinking turn · N thinking tokens]`), tool calls render as
|
|
16
|
+
`[agy tool: name] args -> output` reasoning annotations, and failed tools
|
|
17
|
+
render `[agy tool error: name] message`.
|
|
18
|
+
- **Fix: `result.status=ERROR` with a usable response** (e.g. a tool timed
|
|
19
|
+
out mid-run) no longer discards the answer — the response streams, the
|
|
20
|
+
error is annotated, the turn finishes normally. A bare envelope error
|
|
21
|
+
now maps to a precise `AGY_ERROR` finish instead of INVALID_OUTPUT.
|
|
22
|
+
- **Fix: `agy models` deadlock — stdin pipe never closed.** agy reads stdin
|
|
23
|
+
when it is a pipe and waits for EOF; every non-auth spawn kept the pipe
|
|
24
|
+
open, so model discovery silently timed out and the catalog always fell
|
|
25
|
+
back to the bundled list. Stdin now closes right after spawn (auth probes
|
|
26
|
+
keep it open).
|
|
27
|
+
- **Fix: auth phase was always `idle`** until someone ran `/agy auth`, so
|
|
28
|
+
the settings page claimed "needs Google login" for signed-in users.
|
|
29
|
+
Status surfaces now lazily probe the real login state via `agy models`
|
|
30
|
+
(60s cache) and report `ok` / `signed-out` truthfully.
|
|
31
|
+
- **Fix: `/plugins/agy-link/*` routes never registered on dsh web** (settings
|
|
32
|
+
page stuck on `binary: not found / auth: unknown / models: 0`). Routes are
|
|
33
|
+
now registered through a reactive `ctx.inject(['webServer'])` sub-fiber,
|
|
34
|
+
so they attach whenever the service appears instead of racing it. The
|
|
35
|
+
settings page also renders an honest "endpoint unreachable" state instead
|
|
36
|
+
of misleading placeholders when the route is missing.
|
|
37
|
+
- **Removed: sidebar footer AGY button** (user request). Login (QR +
|
|
38
|
+
authorization code) and mode/effort quick controls moved to Settings →
|
|
39
|
+
Antigravity; the conversation header `AGY` pill stays.
|
|
40
|
+
- Tests: real-1.1.15 fixture modes (`real`, `real-error`, `real-fail`) and
|
|
41
|
+
parser/mapper/adapter coverage for the nested format (73 tests).
|
|
42
|
+
|
|
43
|
+
## 0.2.7 (2026-08-19)
|
|
44
|
+
|
|
45
|
+
- Visible agy status in DSH: conversation header `AGY` pill, Settings → Antigravity status page, and sidebar footer label.
|
|
46
|
+
- Workspace auto-binding: agy now runs in the DSH session's `cwd` when `workspaceRoot` is not configured.
|
|
47
|
+
- New `/agy workspace [path]` command and `DSH_AGY_WORKSPACE_ROOT` env.
|
|
48
|
+
- README language switcher at the top (中文 / English).
|
|
49
|
+
|
|
3
50
|
## 0.2.2 (2026-08-19)
|
|
4
51
|
|
|
5
52
|
- README: dedicated Prerequisites section (DSH, Node >= 24, the agy CLI with Google's official install guide link, first-run login, subscription note), a "how it works" intro paragraph, and an honest "what it cannot do" list in both languages - onboarding now covers first-time users.
|
package/README.md
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
<img src="https://img.shields.io/badge/node-%3E%3D24-green" alt="node"/>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
|
+
<p align="center">
|
|
15
|
+
<b>🌐 Language / 语言:</b>
|
|
16
|
+
<a href="#中文">中文</a> ·
|
|
17
|
+
<a href="#english">English</a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
14
20
|
---
|
|
15
21
|
|
|
16
22
|
# 中文
|
|
@@ -31,9 +37,10 @@
|
|
|
31
37
|
| 🌊 **完整流式** | 文本、思考、agy 工具活动(注记为 reasoning 块)全部映射到 DSH 原生流协议 |
|
|
32
38
|
| 🔗 **会话连续** | 每个 DSH 会话绑定原生 agy 会话(`--conversation`),多轮上下文由 agy 历史承载,不重发全量 |
|
|
33
39
|
| 📊 **token 用量** | 输入/输出/思考/缓存 token 全部进入 DSH 用量统计 |
|
|
34
|
-
|
|
|
40
|
+
| ⚙️ **设置页状态** | DSH 设置 → Antigravity 页面显示 agy 连接/登录/工作区/绑定数/最近运行状态 |
|
|
41
|
+
| 🔐 **GUI 内 Google 登录** | `/agy auth` 打印授权 URL;设置 → Antigravity 页面提供二维码 + 授权码粘贴框 |
|
|
35
42
|
| 🤝 **`agy_ask` 工具** | 任何 DSH 模型都可以把一次性任务委托给 Antigravity 模型(AskAntigravity 模式) |
|
|
36
|
-
| ⌨️ **`/agy` 命令族** | `status` / `auth` / `auth-code` / `models` / `mode` / `effort` / `clear` / `doctor` / `help` |
|
|
43
|
+
| ⌨️ **`/agy` 命令族** | `status` / `auth` / `auth-code` / `models` / `mode` / `effort` / `workspace` / `clear` / `doctor` / `help` |
|
|
37
44
|
| 🖼 **图片多模态(v0.2)** | 图片落盘到本地媒体目录(TTL 清理),prompt 以绝对路径引用 + `--add-dir` 授权,agy 用自己的工具看图 |
|
|
38
45
|
| 📎 **文件内联 + 结构化输出(v0.2)** | `agy_ask` 新增 `readPaths`(文本文件内联)与 `schema`(`--json-schema` 强约束答案) |
|
|
39
46
|
| 🌉 **MCP 反向桥(v0.2 实验)** | `mcpBridge: true` 开启后 agy 可直接调 DSH 侧工具(回环 + token 守卫端点 + 零依赖 stdio MCP 服务器,`.mcp.json` 合并写入/禁用还原) |
|
|
@@ -68,7 +75,7 @@ dsh plugin --profile web add dsh-agy-link
|
|
|
68
75
|
|
|
69
76
|
# 3. 登录(一次性;终端里已经跑过 agy 登录过的可跳过):
|
|
70
77
|
# /agy auth ← 打开授权 URL,批准并复制授权码
|
|
71
|
-
# /agy auth-code <授权码> ←
|
|
78
|
+
# /agy auth-code <授权码> ← 或直接用设置 → Antigravity 页面的二维码 + 粘贴框
|
|
72
79
|
|
|
73
80
|
# 4. /model 选择器里选 antigravity 模型,开聊
|
|
74
81
|
```
|
|
@@ -90,6 +97,7 @@ dsh plugin --profile web add dsh-agy-link
|
|
|
90
97
|
| defaultEffort | `DSH_AGY_DEFAULT_EFFORT` | `(模型默认)` | `low` / `medium` / `high` |
|
|
91
98
|
| timeoutMs | `DSH_AGY_TIMEOUT_MS` | `600000` | 单轮看门狗 |
|
|
92
99
|
| extraArgs | `DSH_AGY_EXTRA_ARGS` | — | 附加 agy 参数(空格分隔) |
|
|
100
|
+
| workspaceRoot | `DSH_AGY_WORKSPACE_ROOT` | 会话 cwd | agy 工作区;显式配置优先,未设置时自动使用 DSH 会话的 cwd |
|
|
93
101
|
|
|
94
102
|
### ⚠️ 权限模式——必读
|
|
95
103
|
|
|
@@ -103,7 +111,7 @@ DSH 侧的工具与权限不受影响——这里只约束 agy 子进程在自
|
|
|
103
111
|
|
|
104
112
|
## 🧩 工作原理
|
|
105
113
|
|
|
106
|
-
一次 DSH 模型调用 = 一个短生命周期 `agy -p --output-format stream-json` 进程。NDJSON 事件流被解析、归一化并映射为 DSH StreamChunk
|
|
114
|
+
一次 DSH 模型调用 = 一个短生命周期 `agy -p --output-format stream-json` 进程。NDJSON 事件流被解析、归一化并映射为 DSH StreamChunk:文本 → text 块,工具活动 → 注记 reasoning 块(含失败注记),结果信封 → usage + finish。agy 的 print 模式**不输出思考文本**(只有 `thinking_tokens` 计数),因此每个思考轮次以 `[agy thinking turn · N thinking tokens]` 注记呈现,思考 token 也进入用量统计。会话 id 优先取自流本身,conversations 目录快照对比兜底。**不逆向数据库、不解码 protobuf、不碰 token 文件**——只启动官方未修改的 agy 二进制。
|
|
107
115
|
|
|
108
116
|
## 📋 它做不到什么(诚实清单)
|
|
109
117
|
|
|
@@ -140,9 +148,10 @@ Bring **Google Antigravity models into DeepSeek Harness (DSH)** — chat, thinki
|
|
|
140
148
|
| 🌊 **Full streaming** | Text, thinking (reasoning), and agy tool activity (annotated reasoning blocks) mapped onto DSH's native chunk protocol |
|
|
141
149
|
| 🔗 **Session continuity** | Each DSH session binds to a native agy conversation (`--conversation`); multi-turn context rides agy history instead of re-sending everything |
|
|
142
150
|
| 📊 **Token usage** | Input/output/thinking/cache tokens surface in DSH usage accounting |
|
|
143
|
-
|
|
|
151
|
+
| ⚙️ **Settings status** | DSH Settings → Antigravity page shows agy connection/login/workspace/bindings/last-run state |
|
|
152
|
+
| 🔐 **In-GUI Google login** | `/agy auth` prints the consent URL; Settings → Antigravity adds a QR code and a paste box for the authorization code |
|
|
144
153
|
| 🤝 **`agy_ask` tool** | Let any DSH model delegate a one-shot task to an Antigravity model (the AskAntigravity pattern) |
|
|
145
|
-
| ⌨️ **`/agy` commands** | `status`, `auth`, `auth-code`, `models`, `mode`, `effort`, `clear`, `doctor`, `help` |
|
|
154
|
+
| ⌨️ **`/agy` commands** | `status`, `auth`, `auth-code`, `models`, `mode`, `effort`, `workspace`, `clear`, `doctor`, `help` |
|
|
146
155
|
| 😴 **Dormant-safe** | No agy binary? Not signed in? The plugin loads anyway and tells you what to fix |
|
|
147
156
|
| 🖼 **Image multimodal (v0.2)** | image attachments staged to a TTL-swept local dir, referenced by absolute path with `--add-dir`; agy views them with its own tools |
|
|
148
157
|
| 📎 **File inlining + structured output (v0.2)** | `agy_ask` gains `readPaths` (inline text files) and `schema` (enforced via `--json-schema`) |
|
|
@@ -177,7 +186,7 @@ dsh plugin --profile web add dsh-agy-link
|
|
|
177
186
|
|
|
178
187
|
# 3. Login (one time; skip if you already ran agy in a terminal):
|
|
179
188
|
# /agy auth <- open the consent URL, approve, copy the code
|
|
180
|
-
# /agy auth-code <code> <- or use
|
|
189
|
+
# /agy auth-code <code> <- or use Settings -> Antigravity (QR + paste box)
|
|
181
190
|
|
|
182
191
|
# 4. Pick an antigravity model in /model and chat
|
|
183
192
|
```
|
|
@@ -199,6 +208,7 @@ Config lives in the `agy-link` plugin entry (edit via `/plugin` or the profile p
|
|
|
199
208
|
| defaultEffort | `DSH_AGY_DEFAULT_EFFORT` | `(model default)` | `low` / `medium` / `high` |
|
|
200
209
|
| timeoutMs | `DSH_AGY_TIMEOUT_MS` | `600000` | per-turn watchdog |
|
|
201
210
|
| extraArgs | `DSH_AGY_EXTRA_ARGS` | — | extra agy flags, space-separated |
|
|
211
|
+
| workspaceRoot | `DSH_AGY_WORKSPACE_ROOT` | session cwd | agy workspace; explicit config wins, otherwise the DSH session's cwd is used |
|
|
202
212
|
|
|
203
213
|
### ⚠️ Permission modes — read this
|
|
204
214
|
|
package/cordis.patch.yml
CHANGED
|
@@ -10,5 +10,6 @@
|
|
|
10
10
|
# permissionMode: skip | plan | accept-edits (skip passes
|
|
11
11
|
# --dangerously-skip-permissions; see README security notes)
|
|
12
12
|
permissionMode: !!js "process.env.DSH_AGY_MODE ?? 'skip'"
|
|
13
|
+
workspaceRoot: !!js "process.env.DSH_AGY_WORKSPACE_ROOT ?? ''"
|
|
13
14
|
defaultModel: !!js "process.env.DSH_AGY_DEFAULT_MODEL ?? ''"
|
|
14
15
|
defaultEffort: !!js "process.env.DSH_AGY_DEFAULT_EFFORT ?? ''"
|
package/dist/client.js
CHANGED
|
@@ -6,10 +6,7 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
//#region src/client/index.ts
|
|
8
8
|
const { createElement: h, useState, useEffect } = require("react");
|
|
9
|
-
const reactDom = require("react-dom");
|
|
10
9
|
const win = globalThis;
|
|
11
|
-
const bodyEl = win.document?.body;
|
|
12
|
-
const portalToBody = bodyEl != null && reactDom.createPortal ? (node) => reactDom.createPortal(node, bodyEl) : (node) => node;
|
|
13
10
|
const name = "dsh-agy-link-client";
|
|
14
11
|
const inject = ["slots"];
|
|
15
12
|
const base = win.location?.origin ?? "";
|
|
@@ -73,14 +70,12 @@ window.__ModuleLoader__.load({
|
|
|
73
70
|
}
|
|
74
71
|
};
|
|
75
72
|
function apply(ctx) {
|
|
76
|
-
const
|
|
77
|
-
const [open, setOpen] = useState(false);
|
|
73
|
+
const AgySettingsSection = () => {
|
|
78
74
|
const [status, setStatus] = useState(null);
|
|
79
75
|
const [code, setCode] = useState("");
|
|
80
76
|
const [busy, setBusy] = useState(false);
|
|
81
77
|
const [notice, setNotice] = useState("");
|
|
82
78
|
useEffect(() => {
|
|
83
|
-
if (!open) return;
|
|
84
79
|
let alive = true;
|
|
85
80
|
const tick = async () => {
|
|
86
81
|
const st = await getStatus();
|
|
@@ -92,14 +87,15 @@ window.__ModuleLoader__.load({
|
|
|
92
87
|
alive = false;
|
|
93
88
|
clearInterval(timer);
|
|
94
89
|
};
|
|
95
|
-
}, [
|
|
96
|
-
const
|
|
90
|
+
}, []);
|
|
91
|
+
const refresh = async () => {
|
|
92
|
+
setStatus(await getStatus());
|
|
93
|
+
};
|
|
97
94
|
const startAuth = async () => {
|
|
98
95
|
setBusy(true);
|
|
99
96
|
setNotice("");
|
|
100
97
|
await postJson("/plugins/agy-link/auth", {});
|
|
101
|
-
|
|
102
|
-
setStatus(st);
|
|
98
|
+
await refresh();
|
|
103
99
|
setBusy(false);
|
|
104
100
|
};
|
|
105
101
|
const submitCode = async () => {
|
|
@@ -118,36 +114,17 @@ window.__ModuleLoader__.load({
|
|
|
118
114
|
key,
|
|
119
115
|
value
|
|
120
116
|
});
|
|
121
|
-
|
|
122
|
-
setStatus(st);
|
|
117
|
+
await refresh();
|
|
123
118
|
setBusy(false);
|
|
124
119
|
};
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
style: {
|
|
129
|
-
background: "transparent",
|
|
130
|
-
border: "none",
|
|
131
|
-
cursor: "pointer",
|
|
132
|
-
padding: "2px 6px",
|
|
133
|
-
fontSize: "14px",
|
|
134
|
-
lineHeight: 1
|
|
135
|
-
},
|
|
136
|
-
title: "Antigravity (agy) bridge"
|
|
137
|
-
}, h("span", { style: {
|
|
138
|
-
display: "inline-block",
|
|
139
|
-
width: "9px",
|
|
140
|
-
height: "9px",
|
|
141
|
-
borderRadius: "50%",
|
|
142
|
-
background: dot
|
|
143
|
-
} }));
|
|
144
|
-
if (!open) return badge;
|
|
145
|
-
const needsAuth = status !== null && !status.dormantReason && status.auth !== void 0 && status.auth.phase !== "ok";
|
|
146
|
-
const dormantLine = h("div", null, "Bridge dormant: ", status?.dormantReason ?? "");
|
|
120
|
+
const reachable = status !== null;
|
|
121
|
+
const summary = !reachable ? "Status endpoint unreachable — the plugin host did not answer /plugins/agy-link/status. Restart the DSH web server and check its logs." : status.dormantReason ? "Not ready — " + status.dormantReason : status.auth?.phase === "ok" ? "Ready — agy is connected" : "Needs Google login — start the login flow below, or run /agy auth";
|
|
122
|
+
const color = !reachable ? "#e5484d" : status.dormantReason ? "#f5a623" : status.auth?.phase === "ok" ? "#30a46c" : "#f5a623";
|
|
147
123
|
const infoBlock = h("div", { style: {
|
|
148
124
|
...S.muted,
|
|
149
|
-
lineHeight: 1.
|
|
150
|
-
|
|
125
|
+
lineHeight: 1.6,
|
|
126
|
+
margin: "6px 0 12px"
|
|
127
|
+
} }, h("div", null, "agy binary: ", status?.bin ?? "not found", status?.version ? " — v" + status.version : ""), h("div", null, "auth: ", status?.auth?.phase ?? "unknown"), h("div", null, "workspace: ", status?.workspaceRoot ? status.workspaceRoot : "(session cwd / process cwd)"), h("div", null, "models: ", String(status?.catalog?.count ?? 0), " — ", status?.catalog?.source ?? ""), h("div", null, "bindings: ", String(status?.bindings ?? 0)), h("div", null, "last run: ", status?.lastRun ? (status.lastRun.ok ? "ok" : status.lastRun.code) + " — " + status.lastRun.model : "none"));
|
|
151
128
|
const modeRow = h("div", { style: {
|
|
152
129
|
...S.row,
|
|
153
130
|
marginTop: "8px"
|
|
@@ -185,7 +162,10 @@ window.__ModuleLoader__.load({
|
|
|
185
162
|
style: status?.defaultEffort === "" ? S.btnPrimary : S.btn,
|
|
186
163
|
onClick: () => void setCfg("defaultEffort", "")
|
|
187
164
|
}, "auto"));
|
|
188
|
-
const skipWarn = h("div", { style:
|
|
165
|
+
const skipWarn = h("div", { style: {
|
|
166
|
+
...S.muted,
|
|
167
|
+
marginBottom: "8px"
|
|
168
|
+
} }, "skip runs agy tools with --dangerously-skip-permissions (no approval prompts). plan is the safe read-only default.");
|
|
189
169
|
const authBlock = h("div", null, h("div", { style: {
|
|
190
170
|
fontWeight: 600,
|
|
191
171
|
marginBottom: "4px"
|
|
@@ -214,43 +194,81 @@ window.__ModuleLoader__.load({
|
|
|
214
194
|
disabled: busy,
|
|
215
195
|
onClick: () => void startAuth()
|
|
216
196
|
}, "Restart login")), notice !== "" ? h("div", null, notice) : null);
|
|
217
|
-
const
|
|
218
|
-
const
|
|
197
|
+
const needsAuth = reachable && !status.dormantReason && status.auth !== void 0 && status.auth.phase !== "ok";
|
|
198
|
+
const controls = !reachable ? null : status.dormantReason ? h("div", { style: S.muted }, "Bridge dormant: ", status.dormantReason) : needsAuth ? authBlock : h("div", null, infoBlock, modeRow, effortRow, skipWarn);
|
|
199
|
+
return h("div", { style: { lineHeight: 1.6 } }, h("div", { style: {
|
|
219
200
|
display: "flex",
|
|
220
|
-
justifyContent: "space-between",
|
|
221
201
|
alignItems: "center",
|
|
222
|
-
|
|
223
|
-
} }, h("span", { style: {
|
|
202
|
+
gap: "8px",
|
|
224
203
|
fontWeight: 600,
|
|
225
|
-
fontSize: "
|
|
226
|
-
} },
|
|
204
|
+
fontSize: "14px"
|
|
205
|
+
} }, h("span", { style: {
|
|
206
|
+
display: "inline-block",
|
|
207
|
+
width: "10px",
|
|
208
|
+
height: "10px",
|
|
209
|
+
borderRadius: "50%",
|
|
210
|
+
background: color
|
|
211
|
+
} }), "Antigravity (agy CLI)"), h("div", { style: {
|
|
212
|
+
...S.muted,
|
|
213
|
+
margin: "6px 0 12px"
|
|
214
|
+
} }, summary), controls, h("div", { style: S.muted }, "Run `/agy status` for full diagnostics, `/agy doctor` to export a report."));
|
|
215
|
+
};
|
|
216
|
+
const AgySessionStatus = () => {
|
|
217
|
+
const [status, setStatus] = useState(null);
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
let alive = true;
|
|
220
|
+
const tick = async () => {
|
|
221
|
+
const st = await getStatus();
|
|
222
|
+
if (alive) setStatus(st);
|
|
223
|
+
};
|
|
224
|
+
tick();
|
|
225
|
+
const timer = setInterval(tick, 3e3);
|
|
226
|
+
return () => {
|
|
227
|
+
alive = false;
|
|
228
|
+
clearInterval(timer);
|
|
229
|
+
};
|
|
230
|
+
}, []);
|
|
231
|
+
const color = status === null ? "#9aa0a6" : status.dormantReason ? "#f5a623" : status.auth?.phase === "ok" ? "#30a46c" : "#f5a623";
|
|
232
|
+
const title = status === null ? "Antigravity (agy) status…" : status.dormantReason ? "Antigravity not ready: " + status.dormantReason : status.auth?.phase === "ok" ? "Antigravity ready — agy is connected" : "Antigravity needs Google login";
|
|
233
|
+
return h("button", {
|
|
227
234
|
type: "button",
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
235
|
+
title,
|
|
236
|
+
style: {
|
|
237
|
+
background: "transparent",
|
|
238
|
+
border: "1px solid rgba(128,128,128,0.3)",
|
|
239
|
+
borderRadius: "999px",
|
|
240
|
+
cursor: "default",
|
|
241
|
+
padding: "2px 8px",
|
|
242
|
+
fontSize: "11px",
|
|
243
|
+
lineHeight: 1.5,
|
|
244
|
+
color: "inherit",
|
|
245
|
+
display: "inline-flex",
|
|
246
|
+
alignItems: "center",
|
|
247
|
+
gap: "5px"
|
|
248
|
+
}
|
|
249
|
+
}, h("span", { style: {
|
|
250
|
+
display: "inline-block",
|
|
251
|
+
width: "8px",
|
|
252
|
+
height: "8px",
|
|
253
|
+
borderRadius: "50%",
|
|
254
|
+
background: color
|
|
255
|
+
} }), "AGY");
|
|
246
256
|
};
|
|
247
|
-
ctx.slots.inject("
|
|
257
|
+
ctx.slots.inject("conversation.session.header.actions", () => {
|
|
258
|
+
return ctx.slots.register({
|
|
259
|
+
name: "conversation.session.header.actions",
|
|
260
|
+
id: "agy-link-status",
|
|
261
|
+
order: 10,
|
|
262
|
+
label: "Antigravity"
|
|
263
|
+
}, AgySessionStatus);
|
|
264
|
+
});
|
|
265
|
+
ctx.slots.inject("settings.section", () => {
|
|
248
266
|
return ctx.slots.register({
|
|
249
|
-
name: "
|
|
250
|
-
id: "agy-link
|
|
251
|
-
order:
|
|
267
|
+
name: "settings.section",
|
|
268
|
+
id: "agy-link",
|
|
269
|
+
order: 20,
|
|
252
270
|
label: "Antigravity"
|
|
253
|
-
},
|
|
271
|
+
}, AgySettingsSection);
|
|
254
272
|
});
|
|
255
273
|
}
|
|
256
274
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -248,6 +248,7 @@ function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
|
|
|
248
248
|
if (t && t > 0) cfg.timeoutMs = t;
|
|
249
249
|
}
|
|
250
250
|
if (env.DSH_AGY_EXTRA_ARGS) cfg.extraArgs = env.DSH_AGY_EXTRA_ARGS.split(/\s+/).filter(Boolean);
|
|
251
|
+
if (env.DSH_AGY_WORKSPACE_ROOT) cfg.workspaceRoot = env.DSH_AGY_WORKSPACE_ROOT;
|
|
251
252
|
if (env.DSH_AGY_MEDIA_DIR) cfg.mediaDir = env.DSH_AGY_MEDIA_DIR;
|
|
252
253
|
if (env.DSH_AGY_MEDIA_TTL_MS) {
|
|
253
254
|
const t = asNum(env.DSH_AGY_MEDIA_TTL_MS);
|
|
@@ -348,6 +349,7 @@ var EventMapper = class {
|
|
|
348
349
|
openAcc = "";
|
|
349
350
|
emittedByKey = /* @__PURE__ */ new Map();
|
|
350
351
|
toolPhaseSeen = /* @__PURE__ */ new Set();
|
|
352
|
+
thinkingAnnounced = /* @__PURE__ */ new Set();
|
|
351
353
|
sawTextStep = false;
|
|
352
354
|
finished = false;
|
|
353
355
|
constructor(opts = {}) {
|
|
@@ -407,11 +409,27 @@ var EventMapper = class {
|
|
|
407
409
|
if (ev.kind === "garbage") return;
|
|
408
410
|
if (ev.kind === "step") {
|
|
409
411
|
if (ev.stepKind === "text") {
|
|
412
|
+
const thoughtTokens = ev.usage?.thinking_tokens ?? 0;
|
|
413
|
+
if (ev.text === "" && thoughtTokens > 0 && !this.thinkingAnnounced.has(ev.stepKey)) {
|
|
414
|
+
this.thinkingAnnounced.add(ev.stepKey);
|
|
415
|
+
yield* this.ensureBlock("reasoning");
|
|
416
|
+
const d = this.appendDelta("[agy thinking turn · " + thoughtTokens + " thinking tokens]\n");
|
|
417
|
+
if (d) yield d;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (ev.text === "" && !ev.fragment) return;
|
|
410
421
|
this.sawTextStep = true;
|
|
411
422
|
yield* this.ensureBlock("text");
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
423
|
+
let d;
|
|
424
|
+
if (ev.fragment === true) {
|
|
425
|
+
const acc = (this.emittedByKey.get(ev.stepKey) ?? "") + ev.text;
|
|
426
|
+
this.emittedByKey.set(ev.stepKey, acc);
|
|
427
|
+
d = this.appendDelta(ev.text);
|
|
428
|
+
} else {
|
|
429
|
+
const delta = suffixDelta(this.emittedByKey.get(ev.stepKey) ?? "", ev.text);
|
|
430
|
+
this.emittedByKey.set(ev.stepKey, ev.text);
|
|
431
|
+
d = this.appendDelta(delta);
|
|
432
|
+
}
|
|
415
433
|
if (d) yield d;
|
|
416
434
|
return;
|
|
417
435
|
}
|
|
@@ -431,6 +449,12 @@ var EventMapper = class {
|
|
|
431
449
|
if (d) yield d;
|
|
432
450
|
}
|
|
433
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
|
+
}
|
|
434
458
|
if (ev.tool.output !== void 0 && !this.toolPhaseSeen.has(outKey)) {
|
|
435
459
|
this.toolPhaseSeen.add(outKey);
|
|
436
460
|
const rendered = this.opts.toolOutput ? this.opts.toolOutput(ev.tool.name, ev.tool.args, ev.tool.output) : defaultOutput(ev.tool.output);
|
|
@@ -447,7 +471,32 @@ var EventMapper = class {
|
|
|
447
471
|
}
|
|
448
472
|
return;
|
|
449
473
|
}
|
|
450
|
-
if (!ev.ok)
|
|
474
|
+
if (!ev.ok) {
|
|
475
|
+
if (ev.response === "") return;
|
|
476
|
+
if (!this.sawTextStep) {
|
|
477
|
+
yield* this.ensureBlock("text");
|
|
478
|
+
const d = this.appendDelta(ev.response);
|
|
479
|
+
if (d) yield d;
|
|
480
|
+
}
|
|
481
|
+
if (ev.error !== void 0 && ev.error !== "") {
|
|
482
|
+
yield* this.ensureBlock("reasoning");
|
|
483
|
+
const d = this.appendDelta("[agy finished with error] " + ev.error + "\n");
|
|
484
|
+
if (d) yield d;
|
|
485
|
+
}
|
|
486
|
+
const closeErr = this.closeOpen();
|
|
487
|
+
if (closeErr) yield closeErr;
|
|
488
|
+
yield {
|
|
489
|
+
type: "usage",
|
|
490
|
+
usage: usageFromRaw(ev.usage)
|
|
491
|
+
};
|
|
492
|
+
yield {
|
|
493
|
+
type: "finish",
|
|
494
|
+
reason: { kind: "stop" },
|
|
495
|
+
...ev.conversationId !== "" ? { replayState: { response: { conversationId: ev.conversationId } } } : {}
|
|
496
|
+
};
|
|
497
|
+
this.finished = true;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
451
500
|
if (!this.sawTextStep && ev.response !== "") {
|
|
452
501
|
yield* this.ensureBlock("text");
|
|
453
502
|
const d = this.appendDelta(ev.response);
|
|
@@ -511,7 +560,7 @@ function parseModelsOutput(stdout) {
|
|
|
511
560
|
for (const line of text.split(/\n/)) {
|
|
512
561
|
const t = line.trim();
|
|
513
562
|
if (t === "" || t.startsWith("Fetching") || t.startsWith("Error")) continue;
|
|
514
|
-
const m = t.match(/^(\S+)
|
|
563
|
+
const m = t.match(/^(\S+)(?:\t+|\s{2,})(.+)$/);
|
|
515
564
|
if (m && m[1] !== void 0 && m[2] !== void 0) out.push({
|
|
516
565
|
slug: m[1],
|
|
517
566
|
label: m[2].trim()
|
|
@@ -716,7 +765,9 @@ const TEXT_STEP_TYPES = /* @__PURE__ */ new Set([
|
|
|
716
765
|
"model_response",
|
|
717
766
|
"modelresponse",
|
|
718
767
|
"message",
|
|
719
|
-
"response_text"
|
|
768
|
+
"response_text",
|
|
769
|
+
"agent_response",
|
|
770
|
+
"agentresponse"
|
|
720
771
|
]);
|
|
721
772
|
const THINKING_STEP_TYPES = /* @__PURE__ */ new Set([
|
|
722
773
|
"thinking",
|
|
@@ -801,6 +852,11 @@ function extractText(obj) {
|
|
|
801
852
|
}
|
|
802
853
|
return "";
|
|
803
854
|
}
|
|
855
|
+
/** agy ≥1.1.15 streams assistant text as `text_delta` fragments. */
|
|
856
|
+
function extractTextDelta(obj) {
|
|
857
|
+
const v = obj.text_delta ?? obj.textDelta;
|
|
858
|
+
return typeof v === "string" ? v : void 0;
|
|
859
|
+
}
|
|
804
860
|
function extractTool(obj) {
|
|
805
861
|
const info = pick(obj, [
|
|
806
862
|
"tool_info",
|
|
@@ -817,21 +873,31 @@ function extractTool(obj) {
|
|
|
817
873
|
"canonical_name"
|
|
818
874
|
]);
|
|
819
875
|
if (typeof name !== "string" || name === "") return void 0;
|
|
876
|
+
const args = pick(src, [
|
|
877
|
+
"parameters",
|
|
878
|
+
"params",
|
|
879
|
+
"input",
|
|
880
|
+
"args",
|
|
881
|
+
"input_json",
|
|
882
|
+
"inputJson"
|
|
883
|
+
]);
|
|
884
|
+
const output = pick(src, [
|
|
885
|
+
"output",
|
|
886
|
+
"result",
|
|
887
|
+
"output_text"
|
|
888
|
+
]);
|
|
889
|
+
const errObj = src.error;
|
|
890
|
+
let error;
|
|
891
|
+
if (typeof errObj === "string") error = errObj;
|
|
892
|
+
else if (errObj && typeof errObj === "object") {
|
|
893
|
+
const msg = errObj.message;
|
|
894
|
+
if (typeof msg === "string" && msg !== "") error = msg;
|
|
895
|
+
}
|
|
820
896
|
return {
|
|
821
897
|
name,
|
|
822
|
-
args
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
"input",
|
|
826
|
-
"args",
|
|
827
|
-
"input_json",
|
|
828
|
-
"inputJson"
|
|
829
|
-
]),
|
|
830
|
-
output: pick(src, [
|
|
831
|
-
"output",
|
|
832
|
-
"result",
|
|
833
|
-
"output_text"
|
|
834
|
-
])
|
|
898
|
+
args,
|
|
899
|
+
output,
|
|
900
|
+
...error !== void 0 ? { error } : {}
|
|
835
901
|
};
|
|
836
902
|
}
|
|
837
903
|
function parseUsage(v) {
|
|
@@ -865,20 +931,36 @@ function classifyEvent(obj, seq) {
|
|
|
865
931
|
"session_id",
|
|
866
932
|
"sessionId"
|
|
867
933
|
]);
|
|
934
|
+
const initObj = o.init;
|
|
935
|
+
const initSrc = initObj && typeof initObj === "object" && !Array.isArray(initObj) ? initObj : {};
|
|
868
936
|
const model = pick(o, [
|
|
869
937
|
"model",
|
|
870
938
|
"model_name",
|
|
871
939
|
"modelName"
|
|
940
|
+
]) ?? pick(initSrc, [
|
|
941
|
+
"model",
|
|
942
|
+
"model_name",
|
|
943
|
+
"modelName"
|
|
872
944
|
]);
|
|
945
|
+
let convId = typeof cid === "string" && cid !== "" ? cid : void 0;
|
|
946
|
+
if (convId === void 0) {
|
|
947
|
+
const nested = pick(initSrc, ["conversation_id", "conversationId"]);
|
|
948
|
+
if (typeof nested === "string" && nested !== "") convId = nested;
|
|
949
|
+
}
|
|
873
950
|
return {
|
|
874
951
|
kind: "init",
|
|
875
|
-
...
|
|
952
|
+
...convId !== void 0 ? { conversationId: convId } : {},
|
|
876
953
|
...typeof model === "string" ? { model } : {},
|
|
877
954
|
raw: o
|
|
878
955
|
};
|
|
879
956
|
}
|
|
880
957
|
if (evt === "step_update" || evt === "step" || evt === "stepUpdate") {
|
|
881
|
-
const
|
|
958
|
+
const nested = o.step_update ?? o.stepUpdate ?? o.step;
|
|
959
|
+
const su = nested && typeof nested === "object" && !Array.isArray(nested) ? nested : {};
|
|
960
|
+
const src = Object.keys(su).length > 0 ? su : o;
|
|
961
|
+
const idxV = pick(src, [
|
|
962
|
+
"step_index",
|
|
963
|
+
"stepIndex",
|
|
882
964
|
"idx",
|
|
883
965
|
"index",
|
|
884
966
|
"step_idx",
|
|
@@ -888,18 +970,25 @@ function classifyEvent(obj, seq) {
|
|
|
888
970
|
"stepId"
|
|
889
971
|
]);
|
|
890
972
|
const stepKey = typeof idxV === "number" || typeof idxV === "string" ? String(idxV) : String(seq);
|
|
891
|
-
const stepKind = normalizeStepKind(pick(
|
|
973
|
+
const stepKind = normalizeStepKind(pick(src, [
|
|
892
974
|
"step_type",
|
|
893
975
|
"stepType",
|
|
894
976
|
"type"
|
|
895
977
|
]));
|
|
896
|
-
const toolInfo = stepKind === "tool" ? extractTool(
|
|
978
|
+
const toolInfo = stepKind === "tool" ? extractTool(src) : void 0;
|
|
979
|
+
const delta = extractTextDelta(src);
|
|
980
|
+
const text = delta !== void 0 ? delta : extractText(src);
|
|
981
|
+
const stateV = pick(src, ["state"]);
|
|
982
|
+
const usageRaw = pick(src, ["usage"]);
|
|
897
983
|
return {
|
|
898
984
|
kind: "step",
|
|
899
985
|
stepKey,
|
|
900
986
|
stepKind,
|
|
901
|
-
text
|
|
987
|
+
text,
|
|
988
|
+
...delta !== void 0 ? { fragment: true } : {},
|
|
902
989
|
...toolInfo !== void 0 ? { tool: toolInfo } : {},
|
|
990
|
+
...typeof stateV === "string" ? { state: stateV } : {},
|
|
991
|
+
...usageRaw && typeof usageRaw === "object" ? { usage: parseUsage(usageRaw) } : {},
|
|
903
992
|
raw: o
|
|
904
993
|
};
|
|
905
994
|
}
|
|
@@ -937,6 +1026,10 @@ function classifyEvent(obj, seq) {
|
|
|
937
1026
|
event: "step_update",
|
|
938
1027
|
...o
|
|
939
1028
|
}, seq);
|
|
1029
|
+
if ((o.step_update !== void 0 || o.stepUpdate !== void 0) && (o.step_update ?? o.stepUpdate) !== null && typeof (o.step_update ?? o.stepUpdate) === "object") return classifyEvent({
|
|
1030
|
+
event: "step_update",
|
|
1031
|
+
step_update: o.step_update ?? o.stepUpdate
|
|
1032
|
+
}, seq);
|
|
940
1033
|
}
|
|
941
1034
|
var StreamJsonParser = class {
|
|
942
1035
|
buffer = "";
|
|
@@ -946,7 +1039,8 @@ var StreamJsonParser = class {
|
|
|
946
1039
|
garbage: 0,
|
|
947
1040
|
consecutiveGarbage: 0,
|
|
948
1041
|
authUrl: void 0,
|
|
949
|
-
sawAuthFailure: false
|
|
1042
|
+
sawAuthFailure: false,
|
|
1043
|
+
lastResultError: void 0
|
|
950
1044
|
};
|
|
951
1045
|
/** Ring buffer of raw stdout lines for /agy doctor export. */
|
|
952
1046
|
recentLines = [];
|
|
@@ -1002,6 +1096,7 @@ var StreamJsonParser = class {
|
|
|
1002
1096
|
line
|
|
1003
1097
|
};
|
|
1004
1098
|
}
|
|
1099
|
+
if (ev.kind === "result") this.stats.lastResultError = ev.error ?? void 0;
|
|
1005
1100
|
this.stats.consecutiveGarbage = 0;
|
|
1006
1101
|
return ev;
|
|
1007
1102
|
}
|
|
@@ -1224,6 +1319,9 @@ function startAgyProcess(opts) {
|
|
|
1224
1319
|
let timedOut = false;
|
|
1225
1320
|
let aborted = false;
|
|
1226
1321
|
let settled = false;
|
|
1322
|
+
if (!opts.keepStdin) try {
|
|
1323
|
+
child.stdin?.end();
|
|
1324
|
+
} catch {}
|
|
1227
1325
|
const watchdog = opts.timeoutMs && opts.timeoutMs > 0 ? setTimeout(() => {
|
|
1228
1326
|
timedOut = true;
|
|
1229
1327
|
killTree(child);
|
|
@@ -1262,9 +1360,6 @@ function startAgyProcess(opts) {
|
|
|
1262
1360
|
opts.onLine?.(pending);
|
|
1263
1361
|
pending = "";
|
|
1264
1362
|
}
|
|
1265
|
-
if (!opts.keepStdin) try {
|
|
1266
|
-
child.stdin?.end();
|
|
1267
|
-
} catch {}
|
|
1268
1363
|
resolve({
|
|
1269
1364
|
code,
|
|
1270
1365
|
signal,
|
|
@@ -1452,6 +1547,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1452
1547
|
const isAux = options.purpose === "compaction" || options.purpose === "session-title";
|
|
1453
1548
|
if (isAux && !cfg.allowAuxiliary) throw new LlmError("auxiliary calls are disabled for the antigravity route (allowAuxiliary: false)", Err.AUX_DISABLED);
|
|
1454
1549
|
const sessionKey = options.sessionId !== void 0 ? String(options.sessionId) : "";
|
|
1550
|
+
const workspaceRoot = cfg.workspaceRoot !== "" ? cfg.workspaceRoot : this.deps.sessionCwd?.(sessionKey) || process.cwd();
|
|
1455
1551
|
const catalog = this.deps.catalog.get();
|
|
1456
1552
|
const model = options.model;
|
|
1457
1553
|
const entry = findEntry(catalog, model);
|
|
@@ -1521,7 +1617,8 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1521
1617
|
if (prompt.trim() === "") throw new LlmError("request carries no user text or images to forward to agy", Err.AGY_ERROR);
|
|
1522
1618
|
} else if (prompt.trim() === "") throw new LlmError("request carries no user text to forward to agy", Err.AGY_ERROR);
|
|
1523
1619
|
const before = snapshotConversations();
|
|
1524
|
-
const
|
|
1620
|
+
const toolOutput = this.deps.toolOutput;
|
|
1621
|
+
const mapper = new EventMapper({ toolOutput: toolOutput ? (name, args, output) => toolOutput(name, args, output, workspaceRoot) : void 0 });
|
|
1525
1622
|
const parser = new StreamJsonParser();
|
|
1526
1623
|
this.deps.onParser?.(parser);
|
|
1527
1624
|
const queue = new ChunkQueue();
|
|
@@ -1548,7 +1645,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1548
1645
|
proc = startAgyProcess({
|
|
1549
1646
|
bin,
|
|
1550
1647
|
args,
|
|
1551
|
-
cwd:
|
|
1648
|
+
cwd: workspaceRoot,
|
|
1552
1649
|
timeoutMs: cfg.timeoutMs,
|
|
1553
1650
|
signal: options.signal,
|
|
1554
1651
|
onLine: (line) => {
|
|
@@ -1594,6 +1691,11 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
1594
1691
|
code: Err.PROCESS_EXIT,
|
|
1595
1692
|
message: "agy exited with code " + outcome.code + (outcome.stderrTail !== "" ? ": " + brief(outcome.stderrTail) : "")
|
|
1596
1693
|
};
|
|
1694
|
+
else if (parser.stats.lastResultError) failure = {
|
|
1695
|
+
kind: "error",
|
|
1696
|
+
code: Err.AGY_ERROR,
|
|
1697
|
+
message: "agy reported an error: " + parser.stats.lastResultError
|
|
1698
|
+
};
|
|
1597
1699
|
else failure = {
|
|
1598
1700
|
kind: "error",
|
|
1599
1701
|
code: Err.INVALID_OUTPUT,
|
|
@@ -1904,12 +2006,63 @@ var AuthHelper = class {
|
|
|
1904
2006
|
capturedUrl = null;
|
|
1905
2007
|
urlWaiter = null;
|
|
1906
2008
|
urlTimer = null;
|
|
2009
|
+
signedInCache = null;
|
|
2010
|
+
signedInFlight = null;
|
|
1907
2011
|
constructor(bin) {
|
|
1908
2012
|
this.bin = bin;
|
|
1909
2013
|
}
|
|
1910
2014
|
status() {
|
|
1911
2015
|
return { ...this.state };
|
|
1912
2016
|
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Ground-truth login probe: `agy models` succeeds only when signed in and
|
|
2019
|
+
* prints a sign-in error otherwise. Cached for 60s; in-flight calls share
|
|
2020
|
+
* one spawn. Returns true/false, or null when undeterminable (no binary,
|
|
2021
|
+
* spawn failure, ambiguous output). While a login flow is active the
|
|
2022
|
+
* in-memory phase wins and the probe is skipped.
|
|
2023
|
+
*/
|
|
2024
|
+
async probeSignedIn(force = false) {
|
|
2025
|
+
if (this.state.phase === "pending" || this.state.phase === "submitting") return null;
|
|
2026
|
+
if (this.state.phase === "ok") return true;
|
|
2027
|
+
const now = Date.now();
|
|
2028
|
+
if (!force && this.signedInCache !== null && now - this.signedInCache.at < 6e4) return this.signedInCache.value;
|
|
2029
|
+
if (this.signedInFlight !== null) return this.signedInFlight;
|
|
2030
|
+
const bin = this.bin();
|
|
2031
|
+
if (!bin) return null;
|
|
2032
|
+
this.signedInFlight = (async () => {
|
|
2033
|
+
let value = null;
|
|
2034
|
+
try {
|
|
2035
|
+
const out = await probeProcess(bin, ["models"], 15e3);
|
|
2036
|
+
const tail = out.stderrTail + "\n" + out.stdout.slice(0, 2e3);
|
|
2037
|
+
if (out.code === 0 && !looksLikeAuthFailure(out.stdout)) value = true;
|
|
2038
|
+
else if (out.code !== 0 && looksLikeAuthFailure(tail)) value = false;
|
|
2039
|
+
} catch {
|
|
2040
|
+
value = null;
|
|
2041
|
+
}
|
|
2042
|
+
this.signedInCache = {
|
|
2043
|
+
value,
|
|
2044
|
+
at: Date.now()
|
|
2045
|
+
};
|
|
2046
|
+
this.signedInFlight = null;
|
|
2047
|
+
return value;
|
|
2048
|
+
})();
|
|
2049
|
+
return this.signedInFlight;
|
|
2050
|
+
}
|
|
2051
|
+
/** status() enriched with a lazily probed login state for idle phases. */
|
|
2052
|
+
async resolvedStatus() {
|
|
2053
|
+
const st = this.status();
|
|
2054
|
+
if (st.phase !== "idle") return st;
|
|
2055
|
+
const signedIn = await this.probeSignedIn();
|
|
2056
|
+
if (signedIn === true) return {
|
|
2057
|
+
phase: "ok",
|
|
2058
|
+
message: "signed in (probed via agy models)"
|
|
2059
|
+
};
|
|
2060
|
+
if (signedIn === false) return {
|
|
2061
|
+
phase: "signed-out",
|
|
2062
|
+
message: "agy is not signed in — run /agy auth"
|
|
2063
|
+
};
|
|
2064
|
+
return st;
|
|
2065
|
+
}
|
|
1913
2066
|
startProbe() {
|
|
1914
2067
|
const bin = this.bin();
|
|
1915
2068
|
if (!bin) return null;
|
|
@@ -2070,6 +2223,7 @@ const HELP = [
|
|
|
2070
2223
|
"- `/agy models` — refresh and list discovered models",
|
|
2071
2224
|
"- `/agy mode <skip|plan|accept-edits>` — permission mode (next turn)",
|
|
2072
2225
|
"- `/agy effort <low|medium|high|default>` — default reasoning effort",
|
|
2226
|
+
"- `/agy workspace [path]` — set the agy working directory (absolute path; omit to show)",
|
|
2073
2227
|
"- `/agy clear` — drop the most recent conversation binding",
|
|
2074
2228
|
"- `/agy doctor` — write a diagnostic report and return its path",
|
|
2075
2229
|
"- `/agy help` — this text"
|
|
@@ -2086,7 +2240,7 @@ async function handle(deps, raw) {
|
|
|
2086
2240
|
const sub = parts[0] ?? "help";
|
|
2087
2241
|
const arg = parts[1] ?? "";
|
|
2088
2242
|
try {
|
|
2089
|
-
if (sub === "status") return ok(renderStatus(deps));
|
|
2243
|
+
if (sub === "status") return ok(await renderStatus(deps));
|
|
2090
2244
|
if (sub === "auth") {
|
|
2091
2245
|
const auth = deps.auth();
|
|
2092
2246
|
if (!auth) return err("agy binary not found — install the CLI first");
|
|
@@ -2133,6 +2287,16 @@ async function handle(deps, raw) {
|
|
|
2133
2287
|
deps.setOverride("defaultEffort", arg === "default" ? "" : arg);
|
|
2134
2288
|
return ok("Default effort set to **" + (arg === "default" ? "model default" : arg) + "**.");
|
|
2135
2289
|
}
|
|
2290
|
+
if (sub === "workspace") {
|
|
2291
|
+
if (arg === "") return ok("Current workspace: " + (deps.cfg().workspaceRoot !== "" ? deps.cfg().workspaceRoot : "(session cwd / process cwd)"));
|
|
2292
|
+
if (arg === "default" || arg === "clear") {
|
|
2293
|
+
deps.setOverride("workspaceRoot", "");
|
|
2294
|
+
return ok("Workspace reset to session cwd / process cwd.");
|
|
2295
|
+
}
|
|
2296
|
+
if (!isAbsolute(arg)) return err("workspace must be an absolute path (or `default` to clear)");
|
|
2297
|
+
deps.setOverride("workspaceRoot", arg);
|
|
2298
|
+
return ok("Workspace set to **" + arg + "** — effective next turn.");
|
|
2299
|
+
}
|
|
2136
2300
|
if (sub === "clear") {
|
|
2137
2301
|
const all = deps.store().all();
|
|
2138
2302
|
const keys = Object.keys(all);
|
|
@@ -2149,10 +2313,11 @@ async function handle(deps, raw) {
|
|
|
2149
2313
|
return err(e instanceof Error ? e.message : String(e));
|
|
2150
2314
|
}
|
|
2151
2315
|
}
|
|
2152
|
-
function renderStatus(deps) {
|
|
2316
|
+
async function renderStatus(deps) {
|
|
2153
2317
|
const cfg = deps.cfg();
|
|
2154
2318
|
const bin = deps.bin();
|
|
2155
|
-
const
|
|
2319
|
+
const authHelper = deps.auth();
|
|
2320
|
+
const auth = authHelper ? await authHelper.resolvedStatus() : void 0;
|
|
2156
2321
|
const cat = deps.catalog().get();
|
|
2157
2322
|
const bindings = Object.keys(deps.store().all()).length;
|
|
2158
2323
|
const last = deps.lastRun();
|
|
@@ -2162,6 +2327,7 @@ function renderStatus(deps) {
|
|
|
2162
2327
|
"- version: " + (deps.version() ?? "unknown"),
|
|
2163
2328
|
"- auth: " + (auth ? auth.phase + (auth.message ? " — " + auth.message : "") : "unknown"),
|
|
2164
2329
|
"- permission mode: " + cfg.permissionMode + (cfg.permissionMode === "skip" ? " — WARNING: agy runs tools without approval" : ""),
|
|
2330
|
+
"- workspace: " + (cfg.workspaceRoot !== "" ? cfg.workspaceRoot : "(session cwd / process cwd)"),
|
|
2165
2331
|
"- default model: " + (cfg.defaultModel === "" ? "(agy default)" : cfg.defaultModel),
|
|
2166
2332
|
"- default effort: " + (cfg.defaultEffort === "" ? "(model default)" : cfg.defaultEffort),
|
|
2167
2333
|
"- catalog: " + cat.models.length + " models — " + cat.source + (cat.lastError === void 0 ? "" : " — last error: " + cat.lastError),
|
|
@@ -2591,9 +2757,12 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2591
2757
|
bin,
|
|
2592
2758
|
acquire: () => semaphore.acquire(),
|
|
2593
2759
|
log,
|
|
2594
|
-
toolOutput: (name, args, output) => {
|
|
2760
|
+
toolOutput: (name, args, output, cwd) => {
|
|
2595
2761
|
const ws = getConfig().workspaceRoot;
|
|
2596
|
-
return renderToolActivity(name, args, output, ws !== "" ? ws : process.cwd());
|
|
2762
|
+
return renderToolActivity(name, args, output, cwd ?? (ws !== "" ? ws : process.cwd()));
|
|
2763
|
+
},
|
|
2764
|
+
sessionCwd: (sessionId) => {
|
|
2765
|
+
return ctx.get("sessions")?.get(sessionId)?.header?.cwd;
|
|
2597
2766
|
},
|
|
2598
2767
|
onRun: (info) => {
|
|
2599
2768
|
lastRun = info;
|
|
@@ -2689,59 +2858,66 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2689
2858
|
}
|
|
2690
2859
|
};
|
|
2691
2860
|
syncAskTool();
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
const
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2861
|
+
const registerRoutes = (webServer) => {
|
|
2862
|
+
const disposers = [];
|
|
2863
|
+
const reg = (route) => {
|
|
2864
|
+
const d = webServer.register(route);
|
|
2865
|
+
if (typeof d === "function") disposers.push(d);
|
|
2866
|
+
};
|
|
2867
|
+
const sendJson = (res, status, body) => {
|
|
2868
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
2869
|
+
res.end(JSON.stringify(body));
|
|
2870
|
+
};
|
|
2871
|
+
const readBody = (req) => {
|
|
2872
|
+
const r = req;
|
|
2873
|
+
return new Promise((resolve) => {
|
|
2874
|
+
const chunks = [];
|
|
2875
|
+
r.on?.("data", (c) => chunks.push(c));
|
|
2876
|
+
r.on?.("end", () => {
|
|
2877
|
+
try {
|
|
2878
|
+
const v = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
2879
|
+
resolve(v && typeof v === "object" ? v : {});
|
|
2880
|
+
} catch {
|
|
2881
|
+
resolve({});
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2709
2884
|
});
|
|
2710
|
-
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
if (webServer) {
|
|
2717
|
-
webServer.register({
|
|
2885
|
+
};
|
|
2886
|
+
const methodOf = (req) => {
|
|
2887
|
+
const m = req.method;
|
|
2888
|
+
return typeof m === "string" ? m.toUpperCase() : "GET";
|
|
2889
|
+
};
|
|
2890
|
+
reg({
|
|
2718
2891
|
kind: "exact",
|
|
2719
2892
|
path: "/plugins/agy-link/status",
|
|
2720
2893
|
handler: (_req, res) => {
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2894
|
+
(async () => {
|
|
2895
|
+
const cfg = getConfig();
|
|
2896
|
+
const cat = catalog.get();
|
|
2897
|
+
sendJson(res, 200, {
|
|
2898
|
+
plugin: "dsh-agy-link",
|
|
2899
|
+
bin: bin(),
|
|
2900
|
+
version: versionCache,
|
|
2901
|
+
dormantReason,
|
|
2902
|
+
enabled: cfg.enabled,
|
|
2903
|
+
permissionMode: cfg.permissionMode,
|
|
2904
|
+
workspaceRoot: cfg.workspaceRoot,
|
|
2905
|
+
defaultModel: cfg.defaultModel,
|
|
2906
|
+
defaultEffort: cfg.defaultEffort,
|
|
2907
|
+
askTool: cfg.askTool,
|
|
2908
|
+
auth: await auth.resolvedStatus(),
|
|
2909
|
+
catalog: {
|
|
2910
|
+
source: cat.source,
|
|
2911
|
+
count: cat.models.length,
|
|
2912
|
+
lastError: cat.lastError ?? null
|
|
2913
|
+
},
|
|
2914
|
+
bindings: Object.keys(store.all()).length,
|
|
2915
|
+
lastRun
|
|
2916
|
+
});
|
|
2917
|
+
})();
|
|
2742
2918
|
}
|
|
2743
2919
|
});
|
|
2744
|
-
|
|
2920
|
+
reg({
|
|
2745
2921
|
kind: "exact",
|
|
2746
2922
|
path: "/plugins/agy-link/auth",
|
|
2747
2923
|
handler: (req, res) => {
|
|
@@ -2756,7 +2932,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2756
2932
|
})();
|
|
2757
2933
|
}
|
|
2758
2934
|
});
|
|
2759
|
-
|
|
2935
|
+
reg({
|
|
2760
2936
|
kind: "exact",
|
|
2761
2937
|
path: "/plugins/agy-link/auth-code",
|
|
2762
2938
|
handler: (req, res) => {
|
|
@@ -2777,7 +2953,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2777
2953
|
})();
|
|
2778
2954
|
}
|
|
2779
2955
|
});
|
|
2780
|
-
|
|
2956
|
+
reg({
|
|
2781
2957
|
kind: "exact",
|
|
2782
2958
|
path: "/plugins/agy-link/config",
|
|
2783
2959
|
handler: (req, res) => {
|
|
@@ -2792,7 +2968,8 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2792
2968
|
"permissionMode",
|
|
2793
2969
|
"defaultModel",
|
|
2794
2970
|
"defaultEffort",
|
|
2795
|
-
"askTool"
|
|
2971
|
+
"askTool",
|
|
2972
|
+
"workspaceRoot"
|
|
2796
2973
|
].includes(key)) {
|
|
2797
2974
|
sendJson(res, 400, { error: "key not settable" });
|
|
2798
2975
|
return;
|
|
@@ -2807,7 +2984,7 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2807
2984
|
})();
|
|
2808
2985
|
}
|
|
2809
2986
|
});
|
|
2810
|
-
|
|
2987
|
+
reg({
|
|
2811
2988
|
kind: "exact",
|
|
2812
2989
|
path: "/plugins/agy-link/qr",
|
|
2813
2990
|
handler: (_req, res) => {
|
|
@@ -2838,7 +3015,17 @@ function apply(ctx, entryConfig = {}) {
|
|
|
2838
3015
|
})();
|
|
2839
3016
|
}
|
|
2840
3017
|
});
|
|
2841
|
-
|
|
3018
|
+
return () => {
|
|
3019
|
+
for (const d of disposers) try {
|
|
3020
|
+
d();
|
|
3021
|
+
} catch {}
|
|
3022
|
+
};
|
|
3023
|
+
};
|
|
3024
|
+
ctx.inject(["webServer"], (sub) => {
|
|
3025
|
+
const webServer = sub.get("webServer");
|
|
3026
|
+
if (!webServer) return;
|
|
3027
|
+
return registerRoutes(webServer);
|
|
3028
|
+
});
|
|
2842
3029
|
const mediaDir = () => {
|
|
2843
3030
|
const cfg = getConfig();
|
|
2844
3031
|
return cfg.mediaDir !== "" ? cfg.mediaDir : defaultMediaDir(stateDir());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
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",
|