@themoltnet/node-red-contrib-core 0.10.0 → 0.11.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.
@@ -8,11 +8,11 @@
8
8
  },
9
9
  {
10
10
  "apiUrl": "",
11
- "clientId": "",
12
- "diaryId": "",
11
+ "clientId": "b46d59c1-0b14-453c-a57b-8be1ced93141",
12
+ "diaryId": "6e4d9948-8ec5-4f59-b82a-3acbc4bbc396",
13
13
  "id": "deep_review_agent_cfg",
14
14
  "name": "deep-review-agent",
15
- "teamId": "",
15
+ "teamId": "6743b4b1-6b93-46e2-a048-19490f04f91a",
16
16
  "type": "moltnet-agent"
17
17
  },
18
18
  {
@@ -20,7 +20,7 @@
20
20
  "id": "deep_review_profile_freeze",
21
21
  "name": "FREEZE profile",
22
22
  "profileId": "1a653eb9-7bfa-475f-b517-c070c9c25b5e",
23
- "profileName": "deep-review-freeze-ollama-qwen-coder-v1",
23
+ "profileName": "deep-review-freeze-v1",
24
24
  "type": "moltnet-runtime-profile"
25
25
  },
26
26
  {
@@ -28,7 +28,7 @@
28
28
  "id": "deep_review_profile_preflight",
29
29
  "name": "PREFLIGHT profile",
30
30
  "profileId": "f4bb1d9b-6281-4158-ad88-cbcb1198c3dc",
31
- "profileName": "deep-review-preflight-ollama-qwen-coder-v1",
31
+ "profileName": "deep-review-preflight-v1",
32
32
  "type": "moltnet-runtime-profile"
33
33
  },
34
34
  {
@@ -36,7 +36,7 @@
36
36
  "id": "deep_review_profile_specialist",
37
37
  "name": "SPECIALIST profile",
38
38
  "profileId": "f50e9c58-4180-4e07-b120-08b6097c13d5",
39
- "profileName": "deep-review-specialist-ollama-qwen-coder-v1",
39
+ "profileName": "deep-review-specialist-v1",
40
40
  "type": "moltnet-runtime-profile"
41
41
  },
42
42
  {
@@ -44,16 +44,16 @@
44
44
  "id": "deep_review_profile_aggregate",
45
45
  "name": "AGGREGATE profile",
46
46
  "profileId": "29db793d-3ad9-420b-96e7-df5356b3d19b",
47
- "profileName": "deep-review-aggregate-ollama-kimi-v1",
47
+ "profileName": "deep-review-aggregate-v1",
48
48
  "type": "moltnet-runtime-profile"
49
49
  },
50
50
  {
51
51
  "id": "deep_review_note",
52
- "info": "This example keeps MoltNet generic: every agent step is taskType=freeform. Node-RED owns orchestration, correlationId threading, gates, profile routing, specialist fan-out/fan-in, and aggregation. Agents own the judgment and produce structured freeform artifacts. The FREEZE branch parses compact inline JSON for control flow, then lists and downloads any persistent review bundle artifact uploaded by the agent during the attempt. Runtime-profile config nodes pin each stage to an Ollama Cloud profile: FREEZE/PREFLIGHT/SPECIALIST use qwen3-coder:480b-cloud for reliable tool calls, and AGGREGATE uses kimi-k2.7-code:cloud.",
52
+ "info": "This example keeps MoltNet generic: every agent step is taskType=freeform. Node-RED owns orchestration, correlationId threading, accepted gates, profile routing, specialist fan-out/fan-in, and aggregation. Task bodies pass through moltnet-task-builder before creation so the example follows the same validated envelope pattern as newer flows. Non-accepted task attempts and node errors are packed into explicit failure payloads; specialist failures become coverage gaps while terminal stage failures route to the final debug output. The FREEZE branch parses compact inline JSON for control flow, then lists and downloads any persistent review bundle artifact uploaded by the agent during the attempt. Runtime-profile config nodes pin each stage to an Ollama Cloud profile: FREEZE/PREFLIGHT/SPECIALIST use qwen3-coder:480b-cloud for reliable tool calls, and AGGREGATE uses kimi-k2.7-code:cloud. Runtime session metadata side lanes show the durable transcript record for completed FREEZE and PREFLIGHT attempts without feeding raw transcript content into downstream review tasks.",
53
53
  "name": "Freeform-only deep review workflow",
54
54
  "type": "comment",
55
55
  "wires": [],
56
- "x": 260,
56
+ "x": 300,
57
57
  "y": 40,
58
58
  "z": "deep_review_tab"
59
59
  },
@@ -74,8 +74,8 @@
74
74
  "topic": "",
75
75
  "type": "inject",
76
76
  "wires": [["deep_review_seed"]],
77
- "x": 130,
78
- "y": 100,
77
+ "x": 120,
78
+ "y": 125,
79
79
  "z": "deep_review_tab"
80
80
  },
81
81
  {
@@ -91,7 +91,7 @@
91
91
  "type": "function",
92
92
  "wires": [["deep_review_entries_search"]],
93
93
  "x": 350,
94
- "y": 100,
94
+ "y": 125,
95
95
  "z": "deep_review_tab"
96
96
  },
97
97
  {
@@ -111,8 +111,8 @@
111
111
  "wRecency": "",
112
112
  "wRelevance": "",
113
113
  "wires": [["deep_review_stash_memory"]],
114
- "x": 610,
115
- "y": 100,
114
+ "x": 620,
115
+ "y": 125,
116
116
  "z": "deep_review_tab"
117
117
  },
118
118
  {
@@ -127,13 +127,13 @@
127
127
  "timeout": 0,
128
128
  "type": "function",
129
129
  "wires": [["deep_review_build_freeze"]],
130
- "x": 850,
131
- "y": 100,
130
+ "x": 900,
131
+ "y": 125,
132
132
  "z": "deep_review_tab"
133
133
  },
134
134
  {
135
135
  "finalize": "",
136
- "func": "const target = msg.review.target;\nmsg.payload = {\n taskType: 'freeform',\n title: 'Deep review: freeze scope',\n tags: ['deep-review', 'deep-review:freeze', msg.review.workflow],\n correlationId: msg.correlationId,\n input: {\n brief: [\n 'Freeze the review scope for a deep code review workflow.',\n '',\n 'Target JSON:',\n JSON.stringify(target, null, 2),\n '',\n 'Create an immutable review bundle. Always return a compact inline freeform artifact. If task artifact upload tools are available, also upload large patch/file-list content after writing the local files successfully.',\n '',\n 'Before calling moltnet_upload_task_artifact for a workspace file, create the file first and verify it exists. Never call artifact upload with a guessed path such as review.patch if the file was not written.',\n '',\n 'The bundle should include: target metadata, base/head refs or PR number, changed files, diff stats, and either a patch artifact CID or enough precise instructions for downstream agents to fetch the same diff.',\n '',\n 'Return a freeform artifact with kind \"json\" and title \"review-bundle\". The artifact body must be JSON with fields: target, changedFiles, stats, diffArtifactCid, patchArtifactCid, notes, memoryEntryIds.',\n '',\n 'If the submit tool requires a verification block, every verification.results[].kind must be one of: gate, assertion, rubric, sideEffect. For a submit-tool-call success criterion, report it as kind \"gate\"; never use \"submit-tool-call\" as a verification result kind.'\n ].join('\\n'),\n expectedOutput: 'Summary plus artifact kind=json title=review-bundle. Use task artifact CIDs for large patch/file-list content only after the local files exist.',\n constraints: [\n 'Do not review findings in this task.',\n 'Do not modify code.',\n 'If the target cannot be resolved, explain the blocker in the output.',\n 'When self-verifying a submit-tool-call gate, use verification result kind \"gate\".'\n ],\n context: [\n { slug: 'review-config', binding: 'context_inline', content: JSON.stringify(msg.review, null, 2) },\n { slug: 'memory-hits', binding: 'context_inline', content: JSON.stringify(msg.reviewMemory || [], null, 2) }\n ],\n execution: { workspace: 'dedicated_worktree' }\n }\n};\nreturn msg;",
136
+ "func": "const target = msg.review.target;\nmsg.payload = {\n taskType: 'freeform',\n title: 'Deep review: freeze scope',\n tags: ['deep-review', 'deep-review:freeze', msg.review.workflow],\n correlationId: msg.correlationId,\n input: {\n brief: [\n 'Freeze the review scope for a deep code review workflow.',\n '',\n 'Target JSON:',\n JSON.stringify(target, null, 2),\n '',\n 'Create an immutable review bundle. Always return a compact inline freeform artifact, and when artifact upload tools are available, upload the patch and file list as task artifacts before submitting the accepted output.',\n '',\n 'Task artifact CIDs are the durable contract for downstream agents. Local files are only staging inputs for upload; never return a local filesystem path as a bundle reference.',\n '',\n 'Before calling moltnet_upload_task_artifact for a workspace file, write the file, stat the exact guest path you will pass to the upload tool, and only then upload it. Never call artifact upload with a guessed path such as review.patch, an absolute host/worktree path, a URI-encoded session path, or any path that was not verified inside the guest workspace.',\n '',\n 'If patch upload fails or artifact tools are unavailable, set patchArtifactCid and diffArtifactCid to null and explain the fallback in notes. Do not put local paths in patchArtifactCid, diffArtifactCid, or notes.',\n '',\n 'The bundle should include: target metadata, base/head refs or PR number, changed files, diff stats, task artifact CIDs for uploaded patch/file-list content, and enough precise instructions for downstream agents to fetch the same diff if no CID is available.',\n '',\n 'Return a freeform artifact with kind \"json\" and title \"review-bundle\". The artifact body must be JSON with fields: target, changedFiles, stats, diffArtifactCid, patchArtifactCid, notes, memoryEntryIds.',\n '',\n 'If the submit tool requires a verification block, every verification.results[].kind must be one of: gate, assertion, rubric, sideEffect. For a submit-tool-call success criterion, report it as kind \"gate\"; never use \"submit-tool-call\" as a verification result kind.'\n ].join('\\n'),\n expectedOutput: 'Summary plus artifact kind=json title=review-bundle. Use task artifact CIDs, or null plus notes when upload is unavailable; never use local paths as durable bundle references.',\n constraints: [\n 'Do not review findings in this task.',\n 'Do not modify code.',\n 'If the target cannot be resolved, explain the blocker in the output.',\n 'Do not return host, worktree, session, or guest-local filesystem paths as artifact references.',\n 'When self-verifying a submit-tool-call gate, use verification result kind \"gate\".'\n ],\n context: [\n { slug: 'review-config', binding: 'context_inline', content: JSON.stringify(msg.review, null, 2) },\n { slug: 'memory-hits', binding: 'context_inline', content: JSON.stringify(msg.reviewMemory || [], null, 2) }\n ],\n execution: { workspace: 'dedicated_worktree' }\n }\n};\nreturn msg;",
137
137
  "id": "deep_review_build_freeze",
138
138
  "initialize": "",
139
139
  "libs": [],
@@ -142,9 +142,9 @@
142
142
  "outputs": 1,
143
143
  "timeout": 0,
144
144
  "type": "function",
145
- "wires": [["deep_review_create_freeze"]],
146
- "x": 170,
147
- "y": 180,
145
+ "wires": [["deep_review_builder_freeze"]],
146
+ "x": 150,
147
+ "y": 265,
148
148
  "z": "deep_review_tab"
149
149
  },
150
150
  {
@@ -156,22 +156,22 @@
156
156
  "runtimeProfile": "deep_review_profile_freeze",
157
157
  "type": "moltnet-tasks-create",
158
158
  "wires": [["deep_review_wait_freeze"]],
159
- "x": 400,
160
- "y": 180,
159
+ "x": 640,
160
+ "y": 265,
161
161
  "z": "deep_review_tab"
162
162
  },
163
163
  {
164
164
  "agent": "deep_review_agent_cfg",
165
165
  "id": "deep_review_wait_freeze",
166
- "intervalMs": 2500,
167
166
  "kinds": "",
168
167
  "name": "wait FREEZE",
168
+ "pollIntervalSec": 2.5,
169
169
  "tail": true,
170
170
  "timeoutSec": 7200,
171
171
  "type": "moltnet-task-wait",
172
- "wires": [["1d29dc178889e4a5"], ["deep_review_stash_freeze_snapshot"]],
173
- "x": 620,
174
- "y": 180,
172
+ "wires": [["1d29dc178889e4a5"], ["deep_review_gate_freeze"]],
173
+ "x": 900,
174
+ "y": 265,
175
175
  "z": "deep_review_tab"
176
176
  },
177
177
  {
@@ -186,8 +186,8 @@
186
186
  "timeout": 0,
187
187
  "type": "function",
188
188
  "wires": [["deep_review_read_freeze"]],
189
- "x": 850,
190
- "y": 200,
189
+ "x": 1400,
190
+ "y": 265,
191
191
  "z": "deep_review_tab"
192
192
  },
193
193
  {
@@ -201,8 +201,8 @@
201
201
  "teamId": "",
202
202
  "type": "moltnet-task-artifacts-list",
203
203
  "wires": [["deep_review_select_bundle_artifact"]],
204
- "x": 740,
205
- "y": 300,
204
+ "x": 950,
205
+ "y": 365,
206
206
  "z": "deep_review_tab"
207
207
  },
208
208
  {
@@ -217,8 +217,8 @@
217
217
  "timeout": 0,
218
218
  "type": "function",
219
219
  "wires": [["deep_review_download_bundle_artifact"]],
220
- "x": 980,
221
- "y": 300,
220
+ "x": 1210,
221
+ "y": 365,
222
222
  "z": "deep_review_tab"
223
223
  },
224
224
  {
@@ -233,8 +233,8 @@
233
233
  "teamId": "",
234
234
  "type": "moltnet-task-artifact-download",
235
235
  "wires": [["deep_review_debug_bundle_artifact"]],
236
- "x": 1230,
237
- "y": 300,
236
+ "x": 1490,
237
+ "y": 365,
238
238
  "z": "deep_review_tab"
239
239
  },
240
240
  {
@@ -246,8 +246,8 @@
246
246
  "source": "payload",
247
247
  "type": "moltnet-task-reader",
248
248
  "wires": [["deep_review_stash_bundle"]],
249
- "x": 1090,
250
- "y": 200,
249
+ "x": 1650,
250
+ "y": 265,
251
251
  "z": "deep_review_tab"
252
252
  },
253
253
  {
@@ -263,13 +263,13 @@
263
263
  "tostatus": false,
264
264
  "type": "debug",
265
265
  "wires": [],
266
- "x": 1500,
267
- "y": 300,
266
+ "x": 1780,
267
+ "y": 365,
268
268
  "z": "deep_review_tab"
269
269
  },
270
270
  {
271
271
  "finalize": "",
272
- "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || {};\nmsg.reviewBundle = msg.result?.artifactBody || msg.payload || {};\nmsg.reviewBundleRef = msg.result?.artifactRef || msg.result?.outputRef || null;\nmsg.review.freezeTaskId = msg.freezeSnapshot?.taskId || msg.result?.outputRef?.taskId || null;\nmsg.review.freezeAttemptN = msg.freezeSnapshot?.acceptedAttemptN || msg.freezeSnapshot?.attempt?.attemptN || null;\nmsg.taskId = msg.review.freezeTaskId;\nmsg.attemptN = msg.review.freezeAttemptN;\nstate.correlationId = msg.correlationId;\nstate.review = msg.review;\nstate.reviewBundle = msg.reviewBundle;\nstate.reviewBundleRef = msg.reviewBundleRef;\nstate.freezeSnapshot = msg.freezeSnapshot || null;\nstate.tasks = state.tasks || {};\nstate.tasks.freeze = { taskId: msg.review.freezeTaskId, attemptN: msg.review.freezeAttemptN };\nstate.specialistResults = state.specialistResults || [];\nflow.set(key, state);\nmsg.reviewState = state;\nreturn msg;",
272
+ "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || {};\nmsg.reviewBundle = msg.result?.artifactBody || msg.payload || {};\nmsg.reviewBundleRef = msg.result?.artifactRef || msg.result?.outputRef || null;\nmsg.review.freezeTaskId = msg.freezeSnapshot?.taskId || msg.result?.outputRef?.taskId || null;\nmsg.review.freezeAttemptN = msg.freezeSnapshot?.acceptedAttemptN || msg.freezeSnapshot?.attempt?.attemptN || null;\nmsg.taskId = msg.review.freezeTaskId;\nmsg.attemptN = msg.review.freezeAttemptN;\nstate.correlationId = msg.correlationId;\nstate.review = msg.review;\nstate.reviewBundle = msg.reviewBundle;\nstate.reviewBundleRef = msg.reviewBundleRef;\nstate.freezeSnapshot = msg.freezeSnapshot || null;\nstate.tasks = state.tasks || {};\nstate.tasks.freeze = { taskId: msg.review.freezeTaskId, attemptN: msg.review.freezeAttemptN };\nstate.specialistResults = state.specialistResults || [];\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nreturn msg;",
273
273
  "id": "deep_review_stash_bundle",
274
274
  "initialize": "",
275
275
  "libs": [],
@@ -279,15 +279,19 @@
279
279
  "timeout": 0,
280
280
  "type": "function",
281
281
  "wires": [
282
- ["deep_review_build_preflight", "deep_review_list_freeze_artifacts"]
282
+ [
283
+ "deep_review_bundle_ready_link_out",
284
+ "deep_review_list_freeze_artifacts",
285
+ "deep_review_get_freeze_session"
286
+ ]
283
287
  ],
284
- "x": 1320,
285
- "y": 200,
288
+ "x": 1900,
289
+ "y": 265,
286
290
  "z": "deep_review_tab"
287
291
  },
288
292
  {
289
293
  "finalize": "",
290
- "func": "msg.payload = {\n taskType: 'freeform',\n title: 'Deep review: preflight',\n tags: ['deep-review', 'deep-review:preflight', msg.review.workflow],\n correlationId: msg.correlationId,\n input: {\n brief: [\n 'Run the design preflight for this deep review.',\n '',\n 'Decide whether the approach is sound before line-level review.',\n 'Return a freeform artifact with kind \"json\" and title \"preflight\".',\n '',\n 'The artifact body must be JSON:',\n '{ \"verdict\": \"PROCEED|PIVOT|ASK\", \"summary\": \"...\", \"questions\": [], \"pivotSize\": \"small|medium|large|null\", \"recommendedNextStep\": \"...\" }',\n '',\n 'PIVOT only when the approach is materially wrong, not when you merely prefer another implementation.',\n '',\n 'If the submit tool requires a verification block, every verification.results[].kind must be one of: gate, assertion, rubric, sideEffect. For a submit-tool-call success criterion, report it as kind \"gate\"; never use \"submit-tool-call\" as a verification result kind.'\n ].join('\\n'),\n expectedOutput: 'Summary plus artifact kind=json title=preflight.',\n constraints: ['Do not produce line-level findings.', 'Do not modify code.', 'When self-verifying a submit-tool-call gate, use verification result kind \"gate\".'],\n context: [\n { slug: 'review-config', binding: 'context_inline', content: JSON.stringify(msg.review, null, 2) },\n { slug: 'review-bundle', binding: 'context_inline', content: JSON.stringify(msg.reviewBundle, null, 2) },\n { slug: 'review-bundle-ref', binding: 'context_inline', content: JSON.stringify(msg.reviewBundleRef, null, 2) }\n ],\n execution: { workspace: 'shared_mount' }\n }\n};\nreturn msg;",
294
+ "func": "msg.payload = {\n taskType: 'freeform',\n title: 'Deep review: preflight',\n tags: ['deep-review', 'deep-review:preflight', msg.review.workflow],\n correlationId: msg.correlationId,\n input: {\n brief: [\n 'Run the design preflight for this deep review.',\n '',\n 'Decide whether the approach is sound before line-level review.',\n 'Return a freeform artifact with kind \"json\" and title \"preflight\".',\n '',\n 'The artifact body must be JSON:',\n '{ \"verdict\": \"PROCEED|PIVOT\", \"summary\": \"...\", \"questions\": [], \"pivotSize\": \"small|medium|large|null\", \"recommendedNextStep\": \"...\" }',\n '',\n 'Use PROCEED when line-level specialist review should continue, even if the scope is large. Use PIVOT only when the approach is materially wrong, not when you merely prefer another implementation.',\n '',\n 'If the submit tool requires a verification block, every verification.results[].kind must be one of: gate, assertion, rubric, sideEffect. For a submit-tool-call success criterion, report it as kind \"gate\"; never use \"submit-tool-call\" as a verification result kind.'\n ].join('\\n'),\n expectedOutput: 'Summary plus artifact kind=json title=preflight with verdict PROCEED or PIVOT.',\n constraints: ['Do not produce line-level findings.', 'Do not modify code.', 'Choose PROCEED or PIVOT.', 'When self-verifying a submit-tool-call gate, use verification result kind \"gate\".'],\n context: [\n { slug: 'review-config', binding: 'context_inline', content: JSON.stringify(msg.review, null, 2) },\n { slug: 'review-bundle', binding: 'context_inline', content: JSON.stringify(msg.reviewBundle, null, 2) },\n { slug: 'review-bundle-ref', binding: 'context_inline', content: JSON.stringify(msg.reviewBundleRef, null, 2) }\n ],\n execution: { workspace: 'shared_mount' }\n }\n};\nreturn msg;",
291
295
  "id": "deep_review_build_preflight",
292
296
  "initialize": "",
293
297
  "libs": [],
@@ -296,9 +300,9 @@
296
300
  "outputs": 1,
297
301
  "timeout": 0,
298
302
  "type": "function",
299
- "wires": [["deep_review_create_preflight"]],
300
- "x": 200,
301
- "y": 500,
303
+ "wires": [["deep_review_builder_preflight"]],
304
+ "x": 250,
305
+ "y": 555,
302
306
  "z": "deep_review_tab"
303
307
  },
304
308
  {
@@ -310,22 +314,22 @@
310
314
  "runtimeProfile": "deep_review_profile_preflight",
311
315
  "type": "moltnet-tasks-create",
312
316
  "wires": [["deep_review_wait_preflight"]],
313
- "x": 440,
314
- "y": 500,
317
+ "x": 780,
318
+ "y": 555,
315
319
  "z": "deep_review_tab"
316
320
  },
317
321
  {
318
322
  "agent": "deep_review_agent_cfg",
319
323
  "id": "deep_review_wait_preflight",
320
- "intervalMs": 2500,
321
324
  "kinds": "",
322
325
  "name": "wait PREFLIGHT",
326
+ "pollIntervalSec": 2.5,
323
327
  "tail": true,
324
328
  "timeoutSec": 7200,
325
329
  "type": "moltnet-task-wait",
326
- "wires": [["deep_review_tail_link_out"], ["deep_review_read_preflight"]],
327
- "x": 680,
328
- "y": 500,
330
+ "wires": [["deep_review_tail_link_out"], ["deep_review_gate_preflight"]],
331
+ "x": 1040,
332
+ "y": 555,
329
333
  "z": "deep_review_tab"
330
334
  },
331
335
  {
@@ -337,13 +341,13 @@
337
341
  "source": "payload",
338
342
  "type": "moltnet-task-reader",
339
343
  "wires": [["deep_review_stash_preflight"]],
340
- "x": 920,
341
- "y": 500,
344
+ "x": 1790,
345
+ "y": 555,
342
346
  "z": "deep_review_tab"
343
347
  },
344
348
  {
345
349
  "finalize": "",
346
- "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || {};\nmsg.preflight = msg.result?.artifactBody || msg.payload || {};\nmsg.preflight.verdict = String(msg.preflight.verdict || 'ASK').toUpperCase();\nstate.correlationId = msg.correlationId;\nstate.review = state.review || msg.review;\nstate.reviewBundle = state.reviewBundle || msg.reviewBundle;\nstate.reviewBundleRef = state.reviewBundleRef || msg.reviewBundleRef;\nstate.preflight = msg.preflight;\nstate.preflightRef = msg.result?.artifactRef || msg.result?.outputRef || null;\nstate.tasks = state.tasks || {};\nstate.tasks.preflight = { taskId: msg.result?.outputRef?.taskId || msg.payload?.taskId || null };\nflow.set(key, state);\nmsg.reviewState = state;\nreturn msg;",
350
+ "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || {};\nmsg.preflight = msg.result?.artifactBody || msg.payload || {};\nmsg.preflight.verdict = String(msg.preflight.verdict || 'PROCEED').toUpperCase();\nstate.correlationId = msg.correlationId;\nstate.review = state.review || msg.review;\nstate.reviewBundle = state.reviewBundle || msg.reviewBundle;\nstate.reviewBundleRef = state.reviewBundleRef || msg.reviewBundleRef;\nstate.preflight = msg.preflight;\nstate.preflightRef = msg.result?.artifactRef || msg.result?.outputRef || null;\nmsg.review = state.review || msg.review;\nmsg.review.preflightTaskId = msg.preflightSnapshot?.taskId || msg.result?.outputRef?.taskId || null;\nmsg.review.preflightAttemptN = msg.preflightSnapshot?.acceptedAttemptN || msg.preflightSnapshot?.attempt?.attemptN || null;\nmsg.taskId = msg.review.preflightTaskId;\nmsg.attemptN = msg.review.preflightAttemptN;\nstate.tasks = state.tasks || {};\nstate.tasks.preflight = { taskId: msg.review.preflightTaskId, attemptN: msg.review.preflightAttemptN };\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nreturn msg;",
347
351
  "id": "deep_review_stash_preflight",
348
352
  "initialize": "",
349
353
  "libs": [],
@@ -352,25 +356,22 @@
352
356
  "outputs": 1,
353
357
  "timeout": 0,
354
358
  "type": "function",
355
- "wires": [["deep_review_switch_preflight"]],
356
- "x": 1140,
357
- "y": 500,
359
+ "wires": [
360
+ ["deep_review_switch_preflight", "deep_review_get_preflight_session"]
361
+ ],
362
+ "x": 2030,
363
+ "y": 555,
358
364
  "z": "deep_review_tab"
359
365
  },
360
366
  {
361
367
  "checkall": "true",
362
368
  "id": "deep_review_switch_preflight",
363
369
  "name": "preflight verdict",
364
- "outputs": 4,
370
+ "outputs": 3,
365
371
  "property": "preflight.verdict",
366
372
  "propertyType": "msg",
367
373
  "repair": false,
368
374
  "rules": [
369
- {
370
- "t": "eq",
371
- "v": "ASK",
372
- "vt": "str"
373
- },
374
375
  {
375
376
  "t": "eq",
376
377
  "v": "PIVOT",
@@ -387,13 +388,12 @@
387
388
  ],
388
389
  "type": "switch",
389
390
  "wires": [
390
- ["deep_review_debug_ask"],
391
- ["deep_review_build_pivot_publish"],
391
+ ["0e5bc273a0b26717"],
392
392
  ["deep_review_dimensions_payload"],
393
- ["deep_review_debug_ask"]
393
+ ["deep_review_dimensions_payload"]
394
394
  ],
395
- "x": 200,
396
- "y": 660,
395
+ "x": 2240,
396
+ "y": 555,
397
397
  "z": "deep_review_tab"
398
398
  },
399
399
  {
@@ -407,9 +407,9 @@
407
407
  "outputs": 1,
408
408
  "timeout": 0,
409
409
  "type": "function",
410
- "wires": [["deep_review_create_pivot_publish"]],
411
- "x": 460,
412
- "y": 640,
410
+ "wires": [["deep_review_builder_pivot_publish"]],
411
+ "x": 270,
412
+ "y": 705,
413
413
  "z": "deep_review_tab"
414
414
  },
415
415
  {
@@ -421,22 +421,22 @@
421
421
  "runtimeProfile": "deep_review_profile_aggregate",
422
422
  "type": "moltnet-tasks-create",
423
423
  "wires": [["deep_review_wait_pivot_publish"]],
424
- "x": 740,
425
- "y": 640,
424
+ "x": 840,
425
+ "y": 705,
426
426
  "z": "deep_review_tab"
427
427
  },
428
428
  {
429
429
  "agent": "deep_review_agent_cfg",
430
430
  "id": "deep_review_wait_pivot_publish",
431
- "intervalMs": 2500,
432
431
  "kinds": "",
433
432
  "name": "wait PIVOT",
433
+ "pollIntervalSec": 2.5,
434
434
  "tail": true,
435
435
  "timeoutSec": 7200,
436
436
  "type": "moltnet-task-wait",
437
- "wires": [["d673ff9b232453fe"], ["deep_review_pivot_done_link_out"]],
438
- "x": 1000,
439
- "y": 640,
437
+ "wires": [["d673ff9b232453fe"], ["deep_review_gate_pivot_publish"]],
438
+ "x": 1110,
439
+ "y": 705,
440
440
  "z": "deep_review_tab"
441
441
  },
442
442
  {
@@ -450,17 +450,14 @@
450
450
  "outputs": 2,
451
451
  "timeout": 0,
452
452
  "type": "function",
453
- "wires": [
454
- ["deep_review_build_specialist"],
455
- ["deep_review_build_aggregate"]
456
- ],
457
- "x": 440,
458
- "y": 720,
453
+ "wires": [["85888005a9fb88bc"], ["791ab27471c4788e"]],
454
+ "x": 150,
455
+ "y": 865,
459
456
  "z": "deep_review_tab"
460
457
  },
461
458
  {
462
459
  "finalize": "",
463
- "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || msg.reviewState || {};\nconst expected = Number(state.expectedSpecialists || state.dimensions?.length || 0);\nconst completed = Array.isArray(state.specialistResults) ? state.specialistResults.length : 0;\nstate.completedSpecialists = completed;\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.review = state.review || msg.review;\nmsg.reviewBundle = state.reviewBundle || msg.reviewBundle;\nmsg.reviewBundleRef = state.reviewBundleRef || msg.reviewBundleRef;\nmsg.preflight = state.preflight || msg.preflight;\nmsg.specialistResults = state.specialistResults || [];\nmsg.pendingDimensions = [];\nif (expected === 0) {\n node.status({ fill: 'yellow', shape: 'ring', text: 'no specialist dimensions' });\n return msg;\n}\nif (completed < expected) {\n node.status({ fill: 'blue', shape: 'dot', text: `${completed}/${expected} specialist(s)` });\n return null;\n}\nif (state.aggregateStarted) {\n node.status({ fill: 'grey', shape: 'ring', text: 'aggregate already started' });\n return null;\n}\nstate.aggregateStarted = true;\nstate.aggregateReadyAt = new Date().toISOString();\nflow.set(key, state);\nmsg.reviewState = state;\nnode.status({ fill: 'green', shape: 'dot', text: `${completed}/${expected} complete` });\nreturn msg;",
460
+ "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || msg.reviewState || {};\nconst expected = Number(state.expectedSpecialists || state.dimensions?.length || 0);\nconst completed = Array.isArray(state.specialistResults) ? state.specialistResults.length : 0;\nstate.completedSpecialists = completed;\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.review = state.review || msg.review;\nmsg.reviewBundle = state.reviewBundle || msg.reviewBundle;\nmsg.reviewBundleRef = state.reviewBundleRef || msg.reviewBundleRef;\nmsg.preflight = state.preflight || msg.preflight;\nmsg.specialistResults = state.specialistResults || [];\nmsg.pendingDimensions = [];\nif (expected === 0) {\n node.status({ fill: 'yellow', shape: 'ring', text: 'no specialist dimensions' });\n return msg;\n}\nif (completed < expected) {\n node.status({ fill: 'blue', shape: 'dot', text: `${completed}/${expected} specialist(s)` });\n return null;\n}\nif (state.aggregateStarted) {\n node.status({ fill: 'grey', shape: 'ring', text: 'aggregate already started' });\n return null;\n}\nstate.aggregateStarted = true;\nstate.aggregateReadyAt = new Date().toISOString();\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nnode.status({ fill: 'green', shape: 'dot', text: `${completed}/${expected} complete` });\nreturn msg;",
464
461
  "id": "deep_review_next_dimension",
465
462
  "initialize": "",
466
463
  "libs": [],
@@ -469,9 +466,9 @@
469
466
  "outputs": 1,
470
467
  "timeout": 0,
471
468
  "type": "function",
472
- "wires": [["deep_review_build_aggregate"]],
473
- "x": 540,
474
- "y": 820,
469
+ "wires": [["121f5380eb3e3a9f"]],
470
+ "x": 270,
471
+ "y": 1145,
475
472
  "z": "deep_review_tab"
476
473
  },
477
474
  {
@@ -485,9 +482,9 @@
485
482
  "outputs": 1,
486
483
  "timeout": 0,
487
484
  "type": "function",
488
- "wires": [["deep_review_create_specialist"]],
489
- "x": 770,
490
- "y": 720,
485
+ "wires": [["deep_review_builder_specialist"]],
486
+ "x": 270,
487
+ "y": 945,
491
488
  "z": "deep_review_tab"
492
489
  },
493
490
  {
@@ -499,22 +496,23 @@
499
496
  "runtimeProfile": "deep_review_profile_specialist",
500
497
  "type": "moltnet-tasks-create",
501
498
  "wires": [["deep_review_wait_specialist"]],
502
- "x": 1010,
503
- "y": 720,
499
+ "x": 840,
500
+ "y": 945,
504
501
  "z": "deep_review_tab"
505
502
  },
506
503
  {
507
504
  "agent": "deep_review_agent_cfg",
508
505
  "id": "deep_review_wait_specialist",
509
- "intervalMs": 2500,
510
506
  "kinds": "",
511
507
  "name": "wait SPECIALIST",
508
+ "pollIntervalSec": 2.5,
512
509
  "tail": true,
510
+ "taskId": "",
513
511
  "timeoutSec": 7200,
514
512
  "type": "moltnet-task-wait",
515
- "wires": [["cceba4a8c3d9aafd"], ["deep_review_read_specialist"]],
516
- "x": 1250,
517
- "y": 720,
513
+ "wires": [["cceba4a8c3d9aafd"], ["deep_review_gate_specialist"]],
514
+ "x": 1110,
515
+ "y": 945,
518
516
  "z": "deep_review_tab"
519
517
  },
520
518
  {
@@ -526,13 +524,13 @@
526
524
  "source": "payload",
527
525
  "type": "moltnet-task-reader",
528
526
  "wires": [["deep_review_pack_specialist"]],
529
- "x": 1480,
530
- "y": 720,
527
+ "x": 1600,
528
+ "y": 925,
531
529
  "z": "deep_review_tab"
532
530
  },
533
531
  {
534
532
  "finalize": "",
535
- "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || msg.reviewState || {};\nconst result = {\n dimension: msg.reviewDimension || msg.payload?.dimension || state.currentDimension,\n output: msg.payload,\n artifact: msg.result?.artifact || null,\n artifactBody: msg.result?.artifactBody || null,\n artifactRef: msg.result?.artifactRef || null,\n outputRef: msg.result?.outputRef || null\n};\nstate.specialistResults = Array.isArray(state.specialistResults) ? state.specialistResults : [];\nstate.specialistResults = state.specialistResults.filter((item) => item.dimension !== result.dimension);\nstate.specialistResults.push(result);\nstate.tasks = state.tasks || {};\nstate.tasks.specialists = state.tasks.specialists || {};\nstate.tasks.specialists[result.dimension] = {\n taskId: result.outputRef?.taskId || msg.payload?.id || null,\n outputCid: result.outputRef?.outputCid || null,\n artifactCid: result.artifactRef?.artifactCid || result.artifact?.cid || null\n};\nstate.completedSpecialists = state.specialistResults.length;\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.review = state.review || msg.review;\nmsg.reviewBundle = state.reviewBundle || msg.reviewBundle;\nmsg.reviewBundleRef = state.reviewBundleRef || msg.reviewBundleRef;\nmsg.preflight = state.preflight || msg.preflight;\nmsg.specialistResults = state.specialistResults;\nmsg.pendingDimensions = [];\nmsg.payload = null;\nreturn msg;",
533
+ "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || msg.reviewState || {};\nconst result = {\n dimension: msg.reviewDimension || msg.payload?.dimension || state.currentDimension,\n output: msg.payload,\n artifact: msg.result?.artifact || null,\n artifactBody: msg.result?.artifactBody || null,\n artifactRef: msg.result?.artifactRef || null,\n outputRef: msg.result?.outputRef || null\n};\nstate.specialistResults = Array.isArray(state.specialistResults) ? state.specialistResults : [];\nstate.specialistResults = state.specialistResults.filter((item) => item.dimension !== result.dimension);\nstate.specialistResults.push(result);\nstate.tasks = state.tasks || {};\nstate.tasks.specialists = state.tasks.specialists || {};\nstate.tasks.specialists[result.dimension] = {\n taskId: result.outputRef?.taskId || msg.payload?.id || null,\n outputCid: result.outputRef?.outputCid || null,\n artifactCid: result.artifactRef?.artifactCid || result.artifact?.cid || null\n};\nstate.completedSpecialists = state.specialistResults.length;\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.review = state.review || msg.review;\nmsg.reviewBundle = state.reviewBundle || msg.reviewBundle;\nmsg.reviewBundleRef = state.reviewBundleRef || msg.reviewBundleRef;\nmsg.preflight = state.preflight || msg.preflight;\nmsg.specialistResults = state.specialistResults;\nmsg.pendingDimensions = [];\nmsg.payload = null;\nreturn msg;",
536
534
  "id": "deep_review_pack_specialist",
537
535
  "initialize": "",
538
536
  "libs": [],
@@ -541,15 +539,16 @@
541
539
  "outputs": 1,
542
540
  "timeout": 0,
543
541
  "type": "function",
544
- "wires": [["deep_review_next_dimension"]],
545
- "x": 1710,
546
- "y": 720,
542
+ "wires": [["deep_review_specialist_result_link_out"]],
543
+ "x": 1850,
544
+ "y": 925,
547
545
  "z": "deep_review_tab"
548
546
  },
549
547
  {
550
548
  "id": "deep_review_specialist_error_catch",
551
549
  "name": "SPECIALIST errors",
552
550
  "scope": [
551
+ "deep_review_builder_specialist",
553
552
  "deep_review_create_specialist",
554
553
  "deep_review_wait_specialist",
555
554
  "deep_review_read_specialist"
@@ -557,13 +556,13 @@
557
556
  "type": "catch",
558
557
  "uncaught": false,
559
558
  "wires": [["deep_review_pack_specialist_error"]],
560
- "x": 1490,
561
- "y": 780,
559
+ "x": 1600,
560
+ "y": 1010,
562
561
  "z": "deep_review_tab"
563
562
  },
564
563
  {
565
564
  "finalize": "",
566
- "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || msg.reviewState || {};\nconst payload = msg.payload && typeof msg.payload === 'object' ? msg.payload : {};\nconst tags = Array.isArray(payload.tags) ? payload.tags : [];\nconst dimensionTag = tags.find((tag) => typeof tag === 'string' && tag.startsWith('dimension:'));\nconst dimension = msg.reviewDimension || payload.dimension || (typeof dimensionTag === 'string' ? dimensionTag.slice('dimension:'.length) : null);\nif (!dimension) {\n node.status({ fill: 'red', shape: 'ring', text: 'missing dimension' });\n return null;\n}\nconst error = msg.error || {};\nconst result = {\n dimension,\n status: 'failed',\n output: null,\n artifact: null,\n artifactBody: {\n dimension,\n findings: [],\n clean: false,\n coverageNotes: 'Specialist task failed before readable findings were available.',\n failed: true,\n error: {\n message: String(error.message || 'specialist lane failed'),\n source: error.source || null\n }\n },\n artifactRef: null,\n outputRef: null,\n error: {\n message: String(error.message || 'specialist lane failed'),\n source: error.source || null\n }\n};\nstate.specialistResults = Array.isArray(state.specialistResults) ? state.specialistResults : [];\nstate.specialistResults = state.specialistResults.filter((item) => item.dimension !== dimension);\nstate.specialistResults.push(result);\nstate.tasks = state.tasks || {};\nstate.tasks.specialists = state.tasks.specialists || {};\nstate.tasks.specialists[dimension] = {\n status: 'failed',\n taskId: payload.id || null,\n error: result.error\n};\nstate.completedSpecialists = state.specialistResults.length;\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.review = state.review || msg.review;\nmsg.reviewBundle = state.reviewBundle || msg.reviewBundle;\nmsg.reviewBundleRef = state.reviewBundleRef || msg.reviewBundleRef;\nmsg.preflight = state.preflight || msg.preflight;\nmsg.specialistResults = state.specialistResults;\nmsg.pendingDimensions = [];\nmsg.payload = null;\nnode.status({ fill: 'yellow', shape: 'ring', text: `${dimension} failed` });\nreturn msg;",
565
+ "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || msg.reviewState || {};\nconst payload = msg.payload && typeof msg.payload === 'object' ? msg.payload : {};\nconst tags = Array.isArray(payload.tags) ? payload.tags : [];\nconst dimensionTag = tags.find((tag) => typeof tag === 'string' && tag.startsWith('dimension:'));\nconst dimension = msg.reviewDimension || payload.dimension || (typeof dimensionTag === 'string' ? dimensionTag.slice('dimension:'.length) : null);\nif (!dimension) {\n node.status({ fill: 'red', shape: 'ring', text: 'missing dimension' });\n return null;\n}\nconst error = msg.error || {};\nconst result = {\n dimension,\n status: 'failed',\n output: null,\n artifact: null,\n artifactBody: {\n dimension,\n findings: [],\n clean: false,\n coverageNotes: 'Specialist task failed before readable findings were available.',\n failed: true,\n error: {\n message: String(error.message || 'specialist lane failed'),\n source: error.source || null\n }\n },\n artifactRef: null,\n outputRef: null,\n error: {\n message: String(error.message || 'specialist lane failed'),\n source: error.source || null\n }\n};\nstate.specialistResults = Array.isArray(state.specialistResults) ? state.specialistResults : [];\nstate.specialistResults = state.specialistResults.filter((item) => item.dimension !== dimension);\nstate.specialistResults.push(result);\nstate.tasks = state.tasks || {};\nstate.tasks.specialists = state.tasks.specialists || {};\nstate.tasks.specialists[dimension] = {\n status: 'failed',\n taskId: payload.id || null,\n error: result.error\n};\nstate.completedSpecialists = state.specialistResults.length;\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.review = state.review || msg.review;\nmsg.reviewBundle = state.reviewBundle || msg.reviewBundle;\nmsg.reviewBundleRef = state.reviewBundleRef || msg.reviewBundleRef;\nmsg.preflight = state.preflight || msg.preflight;\nmsg.specialistResults = state.specialistResults;\nmsg.pendingDimensions = [];\nmsg.payload = null;\nnode.status({ fill: 'yellow', shape: 'ring', text: `${dimension} failed` });\nreturn msg;",
567
566
  "id": "deep_review_pack_specialist_error",
568
567
  "initialize": "",
569
568
  "libs": [],
@@ -572,14 +571,14 @@
572
571
  "outputs": 1,
573
572
  "timeout": 0,
574
573
  "type": "function",
575
- "wires": [["deep_review_next_dimension"]],
576
- "x": 1750,
577
- "y": 780,
574
+ "wires": [["deep_review_specialist_result_link_out"]],
575
+ "x": 1850,
576
+ "y": 1010,
578
577
  "z": "deep_review_tab"
579
578
  },
580
579
  {
581
580
  "finalize": "",
582
- "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || msg.reviewState || {};\nmsg.review = state.review || msg.review;\nmsg.reviewBundle = state.reviewBundle || msg.reviewBundle;\nmsg.preflight = state.preflight || msg.preflight;\nmsg.specialistResults = state.specialistResults || msg.specialistResults || [];\nstate.aggregateRequestedAt = new Date().toISOString();\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.payload = {\n taskType: 'freeform',\n title: 'Deep review: aggregate findings',\n tags: ['deep-review', 'deep-review:aggregate', msg.review.workflow],\n correlationId: msg.correlationId,\n input: {\n brief: [\n 'Aggregate the specialist deep-review results into one final review.',\n '',\n 'Dedupe findings, keep the highest severity, rank globally, surface themes, and check whether any changed files in the review bundle were not covered.',\n '',\n 'Some specialist results may have status \"failed\" with an error instead of findings. Treat those as coverage gaps, include them in the final review summary, and do not invent findings for failed dimensions.',\n '',\n 'Return a freeform artifact with kind \"markdown\" and title \"final-review\". Also include a compact kind \"json\" artifact titled \"review-summary\" with fields: verdict, topRisks, findingCounts, coverage.',\n '',\n 'If the submit tool requires a verification block, every verification.results[].kind must be one of: gate, assertion, rubric, sideEffect. For a submit-tool-call success criterion, report it as kind \"gate\"; never use \"submit-tool-call\" as a verification result kind.'\n ].join('\\n'),\n expectedOutput: 'Summary plus artifacts markdown/final-review and json/review-summary.',\n constraints: ['Do not modify code.', 'Do not publish externally unless publishPolicy requires a separate publish task.', 'When self-verifying a submit-tool-call gate, use verification result kind \"gate\".'],\n context: [\n { slug: 'review-config', binding: 'context_inline', content: JSON.stringify(msg.review, null, 2) },\n { slug: 'review-bundle', binding: 'context_inline', content: JSON.stringify(msg.reviewBundle, null, 2) },\n { slug: 'preflight', binding: 'context_inline', content: JSON.stringify(msg.preflight, null, 2) },\n { slug: 'specialist-results', binding: 'context_inline', content: JSON.stringify(msg.specialistResults, null, 2) },\n { slug: 'review-run-state', binding: 'context_inline', content: JSON.stringify({ correlationId: state.correlationId, tasks: state.tasks, dimensions: state.dimensions }, null, 2) }\n ],\n execution: { workspace: 'none' }\n }\n};\nreturn msg;",
581
+ "func": "const key = 'deepReview:' + msg.correlationId;\nconst state = flow.get(key) || msg.reviewState || {};\nmsg.review = state.review || msg.review;\nmsg.reviewBundle = state.reviewBundle || msg.reviewBundle;\nmsg.preflight = state.preflight || msg.preflight;\nmsg.specialistResults = state.specialistResults || msg.specialistResults || [];\nstate.aggregateRequestedAt = new Date().toISOString();\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.payload = {\n taskType: 'freeform',\n title: 'Deep review: aggregate findings',\n tags: ['deep-review', 'deep-review:aggregate', msg.review.workflow],\n correlationId: msg.correlationId,\n input: {\n brief: [\n 'Aggregate the specialist deep-review results into one final review.',\n '',\n 'Dedupe findings, keep the highest severity, rank globally, surface themes, and check whether any changed files in the review bundle were not covered.',\n '',\n 'Some specialist results may have status \"failed\" with an error instead of findings. Treat those as coverage gaps, include them in the final review summary, and do not invent findings for failed dimensions.',\n '',\n 'Return a freeform artifact with kind \"markdown\" and title \"final-review\". Also include a compact kind \"json\" artifact titled \"review-summary\" with fields: verdict, topRisks, findingCounts, coverage.',\n '',\n 'If the submit tool requires a verification block, every verification.results[].kind must be one of: gate, assertion, rubric, sideEffect. For a submit-tool-call success criterion, report it as kind \"gate\"; never use \"submit-tool-call\" as a verification result kind.'\n ].join('\\n'),\n expectedOutput: 'Summary plus artifacts markdown/final-review and json/review-summary.',\n constraints: ['Do not modify code.', 'Do not publish externally unless publishPolicy requires a separate publish task.', 'When self-verifying a submit-tool-call gate, use verification result kind \"gate\".'],\n context: [\n { slug: 'review-config', binding: 'context_inline', content: JSON.stringify(msg.review, null, 2) },\n { slug: 'review-bundle', binding: 'context_inline', content: JSON.stringify(msg.reviewBundle, null, 2) },\n { slug: 'preflight', binding: 'context_inline', content: JSON.stringify(msg.preflight, null, 2) },\n { slug: 'specialist-results', binding: 'context_inline', content: JSON.stringify(msg.specialistResults, null, 2) },\n { slug: 'review-run-state', binding: 'context_inline', content: JSON.stringify({ correlationId: state.correlationId, tasks: state.tasks, dimensions: state.dimensions }, null, 2) }\n ],\n execution: { workspace: 'none' }\n }\n};\nreturn msg;",
583
582
  "id": "deep_review_build_aggregate",
584
583
  "initialize": "",
585
584
  "libs": [],
@@ -588,9 +587,9 @@
588
587
  "outputs": 1,
589
588
  "timeout": 0,
590
589
  "type": "function",
591
- "wires": [["deep_review_create_aggregate"]],
592
- "x": 840,
593
- "y": 820,
590
+ "wires": [["deep_review_builder_aggregate"]],
591
+ "x": 270,
592
+ "y": 1225,
594
593
  "z": "deep_review_tab"
595
594
  },
596
595
  {
@@ -602,39 +601,22 @@
602
601
  "runtimeProfile": "deep_review_profile_aggregate",
603
602
  "type": "moltnet-tasks-create",
604
603
  "wires": [["deep_review_wait_aggregate"]],
605
- "x": 1100,
606
- "y": 820,
604
+ "x": 840,
605
+ "y": 1225,
607
606
  "z": "deep_review_tab"
608
607
  },
609
608
  {
610
609
  "agent": "deep_review_agent_cfg",
611
610
  "id": "deep_review_wait_aggregate",
612
- "intervalMs": 2500,
613
611
  "kinds": "",
614
612
  "name": "wait AGGREGATE",
613
+ "pollIntervalSec": 2.5,
615
614
  "tail": true,
616
615
  "timeoutSec": 7200,
617
616
  "type": "moltnet-task-wait",
618
- "wires": [["3ac746883d75229d"], ["deep_review_aggregate_done_link_out"]],
619
- "x": 1340,
620
- "y": 820,
621
- "z": "deep_review_tab"
622
- },
623
- {
624
- "active": true,
625
- "complete": "preflight",
626
- "console": false,
627
- "id": "deep_review_debug_ask",
628
- "name": "needs human input",
629
- "statusType": "auto",
630
- "statusVal": "",
631
- "targetType": "msg",
632
- "tosidebar": true,
633
- "tostatus": false,
634
- "type": "debug",
635
- "wires": [],
636
- "x": 460,
637
- "y": 580,
617
+ "wires": [["3ac746883d75229d"], ["deep_review_gate_aggregate"]],
618
+ "x": 1110,
619
+ "y": 1225,
638
620
  "z": "deep_review_tab"
639
621
  },
640
622
  {
@@ -644,8 +626,8 @@
644
626
  "name": "tail events",
645
627
  "type": "link out",
646
628
  "wires": [],
647
- "x": 855,
648
- "y": 440,
629
+ "x": 1260,
630
+ "y": 510,
649
631
  "z": "deep_review_tab"
650
632
  },
651
633
  {
@@ -660,8 +642,8 @@
660
642
  "name": "tail events",
661
643
  "type": "link in",
662
644
  "wires": [["deep_review_tail"]],
663
- "x": 215,
664
- "y": 960,
645
+ "x": 120,
646
+ "y": 1760,
665
647
  "z": "deep_review_tab"
666
648
  },
667
649
  {
@@ -677,8 +659,8 @@
677
659
  "tostatus": false,
678
660
  "type": "debug",
679
661
  "wires": [],
680
- "x": 360,
681
- "y": 960,
662
+ "x": 320,
663
+ "y": 1760,
682
664
  "z": "deep_review_tab"
683
665
  },
684
666
  {
@@ -694,8 +676,8 @@
694
676
  "tostatus": false,
695
677
  "type": "debug",
696
678
  "wires": [],
697
- "x": 370,
698
- "y": 1080,
679
+ "x": 320,
680
+ "y": 1840,
699
681
  "z": "deep_review_tab"
700
682
  },
701
683
  {
@@ -705,8 +687,8 @@
705
687
  "name": "tail events",
706
688
  "type": "link out",
707
689
  "wires": [],
708
- "x": 755,
709
- "y": 140,
690
+ "x": 1120,
691
+ "y": 220,
710
692
  "z": "deep_review_tab"
711
693
  },
712
694
  {
@@ -716,8 +698,8 @@
716
698
  "name": "tail events",
717
699
  "type": "link out",
718
700
  "wires": [],
719
- "x": 1575,
720
- "y": 780,
701
+ "x": 1320,
702
+ "y": 1175,
721
703
  "z": "deep_review_tab"
722
704
  },
723
705
  {
@@ -727,8 +709,8 @@
727
709
  "name": "tail events",
728
710
  "type": "link out",
729
711
  "wires": [],
730
- "x": 1435,
731
- "y": 680,
712
+ "x": 1320,
713
+ "y": 895,
732
714
  "z": "deep_review_tab"
733
715
  },
734
716
  {
@@ -738,21 +720,22 @@
738
720
  "name": "tail events",
739
721
  "type": "link out",
740
722
  "wires": [],
741
- "x": 1145,
742
- "y": 600,
723
+ "x": 1320,
724
+ "y": 655,
743
725
  "z": "deep_review_tab"
744
726
  },
745
727
  {
746
728
  "id": "deep_review_done_link_in",
747
729
  "links": [
748
730
  "deep_review_pivot_done_link_out",
749
- "deep_review_aggregate_done_link_out"
731
+ "deep_review_aggregate_done_link_out",
732
+ "deep_review_failure_done_link_out"
750
733
  ],
751
734
  "name": "review done in",
752
735
  "type": "link in",
753
736
  "wires": [["deep_review_done"]],
754
- "x": 215,
755
- "y": 1080,
737
+ "x": 120,
738
+ "y": 1840,
756
739
  "z": "deep_review_tab"
757
740
  },
758
741
  {
@@ -762,8 +745,8 @@
762
745
  "name": "review done out",
763
746
  "type": "link out",
764
747
  "wires": [],
765
- "x": 1145,
766
- "y": 660,
748
+ "x": 1590,
749
+ "y": 705,
767
750
  "z": "deep_review_tab"
768
751
  },
769
752
  {
@@ -773,8 +756,872 @@
773
756
  "name": "review done out",
774
757
  "type": "link out",
775
758
  "wires": [],
776
- "x": 1575,
777
- "y": 840,
759
+ "x": 1600,
760
+ "y": 1225,
761
+ "z": "deep_review_tab"
762
+ },
763
+ {
764
+ "agent": "deep_review_agent_cfg",
765
+ "brief": "",
766
+ "constraints": [],
767
+ "contexts": [],
768
+ "diaryId": "",
769
+ "diaryIdType": "str",
770
+ "expectedOutput": "",
771
+ "id": "deep_review_builder_freeze",
772
+ "name": "build FREEZE body",
773
+ "referencesFrom": "",
774
+ "referencesRole": "context",
775
+ "schemaCid": "",
776
+ "submitOutputGate": false,
777
+ "tags": "",
778
+ "taskType": "freeform",
779
+ "teamId": "",
780
+ "teamIdType": "str",
781
+ "title": "",
782
+ "type": "moltnet-task-builder",
783
+ "wires": [["deep_review_create_freeze"]],
784
+ "workspace": "",
785
+ "x": 390,
786
+ "y": 265,
787
+ "z": "deep_review_tab"
788
+ },
789
+ {
790
+ "checkall": "true",
791
+ "id": "deep_review_gate_freeze",
792
+ "name": "FREEZE accepted?",
793
+ "outputs": 2,
794
+ "property": "payload.accepted",
795
+ "propertyType": "msg",
796
+ "repair": false,
797
+ "rules": [
798
+ {
799
+ "t": "true"
800
+ },
801
+ {
802
+ "t": "else"
803
+ }
804
+ ],
805
+ "type": "switch",
806
+ "wires": [
807
+ ["deep_review_stash_freeze_snapshot"],
808
+ ["deep_review_freeze_failure_link_out"]
809
+ ],
810
+ "x": 1140,
811
+ "y": 265,
812
+ "z": "deep_review_tab"
813
+ },
814
+ {
815
+ "agent": "deep_review_agent_cfg",
816
+ "brief": "",
817
+ "constraints": [],
818
+ "contexts": [],
819
+ "diaryId": "",
820
+ "diaryIdType": "str",
821
+ "expectedOutput": "",
822
+ "id": "deep_review_builder_preflight",
823
+ "name": "build PREFLIGHT body",
824
+ "referencesFrom": "",
825
+ "referencesRole": "context",
826
+ "schemaCid": "",
827
+ "submitOutputGate": false,
828
+ "tags": "",
829
+ "taskType": "freeform",
830
+ "teamId": "",
831
+ "teamIdType": "str",
832
+ "title": "",
833
+ "type": "moltnet-task-builder",
834
+ "wires": [["deep_review_create_preflight"]],
835
+ "workspace": "",
836
+ "x": 510,
837
+ "y": 555,
838
+ "z": "deep_review_tab"
839
+ },
840
+ {
841
+ "checkall": "true",
842
+ "id": "deep_review_gate_preflight",
843
+ "name": "PREFLIGHT accepted?",
844
+ "outputs": 2,
845
+ "property": "payload.accepted",
846
+ "propertyType": "msg",
847
+ "repair": false,
848
+ "rules": [
849
+ {
850
+ "t": "true"
851
+ },
852
+ {
853
+ "t": "else"
854
+ }
855
+ ],
856
+ "type": "switch",
857
+ "wires": [
858
+ ["deep_review_stash_preflight_snapshot"],
859
+ ["deep_review_preflight_failure_link_out"]
860
+ ],
861
+ "x": 1280,
862
+ "y": 555,
863
+ "z": "deep_review_tab"
864
+ },
865
+ {
866
+ "agent": "deep_review_agent_cfg",
867
+ "brief": "",
868
+ "constraints": [],
869
+ "contexts": [],
870
+ "diaryId": "",
871
+ "diaryIdType": "str",
872
+ "expectedOutput": "",
873
+ "id": "deep_review_builder_pivot_publish",
874
+ "name": "build PIVOT body",
875
+ "referencesFrom": "",
876
+ "referencesRole": "context",
877
+ "schemaCid": "",
878
+ "submitOutputGate": false,
879
+ "tags": "",
880
+ "taskType": "freeform",
881
+ "teamId": "",
882
+ "teamIdType": "str",
883
+ "title": "",
884
+ "type": "moltnet-task-builder",
885
+ "wires": [["deep_review_create_pivot_publish"]],
886
+ "workspace": "",
887
+ "x": 550,
888
+ "y": 705,
889
+ "z": "deep_review_tab"
890
+ },
891
+ {
892
+ "checkall": "true",
893
+ "id": "deep_review_gate_pivot_publish",
894
+ "name": "PIVOT accepted?",
895
+ "outputs": 2,
896
+ "property": "payload.accepted",
897
+ "propertyType": "msg",
898
+ "repair": false,
899
+ "rules": [
900
+ {
901
+ "t": "true"
902
+ },
903
+ {
904
+ "t": "else"
905
+ }
906
+ ],
907
+ "type": "switch",
908
+ "wires": [
909
+ ["deep_review_pivot_done_link_out"],
910
+ ["deep_review_pivot_failure_link_out"]
911
+ ],
912
+ "x": 1360,
913
+ "y": 705,
914
+ "z": "deep_review_tab"
915
+ },
916
+ {
917
+ "agent": "deep_review_agent_cfg",
918
+ "brief": "",
919
+ "constraints": [],
920
+ "contexts": [],
921
+ "diaryId": "",
922
+ "diaryIdType": "str",
923
+ "expectedOutput": "",
924
+ "id": "deep_review_builder_specialist",
925
+ "name": "build SPECIALIST body",
926
+ "referencesFrom": "",
927
+ "referencesRole": "context",
928
+ "schemaCid": "",
929
+ "submitOutputGate": false,
930
+ "tags": "",
931
+ "taskType": "freeform",
932
+ "teamId": "",
933
+ "teamIdType": "str",
934
+ "title": "",
935
+ "type": "moltnet-task-builder",
936
+ "wires": [["deep_review_create_specialist"]],
937
+ "workspace": "",
938
+ "x": 550,
939
+ "y": 945,
940
+ "z": "deep_review_tab"
941
+ },
942
+ {
943
+ "checkall": "true",
944
+ "id": "deep_review_gate_specialist",
945
+ "name": "SPECIALIST accepted?",
946
+ "outputs": 2,
947
+ "property": "payload.accepted",
948
+ "propertyType": "msg",
949
+ "repair": false,
950
+ "rules": [
951
+ {
952
+ "t": "true"
953
+ },
954
+ {
955
+ "t": "else"
956
+ }
957
+ ],
958
+ "type": "switch",
959
+ "wires": [
960
+ ["deep_review_read_specialist"],
961
+ ["deep_review_pack_specialist_error"]
962
+ ],
963
+ "x": 1360,
964
+ "y": 945,
965
+ "z": "deep_review_tab"
966
+ },
967
+ {
968
+ "agent": "deep_review_agent_cfg",
969
+ "brief": "",
970
+ "constraints": [],
971
+ "contexts": [],
972
+ "diaryId": "",
973
+ "diaryIdType": "str",
974
+ "expectedOutput": "",
975
+ "id": "deep_review_builder_aggregate",
976
+ "name": "build AGGREGATE body",
977
+ "referencesFrom": "",
978
+ "referencesRole": "context",
979
+ "schemaCid": "",
980
+ "submitOutputGate": false,
981
+ "tags": "",
982
+ "taskType": "freeform",
983
+ "teamId": "",
984
+ "teamIdType": "str",
985
+ "title": "",
986
+ "type": "moltnet-task-builder",
987
+ "wires": [["deep_review_create_aggregate"]],
988
+ "workspace": "",
989
+ "x": 550,
990
+ "y": 1225,
991
+ "z": "deep_review_tab"
992
+ },
993
+ {
994
+ "checkall": "true",
995
+ "id": "deep_review_gate_aggregate",
996
+ "name": "AGGREGATE accepted?",
997
+ "outputs": 2,
998
+ "property": "payload.accepted",
999
+ "propertyType": "msg",
1000
+ "repair": false,
1001
+ "rules": [
1002
+ {
1003
+ "t": "true"
1004
+ },
1005
+ {
1006
+ "t": "else"
1007
+ }
1008
+ ],
1009
+ "type": "switch",
1010
+ "wires": [
1011
+ ["deep_review_aggregate_done_link_out"],
1012
+ ["deep_review_aggregate_failure_link_out"]
1013
+ ],
1014
+ "x": 1360,
1015
+ "y": 1225,
1016
+ "z": "deep_review_tab"
1017
+ },
1018
+ {
1019
+ "finalize": "",
1020
+ "func": "const key = 'deepReview:' + (msg.correlationId || 'unknown');\nconst state = flow.get(key) || msg.reviewState || {};\nconst snapshot = msg.payload && typeof msg.payload === 'object' ? msg.payload : {};\nconst task = snapshot.task || {};\nconst attempt = snapshot.attempt || {};\nconst stage = msg.reviewStage || task.title || task.taskType || msg.error?.source?.name || 'deep-review';\nconst error = snapshot.error || attempt.error || msg.error || { message: stage + ' failed' };\nstate.correlationId = msg.correlationId || state.correlationId || null;\nstate.review = state.review || msg.review || null;\nstate.failed = true;\nstate.failure = {\n stage,\n taskId: snapshot.taskId || task.id || msg.taskId || null,\n attemptN: snapshot.acceptedAttemptN || attempt.attemptN || msg.attemptN || null,\n error: {\n message: String(error.message || error.detail || error || 'deep review stage failed'),\n source: error.source || null\n },\n failedAt: new Date().toISOString()\n};\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.payload = {\n workflowStatus: 'failed',\n correlationId: state.correlationId,\n stage,\n failure: state.failure\n};\nnode.status({ fill: 'red', shape: 'ring', text: stage + ' failed' });\nreturn msg;",
1021
+ "id": "deep_review_pack_terminal_failure",
1022
+ "initialize": "",
1023
+ "libs": [],
1024
+ "name": "pack terminal failure",
1025
+ "noerr": 0,
1026
+ "outputs": 1,
1027
+ "timeout": 0,
1028
+ "type": "function",
1029
+ "wires": [["deep_review_prepare_cancel_running"]],
1030
+ "x": 1180,
1031
+ "y": 1435,
1032
+ "z": "deep_review_tab"
1033
+ },
1034
+ {
1035
+ "finalize": "",
1036
+ "func": "const key = 'deepReview:' + (msg.correlationId || 'unknown');\nconst state = flow.get(key) || msg.reviewState || {};\nconst snapshot = msg.payload && typeof msg.payload === 'object' ? msg.payload : {};\nconst task = snapshot.task || {};\nconst attempt = snapshot.attempt || {};\nconst stage = msg.reviewStage || task.title || task.taskType || msg.error?.source?.name || 'deep-review';\nconst error = snapshot.error || attempt.error || msg.error || { message: stage + ' failed' };\nstate.correlationId = msg.correlationId || state.correlationId || null;\nstate.review = state.review || msg.review || null;\nstate.failed = true;\nstate.failure = {\n stage,\n taskId: snapshot.taskId || task.id || msg.taskId || null,\n attemptN: snapshot.acceptedAttemptN || attempt.attemptN || msg.attemptN || null,\n error: {\n message: String(error.message || error.detail || error || 'deep review stage failed'),\n source: error.source || null\n },\n failedAt: new Date().toISOString()\n};\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nmsg.reviewState = state;\nmsg.payload = {\n workflowStatus: 'failed',\n correlationId: state.correlationId,\n stage,\n failure: state.failure\n};\nnode.status({ fill: 'red', shape: 'ring', text: stage + ' failed' });\nreturn msg;",
1037
+ "id": "deep_review_pack_terminal_error",
1038
+ "initialize": "",
1039
+ "libs": [],
1040
+ "name": "pack node error",
1041
+ "noerr": 0,
1042
+ "outputs": 1,
1043
+ "timeout": 0,
1044
+ "type": "function",
1045
+ "wires": [["deep_review_prepare_cancel_running"]],
1046
+ "x": 1180,
1047
+ "y": 1515,
1048
+ "z": "deep_review_tab"
1049
+ },
1050
+ {
1051
+ "finalize": "",
1052
+ "func": "const failurePayload = msg.payload && typeof msg.payload === 'object' ? msg.payload : {};\nconst correlationId = msg.correlationId || failurePayload.correlationId || msg.reviewState?.correlationId;\nmsg.workflowFailurePayload = failurePayload;\nmsg.payload = {\n statuses: ['queued', 'running'],\n limit: 100\n};\nif (correlationId) {\n msg.correlationId = correlationId;\n msg.payload.correlationId = correlationId;\n}\nnode.status({ fill: 'yellow', shape: 'ring', text: correlationId ? 'cleanup ' + correlationId.slice(0, 8) : 'cleanup no correlation' });\nreturn msg;",
1053
+ "id": "deep_review_prepare_cancel_running",
1054
+ "initialize": "",
1055
+ "libs": [],
1056
+ "name": "prepare cancellation query",
1057
+ "noerr": 0,
1058
+ "outputs": 1,
1059
+ "timeout": 0,
1060
+ "type": "function",
1061
+ "wires": [["deep_review_list_running_for_cancel"]],
1062
+ "x": 1450,
1063
+ "y": 1475,
1064
+ "z": "deep_review_tab"
1065
+ },
1066
+ {
1067
+ "agent": "deep_review_agent_cfg",
1068
+ "claimedByAgentId": "",
1069
+ "completedAfter": "",
1070
+ "completedBefore": "",
1071
+ "correlationId": "",
1072
+ "cursor": "",
1073
+ "diaryId": "",
1074
+ "excludeTags": "",
1075
+ "hasAttempts": "",
1076
+ "id": "deep_review_list_running_for_cancel",
1077
+ "limit": 100,
1078
+ "name": "list running tasks",
1079
+ "profileId": "",
1080
+ "proposedByAgentId": "",
1081
+ "proposedByHumanId": "",
1082
+ "queuedAfter": "",
1083
+ "queuedBefore": "",
1084
+ "status": "",
1085
+ "statusList": "",
1086
+ "tags": "",
1087
+ "taskTypes": "",
1088
+ "type": "moltnet-tasks-list",
1089
+ "wires": [["deep_review_cancel_running_tasks"]],
1090
+ "x": 1670,
1091
+ "y": 1475,
1092
+ "z": "deep_review_tab"
1093
+ },
1094
+ {
1095
+ "agent": "deep_review_agent_cfg",
1096
+ "id": "deep_review_cancel_running_tasks",
1097
+ "ignoreErrors": true,
1098
+ "name": "cancel running tasks",
1099
+ "reason": "deep review workflow failed",
1100
+ "skipMissing": true,
1101
+ "taskId": "",
1102
+ "type": "moltnet-task-cancel",
1103
+ "wires": [["deep_review_restore_failure_payload"]],
1104
+ "x": 1890,
1105
+ "y": 1475,
1106
+ "z": "deep_review_tab"
1107
+ },
1108
+ {
1109
+ "finalize": "",
1110
+ "func": "const failurePayload = msg.workflowFailurePayload && typeof msg.workflowFailurePayload === 'object'\n ? msg.workflowFailurePayload\n : { workflowStatus: 'failed', correlationId: msg.correlationId || null };\nconst cancelledTasks = Array.isArray(msg.cancelledTasks) ? msg.cancelledTasks : [];\nconst cancelErrors = Array.isArray(msg.cancelErrors) ? [...msg.cancelErrors] : [];\nif (msg.error && cancelErrors.length === 0) {\n cancelErrors.push({\n taskId: null,\n message: String(msg.error.message || msg.error.detail || msg.error)\n });\n}\nconst cancelledTaskIds = cancelledTasks\n .map((task) => task && (task.id || task.taskId))\n .filter(Boolean);\nconst cancellation = { cancelledTaskIds, cancelErrors };\nmsg.payload = { ...failurePayload, cancellation };\nconst correlationId = msg.correlationId || failurePayload.correlationId;\nif (correlationId) {\n const key = 'deepReview:' + correlationId;\n const state = flow.get(key) || msg.reviewState || {};\n state.cancellation = cancellation;\n flow.set(key, state);\n msg.reviewState = state;\n}\nnode.status({ fill: cancelledTaskIds.length ? 'yellow' : 'grey', shape: 'dot', text: 'cancelled ' + cancelledTaskIds.length });\nreturn msg;",
1111
+ "id": "deep_review_restore_failure_payload",
1112
+ "initialize": "",
1113
+ "libs": [],
1114
+ "name": "restore failure payload",
1115
+ "noerr": 0,
1116
+ "outputs": 1,
1117
+ "timeout": 0,
1118
+ "type": "function",
1119
+ "wires": [["deep_review_failure_done_link_out"]],
1120
+ "x": 2110,
1121
+ "y": 1475,
1122
+ "z": "deep_review_tab"
1123
+ },
1124
+ {
1125
+ "id": "deep_review_cleanup_error_catch",
1126
+ "name": "cleanup errors",
1127
+ "scope": [
1128
+ "deep_review_list_running_for_cancel",
1129
+ "deep_review_cancel_running_tasks"
1130
+ ],
1131
+ "type": "catch",
1132
+ "uncaught": false,
1133
+ "wires": [["deep_review_restore_failure_payload"]],
1134
+ "x": 1890,
1135
+ "y": 1535,
1136
+ "z": "deep_review_tab"
1137
+ },
1138
+ {
1139
+ "id": "deep_review_failure_done_link_out",
1140
+ "links": ["deep_review_done_link_in"],
1141
+ "mode": "link",
1142
+ "name": "review done out",
1143
+ "type": "link out",
1144
+ "wires": [],
1145
+ "x": 2320,
1146
+ "y": 1475,
1147
+ "z": "deep_review_tab"
1148
+ },
1149
+ {
1150
+ "id": "deep_review_task_error_catch",
1151
+ "name": "deep review task errors",
1152
+ "scope": [
1153
+ "deep_review_builder_freeze",
1154
+ "deep_review_create_freeze",
1155
+ "deep_review_wait_freeze",
1156
+ "deep_review_read_freeze",
1157
+ "deep_review_builder_preflight",
1158
+ "deep_review_create_preflight",
1159
+ "deep_review_wait_preflight",
1160
+ "deep_review_read_preflight",
1161
+ "deep_review_builder_pivot_publish",
1162
+ "deep_review_create_pivot_publish",
1163
+ "deep_review_wait_pivot_publish",
1164
+ "deep_review_builder_aggregate",
1165
+ "deep_review_create_aggregate",
1166
+ "deep_review_wait_aggregate"
1167
+ ],
1168
+ "type": "catch",
1169
+ "uncaught": false,
1170
+ "wires": [["deep_review_pack_terminal_error"]],
1171
+ "x": 930,
1172
+ "y": 1515,
1173
+ "z": "deep_review_tab"
1174
+ },
1175
+ {
1176
+ "id": "deep_review_task_status",
1177
+ "name": "",
1178
+ "scope": [
1179
+ "deep_review_builder_freeze",
1180
+ "deep_review_create_freeze",
1181
+ "deep_review_wait_freeze",
1182
+ "deep_review_read_freeze",
1183
+ "deep_review_builder_preflight",
1184
+ "deep_review_create_preflight",
1185
+ "deep_review_wait_preflight",
1186
+ "deep_review_read_preflight",
1187
+ "deep_review_builder_pivot_publish",
1188
+ "deep_review_create_pivot_publish",
1189
+ "deep_review_wait_pivot_publish",
1190
+ "deep_review_builder_specialist",
1191
+ "deep_review_create_specialist",
1192
+ "deep_review_wait_specialist",
1193
+ "deep_review_read_specialist",
1194
+ "deep_review_builder_aggregate",
1195
+ "deep_review_create_aggregate",
1196
+ "deep_review_wait_aggregate"
1197
+ ],
1198
+ "type": "status",
1199
+ "wires": [[]],
1200
+ "x": 930,
1201
+ "y": 1595,
1202
+ "z": "deep_review_tab"
1203
+ },
1204
+ {
1205
+ "id": "deep_review_lane_setup",
1206
+ "info": "Edit the seed node for the PR, branch, or local diff. Optional memory search enriches the review context before tasks start.",
1207
+ "name": "1. Configure target and memory",
1208
+ "type": "comment",
1209
+ "wires": [],
1210
+ "x": 300,
1211
+ "y": 85,
1212
+ "z": "deep_review_tab"
1213
+ },
1214
+ {
1215
+ "id": "deep_review_lane_freeze",
1216
+ "info": "Creates the immutable review bundle. Patch and file-list handoff should use task artifact CIDs, not local paths. The accepted gate prevents failed attempts from being read as review input.",
1217
+ "name": "2. FREEZE scope bundle",
1218
+ "type": "comment",
1219
+ "wires": [],
1220
+ "x": 300,
1221
+ "y": 210,
1222
+ "z": "deep_review_tab"
1223
+ },
1224
+ {
1225
+ "id": "deep_review_lane_bundle_artifacts",
1226
+ "info": "Optional side lanes: list/download persistent bundle artifacts uploaded by FREEZE, and inspect durable runtime-session metadata for completed FREEZE/PREFLIGHT attempts.",
1227
+ "name": "Review bundle and runtime sessions",
1228
+ "type": "comment",
1229
+ "wires": [],
1230
+ "x": 430,
1231
+ "y": 350,
1232
+ "z": "deep_review_tab"
1233
+ },
1234
+ {
1235
+ "id": "deep_review_lane_preflight",
1236
+ "info": "Reads the preflight JSON artifact and branches to PIVOT or specialist review.",
1237
+ "name": "3. PREFLIGHT decision",
1238
+ "type": "comment",
1239
+ "wires": [],
1240
+ "x": 300,
1241
+ "y": 500,
1242
+ "z": "deep_review_tab"
1243
+ },
1244
+ {
1245
+ "id": "deep_review_lane_pivot",
1246
+ "info": "Only runs when preflight returns PIVOT. It produces the final design-review artifact.",
1247
+ "name": "PIVOT publish lane",
1248
+ "type": "comment",
1249
+ "wires": [],
1250
+ "x": 300,
1251
+ "y": 650,
1252
+ "z": "deep_review_tab"
1253
+ },
1254
+ {
1255
+ "id": "deep_review_lane_specialists",
1256
+ "info": "One task per review dimension. Specialist failures are packed as coverage gaps and still count toward fan-in.",
1257
+ "name": "4. Specialist fan-out",
1258
+ "type": "comment",
1259
+ "wires": [],
1260
+ "x": 300,
1261
+ "y": 810,
1262
+ "z": "deep_review_tab"
1263
+ },
1264
+ {
1265
+ "id": "deep_review_lane_aggregate",
1266
+ "info": "Starts once every specialist dimension has either findings or a packed failure record.",
1267
+ "name": "5. Aggregate final review",
1268
+ "type": "comment",
1269
+ "wires": [],
1270
+ "x": 300,
1271
+ "y": 1090,
1272
+ "z": "deep_review_tab"
1273
+ },
1274
+ {
1275
+ "id": "deep_review_lane_failure",
1276
+ "info": "Terminal stage failures and node errors converge here, then route to the same done output as successful endings.",
1277
+ "name": "Failure handling",
1278
+ "type": "comment",
1279
+ "wires": [],
1280
+ "x": 300,
1281
+ "y": 1380,
1282
+ "z": "deep_review_tab"
1283
+ },
1284
+ {
1285
+ "id": "deep_review_lane_outputs",
1286
+ "info": "Tail events and final workflow results are collected through links to keep the main lanes readable.",
1287
+ "name": "Tail and final outputs",
1288
+ "type": "comment",
1289
+ "wires": [],
1290
+ "x": 300,
1291
+ "y": 1700,
1292
+ "z": "deep_review_tab"
1293
+ },
1294
+ {
1295
+ "id": "deep_review_bundle_ready_link_out",
1296
+ "links": ["deep_review_bundle_ready_link_in"],
1297
+ "mode": "link",
1298
+ "name": "bundle ready",
1299
+ "type": "link out",
1300
+ "wires": [],
1301
+ "x": 2140,
1302
+ "y": 265,
1303
+ "z": "deep_review_tab"
1304
+ },
1305
+ {
1306
+ "id": "deep_review_bundle_ready_link_in",
1307
+ "links": ["deep_review_bundle_ready_link_out", "18a480760520ba5a"],
1308
+ "name": "bundle ready",
1309
+ "type": "link in",
1310
+ "wires": [["deep_review_build_preflight"]],
1311
+ "x": 70,
1312
+ "y": 555,
1313
+ "z": "deep_review_tab"
1314
+ },
1315
+ {
1316
+ "id": "deep_review_specialist_result_link_out",
1317
+ "links": ["deep_review_specialist_result_link_in"],
1318
+ "mode": "link",
1319
+ "name": "specialist result",
1320
+ "type": "link out",
1321
+ "wires": [],
1322
+ "x": 2130,
1323
+ "y": 925,
1324
+ "z": "deep_review_tab"
1325
+ },
1326
+ {
1327
+ "id": "deep_review_specialist_result_link_in",
1328
+ "links": ["deep_review_specialist_result_link_out"],
1329
+ "name": "specialist result",
1330
+ "type": "link in",
1331
+ "wires": [["deep_review_next_dimension"]],
1332
+ "x": 70,
1333
+ "y": 1145,
1334
+ "z": "deep_review_tab"
1335
+ },
1336
+ {
1337
+ "id": "deep_review_freeze_failure_link_out",
1338
+ "links": ["deep_review_terminal_failure_link_in"],
1339
+ "mode": "link",
1340
+ "name": "terminal failure",
1341
+ "type": "link out",
1342
+ "wires": [],
1343
+ "x": 1320,
1344
+ "y": 320,
1345
+ "z": "deep_review_tab"
1346
+ },
1347
+ {
1348
+ "id": "deep_review_preflight_failure_link_out",
1349
+ "links": ["deep_review_terminal_failure_link_in"],
1350
+ "mode": "link",
1351
+ "name": "terminal failure",
1352
+ "type": "link out",
1353
+ "wires": [],
1354
+ "x": 1460,
1355
+ "y": 610,
1356
+ "z": "deep_review_tab"
1357
+ },
1358
+ {
1359
+ "id": "deep_review_pivot_failure_link_out",
1360
+ "links": ["deep_review_terminal_failure_link_in"],
1361
+ "mode": "link",
1362
+ "name": "terminal failure",
1363
+ "type": "link out",
1364
+ "wires": [],
1365
+ "x": 1590,
1366
+ "y": 760,
1367
+ "z": "deep_review_tab"
1368
+ },
1369
+ {
1370
+ "id": "deep_review_aggregate_failure_link_out",
1371
+ "links": ["deep_review_terminal_failure_link_in"],
1372
+ "mode": "link",
1373
+ "name": "terminal failure",
1374
+ "type": "link out",
1375
+ "wires": [],
1376
+ "x": 1600,
1377
+ "y": 1280,
1378
+ "z": "deep_review_tab"
1379
+ },
1380
+ {
1381
+ "id": "deep_review_terminal_failure_link_in",
1382
+ "links": [
1383
+ "deep_review_freeze_failure_link_out",
1384
+ "deep_review_preflight_failure_link_out",
1385
+ "deep_review_pivot_failure_link_out",
1386
+ "deep_review_aggregate_failure_link_out"
1387
+ ],
1388
+ "name": "terminal failure",
1389
+ "type": "link in",
1390
+ "wires": [["deep_review_pack_terminal_failure"]],
1391
+ "x": 930,
1392
+ "y": 1435,
1393
+ "z": "deep_review_tab"
1394
+ },
1395
+ {
1396
+ "id": "deep_review_lane_retry",
1397
+ "info": "Filesystem-backed flow context keeps deepReview:<correlationId> across Node-RED restarts. Use the retry inject after fixing credentials/profile config. Empty correlationId retries the last recorded deep-review run; stage=failed retries state.failure.stage. Retry creates a fresh task for the selected workflow stage rather than mutating an existing task attempt.",
1398
+ "name": "Manual retry from saved state",
1399
+ "type": "comment",
1400
+ "wires": [],
1401
+ "x": 360,
1402
+ "y": 1940,
1403
+ "z": "deep_review_tab"
1404
+ },
1405
+ {
1406
+ "crontab": "",
1407
+ "id": "deep_review_retry_failed",
1408
+ "name": "retry failed stage",
1409
+ "once": false,
1410
+ "onceDelay": 0.1,
1411
+ "payload": "{\"correlationId\":\"\",\"stage\":\"failed\",\"dimension\":\"\"}",
1412
+ "payloadType": "json",
1413
+ "props": [
1414
+ {
1415
+ "p": "payload"
1416
+ }
1417
+ ],
1418
+ "repeat": "",
1419
+ "topic": "",
1420
+ "type": "inject",
1421
+ "wires": [["deep_review_load_retry_state"]],
1422
+ "x": 150,
1423
+ "y": 2000,
1424
+ "z": "deep_review_tab"
1425
+ },
1426
+ {
1427
+ "finalize": "",
1428
+ "func": "const input = msg.payload && typeof msg.payload === 'object' ? msg.payload : {};\nconst correlationId =\n msg.correlationId ||\n input.correlationId ||\n flow.get('deepReview:lastCorrelationId');\nif (!correlationId) {\n node.error('retry: correlationId is required, and no last deep-review run is recorded', msg);\n return null;\n}\nconst key = 'deepReview:' + correlationId;\nconst state = flow.get(key);\nif (!state) {\n node.error('retry: no saved deep-review state for ' + correlationId, msg);\n return null;\n}\nconst source = state.failure?.error?.source || {};\nconst rawStage = [\n input.stage,\n msg.retryStage,\n state.failure?.stage,\n source.id,\n source.name\n]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\nlet stage = null;\nif (!rawStage || rawStage.includes('failed')) {\n const failed = [state.failure?.stage, source.id, source.name]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n if (failed.includes('aggregate')) stage = 'aggregate';\n else if (failed.includes('specialist')) stage = 'specialist';\n else if (failed.includes('pivot')) stage = 'pivot';\n else if (failed.includes('preflight')) stage = 'preflight';\n} else if (rawStage.includes('aggregate')) stage = 'aggregate';\nelse if (rawStage.includes('specialist')) stage = 'specialist';\nelse if (rawStage.includes('pivot')) stage = 'pivot';\nelse if (rawStage.includes('preflight')) stage = 'preflight';\nif (!stage || stage === 'freeze') {\n node.error('retry: unsupported stage \"' + (input.stage || state.failure?.stage || 'unknown') + '\". Restart from the beginning for FREEZE.', msg);\n return null;\n}\nmsg.correlationId = correlationId;\nstate.correlationId = state.correlationId || correlationId;\nmsg.reviewState = state;\nmsg.review = state.review;\nmsg.reviewBundle = state.reviewBundle;\nmsg.reviewBundleRef = state.reviewBundleRef;\nmsg.preflight = state.preflight;\nmsg.specialistResults = state.specialistResults || [];\nstate.retryCount = Number(state.retryCount || 0) + 1;\nstate.lastRetriedStage = stage;\nstate.lastRetryRequestedAt = new Date().toISOString();\nstate.failed = false;\nif (stage === 'aggregate') {\n state.aggregateStarted = false;\n state.aggregateRetryRequestedAt = state.lastRetryRequestedAt;\n}\nif (stage === 'specialist') {\n const dimension = input.dimension || msg.reviewDimension || state.failure?.dimension;\n if (!dimension) {\n node.error('retry: specialist retry requires payload.dimension', msg);\n return null;\n }\n msg.reviewDimension = dimension;\n msg.payload = { dimension, retry: true };\n state.specialistResults = (state.specialistResults || []).filter((item) => item.dimension !== dimension);\n state.completedSpecialists = state.specialistResults.length;\n} else {\n msg.payload = { retry: true, stage };\n}\nif (state.correlationId) flow.set('deepReview:lastCorrelationId', state.correlationId);\nflow.set(key, state);\nnode.status({ fill: 'blue', shape: 'dot', text: 'retry ' + stage });\nif (stage === 'aggregate') return [msg, null, null, null];\nif (stage === 'specialist') return [null, msg, null, null];\nif (stage === 'preflight') return [null, null, msg, null];\nreturn [null, null, null, msg];",
1429
+ "id": "deep_review_load_retry_state",
1430
+ "initialize": "",
1431
+ "libs": [],
1432
+ "name": "load retry state",
1433
+ "noerr": 0,
1434
+ "outputLabels": ["AGGREGATE", "SPECIALIST", "PREFLIGHT", "PIVOT"],
1435
+ "outputs": 4,
1436
+ "timeout": 0,
1437
+ "type": "function",
1438
+ "wires": [
1439
+ ["f25f51b3e15378ba"],
1440
+ ["fac3b7976571600e"],
1441
+ ["18a480760520ba5a"],
1442
+ ["edcc698660d82265"]
1443
+ ],
1444
+ "x": 430,
1445
+ "y": 2000,
1446
+ "z": "deep_review_tab"
1447
+ },
1448
+ {
1449
+ "agent": "deep_review_agent_cfg",
1450
+ "allowMsgTeamOverride": false,
1451
+ "attemptN": "",
1452
+ "id": "deep_review_get_freeze_session",
1453
+ "name": "get FREEZE session",
1454
+ "taskId": "",
1455
+ "teamId": "",
1456
+ "type": "moltnet-runtime-session-get",
1457
+ "wires": [["deep_review_debug_runtime_session"]],
1458
+ "x": 950,
1459
+ "y": 425,
1460
+ "z": "deep_review_tab"
1461
+ },
1462
+ {
1463
+ "finalize": "",
1464
+ "func": "msg.preflightSnapshot = msg.payload;\nreturn msg;",
1465
+ "id": "deep_review_stash_preflight_snapshot",
1466
+ "initialize": "",
1467
+ "libs": [],
1468
+ "name": "stash PREFLIGHT snapshot",
1469
+ "noerr": 0,
1470
+ "outputs": 1,
1471
+ "timeout": 0,
1472
+ "type": "function",
1473
+ "wires": [["deep_review_read_preflight"]],
1474
+ "x": 1540,
1475
+ "y": 555,
1476
+ "z": "deep_review_tab"
1477
+ },
1478
+ {
1479
+ "agent": "deep_review_agent_cfg",
1480
+ "allowMsgTeamOverride": false,
1481
+ "attemptN": "",
1482
+ "id": "deep_review_get_preflight_session",
1483
+ "name": "get PREFLIGHT session",
1484
+ "taskId": "",
1485
+ "teamId": "",
1486
+ "type": "moltnet-runtime-session-get",
1487
+ "wires": [["deep_review_debug_runtime_session"]],
1488
+ "x": 1210,
1489
+ "y": 425,
1490
+ "z": "deep_review_tab"
1491
+ },
1492
+ {
1493
+ "active": true,
1494
+ "complete": "runtimeSession",
1495
+ "console": false,
1496
+ "id": "deep_review_debug_runtime_session",
1497
+ "name": "runtime session metadata",
1498
+ "statusType": "auto",
1499
+ "statusVal": "",
1500
+ "targetType": "msg",
1501
+ "tosidebar": true,
1502
+ "tostatus": false,
1503
+ "type": "debug",
1504
+ "wires": [],
1505
+ "x": 1490,
1506
+ "y": 425,
1507
+ "z": "deep_review_tab"
1508
+ },
1509
+ {
1510
+ "id": "16faf32e4fc42dce",
1511
+ "links": ["0e5bc273a0b26717", "edcc698660d82265"],
1512
+ "name": "pivot ready",
1513
+ "type": "link in",
1514
+ "wires": [["deep_review_build_pivot_publish"]],
1515
+ "x": 70,
1516
+ "y": 705,
1517
+ "z": "deep_review_tab"
1518
+ },
1519
+ {
1520
+ "id": "0e5bc273a0b26717",
1521
+ "links": ["16faf32e4fc42dce"],
1522
+ "mode": "link",
1523
+ "name": "pivot ready",
1524
+ "type": "link out",
1525
+ "wires": [],
1526
+ "x": 2440,
1527
+ "y": 555,
1528
+ "z": "deep_review_tab"
1529
+ },
1530
+ {
1531
+ "id": "edcc698660d82265",
1532
+ "links": ["16faf32e4fc42dce"],
1533
+ "mode": "link",
1534
+ "name": "pivot ready",
1535
+ "type": "link out",
1536
+ "wires": [],
1537
+ "x": 720,
1538
+ "y": 2070,
1539
+ "z": "deep_review_tab"
1540
+ },
1541
+ {
1542
+ "id": "0bb7739dbe669278",
1543
+ "links": ["85888005a9fb88bc", "fac3b7976571600e"],
1544
+ "name": "start specialist",
1545
+ "type": "link in",
1546
+ "wires": [["deep_review_build_specialist"]],
1547
+ "x": 70,
1548
+ "y": 945,
1549
+ "z": "deep_review_tab"
1550
+ },
1551
+ {
1552
+ "id": "85888005a9fb88bc",
1553
+ "links": ["0bb7739dbe669278"],
1554
+ "mode": "link",
1555
+ "name": "start specialist",
1556
+ "type": "link out",
1557
+ "wires": [],
1558
+ "x": 370,
1559
+ "y": 835,
1560
+ "z": "deep_review_tab"
1561
+ },
1562
+ {
1563
+ "id": "fac3b7976571600e",
1564
+ "links": ["0bb7739dbe669278"],
1565
+ "mode": "link",
1566
+ "name": "start specialist",
1567
+ "type": "link out",
1568
+ "wires": [],
1569
+ "x": 720,
1570
+ "y": 1990,
1571
+ "z": "deep_review_tab"
1572
+ },
1573
+ {
1574
+ "id": "b3bb0fc6441c8e1c",
1575
+ "links": ["791ab27471c4788e", "121f5380eb3e3a9f", "f25f51b3e15378ba"],
1576
+ "name": "start aggregation",
1577
+ "type": "link in",
1578
+ "wires": [["deep_review_build_aggregate"]],
1579
+ "x": 70,
1580
+ "y": 1225,
1581
+ "z": "deep_review_tab"
1582
+ },
1583
+ {
1584
+ "id": "791ab27471c4788e",
1585
+ "links": ["b3bb0fc6441c8e1c"],
1586
+ "mode": "link",
1587
+ "name": "start aggregation",
1588
+ "type": "link out",
1589
+ "wires": [],
1590
+ "x": 370,
1591
+ "y": 895,
1592
+ "z": "deep_review_tab"
1593
+ },
1594
+ {
1595
+ "id": "121f5380eb3e3a9f",
1596
+ "links": ["b3bb0fc6441c8e1c"],
1597
+ "mode": "link",
1598
+ "name": "start aggregation",
1599
+ "type": "link out",
1600
+ "wires": [],
1601
+ "x": 500,
1602
+ "y": 1145,
1603
+ "z": "deep_review_tab"
1604
+ },
1605
+ {
1606
+ "id": "f25f51b3e15378ba",
1607
+ "links": ["b3bb0fc6441c8e1c"],
1608
+ "mode": "link",
1609
+ "name": "start aggregation",
1610
+ "type": "link out",
1611
+ "wires": [],
1612
+ "x": 720,
1613
+ "y": 1950,
1614
+ "z": "deep_review_tab"
1615
+ },
1616
+ {
1617
+ "id": "18a480760520ba5a",
1618
+ "links": ["deep_review_bundle_ready_link_in"],
1619
+ "mode": "link",
1620
+ "name": "bundle ready",
1621
+ "type": "link out",
1622
+ "wires": [],
1623
+ "x": 720,
1624
+ "y": 2030,
778
1625
  "z": "deep_review_tab"
779
1626
  }
780
1627
  ]