agy-mcp-bridge 0.1.34 → 0.1.36
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/README.md +6 -3
- package/dist/src/agents/antigravity.js +3 -2
- package/dist/src/agents/registry.js +2 -0
- package/dist/src/antigravity/invocation.js +6 -4
- package/dist/src/antigravity/process.js +1 -1
- package/dist/src/security/input.js +3 -1
- package/dist/src/tasks/manager.js +39 -3
- package/dist/src/tools/register.js +2 -2
- package/docs/VERIFICATION.md +14 -0
- package/package.json +1 -1
- package/skills/antigravity-bridge/SKILL.md +2 -1
- package/skills/known-versions.json +4 -0
- package/ui/app.js +2 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# agy-mcp-bridge
|
|
1
|
+
# agy-mcp-bridge.
|
|
2
2
|
|
|
3
3
|
**An MCP server that hands bounded coding tasks to the CLI agents already logged in on your machine.** One TypeScript process speaks MCP Streamable HTTP on `127.0.0.1` and drives `agy` (Antigravity) and/or `codex`. Every task declares its workspace, acceptance criteria, which agent runs it, how isolated it is, and how much it may self-approve. The orchestrating agent (Codex, Claude Code, any MCP client) keeps the review; the bridge does the reading, writing and waiting.
|
|
4
4
|
|
|
@@ -66,20 +66,23 @@ Then call `antigravity_environment` from a fresh session — that is the proof;
|
|
|
66
66
|
| `isolation` | `none` (default — edits the workspace, globally serial) or `worktree` (branch `bridge/<task_id>` under `.bridge-state/worktrees/<task_id>`, parallel up to `max_parallel_worktrees`). Worktree tasks must declare `owns`, and the repo needs at least one commit. Uncommitted changes in the main checkout are **not** copied in. |
|
|
67
67
|
| `owns` | relative paths the agent may touch. After the run the bridge diffs the worktree against this list and reports anything else in `scope.out_of_scope`. It never auto-merges, never deletes the branch, and does not run repo hooks. |
|
|
68
68
|
| `autonomy` | `suggest` \| `auto` \| `full` — an instruction to the CLI agent, not a permission grant. Destructive actions stay hard-denied (see below). |
|
|
69
|
+
| `resume_conversation` | the `conversation_id` of a finished Antigravity task **in the same workspace**: the new task continues that conversation instead of opening one. An agy conversation id names a session on this machine, not inside one project, so the bridge accepts only ids from its own task history and refuses the rest. A task the retention limit dropped can no longer be resumed. |
|
|
69
70
|
|
|
70
71
|
| Tool | What it does |
|
|
71
72
|
|---|---|
|
|
72
73
|
| `antigravity_environment` | CLI versions, allowlist, limits, agents, parallelism, `recent_tasks`, delegation switch |
|
|
73
|
-
| `agents_list` | probed agents, capabilities, availability, default agent, worktree limits (read-only) |
|
|
74
|
+
| `agents_list` | probed agents, capabilities, availability, default agent, worktree limits, and `conversation_resume` for the agents whose CLI was probed (read-only) |
|
|
74
75
|
| `antigravity_delegate` | accepts a task, returns `running` (or `queued`) with a `task_id` |
|
|
75
76
|
| `antigravity_task_status` | cheap poll: state, pid, elapsed, errors, `denied_actions`, byte counts |
|
|
76
77
|
| `antigravity_task_wait` | blocks up to `wait_seconds` (default cap 55 s), returns early when the task ends |
|
|
77
|
-
| `antigravity_task_output` | final response, stdout/stderr, errors, `denied_actions`, truncation |
|
|
78
|
+
| `antigravity_task_output` | final response, stdout/stderr, errors, `denied_actions`, `conversation_id`, truncation |
|
|
78
79
|
| `antigravity_cancel_task` | kills the task's process tree; does not revert file changes |
|
|
79
80
|
| `code_search` | XERJ passage/definition lookup; registered always but **refused while `xerj.enabled` is off** |
|
|
80
81
|
|
|
81
82
|
`completed` means the CLI exited with a parseable result — always `review_required: true`, `acceptance_verified: false`. A 0 exit code can still be reported as `failed` after a permission denial or a timeout. While a task runs, `wait`/`status` return a short `activity` line instead of making the client read logs.
|
|
82
83
|
|
|
84
|
+
Antigravity names each session (`conversation_id`), so a correction can continue the conversation that already read the code instead of re-explaining it: pass that id as `resume_conversation`. The token cost is the honest part — `agy` re-sends the whole conversation every turn. Measured on `agy` 1.4.12 with the bridge's own flags: a first turn cost 6.107 input tokens beside 12.223 cached, the resumed turn 8.394 input beside 28.500 cached. Continuing is cheap compared with writing the same context into a new task, but it is not free. Codex and OpenCode tasks start fresh: the bridge reads no session id out of their output, so it records none and offers no resume. That is a statement about what the bridge parses, not a claim that those CLIs cannot continue a session.
|
|
85
|
+
|
|
83
86
|
Two switches control delegation itself: `agy-mcp-bridge delegation off` blocks new tasks immediately without a restart, and `BRIDGE_DELEGATION=off` pins it at startup. Tasks still refuse to be delegated by policy the skill describes (security, auth, secrets, data deletion, deploy, CI and public API changes are not delegated).
|
|
84
87
|
|
|
85
88
|
## Agents, profiles and pools
|
|
@@ -10,7 +10,7 @@ export function antigravityAdapter(executable, defaultTransport, options = {}) {
|
|
|
10
10
|
capabilities: CAPABILITIES,
|
|
11
11
|
buildInvocation(task) {
|
|
12
12
|
const transport = task.promptTransport ?? defaultTransport;
|
|
13
|
-
const invocation = invocationFor(task.prompt, task.timeoutSeconds, transport, task.model ?? null, task.extraArgs ?? []);
|
|
13
|
+
const invocation = invocationFor(task.prompt, task.timeoutSeconds, transport, task.model ?? null, task.extraArgs ?? [], task.conversationId ?? null);
|
|
14
14
|
return { executable, args: invocation.args, stdin: invocation.stdin, transport, customEnv: options.env };
|
|
15
15
|
},
|
|
16
16
|
async parseResult(raw) {
|
|
@@ -34,10 +34,11 @@ export function antigravityAdapter(executable, defaultTransport, options = {}) {
|
|
|
34
34
|
})
|
|
35
35
|
: [];
|
|
36
36
|
const numTurns = typeof envelope.num_turns === 'number' ? envelope.num_turns : null;
|
|
37
|
+
const conversationId = typeof envelope.conversation_id === 'string' ? envelope.conversation_id : null;
|
|
37
38
|
const result = {
|
|
38
39
|
success: false, response, error: null,
|
|
39
40
|
cliStatus: typeof envelope.status === 'string' ? envelope.status : null,
|
|
40
|
-
numTurns, deniedActions, stats: extractStats(envelope, null)
|
|
41
|
+
numTurns, deniedActions, stats: extractStats(envelope, null), conversationId
|
|
41
42
|
};
|
|
42
43
|
if (/print timeout|AGY_ERROR.*TIMEOUT/i.test(raw.stderr)) {
|
|
43
44
|
return { ...result, timeout: true, error: 'CLI reported a print timeout (possibly with exit code 0)' };
|
|
@@ -22,6 +22,7 @@ export async function createAgentRegistry(config, antigravity) {
|
|
|
22
22
|
id: 'antigravity', label: 'Antigravity', available: antigravityEnabled && antigravity.supported,
|
|
23
23
|
version: antigravity.version || null, executable: antigravity.executable || null,
|
|
24
24
|
capabilities: ['code', 'review', 'test', 'research', 'long-run'],
|
|
25
|
+
conversation_resume: antigravity.conversationResume,
|
|
25
26
|
warnings: antigravityEnabled ? antigravity.warnings : ['Disabled in the bridge config']
|
|
26
27
|
};
|
|
27
28
|
agents.push({
|
|
@@ -110,6 +111,7 @@ export async function createAgentRegistry(config, antigravity) {
|
|
|
110
111
|
id: profileId, label, available: isProfileEnabled && supported,
|
|
111
112
|
version: version || null, executable: cmd || null,
|
|
112
113
|
capabilities: ['code', 'review', 'test', 'research', 'long-run'],
|
|
114
|
+
conversation_resume: antigravity.conversationResume,
|
|
113
115
|
warnings: isProfileEnabled ? [...antigravity.warnings, ...profileWarnings] : profileWarnings
|
|
114
116
|
};
|
|
115
117
|
agents.push({
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/** `conversation` continues an earlier agy session (`--conversation <id>`), so it goes before `--print`: agy takes the prompt as the argument after it. */
|
|
2
|
+
export function invocationFor(prompt, timeout, transport, model = null, extraArgs = [], conversation = null) {
|
|
3
|
+
const lead = conversation ? ['--conversation', conversation] : [];
|
|
2
4
|
const common = ['--print-timeout', `${timeout}s`, '--disable-slash-commands', ...(model ? ['--model', model] : []), ...extraArgs];
|
|
3
5
|
if (transport === 'argv')
|
|
4
|
-
return { args: ['--print', prompt, '--output-format', 'json', ...common], stdin: undefined };
|
|
6
|
+
return { args: [...lead, '--print', prompt, '--output-format', 'json', ...common], stdin: undefined };
|
|
5
7
|
if (transport === 'argv-stream')
|
|
6
|
-
return { args: ['--print', prompt, '--output-format', 'stream-json', ...common], stdin: undefined };
|
|
7
|
-
return { args: ['--input-format', 'stream-json', '--output-format', 'stream-json', ...common], stdin: JSON.stringify({ event: 'user', message: { content: prompt } }) + '\n' };
|
|
8
|
+
return { args: [...lead, '--print', prompt, '--output-format', 'stream-json', ...common], stdin: undefined };
|
|
9
|
+
return { args: [...lead, '--input-format', 'stream-json', '--output-format', 'stream-json', ...common], stdin: JSON.stringify({ event: 'user', message: { content: prompt } }) + '\n' };
|
|
8
10
|
}
|
|
9
11
|
export function finalEnvelope(output, transport) {
|
|
10
12
|
if (transport === 'argv')
|
|
@@ -170,6 +170,6 @@ export async function inspectCli(command, cwd, run = startProcess, requireStdin
|
|
|
170
170
|
['--print', '--output-format', '--print-timeout', '--disable-slash-commands', ...(requireStdin ? ['--input-format'] : [])].every(flag => new RegExp(`(?:^|\\s)${flag}(?=\\s|$)`).test(help.text));
|
|
171
171
|
const verified = VERIFIED_AGY_VERSIONS.includes(actual);
|
|
172
172
|
const warnings = supported && !verified ? [`agy ${actual} is compatible by 1.4.x version/help checks, but only ${VERIFIED_AGY_VERSIONS.join(' and ')} have been verified with real agent execution`] : [];
|
|
173
|
-
return { version: actual, supported, executable: command.executable, prefixArgs: command.prefixArgs, warnings };
|
|
173
|
+
return { version: actual, supported, executable: command.executable, prefixArgs: command.prefixArgs, warnings, conversationResume: /(?:^|\s)--conversation(?=\s|$)/.test(help.text) };
|
|
174
174
|
}
|
|
175
175
|
//# sourceMappingURL=process.js.map
|
|
@@ -11,7 +11,9 @@ export const delegateSchema = z.object({
|
|
|
11
11
|
agent: z.string().trim().min(1).max(64).optional(),
|
|
12
12
|
isolation: z.enum(['none', 'worktree']).default('none'),
|
|
13
13
|
owns: z.array(ownPath).max(100).default([]),
|
|
14
|
-
autonomy: z.enum(['suggest', 'auto', 'full']).optional()
|
|
14
|
+
autonomy: z.enum(['suggest', 'auto', 'full']).optional(),
|
|
15
|
+
/** Continue an Antigravity conversation the bridge itself ran, by the `conversation_id` in that task's output. */
|
|
16
|
+
resume_conversation: z.uuid().optional().describe('Continue an earlier Antigravity conversation instead of starting one: pass the conversation_id of a finished task in this same workspace. The bridge only accepts conversations it ran itself')
|
|
15
17
|
}).strict().superRefine((input, ctx) => {
|
|
16
18
|
if (input.isolation === 'worktree' && input.owns.length === 0) {
|
|
17
19
|
ctx.addIssue({ code: 'custom', path: ['owns'], message: 'Worktree isolation requires at least one path in owns' });
|
|
@@ -118,7 +118,9 @@ export class TaskManager {
|
|
|
118
118
|
return {
|
|
119
119
|
agents: this.agents.map(({ adapter, status, enabled }) => ({
|
|
120
120
|
id: adapter.id, label: adapter.label, enabled, available: status.available, version: status.version,
|
|
121
|
-
executable: status.executable, capabilities: adapter.capabilities, warnings: status.warnings
|
|
121
|
+
executable: status.executable, capabilities: adapter.capabilities, warnings: status.warnings,
|
|
122
|
+
// A client only offers `resume_conversation` for an agent whose CLI was probed and shown to take `--conversation`.
|
|
123
|
+
conversation_resume: status.conversation_resume
|
|
122
124
|
})),
|
|
123
125
|
default_agent: this.defaultAgent,
|
|
124
126
|
max_parallel_worktrees: this.maxParallelWorktrees
|
|
@@ -128,6 +130,13 @@ export class TaskManager {
|
|
|
128
130
|
delegate(raw, meta = {}) {
|
|
129
131
|
return this.admitInOrder(raw, meta);
|
|
130
132
|
}
|
|
133
|
+
/** The task that ran this agy conversation, while it is still in the bridge's history (a restart reloads it from disk). */
|
|
134
|
+
conversationOwner(conversationId) {
|
|
135
|
+
for (const entry of this.entries.values())
|
|
136
|
+
if (entry.task.conversation_id === conversationId)
|
|
137
|
+
return entry.task;
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
131
140
|
admitInOrder(raw, meta, rerun) {
|
|
132
141
|
const admitted = this.admission.then(() => this.admit(raw, meta, rerun));
|
|
133
142
|
this.admission = admitted.then(() => undefined, () => undefined);
|
|
@@ -158,6 +167,25 @@ export class TaskManager {
|
|
|
158
167
|
}
|
|
159
168
|
const supported = Boolean(agent?.status.available);
|
|
160
169
|
const autonomy = input.autonomy ?? this.config.default_autonomy ?? 'auto';
|
|
170
|
+
// An agy conversation id is machine-global, so the bridge only continues one it ran itself and only in the workspace that
|
|
171
|
+
// created it: otherwise a client could aim a task at another project's context, or at the owner's own interactive session.
|
|
172
|
+
let resumeConversation = null;
|
|
173
|
+
if (input.resume_conversation !== undefined) {
|
|
174
|
+
if (agent?.adapter.family !== 'antigravity') {
|
|
175
|
+
throw new Error(`resume_conversation needs Antigravity, not agent "${agent?.adapter.id ?? input.agent ?? 'the default agent'}"`);
|
|
176
|
+
}
|
|
177
|
+
if (agent.status.conversation_resume === false) {
|
|
178
|
+
throw new Error(`The Antigravity CLI${agent.status.version ? ` ${agent.status.version}` : ''} has no --conversation, so no task can continue a conversation`);
|
|
179
|
+
}
|
|
180
|
+
const source = this.conversationOwner(input.resume_conversation);
|
|
181
|
+
if (!source) {
|
|
182
|
+
throw new Error(`resume_conversation "${input.resume_conversation}" is not a conversation this bridge ran. Read conversation_id from a finished task's output; a task the retention limit dropped can no longer be resumed`);
|
|
183
|
+
}
|
|
184
|
+
if (source.workspace !== workspace) {
|
|
185
|
+
throw new Error(`Conversation "${input.resume_conversation}" was created in another workspace, so it is not continued here`);
|
|
186
|
+
}
|
|
187
|
+
resumeConversation = input.resume_conversation;
|
|
188
|
+
}
|
|
161
189
|
// Everything from here to the registration of the entry is synchronous, so concurrent callers cannot both claim a slot.
|
|
162
190
|
// Ordinary tasks are serialized globally; worktree tasks may run in parallel up to max_parallel_worktrees.
|
|
163
191
|
const queued = supported && !this.canStart(input.isolation);
|
|
@@ -183,6 +211,7 @@ export class TaskManager {
|
|
|
183
211
|
stdout: '', stderr: '', stdout_bytes: 0, stderr_bytes: 0,
|
|
184
212
|
stdout_truncated: false, stderr_truncated: false,
|
|
185
213
|
final_response: null, cli_status: null, error: null, denied_actions: [], review_required: true, acceptance_verified: false,
|
|
214
|
+
conversation_id: resumeConversation,
|
|
186
215
|
...(rerun ? { retry_of: rerun.retryOf, elevation: rerun.elevation } : {})
|
|
187
216
|
};
|
|
188
217
|
const pools = this.config.agents?.pools ?? {};
|
|
@@ -197,7 +226,9 @@ export class TaskManager {
|
|
|
197
226
|
agent: task.agent, isolation: input.isolation, owns: input.owns, autonomy,
|
|
198
227
|
model: agent?.adapter.family === 'antigravity' ? await readModel(this.runtime.stateDirectory) : null,
|
|
199
228
|
permission: permissionState(this.runtime.stateDirectory).level,
|
|
200
|
-
effort: readEffort(this.runtime.stateDirectory)
|
|
229
|
+
effort: readEffort(this.runtime.stateDirectory),
|
|
230
|
+
// Only a task that continues a conversation carries the field, so the request of an ordinary task keeps its shape.
|
|
231
|
+
...(resumeConversation ? { resume_conversation: resumeConversation } : {})
|
|
201
232
|
}
|
|
202
233
|
};
|
|
203
234
|
this.entries.set(task.task_id, entry);
|
|
@@ -349,7 +380,9 @@ export class TaskManager {
|
|
|
349
380
|
autonomy, owns: task.owns, taskId: task.task_id, stateDirectory: this.runtime.stateDirectory,
|
|
350
381
|
// The task's model is a per-family setting: a codex model handed to another family would override that family's own.
|
|
351
382
|
model: antigravity ? entry.request?.model ?? null : agent.adapter.family === 'codex' ? this.config.agents?.codex_model : undefined,
|
|
352
|
-
extraArgs, promptTransport: transport, elevation
|
|
383
|
+
extraArgs, promptTransport: transport, elevation,
|
|
384
|
+
// Set when this task continues an earlier Antigravity conversation (validated when the task was admitted).
|
|
385
|
+
conversationId: task.conversation_id
|
|
353
386
|
});
|
|
354
387
|
if (invocation.resultFile)
|
|
355
388
|
await mkdir(path.dirname(invocation.resultFile), { recursive: true });
|
|
@@ -536,6 +569,9 @@ export class TaskManager {
|
|
|
536
569
|
if (result.stats)
|
|
537
570
|
entry.stats = { ...result.stats, steps: entry.steps?.total ?? null };
|
|
538
571
|
task.cli_status = result.cliStatus;
|
|
572
|
+
// The CLI names the conversation it used, which is what a follow-up task hands back as resume_conversation.
|
|
573
|
+
if (result.conversationId)
|
|
574
|
+
task.conversation_id = result.conversationId;
|
|
539
575
|
task.final_response = result.response === null ? null : redact(result.response);
|
|
540
576
|
task.denied_actions = result.deniedActions.slice(0, 100).map(item => ({
|
|
541
577
|
action: redact(item.action).slice(0, 200), display_name: redact(item.display_name).slice(0, 200),
|
|
@@ -62,7 +62,7 @@ export function createServer(manager, environment, missions) {
|
|
|
62
62
|
inputSchema: z.object({}).strict(), annotations: { readOnlyHint: true, openWorldHint: false }
|
|
63
63
|
}, async () => response(manager.agentsOverview()));
|
|
64
64
|
server.registerTool('antigravity_delegate', {
|
|
65
|
-
description: 'Start one supervised agent task. Pick agent (default from config), isolation "worktree" plus owns paths for parallel isolated work, and autonomy. Returns running with task_id; poll status/output. Completion always needs Tech Lead diff/test review. Never authorize destructive/system/deploy actions with this tool.',
|
|
65
|
+
description: 'Start one supervised agent task. Pick agent (default from config), isolation "worktree" plus owns paths for parallel isolated work, and autonomy. To correct a finished Antigravity task without re-explaining it, pass resume_conversation with that task\'s conversation_id. Returns running with task_id; poll status/output. Completion always needs Tech Lead diff/test review. Never authorize destructive/system/deploy actions with this tool.',
|
|
66
66
|
inputSchema: delegateSchema, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
|
|
67
67
|
}, async (input, ctx) => guard(() => manager.delegate(input, { client: clientLabel(ctx.mcpReq.envelope) })));
|
|
68
68
|
server.registerTool('antigravity_task_status', {
|
|
@@ -91,7 +91,7 @@ export function createServer(manager, environment, missions) {
|
|
|
91
91
|
return withNextStep(manager.summary(task_id), manager.activityNote(task_id));
|
|
92
92
|
}));
|
|
93
93
|
server.registerTool('antigravity_task_output', {
|
|
94
|
-
description: 'Get final response, partial stdout/stderr logs, errors and truncation metadata. Does not wait for completion.',
|
|
94
|
+
description: 'Get final response, partial stdout/stderr logs, errors and truncation metadata. conversation_id names the Antigravity session a follow-up task can continue with resume_conversation. Does not wait for completion.',
|
|
95
95
|
inputSchema: taskIdSchema, annotations: { readOnlyHint: true, openWorldHint: false }
|
|
96
96
|
}, async ({ task_id }) => guard(() => manager.get(task_id)));
|
|
97
97
|
server.registerTool('antigravity_cancel_task', {
|
package/docs/VERIFICATION.md
CHANGED
|
@@ -86,3 +86,17 @@ Unit/integration dùng mock nằm ở `tests/bridge.test.ts`, được tách kh
|
|
|
86
86
|
- [Codex MCP](https://developers.openai.com/codex/mcp/): stdio command/args và timeouts. `codex mcp --help`, `codex mcp add --help` xác nhận CLI quản lý cấu hình hiện hành.
|
|
87
87
|
|
|
88
88
|
Tài liệu headless ghi mặc định print-timeout 5m và workspace write tự cho phép; help 1.4.3 tại máy ghi 0s, và thử ghi thực tế bị từ chối. Bridge luôn truyền timeout rõ ràng và tôn trọng denial thực tế, không giả định theo mô tả chung.
|
|
89
|
+
|
|
90
|
+
## Bổ sung 2026-11-06 — hội thoại Antigravity (`--conversation`)
|
|
91
|
+
|
|
92
|
+
Máy thật, `agy` 1.4.12 (`%LOCALAPPDATA%\agy\bin\agy.exe`), tài khoản đang đăng nhập. Các con số dưới là JSON do chính CLI trả, không suy ra từ tài liệu.
|
|
93
|
+
|
|
94
|
+
- Envelope in ra có `conversation_id`; phiên thứ hai với `--conversation <id>` trả về **cùng id** và `num_turns: 2`. CLI đọc hội thoại từ `~/.gemini/antigravity-cli/conversations/<id>.db`, tức **id này là toàn máy**, không gắn với một thư mục dự án.
|
|
95
|
+
- Bridge dùng điều đó làm hai lớp chặn: `resume_conversation` chỉ nhận id mà chính bridge đã ghi trong lịch sử task, và chỉ trong **đúng workspace** đã tạo hội thoại đó. Không có hai chặn này, một client chọn id tùy ý sẽ nạp ngữ cảnh của dự án khác, hoặc của phiên tương tác của chủ máy, vào task.
|
|
96
|
+
- Chi phí token đo bằng cờ của bridge (`--disable-slash-commands --mode auto`): lượt đầu 6.107 input / 12.223 cache-read; lượt tiếp theo của cùng hội thoại 8.394 input / 28.500 cache-read. `agy` gửi lại toàn bộ hội thoại làm input mỗi lượt — tiếp nối rẻ hơn viết lại ngữ cảnh, nhưng không miễn phí.
|
|
97
|
+
- `--mode auto` in cảnh báo `not available for this account; continuing in the default mode` ở cả hai lượt; bridge không đổi hành vi vì cảnh báo này.
|
|
98
|
+
- Probe `--help` quyết định `agents_list.conversation_resume`, và probe tại máy cho thấy cờ này **không phải thứ phân biệt các bản**: `agy` 1.4.3 (bảng help ghi ngày 2026-10-26, mục "Các tham số ... được công bố bởi help tại máy" ở phần trên) và `agy` 1.5.1 (đo lại 2026-11-06) đều liệt kê `--conversation`. Điều bridge từ chối resume vì help không có cờ do đó gần như không xảy ra trên 1.4.x; gate chủ yếu để chặn các bản CLI cũ hơn mà bridge chưa từng probe. Chưa chạy task resume thật trên 1.4.3 — bằng chứng execution chỉ có ở 1.4.12. Codex: điều đã đo là **phía bridge** — `src/agents/codex.ts` chỉ đọc các event `error`, `turn.completed`, `item.completed` từ `codex exec --json` và không lấy id nào, nên bridge ghi `null` và không hứa resume. Việc bản thân CLI Codex có tiếp tục được một phiên hay không **chưa đo ở đây**, và tài liệu này không khẳng định là không.
|
|
99
|
+
- Bridge **không** dùng `-c` / `--continue` ("Continue the most recent conversation", có trong help 1.5.1): nó trỏ tới hội thoại mới nhất của toàn máy, kể cả phiên tương tác mà chính chủ máy đang dùng, nên một task headless có thể ghi đè ngữ cảnh đó. `--conversation <id>` cộng với hai lớp chặn ở trên là lựa chọn có chủ đích, không phải thiếu sót.
|
|
100
|
+
- `--conversation` dùng chung với `--json-schema` vẫn chạy: cùng `conversation_id`, và CLI thêm trường `structured_output` chứa object đã_validate (trường `response` vẫn lẫn văn bản model tự viết, nên muốn tin thì phải đọc `structured_output`). Lượt đó mất 4 turn và 702 giây — bridge chưa dùng `--json-schema`, mục này chỉ ghi lại rằng hai cờ không xung đột.
|
|
101
|
+
- Không đo: `agy_acp_server.par` (artifact Google có trên CDN cho Windows x64/arm64, bản 1.4.1). Đó là hướng thay đổi adapter, không thuộc phạm vi bổ sung này.
|
|
102
|
+
- Đo lúc 2026-11-06 (sau khi các dòng trên đã ghi): `agy --version` trên máy này là **1.5.1** — CLI tự cập nhật, còn các số liệu token/help ở trên lấy khi nó là 1.4.12. Help 1.5.1 vẫn liệt kê đầy đủ mọi cờ mà probe yêu cầu (`--print`, `--output-format`, `--print-timeout`, `--disable-slash-commands`, `--input-format`, thêm cả `--conversation`, `--json-schema`, `--mode`, `--effort`, `--log-file`), nhưng `supported` ở `src/antigravity/process.ts` đòi `/^1\.4\.\d+$/`, nên bridge hiện đánh dấu Antigravity unavailable và **từ chối mọi delegation trên chính máy này**.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agy-mcp-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "Local loopback HTTP MCP bridge that delegates bounded coding tasks to multiple agent CLIs (Antigravity, Codex) with optional parallel git worktrees",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -93,6 +93,7 @@ For independent work that may run in parallel, isolate it in a git worktree and
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
- `agent` and `autonomy` are optional. `isolation` defaults to `"none"`; `"worktree"` requires at least one `owns` entry, and `owns` is rejected with `"none"`.
|
|
96
|
+
- Every Antigravity task reports `conversation_id`. By default each delegation still starts a new conversation, so keep writing the context the task needs. `resume_conversation` (that id, same workspace, Antigravity only) continues the earlier one instead: use it for a correction of the same work, and treat what it carries over as history, not as instructions. It costs no extra description, but it does not restore the earlier task's diff or permissions.
|
|
96
97
|
- `owns` entries are relative paths inside the workspace. Absolute paths, drive paths, `.` and `..` segments are rejected. The bridge compares the final diff and reports `scope.out_of_scope`.
|
|
97
98
|
- A worktree task runs on branch `bridge/<task_id>` and returns `worktree`/`branch`. The bridge never auto-merges it; the Tech Lead reviews and integrates.
|
|
98
99
|
- `autonomy` (`suggest`/`auto`/`full`) only tells the CLI how much to ask within the declared scope. Deployment, data deletion, credential access, protected-branch pushes and system changes outside the workspace stay forbidden; never add bypass flags.
|
|
@@ -131,6 +132,6 @@ Client disconnect does not cancel an accepted task. Reconnect to the same runnin
|
|
|
131
132
|
|
|
132
133
|
Compare current tracked and untracked files with the baseline; for a worktree task, compare inside `task.worktree` on `task.branch` instead. Inspect the actual code and any affected backend/API contracts. Run meaningful authorized checks yourself; distinguish mock/protocol tests from real CLI integration. Evaluate each acceptance criterion. The bridge always returns `review_required=true` and `acceptance_verified=false`; do not override this by trusting the Developer's report.
|
|
133
134
|
|
|
134
|
-
If correction is needed, submit a new bounded task with the exact defect, relevant failure output, files to preserve, and updated criteria. Include prior findings as context; a previous task ID alone does not resume its CLI conversation
|
|
135
|
+
If correction is needed, submit a new bounded task with the exact defect, relevant failure output, files to preserve, and updated criteria. Include prior findings as context; a previous task ID alone does not resume its CLI conversation, but its `conversation_id` does, through `resume_conversation`. If progress requires a new architecture decision, broader permissions or destructive actions, stop that action for the user's decision while continuing independent review where possible.
|
|
135
136
|
|
|
136
137
|
Report changed files, checks actually run and their results, unmet criteria and limitations. Never claim code was tested/deployed merely because the agent said so. Never delegate deployment, data deletion, system changes, commits or pushes through this bridge. Those actions require their own explicit authorized workflow.
|
|
@@ -69,6 +69,10 @@
|
|
|
69
69
|
{
|
|
70
70
|
"sha256": "d0f94ad3c993ffc7778b68309217704ef236d370a6ea1edbe360f92980af5984",
|
|
71
71
|
"note": "profile and pool ids as agent, quota failover at-most-once, attempts[] evidence, code_search refusal"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"sha256": "006be28209670d2285ef243952643dade865f2ae063b149d9a5f73894f7d896e",
|
|
75
|
+
"note": "conversation_id and resume_conversation: a correction can continue the same Antigravity conversation"
|
|
72
76
|
}
|
|
73
77
|
],
|
|
74
78
|
"mission-orchestrator": [
|
package/ui/app.js
CHANGED
|
@@ -674,7 +674,8 @@ function detailView() {
|
|
|
674
674
|
['Gửi lúc', clock(task.queued_at || task.started_at)], ['Bắt đầu chạy', queued ? 'Chưa chạy' : clock(task.started_at)], ['Kết thúc', clock(task.ended_at)],
|
|
675
675
|
['Thời gian chạy', running ? elapsedEl(task.started_at) : queued ? '—' : duration(task.duration_ms)],
|
|
676
676
|
['Giới hạn', request ? `${request.timeout_seconds}s` : '—'], ['Model', request && 'model' in request ? request.model || 'mặc định' : '—'], ['Quyền', request && request.permission ? (PERMISSION_LEVELS.find(l => l.id === request.permission)?.title || request.permission) : '—'], ['Effort', request && request.effort ? request.effort : '—'], ['Tổng token', state.detail?.stats?.usage && 'total_tokens' in state.detail.stats.usage ? number(state.detail.stats.usage.total_tokens) : '—'], ['Usage (agy báo)', usageLine(state.detail?.stats?.usage) ?? '—'], ['Exit code', task.exit_code ?? '—'], ['PID', task.pid ?? '—'],
|
|
677
|
-
['CLI status', task.cli_status ?? '—']
|
|
677
|
+
['CLI status', task.cli_status ?? '—'],
|
|
678
|
+
['Hội thoại agy', task.conversation_id ? h('span', {class: 'mono', title: request?.resume_conversation ? 'Task này tiếp tục hội thoại của task trước, nên nó mang ngữ cảnh task trước để lại.' : 'Định danh hội thoại agy của task này.', text: `${task.conversation_id}${request?.resume_conversation ? ' (tiếp nối)' : ''}`}) : '—']].map(([k, v]) => h('div', {}, h('dt', {text: k}), h('dd', {}, typeof v === 'string' || typeof v === 'number' ? String(v) : v))));
|
|
678
679
|
|
|
679
680
|
const asker = request?.client ? `Tech Lead · ${request.client}` : 'Tech Lead';
|
|
680
681
|
const asked = h('div', {class: 'msg out'},
|