@exaudeus/workrail 3.73.2 → 3.74.1
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/dist/cli-worktrain.js +126 -1
- package/dist/console-ui/assets/{index-CfI4I3OX.js → index-BmDxs-a5.js} +1 -1
- package/dist/console-ui/index.html +1 -1
- package/dist/coordinators/pr-review.d.ts +11 -1
- package/dist/coordinators/types.d.ts +15 -0
- package/dist/coordinators/types.js +2 -0
- package/dist/manifest.json +17 -9
- package/dist/trigger/coordinator-deps.js +203 -36
- package/docs/authoring.md +23 -0
- package/docs/ideas/backlog.md +299 -60
- package/docs/planning/README.md +6 -9
- package/docs/roadmap/archive/README.md +8 -0
- package/docs/tickets/next-up.md +6 -1
- package/docs/vision.md +115 -0
- package/package.json +1 -1
- package/spec/authoring-spec.json +36 -1
- /package/docs/roadmap/{now-next-later.md → archive/now-next-later.md} +0 -0
- /package/docs/roadmap/{open-work-inventory.md → archive/open-work-inventory.md} +0 -0
package/dist/manifest.json
CHANGED
|
@@ -238,8 +238,8 @@
|
|
|
238
238
|
"bytes": 31
|
|
239
239
|
},
|
|
240
240
|
"cli-worktrain.js": {
|
|
241
|
-
"sha256": "
|
|
242
|
-
"bytes":
|
|
241
|
+
"sha256": "fe38aca6a553491f3c9e0bd916cedbcd6a58d5cce230b1f47a52066f0b622851",
|
|
242
|
+
"bytes": 67617
|
|
243
243
|
},
|
|
244
244
|
"cli.d.ts": {
|
|
245
245
|
"sha256": "43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012",
|
|
@@ -473,8 +473,8 @@
|
|
|
473
473
|
"sha256": "5fe866e54f796975dec5d8ba9983aefd86074db212d3fccd64eed04bc9f0b3da",
|
|
474
474
|
"bytes": 8011
|
|
475
475
|
},
|
|
476
|
-
"console-ui/assets/index-
|
|
477
|
-
"sha256": "
|
|
476
|
+
"console-ui/assets/index-BmDxs-a5.js": {
|
|
477
|
+
"sha256": "8090617babfe49eb6f4b313ee45d13d862eba3ba8c9714ef805cda33918e4f08",
|
|
478
478
|
"bytes": 768234
|
|
479
479
|
},
|
|
480
480
|
"console-ui/assets/index-DHrKiMCf.css": {
|
|
@@ -482,7 +482,7 @@
|
|
|
482
482
|
"bytes": 60673
|
|
483
483
|
},
|
|
484
484
|
"console-ui/index.html": {
|
|
485
|
-
"sha256": "
|
|
485
|
+
"sha256": "d7ca1e4d2950572216e245c8a6a5130f8393ed3c961f067d2320202295b18183",
|
|
486
486
|
"bytes": 417
|
|
487
487
|
},
|
|
488
488
|
"console/standalone-console.d.ts": {
|
|
@@ -574,8 +574,8 @@
|
|
|
574
574
|
"bytes": 1198
|
|
575
575
|
},
|
|
576
576
|
"coordinators/pr-review.d.ts": {
|
|
577
|
-
"sha256": "
|
|
578
|
-
"bytes":
|
|
577
|
+
"sha256": "0dba830dd29cd82c58300ca9fdfb4c29d0acd0b257740ce3e65f2360239a106b",
|
|
578
|
+
"bytes": 4501
|
|
579
579
|
},
|
|
580
580
|
"coordinators/pr-review.js": {
|
|
581
581
|
"sha256": "385baa9e6252dbd84060bb423ce219884d519752f4a6e9f8f04e5f503fa38b67",
|
|
@@ -589,6 +589,14 @@
|
|
|
589
589
|
"sha256": "195953d6c0e28d749407e5e3fb29b963cf14629c03508792a44bf0866f7c9d33",
|
|
590
590
|
"bytes": 1815
|
|
591
591
|
},
|
|
592
|
+
"coordinators/types.d.ts": {
|
|
593
|
+
"sha256": "3043520258033bcdf5801ce8af81c8fa01c04573fc158a847addd8ec22f992e7",
|
|
594
|
+
"bytes": 393
|
|
595
|
+
},
|
|
596
|
+
"coordinators/types.js": {
|
|
597
|
+
"sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
|
|
598
|
+
"bytes": 77
|
|
599
|
+
},
|
|
592
600
|
"core/error-handler.d.ts": {
|
|
593
601
|
"sha256": "80451f12ac8e185133ec3dc4c57285491a785f27525ed21e729db1da3f61010d",
|
|
594
602
|
"bytes": 1368
|
|
@@ -1786,8 +1794,8 @@
|
|
|
1786
1794
|
"bytes": 854
|
|
1787
1795
|
},
|
|
1788
1796
|
"trigger/coordinator-deps.js": {
|
|
1789
|
-
"sha256": "
|
|
1790
|
-
"bytes":
|
|
1797
|
+
"sha256": "1b400abbd6158e900d4559c74b9f3069a46b60fd02d046f36e30cc40a58acb51",
|
|
1798
|
+
"bytes": 22626
|
|
1791
1799
|
},
|
|
1792
1800
|
"trigger/delivery-action.d.ts": {
|
|
1793
1801
|
"sha256": "bba98a08e35653304b604cd3ec126374cb731620db27ee2c8d6782d5b5b31207",
|
|
@@ -45,6 +45,102 @@ const infra_js_1 = require("../context-assembly/infra.js");
|
|
|
45
45
|
function createCoordinatorDeps(deps) {
|
|
46
46
|
const { ctx, execFileAsync, consoleService } = deps;
|
|
47
47
|
let dispatch = null;
|
|
48
|
+
async function fetchAgentResult(sessionHandle) {
|
|
49
|
+
const emptyResult = { recapMarkdown: null, artifacts: [] };
|
|
50
|
+
if (consoleService === null) {
|
|
51
|
+
return emptyResult;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const detailResult = await consoleService.getSessionDetail(sessionHandle);
|
|
55
|
+
if (detailResult.isErr())
|
|
56
|
+
return emptyResult;
|
|
57
|
+
const run = detailResult.value.runs[0];
|
|
58
|
+
if (!run)
|
|
59
|
+
return emptyResult;
|
|
60
|
+
const tipNodeId = run.preferredTipNodeId;
|
|
61
|
+
if (!tipNodeId)
|
|
62
|
+
return emptyResult;
|
|
63
|
+
const allNodeIds = run.nodes
|
|
64
|
+
.map((n) => n.nodeId)
|
|
65
|
+
.filter((id) => typeof id === 'string' && id !== '');
|
|
66
|
+
const nodeIdsToFetch = allNodeIds.length > 0 ? allNodeIds : [tipNodeId];
|
|
67
|
+
let recap = null;
|
|
68
|
+
const collectedArtifacts = [];
|
|
69
|
+
for (const nodeId of nodeIdsToFetch) {
|
|
70
|
+
try {
|
|
71
|
+
const nodeResult = await consoleService.getNodeDetail(sessionHandle, nodeId);
|
|
72
|
+
if (nodeResult.isErr())
|
|
73
|
+
continue;
|
|
74
|
+
if (nodeId === tipNodeId)
|
|
75
|
+
recap = nodeResult.value.recapMarkdown;
|
|
76
|
+
if (nodeResult.value.artifacts.length > 0)
|
|
77
|
+
collectedArtifacts.push(...nodeResult.value.artifacts);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { recapMarkdown: recap, artifacts: collectedArtifacts };
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
87
|
+
process.stderr.write(`[WARN coord:reason=exception handle=${sessionHandle.slice(0, 16)}] fetchAgentResult: ${msg}\n`);
|
|
88
|
+
return emptyResult;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function fetchChildSessionResult(handle, coordinatorSessionId) {
|
|
92
|
+
if (consoleService === null) {
|
|
93
|
+
process.stderr.write(`[WARN coord:reason=await_degraded handle=${handle.slice(0, 16)}${coordinatorSessionId ? ' parent=' + coordinatorSessionId.slice(0, 16) : ''}] fetchChildSessionResult: ConsoleService unavailable\n`);
|
|
94
|
+
return {
|
|
95
|
+
kind: 'await_degraded',
|
|
96
|
+
message: 'ConsoleService unavailable -- cannot read child session outcome',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
let runStatus = null;
|
|
100
|
+
try {
|
|
101
|
+
const detailResult = await consoleService.getSessionDetail(handle);
|
|
102
|
+
if (detailResult.isErr()) {
|
|
103
|
+
process.stderr.write(`[WARN coord:reason=getSessionDetail_failed handle=${handle.slice(0, 16)}] fetchChildSessionResult: ${String(detailResult.error)}\n`);
|
|
104
|
+
return {
|
|
105
|
+
kind: 'failed',
|
|
106
|
+
reason: 'error',
|
|
107
|
+
message: `Could not read session detail: ${String(detailResult.error)}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const run = detailResult.value.runs[0];
|
|
111
|
+
runStatus = run?.status ?? null;
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
115
|
+
process.stderr.write(`[WARN coord:reason=exception handle=${handle.slice(0, 16)}] fetchChildSessionResult getSessionDetail: ${msg}\n`);
|
|
116
|
+
return { kind: 'failed', reason: 'error', message: `Exception reading session detail: ${msg}` };
|
|
117
|
+
}
|
|
118
|
+
if (runStatus === 'complete' || runStatus === 'complete_with_gaps') {
|
|
119
|
+
const agentResult = await fetchAgentResult(handle);
|
|
120
|
+
return {
|
|
121
|
+
kind: 'success',
|
|
122
|
+
notes: agentResult.recapMarkdown,
|
|
123
|
+
artifacts: agentResult.artifacts,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (runStatus === 'blocked') {
|
|
127
|
+
return {
|
|
128
|
+
kind: 'failed',
|
|
129
|
+
reason: 'stuck',
|
|
130
|
+
message: `Child session ${handle.slice(0, 16)} reached blocked state`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (runStatus === null) {
|
|
134
|
+
return {
|
|
135
|
+
kind: 'timed_out',
|
|
136
|
+
message: `Child session ${handle.slice(0, 16)} has no terminal run status (likely timed out)`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
kind: 'timed_out',
|
|
141
|
+
message: `Child session ${handle.slice(0, 16)} is still in state '${runStatus}' -- awaitSessions may not have been called`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
48
144
|
return {
|
|
49
145
|
setDispatch(fn) {
|
|
50
146
|
if (dispatch !== null) {
|
|
@@ -53,11 +149,16 @@ function createCoordinatorDeps(deps) {
|
|
|
53
149
|
}
|
|
54
150
|
dispatch = fn;
|
|
55
151
|
},
|
|
56
|
-
spawnSession: async (workflowId, goal, workspace, context, agentConfig) => {
|
|
152
|
+
spawnSession: async (workflowId, goal, workspace, context, agentConfig, parentSessionId) => {
|
|
57
153
|
if (dispatch === null) {
|
|
58
154
|
return { kind: 'err', error: 'in-process router not initialized -- coordinator deps not ready' };
|
|
59
155
|
}
|
|
60
|
-
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId, workspacePath: workspace, goal }, ctx, {
|
|
156
|
+
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId, workspacePath: workspace, goal }, ctx, {
|
|
157
|
+
is_autonomous: 'true',
|
|
158
|
+
workspacePath: workspace,
|
|
159
|
+
triggerSource: 'daemon',
|
|
160
|
+
...(parentSessionId !== undefined ? { parentSessionId } : {}),
|
|
161
|
+
});
|
|
61
162
|
if (startResult.isErr()) {
|
|
62
163
|
const detail = `${startResult.error.kind}${'message' in startResult.error ? ': ' + startResult.error.message : ''}`;
|
|
63
164
|
return { kind: 'err', error: `Session creation failed: ${detail}` };
|
|
@@ -173,47 +274,113 @@ function createCoordinatorDeps(deps) {
|
|
|
173
274
|
};
|
|
174
275
|
},
|
|
175
276
|
getAgentResult: async (sessionHandle) => {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
277
|
+
return fetchAgentResult(sessionHandle);
|
|
278
|
+
},
|
|
279
|
+
getChildSessionResult: async (handle, coordinatorSessionId) => {
|
|
280
|
+
return fetchChildSessionResult(handle, coordinatorSessionId);
|
|
281
|
+
},
|
|
282
|
+
spawnAndAwait: async (workflowId, goal, workspace, opts) => {
|
|
283
|
+
const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
284
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
285
|
+
const coordinatorSessionId = opts?.coordinatorSessionId;
|
|
286
|
+
const agentConfig = opts?.agentConfig;
|
|
287
|
+
if (dispatch === null) {
|
|
288
|
+
return {
|
|
289
|
+
kind: 'failed',
|
|
290
|
+
reason: 'error',
|
|
291
|
+
message: 'spawnAndAwait: in-process router not initialized (setDispatch not called)',
|
|
292
|
+
};
|
|
179
293
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
294
|
+
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId, workspacePath: workspace, goal }, ctx, {
|
|
295
|
+
is_autonomous: 'true',
|
|
296
|
+
workspacePath: workspace,
|
|
297
|
+
triggerSource: 'daemon',
|
|
298
|
+
...(coordinatorSessionId !== undefined ? { parentSessionId: coordinatorSessionId } : {}),
|
|
299
|
+
});
|
|
300
|
+
if (startResult.isErr()) {
|
|
301
|
+
const detail = `${startResult.error.kind}${'message' in startResult.error ? ': ' + startResult.error.message : ''}`;
|
|
302
|
+
return { kind: 'failed', reason: 'error', message: `Session creation failed: ${detail}` };
|
|
303
|
+
}
|
|
304
|
+
const startContinueToken = startResult.value.response.continueToken;
|
|
305
|
+
let handle;
|
|
306
|
+
if (!startContinueToken) {
|
|
307
|
+
handle = workflowId;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
const tokenResult = await (0, v2_token_ops_js_1.parseContinueTokenOrFail)(startContinueToken, ctx.v2.tokenCodecPorts, ctx.v2.tokenAliasStore);
|
|
311
|
+
if (tokenResult.isErr()) {
|
|
312
|
+
return {
|
|
313
|
+
kind: 'failed',
|
|
314
|
+
reason: 'error',
|
|
315
|
+
message: `Internal error: could not extract session handle from new session: ${tokenResult.error.message}`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
handle = tokenResult.value.sessionId;
|
|
319
|
+
const trigger = {
|
|
320
|
+
workflowId,
|
|
321
|
+
goal,
|
|
322
|
+
workspacePath: workspace,
|
|
323
|
+
...(agentConfig !== undefined ? { agentConfig } : {}),
|
|
324
|
+
};
|
|
325
|
+
const r = startResult.value.response;
|
|
326
|
+
const allocatedSession = {
|
|
327
|
+
continueToken: r.continueToken ?? '',
|
|
328
|
+
checkpointToken: r.checkpointToken,
|
|
329
|
+
firstStepPrompt: r.pending?.prompt ?? '',
|
|
330
|
+
isComplete: r.isComplete,
|
|
331
|
+
triggerSource: 'daemon',
|
|
332
|
+
};
|
|
333
|
+
const source = {
|
|
334
|
+
kind: 'pre_allocated',
|
|
335
|
+
trigger,
|
|
336
|
+
session: allocatedSession,
|
|
337
|
+
};
|
|
338
|
+
dispatch(trigger, source);
|
|
339
|
+
}
|
|
340
|
+
const awaitResult = await (async () => {
|
|
341
|
+
const POLL_INTERVAL_MS = 3000;
|
|
342
|
+
if (consoleService === null) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const startMs = Date.now();
|
|
346
|
+
const pending = new Set([handle]);
|
|
347
|
+
while (pending.size > 0) {
|
|
348
|
+
const elapsed = Date.now() - startMs;
|
|
349
|
+
if (elapsed >= timeoutMs)
|
|
350
|
+
break;
|
|
351
|
+
for (const h of [...pending]) {
|
|
352
|
+
try {
|
|
353
|
+
const detail = await consoleService.getSessionDetail(h);
|
|
354
|
+
if (detail.isErr())
|
|
355
|
+
continue;
|
|
356
|
+
const run = detail.value.runs[0];
|
|
357
|
+
if (!run)
|
|
358
|
+
continue;
|
|
359
|
+
const status = run.status;
|
|
360
|
+
if (status === 'complete' || status === 'complete_with_gaps') {
|
|
361
|
+
pending.delete(h);
|
|
362
|
+
}
|
|
363
|
+
else if (status === 'blocked') {
|
|
364
|
+
pending.delete(h);
|
|
365
|
+
}
|
|
201
366
|
}
|
|
202
|
-
|
|
203
|
-
|
|
367
|
+
catch {
|
|
368
|
+
pending.delete(h);
|
|
204
369
|
}
|
|
205
370
|
}
|
|
206
|
-
|
|
207
|
-
|
|
371
|
+
if (pending.size > 0) {
|
|
372
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
208
373
|
}
|
|
209
374
|
}
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
375
|
+
return handle;
|
|
376
|
+
})();
|
|
377
|
+
if (awaitResult === null) {
|
|
378
|
+
return {
|
|
379
|
+
kind: 'await_degraded',
|
|
380
|
+
message: 'ConsoleService unavailable -- cannot await child session outcome',
|
|
381
|
+
};
|
|
216
382
|
}
|
|
383
|
+
return fetchChildSessionResult(handle, coordinatorSessionId);
|
|
217
384
|
},
|
|
218
385
|
listOpenPRs: async (workspace) => {
|
|
219
386
|
try {
|
package/docs/authoring.md
CHANGED
|
@@ -761,6 +761,28 @@ Canonical current rules for authoring good WorkRail workflows. workflow.schema.j
|
|
|
761
761
|
|
|
762
762
|
|
|
763
763
|
## Artifacts and planning surfaces
|
|
764
|
+
### coordinator-result-artifact-schema
|
|
765
|
+
- **Level**: required
|
|
766
|
+
- **Status**: active
|
|
767
|
+
- **Scope**: artifact.coordinator-result
|
|
768
|
+
- **Rule**: When a workflow step signals coordinator phase completion, emit a `wr.coordinator_result` artifact with exactly 4 fields: `outcome` (enum: success|failed|timed_out|await_degraded), `summary` (string), `sessionId` (string), `error` (string|null). No additional fields allowed.
|
|
769
|
+
- **Why**: Coordinators read this artifact to determine whether to proceed, retry, or escalate. Extra fields pollute the schema boundary and break forward compatibility. The 4-field constraint is a hard limit, not a guideline.
|
|
770
|
+
- **Enforced by**: advisory
|
|
771
|
+
|
|
772
|
+
**Checks**
|
|
773
|
+
- Exactly 4 fields present: outcome, summary, sessionId, error.
|
|
774
|
+
- outcome is one of: success, failed, timed_out, await_degraded.
|
|
775
|
+
- error is string|null -- null when outcome is success, non-null string when outcome is failed.
|
|
776
|
+
- No workflow-specific fields (prUrl, branchName, commitSha, etc.) in wr.coordinator_result. Those belong in workflow-specific artifacts.
|
|
777
|
+
|
|
778
|
+
**Anti-patterns**
|
|
779
|
+
- Adding prUrl, branchName, or commitSha to wr.coordinator_result
|
|
780
|
+
- Using a free-form notes string instead of the typed outcome enum
|
|
781
|
+
- Omitting sessionId (required for coordinator tracing and console parent-child display)
|
|
782
|
+
|
|
783
|
+
**Source refs**
|
|
784
|
+
- `src/coordinators/types.ts` (runtime) — ChildSessionResult discriminated union -- the runtime type that wr.coordinator_result maps to.
|
|
785
|
+
|
|
764
786
|
### artifact-canonicality
|
|
765
787
|
- **Level**: recommended
|
|
766
788
|
- **Status**: active
|
|
@@ -913,6 +935,7 @@ Canonical current rules for authoring good WorkRail workflows. workflow.schema.j
|
|
|
913
935
|
- `artifact.plan`: Implementation-planning artifacts
|
|
914
936
|
- `artifact.spec`: Behavior/specification artifacts
|
|
915
937
|
- `artifact.verification`: Verification or handoff artifacts
|
|
938
|
+
- `artifact.coordinator-result`: wr.coordinator_result artifact emitted by coordinator-phase workflows to signal phase completion to the coordinator
|
|
916
939
|
- `delegation.context-packet`: Structured context passed to subagents
|
|
917
940
|
- `delegation.result-envelope`: Structured result shape returned by subagents
|
|
918
941
|
- `legacy.patterns`: Older authoring patterns that should now be discouraged or avoided
|