@mmerterden/multi-agent-pipeline 20.28.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 (186) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +19 -6
  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 +122 -1
  9. package/install/codex.mjs +2 -0
  10. package/install/copilot.mjs +13 -0
  11. package/install/templates/claude-hooks.json +30 -1
  12. package/manifest.json +189 -77
  13. package/package.json +2 -1
  14. package/pipeline/commands/multi-agent/analysis/SKILL.md +3 -1
  15. package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +13 -15
  16. package/pipeline/commands/multi-agent/office-off/SKILL.md +27 -0
  17. package/pipeline/commands/multi-agent/office-on/SKILL.md +50 -0
  18. package/pipeline/commands/multi-agent/office-status/SKILL.md +29 -0
  19. package/pipeline/commands/multi-agent/resume/SKILL.md +2 -1
  20. package/pipeline/commands/multi-agent/serve/SKILL.md +4 -2
  21. package/pipeline/commands/multi-agent/stack/SKILL.md +15 -8
  22. package/pipeline/commands/multi-agent/steer/SKILL.md +10 -9
  23. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  24. package/pipeline/contract/CHANGELOG.md +62 -0
  25. package/pipeline/contract/README.md +52 -6
  26. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  27. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  28. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  29. package/pipeline/contract/manifest.json +367 -22
  30. package/pipeline/contract/types/index.d.ts +531 -4
  31. package/pipeline/lib/hook-registration.mjs +252 -0
  32. package/pipeline/lib/prefs-value.mjs +44 -0
  33. package/pipeline/lib/run-completion.mjs +148 -0
  34. package/pipeline/multi-agent-refs/analysis/render.md +4 -4
  35. package/pipeline/multi-agent-refs/cross-cli-contract.md +5 -4
  36. package/pipeline/multi-agent-refs/features/doctor.md +11 -0
  37. package/pipeline/multi-agent-refs/features/office-threat-model.md +91 -0
  38. package/pipeline/multi-agent-refs/help/en.md +3 -0
  39. package/pipeline/multi-agent-refs/help/tr.md +3 -0
  40. package/pipeline/multi-agent-refs/phases/operations.md +17 -0
  41. package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -2
  42. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +2 -2
  43. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +1 -1
  44. package/pipeline/multi-agent-refs/phases/phase-3-review.md +2 -2
  45. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +7 -6
  46. package/pipeline/multi-agent-refs/phases/phase-5-report.md +5 -4
  47. package/pipeline/office/NOTICE.md +42 -0
  48. package/pipeline/office/assets/BIN.png +0 -0
  49. package/pipeline/office/assets/BOOKSHELF.png +0 -0
  50. package/pipeline/office/assets/CACTUS.png +0 -0
  51. package/pipeline/office/assets/CLOCK.png +0 -0
  52. package/pipeline/office/assets/COFFEE.png +0 -0
  53. package/pipeline/office/assets/COFFEE_TABLE.png +0 -0
  54. package/pipeline/office/assets/CUSHIONED_BENCH.png +0 -0
  55. package/pipeline/office/assets/CUSHIONED_CHAIR_BACK.png +0 -0
  56. package/pipeline/office/assets/CUSHIONED_CHAIR_FRONT.png +0 -0
  57. package/pipeline/office/assets/CUSHIONED_CHAIR_SIDE.png +0 -0
  58. package/pipeline/office/assets/DESK_FRONT.png +0 -0
  59. package/pipeline/office/assets/DESK_SIDE.png +0 -0
  60. package/pipeline/office/assets/DOUBLE_BOOKSHELF.png +0 -0
  61. package/pipeline/office/assets/HANGING_PLANT.png +0 -0
  62. package/pipeline/office/assets/LARGE_PAINTING.png +0 -0
  63. package/pipeline/office/assets/LARGE_PLANT.png +0 -0
  64. package/pipeline/office/assets/PC_BACK.png +0 -0
  65. package/pipeline/office/assets/PC_FRONT_OFF.png +0 -0
  66. package/pipeline/office/assets/PC_FRONT_ON_1.png +0 -0
  67. package/pipeline/office/assets/PC_FRONT_ON_2.png +0 -0
  68. package/pipeline/office/assets/PC_FRONT_ON_3.png +0 -0
  69. package/pipeline/office/assets/PC_SIDE.png +0 -0
  70. package/pipeline/office/assets/PLANT.png +0 -0
  71. package/pipeline/office/assets/PLANT_2.png +0 -0
  72. package/pipeline/office/assets/POT.png +0 -0
  73. package/pipeline/office/assets/SMALL_PAINTING.png +0 -0
  74. package/pipeline/office/assets/SMALL_PAINTING_2.png +0 -0
  75. package/pipeline/office/assets/SMALL_TABLE_FRONT.png +0 -0
  76. package/pipeline/office/assets/SMALL_TABLE_SIDE.png +0 -0
  77. package/pipeline/office/assets/SOFA_BACK.png +0 -0
  78. package/pipeline/office/assets/SOFA_FRONT.png +0 -0
  79. package/pipeline/office/assets/SOFA_SIDE.png +0 -0
  80. package/pipeline/office/assets/TABLE_FRONT.png +0 -0
  81. package/pipeline/office/assets/WHITEBOARD.png +0 -0
  82. package/pipeline/office/assets/WOODEN_BENCH.png +0 -0
  83. package/pipeline/office/assets/WOODEN_CHAIR_BACK.png +0 -0
  84. package/pipeline/office/assets/WOODEN_CHAIR_FRONT.png +0 -0
  85. package/pipeline/office/assets/WOODEN_CHAIR_SIDE.png +0 -0
  86. package/pipeline/office/assets/char_0.png +0 -0
  87. package/pipeline/office/assets/char_1.png +0 -0
  88. package/pipeline/office/assets/char_2.png +0 -0
  89. package/pipeline/office/assets/char_3.png +0 -0
  90. package/pipeline/office/assets/char_4.png +0 -0
  91. package/pipeline/office/assets/char_5.png +0 -0
  92. package/pipeline/office/assets/floor_0.png +0 -0
  93. package/pipeline/office/assets/floor_1.png +0 -0
  94. package/pipeline/office/assets/floor_2.png +0 -0
  95. package/pipeline/office/assets/floor_3.png +0 -0
  96. package/pipeline/office/assets/floor_4.png +0 -0
  97. package/pipeline/office/assets/floor_5.png +0 -0
  98. package/pipeline/office/assets/floor_6.png +0 -0
  99. package/pipeline/office/assets/floor_7.png +0 -0
  100. package/pipeline/office/assets/floor_8.png +0 -0
  101. package/pipeline/office/assets/wall_0.png +0 -0
  102. package/pipeline/office/index.html +5319 -0
  103. package/pipeline/preferences-template.json +7 -0
  104. package/pipeline/rules/figma-pipeline.md +2 -0
  105. package/pipeline/schemas/activity-chat.schema.json +78 -0
  106. package/pipeline/schemas/activity-roles.json +48 -0
  107. package/pipeline/schemas/activity-roles.schema.json +49 -0
  108. package/pipeline/schemas/activity.schema.json +429 -0
  109. package/pipeline/schemas/agent-state.schema.json +20 -5
  110. package/pipeline/schemas/autopilot-on-request.schema.json +33 -0
  111. package/pipeline/schemas/autopilot-on.schema.json +111 -0
  112. package/pipeline/schemas/contract-error.schema.json +33 -3
  113. package/pipeline/schemas/issue-maturity.schema.json +68 -0
  114. package/pipeline/schemas/ledger.schema.json +202 -0
  115. package/pipeline/schemas/permissions.schema.json +47 -0
  116. package/pipeline/schemas/phases.json +3 -2
  117. package/pipeline/schemas/phases.schema.json +5 -1
  118. package/pipeline/schemas/prefs.schema.json +56 -0
  119. package/pipeline/schemas/prs.schema.json +73 -0
  120. package/pipeline/schemas/review-verdict-request.schema.json +20 -0
  121. package/pipeline/schemas/review-verdict.schema.json +62 -0
  122. package/pipeline/schemas/routine-delete.schema.json +59 -0
  123. package/pipeline/schemas/routines.schema.json +38 -0
  124. package/pipeline/schemas/run-evidence.schema.json +29 -0
  125. package/pipeline/schemas/run-results.schema.json +218 -0
  126. package/pipeline/schemas/runs-index.schema.json +1 -1
  127. package/pipeline/schemas/service-read.schema.json +58 -0
  128. package/pipeline/schemas/stack-change.schema.json +82 -0
  129. package/pipeline/schemas/stack-request.schema.json +33 -0
  130. package/pipeline/schemas/stack.schema.json +59 -0
  131. package/pipeline/schemas/steer-request.schema.json +25 -0
  132. package/pipeline/schemas/steer.schema.json +59 -0
  133. package/pipeline/schemas/tool-call-request.schema.json +19 -0
  134. package/pipeline/schemas/tool-call.schema.json +47 -0
  135. package/pipeline/schemas/tools.schema.json +60 -0
  136. package/pipeline/scripts/_active-run.mjs +158 -0
  137. package/pipeline/scripts/_autopilot-outcomes.mjs +1 -0
  138. package/pipeline/scripts/_cost.mjs +66 -1
  139. package/pipeline/scripts/_guard_rules.py +605 -0
  140. package/pipeline/scripts/_office-smoke-lib.sh +115 -0
  141. package/pipeline/scripts/_spawn-async.mjs +73 -0
  142. package/pipeline/scripts/_transcript-tail.mjs +274 -0
  143. package/pipeline/scripts/activity-snapshot.mjs +1027 -0
  144. package/pipeline/scripts/agent-guard.py +118 -7
  145. package/pipeline/scripts/agent-guard.sh +50 -3
  146. package/pipeline/scripts/argv-secret-scan.mjs +114 -0
  147. package/pipeline/scripts/autopilot-control.mjs +214 -9
  148. package/pipeline/scripts/autopilot-runner.mjs +8 -2
  149. package/pipeline/scripts/confirm-command.mjs +70 -0
  150. package/pipeline/scripts/contract-server.mjs +1178 -48
  151. package/pipeline/scripts/cost-analyze.mjs +2 -53
  152. package/pipeline/scripts/cost-table.json +1 -0
  153. package/pipeline/scripts/doctor.mjs +54 -0
  154. package/pipeline/scripts/evidence-gate.mjs +8 -4
  155. package/pipeline/scripts/gate-ledger.mjs +163 -7
  156. package/pipeline/scripts/office-build.mjs +89 -0
  157. package/pipeline/scripts/office-control.mjs +383 -0
  158. package/pipeline/scripts/office-ledger.mjs +409 -0
  159. package/pipeline/scripts/plan-coverage-gate.mjs +9 -8
  160. package/pipeline/scripts/pre-commit-check.sh +12 -0
  161. package/pipeline/scripts/routine-registry.mjs +42 -6
  162. package/pipeline/scripts/run-results.mjs +373 -0
  163. package/pipeline/scripts/run-scope-guard.mjs +188 -0
  164. package/pipeline/scripts/run-scope-guard.sh +23 -0
  165. package/pipeline/scripts/run-stop-guard.mjs +246 -0
  166. package/pipeline/scripts/run-stop-guard.sh +25 -0
  167. package/pipeline/scripts/service-read.mjs +244 -0
  168. package/pipeline/scripts/stack-toggle.mjs +285 -0
  169. package/pipeline/scripts/steer-run.mjs +97 -0
  170. package/pipeline/scripts/toolkit-tools.mjs +599 -0
  171. package/pipeline/scripts/uninstall.mjs +4 -0
  172. package/pipeline/scripts/validate-state.mjs +26 -0
  173. package/pipeline/scripts/work-items.mjs +304 -0
  174. package/pipeline/scripts/write-state.mjs +114 -0
  175. package/pipeline/skills/.skill-manifest.json +19 -7
  176. package/pipeline/skills/.skills-index.json +34 -1
  177. package/pipeline/skills/shared/README.md +6 -3
  178. package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +13 -15
  179. package/pipeline/skills/shared/core/multi-agent-office-off/SKILL.md +16 -0
  180. package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +22 -0
  181. package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +17 -0
  182. package/pipeline/skills/shared/core/multi-agent-serve/SKILL.md +4 -2
  183. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +14 -6
  184. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +10 -9
  185. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
  186. package/pipeline/skills/skills-index.md +4 -1
@@ -0,0 +1,82 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/stack-change.schema.json",
4
+ "title": "Stack change",
5
+ "description": "What POST /v1/stack returns (`stack-toggle.mjs set`). Step one: preview {stacks, changed, enabledPlugins, dropped} with confirmToken and expiresAt, nothing written. Step two: the written enabledPlugins, the keys that changed and written.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion"],
9
+ "properties": {
10
+ "contractVersion": {
11
+ "type": "string",
12
+ "pattern": "^1\\.[0-9]+\\.[0-9]+$",
13
+ "x-sensitivity": "none"
14
+ },
15
+ "preview": {
16
+ "type": "object",
17
+ "additionalProperties": false,
18
+ "required": ["stacks", "changed", "enabledPlugins", "dropped"],
19
+ "properties": {
20
+ "stacks": {
21
+ "type": "array",
22
+ "items": {
23
+ "type": "string"
24
+ }
25
+ },
26
+ "changed": {
27
+ "type": "array",
28
+ "items": {
29
+ "type": "string"
30
+ }
31
+ },
32
+ "enabledPlugins": {
33
+ "type": "object",
34
+ "additionalProperties": {
35
+ "type": "boolean"
36
+ }
37
+ },
38
+ "dropped": {
39
+ "type": "array",
40
+ "items": {
41
+ "type": "string"
42
+ }
43
+ }
44
+ }
45
+ },
46
+ "confirmToken": {
47
+ "type": "string",
48
+ "pattern": "^[A-Za-z0-9_-]{16,128}$"
49
+ },
50
+ "expiresAt": {
51
+ "type": "string",
52
+ "format": "date-time"
53
+ },
54
+ "stacks": {
55
+ "type": "array",
56
+ "items": {
57
+ "type": "string"
58
+ }
59
+ },
60
+ "enabledPlugins": {
61
+ "type": "object",
62
+ "additionalProperties": {
63
+ "type": "boolean"
64
+ }
65
+ },
66
+ "changed": {
67
+ "type": "array",
68
+ "items": {
69
+ "type": "string"
70
+ }
71
+ },
72
+ "dropped": {
73
+ "type": "array",
74
+ "items": {
75
+ "type": "string"
76
+ }
77
+ },
78
+ "written": {
79
+ "type": "boolean"
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/stack-request.schema.json",
4
+ "title": "Stack change request",
5
+ "description": "The body of POST /v1/stack. The first request names repo (a name from GET /v1/repos or its path) and stacks (as /multi-agent:stack takes them, aliases allowed); the second sends only confirmToken.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion"],
9
+ "properties": {
10
+ "contractVersion": {
11
+ "type": "string",
12
+ "pattern": "^1\\.[0-9]+\\.[0-9]+$",
13
+ "x-sensitivity": "none"
14
+ },
15
+ "repo": {
16
+ "type": "string",
17
+ "maxLength": 4096
18
+ },
19
+ "stacks": {
20
+ "type": "array",
21
+ "minItems": 1,
22
+ "maxItems": 8,
23
+ "items": {
24
+ "type": "string",
25
+ "pattern": "^[a-z]{2,16}$"
26
+ }
27
+ },
28
+ "confirmToken": {
29
+ "type": "string",
30
+ "pattern": "^[A-Za-z0-9_-]{16,128}$"
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/stack.schema.json",
4
+ "title": "Stack status",
5
+ "description": "What GET /v1/stack?repo= returns (`stack-toggle.mjs status --json`): the stacks a repository enables in .claude/settings.json and every marketplace plugin with its skills. manifestVerified is false when the marketplace manifest could not be read and the plugin list is the convention.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion", "stacks", "knownStacks", "plugins", "manifestVerified"],
9
+ "properties": {
10
+ "contractVersion": {
11
+ "type": "string",
12
+ "pattern": "^1\\.[0-9]+\\.[0-9]+$",
13
+ "x-sensitivity": "none"
14
+ },
15
+ "stacks": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "string"
19
+ }
20
+ },
21
+ "knownStacks": {
22
+ "type": "array",
23
+ "items": {
24
+ "type": "string"
25
+ }
26
+ },
27
+ "plugins": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "required": ["name", "enabled", "alwaysOn", "stack", "skills"],
33
+ "properties": {
34
+ "name": {
35
+ "type": "string"
36
+ },
37
+ "enabled": {
38
+ "type": "boolean"
39
+ },
40
+ "alwaysOn": {
41
+ "type": "boolean"
42
+ },
43
+ "stack": {
44
+ "type": ["string", "null"]
45
+ },
46
+ "skills": {
47
+ "type": "array",
48
+ "items": {
49
+ "type": "string"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ },
55
+ "manifestVerified": {
56
+ "type": "boolean"
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/steer-request.schema.json",
4
+ "title": "Steer request",
5
+ "description": "The body of POST /v1/runs/{id}/steer. The first request carries text (verbatim, up to 4000 characters) and gets a preview and a confirm token bound to the run and that text; the second carries only confirmToken.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion"],
9
+ "properties": {
10
+ "contractVersion": {
11
+ "type": "string",
12
+ "pattern": "^1\\.[0-9]+\\.[0-9]+$",
13
+ "x-sensitivity": "none"
14
+ },
15
+ "text": {
16
+ "type": "string",
17
+ "minLength": 1,
18
+ "maxLength": 4000
19
+ },
20
+ "confirmToken": {
21
+ "type": "string",
22
+ "pattern": "^[A-Za-z0-9_-]{16,128}$"
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/steer.schema.json",
4
+ "title": "Steer queued",
5
+ "description": "What POST /v1/runs/{id}/steer returns (`steer-run.mjs`). Step one: preview {text, status, currentPhase, replaces} with confirmToken and expiresAt, nothing written. Step two: the instruction is queued as the run's pendingSteer and applied at the entry to its next phase; replaced is the unconsumed instruction it replaced, or null.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion", "taskId"],
9
+ "properties": {
10
+ "contractVersion": {
11
+ "type": "string",
12
+ "pattern": "^1\\.[0-9]+\\.[0-9]+$",
13
+ "x-sensitivity": "none"
14
+ },
15
+ "taskId": {
16
+ "type": "string"
17
+ },
18
+ "queuedAt": {
19
+ "type": "string",
20
+ "format": "date-time"
21
+ },
22
+ "replaced": {
23
+ "type": ["string", "null"]
24
+ },
25
+ "status": {
26
+ "type": ["string", "null"]
27
+ },
28
+ "currentPhase": {
29
+ "type": ["integer", "null"]
30
+ },
31
+ "preview": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "required": ["text", "status", "currentPhase", "replaces"],
35
+ "properties": {
36
+ "text": {
37
+ "type": "string"
38
+ },
39
+ "status": {
40
+ "type": ["string", "null"]
41
+ },
42
+ "currentPhase": {
43
+ "type": ["integer", "null"]
44
+ },
45
+ "replaces": {
46
+ "type": ["string", "null"]
47
+ }
48
+ }
49
+ },
50
+ "confirmToken": {
51
+ "type": "string",
52
+ "pattern": "^[A-Za-z0-9_-]{16,128}$"
53
+ },
54
+ "expiresAt": {
55
+ "type": "string",
56
+ "format": "date-time"
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/tool-call-request.schema.json",
4
+ "title": "Tool call request",
5
+ "description": "The body of POST /v1/tools/{name}/call: the tool's arguments, which must satisfy its inputSchema. At most 64 KiB.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion"],
9
+ "properties": {
10
+ "contractVersion": {
11
+ "type": "string",
12
+ "pattern": "^1\\.[0-9]+\\.[0-9]+$",
13
+ "x-sensitivity": "none"
14
+ },
15
+ "arguments": {
16
+ "type": "object"
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/tool-call.schema.json",
4
+ "title": "Tool call result",
5
+ "description": "What POST /v1/tools/{name}/call returns (`toolkit-tools.mjs call`): the tool's result as text, JSON (as text) and images (base64, image types only). Every text is redacted.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion", "tool", "risk", "isError", "content"],
9
+ "properties": {
10
+ "contractVersion": {
11
+ "type": "string",
12
+ "pattern": "^1\\.[0-9]+\\.[0-9]+$",
13
+ "x-sensitivity": "none"
14
+ },
15
+ "tool": {
16
+ "type": "string"
17
+ },
18
+ "risk": {
19
+ "enum": ["read", "low", "high"]
20
+ },
21
+ "isError": {
22
+ "type": "boolean"
23
+ },
24
+ "content": {
25
+ "type": "array",
26
+ "items": {
27
+ "type": "object",
28
+ "additionalProperties": false,
29
+ "required": ["kind"],
30
+ "properties": {
31
+ "kind": {
32
+ "enum": ["text", "json", "image"]
33
+ },
34
+ "text": {
35
+ "type": "string"
36
+ },
37
+ "mime": {
38
+ "type": "string"
39
+ },
40
+ "data": {
41
+ "type": "string"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/tools.schema.json",
4
+ "title": "Toolkit tools",
5
+ "description": "What GET /v1/tools returns (`toolkit-tools.mjs list`): every tool the registered multi-agent-toolkit MCP server serves, with its category (the name's prefix), risk from its own annotations (readOnlyHint read, destructiveHint high, else low) and its inputSchema, from which a client builds the form. Calls are denied by default; allowed says which tools can be called and reason why the others cannot.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion", "tools"],
9
+ "properties": {
10
+ "contractVersion": {
11
+ "type": "string",
12
+ "pattern": "^1\\.[0-9]+\\.[0-9]+$",
13
+ "x-sensitivity": "none"
14
+ },
15
+ "tools": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "required": [
21
+ "name",
22
+ "title",
23
+ "category",
24
+ "description",
25
+ "risk",
26
+ "inputSchema",
27
+ "allowed",
28
+ "reason"
29
+ ],
30
+ "properties": {
31
+ "name": {
32
+ "type": "string"
33
+ },
34
+ "title": {
35
+ "type": ["string", "null"]
36
+ },
37
+ "category": {
38
+ "type": "string"
39
+ },
40
+ "description": {
41
+ "type": "string"
42
+ },
43
+ "risk": {
44
+ "enum": ["read", "low", "high"]
45
+ },
46
+ "inputSchema": {
47
+ "type": "object"
48
+ },
49
+ "allowed": {
50
+ "type": "boolean",
51
+ "description": "Whether POST /v1/tools/{name}/call accepts this tool: only a readOnlyHint tool whose inputSchema takes no location (path, URL, file, directory, output, project, root) and that is not in the web or context family."
52
+ },
53
+ "reason": {
54
+ "type": ["string", "null"]
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @file _active-run.mjs - which pipeline run a hook call belongs to, and where
3
+ * that run stands against its mode's phase list.
4
+ *
5
+ * A hook receives the host's session id and working directory, never a task id.
6
+ * A run is bound to a session by one of three facts, strongest first:
7
+ *
8
+ * 1. `sessionId` equals MULTI_AGENT_SESSION_ID in the hook's environment: the
9
+ * id a launcher started the run with (continuous mode, a client).
10
+ * 2. `hostSessionId` equals the payload's `session_id`: write-state.mjs stamps
11
+ * the host session that made the run's last phase transition
12
+ * (CLAUDE_CODE_SESSION_ID), so the session driving a run is the one bound
13
+ * to it, and a resume in a new session rebinds it at its first transition.
14
+ * 3. A state with no `hostSessionId` whose `worktreePath` is the git toplevel
15
+ * of the payload's cwd: a run written before the stamp existed. A state
16
+ * that carries a different `hostSessionId` is another session's run and
17
+ * never matches this way.
18
+ *
19
+ * Nothing here decides anything; callers (run-stop-guard, run-scope-guard)
20
+ * apply their own rule to the run this returns. Zero runtime dependencies.
21
+ *
22
+ * @module pipeline/scripts/_active-run
23
+ */
24
+
25
+ import { spawnSync } from "node:child_process";
26
+ import { appendFileSync, mkdirSync, realpathSync, statSync } from "node:fs";
27
+ import { dirname, join } from "node:path";
28
+
29
+ import { readRegularJson } from "../lib/regular-file.mjs";
30
+ import { listRuns, logsRoot, resolveRunFile } from "./_run-paths.mjs";
31
+
32
+ export {
33
+ finalPhaseOf,
34
+ modeOf,
35
+ modePhases,
36
+ nextPhaseOf,
37
+ phaseContract,
38
+ phaseName,
39
+ } from "../lib/run-completion.mjs";
40
+
41
+ function real(p) {
42
+ try {
43
+ return realpathSync(p);
44
+ } catch {
45
+ return p;
46
+ }
47
+ }
48
+
49
+ /** The git toplevel of `dir`, real-pathed, or null outside a repository. */
50
+ export function gitToplevel(dir) {
51
+ if (!dir) return null;
52
+ const r = spawnSync("git", ["-C", dir, "rev-parse", "--show-toplevel"], {
53
+ encoding: "utf8",
54
+ timeout: 5000,
55
+ });
56
+ return r.status === 0 ? real(r.stdout.trim()) : null;
57
+ }
58
+
59
+ function mtime(p) {
60
+ try {
61
+ return statSync(p).mtimeMs;
62
+ } catch {
63
+ return 0;
64
+ }
65
+ }
66
+
67
+ const LIVE_STATUSES = new Set(["in_progress", "awaiting_input"]);
68
+
69
+ /**
70
+ * The run bound to this hook call, or null.
71
+ *
72
+ * @param {{sessionId?: string|null, cwd?: string|null, env?: Record<string,string|undefined>}} opts
73
+ * @returns {{statePath: string, state: object, matchedBy: "session"|"host-session"|"worktree"} | null}
74
+ * with `unreadable` (count of state files that did not parse) on the returned
75
+ * object or on the `findActiveRun.lastUnreadable` property when nothing matched
76
+ */
77
+ export function findActiveRun({ sessionId = null, cwd = null, env = process.env } = {}) {
78
+ const launched = env.MULTI_AGENT_SESSION_ID || "";
79
+ let top;
80
+ const topOf = () => {
81
+ if (top === undefined) top = cwd ? gitToplevel(cwd) : null;
82
+ return top;
83
+ };
84
+ const hits = [];
85
+ let unreadable = 0;
86
+ const prevRoot = process.env.LOGS_ROOT;
87
+ if (env.LOGS_ROOT !== undefined) process.env.LOGS_ROOT = env.LOGS_ROOT;
88
+ try {
89
+ for (const run of listRuns()) {
90
+ const p = resolveRunFile(run.taskId, "agent-state.json", run.project);
91
+ if (!p) continue;
92
+ const doc = readRegularJson(p, null);
93
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
94
+ unreadable += 1;
95
+ continue;
96
+ }
97
+ let matchedBy = null;
98
+ if (launched && doc.sessionId === launched) matchedBy = "session";
99
+ else if (sessionId && doc.hostSessionId === sessionId) matchedBy = "host-session";
100
+ else if (
101
+ !doc.hostSessionId &&
102
+ typeof doc.worktreePath === "string" &&
103
+ doc.worktreePath &&
104
+ topOf() &&
105
+ real(doc.worktreePath) === topOf()
106
+ ) {
107
+ matchedBy = "worktree";
108
+ }
109
+ if (matchedBy) hits.push({ statePath: p, state: doc, matchedBy, t: mtime(p) });
110
+ }
111
+ } finally {
112
+ if (prevRoot === undefined) delete process.env.LOGS_ROOT;
113
+ else process.env.LOGS_ROOT = prevRoot;
114
+ }
115
+ findActiveRun.lastUnreadable = unreadable;
116
+ if (!hits.length) return null;
117
+ const rank = { session: 0, "host-session": 1, worktree: 2 };
118
+ hits.sort((a, b) => rank[a.matchedBy] - rank[b.matchedBy] || b.t - a.t);
119
+ const lead = hits.filter((h) => h.matchedBy === hits[0].matchedBy);
120
+ // A session can hold a finished run next to a live one. The run started
121
+ // last is the one the session is on: a live run started after the finished
122
+ // one wins however recently the finished file was touched, and a live run
123
+ // started before it was abandoned without a halt and does not hold the
124
+ // session. startedAt orders them; the state's mtime stands in without it.
125
+ const started = (h) => Date.parse(h.state?.startedAt) || h.t;
126
+ const isLive = (h) => LIVE_STATUSES.has(h.state?.status);
127
+ const liveRun = lead.find(isLive);
128
+ const finished = lead.filter((h) => !isLive(h));
129
+ const lastFinished = finished.sort((a, b) => started(b) - started(a))[0];
130
+ let pick = lead[0];
131
+ if (liveRun)
132
+ pick = lastFinished && started(lastFinished) > started(liveRun) ? lastFinished : liveRun;
133
+ const { t: _t, ...best } = pick;
134
+ return { ...best, unreadable };
135
+ }
136
+ findActiveRun.lastUnreadable = 0;
137
+
138
+ /**
139
+ * Append one line to the hook failure log under the log root. A hook that
140
+ * fails open says why somewhere a person can read later; this never throws.
141
+ *
142
+ * @param {string} hook
143
+ * @param {string} reason
144
+ * @param {Record<string,string|undefined>} [env]
145
+ */
146
+ export function logHookFailure(hook, reason, env = process.env) {
147
+ try {
148
+ const root = env.LOGS_ROOT || logsRoot();
149
+ mkdirSync(root, { recursive: true });
150
+ const line = `${new Date().toISOString()} ${hook}: ${String(reason).replace(/\s+/g, " ")}\n`;
151
+ appendFileSync(join(root, "hook-failures.log"), line);
152
+ } catch {
153
+ // Logging is best-effort; the hook's verdict never depends on it.
154
+ }
155
+ }
156
+
157
+ /** The run directory that holds a state file. */
158
+ export const runDirOf = (statePath) => dirname(statePath);
@@ -27,6 +27,7 @@ export const OUTCOME = Object.freeze({
27
27
  MERGED: "merged",
28
28
  SKIPPED: "skipped",
29
29
  COMPLETED_NO_PR: "completed-no-pr",
30
+ STOPPED_INCOMPLETE: "stopped-incomplete",
30
31
  NEEDS_INPUT: "needs-input",
31
32
  AWAITING_ANSWER: "awaiting-answer",
32
33
  VERIFICATION_FAILED: "verification-failed",
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @file _cost.mjs - shared token-cost pricing math for the .mjs cost scripts
3
- * (cost-budget-check.mjs, run-aggregator.mjs).
3
+ * (cost-budget-check.mjs, run-aggregator.mjs, cost-analyze.mjs,
4
+ * office-ledger.mjs).
4
5
  *
5
6
  * The single source of the pricing formula for Node callers. Rate DATA still
6
7
  * lives in cost-table.json; this module owns only the arithmetic so the formula
@@ -73,3 +74,67 @@ export function attemptSpend(rows) {
73
74
  }
74
75
  return { usd, unknown };
75
76
  }
77
+
78
+ /**
79
+ * Wire ID -> rate. Transcripts carry the wire value ("claude-opus-5-5"); the
80
+ * price table is keyed by rung, so each rung's `modelId` and `copilotModelId`
81
+ * map to its rate, `legacy` adds previous-generation IDs at their own rates,
82
+ * and `models` overrides the rung rate for a dispatched ID whose model is not
83
+ * the rung's (Copilot CLI's older models bill at their own rate). Matching on
84
+ * the ID rather than the rung name keeps a generation move a data edit, which
85
+ * is the contract cost-table.json states for itself. A rung name ("opus")
86
+ * prices as its rung, which is what a tracker records.
87
+ *
88
+ * @param {object} table cost-table.json
89
+ * @returns {(modelId: string|null|undefined) => object|null}
90
+ */
91
+ export function modelRateLookup(table) {
92
+ const byId = new Map([
93
+ ...Object.entries(table.legacy || {}),
94
+ ...Object.values(table.prices || {}).flatMap((p) =>
95
+ [p.modelId, p.copilotModelId].filter(Boolean).map((id) => [id, p]),
96
+ ),
97
+ ...Object.entries(table.models || {}),
98
+ ]);
99
+ const longestFirst = [...byId.keys()].sort((a, b) => b.length - a.length);
100
+ return (modelId) => {
101
+ if (!modelId) return null;
102
+ const exact = byId.get(modelId) ?? table.prices?.[modelId];
103
+ if (exact) return exact;
104
+ // A dated snapshot id (a known id, a hyphen, a release date) prices as that
105
+ // id; the longest known id wins, so a current id is never read as a shorter
106
+ // legacy one. Written as a shape rather than as a literal, because
107
+ // smoke-model-fallback.sh reads every model id in this tree and requires it
108
+ // to exist in cost-table.json. An id nobody knows prices as nothing and is
109
+ // COUNTED as unpriced rather than folded in at zero.
110
+ const id = longestFirst.find(
111
+ (known) => modelId.startsWith(`${known}-`) || modelId.startsWith(`${known}[`),
112
+ );
113
+ return id ? byId.get(id) : null;
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Cache WRITES are not a discount, they are a surcharge: Anthropic prices a
119
+ * 1h cache write above fresh input. cost-table.json carries cacheReadPerMtok
120
+ * only, so the write rate is derived at the published 1.25x of input - an
121
+ * unpriced component silently dropped would make every long-context session
122
+ * look cheap, which is the direction that hurts.
123
+ */
124
+ export const CACHE_WRITE_MULTIPLIER = 1.25;
125
+
126
+ /**
127
+ * USD for one transcript usage record, or null without a rate.
128
+ *
129
+ * @param {object|null} rate
130
+ * @param {{input_tokens?:number, output_tokens?:number, cache_read_input_tokens?:number, cache_creation_input_tokens?:number}} u
131
+ */
132
+ export function priceUsage(rate, u) {
133
+ const fresh = Number(u.input_tokens || 0);
134
+ const out = Number(u.output_tokens || 0);
135
+ const cacheRead = Number(u.cache_read_input_tokens || 0);
136
+ const cacheWrite = Number(u.cache_creation_input_tokens || 0);
137
+ const base = costUsd(rate, fresh, out, cacheRead);
138
+ if (base === null) return null;
139
+ return base + (cacheWrite / 1_000_000) * (rate.inPerMtok || 0) * CACHE_WRITE_MULTIPLIER;
140
+ }