@mmerterden/multi-agent-pipeline 20.29.0 → 20.30.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 (146) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +5 -5
  3. package/README.tr.md +5 -5
  4. package/docs/architecture.md +3 -3
  5. package/docs/ecosystem.md +5 -5
  6. package/docs/facts.json +7 -7
  7. package/install/catalog-history.json +1 -1
  8. package/install/claude.mjs +10 -1
  9. package/install/codex.mjs +2 -0
  10. package/install/copilot.mjs +13 -0
  11. package/manifest.json +149 -48
  12. package/package.json +2 -1
  13. package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +13 -15
  14. package/pipeline/commands/multi-agent/office-off/SKILL.md +27 -0
  15. package/pipeline/commands/multi-agent/office-on/SKILL.md +50 -0
  16. package/pipeline/commands/multi-agent/office-status/SKILL.md +29 -0
  17. package/pipeline/commands/multi-agent/serve/SKILL.md +4 -2
  18. package/pipeline/commands/multi-agent/stack/SKILL.md +15 -8
  19. package/pipeline/commands/multi-agent/steer/SKILL.md +10 -9
  20. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  21. package/pipeline/contract/CHANGELOG.md +62 -0
  22. package/pipeline/contract/README.md +52 -6
  23. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  24. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  25. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  26. package/pipeline/contract/manifest.json +367 -22
  27. package/pipeline/contract/types/index.d.ts +530 -3
  28. package/pipeline/multi-agent-refs/cross-cli-contract.md +5 -4
  29. package/pipeline/multi-agent-refs/features/office-threat-model.md +91 -0
  30. package/pipeline/multi-agent-refs/help/en.md +3 -0
  31. package/pipeline/multi-agent-refs/help/tr.md +3 -0
  32. package/pipeline/office/NOTICE.md +42 -0
  33. package/pipeline/office/assets/BIN.png +0 -0
  34. package/pipeline/office/assets/BOOKSHELF.png +0 -0
  35. package/pipeline/office/assets/CACTUS.png +0 -0
  36. package/pipeline/office/assets/CLOCK.png +0 -0
  37. package/pipeline/office/assets/COFFEE.png +0 -0
  38. package/pipeline/office/assets/COFFEE_TABLE.png +0 -0
  39. package/pipeline/office/assets/CUSHIONED_BENCH.png +0 -0
  40. package/pipeline/office/assets/CUSHIONED_CHAIR_BACK.png +0 -0
  41. package/pipeline/office/assets/CUSHIONED_CHAIR_FRONT.png +0 -0
  42. package/pipeline/office/assets/CUSHIONED_CHAIR_SIDE.png +0 -0
  43. package/pipeline/office/assets/DESK_FRONT.png +0 -0
  44. package/pipeline/office/assets/DESK_SIDE.png +0 -0
  45. package/pipeline/office/assets/DOUBLE_BOOKSHELF.png +0 -0
  46. package/pipeline/office/assets/HANGING_PLANT.png +0 -0
  47. package/pipeline/office/assets/LARGE_PAINTING.png +0 -0
  48. package/pipeline/office/assets/LARGE_PLANT.png +0 -0
  49. package/pipeline/office/assets/PC_BACK.png +0 -0
  50. package/pipeline/office/assets/PC_FRONT_OFF.png +0 -0
  51. package/pipeline/office/assets/PC_FRONT_ON_1.png +0 -0
  52. package/pipeline/office/assets/PC_FRONT_ON_2.png +0 -0
  53. package/pipeline/office/assets/PC_FRONT_ON_3.png +0 -0
  54. package/pipeline/office/assets/PC_SIDE.png +0 -0
  55. package/pipeline/office/assets/PLANT.png +0 -0
  56. package/pipeline/office/assets/PLANT_2.png +0 -0
  57. package/pipeline/office/assets/POT.png +0 -0
  58. package/pipeline/office/assets/SMALL_PAINTING.png +0 -0
  59. package/pipeline/office/assets/SMALL_PAINTING_2.png +0 -0
  60. package/pipeline/office/assets/SMALL_TABLE_FRONT.png +0 -0
  61. package/pipeline/office/assets/SMALL_TABLE_SIDE.png +0 -0
  62. package/pipeline/office/assets/SOFA_BACK.png +0 -0
  63. package/pipeline/office/assets/SOFA_FRONT.png +0 -0
  64. package/pipeline/office/assets/SOFA_SIDE.png +0 -0
  65. package/pipeline/office/assets/TABLE_FRONT.png +0 -0
  66. package/pipeline/office/assets/WHITEBOARD.png +0 -0
  67. package/pipeline/office/assets/WOODEN_BENCH.png +0 -0
  68. package/pipeline/office/assets/WOODEN_CHAIR_BACK.png +0 -0
  69. package/pipeline/office/assets/WOODEN_CHAIR_FRONT.png +0 -0
  70. package/pipeline/office/assets/WOODEN_CHAIR_SIDE.png +0 -0
  71. package/pipeline/office/assets/char_0.png +0 -0
  72. package/pipeline/office/assets/char_1.png +0 -0
  73. package/pipeline/office/assets/char_2.png +0 -0
  74. package/pipeline/office/assets/char_3.png +0 -0
  75. package/pipeline/office/assets/char_4.png +0 -0
  76. package/pipeline/office/assets/char_5.png +0 -0
  77. package/pipeline/office/assets/floor_0.png +0 -0
  78. package/pipeline/office/assets/floor_1.png +0 -0
  79. package/pipeline/office/assets/floor_2.png +0 -0
  80. package/pipeline/office/assets/floor_3.png +0 -0
  81. package/pipeline/office/assets/floor_4.png +0 -0
  82. package/pipeline/office/assets/floor_5.png +0 -0
  83. package/pipeline/office/assets/floor_6.png +0 -0
  84. package/pipeline/office/assets/floor_7.png +0 -0
  85. package/pipeline/office/assets/floor_8.png +0 -0
  86. package/pipeline/office/assets/wall_0.png +0 -0
  87. package/pipeline/office/index.html +5319 -0
  88. package/pipeline/schemas/activity-chat.schema.json +78 -0
  89. package/pipeline/schemas/activity-roles.json +48 -0
  90. package/pipeline/schemas/activity-roles.schema.json +49 -0
  91. package/pipeline/schemas/activity.schema.json +429 -0
  92. package/pipeline/schemas/autopilot-on-request.schema.json +33 -0
  93. package/pipeline/schemas/autopilot-on.schema.json +111 -0
  94. package/pipeline/schemas/contract-error.schema.json +33 -3
  95. package/pipeline/schemas/issue-maturity.schema.json +68 -0
  96. package/pipeline/schemas/ledger.schema.json +202 -0
  97. package/pipeline/schemas/permissions.schema.json +47 -0
  98. package/pipeline/schemas/prefs.schema.json +19 -0
  99. package/pipeline/schemas/prs.schema.json +73 -0
  100. package/pipeline/schemas/review-verdict-request.schema.json +20 -0
  101. package/pipeline/schemas/review-verdict.schema.json +62 -0
  102. package/pipeline/schemas/routine-delete.schema.json +59 -0
  103. package/pipeline/schemas/routines.schema.json +38 -0
  104. package/pipeline/schemas/run-evidence.schema.json +29 -0
  105. package/pipeline/schemas/run-results.schema.json +218 -0
  106. package/pipeline/schemas/service-read.schema.json +58 -0
  107. package/pipeline/schemas/stack-change.schema.json +82 -0
  108. package/pipeline/schemas/stack-request.schema.json +33 -0
  109. package/pipeline/schemas/stack.schema.json +59 -0
  110. package/pipeline/schemas/steer-request.schema.json +25 -0
  111. package/pipeline/schemas/steer.schema.json +59 -0
  112. package/pipeline/schemas/tool-call-request.schema.json +19 -0
  113. package/pipeline/schemas/tool-call.schema.json +47 -0
  114. package/pipeline/schemas/tools.schema.json +60 -0
  115. package/pipeline/scripts/_cost.mjs +66 -1
  116. package/pipeline/scripts/_office-smoke-lib.sh +115 -0
  117. package/pipeline/scripts/_spawn-async.mjs +73 -0
  118. package/pipeline/scripts/_transcript-tail.mjs +274 -0
  119. package/pipeline/scripts/activity-snapshot.mjs +1027 -0
  120. package/pipeline/scripts/autopilot-control.mjs +214 -9
  121. package/pipeline/scripts/contract-server.mjs +1178 -48
  122. package/pipeline/scripts/cost-analyze.mjs +2 -53
  123. package/pipeline/scripts/cost-table.json +1 -0
  124. package/pipeline/scripts/office-build.mjs +89 -0
  125. package/pipeline/scripts/office-control.mjs +383 -0
  126. package/pipeline/scripts/office-ledger.mjs +409 -0
  127. package/pipeline/scripts/routine-registry.mjs +42 -6
  128. package/pipeline/scripts/run-results.mjs +373 -0
  129. package/pipeline/scripts/service-read.mjs +244 -0
  130. package/pipeline/scripts/stack-toggle.mjs +285 -0
  131. package/pipeline/scripts/steer-run.mjs +97 -0
  132. package/pipeline/scripts/toolkit-tools.mjs +599 -0
  133. package/pipeline/scripts/uninstall.mjs +3 -0
  134. package/pipeline/scripts/work-items.mjs +304 -0
  135. package/pipeline/skills/.skill-manifest.json +19 -7
  136. package/pipeline/skills/.skills-index.json +34 -1
  137. package/pipeline/skills/shared/README.md +6 -3
  138. package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +13 -15
  139. package/pipeline/skills/shared/core/multi-agent-office-off/SKILL.md +16 -0
  140. package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +22 -0
  141. package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +17 -0
  142. package/pipeline/skills/shared/core/multi-agent-serve/SKILL.md +4 -2
  143. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +14 -6
  144. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +10 -9
  145. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
  146. package/pipeline/skills/skills-index.md +4 -1
@@ -16,8 +16,17 @@
16
16
  * under `autopilot/abandoned/<task-id>` and kept, a clean one is
17
17
  * removed. Without --now nothing in flight is touched.
18
18
  *
19
- * Turning the mode on stays /multi-agent:autopilot-on: it picks repositories
20
- * with the user.
19
+ * on --repo <owner/name>... [--configure-only]
20
+ * the writing half of /multi-agent:autopilot-on, for a selection
21
+ * already made: config.json keeps exactly the selected repos
22
+ * (each must already be configured; adding a new one, with its
23
+ * sources and label, stays the terminal picker), every
24
+ * credential the result needs is probed (autopilot-arming.mjs
25
+ * probeCredentials), and unless --configure-only or a probe
26
+ * fails, the schedule plist is rendered and loaded and the awake
27
+ * agent installed. An empty selection is refused.
28
+ * on-preview --repo <owner/name>...
29
+ * what `on` would keep, drop and probe, writing nothing.
21
30
  *
22
31
  * Injectable for tests: MA_AUTOPILOT_ROOT, MA_AUTOPILOT_LABEL,
23
32
  * MA_AUTOPILOT_PLIST, MA_AP_LAUNCH_AGENTS, MA_AP_LAUNCHCTL_BIN,
@@ -26,14 +35,28 @@
26
35
  * Usage:
27
36
  * node autopilot-control.mjs status
28
37
  * node autopilot-control.mjs off [--now]
38
+ * node autopilot-control.mjs on --repo <owner/name>... [--configure-only]
39
+ * node autopilot-control.mjs on-preview --repo <owner/name>...
29
40
  *
30
- * Exit: 0 done, 2 usage, 3 status could not be built.
41
+ * With MA_CONTROL_JSON_ERRORS=1 a refusal is also printed on stdout as
42
+ * {error: {code, message}}, for contract-server.mjs, which runs this script
43
+ * as a child process.
44
+ *
45
+ * Exit: 0 done, 1 `on` refused, 2 usage, 3 status could not be built.
31
46
  *
32
47
  * @module pipeline/scripts/autopilot-control
33
48
  */
34
49
 
35
50
  import { execFileSync, spawnSync } from "node:child_process";
36
- import { existsSync, readFileSync, rmSync } from "node:fs";
51
+ import {
52
+ chmodSync,
53
+ existsSync,
54
+ mkdirSync,
55
+ readFileSync,
56
+ renameSync,
57
+ rmSync,
58
+ writeFileSync,
59
+ } from "node:fs";
37
60
  import { homedir } from "node:os";
38
61
  import { dirname, join } from "node:path";
39
62
  import { fileURLToPath } from "node:url";
@@ -45,7 +68,9 @@ import {
45
68
  sessionIsLive,
46
69
  stashWorktree,
47
70
  } from "./autopilot-runner.mjs";
48
- import { removeAwake } from "./autopilot-awake.mjs";
71
+ import { installAwake, removeAwake } from "./autopilot-awake.mjs";
72
+ import { probeCredentials } from "./autopilot-arming.mjs";
73
+ import { configRefusal } from "./_autopilot-config.mjs";
49
74
  import { updateState } from "./gate-ledger.mjs";
50
75
  import { autopilotRoot } from "../lib/pr-request-location.mjs";
51
76
  import { runMain } from "../lib/fatal.mjs";
@@ -55,7 +80,12 @@ export const CONTRACT_VERSION = "1.0.0";
55
80
 
56
81
  const HERE = dirname(fileURLToPath(import.meta.url));
57
82
 
58
- export class ControlError extends Error {}
83
+ export class ControlError extends Error {
84
+ constructor(message, code = "internal") {
85
+ super(message);
86
+ this.code = code;
87
+ }
88
+ }
59
89
 
60
90
  function readJson(path, fallback) {
61
91
  try {
@@ -196,6 +226,155 @@ export async function autopilotOff({ now = false, env = process.env } = {}) {
196
226
  return { enabled: false, stoppedInFlight };
197
227
  }
198
228
 
229
+ const SCHEDULE_TEMPLATE = "multi-agent-autopilot.plist.template";
230
+
231
+ /** The installed tree keeps templates/ beside scripts/; the repo keeps it in install/. */
232
+ function scheduleTemplate(env) {
233
+ const home = env.HOME || homedir();
234
+ return (
235
+ [
236
+ env.MA_AP_SCHEDULE_TEMPLATE,
237
+ join(HERE, "..", "templates", SCHEDULE_TEMPLATE),
238
+ join(HERE, "..", "..", "install", "templates", SCHEDULE_TEMPLATE),
239
+ ...[".claude", ".copilot", ".codex"].map((d) =>
240
+ join(home, d, "templates", SCHEDULE_TEMPLATE),
241
+ ),
242
+ ]
243
+ .filter(Boolean)
244
+ .find((p) => existsSync(p)) ?? null
245
+ );
246
+ }
247
+
248
+ const xml = (v) => String(v).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
249
+
250
+ function which(bin, env) {
251
+ const r = spawnSync("/bin/sh", ["-c", `command -v ${bin}`], {
252
+ env,
253
+ encoding: "utf8",
254
+ timeout: 5000,
255
+ });
256
+ return r.status === 0 ? r.stdout.trim() : "";
257
+ }
258
+
259
+ /** The schedule plist, rendered with the seven placeholders autopilot-on fills. */
260
+ export function renderSchedule(config, env = process.env) {
261
+ const tpl = scheduleTemplate(env);
262
+ if (!tpl)
263
+ throw new ControlError(
264
+ `${SCHEDULE_TEMPLATE} is not installed - re-run the installer`,
265
+ "not-installed",
266
+ );
267
+ const home = env.HOME || homedir();
268
+ const values = {
269
+ NODE: env.MA_AP_NODE_BIN || process.execPath,
270
+ TICK: join(HERE, "autopilot-runner.mjs"),
271
+ LABEL: env.MA_AUTOPILOT_LABEL || "com.multi-agent.autopilot",
272
+ ROOT: autopilotRoot(env),
273
+ INTERVAL: Number.isInteger(config?.scanIntervalSeconds) ? config.scanIntervalSeconds : 120,
274
+ HOME: home,
275
+ CLAUDE: env.MA_AP_CLAUDE_BIN || which("claude", env),
276
+ };
277
+ return readFileSync(tpl, "utf8").replace(/\{\{([A-Z]+)\}\}/g, (m, k) =>
278
+ k in values ? xml(values[k]) : m,
279
+ );
280
+ }
281
+
282
+ function readPrefs(env) {
283
+ const path =
284
+ env.MULTI_AGENT_PREFS ||
285
+ env.MA_PREFS_FILE ||
286
+ join(env.HOME || homedir(), ".claude", "multi-agent-preferences.json");
287
+ return readJson(path, {});
288
+ }
289
+
290
+ /**
291
+ * What `on` would do for a selection: the config it writes and the probe of
292
+ * every credential that config needs. Writes nothing.
293
+ *
294
+ * @param {{repos: string[], env?: object}} opts nameWithOwner of each selected repo
295
+ */
296
+ export function autopilotOnPlan({ repos, env = process.env }) {
297
+ const selected = [...new Set(Array.isArray(repos) ? repos : [])];
298
+ if (!selected.length) {
299
+ throw new ControlError(
300
+ "no repository selected; autopilot is never turned on without one",
301
+ "no-selection",
302
+ );
303
+ }
304
+ const root = autopilotRoot(env);
305
+ const config = readJson(join(root, "config.json"), null);
306
+ const configured = Array.isArray(config?.repos) ? config.repos : [];
307
+ const names = configured.map((r) => r?.nameWithOwner);
308
+ const missing = selected.filter((n) => !names.includes(n));
309
+ if (missing.length) {
310
+ throw new ControlError(
311
+ `not configured yet: ${missing.join(", ")} - add a repository with /multi-agent:autopilot-on in a terminal, which asks its sources and label`,
312
+ "repo-not-configured",
313
+ );
314
+ }
315
+ const next = { ...config, repos: configured.filter((r) => selected.includes(r?.nameWithOwner)) };
316
+ const refusal = configRefusal(next);
317
+ if (refusal) throw new ControlError(refusal, "invalid-config");
318
+ const probe = probeCredentials({ config: next, prefs: readPrefs(env), env });
319
+ return {
320
+ config: next,
321
+ keep: selected,
322
+ drop: names.filter((n) => !selected.includes(n)),
323
+ probe: {
324
+ ready: probe.ready,
325
+ problems: probe.problems.map((p) => p.message),
326
+ warnings: probe.warnings,
327
+ },
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Turn continuous mode on for a selection of configured repositories.
333
+ *
334
+ * @param {{repos: string[], configureOnly?: boolean, env?: object}} opts
335
+ * @returns {{enabled: boolean, scheduled: boolean, repos: string[], dropped: string[],
336
+ * probe: {ready: boolean, problems: string[], warnings: string[]}, awake: boolean|null}}
337
+ */
338
+ export function autopilotOn({ repos, configureOnly = false, env = process.env }) {
339
+ const plan = autopilotOnPlan({ repos, env });
340
+ const root = autopilotRoot(env);
341
+ mkdirSync(root, { recursive: true, mode: 0o700 });
342
+ const file = join(root, "config.json");
343
+ const tmp = join(root, `.config.json.${process.pid}`);
344
+ writeFileSync(tmp, JSON.stringify(plan.config, null, 2) + "\n", { mode: 0o600 });
345
+ chmodSync(tmp, 0o600);
346
+ renameSync(tmp, file);
347
+ const base = { repos: plan.keep, dropped: plan.drop, probe: plan.probe };
348
+ if (configureOnly || !plan.probe.ready) {
349
+ return { enabled: false, scheduled: false, awake: null, ...base };
350
+ }
351
+ const label = env.MA_AUTOPILOT_LABEL || "com.multi-agent.autopilot";
352
+ const agents = env.MA_AP_LAUNCH_AGENTS || join(env.HOME || homedir(), "Library", "LaunchAgents");
353
+ const plist = env.MA_AUTOPILOT_PLIST || join(agents, `${label}.plist`);
354
+ mkdirSync(agents, { recursive: true });
355
+ const ptmp = `${plist}.${process.pid}.tmp`;
356
+ writeFileSync(ptmp, renderSchedule(plan.config, env), { mode: 0o644 });
357
+ renameSync(ptmp, plist);
358
+ const loaded =
359
+ launchctl(["bootstrap", `gui/${process.getuid()}`, plist], env) ||
360
+ launchctl(["load", plist], env);
361
+ if (!loaded) {
362
+ throw new ControlError("launchd refused the schedule plist", "launchd-refused");
363
+ }
364
+ const awake = installAwake({ env });
365
+ return { enabled: true, scheduled: true, awake: awake.loaded, ...base };
366
+ }
367
+
368
+ function refuse(err, code) {
369
+ process.stderr.write(`autopilot-control: ${err.message}\n`);
370
+ if (process.env.MA_CONTROL_JSON_ERRORS === "1") {
371
+ process.stdout.write(
372
+ `${JSON.stringify({ error: { code: err.code, message: err.message } })}\n`,
373
+ );
374
+ }
375
+ return code;
376
+ }
377
+
199
378
  async function main(argv) {
200
379
  const [mode, ...rest] = argv;
201
380
  if (mode === "status" && !rest.length) {
@@ -203,8 +382,8 @@ async function main(argv) {
203
382
  process.stdout.write(`${JSON.stringify(autopilotStatus())}\n`);
204
383
  return 0;
205
384
  } catch (err) {
206
- process.stderr.write(`autopilot-control: ${err.message}\n`);
207
- return 3;
385
+ if (!(err instanceof ControlError)) throw err;
386
+ return refuse(err, 3);
208
387
  }
209
388
  }
210
389
  if (mode === "off" && rest.every((a) => a === "--now")) {
@@ -212,7 +391,33 @@ async function main(argv) {
212
391
  process.stdout.write(`${JSON.stringify(out)}\n`);
213
392
  return 0;
214
393
  }
215
- process.stderr.write("usage: autopilot-control.mjs status | off [--now]\n");
394
+ if (mode === "on" || mode === "on-preview") {
395
+ const repos = [];
396
+ let configureOnly = false;
397
+ for (let i = 0; i < rest.length; i++) {
398
+ if (rest[i] === "--repo" && rest[i + 1]) repos.push(rest[++i]);
399
+ else if (rest[i] === "--configure-only" && mode === "on") configureOnly = true;
400
+ else return usage();
401
+ }
402
+ try {
403
+ const out =
404
+ mode === "on"
405
+ ? autopilotOn({ repos, configureOnly })
406
+ : (({ config: _c, ...plan }) => plan)(autopilotOnPlan({ repos }));
407
+ process.stdout.write(`${JSON.stringify(out)}\n`);
408
+ return 0;
409
+ } catch (err) {
410
+ if (!(err instanceof ControlError)) throw err;
411
+ return refuse(err, 1);
412
+ }
413
+ }
414
+ return usage();
415
+ }
416
+
417
+ function usage() {
418
+ process.stderr.write(
419
+ "usage: autopilot-control.mjs status | off [--now] | on --repo <owner/name>... [--configure-only] | on-preview --repo <owner/name>...\n",
420
+ );
216
421
  return 2;
217
422
  }
218
423