@mmerterden/multi-agent-pipeline 20.30.0 → 20.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +45 -0
  3. package/README.tr.md +59 -0
  4. package/docs/facts.json +3 -3
  5. package/install/_unattended-profile.mjs +61 -0
  6. package/install/catalog-history.json +1 -1
  7. package/install/claude.mjs +9 -2
  8. package/install/index.mjs +10 -1
  9. package/install/templates/claude-hooks.json +12 -1
  10. package/manifest.json +123 -82
  11. package/package.json +2 -1
  12. package/pipeline/commands/multi-agent/analysis/SKILL.md +4 -4
  13. package/pipeline/commands/multi-agent/office-on/SKILL.md +8 -7
  14. package/pipeline/commands/multi-agent/office-status/SKILL.md +1 -1
  15. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -1
  16. package/pipeline/commands/multi-agent/review/SKILL.md +4 -3
  17. package/pipeline/commands/multi-agent/scenario-audit/SKILL.md +4 -2
  18. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -0
  19. package/pipeline/commands/multi-agent/test-gen/SKILL.md +29 -3
  20. package/pipeline/contract/CHANGELOG.md +46 -0
  21. package/pipeline/contract/README.md +22 -8
  22. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  23. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  24. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  25. package/pipeline/contract/fixtures/launch-plan.json +1 -1
  26. package/pipeline/contract/fixtures/runs-awaiting-question.json +6 -1
  27. package/pipeline/contract/fixtures/runs-empty.json +1 -1
  28. package/pipeline/contract/fixtures/runs-failed.json +6 -1
  29. package/pipeline/contract/fixtures/runs-old-schema.json +6 -1
  30. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +5 -1
  31. package/pipeline/contract/fixtures/runs-pr-opened.json +6 -1
  32. package/pipeline/contract/fixtures/runs-running.json +6 -1
  33. package/pipeline/contract/manifest.json +153 -19
  34. package/pipeline/contract/types/index.d.ts +304 -6
  35. package/pipeline/lib/bg-launch.mjs +106 -0
  36. package/pipeline/lib/code-digest.mjs +49 -0
  37. package/pipeline/lib/credential-resolve.mjs +42 -2
  38. package/pipeline/multi-agent-refs/features/office-threat-model.md +24 -6
  39. package/pipeline/multi-agent-refs/features/unattended-security.md +1 -1
  40. package/pipeline/multi-agent-refs/help/en.md +2 -2
  41. package/pipeline/multi-agent-refs/help/tr.md +2 -2
  42. package/pipeline/multi-agent-refs/phases/modes.md +7 -7
  43. package/pipeline/multi-agent-refs/phases/phase-5-report.md +16 -10
  44. package/pipeline/multi-agent-refs/review-standalone-tracker.md +37 -0
  45. package/pipeline/multi-agent-refs/tracker-contract.md +8 -3
  46. package/pipeline/office/NOTICE.md +4 -1
  47. package/pipeline/office/assets/ARCHIVE_BOXES.png +0 -0
  48. package/pipeline/office/assets/COFFEE_MACHINE.png +0 -0
  49. package/pipeline/office/assets/COUNTER_SINK.png +0 -0
  50. package/pipeline/office/assets/DEVICE_STAND.png +0 -0
  51. package/pipeline/office/assets/FILING_CABINET.png +0 -0
  52. package/pipeline/office/assets/FRIDGE.png +0 -0
  53. package/pipeline/office/assets/MAILBOX.png +0 -0
  54. package/pipeline/office/assets/MOP_BUCKET.png +0 -0
  55. package/pipeline/office/assets/PRINTER.png +0 -0
  56. package/pipeline/office/assets/RECEPTION_DESK.png +0 -0
  57. package/pipeline/office/assets/SERVER_RACK.png +0 -0
  58. package/pipeline/office/assets/SERVER_RACK_2.png +0 -0
  59. package/pipeline/office/assets/TOILET_STALL.png +0 -0
  60. package/pipeline/office/assets/VENDING.png +0 -0
  61. package/pipeline/office/assets/WASHBASIN.png +0 -0
  62. package/pipeline/office/assets/WATER_COOLER.png +0 -0
  63. package/pipeline/office/index.html +3996 -884
  64. package/pipeline/office/toolkit.json +3 -0
  65. package/pipeline/schemas/activity.schema.json +188 -4
  66. package/pipeline/schemas/agent-state.schema.json +493 -115
  67. package/pipeline/schemas/contract-error.schema.json +12 -1
  68. package/pipeline/schemas/job-change.schema.json +72 -0
  69. package/pipeline/schemas/job-request.schema.json +38 -0
  70. package/pipeline/schemas/launch.json +4 -4
  71. package/pipeline/schemas/office-credentials.schema.json +71 -0
  72. package/pipeline/schemas/office-jobs.json +253 -0
  73. package/pipeline/schemas/office-jobs.schema.json +62 -0
  74. package/pipeline/schemas/office-memory.schema.json +105 -0
  75. package/pipeline/schemas/office-settings-request.schema.json +30 -0
  76. package/pipeline/schemas/office-settings.schema.json +67 -0
  77. package/pipeline/schemas/phases.json +10 -2
  78. package/pipeline/schemas/phases.schema.json +86 -19
  79. package/pipeline/schemas/prs.schema.json +5 -0
  80. package/pipeline/schemas/prune-logs-request.schema.json +26 -0
  81. package/pipeline/schemas/prune-logs.schema.json +52 -0
  82. package/pipeline/schemas/repo-change.schema.json +54 -0
  83. package/pipeline/schemas/repo-request.schema.json +29 -0
  84. package/pipeline/schemas/repos.schema.json +49 -1
  85. package/pipeline/schemas/runs-index.schema.json +257 -46
  86. package/pipeline/schemas/system-status.schema.json +111 -0
  87. package/pipeline/schemas/tools.schema.json +5 -0
  88. package/pipeline/schemas/unattended-profile.json +7 -2
  89. package/pipeline/scripts/_active-run.mjs +2 -1
  90. package/pipeline/scripts/_spawn-async.mjs +9 -5
  91. package/pipeline/scripts/activity-snapshot.mjs +219 -23
  92. package/pipeline/scripts/autopilot-runner.mjs +7 -5
  93. package/pipeline/scripts/contract-server.mjs +551 -25
  94. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -1
  95. package/pipeline/scripts/job-launch.mjs +477 -0
  96. package/pipeline/scripts/launch-request.mjs +94 -9
  97. package/pipeline/scripts/office-build.mjs +148 -11
  98. package/pipeline/scripts/office-control.mjs +34 -7
  99. package/pipeline/scripts/office-credentials.mjs +106 -0
  100. package/pipeline/scripts/office-memory.mjs +200 -0
  101. package/pipeline/scripts/office-prune.mjs +125 -0
  102. package/pipeline/scripts/office-settings.mjs +145 -0
  103. package/pipeline/scripts/phase-tracker.sh +12 -5
  104. package/pipeline/scripts/prune-logs.sh +53 -2
  105. package/pipeline/scripts/runs-index.mjs +62 -2
  106. package/pipeline/scripts/system-status.mjs +196 -0
  107. package/pipeline/scripts/task-mirror.mjs +150 -0
  108. package/pipeline/scripts/task-mirror.sh +20 -0
  109. package/pipeline/scripts/toolkit-tools.mjs +33 -7
  110. package/pipeline/scripts/transcript-index.mjs +4 -1
  111. package/pipeline/scripts/work-items.mjs +191 -22
  112. package/pipeline/skills/.skill-manifest.json +6 -6
  113. package/pipeline/skills/.skills-index.json +1 -1
  114. package/pipeline/skills/shared/README.md +1 -1
  115. package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +3 -4
  116. package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +4 -1
  117. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +19 -0
  118. package/pipeline/skills/shared/core/multi-agent-scenario-audit/SKILL.md +4 -2
  119. package/pipeline/skills/shared/core/multi-agent-test-gen/SKILL.md +26 -3
  120. package/pipeline/skills/skills-index.md +1 -1
@@ -55,8 +55,10 @@ import { spawnAsync } from "./_spawn-async.mjs";
55
55
  import { readRegularJson } from "../lib/regular-file.mjs";
56
56
  import { runMain } from "../lib/fatal.mjs";
57
57
  import { invokedDirectly } from "../lib/invoked-directly.mjs";
58
+ import { resolveTokenAsync } from "../lib/credential-resolve.mjs";
59
+ import { homedir } from "node:os";
58
60
 
59
- export const CONTRACT_VERSION = "1.0.0";
61
+ export const CONTRACT_VERSION = "1.1.0";
60
62
  const HERE = dirname(fileURLToPath(import.meta.url));
61
63
  /** A Jira key, `owner/repo#N` or `#N`: the structured inputs launch accepts too. */
62
64
  export const ISSUE_KEY =
@@ -114,7 +116,130 @@ export async function issueMaturity(key, env = process.env) {
114
116
  };
115
117
  }
116
118
 
117
- async function ghSearch(env, role) {
119
+ /**
120
+ * The GitHub accounts `gh` is logged in to, as host and login: a personal and
121
+ * a corporate account are asked one by one, each with its own token, so a
122
+ * review request on either shows. Empty when `gh auth status` says nothing
123
+ * readable; the caller then asks with gh's active account alone.
124
+ */
125
+ async function ghAccounts(env) {
126
+ const gh = env.MA_GH_BIN || "gh";
127
+ const r = await spawnAsync(gh, ["auth", "status"], {
128
+ env,
129
+ timeoutMs: TIMEOUT_MS,
130
+ withStderr: true,
131
+ });
132
+ const text = r.stdout || "";
133
+ const out = [];
134
+ for (const m of text.matchAll(/Logged in to (\S+) account ([A-Za-z0-9_.-]+)/g))
135
+ if (!out.some((a) => a.host === m[1] && a.login === m[2]))
136
+ out.push({ host: m[1], login: m[2] });
137
+ return out;
138
+ }
139
+
140
+ /**
141
+ * gh's token for one account, carried in the environment only (GH_TOKEN, and
142
+ * GH_ENTERPRISE_TOKEN for an Enterprise Server host), never on a command line.
143
+ */
144
+ async function accountEnv(env, account) {
145
+ const gh = env.MA_GH_BIN || "gh";
146
+ const r = await spawnAsync(
147
+ gh,
148
+ ["auth", "token", "--hostname", account.host, "--user", account.login],
149
+ { env, timeoutMs: TIMEOUT_MS },
150
+ );
151
+ const token = (r.stdout || "").trim();
152
+ if (r.status !== 0 || !token) return null;
153
+ // gh reads GH_TOKEN for github.com and GH_ENTERPRISE_TOKEN for a GitHub
154
+ // Enterprise Server host, so an enterprise account gets both.
155
+ const enterprise = !/^(github\.com|[a-z0-9-]+\.ghe\.com)$/i.test(account.host);
156
+ return {
157
+ ...env,
158
+ GH_TOKEN: token,
159
+ GH_HOST: account.host,
160
+ ...(enterprise ? { GH_ENTERPRISE_TOKEN: token } : {}),
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Open pull requests on the configured Bitbucket Server where this user is a
166
+ * reviewer or the author, from its dashboard API. The token comes from the
167
+ * credential store and travels in the request header only.
168
+ */
169
+ async function bitbucketPrs(env) {
170
+ let host = env.MA_BITBUCKET_HOST || null;
171
+ if (!host) {
172
+ try {
173
+ const prefs = readRegularJson(
174
+ env.MULTI_AGENT_PREFS ||
175
+ join(env.HOME || homedir(), ".claude", "multi-agent-preferences.json"),
176
+ null,
177
+ );
178
+ host = prefs?.global?.hosts?.bitbucket ?? null;
179
+ } catch {
180
+ host = null;
181
+ }
182
+ }
183
+ if (typeof host !== "string" || !host) return { ok: false, items: [], reason: "not configured" };
184
+ // The token travels in a header, so only over TLS: a host written with
185
+ // http:// is refused unless it is this machine's own loopback.
186
+ const loopback = /^http:\/\/(127\.0\.0\.1|localhost)(:\d+)?(\/|$)/i.test(host);
187
+ if (/^http:\/\//i.test(host) && !loopback)
188
+ return { ok: false, items: [], reason: "https required" };
189
+ const base = /^https?:\/\//i.test(host) ? host.replace(/\/+$/, "") : `https://${host}`;
190
+ const token =
191
+ env.MA_BITBUCKET_TOKEN ||
192
+ (await resolveTokenAsync("bitbucket_token", "BITBUCKET_TOKEN", { env }));
193
+ if (!token) return { ok: false, items: [], reason: "no token in the credential store" };
194
+ // The same rule as credential-inventory.sh: Basic with the stored user (an
195
+ // app password), else the token as a bearer.
196
+ const user =
197
+ env.MA_BITBUCKET_USER || (await resolveTokenAsync("bitbucket_user", "BITBUCKET_USER", { env }));
198
+ const authorization = user
199
+ ? "Basic " + Buffer.from(`${user}:${token}`).toString("base64")
200
+ : `Bearer ${token}`;
201
+ const items = [];
202
+ try {
203
+ for (const role of ["REVIEWER", "AUTHOR"]) {
204
+ const res = await fetch(
205
+ `${base}/rest/api/1.0/dashboard/pull-requests?state=OPEN&role=${role}&limit=50`,
206
+ {
207
+ headers: { Authorization: authorization, Accept: "application/json" },
208
+ redirect: "error",
209
+ signal: AbortSignal.timeout(TIMEOUT_MS),
210
+ },
211
+ );
212
+ if (res.status === 401 || res.status === 403)
213
+ return { ok: false, items: [], reason: "the token was refused" };
214
+ if (!res.ok) return { ok: false, items: [], reason: `Bitbucket answered ${res.status}` };
215
+ const doc = await res.json();
216
+ for (const p of Array.isArray(doc?.values) ? doc.values : []) {
217
+ const repo = p?.toRef?.repository;
218
+ items.push({
219
+ source: "bitbucket",
220
+ account: null,
221
+ repo: String(repo ? `${repo.project?.key ?? ""}/${repo.slug ?? ""}` : ""),
222
+ number: Number(p?.id) || 0,
223
+ title: scrub(String(p?.title ?? "")),
224
+ url: typeof p?.links?.self?.[0]?.href === "string" ? p.links.self[0].href : null,
225
+ author: p?.author?.user?.name ? String(p.author.user.name) : null,
226
+ updatedAt: Number.isFinite(p?.updatedDate) ? new Date(p.updatedDate).toISOString() : null,
227
+ role: role === "REVIEWER" ? "reviewer" : "author",
228
+ });
229
+ }
230
+ }
231
+ } catch (e) {
232
+ const why = String(e?.cause?.message ?? e?.message ?? "");
233
+ return {
234
+ ok: false,
235
+ items: [],
236
+ reason: /redirect/i.test(why) ? "redirect refused" : "unreachable (VPN?)",
237
+ };
238
+ }
239
+ return { ok: true, items };
240
+ }
241
+
242
+ async function ghSearch(env, role, account = null) {
118
243
  const gh = env.MA_GH_BIN || "gh";
119
244
  const flag = role === "reviewer" ? "--review-requested=@me" : "--author=@me";
120
245
  const r = await spawnAsync(
@@ -139,6 +264,7 @@ async function ghSearch(env, role) {
139
264
  ok: true,
140
265
  items: (Array.isArray(rows) ? rows : []).map((p) => ({
141
266
  source: "github",
267
+ account,
142
268
  repo: String(p?.repository?.nameWithOwner ?? p?.repository?.name ?? ""),
143
269
  number: Number(p?.number) || 0,
144
270
  title: scrub(String(p?.title ?? "")),
@@ -153,31 +279,74 @@ async function ghSearch(env, role) {
153
279
  }
154
280
  }
155
281
 
156
- /** Open pull requests on every host the pipeline can list, one list. */
282
+ /**
283
+ * Open pull requests on every host the pipeline can list, one list: every
284
+ * GitHub account gh is logged in to (personal and corporate alike) and the
285
+ * configured Bitbucket Server.
286
+ */
157
287
  export async function pullRequests({ env = process.env, redact = false } = {}) {
158
- const [reviewer, author] = await Promise.all([
159
- ghSearch(env, "reviewer"),
160
- ghSearch(env, "author"),
161
- ]);
288
+ const accounts = await ghAccounts(env);
289
+ // gh before `auth token --user` hands out no token per account; then the
290
+ // active account answers alone, as a plain `gh search` does.
291
+ const envs = await Promise.all(accounts.map((a) => accountEnv(env, a)));
292
+ const perAccount = envs.some(Boolean);
293
+ const github = [];
294
+ if (accounts.length && perAccount) {
295
+ for (const [i, a] of accounts.entries()) {
296
+ const aEnv = envs[i];
297
+ if (!aEnv) {
298
+ github.push({
299
+ source: `github:${a.login}`,
300
+ ok: false,
301
+ items: [],
302
+ reason: "gh has no token for it",
303
+ });
304
+ continue;
305
+ }
306
+ const [rv, au] = await Promise.all([
307
+ ghSearch(aEnv, "reviewer", a.login),
308
+ ghSearch(aEnv, "author", a.login),
309
+ ]);
310
+ github.push({
311
+ source: `github:${a.login}`,
312
+ ok: rv.ok || au.ok,
313
+ items: [...rv.items, ...au.items],
314
+ reason: rv.ok || au.ok ? undefined : "gh could not list pull requests",
315
+ });
316
+ }
317
+ } else {
318
+ const [rv, au] = await Promise.all([ghSearch(env, "reviewer"), ghSearch(env, "author")]);
319
+ github.push({
320
+ source: "github",
321
+ ok: rv.ok || au.ok,
322
+ items: [...rv.items, ...au.items],
323
+ reason: rv.ok || au.ok ? undefined : "gh could not list pull requests",
324
+ });
325
+ }
326
+ const bitbucket = { source: "bitbucket", ...(await bitbucketPrs(env)) };
162
327
  const seen = new Set();
163
328
  const items = [];
164
- for (const p of [...reviewer.items, ...author.items]) {
165
- const k = `${p.repo}#${p.number}`;
166
- if (seen.has(k)) continue;
167
- seen.add(k);
168
- items.push(redact ? { ...p, url: null } : p);
329
+ const sources = [];
330
+ // A redacted answer (the phone's) names no login: accounts become numbers.
331
+ let accountNo = 0;
332
+ for (const src of [...github, bitbucket]) {
333
+ const label = redact && src.source.startsWith("github:") ? `github:${++accountNo}` : src.source;
334
+ let count = 0;
335
+ for (const p of src.items) {
336
+ const k = `${p.source}:${p.repo}#${p.number}`;
337
+ if (seen.has(k)) continue;
338
+ seen.add(k);
339
+ count++;
340
+ items.push(redact ? { ...p, url: null, account: null, author: null } : p);
341
+ }
342
+ sources.push(
343
+ src.ok
344
+ ? { source: label, ok: true, count }
345
+ : { source: label, ok: false, count: 0, reason: src.reason || "unreadable" },
346
+ );
169
347
  }
170
348
  items.sort((a, b) => ((a.updatedAt ?? "") < (b.updatedAt ?? "") ? 1 : -1));
171
- return {
172
- contractVersion: CONTRACT_VERSION,
173
- items,
174
- sources: [
175
- reviewer.ok || author.ok
176
- ? { source: "github", ok: true, count: items.length }
177
- : { source: "github", ok: false, count: 0, reason: "gh could not list pull requests" },
178
- { source: "bitbucket", ok: false, count: 0, reason: "not-implemented" },
179
- ],
180
- };
349
+ return { contractVersion: CONTRACT_VERSION, items, sources };
181
350
  }
182
351
 
183
352
  function stateOf(taskId) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-24T21:22:00Z",
3
+ "generatedAt": "2026-10-25T17:09:06Z",
4
4
  "skillCount": 237,
5
5
  "entries": [
6
6
  {
@@ -133,7 +133,7 @@
133
133
  },
134
134
  {
135
135
  "path": "shared/core/multi-agent-office-on/SKILL.md",
136
- "sha256": "f1841f3092275e54b669a2701796c187ecaa16d71b0cd4311915218319d6950c"
136
+ "sha256": "b46a2f79beeee65ae7ad6cbfc58b4631c5146a7761e91a99b8a056d6f49c690d"
137
137
  },
138
138
  {
139
139
  "path": "shared/core/multi-agent-office-status/SKILL.md",
@@ -145,7 +145,7 @@
145
145
  },
146
146
  {
147
147
  "path": "shared/core/multi-agent-prune-logs/SKILL.md",
148
- "sha256": "9e0745c2aac474b401126218a7e0d81a72942cd331ac0e591da72407d523fda9"
148
+ "sha256": "649622e6f18887f986e0acd8b743df0a345a3adf89ccab58d6c4b191b770c1ac"
149
149
  },
150
150
  {
151
151
  "path": "shared/core/multi-agent-prune-prompts/SKILL.md",
@@ -181,7 +181,7 @@
181
181
  },
182
182
  {
183
183
  "path": "shared/core/multi-agent-review/SKILL.md",
184
- "sha256": "e3a673e13b9b7a227334afe79e20e2be837c0900e16e88003b4692a1d74dea41"
184
+ "sha256": "4a110e0cc6c36185d76e1a99d7a397a937460377c6bc426714a236a5371a26b0"
185
185
  },
186
186
  {
187
187
  "path": "shared/core/multi-agent-route-off/SKILL.md",
@@ -213,7 +213,7 @@
213
213
  },
214
214
  {
215
215
  "path": "shared/core/multi-agent-scenario-audit/SKILL.md",
216
- "sha256": "b31921106fa02f3ac48d3409b866a0d58a84dd98ec8888a4da37964ebbce48ef"
216
+ "sha256": "b8bc2de0e889007ae64d615e291ea6d369c96dbed0b62fed47cf19c372af4f1b"
217
217
  },
218
218
  {
219
219
  "path": "shared/core/multi-agent-search/SKILL.md",
@@ -265,7 +265,7 @@
265
265
  },
266
266
  {
267
267
  "path": "shared/core/multi-agent-test-gen/SKILL.md",
268
- "sha256": "0da450ed0c74ab610d3c8f0cf1b0facd2a4ae179afc832440939c3367de7e762"
268
+ "sha256": "a66d1171455d95df92a367095eeec11f50033d86c53a7d0032597cebd40d6f21"
269
269
  },
270
270
  {
271
271
  "path": "shared/core/multi-agent-test-screenshots/SKILL.md",
@@ -1368,7 +1368,7 @@
1368
1368
  },
1369
1369
  {
1370
1370
  "name": "multi-agent-office-on",
1371
- "description": "Open the office panel: a local browser page with agents at work, their chats, cost ledger and new runs. Use when you want to watch or drive runs from a browser.",
1371
+ "description": "Open the office: a local browser page that shows runs and agents live and drives the ecosystem (new runs, jobs, PRs, keys, memory, system, cleanup). Use when you want to watch or drive runs from a browser.",
1372
1372
  "platform": null,
1373
1373
  "group": "core",
1374
1374
  "plugin": null,
@@ -58,7 +58,7 @@ Source layout is logical grouping only - skill discovery at runtime is unchang
58
58
  | [`multi-agent-manual-test`](./core/multi-agent-manual-test/) | `core` | Switch to the active task's branch and prepare it for manual testing in Xcode. Phase 3 user-test step, standalone (the UI Bug Hunter lives a |
59
59
  | [`multi-agent-model`](./core/multi-agent-model/) | `core` | Turn the top model rung on or off and keep the cost ledger's pricing in step with it. Use when asked to enable or disable Fable, or which mo |
60
60
  | [`multi-agent-office-off`](./core/multi-agent-office-off/) | `core` | Close the office panel and stop its local server. Use when done with the panel. |
61
- | [`multi-agent-office-on`](./core/multi-agent-office-on/) | `core` | Open the office panel: a local browser page with agents at work, their chats, cost ledger and new runs. Use when you want to watch or drive |
61
+ | [`multi-agent-office-on`](./core/multi-agent-office-on/) | `core` | Open the office: a local browser page that shows runs and agents live and drives the ecosystem (new runs, jobs, PRs, keys, memory, system, c |
62
62
  | [`multi-agent-office-status`](./core/multi-agent-office-status/) | `core` | Whether the office panel is running, its URL (never the token) and its last actions. Use when unsure if the panel is open. |
63
63
  | [`multi-agent-postmortem`](./core/multi-agent-postmortem/) | `core` | Post-mortem of a run from its session transcripts: loops, stalls, root-cause hypotheses on file:line, fixes. Read-only, redacted. Use when a |
64
64
  | [`multi-agent-prune-logs`](./core/multi-agent-prune-logs/) | `core` | Delete per-task project logs under ~/.claude/logs/multi-agent (filter by age/project/task). Audit trail + metrics are preserved. Dry-run fir |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: multi-agent-office-on
3
- description: "Open the office panel: a local browser page with agents at work, their chats, cost ledger and new runs. Use when you want to watch or drive runs from a browser."
3
+ description: "Open the office: a local browser page that shows runs and agents live and drives the ecosystem (new runs, jobs, PRs, keys, memory, system, cleanup). Use when you want to watch or drive runs from a browser."
4
4
  user-invocable: true
5
5
  metadata:
6
6
  language: en
@@ -14,9 +14,8 @@ and opens the browser at `/office` with a one-time code in the URL fragment
14
14
 
15
15
  1. `node "$HOME/.copilot/scripts/office-control.mjs" on --json`
16
16
  2. Report in `outputLanguage`: `url` (it carries no code), whether the server
17
- was started or `reused`, that it stops after `idleMinutes` without a request
18
- and after 8 hours in any case, and that `/multi-agent-office-off` stops it
19
- now. If `opened` is false, say to run it again from a desktop session.
17
+ was started or `reused`, and that it runs until `/multi-agent-office-off`
18
+ stops it. If `opened` is false, say to run it again from a desktop session.
20
19
 
21
20
  Never print the token, read the token file or pass `--print-token`. The server
22
21
  listens on 127.0.0.1 only; remote access is not part of this installation.
@@ -17,7 +17,10 @@ whole task dirs, with optional age / project / task filters.
17
17
  Backed by `$HOME/.claude/scripts/prune-logs.sh`. Safe by default: it previews
18
18
  and deletes nothing until you confirm. The audit trail (`audit.jsonl` +
19
19
  rotations) and the metrics corpus (`metrics.jsonl`, `metrics-summary.md`) live
20
- at the log-root top level and are never removed; `.counter` files survive too.
20
+ at the log-root top level and are never removed; `.counter` files survive too. A run that can still be resumed (its `agent-state.json` status is not
21
+ finished, abandoned or cancelled) keeps its log however old it is, and
22
+ so does a folder written in the last 10 minutes; `--task` names one dir and
23
+ bypasses both.
21
24
 
22
25
  Unlike `/multi-agent:purge` (which wipes worktrees, branches, logs, and state
23
26
  together), this only removes log dirs and leaves your worktrees and branches
@@ -20,6 +20,25 @@ Skip Phase 0-3 and review a diff only. Input shapes: a PR (`#N`, `repo#N`, GitHu
20
20
 
21
21
  ## Steps
22
22
 
23
+ 0. **Session id and tracker record.** A pipeline run passes its own `TASK_ID`; a standalone review seeds a `review-` one and registers a tracker record, so the review shows wherever runs are read (the office panel, `multi-agent-status`):
24
+
25
+ ```bash
26
+ : "${TASK_ID:=review-$(date +%s)-$$}"
27
+ case "$TASK_ID" in review-*)
28
+ S="${LOGS_ROOT:-$HOME/.claude/logs/multi-agent}/$TASK_ID"; mkdir -p "$S"
29
+ printf '{"taskId":"%s","mode":"review","status":"in_progress","currentPhase":0}' "$TASK_ID" \
30
+ | node "$HOME/.copilot/scripts/write-state.mjs" "$S/agent-state.json" >/dev/null
31
+ export MULTI_AGENT_TASK_ID="$TASK_ID"
32
+ bash "$HOME/.copilot/scripts/phase-tracker.sh" init "$TASK_ID"
33
+ for p in "0:Input" "1:Diff" "2:Reviewers" "3:Triage" "4:Report"; do
34
+ bash "$HOME/.copilot/scripts/phase-tracker.sh" add "${p%%:*}" "${p#*:}"
35
+ done
36
+ bash "$HOME/.copilot/scripts/phase-tracker.sh" update 0 in_progress ;;
37
+ esac
38
+ ```
39
+
40
+ The steps map to tracker steps as in `$HOME/.claude/multi-agent-refs/review-standalone-tracker.md` (1/1a is 0, 2 is 1, 3-4 are 2, 5 is 3, 6-7 are 4), each call prefixed `MULTI_AGENT_TASK_ID=<the review- id> MULTI_AGENT_STRICT_TASK_ID=1`; there is no task widget, so call `phase-tracker.sh render` after each change. After step 7 close the record with `"status":"complete","currentPhase":4` through the same `write-state.mjs` call.
41
+
23
42
  1. **Resolve the input** (`{kind, provider}`):
24
43
  - PR URL / `#N` / `repo#N` -> `kind=pr` (`github` or `bitbucket-server`).
25
44
  - branch name -> `kind=branch` (local).
@@ -53,7 +53,9 @@ Read the candidate lines it returns. A term nothing matched is listed under
53
53
 
54
54
  ## Step 3 - Verdict
55
55
 
56
- Write `/tmp/ma-verdicts.json`: `[{id, status, citations, searched, note}]`.
56
+ Write the verdicts to a file in a folder of this run's own
57
+ (`VERDICTS="$(mktemp -d)/verdicts.json"`, never a fixed `/tmp` name another
58
+ user or a second audit could share): `[{id, status, citations, searched, note}]`.
57
59
 
58
60
  | Status | When | Needs |
59
61
  |---|---|---|
@@ -64,7 +66,7 @@ Write `/tmp/ma-verdicts.json`: `[{id, status, citations, searched, note}]`.
64
66
 
65
67
  ```bash
66
68
  node "$HOME/.claude/scripts/scenario-audit.mjs" verify --csv "<scenarios.csv>" \
67
- --verdicts /tmp/ma-verdicts.json --repo "<repo>" --lang "<outputLanguage>"
69
+ --verdicts "$VERDICTS" --repo "<repo>" --lang "<outputLanguage>"
68
70
  ```
69
71
 
70
72
  ## Step 4 - Report
@@ -43,9 +43,32 @@ Scripts: `test-gen-gate.mjs` (artefact, handle and write gates),
43
43
  ## Step 1 - Prepare
44
44
 
45
45
  1. Repo: `--repo`, else the current directory; confirm it in one line.
46
- Platform: `--platform`, else detect as `multi-agent-test` does. Run id
47
- `tg-<timestamp>`; `RUN="$HOME/.claude/logs/test-gen/<repo>/<timestamp>"`,
48
- created with `mkdir -p "$RUN"`.
46
+ Platform: `--platform`, else detect as `multi-agent-test` does. One
47
+ timestamp names the run everywhere: `TS="$(date +%Y%m%d%H%M%S)"`, run id
48
+ `tg-$TS`, `RUN="$HOME/.claude/logs/test-gen/<repo>/$TS"`, created with
49
+ `mkdir -p "$RUN"`.
50
+ Register the run so its progress shows wherever runs are read (the office
51
+ panel, `multi-agent-status`), with one step per stage below:
52
+
53
+ ```bash
54
+ TG_ID="tg-$TS"
55
+ S="${LOGS_ROOT:-$HOME/.claude/logs/multi-agent}/$TG_ID"; mkdir -p "$S"
56
+ printf '{"taskId":"%s","mode":"test-gen","status":"in_progress","currentPhase":0}' "$TG_ID" \
57
+ | node "$HOME/.copilot/scripts/write-state.mjs" "$S/agent-state.json" >/dev/null
58
+ export MULTI_AGENT_TASK_ID="$TG_ID"
59
+ bash "$HOME/.copilot/scripts/phase-tracker.sh" init "$TG_ID"
60
+ for p in "0:Prepare" "1:Inventory" "2:Discovery" "3:Handles" "4:Tests" "5:Report"; do
61
+ bash "$HOME/.copilot/scripts/phase-tracker.sh" add "${p%%:*}" "${p#*:}"
62
+ done
63
+ bash "$HOME/.copilot/scripts/phase-tracker.sh" update 0 in_progress
64
+ ```
65
+
66
+ Each step below starts with `update <N> in_progress` and ends with
67
+ `update <N> completed` (Step 5 and Step 6 share step 4), every call prefixed
68
+ `MULTI_AGENT_TASK_ID=<the tg- id, written out> MULTI_AGENT_STRICT_TASK_ID=1`.
69
+ There is no task widget here: call `phase-tracker.sh render` after each
70
+ change, and close the record with `"status":"complete","currentPhase":5`
71
+ through the same `write-state.mjs` call when the report is written.
49
72
  2. Toolkit: `test_results_classify`, `coverage_gap` and `ui_locator_codegen`
50
73
  need `@mmerterden/multi-agent-toolkit-mcp` >= v3.25.0. Check the tool list
51
74
  once. Each missing tool has a fallback in `stages.md` (classify from the
@@ -131,7 +131,7 @@
131
131
  | core | `multi-agent-manual-test` | - | Switch to the active task's branch and prepare it for manual testing in Xcode. Phase 3 user-test step, standalone (the UI Bug Hunter lives a |
132
132
  | core | `multi-agent-model` | - | Turn the top model rung on or off and keep the cost ledger's pricing in step with it. Use when asked to enable or disable Fable, or which mo |
133
133
  | core | `multi-agent-office-off` | - | Close the office panel and stop its local server. Use when done with the panel. |
134
- | core | `multi-agent-office-on` | - | Open the office panel: a local browser page with agents at work, their chats, cost ledger and new runs. Use when you want to watch or drive |
134
+ | core | `multi-agent-office-on` | - | Open the office: a local browser page that shows runs and agents live and drives the ecosystem (new runs, jobs, PRs, keys, memory, system, c |
135
135
  | core | `multi-agent-office-status` | - | Whether the office panel is running, its URL (never the token) and its last actions. Use when unsure if the panel is open. |
136
136
  | core | `multi-agent-postmortem` | - | Post-mortem of a run from its session transcripts: loops, stalls, root-cause hypotheses on file:line, fixes. Read-only, redacted. Use when a |
137
137
  | core | `multi-agent-prune-logs` | - | Delete per-task project logs under ~/.claude/logs/multi-agent (filter by age/project/task). Audit trail + metrics are preserved. Dry-run fir |