@enderfga/claw-orchestrator 5.0.0 → 6.0.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 (158) hide show
  1. package/README.md +28 -28
  2. package/dist/bin/cli.js +107 -1
  3. package/dist/bin/cli.js.map +1 -1
  4. package/dist/src/acp-server.d.ts +5 -5
  5. package/dist/src/acp-server.js +3 -3
  6. package/dist/src/acp-server.js.map +1 -1
  7. package/dist/src/autoloop/dispatcher.d.ts +22 -0
  8. package/dist/src/autoloop/dispatcher.js +71 -13
  9. package/dist/src/autoloop/dispatcher.js.map +1 -1
  10. package/dist/src/autoloop/messages.d.ts +10 -0
  11. package/dist/src/autoloop/messages.js.map +1 -1
  12. package/dist/src/autoloop/runner.js +6 -0
  13. package/dist/src/autoloop/runner.js.map +1 -1
  14. package/dist/src/constants.d.ts +0 -6
  15. package/dist/src/constants.js +0 -6
  16. package/dist/src/constants.js.map +1 -1
  17. package/dist/src/council.d.ts +15 -0
  18. package/dist/src/council.js +48 -35
  19. package/dist/src/council.js.map +1 -1
  20. package/dist/src/dashboard/index.html +191 -6
  21. package/dist/src/embedded-server.js +132 -9
  22. package/dist/src/embedded-server.js.map +1 -1
  23. package/dist/src/fanout.d.ts +30 -1
  24. package/dist/src/fanout.js +32 -3
  25. package/dist/src/fanout.js.map +1 -1
  26. package/dist/src/index.d.ts +1 -0
  27. package/dist/src/index.js +360 -4
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/kernel/agent-step.d.ts +59 -0
  30. package/dist/src/kernel/agent-step.js +100 -0
  31. package/dist/src/kernel/agent-step.js.map +1 -0
  32. package/dist/src/kernel/conditions.d.ts +11 -0
  33. package/dist/src/kernel/conditions.js +24 -0
  34. package/dist/src/kernel/conditions.js.map +1 -0
  35. package/dist/src/kernel/engine.d.ts +319 -0
  36. package/dist/src/kernel/engine.js +1047 -0
  37. package/dist/src/kernel/engine.js.map +1 -0
  38. package/dist/src/kernel/exec.d.ts +43 -0
  39. package/dist/src/kernel/exec.js +112 -0
  40. package/dist/src/kernel/exec.js.map +1 -0
  41. package/dist/src/kernel/file-lock.d.ts +50 -0
  42. package/dist/src/kernel/file-lock.js +135 -0
  43. package/dist/src/kernel/file-lock.js.map +1 -0
  44. package/dist/src/kernel/nodes/agent.d.ts +4 -0
  45. package/dist/src/kernel/nodes/agent.js +35 -0
  46. package/dist/src/kernel/nodes/agent.js.map +1 -0
  47. package/dist/src/kernel/nodes/autoloop.d.ts +78 -0
  48. package/dist/src/kernel/nodes/autoloop.js +75 -0
  49. package/dist/src/kernel/nodes/autoloop.js.map +1 -0
  50. package/dist/src/kernel/nodes/council.d.ts +12 -0
  51. package/dist/src/kernel/nodes/council.js +88 -0
  52. package/dist/src/kernel/nodes/council.js.map +1 -0
  53. package/dist/src/kernel/nodes/fanout.d.ts +11 -0
  54. package/dist/src/kernel/nodes/fanout.js +63 -0
  55. package/dist/src/kernel/nodes/fanout.js.map +1 -0
  56. package/dist/src/kernel/nodes/human-gate.d.ts +4 -0
  57. package/dist/src/kernel/nodes/human-gate.js +7 -0
  58. package/dist/src/kernel/nodes/human-gate.js.map +1 -0
  59. package/dist/src/kernel/nodes/index.d.ts +12 -0
  60. package/dist/src/kernel/nodes/index.js +21 -0
  61. package/dist/src/kernel/nodes/index.js.map +1 -0
  62. package/dist/src/kernel/nodes/router.d.ts +4 -0
  63. package/dist/src/kernel/nodes/router.js +12 -0
  64. package/dist/src/kernel/nodes/router.js.map +1 -0
  65. package/dist/src/kernel/nodes/subflow.d.ts +13 -0
  66. package/dist/src/kernel/nodes/subflow.js +38 -0
  67. package/dist/src/kernel/nodes/subflow.js.map +1 -0
  68. package/dist/src/kernel/nodes/ultraapp.d.ts +60 -0
  69. package/dist/src/kernel/nodes/ultraapp.js +62 -0
  70. package/dist/src/kernel/nodes/ultraapp.js.map +1 -0
  71. package/dist/src/kernel/nodes/verifier.d.ts +14 -0
  72. package/dist/src/kernel/nodes/verifier.js +84 -0
  73. package/dist/src/kernel/nodes/verifier.js.map +1 -0
  74. package/dist/src/kernel/projections.d.ts +42 -0
  75. package/dist/src/kernel/projections.js +133 -0
  76. package/dist/src/kernel/projections.js.map +1 -0
  77. package/dist/src/kernel/repo.d.ts +13 -0
  78. package/dist/src/kernel/repo.js +64 -0
  79. package/dist/src/kernel/repo.js.map +1 -0
  80. package/dist/src/kernel/secrets.d.ts +25 -0
  81. package/dist/src/kernel/secrets.js +48 -0
  82. package/dist/src/kernel/secrets.js.map +1 -0
  83. package/dist/src/kernel/store.d.ts +225 -0
  84. package/dist/src/kernel/store.js +838 -0
  85. package/dist/src/kernel/store.js.map +1 -0
  86. package/dist/src/kernel/templates/index.d.ts +140 -0
  87. package/dist/src/kernel/templates/index.js +266 -0
  88. package/dist/src/kernel/templates/index.js.map +1 -0
  89. package/dist/src/kernel/types.d.ts +326 -0
  90. package/dist/src/kernel/types.js +19 -0
  91. package/dist/src/kernel/types.js.map +1 -0
  92. package/dist/src/models.d.ts +1 -1
  93. package/dist/src/models.js +31 -3
  94. package/dist/src/models.js.map +1 -1
  95. package/dist/src/persistent-cursor-session.js +6 -1
  96. package/dist/src/persistent-cursor-session.js.map +1 -1
  97. package/dist/src/persistent-grok-session.d.ts +40 -0
  98. package/dist/src/persistent-grok-session.js +197 -0
  99. package/dist/src/persistent-grok-session.js.map +1 -0
  100. package/dist/src/run-ledger.d.ts +57 -3
  101. package/dist/src/run-ledger.js +45 -2
  102. package/dist/src/run-ledger.js.map +1 -1
  103. package/dist/src/session-manager.d.ts +176 -129
  104. package/dist/src/session-manager.js +657 -603
  105. package/dist/src/session-manager.js.map +1 -1
  106. package/dist/src/types.d.ts +37 -4
  107. package/dist/src/types.js +15 -1
  108. package/dist/src/types.js.map +1 -1
  109. package/dist/src/ultraapp/build.d.ts +117 -3
  110. package/dist/src/ultraapp/build.js +319 -3
  111. package/dist/src/ultraapp/build.js.map +1 -1
  112. package/dist/src/ultraapp/contract.d.ts +52 -0
  113. package/dist/src/ultraapp/contract.js +83 -0
  114. package/dist/src/ultraapp/contract.js.map +1 -0
  115. package/dist/src/ultraapp/conventions.js +9 -2
  116. package/dist/src/ultraapp/conventions.js.map +1 -1
  117. package/dist/src/ultraapp/fix-on-failure.d.ts +21 -2
  118. package/dist/src/ultraapp/fix-on-failure.js +46 -62
  119. package/dist/src/ultraapp/fix-on-failure.js.map +1 -1
  120. package/dist/src/ultraapp/manager.d.ts +107 -2
  121. package/dist/src/ultraapp/manager.js +305 -86
  122. package/dist/src/ultraapp/manager.js.map +1 -1
  123. package/dist/src/verify/baseline.d.ts +73 -0
  124. package/dist/src/verify/baseline.js +186 -0
  125. package/dist/src/verify/baseline.js.map +1 -0
  126. package/dist/src/verify/contract.d.ts +116 -0
  127. package/dist/src/verify/contract.js +142 -0
  128. package/dist/src/verify/contract.js.map +1 -0
  129. package/dist/src/verify/evidence.d.ts +61 -0
  130. package/dist/src/verify/evidence.js +133 -0
  131. package/dist/src/verify/evidence.js.map +1 -0
  132. package/dist/src/verify/runner.d.ts +63 -0
  133. package/dist/src/verify/runner.js +317 -0
  134. package/dist/src/verify/runner.js.map +1 -0
  135. package/openclaw.plugin.json +8 -0
  136. package/package.json +2 -2
  137. package/skills/SKILL.md +121 -80
  138. package/skills/references/acp.md +18 -18
  139. package/skills/references/autoloop.md +148 -72
  140. package/skills/references/claude-cli-tracking.md +4 -4
  141. package/skills/references/cli.md +103 -60
  142. package/skills/references/council.md +109 -37
  143. package/skills/references/dashboard.md +34 -6
  144. package/skills/references/getting-started.md +14 -14
  145. package/skills/references/inbox.md +4 -4
  146. package/skills/references/mcp.md +39 -34
  147. package/skills/references/multi-engine.md +109 -51
  148. package/skills/references/observability.md +88 -27
  149. package/skills/references/openai-compat.md +40 -40
  150. package/skills/references/sessions.md +44 -26
  151. package/skills/references/tools.md +402 -309
  152. package/skills/references/ultra.md +45 -45
  153. package/skills/references/ultraapp.md +126 -50
  154. package/skills/references/verification.md +187 -0
  155. package/skills/references/workflow.md +362 -0
  156. package/dist/src/ultraapp/fix-on-failure-session.d.ts +0 -23
  157. package/dist/src/ultraapp/fix-on-failure-session.js +0 -51
  158. package/dist/src/ultraapp/fix-on-failure-session.js.map +0 -1
package/dist/src/index.js CHANGED
@@ -13,6 +13,9 @@ import { createProxyHandler } from './proxy/handler.js';
13
13
  import { EmbeddedServer } from './embedded-server.js';
14
14
  import { sanitizeCwd, validateRegex } from './validation.js';
15
15
  import { ENGINE_TYPES, } from './types.js';
16
+ import { councilWorkflow, fanoutWorkflow, solveWorkflow } from './kernel/templates/index.js';
17
+ import { readEvents as readKernelEvents } from './kernel/store.js';
18
+ import { resolveSecretRefs } from './kernel/secrets.js';
16
19
  // ─── Standalone Export ───────────────────────────────────────────────────────
17
20
  export { SessionManager } from './session-manager.js';
18
21
  export { PersistentClaudeSession } from './persistent-session.js';
@@ -21,6 +24,7 @@ export { PersistentCodexSession } from './persistent-codex-session.js';
21
24
  export { PersistentGeminiSession } from './persistent-gemini-session.js';
22
25
  export { PersistentAgySession } from './persistent-agy-session.js';
23
26
  export { PersistentCursorSession } from './persistent-cursor-session.js';
27
+ export { PersistentGrokSession } from './persistent-grok-session.js';
24
28
  export { PersistentOpencodeSession } from './persistent-opencode-session.js';
25
29
  export { PersistentCustomSession } from './persistent-custom-session.js';
26
30
  export { Council, getDefaultCouncilConfig } from './council.js';
@@ -939,7 +943,7 @@ const plugin = {
939
943
  required: ['task', 'projectDir', 'agents'],
940
944
  },
941
945
  execute: async (_id, args) => {
942
- const session = getManager().fanoutStart({
946
+ const session = await getManager().fanoutStart({
943
947
  task: args.task,
944
948
  projectDir: sanitizeCwd(args.projectDir),
945
949
  agents: args.agents,
@@ -976,6 +980,358 @@ const plugin = {
976
980
  return { ok: true };
977
981
  },
978
982
  });
983
+ // ─── Tools: workflow_* (durable run kernel) ─────────────────────────
984
+ api.registerTool({
985
+ name: 'workflow_start',
986
+ description: "Start a durable workflow run. Nodes: agent | fanout | council | verifier | human_gate | router | subflow. Every state transition is checkpointed to disk, so a run survives a process restart and can be resumed. When a contract is supplied the run cannot reach `completed` unless the runtime's own checks pass — without one it completes as `unverified`, which means nothing checked it. Runs in background; poll with workflow_status.",
987
+ parameters: {
988
+ type: 'object',
989
+ properties: {
990
+ spec: {
991
+ type: 'object',
992
+ description: 'WorkflowSpec: { name, nodes[], cwd?, contract?, maxNodeVisits? }. See skills/references/workflow.md for the node shapes.',
993
+ },
994
+ template: {
995
+ type: 'string',
996
+ enum: ['solve', 'council', 'fanout'],
997
+ description: 'Build a built-in workflow instead of supplying `spec`. `solve` = triage → implement → verify → repair-until-green → review.',
998
+ },
999
+ task: { type: 'string', description: 'Task text, when using `template`.' },
1000
+ agents: {
1001
+ type: 'array',
1002
+ maxItems: 16,
1003
+ description: 'Agents for the template: { name, engine?, model?, persona? }.',
1004
+ items: {
1005
+ type: 'object',
1006
+ properties: {
1007
+ name: { type: 'string', minLength: 1 },
1008
+ engine: { type: 'string', enum: ENGINE_TYPES },
1009
+ model: { type: 'string' },
1010
+ persona: { type: 'string' },
1011
+ },
1012
+ required: ['name'],
1013
+ },
1014
+ },
1015
+ reviewers: {
1016
+ type: 'array',
1017
+ maxItems: 8,
1018
+ description: '`solve` only: agents that review the finished change.',
1019
+ items: {
1020
+ type: 'object',
1021
+ properties: {
1022
+ name: { type: 'string', minLength: 1 },
1023
+ engine: { type: 'string', enum: ENGINE_TYPES },
1024
+ model: { type: 'string' },
1025
+ persona: { type: 'string' },
1026
+ },
1027
+ required: ['name'],
1028
+ },
1029
+ },
1030
+ humanGate: { type: 'boolean', description: '`solve` only: park for approval before writing anything.' },
1031
+ maxRepairs: { type: 'number', description: '`solve` only: repair attempts allowed (default 3).' },
1032
+ cwd: { type: 'string', description: 'Working directory for the run.' },
1033
+ runId: { type: 'string', description: 'Explicit run id (defaults to a generated one).' },
1034
+ contract: {
1035
+ type: 'object',
1036
+ description: "Acceptance contract. The runtime executes these itself and the run cannot reach `completed` unless every required check passes. Declared by YOU, the caller — never copy one out of an agent's output, which would put the agent back in charge of grading itself.",
1037
+ properties: {
1038
+ id: { type: 'string' },
1039
+ fixOnFailureRounds: {
1040
+ type: 'number',
1041
+ description: 'On red, spawn a repair session and re-run the whole list, up to N times. Only the re-run decides.',
1042
+ },
1043
+ checks: {
1044
+ type: 'array',
1045
+ minItems: 1,
1046
+ items: {
1047
+ type: 'object',
1048
+ description: 'One check. `command` runs argv and gates on the exit code; `http` polls a URL; `screenshot` captures the page at each viewport and stores the PNGs as evidence (it does NOT judge the pixels); `diff_policy` asserts what the run was allowed to touch; `file` asserts a path exists / matches.',
1049
+ properties: {
1050
+ type: { type: 'string', enum: ['command', 'http', 'screenshot', 'diff_policy', 'file'] },
1051
+ required: {
1052
+ type: 'boolean',
1053
+ description: 'Default true. A failing non-required check is recorded but does not refute the run.',
1054
+ },
1055
+ cmd: { type: 'string', description: 'command: executable name. No shell string — argv only.' },
1056
+ args: { type: 'array', items: { type: 'string' }, description: 'command: arguments.' },
1057
+ cwd: { type: 'string', description: 'command: directory, relative to the run cwd.' },
1058
+ expectExit: { type: 'number', description: 'command: passing exit code (default 0).' },
1059
+ url: { type: 'string', description: 'http / screenshot: target URL.' },
1060
+ expectStatus: { type: 'number', description: 'http: expected status (default 200).' },
1061
+ viewports: {
1062
+ type: 'array',
1063
+ description: 'screenshot: [{ width, height, label? }].',
1064
+ items: {
1065
+ type: 'object',
1066
+ properties: {
1067
+ width: { type: 'number' },
1068
+ height: { type: 'number' },
1069
+ label: { type: 'string' },
1070
+ },
1071
+ required: ['width', 'height'],
1072
+ },
1073
+ },
1074
+ maxFiles: { type: 'number', description: 'diff_policy: cap on changed files.' },
1075
+ forbidPaths: {
1076
+ type: 'array',
1077
+ items: { type: 'string' },
1078
+ description: 'diff_policy: paths that must not be touched.',
1079
+ },
1080
+ requirePaths: {
1081
+ type: 'array',
1082
+ items: { type: 'string' },
1083
+ description: 'diff_policy: at least one change must land under one of these.',
1084
+ },
1085
+ path: { type: 'string', description: 'file: path relative to the run cwd.' },
1086
+ exists: { type: 'boolean', description: 'file: default true; false asserts absence.' },
1087
+ matches: { type: 'string', description: 'file: regex the contents must match.' },
1088
+ timeoutMs: { type: 'number', description: 'Per-check wall clock.' },
1089
+ },
1090
+ required: ['type'],
1091
+ },
1092
+ },
1093
+ },
1094
+ required: ['checks'],
1095
+ },
1096
+ },
1097
+ required: [],
1098
+ },
1099
+ execute: async (_id, args) => {
1100
+ const cwd = sanitizeCwd(args.cwd);
1101
+ let spec = args.spec;
1102
+ if (!spec) {
1103
+ const template = args.template || 'solve';
1104
+ const task = args.task;
1105
+ if (!task)
1106
+ throw new Error('workflow_start needs `spec`, or `template` with `task`');
1107
+ const agents = args.agents ?? [{ name: 'agent' }];
1108
+ if (template === 'council') {
1109
+ spec = councilWorkflow({ task, cwd, agents });
1110
+ }
1111
+ else if (template === 'fanout') {
1112
+ spec = fanoutWorkflow({ task, cwd, agents, synthesize: agents.length >= 2 });
1113
+ }
1114
+ else {
1115
+ spec = solveWorkflow({
1116
+ task,
1117
+ cwd,
1118
+ scouts: agents,
1119
+ reviewers: args.reviewers,
1120
+ humanGate: args.humanGate,
1121
+ maxRepairs: args.maxRepairs,
1122
+ });
1123
+ }
1124
+ }
1125
+ const record = await getManager().workflowStart(spec, {
1126
+ cwd,
1127
+ runId: args.runId,
1128
+ contract: args.contract,
1129
+ });
1130
+ return {
1131
+ ok: true,
1132
+ runId: record.runId,
1133
+ workflow: record.workflow,
1134
+ state: record.state,
1135
+ nodes: Object.keys(record.nodes),
1136
+ note: 'Workflow running in background. Poll with workflow_status.',
1137
+ };
1138
+ },
1139
+ });
1140
+ api.registerTool({
1141
+ name: 'workflow_status',
1142
+ description: 'Poll a workflow run. Returns state, per-node status, and the outcome: `verified` (a contract passed), `refuted` (it failed), or `unverified` (none was declared — nothing checked the work).',
1143
+ parameters: {
1144
+ type: 'object',
1145
+ properties: {
1146
+ runId: { type: 'string' },
1147
+ events: { type: 'number', description: 'Also return the last N events from the run log.' },
1148
+ },
1149
+ required: ['runId'],
1150
+ },
1151
+ execute: async (_id, args) => {
1152
+ const record = getManager().workflowStatus(args.runId);
1153
+ const limit = args.events;
1154
+ return {
1155
+ ok: true,
1156
+ runId: record.runId,
1157
+ workflow: record.workflow,
1158
+ state: record.state,
1159
+ outcome: record.outcome,
1160
+ currentNode: record.currentNode,
1161
+ evidenceId: record.evidenceId,
1162
+ costUsd: record.costUsd,
1163
+ error: record.error,
1164
+ nodes: record.nodes,
1165
+ consensusVotes: record.consensusVotes,
1166
+ events: limit ? readKernelEvents(record.runId, limit) : undefined,
1167
+ };
1168
+ },
1169
+ });
1170
+ api.registerTool({
1171
+ name: 'workflow_list',
1172
+ description: 'List workflow runs on this machine, newest first. Survives restarts.',
1173
+ parameters: {
1174
+ type: 'object',
1175
+ properties: {
1176
+ workflow: { type: 'string' },
1177
+ state: {
1178
+ type: 'string',
1179
+ enum: ['pending', 'running', 'awaiting_human', 'verifying', 'completed', 'failed', 'cancelled'],
1180
+ },
1181
+ limit: { type: 'number' },
1182
+ },
1183
+ },
1184
+ execute: async (_id, args) => ({
1185
+ ok: true,
1186
+ runs: getManager().workflowList({
1187
+ workflow: args.workflow,
1188
+ state: args.state,
1189
+ limit: args.limit ?? 25,
1190
+ }),
1191
+ }),
1192
+ });
1193
+ api.registerTool({
1194
+ name: 'workflow_resume',
1195
+ description: 'Re-attach to a workflow whose process died. Nodes already marked succeeded are not re-run; the node that was in flight is retried, because a half-finished node left no result to trust.',
1196
+ parameters: {
1197
+ type: 'object',
1198
+ properties: {
1199
+ runId: { type: 'string' },
1200
+ agentCustomEngineRefs: {
1201
+ type: 'object',
1202
+ description: 'Per-agent custom-engine credentials, by name: { "<agent>": "<REF>" }. The orchestrator resolves each REF from CLAWO_CUSTOM_ENGINE_<REF> on its own host — the credentials themselves are never persisted with the run and never travel here. Required to resume a run whose agents used a custom engine.',
1203
+ additionalProperties: { type: 'string' },
1204
+ },
1205
+ },
1206
+ required: ['runId'],
1207
+ },
1208
+ execute: async (_id, args) => {
1209
+ const refs = args.agentCustomEngineRefs;
1210
+ const record = await getManager().workflowResume(args.runId, {
1211
+ secrets: refs ? { agentCustomEngines: resolveSecretRefs(refs) } : undefined,
1212
+ });
1213
+ return { ok: true, runId: record.runId, state: record.state, currentNode: record.currentNode };
1214
+ },
1215
+ });
1216
+ api.registerTool({
1217
+ name: 'workflow_cancel',
1218
+ description: 'Cancel a running workflow. The current node is asked to stop; the run ends in `cancelled`.',
1219
+ parameters: { type: 'object', properties: { runId: { type: 'string' } }, required: ['runId'] },
1220
+ execute: async (_id, args) => ({ ok: true, ...getManager().workflowCancel(args.runId) }),
1221
+ });
1222
+ api.registerTool({
1223
+ name: 'workflow_steer',
1224
+ description: "Queue a correction for a running workflow. The text is prepended to the next agent node's prompt — corrections belong before the task, not after it.",
1225
+ parameters: {
1226
+ type: 'object',
1227
+ properties: { runId: { type: 'string' }, text: { type: 'string' } },
1228
+ required: ['runId', 'text'],
1229
+ },
1230
+ execute: async (_id, args) => ({
1231
+ ok: true,
1232
+ ...getManager().workflowSteer(args.runId, args.text),
1233
+ }),
1234
+ });
1235
+ api.registerTool({
1236
+ name: 'workflow_approve',
1237
+ description: 'Answer a workflow parked at a human_gate node.',
1238
+ parameters: {
1239
+ type: 'object',
1240
+ properties: { runId: { type: 'string' }, approved: { type: 'boolean' } },
1241
+ required: ['runId', 'approved'],
1242
+ },
1243
+ execute: async (_id, args) => ({
1244
+ ok: true,
1245
+ ...getManager().workflowApprove(args.runId, args.approved),
1246
+ }),
1247
+ });
1248
+ api.registerTool({
1249
+ name: 'verify_run',
1250
+ description: 'Run an acceptance contract against a directory and return the evidence bundle. Use it to check work that did not come through a workflow — a plain session that edited a repo, say. The contract is yours; nothing is taken from agent output.',
1251
+ parameters: {
1252
+ type: 'object',
1253
+ properties: {
1254
+ cwd: { type: 'string', description: 'Directory to check.' },
1255
+ baseSha: {
1256
+ type: 'string',
1257
+ description: 'Commit to measure the change against. Without it, diff_policy sees untracked files only.',
1258
+ },
1259
+ label: { type: 'string', description: 'Name for the stored evidence (defaults to a generated one).' },
1260
+ contract: {
1261
+ type: 'object',
1262
+ description: "Acceptance contract. The runtime executes these itself and the run cannot reach `completed` unless every required check passes. Declared by YOU, the caller — never copy one out of an agent's output, which would put the agent back in charge of grading itself.",
1263
+ properties: {
1264
+ id: { type: 'string' },
1265
+ fixOnFailureRounds: {
1266
+ type: 'number',
1267
+ description: 'On red, spawn a repair session and re-run the whole list, up to N times. Only the re-run decides.',
1268
+ },
1269
+ checks: {
1270
+ type: 'array',
1271
+ minItems: 1,
1272
+ items: {
1273
+ type: 'object',
1274
+ description: 'One check. `command` runs argv and gates on the exit code; `http` polls a URL; `screenshot` captures the page at each viewport and stores the PNGs as evidence (it does NOT judge the pixels); `diff_policy` asserts what the run was allowed to touch; `file` asserts a path exists / matches.',
1275
+ properties: {
1276
+ type: { type: 'string', enum: ['command', 'http', 'screenshot', 'diff_policy', 'file'] },
1277
+ required: {
1278
+ type: 'boolean',
1279
+ description: 'Default true. A failing non-required check is recorded but does not refute the run.',
1280
+ },
1281
+ cmd: { type: 'string', description: 'command: executable name. No shell string — argv only.' },
1282
+ args: { type: 'array', items: { type: 'string' }, description: 'command: arguments.' },
1283
+ cwd: { type: 'string', description: 'command: directory, relative to the run cwd.' },
1284
+ expectExit: { type: 'number', description: 'command: passing exit code (default 0).' },
1285
+ url: { type: 'string', description: 'http / screenshot: target URL.' },
1286
+ expectStatus: { type: 'number', description: 'http: expected status (default 200).' },
1287
+ viewports: {
1288
+ type: 'array',
1289
+ description: 'screenshot: [{ width, height, label? }].',
1290
+ items: {
1291
+ type: 'object',
1292
+ properties: {
1293
+ width: { type: 'number' },
1294
+ height: { type: 'number' },
1295
+ label: { type: 'string' },
1296
+ },
1297
+ required: ['width', 'height'],
1298
+ },
1299
+ },
1300
+ maxFiles: { type: 'number', description: 'diff_policy: cap on changed files.' },
1301
+ forbidPaths: {
1302
+ type: 'array',
1303
+ items: { type: 'string' },
1304
+ description: 'diff_policy: paths that must not be touched.',
1305
+ },
1306
+ requirePaths: {
1307
+ type: 'array',
1308
+ items: { type: 'string' },
1309
+ description: 'diff_policy: at least one change must land under one of these.',
1310
+ },
1311
+ path: { type: 'string', description: 'file: path relative to the run cwd.' },
1312
+ exists: { type: 'boolean', description: 'file: default true; false asserts absence.' },
1313
+ matches: { type: 'string', description: 'file: regex the contents must match.' },
1314
+ timeoutMs: { type: 'number', description: 'Per-check wall clock.' },
1315
+ },
1316
+ required: ['type'],
1317
+ },
1318
+ },
1319
+ },
1320
+ required: ['checks'],
1321
+ },
1322
+ },
1323
+ required: ['cwd', 'contract'],
1324
+ },
1325
+ execute: async (_id, args) => {
1326
+ const bundle = await getManager().verifyRun({
1327
+ cwd: sanitizeCwd(args.cwd),
1328
+ contract: args.contract,
1329
+ baseSha: args.baseSha,
1330
+ label: args.label,
1331
+ });
1332
+ return { ok: true, ...bundle };
1333
+ },
1334
+ });
979
1335
  // ─── Tool: council_start ────────────────────────────────────────────
980
1336
  api.registerTool({
981
1337
  name: 'council_start',
@@ -1041,7 +1397,7 @@ const plugin = {
1041
1397
  maxBudgetUsd: args.maxBudgetUsd,
1042
1398
  defaultPermissionMode: args.defaultPermissionMode,
1043
1399
  };
1044
- const session = getManager().councilStart(args.task, config);
1400
+ const session = await getManager().councilStart(args.task, config);
1045
1401
  return { ok: true, ...session, note: 'Council running in background. Poll with council_status.' };
1046
1402
  },
1047
1403
  });
@@ -1205,7 +1561,7 @@ const plugin = {
1205
1561
  required: ['task'],
1206
1562
  },
1207
1563
  execute: async (_id, args) => {
1208
- const result = getManager().ultraplanStart(args.task, {
1564
+ const result = await getManager().ultraplanStart(args.task, {
1209
1565
  cwd: sanitizeCwd(args.cwd),
1210
1566
  model: args.model,
1211
1567
  timeout: args.timeout,
@@ -1253,7 +1609,7 @@ const plugin = {
1253
1609
  required: ['cwd'],
1254
1610
  },
1255
1611
  execute: async (_id, args) => {
1256
- const result = getManager().ultrareviewStart(sanitizeCwd(args.cwd), {
1612
+ const result = await getManager().ultrareviewStart(sanitizeCwd(args.cwd), {
1257
1613
  agentCount: args.agentCount,
1258
1614
  maxDurationMinutes: args.maxDurationMinutes,
1259
1615
  model: args.model,