agy-mcp-bridge 0.1.35 → 0.1.37
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 +7 -4
- package/dist/src/agents/antigravity.js +4 -3
- package/dist/src/agents/registry.js +2 -0
- package/dist/src/antigravity/invocation.js +6 -4
- package/dist/src/antigravity/process.js +12 -5
- package/dist/src/runtime.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/HARDENING.md +2 -2
- package/docs/VERIFICATION.md +39 -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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
> **There is no authentication, on purpose.** The bridge binds `127.0.0.1` only, but any process running as your user on this machine can call its tools, read and cancel tasks, and shut it down. Do not expose the port, do not put it behind a public reverse proxy, do not run it where you do not control local processes. [Security model](#security-model).
|
|
6
6
|
|
|
7
|
-
Package `agy-mcp-bridge` on npm · requires Node.js ≥ 20 · Git · a logged-in `agy` 1.
|
|
7
|
+
Package `agy-mcp-bridge` on npm · requires Node.js ≥ 20 · Git · a logged-in `agy` 1.x and/or Codex CLI.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -18,7 +18,7 @@ Fastest first run: from inside the project you want to work on, `agy-mcp-bridge
|
|
|
18
18
|
|
|
19
19
|
`agy-mcp-bridge check | setup codex|claude|freebuff|both | delegation on|off|status | skill install | config | update | help`. `--open` launches the web UI in your browser. Config and task history live in the user data directory (`%APPDATA%\agy-mcp-bridge`, `~/.config/agy-mcp-bridge`, `~/Library/Application Support/agy-mcp-bridge`; override with `BRIDGE_HOME`), so an upgrade never deletes them. `agy-mcp-bridge config` prints the real paths.
|
|
20
20
|
|
|
21
|
-
At startup the bridge probes each CLI. An agent that is missing or unsupported is marked unavailable and takes no task; if `agy` is unavailable, the default agent silently becomes the next enabled one, so check `agents_list` after changing machines. Priority and per-agent switches live under `agents` in `config/workspaces.json`.
|
|
21
|
+
At startup the bridge probes each CLI. An agent that is missing or unsupported is marked unavailable and takes no task; if `agy` is unavailable, the default agent silently becomes the next enabled one, so check `agents_list` after changing machines. Priority and per-agent switches live under `agents` in `config/workspaces.json`. The `agy` gate is the 1.x line plus the flags the bridge actually passes, read from the CLI's own help — `agy` updates itself, so pinning one minor would stop delegation on a machine whose CLI is fine. Real agent execution verified on 1.4.3, 1.4.7, 1.4.8, 1.4.9, 1.5.1 and 1.5.2; any other 1.x release runs with a logged warning; a release missing a required flag, or a different major, is refused.
|
|
22
22
|
|
|
23
23
|
From a git clone instead of npm: `npm ci && npm run build && npm start`, then `npm run check` in a second terminal. `npm run dev` builds and runs; there is no hot reload, because an automatic restart would cut off a task mid-edit.
|
|
24
24
|
|
|
@@ -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)' };
|
|
@@ -60,7 +61,7 @@ export function legacyAntigravityAgent(runtime, transport) {
|
|
|
60
61
|
const status = {
|
|
61
62
|
id: 'antigravity', label: 'Antigravity', available: runtime.supported,
|
|
62
63
|
version: null, executable: runtime.executable || null, capabilities: CAPABILITIES,
|
|
63
|
-
warnings: runtime.supported ? [] : ['Requires agy 1.
|
|
64
|
+
warnings: runtime.supported ? [] : ['Requires agy 1.x and all required print/output/timeout flags']
|
|
64
65
|
};
|
|
65
66
|
return { adapter: antigravityAdapter(runtime.executable, transport), status, enabled: true, prefixArgs: runtime.prefixArgs, launch: runtime.launch };
|
|
66
67
|
}
|
|
@@ -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')
|
|
@@ -2,8 +2,12 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { access, readFile, realpath } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { inside } from '../security/workspace.js';
|
|
5
|
-
/**
|
|
6
|
-
export const VERIFIED_AGY_VERSIONS = ['1.4.3', '1.4.7', '1.4.8', '1.4.9'];
|
|
5
|
+
/** Releases exercised with a real agent run; any other release of the 1.x line still runs, with a warning. */
|
|
6
|
+
export const VERIFIED_AGY_VERSIONS = ['1.4.3', '1.4.7', '1.4.8', '1.4.9', '1.5.1', '1.5.2'];
|
|
7
|
+
/** The version family the bridge accepts. Exported because the live verifier has to ask the same question of the CLI it is about to spend real tasks on. */
|
|
8
|
+
export function isSupportedAgyVersion(version) {
|
|
9
|
+
return /^1\.\d+\.\d+$/.test(version);
|
|
10
|
+
}
|
|
7
11
|
/**
|
|
8
12
|
* Resolve a command without ever going through a shell. On Windows an npm-installed CLI is usually a `.cmd`
|
|
9
13
|
* shim; spawning it with shell:false fails, and spawning it through cmd.exe would allow argument injection.
|
|
@@ -166,10 +170,13 @@ export async function inspectCli(command, cwd, run = startProcess, requireStdin
|
|
|
166
170
|
const version = await capture(['--version']);
|
|
167
171
|
const help = await capture(['--help']);
|
|
168
172
|
const actual = version.text.trim();
|
|
169
|
-
|
|
173
|
+
// The family is the agy 1.x line, not one minor of it: agy updates itself, and a minor bump it ships would otherwise
|
|
174
|
+
// mark the agent unavailable and stop every delegation on a machine that is otherwise fine. What has to hold is the
|
|
175
|
+
// flags this bridge passes, checked against the CLI's own help, so a release that drops one is still refused.
|
|
176
|
+
const supported = version.code === 0 && isSupportedAgyVersion(actual) && help.code === 0 &&
|
|
170
177
|
['--print', '--output-format', '--print-timeout', '--disable-slash-commands', ...(requireStdin ? ['--input-format'] : [])].every(flag => new RegExp(`(?:^|\\s)${flag}(?=\\s|$)`).test(help.text));
|
|
171
178
|
const verified = VERIFIED_AGY_VERSIONS.includes(actual);
|
|
172
|
-
const warnings = supported && !verified ? [`agy ${actual} is compatible by 1.
|
|
173
|
-
return { version: actual, supported, executable: command.executable, prefixArgs: command.prefixArgs, warnings };
|
|
179
|
+
const warnings = supported && !verified ? [`agy ${actual} is compatible by 1.x version/help checks, but only ${VERIFIED_AGY_VERSIONS.join(' and ')} have been verified with real agent execution`] : [];
|
|
180
|
+
return { version: actual, supported, executable: command.executable, prefixArgs: command.prefixArgs, warnings, conversationResume: /(?:^|\s)--conversation(?=\s|$)/.test(help.text) };
|
|
174
181
|
}
|
|
175
182
|
//# sourceMappingURL=process.js.map
|
package/dist/src/runtime.js
CHANGED
|
@@ -84,7 +84,7 @@ export async function createRuntime() {
|
|
|
84
84
|
authentication: 'Each agent CLI uses its own cached login; only a live task verifies it.',
|
|
85
85
|
limits: { max_timeout_seconds: config.max_timeout_seconds, max_output_bytes_per_stream: config.max_output_bytes, max_retained_tasks: config.max_retained_tasks, max_mission_steps: MAX_STEPS_PER_MISSION, max_retained_missions: MAX_RETAINED_MISSIONS, concurrent_tasks: maxParallelWorktrees + 1, concurrent_normal_tasks: 1, max_parallel_worktrees: maxParallelWorktrees, max_queued_tasks: config.max_queued_tasks },
|
|
86
86
|
limitations: [
|
|
87
|
-
`Antigravity requires agy 1.
|
|
87
|
+
`Antigravity requires the agy 1.x line with all required flags; real execution was verified on ${VERIFIED_AGY_VERSIONS.join(' and ')}.`,
|
|
88
88
|
'Codex defaults to sandbox workspace-write; the bridge owner may raise sandbox/approval in config (danger-full-access / ask-for-approval never), which is surfaced as agent warnings.',
|
|
89
89
|
'Ordinary tasks are serialized globally. Only tasks with isolation "worktree" run concurrently, each in its own git worktree and branch.',
|
|
90
90
|
'Mission plans are recorded and gated by the bridge, but the bridge never dispatches a step by itself: every step starts on a client call, and a canceled or completed plan grants nothing.',
|
|
@@ -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/HARDENING.md
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
2. Task queued được validate lại trước launch. Chuyển sang running được ghi trước spawn để restore không kết luận sai rằng task chưa từng sửa file. Recheck trạng thái ngay trước spawn ngăn launch tiếp sau cancel/shutdown trong lúc chờ I/O.
|
|
9
9
|
3. POSIX SIGTERM cả nhóm, tối đa 8 giây rồi SIGKILL. Windows vẫn taskkill theo PID/cây thuộc server khi hủy; khi agent tự thoát, bridge còn giải phóng các cây con mà agent để lại (mục "Dọn cây tiến trình agent" dưới đây). Bridge watchdog chạy sau CLI timeout thêm 10 giây; cancellation có thể chờ thêm thời gian cleanup.
|
|
10
10
|
4. Startup và acquisition tự thu hồi lock chỉ khi cả hai PID trong owner đều hợp lệ và không còn sống. Record thiếu/hỏng, PID còn sống, ownership đổi hoặc owner cũ hơn lock thì giữ nguyên. `agy-mcp-bridge unlock` dùng cùng logic và không kill process. PID reuse và server crash vẫn cần xử lý thận trọng; chưa có Windows Job Object.
|
|
11
|
-
5. Family 1.
|
|
11
|
+
5. Family 1.x phải có đủ cờ bắt buộc trong help. Mỗi bản 1.4.3, 1.4.7, 1.4.8, 1.4.9, 1.5.1 và 1.5.2 đều có một lần `npm run test:live` đạt trên CLI thật (smoke + hủy task + shutdown; 1.4.8, 1.4.9, 1.5.1 và 1.5.2 thêm mission ba bước); bản 1.x khác có cảnh báo log/environment; major khác không chạy. Gate không khóa theo minor vì `agy` tự cập nhật: khóa 1.4.x từng làm bridge từ chối mọi task trên máy chỉ vì CLI lên 1.5.1, rồi lại lên 1.5.2 trong vòng ~20 phút sau (đo ngày 2026-11-06, xem VERIFICATION).
|
|
12
12
|
6. Môi trường child mặc định chỉ có PATH, HOME, USERPROFILE, APPDATA, LOCALAPPDATA, TEMP, TMP, TMPDIR, SystemRoot, ComSpec, LANG, LC_*, XDG_*, HTTP_PROXY, HTTPS_PROXY, NO_PROXY; tên không phân biệt hoa thường. Tên chứa TOKEN/SECRET/PASSWORD/API_KEY chỉ được truyền khi có trong `extra_env_passthrough`. Các thiết lập auth/tool phụ thuộc env khác có thể cần opt-in.
|
|
13
13
|
7. Bridge không có bearer token. Chỉ bind 127.0.0.1 và giữ kiểm tra Host/Origin; bất kỳ tiến trình cục bộ cùng user đều có thể điều khiển agent, hủy task hoặc tắt server. Đây là mô hình tin cậy cục bộ có chủ đích, không dùng cho máy chia sẻ hoặc mạng ngoài.
|
|
14
|
-
8. Prompt mặc định vẫn đi qua argv với print/json, là đường đã được chạy thật với `agy` 1.4.3, 1.4.7, 1.4.8
|
|
14
|
+
8. Prompt mặc định vẫn đi qua argv với print/json, là đường đã được chạy thật với `agy` 1.4.3, 1.4.7, 1.4.8, 1.4.9, 1.5.1 và 1.5.2. Chế độ `"prompt_transport": "stdin"` (tùy chọn, chưa mặc định) gửi NDJSON qua stdin để prompt không hiện trong danh sách tiến trình: một event user rồi end, stdout phải có đúng một event result hợp lệ. Chỉ bật khi đã xác nhận bằng một lần chạy `agy` thật; nếu định dạng không khớp, task fail và lỗi có gợi ý quay lại argv. Cap API 24000 ký tự và output cap không thay đổi; progress/init cũng tính vào output.
|
|
15
15
|
9. Output được gom vào mảng chunk; decode/redact snapshot tối đa mỗi giây, cuối task flush đầy đủ. Byte counts và truncated giữ ngữ nghĩa cũ.
|
|
16
16
|
10. Repeated SIGINT/SIGTERM không bỏ cleanup; in thông báo chờ.
|
|
17
17
|
11. Prompt nhận clientLabel thực tế; MCP/UI version đọc manifest. Prerelease được so từng phần, numeric so bằng BigInt, ví dụ rc.2 < rc.10; build metadata không ảnh hưởng precedence.
|
package/docs/VERIFICATION.md
CHANGED
|
@@ -86,3 +86,42 @@ 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**. Bản sửa và bằng chứng chạy thật ở mục dưới.
|
|
103
|
+
|
|
104
|
+
## Bổ sung 2026-11-06 — gate họ CLI khi `agy` tự lên 1.5.1
|
|
105
|
+
|
|
106
|
+
Máy thật, cùng máy với các đo ở trên. `agy` đã tự cập nhật **1.4.12 → 1.5.1** giữa lúc đang đo resume.
|
|
107
|
+
|
|
108
|
+
- Help 1.5.1 vẫn liệt kê đủ mọi cờ probe yêu cầu và cả `--conversation`, `--json-schema`, `--mode`, `--effort`, `--log-file` (đếm trên `agy --help`, không suy từ tài liệu). Vậy 1.5.1 không mất gì so với 1.4.12 về phía cờ.
|
|
109
|
+
- Trước bản sửa: `supported` = false vì `/^1\.4\.\d+$/`, agent báo unavailable, delegation chết hẳn trên máy đang chạy tốt. Đây là bug do pin theo minor, trái với ý định đã ghi trong HARDENING ("minor/major khác không chạy" nhưng agy tự lên minor).
|
|
110
|
+
- Bản sửa: gate là họ `^1\.\d+\.\d+$` + đúng bộ cờ help; major khác vẫn từ chối. `isSupportedAgyVersion` export từ `src/antigravity/process.ts` để **một chỗ duy nhất** trả lời câu hỏi này.
|
|
111
|
+
- Lần chạy `npm run test:live` đầu tiên **thất bại vì chính script verifier**: phần `assert` trong `scripts/live.ts` giữ bản sao `/^1\.4\.\d+$/` riêng và đo `environment.cli_version` theo nó, nên bridge đã nhận 1.5.1 mà live vẫn abort (`error: "The input did not match the regular expression /^1\\.4\\.\\d+$/. Input: '1.5.1'"`, `server_exit_code=0`, `success=false`). Nếu chỉ sửa regex trong `src` mà không chạy live, bug này lọt. `live.ts` nay dùng `isSupportedAgyVersion`.
|
|
112
|
+
- Lần chạy `npm run test:live` thứ hai trên CLI thật 1.5.1: **exit 0**, `success: true`, 14:23:56Z → 14:25:13Z (77 s), smoke `completed` với đúng `BRIDGE_SMOKE_OK`, `response_verified=true`, `workspace_unchanged=true`, `reconnected=true`, mission ba bước `a/b/c` đều `completed`, cancel `canceled`, `shutdown_with_active_task_verified=true`, `server_exit_code=0`.
|
|
113
|
+
- Bằng chứng cộng thêm cho resume: task live trên 1.5.1 trả `conversation_id` thật (`5de8322b-19f0-47bc-ae57-4e735a95a6b5`), tức field bridge đọc từ envelope không phải chỉ có trong fixture.
|
|
114
|
+
- Vì có một loạt task thật đạt, `1.5.1` được thêm vào `VERIFIED_AGY_VERSIONS`; environment trên máy này không còn in cảnh báo "only ... verified".
|
|
115
|
+
- Mutation check (bỏ bản sửa là test phải đỏ): đổi thân `isSupportedAgyVersion` về `/^1\.4\.\d+$/` rồi build lại → `tests/hardening-inspection.test.ts` **đỏ** ngay tại `AssertionError [ERR_ASSERTION]: 1.5.0, actual: false, expected: true`; khôi phục regex họ 1.x → xanh (2/2 test của file).
|
|
116
|
+
- Suite đầy đủ trên nội dung cuối (Windows, Node **24.15.0** — `node -v` trên máy chạy suite): **351 test / 344 pass / 0 fail / 7 skipped**, 370 s, exit 0. `skills/` không đổi nên không đăng ký hash mới.
|
|
117
|
+
- Chưa đo: behavior khác của 1.5.1 và 1.5.2 so với 1.4.x ngoài bộ cờ và các đường live trên (ví dụ `--remote-agent`, `agy_acp_server`).
|
|
118
|
+
|
|
119
|
+
## Bổ sung 2026-11-06 (lần 2) — `agy` lên 1.5.2 ngay khi PR gate đang mở
|
|
120
|
+
|
|
121
|
+
Cùng máy, cùng ngày. Lần đo 1.5.1 kết thúc 14:25:13Z; server build từ nhánh gate khởi động lúc ~14:41Z đã thấy **1.5.2**.
|
|
122
|
+
|
|
123
|
+
- `npm run check` trên built server (không task AI nào): `bridge_status: ready`, `antigravity.available: true`, `version: 1.5.2`, `conversation_resume: true`, kèm đúng cảnh báo dự kiến `agy 1.5.2 is compatible by 1.x version/help checks, but only 1.4.3 and 1.4.7 and 1.4.8 and 1.4.9 and 1.5.1 have been verified with real agent execution`. Đây chính là đường "họ 1.x chấp nhận + cảnh báo" mà bản sửa mở ra; với gate `^1\.4\.\d+$` cũ, 1.5.2 cũng chết delegation như 1.5.1.
|
|
124
|
+
- `npm run test:live` trên CLI thật 1.5.2: **exit 0**, `success: true`, 14:43:01Z → 14:44:07Z (66 s), smoke `completed` exit 0 trả đúng `BRIDGE_SMOKE_OK`, `response_verified=true`, `workspace_unchanged=true`, `reconnected=true`, mission ba bước đều `completed`, cancel `canceled` exit 1 với `process_running=false` và `denied_actions=[]`, `shutdown_with_active_task_verified=true`, `server_exit_code=0`. Task smoke trả `conversation_id` thật `9254265e-acfc-45c1-8e33-a3ef02b59f90`.
|
|
125
|
+
- Theo chuẩn đã ghi (một lần `test:live` **đầy đủ** đạt, không phải một task lẻ), `1.5.2` được thêm vào `VERIFIED_AGY_VERSIONS`, kèm dòng `['1.5.2','',true,false]` trong bảng `tests/hardening-inspection.test.ts`.
|
|
126
|
+
- Mutation check cho chính danh sách: bỏ `1.5.2` khỏi constant rồi build lại → test inspection **đỏ** (`fail 1`, `actual: true, expected: false` — đúng cái cờ `warn` của dòng `['1.5.2','',true,false]`, tức CLI chưa đo phải còn bị cảnh báo); khôi phục constant → xanh (2/2).
|
|
127
|
+
- Suite đầy đủ trên nội dung cuối (Windows, Node 24.15.0): **351 test / 344 pass / 0 fail / 7 skipped**, 384 s, exit 0.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agy-mcp-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
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'},
|