@volter/supercode-orchestrator 0.5.228 → 0.5.229
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.
- package/README.md +18 -2
- package/activation.mjs +1 -1
- package/adapters/agent-mailbox.mjs +10 -0
- package/adapters/api-server.mjs +6 -0
- package/agents.mjs +149 -40
- package/apply/doors.mjs +3 -1
- package/bin/agent-channel.mjs +7 -2
- package/bin/connection-runner.mjs +60 -19
- package/bin/hermes-worker.mjs +2 -1
- package/bin/orchestrator.mjs +3 -3
- package/bin/runtime.mjs +3 -2
- package/board/agent-current.mjs +82 -0
- package/board/agent-declarations.mjs +10 -3
- package/board/args.mjs +137 -6
- package/board/call.mjs +16 -1
- package/board/caller-session.mjs +3 -1
- package/board/capacity.mjs +59 -45
- package/board/card-state.mjs +14 -5
- package/board/ci.mjs +521 -178
- package/board/cli.mjs +614 -209
- package/board/client.mjs +64 -21
- package/board/close-current.mjs +36 -0
- package/board/current-records.mjs +44 -0
- package/board/custody.mjs +2 -1
- package/board/dispatch.mjs +1973 -846
- package/board/doors.mjs +2 -2
- package/board/engine.mjs +623 -85
- package/board/event-stream.mjs +10 -5
- package/board/fence.mjs +2 -0
- package/board/files.mjs +93 -62
- package/board/generation-owner.mjs +188 -53
- package/board/git-document.mjs +3 -1
- package/board/github-rest.mjs +29 -7
- package/board/incidents.mjs +5 -17
- package/board/listing.mjs +14 -8
- package/board/local-admission.mjs +59 -0
- package/board/lock-holder.mjs +18 -6
- package/board/machine-fleet.mjs +66 -13
- package/board/owner-door-worker.mjs +3 -3
- package/board/owner-door.mjs +17 -7
- package/board/owner-health.mjs +12 -15
- package/board/owner-interface.mjs +6 -0
- package/board/publication-reader.mjs +50 -0
- package/board/published.mjs +40 -12
- package/board/queries.mjs +24 -4
- package/board/record-view.mjs +30 -3
- package/board/request-mail.mjs +37 -6
- package/board/resting-admission.mjs +318 -0
- package/board/resting-codec-qualifications.json +33 -0
- package/board/resting-protocol.mjs +71 -0
- package/board/resting-state-protocol.json +120 -0
- package/board/resting-state.mjs +113 -0
- package/board/runtime-door.mjs +2 -2
- package/board/session-continuity.mjs +127 -0
- package/board/session-work.mjs +25 -13
- package/board/standalone-declaration.mjs +24 -0
- package/board/storage-contract.mjs +129 -7
- package/board/store.mjs +73 -11
- package/board/tools.mjs +56 -13
- package/board/wake.mjs +18 -5
- package/board/workflow.mjs +284 -82
- package/board/workspace.mjs +86 -9
- package/board/ztrack.mjs +344 -91
- package/connection.mjs +72 -17
- package/gates.mjs +2 -1
- package/hand-off.mjs +4 -3
- package/hermes-cli.mjs +5 -3
- package/hermes-locks.mjs +5 -2
- package/hosted.mjs +2 -1
- package/index.d.ts +2 -0
- package/index.mjs +1 -1
- package/kanban-worker.mjs +63 -13
- package/loop.mjs +33 -0
- package/mcp/hermes-kanban-tools.json +109 -50
- package/mcp/server.mjs +7 -1
- package/ontology.d.ts +26 -0
- package/orchestration.mjs +15 -4
- package/package.json +19 -12
- package/session-recovery.mjs +2 -1
- package/worker-route.mjs +37 -19
package/loop.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { registerWorkService, closeWorkLedger } from '@volter/supercode-harness-sdk/work-ledger';
|
|
2
|
+
import { registerWorkOperations, measureWork } from "@volter/supercode-harness-sdk/work-ledger";
|
|
3
|
+
registerWorkOperations([{"kind":"pass","operation":"gateway.save"},{"kind":"pass","operation":"gateway.jobs"},{"kind":"pass","operation":"gateway.tick"},{"kind":"pass","operation":"gateway.apply"},{"kind":"pass","operation":"gateway.effect"},{"kind":"call","operation":"gateway.input"},{"kind":"call","operation":"gateway.delivery"}]);
|
|
1
4
|
// The orchestrator loop: events in, `step` in the middle, effects out over the
|
|
2
5
|
// SDK and the adapters (docs/ORCHESTRATOR-IR.md §4–§5, §7).
|
|
3
6
|
//
|
|
@@ -58,6 +61,7 @@ export class Orchestrator {
|
|
|
58
61
|
* @param {(level, message) => void} [o.log]
|
|
59
62
|
*/
|
|
60
63
|
constructor({ state, vault = new Map(), client, adapters = {}, clock = null, launchFor = null, executeJob = null, log = () => {} }) {
|
|
64
|
+
registerWorkService({ role: 'orchestrator-gateway', scope: {kind:'install',id:state.root} });
|
|
61
65
|
this.state = state;
|
|
62
66
|
this.executeJob = executeJob;
|
|
63
67
|
this.hostedTasks = new Set();
|
|
@@ -150,6 +154,7 @@ export class Orchestrator {
|
|
|
150
154
|
|
|
151
155
|
/** The folder written from state, and each profile's `cron/jobs.json` as that write left it. */
|
|
152
156
|
async save() {
|
|
157
|
+
return measureWork("pass", "gateway.save", async () => {
|
|
153
158
|
const { rejected } = await save(this.state, this.state.root);
|
|
154
159
|
// An entry the store could not read was written as the home last held it (or, never held, left out), and is again
|
|
155
160
|
// at every save while it stays as it is; the log names it once per reason, not once per save.
|
|
@@ -164,6 +169,9 @@ export class Orchestrator {
|
|
|
164
169
|
: `${collection} ${key} could not be saved (${reason}): it is left out of the home, and the orchestrator will restart without it`);
|
|
165
170
|
}
|
|
166
171
|
this.jobsWritten = this.jobsFiles();
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
});
|
|
167
175
|
}
|
|
168
176
|
|
|
169
177
|
jobsFiles() {
|
|
@@ -184,6 +192,7 @@ export class Orchestrator {
|
|
|
184
192
|
* every other job keeps this daemon's own state, so a change not yet saved here survives the read.
|
|
185
193
|
*/
|
|
186
194
|
async adoptOutsideJobs() {
|
|
195
|
+
return measureWork("pass", "gateway.jobs", async () => {
|
|
187
196
|
const now = this.jobsFiles();
|
|
188
197
|
const changed = [...now].filter(([path, text]) => this.jobsWritten.has(path) && this.jobsWritten.get(path) !== text).map(([path]) => path);
|
|
189
198
|
if (!changed.length) { this.jobsWritten = now; return; }
|
|
@@ -202,15 +211,22 @@ export class Orchestrator {
|
|
|
202
211
|
this.log('info', `profile ${name}: cron/jobs.json changed outside the daemon; jobs read back: ${touched.join(', ') || 'none'}`);
|
|
203
212
|
}
|
|
204
213
|
this.jobsWritten = now;
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
});
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
/** One clock tick: expiry, due cron jobs, obligation retry, breaker windows (§4.3). */
|
|
208
220
|
tick(at = this.clock()) {
|
|
221
|
+
return measureWork("pass", "gateway.tick", () => {
|
|
209
222
|
this.queue = this.queue.then(() => this.adoptOutsideJobs()).catch((error) => this.log('warn', `cron/jobs.json not read back: ${error.message}`));
|
|
210
223
|
return this.dispatch(events.tick(at, cronInactivitySeconds(), cronMaxParallel())).then(
|
|
211
224
|
(result) => { this.tickerHeartbeat(true); void this.declareLocalAgents().catch((error) => this.log('warn', `agent hand-off: ${error.message}`));return result; },
|
|
212
225
|
(error) => { this.tickerHeartbeat(false); throw error; },
|
|
213
226
|
);
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
});
|
|
214
230
|
}
|
|
215
231
|
|
|
216
232
|
/** The home's agent declarations and ends a launch could not hand to this machine's daemon (none was running) are
|
|
@@ -280,6 +296,7 @@ export class Orchestrator {
|
|
|
280
296
|
this.retired.clear();
|
|
281
297
|
for (const adapter of Object.values(this.adapters)) await attempt('adapter', () => adapter.close?.());
|
|
282
298
|
await attempt('World reference', () => releaseWorld());
|
|
299
|
+
await closeWorkLedger();
|
|
283
300
|
if (this.stopErrors.length) throw new AggregateError(this.stopErrors, this.stopErrors.map(error => error.message).join('; '));
|
|
284
301
|
}
|
|
285
302
|
|
|
@@ -380,6 +397,7 @@ export class Orchestrator {
|
|
|
380
397
|
}
|
|
381
398
|
|
|
382
399
|
async apply(event) {
|
|
400
|
+
return measureWork("pass", "gateway.apply", async () => {
|
|
383
401
|
if (event.type === 'webhook' && this.webhookAdmissions.get(event.admission_id)?.outcome?.error) return [];
|
|
384
402
|
if (event.type === 'worker' && ['error', 'ended'].includes(event.ev.kind)) {
|
|
385
403
|
const binding = this.state.profiles[event.profile]?.bindings?.[event.keyString];
|
|
@@ -423,9 +441,13 @@ export class Orchestrator {
|
|
|
423
441
|
}
|
|
424
442
|
}
|
|
425
443
|
return effects;
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
});
|
|
426
447
|
}
|
|
427
448
|
|
|
428
449
|
async perform(effect) {
|
|
450
|
+
return measureWork("pass", "gateway.effect", async () => {
|
|
429
451
|
switch (effect.type) {
|
|
430
452
|
case 'webhook_input': {
|
|
431
453
|
const admission = this.webhookAdmissions.get(effect.admission_id);
|
|
@@ -489,6 +511,9 @@ export class Orchestrator {
|
|
|
489
511
|
case 'refused': this.log('info', effect.message); return;
|
|
490
512
|
default: this.log('warn', `unknown effect ${effect.type}`);
|
|
491
513
|
}
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
});
|
|
492
517
|
}
|
|
493
518
|
|
|
494
519
|
executeHostedJob({ profile: name, job_id, fire_id, machine = null }) {
|
|
@@ -1046,6 +1071,7 @@ export class Orchestrator {
|
|
|
1046
1071
|
}
|
|
1047
1072
|
|
|
1048
1073
|
async sendInput({ profile, keyString, text, context_from = null, skills = [], job_id = null, history = null, webhook_input_id = null }) {
|
|
1074
|
+
return measureWork("call", "gateway.input", async () => {
|
|
1049
1075
|
const admission = webhook_input_id ? this.webhookAdmissions.get(webhook_input_id) : null;
|
|
1050
1076
|
// Shutdown refused this unsent request (or its handler already failed).
|
|
1051
1077
|
// It must not be delivered later by a queued started/final completion.
|
|
@@ -1079,6 +1105,9 @@ export class Orchestrator {
|
|
|
1079
1105
|
this.enqueue(events.worker({ profile, keyString, runtime_id: runtime.handle.runtime_id, ev: { kind: 'error', message: `send failed: ${error.message}` }, at: this.clock() }));
|
|
1080
1106
|
admission?.finish(error);
|
|
1081
1107
|
}
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
});
|
|
1082
1111
|
}
|
|
1083
1112
|
|
|
1084
1113
|
/**
|
|
@@ -1150,6 +1179,7 @@ export class Orchestrator {
|
|
|
1150
1179
|
* open it; any success closes it.
|
|
1151
1180
|
*/
|
|
1152
1181
|
async deliver({ profile: profileName, obligation_id }) {
|
|
1182
|
+
return measureWork("call", "gateway.delivery", async () => {
|
|
1153
1183
|
const profile = this.state.profiles[profileName];
|
|
1154
1184
|
const o = profile.obligations.find((x) => x.id === obligation_id);
|
|
1155
1185
|
if (!o || o.state !== 'pending') return;
|
|
@@ -1195,6 +1225,9 @@ export class Orchestrator {
|
|
|
1195
1225
|
if (o.attempts >= MAX_ATTEMPTS) { o.state = 'failed'; this.recordDelivery(profile, o, error.message); }
|
|
1196
1226
|
}
|
|
1197
1227
|
await this.save();
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
});
|
|
1198
1231
|
}
|
|
1199
1232
|
|
|
1200
1233
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
3
|
"name": "kanban_show",
|
|
4
|
-
"description": "Read a task's full state
|
|
4
|
+
"description": "Read a task's full state — title, body, assignee, parent task handoffs, your prior attempts on this task if any, comments, and recent events. Use this to (re)orient yourself before starting work, especially on retries. The response includes a pre-formatted ``worker_context`` string suitable for inclusion verbatim in your reasoning.",
|
|
5
5
|
"inputSchema": {
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"board": {
|
|
13
13
|
"type": "string",
|
|
14
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
14
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"required": []
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"name": "kanban_list",
|
|
22
|
-
"description": "List Kanban task summaries so an orchestrator profile can discover work to route. Supports the same core filters as the CLI: assignee, status, tenant, include_archived, and limit. Returns compact rows with ids, title, status, assignee, priority, parent/child ids, and counts. Bounded to 50 rows by default, 200 max, with truncation metadata. Also recomputes ready tasks before listing, matching the CLI. Orchestrator-only
|
|
22
|
+
"description": "List Kanban task summaries so an orchestrator profile can discover work to route. Supports the same core filters as the CLI: assignee, status, tenant, include_archived, and limit. Returns compact rows with ids, title, status, assignee, priority, parent/child ids, and counts. Bounded to 50 rows by default, 200 max, with truncation metadata. Also recomputes ready tasks before listing, matching the CLI. Orchestrator-only — dispatcher-spawned task workers never see this tool.",
|
|
23
23
|
"inputSchema": {
|
|
24
24
|
"type": "object",
|
|
25
25
|
"properties": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"board": {
|
|
60
60
|
"type": "string",
|
|
61
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
61
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
64
|
"required": []
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
"name": "kanban_complete",
|
|
69
|
-
"description": "Complete through the home's declared workflow permissions with a structured handoff. Homes may reserve terminal closure to the recorded creator or manager; use kanban_request_state when you need their decision. Prefer ``summary`` for a human-readable 1-3 sentence description of what you did; put machine-readable facts in ``metadata`` (changed_files, tests_run, decisions, findings, etc). At least one of ``summary`` or ``result`` is required. If you created new tasks via ``kanban_create`` during this run, list their ids in ``created_cards``
|
|
69
|
+
"description": "Complete through the home's declared workflow permissions with a structured handoff. Homes may reserve terminal closure to the recorded creator or manager; use kanban_request_state when you need their decision. Prefer ``summary`` for a human-readable 1-3 sentence description of what you did; put machine-readable facts in ``metadata`` (changed_files, tests_run, decisions, findings, etc). At least one of ``summary`` or ``result`` is required. If you created new tasks via ``kanban_create`` during this run, list their ids in ``created_cards`` — the kernel verifies them so phantom references are caught before they leak into downstream automation. If you produced deliverable files (charts, PDFs, spreadsheets, generated images), list their absolute paths in ``artifacts`` — the gateway notifier will upload them as native attachments to the human who subscribed to the task, so the deliverable lands in their chat alongside the summary instead of being a path they have to fetch by hand.",
|
|
70
70
|
"inputSchema": {
|
|
71
71
|
"type": "object",
|
|
72
72
|
"properties": {
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
},
|
|
81
81
|
"metadata": {
|
|
82
82
|
"type": "object",
|
|
83
|
-
"description": "Free-form dict of structured facts about this attempt
|
|
83
|
+
"description": "Free-form dict of structured facts about this attempt — {\"changed_files\": [...], \"tests_run\": 12, \"findings\": [...]}. Surfaced to downstream workers alongside ``summary``."
|
|
84
84
|
},
|
|
85
85
|
"result": {
|
|
86
86
|
"type": "string",
|
|
@@ -91,18 +91,18 @@
|
|
|
91
91
|
"items": {
|
|
92
92
|
"type": "string"
|
|
93
93
|
},
|
|
94
|
-
"description": "Optional structured manifest of task ids you created via ``kanban_create`` during this run. The kernel verifies each id exists and was created by this worker's profile; any phantom id blocks the completion with an error listing what went wrong (auditable in the task's events). Only list ids you got back from a successful ``kanban_create`` call
|
|
94
|
+
"description": "Optional structured manifest of task ids you created via ``kanban_create`` during this run. The kernel verifies each id exists and was created by this worker's profile; any phantom id blocks the completion with an error listing what went wrong (auditable in the task's events). Only list ids you got back from a successful ``kanban_create`` call — do not invent or remember ids from prose. Omit the field if you did not create any cards."
|
|
95
95
|
},
|
|
96
96
|
"artifacts": {
|
|
97
97
|
"type": "array",
|
|
98
98
|
"items": {
|
|
99
99
|
"type": "string"
|
|
100
100
|
},
|
|
101
|
-
"description": "Optional list of absolute paths to deliverable files you produced during this run
|
|
101
|
+
"description": "Optional list of absolute paths to deliverable files you produced during this run — generated charts, PDFs, spreadsheets, images, archives. Examples: [\"/tmp/q3-revenue.png\", \"/tmp/report.pdf\"]. The gateway notifier uploads each path as a native attachment to the subscribed chat (images embed inline, everything else uploads as a file) so the deliverable lands with the completion notification. Skip intermediate scratch files and references that are not the deliverable. The path must exist on disk at completion. Files inside a managed scratch workspace are copied to durable task attachments before cleanup; a missing declared scratch artifact keeps the task in-flight so you can fix the path and retry."
|
|
102
102
|
},
|
|
103
103
|
"board": {
|
|
104
104
|
"type": "string",
|
|
105
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
105
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
106
106
|
},
|
|
107
107
|
"commit": {
|
|
108
108
|
"type": "string",
|
|
@@ -140,21 +140,19 @@
|
|
|
140
140
|
},
|
|
141
141
|
"waiting_on": {
|
|
142
142
|
"type": "string",
|
|
143
|
-
"description": "The id of the message (m
|
|
143
|
+
"description": "The id of the message (m-… or a-…) whose answer this waits for."
|
|
144
144
|
},
|
|
145
145
|
"board": {
|
|
146
146
|
"type": "string",
|
|
147
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
147
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
148
148
|
}
|
|
149
149
|
},
|
|
150
|
-
"required": [
|
|
151
|
-
"reason"
|
|
152
|
-
]
|
|
150
|
+
"required": []
|
|
153
151
|
}
|
|
154
152
|
},
|
|
155
153
|
{
|
|
156
154
|
"name": "kanban_request_review",
|
|
157
|
-
"description": "Request independent review of
|
|
155
|
+
"description": "Request independent review of one ready PR at its exact pushed head while coding and other tasks continue. A request during review queues after the current review. Explicit supersedes corrects one named queued request after verifying its replacement, preserving its receipt and the active review. Missing source details come from recorded board facts when available. Only the board starts the final arc review.",
|
|
158
156
|
"inputSchema": {
|
|
159
157
|
"type": "object",
|
|
160
158
|
"properties": {
|
|
@@ -164,12 +162,17 @@
|
|
|
164
162
|
},
|
|
165
163
|
"summary": {
|
|
166
164
|
"type": "string",
|
|
167
|
-
"description": "What was implemented and how it was verified, in one or two sentences
|
|
165
|
+
"description": "What was implemented and how it was verified, in one or two sentences — shown to the reviewer. Don't paste the whole diff; the reviewer has the board and the PR."
|
|
168
166
|
},
|
|
169
167
|
"reviewer": {
|
|
170
168
|
"type": "string",
|
|
171
169
|
"description": "Optional reviewer profile. When provided, the task is reassigned to that profile before review dispatch."
|
|
172
170
|
},
|
|
171
|
+
"supersedes": {
|
|
172
|
+
"type": "integer",
|
|
173
|
+
"minimum": 1,
|
|
174
|
+
"description": "Optional pending review_queued event ID on this card to supersede with the supplied ready PR/head. Fresh admission must succeed before only that request is settled; the active review stays intact. The replacement keeps the original FIFO position."
|
|
175
|
+
},
|
|
173
176
|
"metadata": {
|
|
174
177
|
"type": "object",
|
|
175
178
|
"description": "Optional structured handoff facts for the reviewer, such as changed_files, tests_run, commit, or decisions.",
|
|
@@ -177,7 +180,7 @@
|
|
|
177
180
|
},
|
|
178
181
|
"board": {
|
|
179
182
|
"type": "string",
|
|
180
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
183
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
181
184
|
},
|
|
182
185
|
"commit": {
|
|
183
186
|
"type": "string",
|
|
@@ -191,7 +194,7 @@
|
|
|
191
194
|
"items": {
|
|
192
195
|
"type": "string"
|
|
193
196
|
},
|
|
194
|
-
"description": "
|
|
197
|
+
"description": "The ready PR URL to review. Defaults to the sole recorded PR when omitted."
|
|
195
198
|
},
|
|
196
199
|
"pr_head": {
|
|
197
200
|
"type": [
|
|
@@ -208,9 +211,7 @@
|
|
|
208
211
|
"description": "Ask the plan review of a big arc's proposed ADR (its PR in pr, its pushed head in pr_head) before any code. Without it, one PR named with its head is that PR's review."
|
|
209
212
|
}
|
|
210
213
|
},
|
|
211
|
-
"required": [
|
|
212
|
-
"summary"
|
|
213
|
-
]
|
|
214
|
+
"required": []
|
|
214
215
|
}
|
|
215
216
|
},
|
|
216
217
|
{
|
|
@@ -229,7 +230,7 @@
|
|
|
229
230
|
},
|
|
230
231
|
"board": {
|
|
231
232
|
"type": "string",
|
|
232
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
233
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
233
234
|
},
|
|
234
235
|
"findings": {
|
|
235
236
|
"type": "array",
|
|
@@ -239,14 +240,12 @@
|
|
|
239
240
|
"description": "Each finding, filed as a task on the arc before the card returns; an arc with no open task is refused."
|
|
240
241
|
}
|
|
241
242
|
},
|
|
242
|
-
"required": [
|
|
243
|
-
"reason"
|
|
244
|
-
]
|
|
243
|
+
"required": []
|
|
245
244
|
}
|
|
246
245
|
},
|
|
247
246
|
{
|
|
248
247
|
"name": "kanban_heartbeat",
|
|
249
|
-
"description": "
|
|
248
|
+
"description": "Record progress and optionally a waiting deadline for this run. A deadline changes no card lane and ends no run; idle supervision honors it until expiry.",
|
|
250
249
|
"inputSchema": {
|
|
251
250
|
"type": "object",
|
|
252
251
|
"properties": {
|
|
@@ -260,7 +259,14 @@
|
|
|
260
259
|
},
|
|
261
260
|
"board": {
|
|
262
261
|
"type": "string",
|
|
263
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
262
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
263
|
+
},
|
|
264
|
+
"until": {
|
|
265
|
+
"type": [
|
|
266
|
+
"string",
|
|
267
|
+
"number"
|
|
268
|
+
],
|
|
269
|
+
"description": "Optional waiting deadline: an ISO time, a duration such as 30m, or epoch seconds."
|
|
264
270
|
}
|
|
265
271
|
},
|
|
266
272
|
"required": []
|
|
@@ -268,13 +274,13 @@
|
|
|
268
274
|
},
|
|
269
275
|
{
|
|
270
276
|
"name": "kanban_comment",
|
|
271
|
-
"description": "Append a comment to a task's thread. Use for durable notes that should outlive this run (questions for the next worker, partial findings, rationale). Ephemeral reasoning doesn't belong here
|
|
277
|
+
"description": "Append a comment to a task's thread. Use for durable notes that should outlive this run (questions for the next worker, partial findings, rationale). Ephemeral reasoning doesn't belong here — use your normal response instead.",
|
|
272
278
|
"inputSchema": {
|
|
273
279
|
"type": "object",
|
|
274
280
|
"properties": {
|
|
275
281
|
"task_id": {
|
|
276
282
|
"type": "string",
|
|
277
|
-
"description": "Task id. Required (may be your own task or another's
|
|
283
|
+
"description": "Task id. Required (may be your own task or another's — comment threads are per-task)."
|
|
278
284
|
},
|
|
279
285
|
"body": {
|
|
280
286
|
"type": "string",
|
|
@@ -282,7 +288,55 @@
|
|
|
282
288
|
},
|
|
283
289
|
"board": {
|
|
284
290
|
"type": "string",
|
|
285
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
291
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
292
|
+
},
|
|
293
|
+
"pr": {
|
|
294
|
+
"type": "string",
|
|
295
|
+
"description": "Exact GitHub PR URL for a source or PR evidence declaration."
|
|
296
|
+
},
|
|
297
|
+
"pr_head": {
|
|
298
|
+
"type": "string",
|
|
299
|
+
"pattern": "^[a-f0-9]{40}$"
|
|
300
|
+
},
|
|
301
|
+
"release_target": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"description": "Intended exact target for a release-only disposition."
|
|
304
|
+
},
|
|
305
|
+
"pr_disposition": {
|
|
306
|
+
"type": "string",
|
|
307
|
+
"enum": [
|
|
308
|
+
"merge",
|
|
309
|
+
"release-only"
|
|
310
|
+
],
|
|
311
|
+
"description": "Implementer/manager exact-head disposition. Default merge. Does not grant release permission."
|
|
312
|
+
},
|
|
313
|
+
"reason": {
|
|
314
|
+
"type": "string"
|
|
315
|
+
},
|
|
316
|
+
"release_readback": {
|
|
317
|
+
"type": "object",
|
|
318
|
+
"properties": {
|
|
319
|
+
"target": {
|
|
320
|
+
"type": "string"
|
|
321
|
+
},
|
|
322
|
+
"artifact": {
|
|
323
|
+
"type": "string",
|
|
324
|
+
"pattern": "^[a-f0-9]{40}$"
|
|
325
|
+
},
|
|
326
|
+
"evidence": {
|
|
327
|
+
"type": "string"
|
|
328
|
+
},
|
|
329
|
+
"attest": {
|
|
330
|
+
"type": "boolean",
|
|
331
|
+
"description": "Explicit manager attestation for a target without a machine verifier."
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
"required": [
|
|
335
|
+
"target",
|
|
336
|
+
"artifact",
|
|
337
|
+
"evidence"
|
|
338
|
+
],
|
|
339
|
+
"additionalProperties": false
|
|
286
340
|
}
|
|
287
341
|
},
|
|
288
342
|
"required": [
|
|
@@ -293,7 +347,7 @@
|
|
|
293
347
|
},
|
|
294
348
|
{
|
|
295
349
|
"name": "kanban_attach",
|
|
296
|
-
"description": "Attach a file to a task by passing its bytes inline (base64). Use for genuine file artifacts the next worker or a human should be able to download
|
|
350
|
+
"description": "Attach a file to a task by passing its bytes inline (base64). Use for genuine file artifacts the next worker or a human should be able to download — generated reports, images, exports. The file is stored as a real attachment (not a comment link) under the task's attachments dir, capped at 25 MB. Prefer kanban_attach_url when you only have a URL.",
|
|
297
351
|
"inputSchema": {
|
|
298
352
|
"type": "object",
|
|
299
353
|
"properties": {
|
|
@@ -315,7 +369,7 @@
|
|
|
315
369
|
},
|
|
316
370
|
"board": {
|
|
317
371
|
"type": "string",
|
|
318
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
372
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
319
373
|
}
|
|
320
374
|
},
|
|
321
375
|
"required": [
|
|
@@ -326,7 +380,7 @@
|
|
|
326
380
|
},
|
|
327
381
|
{
|
|
328
382
|
"name": "kanban_attach_url",
|
|
329
|
-
"description": "Attach a file to a task by URL
|
|
383
|
+
"description": "Attach a file to a task by URL — Hermes downloads it server-side and stores it as a real attachment (capped at 25 MB). Use when you have a link rather than the bytes. Only http/https URLs are accepted.",
|
|
330
384
|
"inputSchema": {
|
|
331
385
|
"type": "object",
|
|
332
386
|
"properties": {
|
|
@@ -348,7 +402,7 @@
|
|
|
348
402
|
},
|
|
349
403
|
"board": {
|
|
350
404
|
"type": "string",
|
|
351
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
405
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
352
406
|
}
|
|
353
407
|
},
|
|
354
408
|
"required": [
|
|
@@ -388,7 +442,7 @@
|
|
|
388
442
|
},
|
|
389
443
|
"board": {
|
|
390
444
|
"type": "string",
|
|
391
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
445
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
392
446
|
}
|
|
393
447
|
},
|
|
394
448
|
"required": []
|
|
@@ -396,7 +450,7 @@
|
|
|
396
450
|
},
|
|
397
451
|
{
|
|
398
452
|
"name": "kanban_create",
|
|
399
|
-
"description": "Create a new kanban task, optionally as a child of the current one (pass the current task id in ``parents``). Used by orchestrator workers to fan out
|
|
453
|
+
"description": "Create a new kanban task, optionally as a child of the current one (pass the current task id in ``parents``). Used by orchestrator workers to fan out — decompose work into child tasks with specific assignees, link them into a pipeline, then complete your own task. The dispatcher picks up the new tasks on its next tick and spawns the assigned profiles.",
|
|
400
454
|
"inputSchema": {
|
|
401
455
|
"type": "object",
|
|
402
456
|
"properties": {
|
|
@@ -410,7 +464,7 @@
|
|
|
410
464
|
},
|
|
411
465
|
"assignee": {
|
|
412
466
|
"type": "string",
|
|
413
|
-
"description": "Profile name that should execute this task (e.g. 'researcher-a', 'reviewer', 'writer'). Required
|
|
467
|
+
"description": "Profile name that should execute this task (e.g. 'researcher-a', 'reviewer', 'writer'). Required — tasks without an assignee are never dispatched."
|
|
414
468
|
},
|
|
415
469
|
"body": {
|
|
416
470
|
"type": "string",
|
|
@@ -450,7 +504,7 @@
|
|
|
450
504
|
},
|
|
451
505
|
"triage": {
|
|
452
506
|
"type": "boolean",
|
|
453
|
-
"description": "If true, task lands in 'triage' instead of 'todo'
|
|
507
|
+
"description": "If true, task lands in 'triage' instead of 'todo' — a specifier profile is expected to flesh out the body before work starts."
|
|
454
508
|
},
|
|
455
509
|
"idempotency_key": {
|
|
456
510
|
"type": "string",
|
|
@@ -473,7 +527,7 @@
|
|
|
473
527
|
"items": {
|
|
474
528
|
"type": "string"
|
|
475
529
|
},
|
|
476
|
-
"description": "Skill names to force-load into the dispatched worker. The kanban lifecycle is already injected automatically; use this to pin a task to a specialist context
|
|
530
|
+
"description": "Skill names to force-load into the dispatched worker. The kanban lifecycle is already injected automatically; use this to pin a task to a specialist context — e.g. ['translation'] for a translation task, ['github-code-review'] for a reviewer task. The names must match skills installed on the assignee's profile."
|
|
477
531
|
},
|
|
478
532
|
"goal_mode": {
|
|
479
533
|
"type": "boolean",
|
|
@@ -489,11 +543,11 @@
|
|
|
489
543
|
},
|
|
490
544
|
"provider": {
|
|
491
545
|
"type": "string",
|
|
492
|
-
"description": "Provider the 'model' belongs to (e.g. 'openrouter', 'anthropic', 'nous'). Set this whenever the model is not from the assignee profile's configured provider
|
|
546
|
+
"description": "Provider the 'model' belongs to (e.g. 'openrouter', 'anthropic', 'nous'). Set this whenever the model is not from the assignee profile's configured provider — a model name alone is resolved against the profile's provider and will fail if it belongs to a different one. Requires 'model'."
|
|
493
547
|
},
|
|
494
548
|
"board": {
|
|
495
549
|
"type": "string",
|
|
496
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
550
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
497
551
|
}
|
|
498
552
|
},
|
|
499
553
|
"required": [
|
|
@@ -505,7 +559,7 @@
|
|
|
505
559
|
},
|
|
506
560
|
{
|
|
507
561
|
"name": "kanban_unblock",
|
|
508
|
-
"description": "
|
|
562
|
+
"description": "Lift a named blocker or all current blockers when on is omitted, through the home workflow. A missing reason records none given. The worker may use this for its own card when its workflow permits it.",
|
|
509
563
|
"inputSchema": {
|
|
510
564
|
"type": "object",
|
|
511
565
|
"properties": {
|
|
@@ -515,17 +569,23 @@
|
|
|
515
569
|
},
|
|
516
570
|
"board": {
|
|
517
571
|
"type": "string",
|
|
518
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
572
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
573
|
+
},
|
|
574
|
+
"on": {
|
|
575
|
+
"type": "string",
|
|
576
|
+
"description": "Optional blocker id; omitted means all current blockers."
|
|
577
|
+
},
|
|
578
|
+
"reason": {
|
|
579
|
+
"type": "string",
|
|
580
|
+
"description": "What arrived; omitted records none given."
|
|
519
581
|
}
|
|
520
582
|
},
|
|
521
|
-
"required": [
|
|
522
|
-
"task_id"
|
|
523
|
-
]
|
|
583
|
+
"required": []
|
|
524
584
|
}
|
|
525
585
|
},
|
|
526
586
|
{
|
|
527
587
|
"name": "kanban_link",
|
|
528
|
-
"description": "Add a parent
|
|
588
|
+
"description": "Add a parent→child dependency edge after both tasks already exist. The child won't promote to 'ready' until all parents are 'done'. Cycles and self-links are rejected.",
|
|
529
589
|
"inputSchema": {
|
|
530
590
|
"type": "object",
|
|
531
591
|
"properties": {
|
|
@@ -539,7 +599,7 @@
|
|
|
539
599
|
},
|
|
540
600
|
"board": {
|
|
541
601
|
"type": "string",
|
|
542
|
-
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env
|
|
602
|
+
"description": "Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call."
|
|
543
603
|
}
|
|
544
604
|
},
|
|
545
605
|
"required": [
|
|
@@ -626,8 +686,7 @@
|
|
|
626
686
|
}
|
|
627
687
|
},
|
|
628
688
|
"required": [
|
|
629
|
-
"state"
|
|
630
|
-
"reason"
|
|
689
|
+
"state"
|
|
631
690
|
]
|
|
632
691
|
}
|
|
633
692
|
}
|
package/mcp/server.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { registerWorkOperations, measureWork } from "@volter/supercode-harness-sdk/work-ledger";
|
|
3
|
+
registerWorkOperations([{"kind":"door","operation":"orchestrator.mcp"}]);
|
|
2
4
|
// The orchestrator's model-facing door: ONE stdio MCP server per worker
|
|
3
5
|
// session, bound to the conversation that spawned it (docs/ORCHESTRATOR-IR.md
|
|
4
6
|
// §7).
|
|
@@ -99,6 +101,7 @@ export function ask(binding, verb, args) {
|
|
|
99
101
|
|
|
100
102
|
/** `crates/harness/src/mcp.rs::handle_request`'s shape, in JavaScript. */
|
|
101
103
|
export async function handleRequest(binding, request) {
|
|
104
|
+
return measureWork("door", "orchestrator.mcp", async () => {
|
|
102
105
|
await knowLocalMachine();
|
|
103
106
|
const id = request?.id;
|
|
104
107
|
const method = request?.method ?? '';
|
|
@@ -135,7 +138,10 @@ export async function handleRequest(binding, request) {
|
|
|
135
138
|
if (id === undefined || id === null) return null; // a notification
|
|
136
139
|
return { jsonrpc: '2.0', id, error: { code: -32601, message: `unknown method \`${method}\`` } };
|
|
137
140
|
}
|
|
138
|
-
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
});
|
|
144
|
+
}
|
|
139
145
|
|
|
140
146
|
/**
|
|
141
147
|
* Every result names what it did (`ran`) first, then carries its rows; a tool Hermes's own handler served
|
package/ontology.d.ts
CHANGED
|
@@ -652,6 +652,32 @@ export interface Posted {
|
|
|
652
652
|
message_id: string;
|
|
653
653
|
}
|
|
654
654
|
|
|
655
|
+
export interface PrDispositionRecorded {
|
|
656
|
+
kind: 'pr_disposition_recorded';
|
|
657
|
+
url: string;
|
|
658
|
+
head: string;
|
|
659
|
+
by?: string;
|
|
660
|
+
actor: string;
|
|
661
|
+
reason: string;
|
|
662
|
+
target: string | null;
|
|
663
|
+
disposition: 'merge' | 'release-only';
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export interface PrReleaseReadback {
|
|
667
|
+
kind: 'pr_release_readback';
|
|
668
|
+
url: string;
|
|
669
|
+
head: string;
|
|
670
|
+
by?: string;
|
|
671
|
+
actor: string;
|
|
672
|
+
reason: string;
|
|
673
|
+
target: string;
|
|
674
|
+
artifact: string;
|
|
675
|
+
evidence: string;
|
|
676
|
+
disposition_event: number;
|
|
677
|
+
verdict: number;
|
|
678
|
+
verification: { kind: 'machine' | 'manager-attestation'; source: string; target: string; observed_at: number; by?: string; actor?: string; receipt?: Record<string, unknown> };
|
|
679
|
+
}
|
|
680
|
+
|
|
655
681
|
/** One profile: a folder with `AGENTS.md` at its root. */
|
|
656
682
|
export interface Profile {
|
|
657
683
|
/** `default` for the root, else the folder name. */
|