agy-mcp-bridge 0.1.21 → 0.1.22

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.
@@ -3,6 +3,7 @@ import { requestContext } from '../request-context.js';
3
3
  import * as z from 'zod/v4';
4
4
  import { delegateSchema, taskIdSchema, waitSchema } from '../security/input.js';
5
5
  import { redact } from '../security/redact.js';
6
+ import { missionCancelSchema, missionCreateSchema, missionDispatchSchema, missionExtendSchema, missionIdSchema } from '../missions/missions.js';
6
7
  import { readPackageInfo } from '../update.js';
7
8
  /** Best-effort label for who delegated: the client's declared name/version when the request carries it, else the HTTP User-Agent. */
8
9
  function clientLabel(envelope) {
@@ -21,7 +22,27 @@ export const WAITING_NOTE = 'Still running. Do nothing else and write no message
21
22
  const withNextStep = (summary, activity = null) => summary.status === 'running' || summary.status === 'queued'
22
23
  ? { task_id: summary.task_id, status: summary.status, duration_ms: summary.duration_ms, ...(activity ? { activity } : {}), next_step: WAITING_NOTE }
23
24
  : summary;
24
- export function createServer(manager, environment) {
25
+ /**
26
+ * A mission step is started by a task like any other delegation, so the same rule applies after dispatch: report the four fields
27
+ * the client needs to wait, and let the task tools carry the rest. The mission id and step id stay in the reply because the client
28
+ * has to come back to this plan after the wait.
29
+ */
30
+ const withStepNote = (value) => value.status === 'running' || value.status === 'queued'
31
+ ? { mission_id: value.mission_id, step_id: value.step_id, task_id: value.task_id, status: value.status, duration_ms: value.duration_ms, next_step: 'Step started. Do nothing else and write no message; call antigravity_task_wait with this task_id.' }
32
+ : value;
33
+ /** The one line that says what the orchestrator can actually do next, computed from the plan rather than repeated from a description. */
34
+ function missionNote(view) {
35
+ if (view.status === 'canceled')
36
+ return 'Mission is canceled: no step can be dispatched. Create a new mission to continue.';
37
+ if (view.ready.length)
38
+ return `Dispatch a ready step with mission_dispatch (${view.ready.join(', ')}), then call antigravity_task_wait on the task_id it returns.`;
39
+ if (view.progress.active)
40
+ return 'A step is running: call antigravity_task_wait on its task_id and make no other call until it ends.';
41
+ if (view.progress.unmet)
42
+ return 'No step is ready and at least one ended without completing: review that step, then add a correction step with mission_extend or cancel the mission.';
43
+ return 'Every step reached a terminal state. Review each diff and test before accepting the mission; the bridge never merges a worktree branch.';
44
+ }
45
+ export function createServer(manager, environment, missions) {
25
46
  const server = new McpServer({ name: 'antigravity-mcp-bridge', version: readPackageInfo().version });
26
47
  const response = (value) => ({ content: [{ type: 'text', text: JSON.stringify(value) }], structuredContent: value });
27
48
  const guard = async (action) => {
@@ -77,6 +98,53 @@ export function createServer(manager, environment) {
77
98
  description: 'Terminate only the managed task process tree. Partial file changes remain; no automatic rollback.',
78
99
  inputSchema: taskIdSchema, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
79
100
  }, async ({ task_id }) => guard(() => manager.cancel(task_id)));
101
+ // Missions: the bridge records and gates the plan, the client drives it. Nothing here starts an agent on its own.
102
+ const receipt = (mission_id) => {
103
+ const view = missions.view(mission_id);
104
+ return { mission_id, workspace: view.workspace, status: view.status, progress: view.progress, ready: view.ready, next_step: missionNote(view) };
105
+ };
106
+ server.registerTool('mission_create', {
107
+ description: 'Record a dependency-ordered plan of agent tasks, all in one workspace, and return its mission_id. Steps are started only by mission_dispatch from this client: the bridge never runs a step by itself. The plan is append-only, so a retry is a new step rather than an edit. Validation is strict: unique step ids, existing dependencies, no cycles, one workspace, bridge limits.',
108
+ inputSchema: missionCreateSchema, annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false }
109
+ }, async (input, ctx) => guard(async () => {
110
+ const mission = await missions.create(input, { client: clientLabel(ctx.mcpReq.envelope) });
111
+ return receipt(mission.mission_id);
112
+ }));
113
+ server.registerTool('mission_extend', {
114
+ description: 'Append steps to an open mission. Existing steps are never modified: use this for corrections and follow-up work instead of re-dispatching a step that already ran.',
115
+ inputSchema: missionExtendSchema, annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false }
116
+ }, async ({ mission_id, steps }) => guard(async () => {
117
+ await missions.extend(mission_id, steps);
118
+ return receipt(mission_id);
119
+ }));
120
+ server.registerTool('mission_dispatch', {
121
+ description: 'Start one ready step with the arguments recorded in its plan entry, as a normal supervised task. Refused while any dependency is not completed, if the step already has a task, or once the mission is canceled. Returns running with task_id: wait with antigravity_task_wait, then read mission_status. Completion still needs Tech Lead diff/test review, and a worktree branch is never merged automatically.',
122
+ inputSchema: missionDispatchSchema, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true }
123
+ }, async ({ mission_id, step_id }, ctx) => guard(async () => {
124
+ const { task } = await missions.dispatch(mission_id, step_id, { client: clientLabel(ctx.mcpReq.envelope) });
125
+ return withStepNote({ mission_id, step_id, task_id: task.task_id, status: task.status, duration_ms: task.duration_ms });
126
+ }));
127
+ server.registerTool('mission_status', {
128
+ description: 'Mission progress: one short entry per step (state, task_id, dependencies and what blocks them, scope counts) with the step ids that are ready now. No task text or logs, so it is cheap to read after a reconnect or a restart. Does not start or cancel anything.',
129
+ inputSchema: missionIdSchema, annotations: { readOnlyHint: true, openWorldHint: false }
130
+ }, async ({ mission_id }) => guard(() => {
131
+ const view = missions.view(mission_id);
132
+ return { ...view, next_step: missionNote(view) };
133
+ }));
134
+ server.registerTool('mission_plan', {
135
+ description: 'Full recorded plan: mission goal, requesting client and every step with its exact delegate arguments. Use it to recover the plan after losing context, instead of re-reading the repository.',
136
+ inputSchema: missionIdSchema, annotations: { readOnlyHint: true, openWorldHint: false }
137
+ }, async ({ mission_id }) => guard(() => {
138
+ const view = missions.view(mission_id);
139
+ return { ...missions.planOf(mission_id), next_step: missionNote(view) };
140
+ }));
141
+ server.registerTool('mission_cancel', {
142
+ description: 'Cancel a mission so no further step can start. Tasks already running or queued are untouched unless cancel_running_tasks is true, because stopping an agent mid-edit is a separate decision; those task ids are returned. Partial file changes remain and nothing is rolled back.',
143
+ inputSchema: missionCancelSchema, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
144
+ }, async ({ mission_id, cancel_running_tasks }) => guard(async () => {
145
+ const { mission, running, canceled } = await missions.cancel(mission_id, { cancelRunningTasks: cancel_running_tasks === true });
146
+ return { mission_id, status: mission.status, running_tasks: running, canceled_tasks: canceled, progress: missions.view(mission_id).progress };
147
+ }));
80
148
  return server;
81
149
  }
82
150
  //# sourceMappingURL=register.js.map
@@ -70,6 +70,7 @@ async function overview(ctx, tzOffsetMinutes = 0) {
70
70
  progress: readProgress(ctx.manager.stateDirectory),
71
71
  wait: { seconds: ctx.manager.maxWaitSeconds, client_tool_timeout_seconds: clientToolTimeoutSeconds(ctx.manager.maxWaitSeconds) },
72
72
  queue: ctx.manager.overview().queue,
73
+ missions: ctx.missions.overview(),
73
74
  warnings: [await ctx.manager.lockDiagnostic()].filter(Boolean),
74
75
  update: knownUpdate() && { ...knownUpdate(), package: readPackageInfo().name, command: 'agy-mcp-bridge update' },
75
76
  tasks: ctx.manager.list()
@@ -176,6 +177,28 @@ export async function handleUi(req, res, ctx) {
176
177
  json(res, 200, ctx.manager.detail(cancel[1]));
177
178
  return true;
178
179
  }
180
+ if (pathname === '/ui/api/missions' && method === 'GET') {
181
+ json(res, 200, { missions: ctx.missions.list(), ...ctx.missions.overview() });
182
+ return true;
183
+ }
184
+ const mission = pathname.match(new RegExp(`^/ui/api/missions/(${UUID})$`));
185
+ if (mission && method === 'GET') {
186
+ json(res, 200, ctx.missions.planOf(mission[1]));
187
+ return true;
188
+ }
189
+ // Stopping a plan is a decision a person in front of the page may make, but it never touches a running agent:
190
+ // cancelling one of its tasks stays on that task's own page.
191
+ const stopMission = pathname.match(new RegExp(`^/ui/api/missions/(${UUID})/cancel$`));
192
+ if (stopMission && method === 'POST') {
193
+ const body = await readJson(req);
194
+ if (body.confirm !== true) {
195
+ json(res, 400, { error: 'confirm must be true' });
196
+ return true;
197
+ }
198
+ const { mission: stopped, running } = await ctx.missions.cancel(stopMission[1]);
199
+ json(res, 200, { ...ctx.missions.planOf(stopped.mission_id), running_tasks: running });
200
+ return true;
201
+ }
179
202
  if (pathname === '/ui/api/delegation' && method === 'POST') {
180
203
  const body = await readJson(req);
181
204
  if (typeof body.enabled !== 'boolean') {
@@ -1,7 +1,7 @@
1
1
  # Kế hoạch: MCP Bridge đa agent (Orchestrator + Agent Router)
2
2
 
3
- Trạng thái: Phase 1 hoàn tất (2026-10-26); Phase 2 là bước tiếp theo.
4
- Cập nhật: 2026-10-26.
3
+ Trạng thái: Phase 1 hoàn tất (2026-10-26); Phase 2 hoàn tất (2026-10-27), chờ duyệt PR.
4
+ Cập nhật: 2026-10-27.
5
5
 
6
6
  ## 1. Mục tiêu
7
7
 
@@ -167,12 +167,65 @@ Kết quả: `npm test` xanh (125 pass, 3 skip do thiếu môi trường); test
167
167
  đều có test tự động; đã xác minh `resolveCommand` với Codex npm thật
168
168
  (`codex-cli 0.163.0`, shim `.cmd`). Chưa chạy lại `test:live` với Codex thật.
169
169
 
170
- ### Phase 2 — Mission daemon + Orchestrator skill
171
-
172
- - Lưu mission/plan có cấu trúc; trạng thái từng bước; resume sau restart.
173
- - Skill hướng dẫn orchestrator: chọn role, chọn agent, cắt context, ngân sách token.
174
- - Tool: `mission_create`, `mission_status`, `mission_plan`, `mission_cancel`.
175
- - Không cho agent tự sửa plan của agent khác; plan là dữ liệu chỉ đọc với worker.
170
+ ### Phase 2 — Mission daemon + Orchestrator skill (hoàn tất 2026-10-27, chờ duyệt PR)
171
+
172
+ Bốn quyết định đã chốt với chủ dự án trước khi viết code:
173
+
174
+ - **Bridge ghi nhận, agent ngoài điều khiển.** Bridge lưu plan + trạng thái từng bước, **từ chối**
175
+ `mission_dispatch` khi một dependency chưa `completed`, coi plan là append-only, và **không bao giờ
176
+ tự bắt đầu bước nào**. Restart phục hồi plan nhưng không chạy lại gì.
177
+ - Phạm vi: toàn bộ Phase 2 (mission daemon + UI + skill), không cắt bớt.
178
+ - Hai deliverable song song: trang UI **Nhiệm vụ** và skill thứ hai `mission-orchestrator` — tách
179
+ khỏi `antigravity-bridge` vì hai hợp đồng khác nhau (một bên là giao việc đơn lẻ + đọc kết quả,
180
+ một bên là viết plan và điều phối cổng phụ thuộc).
181
+ - Phát hành: nhánh mới từ `main` → PR → chủ dự án tự duyệt; không auto-merge, không release npm.
182
+
183
+ Đã triển khai:
184
+
185
+ 1. `src/missions/missions.ts` — `MissionManager`: `create` / `extend` / `dispatch` / `cancel` /
186
+ `view` / `planOf` / `list` / `overview` / `restore`. Chỉ cần `TaskRunner`
187
+ (`delegate`/`summary`/`cancel`/`stateDirectory`) và `Pick<Config, 'allowed_workspaces' |
188
+ 'max_timeout_seconds'>`, nên mọi cổng chặn test được không cần CLI.
189
+ 2. Cổng chặn lúc `dispatch`: mission đã dừng → bước đã bind task → dependency chưa `completed` →
190
+ bước đang được một call khác dispatch. Sau cổng, việc chạy đi qua **`TaskManager.delegate` y như
191
+ một task thường**, nên toàn bộ bảo vệ Phase 1 (workspace allowlist, `owns`, worktree, timeout,
192
+ redaction, reaper) vẫn áp dụng.
193
+ 3. Trạng thái bước **không lưu trên đĩa**: suy ra từ bản ghi task mỗi lần đọc; bản ghi task bị
194
+ retention đuổi thì bước là `lost` — `lost` không bao giờ mở khóa bước kế tiếp.
195
+ 4. Sáu tool MCP mới: `mission_create`, `mission_extend`, `mission_dispatch`, `mission_status`,
196
+ `mission_plan`, `mission_cancel` (tổng 13). Trả lời gọn theo hướng tiết kiệm token:
197
+ `create`/`extend` trả receipt (id, progress, `ready`, `next_step`) chứ không lặp lại plan;
198
+ `dispatch` chỉ trả `{mission_id, step_id, task_id, status, duration_ms, next_step}`;
199
+ `mission_status` bỏ hẳn text của task; chỉ `mission_plan` mang `delegate` đầy đủ.
200
+ 5. UI: trang **Nhiệm vụ** (`GET /ui/api/missions`, `GET /ui/api/missions/:uuid`,
201
+ `POST /ui/api/missions/:uuid/cancel` cần `confirm: true`) + bộ đếm mission trong overview.
202
+ Trang này **chỉ đọc và dừng kế hoạch**; nút dừng không đi kèm lựa chọn giết agent.
203
+ 6. Skill `skills/mission-orchestrator/SKILL.md` + installer đa skill: `packagedSkills()` liệt kê mọi
204
+ `skills/<name>/SKILL.md`, `--skill <name>` chọn từng cái, `known-versions.json` chuyển sang lịch sử
205
+ hash **riêng từng skill** để hash của skill này không thể authorize việc ghi đè file của skill kia.
206
+
207
+ Giới hạn: 12 bước/mission, plan ≤100.000 ký tự, giữ 20 mission (nạn nhân là bản ghi cũ nhất **không**
208
+ có task đang chạy), `step_id` là slug `[a-z0-9][a-z0-9._-]{0,63}`, timeout mỗi bước vẫn chịu
209
+ `max_timeout_seconds` của bridge, một mission chỉ chạy trong **một** workspace.
210
+
211
+ Sai lệch so với kế hoạch gốc (mục từng nêu bốn tool):
212
+
213
+ - Thêm `mission_dispatch` và `mission_extend`. `mission_create` không thể bắt đầu bước (điều kiện
214
+ "bridge không tự chạy"), nên cần tool tách để gọi cổng chặn; và vì plan là append-only, bước sửa
215
+ sai bắt buộc phải là bước mới → cần `mission_extend`.
216
+ - **Không** có `mission_list`: giữ surface ở 13 tool. Client phải lưu `mission_id` trong bối cảnh;
217
+ người dùng đọc danh sách ở UI.
218
+ - ý "không cho agent tự sửa plan của agent khác" được hiện thực mạnh hơn dự kiến: plan không đổi
219
+ được bởi bất kỳ ai (`extend` chỉ append, `dispatch` không sửa `delegate`, `cancel` chỉ đóng).
220
+
221
+ Bằng chứng: `tests/missions.test.ts` 17 test (rules, schema, allowlist, cổng dependency, failed/lost
222
+ dependency, đúng tham số đã ghi, single-flight, dispatch bị từ chối không bind, cancel hai chiều,
223
+ append-only, retention, restart, record lạ, redaction, MCP surface, UI route). Xem `docs/HANDOFF.md`
224
+ mục 5 cho số liệu suite và bảng mutation.
225
+
226
+ Việc còn mở của Phase 2: chọn bước tự động (router đề xuất bước kế tiếp) vẫn thuộc Phase 4; **chưa có**
227
+ ngân sách token theo mission — usage vẫn chỉ được ghi và tổng hợp theo task/theo ngày, nên một plan
228
+ 12 bước không có trần chi phí; mission không có dependency mang điều kiện (chỉ "bước A xong rồi tới B").
176
229
 
177
230
  ### Phase 3 — Adapter cho OpenCode, Qoder, Zcode
178
231
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agy-mcp-bridge",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
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": [
@@ -1,69 +1,77 @@
1
1
  {
2
- "comment": "sha256 of every SKILL.md this project has shipped, line endings normalized to LF. A file matching one of these is an old packaged copy that `skill install --update` may replace; anything else is treated as the owner's edit. Append the hash of the new SKILL.md here whenever you change it (a test enforces this).",
3
- "versions": [
4
- {
5
- "sha256": "d6776cbecbbbd190df6ba83c16b821140aa7bcc855f8768677a00f210bc9de88",
6
- "note": "first packaged skill (installer bundled)"
7
- },
8
- {
9
- "sha256": "4fbf0722123c099625fd70986b5fa314c4210c56d8bf27d00eff926023b3a60b",
10
- "note": "added task wait / log-free status"
11
- },
12
- {
13
- "sha256": "3f0169d4f33c76e41521ade852d648e370c7333fbaf42a7e1aec6738b64adf6f",
14
- "note": "added \"when not to delegate\" and the delegation switch"
15
- },
16
- {
17
- "sha256": "055f773072ea93535ef09d0d2309074a32165a8f96491b2cc35b751176001415",
18
- "note": "documented the queue and queued status"
19
- },
20
- {
21
- "sha256": "9a3cfaf9adb21a637084f19c53ebe2e5d5b43fa673f7c58c04e9b0f0ef7b3087",
22
- "note": "npm install commands and agy-mcp-bridge CLI"
23
- },
24
- {
25
- "sha256": "afc8d8e76f977e0623b73bbb1e90f3e485cfdaf55036c36db6234328c111660b",
26
- "note": "clarified cancel timing, restart of queued tasks, unlock, environment limitations"
27
- },
28
- {
29
- "sha256": "451fe5791b1753f1550275a709c561442e62d1e8b86d3fe8c4525998d8d85659",
30
- "note": "model choice is the owner's setting; no step progress in the tools"
31
- },
32
- {
33
- "sha256": "65c0a72bb016ee8d891bfea17ea5d972dc79dc7c12c57edb17c1e6e8bbd37078",
34
- "note": "Antigravity permission level is the owner's setting (agy_permissions)"
35
- },
36
- {
37
- "sha256": "066f499ccd7341c7f77c2a409b3632e4cefe16e45ed2c77e75c8154028e951f9",
38
- "note": "long waits (limits.max_wait_seconds), fewer polling calls"
39
- },
40
- {
41
- "sha256": "bf96a492847e59fcdbd4eb7685a316a606699af2d217ea35a8e27dad85ccb2aa",
42
- "note": "timeout guidance: split tasks, narrower follow-up after a timeout, limits.max_timeout_seconds"
43
- },
44
- {
45
- "sha256": "83a5d812553cc79e8eec690cef825ced49b82b13e0991d4c632de5b6675b19f1",
46
- "note": "opt-in auto-split mode (easy parts to Antigravity, hard parts by the Tech Lead)"
47
- },
48
- {
49
- "sha256": "978deb00a9eb68780297eaf538da9272ac11e94c2942141b1d5e999fee218354",
50
- "note": "auto-split mode follows the token-saving workflow (delegate whole, check just enough, short reports)"
51
- },
52
- {
53
- "sha256": "117fa45228f421cf266f45c1a112e37ba2c164fb31c8fd64392495efa025828c",
54
- "note": "while a task runs only wait: no reads or messages between waits, next_step note, no custom client"
55
- },
56
- {
57
- "sha256": "d4246abf59e2aea3ad08c7b858cd6404ee79f5656e580142d019f6f6c443f8df",
58
- "note": "activity line, slow is not stuck, soft deadline extension"
59
- },
60
- {
61
- "sha256": "243a279ff026ed30ad91eaeb7376a4556bc9a1c4a9f4c206f64301c63d8853c4",
62
- "note": "steps are for the user's web UI; do not fetch them"
63
- },
64
- {
65
- "sha256": "fbd0fdebe8fcb95416f89dff95f8b4922e86d243d1139e613a88bde8cc54b034",
66
- "note": "multi-agent delegation (agents_list, agent/isolation/owns/autonomy) with no bridge authentication"
67
- }
68
- ]
2
+ "comment": "sha256 of the SKILL.md versions of each skill this project has shipped, line endings normalized to LF. A file matching one of these is an old packaged copy that `skill install --update` may replace; anything else is treated as the owner's edit. Append the hash of the new SKILL.md here whenever you change it (a test enforces this).",
3
+ "skills": {
4
+ "antigravity-bridge": [
5
+ {
6
+ "sha256": "d6776cbecbbbd190df6ba83c16b821140aa7bcc855f8768677a00f210bc9de88",
7
+ "note": "first packaged skill (installer bundled)"
8
+ },
9
+ {
10
+ "sha256": "4fbf0722123c099625fd70986b5fa314c4210c56d8bf27d00eff926023b3a60b",
11
+ "note": "added task wait / log-free status"
12
+ },
13
+ {
14
+ "sha256": "3f0169d4f33c76e41521ade852d648e370c7333fbaf42a7e1aec6738b64adf6f",
15
+ "note": "added \"when not to delegate\" and the delegation switch"
16
+ },
17
+ {
18
+ "sha256": "055f773072ea93535ef09d0d2309074a32165a8f96491b2cc35b751176001415",
19
+ "note": "documented the queue and queued status"
20
+ },
21
+ {
22
+ "sha256": "9a3cfaf9adb21a637084f19c53ebe2e5d5b43fa673f7c58c04e9b0f0ef7b3087",
23
+ "note": "npm install commands and agy-mcp-bridge CLI"
24
+ },
25
+ {
26
+ "sha256": "afc8d8e76f977e0623b73bbb1e90f3e485cfdaf55036c36db6234328c111660b",
27
+ "note": "clarified cancel timing, restart of queued tasks, unlock, environment limitations"
28
+ },
29
+ {
30
+ "sha256": "451fe5791b1753f1550275a709c561442e62d1e8b86d3fe8c4525998d8d85659",
31
+ "note": "model choice is the owner's setting; no step progress in the tools"
32
+ },
33
+ {
34
+ "sha256": "65c0a72bb016ee8d891bfea17ea5d972dc79dc7c12c57edb17c1e6e8bbd37078",
35
+ "note": "Antigravity permission level is the owner's setting (agy_permissions)"
36
+ },
37
+ {
38
+ "sha256": "066f499ccd7341c7f77c2a409b3632e4cefe16e45ed2c77e75c8154028e951f9",
39
+ "note": "long waits (limits.max_wait_seconds), fewer polling calls"
40
+ },
41
+ {
42
+ "sha256": "bf96a492847e59fcdbd4eb7685a316a606699af2d217ea35a8e27dad85ccb2aa",
43
+ "note": "timeout guidance: split tasks, narrower follow-up after a timeout, limits.max_timeout_seconds"
44
+ },
45
+ {
46
+ "sha256": "83a5d812553cc79e8eec690cef825ced49b82b13e0991d4c632de5b6675b19f1",
47
+ "note": "opt-in auto-split mode (easy parts to Antigravity, hard parts by the Tech Lead)"
48
+ },
49
+ {
50
+ "sha256": "978deb00a9eb68780297eaf538da9272ac11e94c2942141b1d5e999fee218354",
51
+ "note": "auto-split mode follows the token-saving workflow (delegate whole, check just enough, short reports)"
52
+ },
53
+ {
54
+ "sha256": "117fa45228f421cf266f45c1a112e37ba2c164fb31c8fd64392495efa025828c",
55
+ "note": "while a task runs only wait: no reads or messages between waits, next_step note, no custom client"
56
+ },
57
+ {
58
+ "sha256": "d4246abf59e2aea3ad08c7b858cd6404ee79f5656e580142d019f6f6c443f8df",
59
+ "note": "activity line, slow is not stuck, soft deadline extension"
60
+ },
61
+ {
62
+ "sha256": "243a279ff026ed30ad91eaeb7376a4556bc9a1c4a9f4c206f64301c63d8853c4",
63
+ "note": "steps are for the user's web UI; do not fetch them"
64
+ },
65
+ {
66
+ "sha256": "fbd0fdebe8fcb95416f89dff95f8b4922e86d243d1139e613a88bde8cc54b034",
67
+ "note": "multi-agent delegation (agents_list, agent/isolation/owns/autonomy) with no bridge authentication"
68
+ }
69
+ ],
70
+ "mission-orchestrator": [
71
+ {
72
+ "sha256": "828f9d44c0edf38a0dbbb39c469f47968b4be37512b224eb12b985b8b15e4624",
73
+ "note": "first packaged mission skill (Phase 2)"
74
+ }
75
+ ]
76
+ }
69
77
  }
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: mission-orchestrator
3
+ description: Drive a dependency-ordered plan of delegated coding steps through the MCP bridge (mission_create / extend / dispatch / status / plan / cancel) - one step at a time, each step's agent and scope chosen up front, results reviewed before the next step unlocks. Use when work needs several delegated steps in an order, or to recover such a plan after a reconnect or restart.
4
+ ---
5
+
6
+ # Mission Orchestrator
7
+
8
+ A mission is a plan the bridge **records and gates but never runs**: it stores the steps and refuses to start one whose dependencies have not completed. You decide the order, dispatch one step at a time, and wait. The bridge has no scheduler, no retry and no background thread; if you stop, the plan simply sits there until you or the user resumes it. Read `antigravity-bridge` first: delegation consent, the kill switch, task observation, permissions and review rules all apply unchanged to mission steps.
9
+
10
+ ## When a mission is the right shape
11
+
12
+ Use one when the work is genuinely a sequence: implement → test what you changed → fix what the tests found, or several independent modules then an integration step. Use plain `antigravity_delegate` for a single bounded task; a plan of one step adds nothing. Never use a mission to make the bridge "run the whole feature by itself" - it will not, and every step still needs your dispatch call and your review.
13
+
14
+ ## Before writing the plan
15
+
16
+ - Call `antigravity_environment` and `agents_list`. Require `bridge_status=ready`, `supported.missions=true`, `delegation.enabled=true` and `accepting_tasks=true`, and take the workspace from `allowed_workspaces` (an empty allowlist permits nothing). Read `limits`: `max_mission_steps`, `max_retained_missions`, `max_timeout_seconds`, `max_parallel_worktrees`.
17
+ - Every step runs in the mission's single `workspace`; a step that names another one is rejected. Plan one mission per repository.
18
+ - Look at the repository yourself first (status, the files in scope, existing untracked work) and record a baseline. The plan is where you write down what you already learned, so a step does not have to rediscover it.
19
+ - The tools do not list missions, so save the `mission_id` from the receipt the moment it returns. If it is lost, ask the user to read it in the web UI (Nhiệm vụ page) rather than recreating a plan that may already be running.
20
+
21
+ ## Writing the plan
22
+
23
+ `mission_create` takes `{title, goal, workspace, steps[]}` and returns only a receipt (`mission_id`, `workspace`, `status`, `progress`, `ready`, `next_step`) - the plan is not echoed back. Each step is `{step_id, title, depends_on[], delegate}` where `delegate` is exactly the object you would pass to `antigravity_delegate` (`task`, `workspace`, `acceptance_criteria`, optional `context`, `timeout_seconds`, `agent`, `isolation`, `owns`, `autonomy`). Strict schema: unknown fields, a duplicate or self-referencing `step_id`, an unknown dependency, a cycle, a foreign workspace, a step timeout above the bridge ceiling, more than `limits.max_mission_steps` steps or a plan over the size limit are all refused at creation.
24
+
25
+ Write steps that can be checked without the next one:
26
+
27
+ ```json
28
+ {
29
+ "title": "Range validation for the parser",
30
+ "goal": "Reject negative counts at the boundary and prove it with tests.",
31
+ "workspace": "C:/absolute/path/to/approved-project",
32
+ "steps": [
33
+ {"step_id": "impl", "title": "Guard the parser", "depends_on": [],
34
+ "delegate": {"task": "Reject negative values in parseCount (src/count.ts) and update its tests. Preserve the public API. Do not run commands; the Tech Lead runs tests.", "workspace": "C:/absolute/path/to/approved-project", "acceptance_criteria": ["Negative values return the existing validation error"], "context": "parseCount is called from 3 sites; all pass non-negative today.", "agent": "codex", "timeout_seconds": 300}},
35
+ {"step_id": "verify", "title": "Run the checks", "depends_on": ["impl"],
36
+ "delegate": {"task": "Add a regression test for the boundary found in step impl.", "workspace": "C:/absolute/path/to/approved-project", "acceptance_criteria": ["The new test fails before the fix and passes after"], "isolation": "worktree", "owns": ["test/count.test.ts"]}}
37
+ ]
38
+ }
39
+ ```
40
+
41
+ - `step_id` is a lowercase slug (`a-z0-9`, dot, dash, underscore, at most 64 characters). It is the only handle you get back, so name steps for what they prove (`impl`, `api-tests`, `fix-timeout-1`), not for their order.
42
+ - Give the dependent step the criteria you will need to check the step before it. `depends_on` should be the shortest honest chain: two ready steps can run in parallel when worktree-isolated, while a long chain serializes everything behind the first slow step.
43
+ - Put facts the agent cannot see into `context` (contracts, your findings, what is already done). Do not paste the repository, and do not write "as before" - a step is a fresh CLI conversation, and each one reads the files itself.
44
+ - Secrets do not belong in a plan: the bridge redacts secret-shaped text in titles, goals and step instructions before writing to disk, so a `[REDACTED]` in a recovered plan means the value is gone, not hidden.
45
+ - Size each step like a single delegation: one goal, a few files, measurable criteria, a `timeout_seconds` that fits. A mission of 12 small steps beats 2 giant ones - the gate gives you a review point between them.
46
+
47
+ ## Driving it
48
+
49
+ The loop is always: dispatch one ready step → `antigravity_task_wait` on the `task_id` → read `mission_status` → review the diff → decide the next dispatch.
50
+
51
+ - Take the step id from `ready` in the receipt or in `mission_status`. A step outside `ready` is refused, and the error says which dependency is in what state; do not retry it hoping for a different answer.
52
+ - `mission_dispatch` starts the task with the arguments **recorded in the plan**, not with arguments you supply now. If the recorded instruction is wrong, append a corrected step with `mission_extend` - a step that already ran keeps its task and is never re-dispatched or edited.
53
+ - The dispatch reply for a `running` or `queued` step carries only `{mission_id, step_id, task_id, status, duration_ms, next_step}`. Save `mission_id`, `step_id` and `task_id` immediately, then wait. While a step runs, waiting is your whole job: no file reads, git, tests or messages between waits (see `antigravity-bridge`).
54
+ - Review the completed step **before** dispatching the one that depends on it. That is the point of the gate: the dependent step is built on top of what the previous step actually did, not on what it claimed.
55
+ - One dispatch per step at a time. A second concurrent call for the same step is refused ("already being dispatched"); two ready steps may both be started, but ordinary tasks run one at a time across the bridge, so the second waits in the queue (`status=queued`, no process, no file changes).
56
+ - `next_step` on every reply states what is actually possible now. Trust it over your memory of the plan.
57
+
58
+ ## Reading state cheaply
59
+
60
+ `mission_status` is the call to make after a wait, a reconnect or a restart: one short entry per step (`state`, `task_id`, `blocked_by`, agent, `isolation`, `scope`) plus `progress` (`total`, `completed`, `active`, `ready`, `pending`, `unmet`), with no task text at all. `mission_plan` is the same view **with** the recorded delegate arguments - call it only when you have lost the plan (a new session, a compaction, a different client) rather than re-reading the repository to guess what you asked for.
61
+
62
+ States come from the task records, live: `ready` (dispatchable now), `pending` (a dependency is not `completed`), then the task's own status. `lost` means the bridge dropped that task record under retention - it is not a failure the agent reported, and it never unlocks the step waiting for it. If you need to know what that step changed, inspect the workspace and then append a new step; do not assume it completed.
63
+
64
+ ## Corrections, failure and closing
65
+
66
+ | Situation | What to do |
67
+ |---|---|
68
+ | Dependency `failed`, `timeout`, `canceled` or `lost` | Nothing unlocks automatically. Read the step's task output, then `mission_extend` a new step (a new `step_id`) that carries the fix, with the failure output and what is already done in its `context`. |
69
+ | A step needs a second attempt | Append a step; the plan is append-only, so an old `step_id` is permanently used. The same `task_id` is never started twice. |
70
+ | Work discovered while the plan runs | `mission_extend` while the mission is `open`. Keep the goal of the mission the same; add steps, do not rewrite history. |
71
+ | The plan no longer matches reality | `mission_cancel`. No further step can be dispatched and no further step can be appended. Start a new mission instead of forcing an old plan. |
72
+ | The user stops the work | `mission_cancel`. Running steps are **not** killed by it: the reply lists them in `running_tasks`, so tell the user they are still running and that stopping one is `antigravity_cancel_task` on that task - or your own decision, with `cancel_running_tasks=true`, which is a separate destructive choice, not a cleanup step. |
73
+
74
+ Partial file edits from a canceled or failed step stay on disk; nothing is rolled back, and a worktree branch is never merged by the bridge. `unmet` in `progress` counts exactly the steps that can no longer become ready on their own - when it is the only thing left, the plan needs a correction step or a cancel, not more waiting.
75
+
76
+ ## Cost discipline
77
+
78
+ A mission multiplies delegations, so budget it like a project, not like a task. Choose the agent per step: a mechanical step (add tests, rename, format) goes to the cheap/fast default; a step whose cause is unknown or whose contract is delicate goes to a stronger agent, or you do it yourself. The bridge owner picks the model, not you. Estimate the mission's cost before the first dispatch from the number of steps and their `timeout_seconds`, tell the user what you expect, and stop to report when the real cost crosses that estimate rather than finishing the plan silently. Do not add "verify" steps that re-do work the previous step already proved; do not re-dispatch a step because a wait timed out client-side (the task is still running).
79
+
80
+ ## Hard rules
81
+
82
+ `isError=true` is an error even when the client renders text. Never invent a mission tool, a field, or an HTTP payload; use the advertised names. Do not edit `.bridge-state` or the mission records on disk to change a plan, unlock a step, or forget a cancellation. Never plan or delegate deployment, a push to a protected branch, data deletion, credential access, dependency or CI changes, or system changes outside the workspace. Never merge a worktree branch yourself without the user's review of the diff. A mission is a plan of code edits under the same consent rules as one delegation: the user's approval to delegate is not approval to run the whole plan unattended.
package/ui/app.css CHANGED
@@ -160,6 +160,17 @@ details.more .body { padding: 10px 12px 12px; }
160
160
  .tabs button[aria-pressed="true"] { background: var(--surface); color: var(--text); box-shadow: 0 1px 2px rgba(0,0,0,.12); }
161
161
  pre.log { margin: 0; padding: 12px; border-radius: 10px; background: #0b0e14; color: #d6dbe8; font: 12.5px/1.55 var(--mono); max-height: 320px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; }
162
162
 
163
+ /* Missions */
164
+ .mission-page { display: grid; gap: 16px; }
165
+ .mission { overflow-x: auto; }
166
+ .mission-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
167
+ .mission-head h2 { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
168
+ .mission-head .meta-line { margin-top: 4px; }
169
+ .pill.ready { background: var(--accent-soft); color: var(--accent); }
170
+ .pill.lost { background: var(--warn-soft); color: var(--warn); }
171
+ .link-btn { margin-left: 8px; border: 0; background: none; padding: 0; color: var(--accent); font: 600 12.5px var(--sans); text-decoration: underline; }
172
+ .step-args { display: grid; gap: 8px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface-2); min-width: 0; }
173
+
163
174
  /* Settings */
164
175
  .settings { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
165
176
  .panel { padding: 18px 20px; display: grid; gap: 12px; min-width: 0; }