@itsshadowai/refinery 0.1.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.
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/coral/agents/claim-scout/coral-agent.toml +23 -0
- package/coral/agents/decision-synthesizer/coral-agent.toml +23 -0
- package/coral/agents/evidence-auditor/coral-agent.toml +23 -0
- package/coral/agents/memory-cartographer/coral-agent.toml +23 -0
- package/coral/agents/proposal-editor/coral-agent.toml +23 -0
- package/coral/agents/run-worker.sh +19 -0
- package/coral/refinery-config.toml +16 -0
- package/dist/adapters/codex-memory.d.ts +6 -0
- package/dist/adapters/codex-memory.js +264 -0
- package/dist/adapters/codex-memory.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +671 -0
- package/dist/cli.js.map +1 -0
- package/dist/coral/client.d.ts +107 -0
- package/dist/coral/client.js +214 -0
- package/dist/coral/client.js.map +1 -0
- package/dist/coral/definitions.d.ts +25 -0
- package/dist/coral/definitions.js +53 -0
- package/dist/coral/definitions.js.map +1 -0
- package/dist/coral/mcp.d.ts +17 -0
- package/dist/coral/mcp.js +71 -0
- package/dist/coral/mcp.js.map +1 -0
- package/dist/coral/review-conductor.d.ts +120 -0
- package/dist/coral/review-conductor.js +1290 -0
- package/dist/coral/review-conductor.js.map +1 -0
- package/dist/coral/smoke.d.ts +1 -0
- package/dist/coral/smoke.js +265 -0
- package/dist/coral/smoke.js.map +1 -0
- package/dist/coral/topology.d.ts +5 -0
- package/dist/coral/topology.js +15 -0
- package/dist/coral/topology.js.map +1 -0
- package/dist/coral/worker.d.ts +34 -0
- package/dist/coral/worker.js +671 -0
- package/dist/coral/worker.js.map +1 -0
- package/dist/core/adapter.d.ts +93 -0
- package/dist/core/adapter.js +112 -0
- package/dist/core/adapter.js.map +1 -0
- package/dist/core/artifacts.d.ts +93 -0
- package/dist/core/artifacts.js +200 -0
- package/dist/core/artifacts.js.map +1 -0
- package/dist/core/deliberation.d.ts +89 -0
- package/dist/core/deliberation.js +385 -0
- package/dist/core/deliberation.js.map +1 -0
- package/dist/core/errors.d.ts +26 -0
- package/dist/core/errors.js +50 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/intents.d.ts +5 -0
- package/dist/core/intents.js +34 -0
- package/dist/core/intents.js.map +1 -0
- package/dist/core/live-review.d.ts +93 -0
- package/dist/core/live-review.js +269 -0
- package/dist/core/live-review.js.map +1 -0
- package/dist/core/model-client.d.ts +19 -0
- package/dist/core/model-client.js +45 -0
- package/dist/core/model-client.js.map +1 -0
- package/dist/core/paths.d.ts +16 -0
- package/dist/core/paths.js +43 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/review.d.ts +93 -0
- package/dist/core/review.js +102 -0
- package/dist/core/review.js.map +1 -0
- package/dist/core/specialists/claim-scout.d.ts +2 -0
- package/dist/core/specialists/claim-scout.js +26 -0
- package/dist/core/specialists/claim-scout.js.map +1 -0
- package/dist/core/specialists/decision-synthesizer.d.ts +2 -0
- package/dist/core/specialists/decision-synthesizer.js +35 -0
- package/dist/core/specialists/decision-synthesizer.js.map +1 -0
- package/dist/core/specialists/evidence-auditor.d.ts +2 -0
- package/dist/core/specialists/evidence-auditor.js +35 -0
- package/dist/core/specialists/evidence-auditor.js.map +1 -0
- package/dist/core/specialists/harness.d.ts +2 -0
- package/dist/core/specialists/harness.js +13 -0
- package/dist/core/specialists/harness.js.map +1 -0
- package/dist/core/specialists/index.d.ts +8 -0
- package/dist/core/specialists/index.js +8 -0
- package/dist/core/specialists/index.js.map +1 -0
- package/dist/core/specialists/memory-cartographer.d.ts +2 -0
- package/dist/core/specialists/memory-cartographer.js +33 -0
- package/dist/core/specialists/memory-cartographer.js.map +1 -0
- package/dist/core/specialists/prompt.d.ts +3 -0
- package/dist/core/specialists/prompt.js +25 -0
- package/dist/core/specialists/prompt.js.map +1 -0
- package/dist/core/specialists/proposal-editor.d.ts +2 -0
- package/dist/core/specialists/proposal-editor.js +37 -0
- package/dist/core/specialists/proposal-editor.js.map +1 -0
- package/dist/core/specialists/types.d.ts +20 -0
- package/dist/core/specialists/types.js +2 -0
- package/dist/core/specialists/types.js.map +1 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.js +53 -0
- package/dist/env.js.map +1 -0
- package/dist/mcp.d.ts +9 -0
- package/dist/mcp.js +147 -0
- package/dist/mcp.js.map +1 -0
- package/package.json +50 -0
- package/skills/refinery/SKILL.md +117 -0
- package/skills/refinery/agents/openai.yaml +4 -0
|
@@ -0,0 +1,1290 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { allMessages, buildCoralSessionRequest, classifyAgentReadiness, closeSession, createSession, getExtended, getLocalAgent, puppetCreateThread, puppetSendMessage, waitForAgentsReady, } from "./client.js";
|
|
8
|
+
import { refineryCoralAgentNames, refineryCoralAuthKey, refineryCoralConfigPath, refineryCoralModelDefaults, refineryCoralPort, } from "./definitions.js";
|
|
9
|
+
import { defaultReviewTopology } from "./topology.js";
|
|
10
|
+
import { memoryMaintenanceActions, refineryReviewSchemaVersion, } from "../core/adapter.js";
|
|
11
|
+
import { loadLocalEnv, parseModelMaxTokens } from "../env.js";
|
|
12
|
+
import { applyErrorContext, asRefineryError, RefineryError, } from "../core/errors.js";
|
|
13
|
+
import { writeReviewArtifactManifest, reviewStepOrder } from "../core/artifacts.js";
|
|
14
|
+
import { buildDeliberationArtifacts, claimCardsForCritique, } from "../core/deliberation.js";
|
|
15
|
+
import { deliverReviewSink, writeReviewFailureStatus, } from "../core/review.js";
|
|
16
|
+
import { defaultReviewIntent, describeReviewIntent } from "../core/intents.js";
|
|
17
|
+
const DEFAULT_SOURCE_LIMIT = 3;
|
|
18
|
+
const DEFAULT_SOURCE_CHAR_LIMIT = 6000;
|
|
19
|
+
const DEFAULT_PIPELINE_WAIT_TIMEOUT_MS = 180_000;
|
|
20
|
+
const DEFAULT_DEBATE_CRITIQUE_WAIT_TIMEOUT_MS = 600_000;
|
|
21
|
+
const DEFAULT_WAIT_INTERVAL_MS = 1_500;
|
|
22
|
+
const MAX_EXCERPT_CHARS = 1200;
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const repoRoot = path.resolve(__dirname, "../..");
|
|
25
|
+
function writeJson(filePath, value) {
|
|
26
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
27
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
28
|
+
}
|
|
29
|
+
export function defaultCoralReviewTimeoutMs(topology) {
|
|
30
|
+
return topology === "debate-critique"
|
|
31
|
+
? DEFAULT_DEBATE_CRITIQUE_WAIT_TIMEOUT_MS
|
|
32
|
+
: DEFAULT_PIPELINE_WAIT_TIMEOUT_MS;
|
|
33
|
+
}
|
|
34
|
+
function compactText(text, max = MAX_EXCERPT_CHARS) {
|
|
35
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
36
|
+
if (compact.length <= max)
|
|
37
|
+
return compact;
|
|
38
|
+
return `${compact.slice(0, max - 3).trimEnd()}...`;
|
|
39
|
+
}
|
|
40
|
+
function sourceRefs(source) {
|
|
41
|
+
if (source.refs && source.refs.length > 0)
|
|
42
|
+
return source.refs;
|
|
43
|
+
return [{ source_id: source.id, source_path: source.path ?? null, kind: source.kind }];
|
|
44
|
+
}
|
|
45
|
+
function toSourceChunks(sources, charLimit) {
|
|
46
|
+
let remaining = charLimit;
|
|
47
|
+
return sources
|
|
48
|
+
.map((source) => {
|
|
49
|
+
const text = compactText(source.text, Math.max(0, remaining));
|
|
50
|
+
remaining -= text.length;
|
|
51
|
+
return {
|
|
52
|
+
id: source.id,
|
|
53
|
+
kind: source.kind,
|
|
54
|
+
path: source.path ?? null,
|
|
55
|
+
text,
|
|
56
|
+
refs: sourceRefs(source),
|
|
57
|
+
};
|
|
58
|
+
})
|
|
59
|
+
.filter((source) => source.text.length > 0);
|
|
60
|
+
}
|
|
61
|
+
function memoryHints(memories, limit = 12) {
|
|
62
|
+
return memories.slice(0, limit).map((memory) => ({
|
|
63
|
+
id: memory.id,
|
|
64
|
+
type: memory.type,
|
|
65
|
+
scope: memory.scope,
|
|
66
|
+
body: compactText(memory.body, 360),
|
|
67
|
+
provenance: memory.provenance ?? null,
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
function buildConsoleUrl(apiUrl, pathname) {
|
|
71
|
+
try {
|
|
72
|
+
const url = new URL(apiUrl);
|
|
73
|
+
url.pathname = pathname;
|
|
74
|
+
url.search = "";
|
|
75
|
+
url.hash = "";
|
|
76
|
+
return url.toString();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return `${apiUrl.replace(/\/+$/, "")}${pathname}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function resolveConfiguredModel(coral) {
|
|
83
|
+
const localEnv = loadLocalEnv(repoRoot);
|
|
84
|
+
const readConfig = (name) => process.env[name] ?? localEnv[name];
|
|
85
|
+
return {
|
|
86
|
+
provider: readConfig("MODEL_PROVIDER") ?? readConfig("REFINERY_MODEL_PROVIDER") ?? "openrouter",
|
|
87
|
+
baseUrl: coral.modelBaseUrl ?? readConfig("MODEL_BASE_URL") ?? readConfig("REFINERY_MODEL_BASE_URL") ?? refineryCoralModelDefaults.baseUrl,
|
|
88
|
+
modelName: coral.modelName ?? readConfig("MODEL_NAME") ?? readConfig("REFINERY_MODEL_NAME") ?? refineryCoralModelDefaults.modelName,
|
|
89
|
+
reasoningEffort: coral.reasoningEffort ?? readConfig("REASONING_EFFORT") ?? refineryCoralModelDefaults.reasoningEffort,
|
|
90
|
+
maxTokens: parseModelMaxTokens(readConfig("MODEL_MAX_TOKENS") ?? readConfig("REFINERY_MODEL_MAX_TOKENS")),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function buildReviewIntake(args) {
|
|
94
|
+
return {
|
|
95
|
+
schemaVersion: refineryReviewSchemaVersion,
|
|
96
|
+
type: "refinery-review-intake",
|
|
97
|
+
runId: args.runId,
|
|
98
|
+
project: args.project,
|
|
99
|
+
source: args.source,
|
|
100
|
+
target: args.target,
|
|
101
|
+
adapter: args.adapterName,
|
|
102
|
+
scope: args.scope,
|
|
103
|
+
intent: args.intent,
|
|
104
|
+
request: args.request,
|
|
105
|
+
intentDescription: describeReviewIntent(args.intent),
|
|
106
|
+
noApply: true,
|
|
107
|
+
dryRun: true,
|
|
108
|
+
topology: args.topology,
|
|
109
|
+
phase: args.topology === "debate-critique" ? "proposal-intake" : "pipeline",
|
|
110
|
+
sourceLimit: args.sourceLimit,
|
|
111
|
+
sourceCharLimit: args.sourceCharLimit,
|
|
112
|
+
source_chunks: args.sourceChunks,
|
|
113
|
+
active_memory_hints: args.activeMemoryHints,
|
|
114
|
+
proposal_schema: {
|
|
115
|
+
schemaVersion: refineryReviewSchemaVersion,
|
|
116
|
+
lifecycle: "proposed",
|
|
117
|
+
writesAttempted: false,
|
|
118
|
+
actions: memoryMaintenanceActions,
|
|
119
|
+
intentFields: [
|
|
120
|
+
"staleness_reason",
|
|
121
|
+
"forget_reason",
|
|
122
|
+
"update_reason",
|
|
123
|
+
"conflict_reason",
|
|
124
|
+
"scope_reason",
|
|
125
|
+
"replacement_body",
|
|
126
|
+
"ambiguities",
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
instruction: [
|
|
130
|
+
"Coordinate over this intake and emit proposal-shaped outputs only.",
|
|
131
|
+
`Review intent: ${args.intent}. ${describeReviewIntent(args.intent)}`,
|
|
132
|
+
args.request ? `User request: ${args.request}` : "No additional user request.",
|
|
133
|
+
"Do not activate, approve, or write memory.",
|
|
134
|
+
args.topology === "debate-critique"
|
|
135
|
+
? "Use debate/critique topology: proposal work and critique work happen in separate Coral threads before final synthesis."
|
|
136
|
+
: "Use the default pipeline topology.",
|
|
137
|
+
].join(" "),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function parseReviewOutput(text) {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(text);
|
|
143
|
+
const status = parsed?.status === "failed" ? "failed" : "succeeded";
|
|
144
|
+
if (parsed?.type !== "refinery-review-output" ||
|
|
145
|
+
typeof parsed.runId !== "string" ||
|
|
146
|
+
typeof parsed.step !== "string" ||
|
|
147
|
+
!reviewStepOrder.includes(parsed.step) ||
|
|
148
|
+
(status === "succeeded" &&
|
|
149
|
+
(!parsed.output || typeof parsed.output !== "object" || Array.isArray(parsed.output))) ||
|
|
150
|
+
(status === "failed" &&
|
|
151
|
+
(!parsed.error || typeof parsed.error !== "object" || Array.isArray(parsed.error)))) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
...parsed,
|
|
156
|
+
status,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function collectSpecialistMessages(messages, threadIds, runId) {
|
|
164
|
+
const allowedThreadIds = new Set(threadIds);
|
|
165
|
+
return messages
|
|
166
|
+
.filter((message) => allowedThreadIds.has(message.threadId))
|
|
167
|
+
.map((message) => ({ message, envelope: parseReviewOutput(message.text) }))
|
|
168
|
+
.filter((item) => item.envelope !== null && item.envelope.runId === runId)
|
|
169
|
+
.map(({ message, envelope }) => ({
|
|
170
|
+
step: envelope.step,
|
|
171
|
+
agent: message.senderName,
|
|
172
|
+
status: envelope.status,
|
|
173
|
+
messageId: message.id,
|
|
174
|
+
threadId: message.threadId,
|
|
175
|
+
mentionNames: message.mentionNames ?? [],
|
|
176
|
+
textExcerpt: compactText(message.text),
|
|
177
|
+
rawOutput: typeof envelope.rawOutput === "string" ? envelope.rawOutput : null,
|
|
178
|
+
output: envelope.output ?? null,
|
|
179
|
+
model: envelope.model && typeof envelope.model === "object" && !Array.isArray(envelope.model)
|
|
180
|
+
? envelope.model
|
|
181
|
+
: null,
|
|
182
|
+
providerMetadata: envelope.providerMetadata ?? null,
|
|
183
|
+
promptVersion: typeof envelope.promptVersion === "string" ? envelope.promptVersion : null,
|
|
184
|
+
prompt: envelope.prompt ?? null,
|
|
185
|
+
topology: envelope.topology ?? defaultReviewTopology,
|
|
186
|
+
phase: typeof envelope.phase === "string" ? envelope.phase : null,
|
|
187
|
+
error: envelope.error && typeof envelope.error === "object" && !Array.isArray(envelope.error)
|
|
188
|
+
? envelope.error
|
|
189
|
+
: null,
|
|
190
|
+
}))
|
|
191
|
+
.sort((left, right) => reviewStepOrder.indexOf(left.step) - reviewStepOrder.indexOf(right.step));
|
|
192
|
+
}
|
|
193
|
+
function debatePriority(message) {
|
|
194
|
+
if (message.step === "decision-synthesizer" && message.phase === "proposal-synthesis")
|
|
195
|
+
return 3;
|
|
196
|
+
if (message.phase === "pipeline")
|
|
197
|
+
return 2;
|
|
198
|
+
if (!message.phase)
|
|
199
|
+
return 1;
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
function outputMap(messages, topology = defaultReviewTopology) {
|
|
203
|
+
const byStep = new Map();
|
|
204
|
+
for (const message of messages) {
|
|
205
|
+
if (message.status !== "succeeded" || !message.output)
|
|
206
|
+
continue;
|
|
207
|
+
if (topology !== "debate-critique" && !byStep.has(message.step)) {
|
|
208
|
+
byStep.set(message.step, message);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const current = byStep.get(message.step);
|
|
212
|
+
if (!current || debatePriority(message) >= debatePriority(current)) {
|
|
213
|
+
byStep.set(message.step, message);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return byStep;
|
|
217
|
+
}
|
|
218
|
+
function normalizeId(value) {
|
|
219
|
+
if (value === null || value === undefined)
|
|
220
|
+
return null;
|
|
221
|
+
if (typeof value === "number")
|
|
222
|
+
return `memory:${value}`;
|
|
223
|
+
if (typeof value === "string")
|
|
224
|
+
return value;
|
|
225
|
+
throw new Error("target_memory_id must be string, number, or null.");
|
|
226
|
+
}
|
|
227
|
+
function normalizeIds(value) {
|
|
228
|
+
if (value === null || value === undefined)
|
|
229
|
+
return [];
|
|
230
|
+
if (Array.isArray(value))
|
|
231
|
+
return value.map((item) => {
|
|
232
|
+
const normalized = normalizeId(item);
|
|
233
|
+
if (!normalized)
|
|
234
|
+
throw new Error("target_memory_id array must not contain null values.");
|
|
235
|
+
return normalized;
|
|
236
|
+
});
|
|
237
|
+
const normalized = normalizeId(value);
|
|
238
|
+
return normalized ? [normalized] : [];
|
|
239
|
+
}
|
|
240
|
+
function parseAction(value) {
|
|
241
|
+
if (!memoryMaintenanceActions.includes(value)) {
|
|
242
|
+
throw new Error(`Invalid proposal action: ${String(value)}`);
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
function asRecords(value, label) {
|
|
247
|
+
if (!Array.isArray(value))
|
|
248
|
+
throw new Error(`${label} must be an array.`);
|
|
249
|
+
return value.map((item, index) => {
|
|
250
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
251
|
+
throw new Error(`${label}[${index}] must be an object.`);
|
|
252
|
+
}
|
|
253
|
+
return item;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function requiredString(record, field) {
|
|
257
|
+
const value = record[field];
|
|
258
|
+
if (typeof value !== "string" || !value.trim())
|
|
259
|
+
throw new Error(`${field} must be a non-empty string.`);
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
function requiredNumber(record, field) {
|
|
263
|
+
const value = record[field];
|
|
264
|
+
if (typeof value !== "number" || value < 0 || value > 1) {
|
|
265
|
+
throw new Error(`${field} must be a number from 0 to 1.`);
|
|
266
|
+
}
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
function optionalString(record, field) {
|
|
270
|
+
if (!(field in record))
|
|
271
|
+
return undefined;
|
|
272
|
+
const value = record[field];
|
|
273
|
+
if (value === null)
|
|
274
|
+
return null;
|
|
275
|
+
if (typeof value !== "string")
|
|
276
|
+
throw new Error(`${field} must be string or null when present.`);
|
|
277
|
+
return value;
|
|
278
|
+
}
|
|
279
|
+
function optionalStringArray(record, field) {
|
|
280
|
+
if (!(field in record))
|
|
281
|
+
return undefined;
|
|
282
|
+
const value = record[field];
|
|
283
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
284
|
+
throw new Error(`${field} must be an array of strings when present.`);
|
|
285
|
+
}
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
288
|
+
function parseDecisionSynthesizerOutput(runId, output) {
|
|
289
|
+
const proposalRows = asRecords(output.proposals, "decision-synthesizer.proposals");
|
|
290
|
+
const rejectedRows = asRecords(output.rejected ?? [], "decision-synthesizer.rejected");
|
|
291
|
+
return {
|
|
292
|
+
proposals: proposalRows.map((row, index) => {
|
|
293
|
+
const targetMemoryIds = normalizeIds(row.target_memory_ids ?? row.target_memory_id);
|
|
294
|
+
return {
|
|
295
|
+
schemaVersion: refineryReviewSchemaVersion,
|
|
296
|
+
id: `proposal:${runId}:${index + 1}`,
|
|
297
|
+
action: parseAction(row.action),
|
|
298
|
+
lifecycle: "proposed",
|
|
299
|
+
intent: typeof row.intent === "string" ? row.intent : undefined,
|
|
300
|
+
memoryType: requiredString(row, "memory_type"),
|
|
301
|
+
scope: requiredString(row, "proposed_scope"),
|
|
302
|
+
body: requiredString(row, "body"),
|
|
303
|
+
confidence: requiredNumber(row, "confidence"),
|
|
304
|
+
rationale: requiredString(row, "rationale"),
|
|
305
|
+
sourceRefs: Array.isArray(row.source_refs) ? row.source_refs : [],
|
|
306
|
+
targetMemoryId: targetMemoryIds[0] ?? null,
|
|
307
|
+
...(targetMemoryIds.length > 1 ? { targetMemoryIds } : {}),
|
|
308
|
+
...(optionalString(row, "staleness_reason") !== undefined ? { stalenessReason: optionalString(row, "staleness_reason") } : {}),
|
|
309
|
+
...(optionalString(row, "forget_reason") !== undefined ? { forgetReason: optionalString(row, "forget_reason") } : {}),
|
|
310
|
+
...(optionalString(row, "update_reason") !== undefined ? { updateReason: optionalString(row, "update_reason") } : {}),
|
|
311
|
+
...(optionalString(row, "conflict_reason") !== undefined ? { conflictReason: optionalString(row, "conflict_reason") } : {}),
|
|
312
|
+
...(optionalString(row, "scope_reason") !== undefined ? { scopeReason: optionalString(row, "scope_reason") } : {}),
|
|
313
|
+
...(optionalString(row, "replacement_body") !== undefined ? { replacementBody: optionalString(row, "replacement_body") } : {}),
|
|
314
|
+
...(optionalStringArray(row, "ambiguities") !== undefined ? { ambiguities: optionalStringArray(row, "ambiguities") } : {}),
|
|
315
|
+
};
|
|
316
|
+
}),
|
|
317
|
+
rejected: rejectedRows.map((row, index) => ({
|
|
318
|
+
sourceId: typeof row.source_id === "string" ? row.source_id : `rejected:${runId}:${index + 1}`,
|
|
319
|
+
reason: typeof row.reason === "string" && row.reason.trim()
|
|
320
|
+
? row.reason
|
|
321
|
+
: typeof row.rationale === "string" && row.rationale.trim()
|
|
322
|
+
? row.rationale
|
|
323
|
+
: typeof row.type_rationale === "string" && row.type_rationale.trim()
|
|
324
|
+
? row.type_rationale
|
|
325
|
+
: requiredString(row, "update_reason"),
|
|
326
|
+
})),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function appendLogLines(store, prefix, chunk) {
|
|
330
|
+
const lines = chunk.toString("utf8").split(/\r?\n/).filter(Boolean);
|
|
331
|
+
for (const line of lines)
|
|
332
|
+
store.push(`[${prefix}] ${line}`);
|
|
333
|
+
while (store.length > 500)
|
|
334
|
+
store.shift();
|
|
335
|
+
}
|
|
336
|
+
function node24Bin() {
|
|
337
|
+
const candidate = path.join(os.homedir(), ".nvm/versions/node/v24.10.0/bin/node");
|
|
338
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
339
|
+
}
|
|
340
|
+
function coralJavaHome() {
|
|
341
|
+
const candidates = [
|
|
342
|
+
"/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home",
|
|
343
|
+
"/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home",
|
|
344
|
+
];
|
|
345
|
+
return candidates.find((candidate) => fs.existsSync(path.join(candidate, "bin/java"))) ?? null;
|
|
346
|
+
}
|
|
347
|
+
async function isServerReady(apiUrl, authKey) {
|
|
348
|
+
try {
|
|
349
|
+
const res = await fetch(`${apiUrl}/api/v1/registry`, {
|
|
350
|
+
headers: { Authorization: `Bearer ${authKey}` },
|
|
351
|
+
});
|
|
352
|
+
return res.ok;
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function waitForServer(apiUrl, authKey, timeoutMs) {
|
|
359
|
+
const deadline = Date.now() + timeoutMs;
|
|
360
|
+
while (Date.now() < deadline) {
|
|
361
|
+
if (await isServerReady(apiUrl, authKey))
|
|
362
|
+
return true;
|
|
363
|
+
await sleep(1_000);
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
function startCoralServer(args) {
|
|
368
|
+
const configAbs = path.isAbsolute(args.configPath) ? args.configPath : path.resolve(repoRoot, args.configPath);
|
|
369
|
+
const nodeBin = node24Bin();
|
|
370
|
+
const nodeDir = nodeBin ? path.dirname(nodeBin) : null;
|
|
371
|
+
const javaHome = coralJavaHome();
|
|
372
|
+
const pathEntries = [
|
|
373
|
+
nodeDir,
|
|
374
|
+
javaHome ? path.join(javaHome, "bin") : null,
|
|
375
|
+
process.env.PATH,
|
|
376
|
+
].filter((entry) => Boolean(entry));
|
|
377
|
+
const child = spawn("npx", ["-y", args.coralPackage, "server", "start"], {
|
|
378
|
+
cwd: repoRoot,
|
|
379
|
+
env: {
|
|
380
|
+
...process.env,
|
|
381
|
+
CONFIG_FILE_PATH: configAbs,
|
|
382
|
+
REFINERY_NODE_BIN: process.env.REFINERY_NODE_BIN ?? nodeBin ?? undefined,
|
|
383
|
+
JAVA_HOME: process.env.JAVA_HOME ?? javaHome ?? undefined,
|
|
384
|
+
PATH: pathEntries.join(":"),
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
child.stdout.on("data", (chunk) => appendLogLines(args.logs, "coral:stdout", chunk));
|
|
388
|
+
child.stderr.on("data", (chunk) => appendLogLines(args.logs, "coral:stderr", chunk));
|
|
389
|
+
child.on("exit", (code, signal) => args.logs.push(`[coral:exit] code=${code ?? "null"} signal=${signal ?? "null"}`));
|
|
390
|
+
return child;
|
|
391
|
+
}
|
|
392
|
+
async function stopStartedServer(child) {
|
|
393
|
+
if (!child || child.exitCode !== null || child.signalCode !== null)
|
|
394
|
+
return;
|
|
395
|
+
child.kill("SIGTERM");
|
|
396
|
+
await Promise.race([
|
|
397
|
+
new Promise((resolve) => child.once("exit", () => resolve())),
|
|
398
|
+
sleep(5_000).then(() => {
|
|
399
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
400
|
+
child.kill("SIGKILL");
|
|
401
|
+
}),
|
|
402
|
+
]);
|
|
403
|
+
}
|
|
404
|
+
function recordReadinessSnapshot(target, snapshot) {
|
|
405
|
+
target.push({
|
|
406
|
+
at: new Date().toISOString(),
|
|
407
|
+
agents: snapshot.agents
|
|
408
|
+
.filter((agent) => refineryCoralAgentNames.includes(agent.name))
|
|
409
|
+
.map((agent) => ({
|
|
410
|
+
name: agent.name,
|
|
411
|
+
readiness: classifyAgentReadiness(agent),
|
|
412
|
+
status: agent.status ?? null,
|
|
413
|
+
})),
|
|
414
|
+
});
|
|
415
|
+
if (target.length > 80)
|
|
416
|
+
target.shift();
|
|
417
|
+
}
|
|
418
|
+
async function pollReviewOutputs(args) {
|
|
419
|
+
const deadline = Date.now() + args.timeoutMs;
|
|
420
|
+
let lastSnapshot = null;
|
|
421
|
+
let lastMessages = [];
|
|
422
|
+
while (Date.now() < deadline) {
|
|
423
|
+
const snapshot = await getExtended({ apiUrl: args.apiUrl, authKey: args.authKey }, args.session);
|
|
424
|
+
lastSnapshot = snapshot;
|
|
425
|
+
recordReadinessSnapshot(args.readinessSnapshots, snapshot);
|
|
426
|
+
lastMessages = collectSpecialistMessages(allMessages(snapshot), args.threadIds, args.runId);
|
|
427
|
+
if (lastMessages.some((message) => message.status === "failed")) {
|
|
428
|
+
return { snapshot, specialistMessages: lastMessages };
|
|
429
|
+
}
|
|
430
|
+
const byStep = outputMap(lastMessages, args.topology);
|
|
431
|
+
const complete = args.complete ?? (() => reviewStepOrder.every((step) => byStep.has(step)));
|
|
432
|
+
if (complete(lastMessages)) {
|
|
433
|
+
return { snapshot, specialistMessages: lastMessages };
|
|
434
|
+
}
|
|
435
|
+
const stopped = snapshot.agents
|
|
436
|
+
.filter((agent) => refineryCoralAgentNames.includes(agent.name))
|
|
437
|
+
.filter((agent) => classifyAgentReadiness(agent) === "stopped");
|
|
438
|
+
if (stopped.length > 0)
|
|
439
|
+
break;
|
|
440
|
+
await sleep(DEFAULT_WAIT_INTERVAL_MS);
|
|
441
|
+
}
|
|
442
|
+
return { snapshot: lastSnapshot, specialistMessages: lastMessages };
|
|
443
|
+
}
|
|
444
|
+
function findMessage(args) {
|
|
445
|
+
return args.messages.find((message) => message.status === "succeeded" &&
|
|
446
|
+
message.output &&
|
|
447
|
+
message.step === args.step &&
|
|
448
|
+
(args.threadId ? message.threadId === args.threadId : true) &&
|
|
449
|
+
(args.phase ? message.phase === args.phase : true)) ?? null;
|
|
450
|
+
}
|
|
451
|
+
function debateBranchesComplete(messages, proposalThreadId, critiqueThreadId) {
|
|
452
|
+
return Boolean(findMessage({ messages, step: "claim-scout", threadId: proposalThreadId, phase: "candidate-proposal" }) &&
|
|
453
|
+
findMessage({ messages, step: "memory-cartographer", threadId: proposalThreadId, phase: "memory-cartography" }) &&
|
|
454
|
+
findMessage({ messages, step: "proposal-editor", threadId: proposalThreadId, phase: "typed-proposal" }) &&
|
|
455
|
+
findMessage({ messages, step: "evidence-auditor", threadId: critiqueThreadId, phase: "preflight-critique" }));
|
|
456
|
+
}
|
|
457
|
+
function debateFinalComplete(messages, proposalThreadId, critiqueThreadId) {
|
|
458
|
+
return debateBranchesComplete(messages, proposalThreadId, critiqueThreadId) &&
|
|
459
|
+
Boolean(findMessage({ messages, step: "decision-synthesizer", threadId: proposalThreadId, phase: "proposal-synthesis" }));
|
|
460
|
+
}
|
|
461
|
+
function transcriptFromSnapshot(snapshot, threadIds) {
|
|
462
|
+
if (!snapshot)
|
|
463
|
+
return [];
|
|
464
|
+
const allowedThreadIds = new Set(threadIds);
|
|
465
|
+
return allMessages(snapshot)
|
|
466
|
+
.filter((message) => allowedThreadIds.has(message.threadId))
|
|
467
|
+
.map((message) => ({
|
|
468
|
+
id: message.id,
|
|
469
|
+
threadId: message.threadId,
|
|
470
|
+
senderName: message.senderName,
|
|
471
|
+
mentionNames: message.mentionNames ?? [],
|
|
472
|
+
timestamp: message.timestamp ?? null,
|
|
473
|
+
textExcerpt: compactText(message.text),
|
|
474
|
+
}));
|
|
475
|
+
}
|
|
476
|
+
function safeFileToken(value) {
|
|
477
|
+
return value.replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 120) || "message";
|
|
478
|
+
}
|
|
479
|
+
function writeSpecialistMessageArtifacts(runDir, messages) {
|
|
480
|
+
for (const message of messages) {
|
|
481
|
+
const token = safeFileToken(`${message.phase ?? "unphased"}-${message.messageId}`);
|
|
482
|
+
const messageDir = path.join(runDir, "steps", message.step, "messages", token);
|
|
483
|
+
writeJson(path.join(messageDir, "message.json"), message);
|
|
484
|
+
fs.writeFileSync(path.join(messageDir, "output.raw.md"), `${message.rawOutput ?? message.textExcerpt}\n`);
|
|
485
|
+
if (message.output)
|
|
486
|
+
writeJson(path.join(messageDir, "output.parsed.json"), message.output);
|
|
487
|
+
if (message.error)
|
|
488
|
+
writeJson(path.join(messageDir, "error.json"), message.error);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function writeSpecialistStepArtifacts(runDir, messages, topology = defaultReviewTopology) {
|
|
492
|
+
writeSpecialistMessageArtifacts(runDir, messages);
|
|
493
|
+
const canonical = Array.from(outputMap(messages, topology).values());
|
|
494
|
+
for (const message of canonical) {
|
|
495
|
+
const stepDir = path.join(runDir, "steps", message.step);
|
|
496
|
+
writeJson(path.join(stepDir, "input.json"), {
|
|
497
|
+
step: message.step,
|
|
498
|
+
agent: message.agent,
|
|
499
|
+
status: message.status,
|
|
500
|
+
messageId: message.messageId,
|
|
501
|
+
threadId: message.threadId,
|
|
502
|
+
topology: message.topology,
|
|
503
|
+
phase: message.phase,
|
|
504
|
+
mentions: message.mentionNames,
|
|
505
|
+
promptVersion: message.promptVersion,
|
|
506
|
+
model: message.model,
|
|
507
|
+
providerMetadata: message.providerMetadata,
|
|
508
|
+
prompt: message.prompt,
|
|
509
|
+
});
|
|
510
|
+
fs.writeFileSync(path.join(stepDir, "output.raw.md"), `${message.rawOutput ?? message.textExcerpt}\n`);
|
|
511
|
+
if (message.output)
|
|
512
|
+
writeJson(path.join(stepDir, "output.parsed.json"), message.output);
|
|
513
|
+
if (message.error)
|
|
514
|
+
writeJson(path.join(stepDir, "error.json"), message.error);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function toDeliberationMessages(messages) {
|
|
518
|
+
return messages.map((message) => ({
|
|
519
|
+
step: message.step,
|
|
520
|
+
agent: message.agent,
|
|
521
|
+
status: message.status,
|
|
522
|
+
messageId: message.messageId,
|
|
523
|
+
threadId: message.threadId,
|
|
524
|
+
phase: message.phase,
|
|
525
|
+
output: message.output,
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
function failedSpecialistError(args) {
|
|
529
|
+
const code = typeof args.message.error?.code === "string" ? args.message.error.code : "CORAL_SPECIALIST_FAILED";
|
|
530
|
+
const message = typeof args.message.error?.message === "string" ? args.message.error.message : "Specialist returned a failed review envelope.";
|
|
531
|
+
return new RefineryError(code, `Coral specialist ${args.message.step} failed: ${message}`, {
|
|
532
|
+
phase: "coral",
|
|
533
|
+
runId: args.runId,
|
|
534
|
+
runDir: args.runDir,
|
|
535
|
+
failedStep: args.message.step,
|
|
536
|
+
rawOutputPath: path.join(args.runDir, "steps", args.message.step, "output.raw.md"),
|
|
537
|
+
details: args.message.error ?? null,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
export async function startCoralConsoleRun(options) {
|
|
541
|
+
const intent = options.intent ?? defaultReviewIntent;
|
|
542
|
+
const request = options.request ?? null;
|
|
543
|
+
const sourceLimit = Math.max(1, Math.min(options.sourceLimit ?? DEFAULT_SOURCE_LIMIT, 10));
|
|
544
|
+
const sourceCharLimit = Math.max(500, Math.min(options.sourceCharLimit ?? DEFAULT_SOURCE_CHAR_LIMIT, 24_000));
|
|
545
|
+
const coral = options.coral ?? {};
|
|
546
|
+
const topology = coral.topology ?? defaultReviewTopology;
|
|
547
|
+
const apiUrl = coral.apiUrl ?? `http://localhost:${refineryCoralPort}`;
|
|
548
|
+
const authKey = coral.authKey ?? refineryCoralAuthKey;
|
|
549
|
+
const configPath = coral.configPath ?? refineryCoralConfigPath;
|
|
550
|
+
const startServer = coral.startServer ?? !coral.apiUrl;
|
|
551
|
+
const serverMode = startServer ? "managed" : "attached";
|
|
552
|
+
const namespace = coral.namespace ?? `refinery-${options.runId}`;
|
|
553
|
+
const timeoutMs = coral.timeoutMs ?? defaultCoralReviewTimeoutMs(topology);
|
|
554
|
+
const configuredModel = resolveConfiguredModel(coral);
|
|
555
|
+
const logs = [];
|
|
556
|
+
const readinessSnapshots = [];
|
|
557
|
+
const seededMessages = [];
|
|
558
|
+
let child = null;
|
|
559
|
+
let session = null;
|
|
560
|
+
let sessionCreated = false;
|
|
561
|
+
let closed = false;
|
|
562
|
+
try {
|
|
563
|
+
const [sources, activeMemories] = await Promise.all([
|
|
564
|
+
options.adapter.listSourceEvidence({ scope: options.scope, limit: sourceLimit }),
|
|
565
|
+
options.adapter.listActiveMemories({ scope: options.scope, limit: 50 }),
|
|
566
|
+
]);
|
|
567
|
+
const sourceChunks = toSourceChunks(sources, sourceCharLimit);
|
|
568
|
+
const activeMemoryHints = memoryHints(activeMemories);
|
|
569
|
+
const intake = buildReviewIntake({
|
|
570
|
+
runId: options.runId,
|
|
571
|
+
project: options.project,
|
|
572
|
+
source: options.source,
|
|
573
|
+
target: options.target,
|
|
574
|
+
adapterName: options.adapter.name,
|
|
575
|
+
scope: options.scope,
|
|
576
|
+
intent,
|
|
577
|
+
request,
|
|
578
|
+
topology,
|
|
579
|
+
sourceLimit,
|
|
580
|
+
sourceCharLimit,
|
|
581
|
+
sourceChunks,
|
|
582
|
+
activeMemoryHints,
|
|
583
|
+
});
|
|
584
|
+
if (startServer && !(await isServerReady(apiUrl, authKey))) {
|
|
585
|
+
child = startCoralServer({
|
|
586
|
+
configPath,
|
|
587
|
+
coralPackage: coral.coralPackage ?? process.env.REFINERY_CORAL_PACKAGE ?? "coralos-dev@RC-1.2.0",
|
|
588
|
+
logs,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
if (!(await waitForServer(apiUrl, authKey, 60_000))) {
|
|
592
|
+
throw new RefineryError("CORAL_SERVER_UNREACHABLE", `Coral server was not reachable at ${apiUrl}.`, { phase: "coral", runId: options.runId });
|
|
593
|
+
}
|
|
594
|
+
const registry = [];
|
|
595
|
+
for (const agentName of refineryCoralAgentNames) {
|
|
596
|
+
try {
|
|
597
|
+
await getLocalAgent({ apiUrl, authKey }, agentName);
|
|
598
|
+
registry.push({ agentName, ok: true });
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
registry.push({ agentName, ok: false, error: error.message });
|
|
602
|
+
throw new RefineryError("CORAL_AGENT_REGISTRY_MISSING", `Coral registry missing ${agentName}: ${error.message}`, { phase: "coral", runId: options.runId, details: registry });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (coral.sessionId) {
|
|
606
|
+
session = { namespace, sessionId: coral.sessionId };
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
session = await createSession({ apiUrl, authKey }, buildCoralSessionRequest({
|
|
610
|
+
namespace,
|
|
611
|
+
runId: options.runId,
|
|
612
|
+
modelName: configuredModel.modelName,
|
|
613
|
+
modelBaseUrl: configuredModel.baseUrl,
|
|
614
|
+
reasoningEffort: configuredModel.reasoningEffort,
|
|
615
|
+
maxTurns: coral.maxTurns ?? process.env.REFINERY_CORAL_MAX_TURNS ?? (topology === "debate-critique" ? "3" : "2"),
|
|
616
|
+
ttlMs: Math.max(timeoutMs + 60_000, 30 * 60_000),
|
|
617
|
+
holdAfterExitMs: Math.max(timeoutMs + 60_000, 30 * 60_000),
|
|
618
|
+
}));
|
|
619
|
+
sessionCreated = true;
|
|
620
|
+
}
|
|
621
|
+
const ready = await waitForAgentsReady({ apiUrl, authKey }, session, refineryCoralAgentNames, (snapshot) => recordReadinessSnapshot(readinessSnapshots, snapshot), { timeoutMs: 90_000, intervalMs: DEFAULT_WAIT_INTERVAL_MS });
|
|
622
|
+
if (!ready.ok) {
|
|
623
|
+
throw new RefineryError("CORAL_AGENTS_NOT_READY", `Agents did not reach readiness. stopped=${ready.stopped.join(",") || "none"}`, { phase: "coral", runId: options.runId, details: ready.snapshot });
|
|
624
|
+
}
|
|
625
|
+
let threadId;
|
|
626
|
+
let threadIds;
|
|
627
|
+
let proposalThreadId;
|
|
628
|
+
let critiqueThreadId;
|
|
629
|
+
const rememberSeed = (message) => {
|
|
630
|
+
seededMessages.push({
|
|
631
|
+
id: message.id,
|
|
632
|
+
threadId: message.threadId,
|
|
633
|
+
senderName: message.senderName,
|
|
634
|
+
mentionNames: message.mentionNames ?? [],
|
|
635
|
+
textExcerpt: compactText(message.text),
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
if (topology === "debate-critique") {
|
|
639
|
+
if (coral.threadId) {
|
|
640
|
+
proposalThreadId = coral.threadId;
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
const proposalThread = await puppetCreateThread({ apiUrl, authKey }, session, "refinery-claim-scout", {
|
|
644
|
+
threadName: `Refinery console ${options.runId} proposal`,
|
|
645
|
+
participantNames: refineryCoralAgentNames,
|
|
646
|
+
});
|
|
647
|
+
proposalThreadId = proposalThread.thread.id;
|
|
648
|
+
}
|
|
649
|
+
const critiqueThread = await puppetCreateThread({ apiUrl, authKey }, session, "refinery-evidence-auditor", {
|
|
650
|
+
threadName: `Refinery console ${options.runId} critique`,
|
|
651
|
+
participantNames: refineryCoralAgentNames,
|
|
652
|
+
});
|
|
653
|
+
critiqueThreadId = critiqueThread.thread.id;
|
|
654
|
+
threadId = proposalThreadId;
|
|
655
|
+
threadIds = [proposalThreadId, critiqueThreadId];
|
|
656
|
+
const proposalSeed = await puppetSendMessage({ apiUrl, authKey }, session, "refinery-evidence-auditor", {
|
|
657
|
+
threadId: proposalThreadId,
|
|
658
|
+
content: JSON.stringify({ ...intake, phase: "proposal-intake" }),
|
|
659
|
+
mentions: ["refinery-claim-scout"],
|
|
660
|
+
});
|
|
661
|
+
rememberSeed(proposalSeed.message);
|
|
662
|
+
const critiqueSeed = await puppetSendMessage({ apiUrl, authKey }, session, "refinery-claim-scout", {
|
|
663
|
+
threadId: critiqueThreadId,
|
|
664
|
+
content: JSON.stringify({ ...intake, phase: "critique-intake" }),
|
|
665
|
+
mentions: ["refinery-evidence-auditor"],
|
|
666
|
+
});
|
|
667
|
+
rememberSeed(critiqueSeed.message);
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
if (coral.threadId) {
|
|
671
|
+
threadId = coral.threadId;
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
const thread = await puppetCreateThread({ apiUrl, authKey }, session, "refinery-claim-scout", {
|
|
675
|
+
threadName: `Refinery console ${options.runId}`,
|
|
676
|
+
participantNames: refineryCoralAgentNames,
|
|
677
|
+
});
|
|
678
|
+
threadId = thread.thread.id;
|
|
679
|
+
}
|
|
680
|
+
threadIds = [threadId];
|
|
681
|
+
const seed = await puppetSendMessage({ apiUrl, authKey }, session, "refinery-evidence-auditor", {
|
|
682
|
+
threadId,
|
|
683
|
+
content: JSON.stringify(intake),
|
|
684
|
+
mentions: ["refinery-claim-scout"],
|
|
685
|
+
});
|
|
686
|
+
rememberSeed(seed.message);
|
|
687
|
+
}
|
|
688
|
+
const close = async () => {
|
|
689
|
+
if (closed)
|
|
690
|
+
return;
|
|
691
|
+
closed = true;
|
|
692
|
+
if (session && sessionCreated && !coral.noTeardown) {
|
|
693
|
+
await closeSession({ apiUrl, authKey }, session);
|
|
694
|
+
}
|
|
695
|
+
await stopStartedServer(child);
|
|
696
|
+
};
|
|
697
|
+
return {
|
|
698
|
+
managedServerStarted: Boolean(child),
|
|
699
|
+
managedProcess: child,
|
|
700
|
+
close,
|
|
701
|
+
result: {
|
|
702
|
+
ok: true,
|
|
703
|
+
schemaVersion: refineryReviewSchemaVersion,
|
|
704
|
+
command: "console run",
|
|
705
|
+
mode: "coral-console",
|
|
706
|
+
source: options.source,
|
|
707
|
+
target: options.target,
|
|
708
|
+
project: options.project,
|
|
709
|
+
adapter: { name: options.adapter.name },
|
|
710
|
+
scope: options.scope,
|
|
711
|
+
dryRun: true,
|
|
712
|
+
archive: false,
|
|
713
|
+
artifactDir: null,
|
|
714
|
+
writesAttempted: false,
|
|
715
|
+
runId: options.runId,
|
|
716
|
+
consoleUrl: buildConsoleUrl(apiUrl, "/ui/console"),
|
|
717
|
+
schemaUrl: buildConsoleUrl(apiUrl, "/api_v1.json"),
|
|
718
|
+
counts: {
|
|
719
|
+
sources: sources.length,
|
|
720
|
+
activeMemories: activeMemories.length,
|
|
721
|
+
seededMessages: seededMessages.length,
|
|
722
|
+
},
|
|
723
|
+
coral: {
|
|
724
|
+
apiUrl,
|
|
725
|
+
namespace: session.namespace,
|
|
726
|
+
sessionId: session.sessionId,
|
|
727
|
+
threadId,
|
|
728
|
+
threadIds,
|
|
729
|
+
...(proposalThreadId ? { proposalThreadId } : {}),
|
|
730
|
+
...(critiqueThreadId ? { critiqueThreadId } : {}),
|
|
731
|
+
agents: refineryCoralAgentNames,
|
|
732
|
+
topology,
|
|
733
|
+
serverMode,
|
|
734
|
+
managedServerStarted: Boolean(child),
|
|
735
|
+
},
|
|
736
|
+
seededMessages,
|
|
737
|
+
next: `Open ${buildConsoleUrl(apiUrl, "/ui/console")} and inspect namespace ${session.namespace}, session ${session.sessionId}.`,
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
if (session && sessionCreated && !coral.noTeardown) {
|
|
743
|
+
await closeSession({ apiUrl, authKey }, session);
|
|
744
|
+
}
|
|
745
|
+
await stopStartedServer(child);
|
|
746
|
+
throw applyErrorContext(asRefineryError(error, { code: "CORAL_CONSOLE_FAILED" }), {
|
|
747
|
+
phase: "coral",
|
|
748
|
+
runId: options.runId,
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
export async function runCoralReview(options) {
|
|
753
|
+
const runDir = path.join(options.outputDir, options.runId);
|
|
754
|
+
const createdAt = new Date().toISOString();
|
|
755
|
+
const intent = options.intent ?? defaultReviewIntent;
|
|
756
|
+
const request = options.request ?? null;
|
|
757
|
+
const sourceLimit = Math.max(1, Math.min(options.sourceLimit ?? DEFAULT_SOURCE_LIMIT, 10));
|
|
758
|
+
const sourceCharLimit = Math.max(500, Math.min(options.sourceCharLimit ?? DEFAULT_SOURCE_CHAR_LIMIT, 24_000));
|
|
759
|
+
const coral = options.coral ?? {};
|
|
760
|
+
const topology = coral.topology ?? defaultReviewTopology;
|
|
761
|
+
const apiUrl = coral.apiUrl ?? `http://localhost:${refineryCoralPort}`;
|
|
762
|
+
const authKey = coral.authKey ?? refineryCoralAuthKey;
|
|
763
|
+
const configPath = coral.configPath ?? refineryCoralConfigPath;
|
|
764
|
+
const startServer = coral.startServer ?? !coral.apiUrl;
|
|
765
|
+
const serverMode = startServer ? "managed" : "attached";
|
|
766
|
+
const namespace = coral.namespace ?? `refinery-${options.runId}`;
|
|
767
|
+
const timeoutMs = coral.timeoutMs ?? defaultCoralReviewTimeoutMs(topology);
|
|
768
|
+
const configuredModel = resolveConfiguredModel(coral);
|
|
769
|
+
const logs = [];
|
|
770
|
+
const readinessSnapshots = [];
|
|
771
|
+
let child = null;
|
|
772
|
+
let session = null;
|
|
773
|
+
let threadId = null;
|
|
774
|
+
let threadIds = [];
|
|
775
|
+
let proposalThreadId = null;
|
|
776
|
+
let critiqueThreadId = null;
|
|
777
|
+
let sessionCreated = false;
|
|
778
|
+
let threadCreated = false;
|
|
779
|
+
let finalSnapshot = null;
|
|
780
|
+
let specialistMessages = [];
|
|
781
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
782
|
+
try {
|
|
783
|
+
const [sources, activeMemories] = await Promise.all([
|
|
784
|
+
options.adapter.listSourceEvidence({ scope: options.scope, limit: sourceLimit }),
|
|
785
|
+
options.adapter.listActiveMemories({ scope: options.scope, limit: 50 }),
|
|
786
|
+
]);
|
|
787
|
+
const sourceChunks = toSourceChunks(sources, sourceCharLimit);
|
|
788
|
+
const activeMemoryHints = memoryHints(activeMemories);
|
|
789
|
+
const intake = buildReviewIntake({
|
|
790
|
+
runId: options.runId,
|
|
791
|
+
project: options.project,
|
|
792
|
+
source: options.source,
|
|
793
|
+
target: options.target,
|
|
794
|
+
adapterName: options.adapter.name,
|
|
795
|
+
scope: options.scope,
|
|
796
|
+
intent,
|
|
797
|
+
request,
|
|
798
|
+
topology,
|
|
799
|
+
sourceLimit,
|
|
800
|
+
sourceCharLimit,
|
|
801
|
+
sourceChunks,
|
|
802
|
+
activeMemoryHints,
|
|
803
|
+
});
|
|
804
|
+
writeJson(path.join(runDir, "input.json"), intake);
|
|
805
|
+
if (startServer && !(await isServerReady(apiUrl, authKey))) {
|
|
806
|
+
child = startCoralServer({
|
|
807
|
+
configPath,
|
|
808
|
+
coralPackage: coral.coralPackage ?? process.env.REFINERY_CORAL_PACKAGE ?? "coralos-dev@RC-1.2.0",
|
|
809
|
+
logs,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
if (!(await waitForServer(apiUrl, authKey, 60_000))) {
|
|
813
|
+
throw new RefineryError("CORAL_SERVER_UNREACHABLE", `Coral server was not reachable at ${apiUrl}.`, { phase: "coral", runId: options.runId, runDir });
|
|
814
|
+
}
|
|
815
|
+
const registry = [];
|
|
816
|
+
for (const agentName of refineryCoralAgentNames) {
|
|
817
|
+
try {
|
|
818
|
+
await getLocalAgent({ apiUrl, authKey }, agentName);
|
|
819
|
+
registry.push({ agentName, ok: true });
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
registry.push({ agentName, ok: false, error: error.message });
|
|
823
|
+
throw new RefineryError("CORAL_AGENT_REGISTRY_MISSING", `Coral registry missing ${agentName}: ${error.message}`, { phase: "coral", runId: options.runId, runDir, details: registry });
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (coral.sessionId) {
|
|
827
|
+
session = { namespace, sessionId: coral.sessionId };
|
|
828
|
+
}
|
|
829
|
+
else {
|
|
830
|
+
session = await createSession({ apiUrl, authKey }, buildCoralSessionRequest({
|
|
831
|
+
namespace,
|
|
832
|
+
runId: options.runId,
|
|
833
|
+
modelName: configuredModel.modelName,
|
|
834
|
+
modelBaseUrl: configuredModel.baseUrl,
|
|
835
|
+
reasoningEffort: configuredModel.reasoningEffort,
|
|
836
|
+
maxTurns: coral.maxTurns ?? process.env.REFINERY_CORAL_MAX_TURNS ?? (topology === "debate-critique" ? "3" : "2"),
|
|
837
|
+
ttlMs: Math.max(timeoutMs + 60_000, 180_000),
|
|
838
|
+
holdAfterExitMs: Math.max(timeoutMs + 60_000, 180_000),
|
|
839
|
+
}));
|
|
840
|
+
sessionCreated = true;
|
|
841
|
+
}
|
|
842
|
+
const ready = await waitForAgentsReady({ apiUrl, authKey }, session, refineryCoralAgentNames, (snapshot) => recordReadinessSnapshot(readinessSnapshots, snapshot), { timeoutMs: 90_000, intervalMs: DEFAULT_WAIT_INTERVAL_MS });
|
|
843
|
+
if (!ready.ok) {
|
|
844
|
+
throw new RefineryError("CORAL_AGENTS_NOT_READY", `Agents did not reach readiness. stopped=${ready.stopped.join(",") || "none"}`, { phase: "coral", runId: options.runId, runDir, details: ready.snapshot });
|
|
845
|
+
}
|
|
846
|
+
if (topology === "debate-critique") {
|
|
847
|
+
if (coral.threadId) {
|
|
848
|
+
proposalThreadId = coral.threadId;
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
const proposalThread = await puppetCreateThread({ apiUrl, authKey }, session, "refinery-claim-scout", {
|
|
852
|
+
threadName: `Refinery review ${options.runId} proposal`,
|
|
853
|
+
participantNames: refineryCoralAgentNames,
|
|
854
|
+
});
|
|
855
|
+
proposalThreadId = proposalThread.thread.id;
|
|
856
|
+
threadCreated = true;
|
|
857
|
+
}
|
|
858
|
+
threadId = proposalThreadId;
|
|
859
|
+
threadIds = [proposalThreadId];
|
|
860
|
+
await puppetSendMessage({ apiUrl, authKey }, session, "refinery-evidence-auditor", {
|
|
861
|
+
threadId: proposalThreadId,
|
|
862
|
+
content: JSON.stringify({ ...intake, phase: "proposal-intake" }),
|
|
863
|
+
mentions: ["refinery-claim-scout"],
|
|
864
|
+
});
|
|
865
|
+
const claimScoutPoll = await pollReviewOutputs({
|
|
866
|
+
apiUrl,
|
|
867
|
+
authKey,
|
|
868
|
+
session,
|
|
869
|
+
threadIds,
|
|
870
|
+
runId: options.runId,
|
|
871
|
+
timeoutMs,
|
|
872
|
+
readinessSnapshots,
|
|
873
|
+
topology,
|
|
874
|
+
complete: (messages) => Boolean(findMessage({ messages, step: "claim-scout", threadId: proposalThreadId, phase: "candidate-proposal" })),
|
|
875
|
+
});
|
|
876
|
+
finalSnapshot = claimScoutPoll.snapshot;
|
|
877
|
+
specialistMessages = claimScoutPoll.specialistMessages;
|
|
878
|
+
const claimScoutFailure = specialistMessages.find((message) => message.status === "failed");
|
|
879
|
+
if (claimScoutFailure) {
|
|
880
|
+
writeSpecialistStepArtifacts(runDir, specialistMessages, topology);
|
|
881
|
+
throw failedSpecialistError({ runDir, runId: options.runId, message: claimScoutFailure });
|
|
882
|
+
}
|
|
883
|
+
const claimScoutMessage = findMessage({
|
|
884
|
+
messages: specialistMessages,
|
|
885
|
+
step: "claim-scout",
|
|
886
|
+
threadId: proposalThreadId,
|
|
887
|
+
phase: "candidate-proposal",
|
|
888
|
+
});
|
|
889
|
+
if (!claimScoutMessage?.output) {
|
|
890
|
+
throw new RefineryError("CORAL_REVIEW_INCOMPLETE", "Debate/critique review did not emit claim scout candidates before claim critique.", { phase: "coral", runId: options.runId, runDir, details: { specialistMessages } });
|
|
891
|
+
}
|
|
892
|
+
const claimCards = claimCardsForCritique({
|
|
893
|
+
runId: options.runId,
|
|
894
|
+
claimScoutOutput: claimScoutMessage.output,
|
|
895
|
+
});
|
|
896
|
+
const critiqueThread = await puppetCreateThread({ apiUrl, authKey }, session, "refinery-evidence-auditor", {
|
|
897
|
+
threadName: `Refinery review ${options.runId} claim critique`,
|
|
898
|
+
participantNames: refineryCoralAgentNames,
|
|
899
|
+
});
|
|
900
|
+
critiqueThreadId = critiqueThread.thread.id;
|
|
901
|
+
threadCreated = true;
|
|
902
|
+
threadIds = [proposalThreadId, critiqueThreadId];
|
|
903
|
+
await puppetSendMessage({ apiUrl, authKey }, session, "refinery-claim-scout", {
|
|
904
|
+
threadId: critiqueThreadId,
|
|
905
|
+
content: JSON.stringify({
|
|
906
|
+
...intake,
|
|
907
|
+
phase: "critique-intake",
|
|
908
|
+
claim_cards: claimCards,
|
|
909
|
+
context: {
|
|
910
|
+
source_chunks: sourceChunks,
|
|
911
|
+
active_memory_hints: activeMemoryHints,
|
|
912
|
+
review_intent: intent,
|
|
913
|
+
review_request: request,
|
|
914
|
+
intent_description: describeReviewIntent(intent),
|
|
915
|
+
topology,
|
|
916
|
+
phase: "critique-intake",
|
|
917
|
+
claim_cards: claimCards,
|
|
918
|
+
},
|
|
919
|
+
}),
|
|
920
|
+
mentions: ["refinery-evidence-auditor"],
|
|
921
|
+
});
|
|
922
|
+
const branches = await pollReviewOutputs({
|
|
923
|
+
apiUrl,
|
|
924
|
+
authKey,
|
|
925
|
+
session,
|
|
926
|
+
threadIds,
|
|
927
|
+
runId: options.runId,
|
|
928
|
+
timeoutMs,
|
|
929
|
+
readinessSnapshots,
|
|
930
|
+
topology,
|
|
931
|
+
complete: (messages) => debateBranchesComplete(messages, proposalThreadId, critiqueThreadId),
|
|
932
|
+
});
|
|
933
|
+
finalSnapshot = branches.snapshot;
|
|
934
|
+
specialistMessages = branches.specialistMessages;
|
|
935
|
+
const branchFailure = specialistMessages.find((message) => message.status === "failed");
|
|
936
|
+
if (branchFailure) {
|
|
937
|
+
writeSpecialistStepArtifacts(runDir, specialistMessages, topology);
|
|
938
|
+
throw failedSpecialistError({ runDir, runId: options.runId, message: branchFailure });
|
|
939
|
+
}
|
|
940
|
+
if (!debateBranchesComplete(specialistMessages, proposalThreadId, critiqueThreadId)) {
|
|
941
|
+
throw new RefineryError("CORAL_REVIEW_INCOMPLETE", "Debate/critique branches did not emit required proposal and critique outputs before merge.", { phase: "coral", runId: options.runId, runDir, details: { specialistMessages } });
|
|
942
|
+
}
|
|
943
|
+
const proposalEditorMessage = findMessage({
|
|
944
|
+
messages: specialistMessages,
|
|
945
|
+
step: "proposal-editor",
|
|
946
|
+
threadId: proposalThreadId,
|
|
947
|
+
phase: "typed-proposal",
|
|
948
|
+
});
|
|
949
|
+
const evidenceAudit = findMessage({
|
|
950
|
+
messages: specialistMessages,
|
|
951
|
+
step: "evidence-auditor",
|
|
952
|
+
threadId: critiqueThreadId,
|
|
953
|
+
phase: "preflight-critique",
|
|
954
|
+
});
|
|
955
|
+
if (!proposalEditorMessage?.output || !evidenceAudit?.output) {
|
|
956
|
+
throw new RefineryError("CORAL_REVIEW_INCOMPLETE", "Debate/critique merge inputs were missing after branch completion.", { phase: "coral", runId: options.runId, runDir, details: { proposalEditorMessage, evidenceAudit } });
|
|
957
|
+
}
|
|
958
|
+
const branchDeliberation = buildDeliberationArtifacts({
|
|
959
|
+
runId: options.runId,
|
|
960
|
+
topology,
|
|
961
|
+
messages: toDeliberationMessages(specialistMessages),
|
|
962
|
+
});
|
|
963
|
+
const critique = {
|
|
964
|
+
topology,
|
|
965
|
+
proposalThreadId,
|
|
966
|
+
critiqueThreadId,
|
|
967
|
+
claim_cards: branchDeliberation.claims.length > 0 ? branchDeliberation.claims : claimCards,
|
|
968
|
+
challenge_ledger: branchDeliberation.challengeLedger,
|
|
969
|
+
deliberation_trace: branchDeliberation.trace,
|
|
970
|
+
evidenceAudit: evidenceAudit.output,
|
|
971
|
+
evidenceMessages: [
|
|
972
|
+
{
|
|
973
|
+
step: evidenceAudit.step,
|
|
974
|
+
phase: evidenceAudit.phase,
|
|
975
|
+
agent: evidenceAudit.agent,
|
|
976
|
+
messageId: evidenceAudit.messageId,
|
|
977
|
+
threadId: evidenceAudit.threadId,
|
|
978
|
+
},
|
|
979
|
+
],
|
|
980
|
+
};
|
|
981
|
+
const merge = {
|
|
982
|
+
schemaVersion: refineryReviewSchemaVersion,
|
|
983
|
+
type: "refinery-review-merge",
|
|
984
|
+
topology,
|
|
985
|
+
phase: "proposal-synthesis-intake",
|
|
986
|
+
runId: options.runId,
|
|
987
|
+
project: options.project,
|
|
988
|
+
source: options.source,
|
|
989
|
+
target: options.target,
|
|
990
|
+
adapter: options.adapter.name,
|
|
991
|
+
scope: options.scope,
|
|
992
|
+
intent,
|
|
993
|
+
request,
|
|
994
|
+
context: {
|
|
995
|
+
source_chunks: sourceChunks,
|
|
996
|
+
active_memory_hints: activeMemoryHints,
|
|
997
|
+
review_intent: intent,
|
|
998
|
+
review_request: request,
|
|
999
|
+
intent_description: describeReviewIntent(intent),
|
|
1000
|
+
topology,
|
|
1001
|
+
claim_cards: branchDeliberation.claims.length > 0 ? branchDeliberation.claims : claimCards,
|
|
1002
|
+
challenge_ledger: branchDeliberation.challengeLedger,
|
|
1003
|
+
debate_critique: critique,
|
|
1004
|
+
},
|
|
1005
|
+
proposal_editor_output: proposalEditorMessage.output,
|
|
1006
|
+
critique,
|
|
1007
|
+
instruction: [
|
|
1008
|
+
"Merge the typed proposal branch with the local claim critique thread.",
|
|
1009
|
+
"Reject, qualify, or endorse candidates according to claim-level challenges and evidence.",
|
|
1010
|
+
"Do not activate, approve, or write memory.",
|
|
1011
|
+
].join(" "),
|
|
1012
|
+
};
|
|
1013
|
+
await puppetSendMessage({ apiUrl, authKey }, session, "refinery-proposal-editor", {
|
|
1014
|
+
threadId: proposalThreadId,
|
|
1015
|
+
content: JSON.stringify(merge),
|
|
1016
|
+
mentions: ["refinery-decision-synthesizer"],
|
|
1017
|
+
});
|
|
1018
|
+
const final = await pollReviewOutputs({
|
|
1019
|
+
apiUrl,
|
|
1020
|
+
authKey,
|
|
1021
|
+
session,
|
|
1022
|
+
threadIds,
|
|
1023
|
+
runId: options.runId,
|
|
1024
|
+
timeoutMs,
|
|
1025
|
+
readinessSnapshots,
|
|
1026
|
+
topology,
|
|
1027
|
+
complete: (messages) => debateFinalComplete(messages, proposalThreadId, critiqueThreadId),
|
|
1028
|
+
});
|
|
1029
|
+
finalSnapshot = final.snapshot;
|
|
1030
|
+
specialistMessages = final.specialistMessages;
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
if (coral.threadId) {
|
|
1034
|
+
threadId = coral.threadId;
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
const thread = await puppetCreateThread({ apiUrl, authKey }, session, "refinery-claim-scout", {
|
|
1038
|
+
threadName: `Refinery review ${options.runId}`,
|
|
1039
|
+
participantNames: refineryCoralAgentNames,
|
|
1040
|
+
});
|
|
1041
|
+
threadId = thread.thread.id;
|
|
1042
|
+
threadCreated = true;
|
|
1043
|
+
}
|
|
1044
|
+
threadIds = [threadId];
|
|
1045
|
+
await puppetSendMessage({ apiUrl, authKey }, session, "refinery-evidence-auditor", {
|
|
1046
|
+
threadId,
|
|
1047
|
+
content: JSON.stringify(intake),
|
|
1048
|
+
mentions: ["refinery-claim-scout"],
|
|
1049
|
+
});
|
|
1050
|
+
const polled = await pollReviewOutputs({
|
|
1051
|
+
apiUrl,
|
|
1052
|
+
authKey,
|
|
1053
|
+
session,
|
|
1054
|
+
threadIds,
|
|
1055
|
+
runId: options.runId,
|
|
1056
|
+
timeoutMs,
|
|
1057
|
+
readinessSnapshots,
|
|
1058
|
+
topology,
|
|
1059
|
+
});
|
|
1060
|
+
finalSnapshot = polled.snapshot;
|
|
1061
|
+
specialistMessages = polled.specialistMessages;
|
|
1062
|
+
}
|
|
1063
|
+
writeSpecialistStepArtifacts(runDir, specialistMessages, topology);
|
|
1064
|
+
const failedMessage = specialistMessages.find((message) => message.status === "failed");
|
|
1065
|
+
if (failedMessage) {
|
|
1066
|
+
throw failedSpecialistError({ runDir, runId: options.runId, message: failedMessage });
|
|
1067
|
+
}
|
|
1068
|
+
const byStep = outputMap(specialistMessages, topology);
|
|
1069
|
+
const missingSteps = reviewStepOrder.filter((step) => !byStep.has(step));
|
|
1070
|
+
if (missingSteps.length > 0) {
|
|
1071
|
+
throw new RefineryError("CORAL_REVIEW_INCOMPLETE", `Coral review did not emit all specialist outputs. Missing: ${missingSteps.join(", ")}`, { phase: "coral", runId: options.runId, runDir, details: { missingSteps, specialistMessages } });
|
|
1072
|
+
}
|
|
1073
|
+
const decisionSynthesis = byStep.get("decision-synthesizer");
|
|
1074
|
+
if (!decisionSynthesis)
|
|
1075
|
+
throw new Error("Missing decision-synthesizer output.");
|
|
1076
|
+
const parsedDecision = parseDecisionSynthesizerOutput(options.runId, decisionSynthesis.output ?? {});
|
|
1077
|
+
parsedDecision.proposals = parsedDecision.proposals.map((proposal) => ({
|
|
1078
|
+
...proposal,
|
|
1079
|
+
intent,
|
|
1080
|
+
}));
|
|
1081
|
+
const evidenceReview = byStep.get("evidence-auditor")?.output ?? { findings: [] };
|
|
1082
|
+
const deliberation = buildDeliberationArtifacts({
|
|
1083
|
+
runId: options.runId,
|
|
1084
|
+
topology,
|
|
1085
|
+
messages: toDeliberationMessages(specialistMessages),
|
|
1086
|
+
});
|
|
1087
|
+
writeJson(path.join(runDir, "claims.json"), deliberation.claims);
|
|
1088
|
+
writeJson(path.join(runDir, "challenge-ledger.json"), deliberation.challengeLedger);
|
|
1089
|
+
writeJson(path.join(runDir, "deliberation.json"), deliberation);
|
|
1090
|
+
writeJson(path.join(runDir, "proposals.json"), parsedDecision.proposals);
|
|
1091
|
+
writeJson(path.join(runDir, "rejected.json"), parsedDecision.rejected);
|
|
1092
|
+
const transcript = transcriptFromSnapshot(finalSnapshot, threadIds);
|
|
1093
|
+
const runtime = {
|
|
1094
|
+
kind: "coral",
|
|
1095
|
+
topology,
|
|
1096
|
+
topologyDesign: topology === "debate-critique" ? "claim-centered-interruptible" : "pipeline",
|
|
1097
|
+
serverMode,
|
|
1098
|
+
apiUrl,
|
|
1099
|
+
authKeyPresent: Boolean(authKey),
|
|
1100
|
+
configPath: path.isAbsolute(configPath) ? configPath : path.resolve(repoRoot, configPath),
|
|
1101
|
+
namespace: session.namespace,
|
|
1102
|
+
sessionId: session.sessionId,
|
|
1103
|
+
threadId,
|
|
1104
|
+
threadIds,
|
|
1105
|
+
proposalThreadId,
|
|
1106
|
+
critiqueThreadId,
|
|
1107
|
+
agents: refineryCoralAgentNames,
|
|
1108
|
+
startedServer: Boolean(child),
|
|
1109
|
+
sessionCreated,
|
|
1110
|
+
threadCreated,
|
|
1111
|
+
noTeardown: Boolean(coral.noTeardown),
|
|
1112
|
+
model: configuredModel,
|
|
1113
|
+
};
|
|
1114
|
+
const coralArtifact = {
|
|
1115
|
+
schemaVersion: "refinery.coral-review.v1",
|
|
1116
|
+
status: "succeeded",
|
|
1117
|
+
runId: options.runId,
|
|
1118
|
+
apiUrl,
|
|
1119
|
+
topology,
|
|
1120
|
+
serverMode,
|
|
1121
|
+
configPath: runtime.configPath,
|
|
1122
|
+
session,
|
|
1123
|
+
threadId,
|
|
1124
|
+
threadIds,
|
|
1125
|
+
proposalThreadId,
|
|
1126
|
+
critiqueThreadId,
|
|
1127
|
+
agents: refineryCoralAgentNames,
|
|
1128
|
+
sessionCreated,
|
|
1129
|
+
threadCreated,
|
|
1130
|
+
model: configuredModel,
|
|
1131
|
+
readinessSnapshots,
|
|
1132
|
+
specialistMessages,
|
|
1133
|
+
deliberation,
|
|
1134
|
+
transcriptExcerpts: transcript,
|
|
1135
|
+
serverLogExcerpt: logs.slice(-200),
|
|
1136
|
+
};
|
|
1137
|
+
writeJson(path.join(runDir, "coral.json"), coralArtifact);
|
|
1138
|
+
writeJson(path.join(runDir, "transcript.json"), transcript);
|
|
1139
|
+
const metadata = {
|
|
1140
|
+
schemaVersion: refineryReviewSchemaVersion,
|
|
1141
|
+
runId: options.runId,
|
|
1142
|
+
adapter: options.adapter.name,
|
|
1143
|
+
scope: options.scope,
|
|
1144
|
+
dryRun: true,
|
|
1145
|
+
mode: "coral",
|
|
1146
|
+
createdAt,
|
|
1147
|
+
writesAttempted: false,
|
|
1148
|
+
sinkUrl: options.sink?.url ?? null,
|
|
1149
|
+
runtime,
|
|
1150
|
+
model: configuredModel,
|
|
1151
|
+
specialistOrder: topology === "debate-critique"
|
|
1152
|
+
? [
|
|
1153
|
+
"proposal:claim-scout",
|
|
1154
|
+
"proposal:memory-cartographer",
|
|
1155
|
+
"critique:evidence-auditor",
|
|
1156
|
+
"proposal:proposal-editor",
|
|
1157
|
+
"proposal:decision-synthesizer",
|
|
1158
|
+
]
|
|
1159
|
+
: reviewStepOrder,
|
|
1160
|
+
sourceLimit,
|
|
1161
|
+
sourceCharLimit,
|
|
1162
|
+
intent,
|
|
1163
|
+
request,
|
|
1164
|
+
};
|
|
1165
|
+
const manifestMetadata = metadata;
|
|
1166
|
+
const result = {
|
|
1167
|
+
ok: true,
|
|
1168
|
+
schemaVersion: refineryReviewSchemaVersion,
|
|
1169
|
+
command: "review",
|
|
1170
|
+
mode: "coral",
|
|
1171
|
+
source: options.source,
|
|
1172
|
+
target: options.target,
|
|
1173
|
+
project: options.project,
|
|
1174
|
+
adapter: { name: options.adapter.name },
|
|
1175
|
+
scope: options.scope,
|
|
1176
|
+
dryRun: true,
|
|
1177
|
+
runId: options.runId,
|
|
1178
|
+
runDir,
|
|
1179
|
+
counts: {
|
|
1180
|
+
sources: sources.length,
|
|
1181
|
+
activeMemories: activeMemories.length,
|
|
1182
|
+
proposals: parsedDecision.proposals.length,
|
|
1183
|
+
rejected: parsedDecision.rejected.length,
|
|
1184
|
+
claims: deliberation.summary.claims,
|
|
1185
|
+
challenges: deliberation.summary.challenges,
|
|
1186
|
+
deliberationMoves: deliberation.summary.moves,
|
|
1187
|
+
},
|
|
1188
|
+
proposals: parsedDecision.proposals,
|
|
1189
|
+
rejected: parsedDecision.rejected,
|
|
1190
|
+
evidenceReview,
|
|
1191
|
+
coral: {
|
|
1192
|
+
namespace: session.namespace,
|
|
1193
|
+
sessionId: session.sessionId,
|
|
1194
|
+
threadId: threadId ?? threadIds[0] ?? "",
|
|
1195
|
+
threadIds,
|
|
1196
|
+
agents: refineryCoralAgentNames,
|
|
1197
|
+
},
|
|
1198
|
+
metadata,
|
|
1199
|
+
};
|
|
1200
|
+
writeJson(path.join(runDir, "metadata.json"), metadata);
|
|
1201
|
+
writeJson(path.join(runDir, "review.json"), result);
|
|
1202
|
+
writeReviewArtifactManifest({
|
|
1203
|
+
runDir,
|
|
1204
|
+
runId: options.runId,
|
|
1205
|
+
adapterName: options.adapter.name,
|
|
1206
|
+
scope: options.scope,
|
|
1207
|
+
mode: "coral",
|
|
1208
|
+
status: "succeeded",
|
|
1209
|
+
createdAt,
|
|
1210
|
+
counts: result.counts,
|
|
1211
|
+
metadata: manifestMetadata,
|
|
1212
|
+
intent,
|
|
1213
|
+
request,
|
|
1214
|
+
});
|
|
1215
|
+
if (!options.sink)
|
|
1216
|
+
return result;
|
|
1217
|
+
const sink = await deliverReviewSink(options.sink, result);
|
|
1218
|
+
const resultWithSink = { ...result, sink };
|
|
1219
|
+
writeJson(path.join(runDir, "sink.json"), sink);
|
|
1220
|
+
writeJson(path.join(runDir, "review.json"), resultWithSink);
|
|
1221
|
+
writeReviewArtifactManifest({
|
|
1222
|
+
runDir,
|
|
1223
|
+
runId: options.runId,
|
|
1224
|
+
adapterName: options.adapter.name,
|
|
1225
|
+
scope: options.scope,
|
|
1226
|
+
mode: "coral",
|
|
1227
|
+
status: "succeeded",
|
|
1228
|
+
createdAt,
|
|
1229
|
+
counts: result.counts,
|
|
1230
|
+
metadata: manifestMetadata,
|
|
1231
|
+
intent,
|
|
1232
|
+
request,
|
|
1233
|
+
});
|
|
1234
|
+
return resultWithSink;
|
|
1235
|
+
}
|
|
1236
|
+
catch (error) {
|
|
1237
|
+
const refineryError = applyErrorContext(asRefineryError(error, { code: "CORAL_REVIEW_FAILED" }), {
|
|
1238
|
+
phase: "coral",
|
|
1239
|
+
runId: options.runId,
|
|
1240
|
+
runDir,
|
|
1241
|
+
});
|
|
1242
|
+
writeJson(path.join(runDir, "coral.json"), {
|
|
1243
|
+
schemaVersion: "refinery.coral-review.v1",
|
|
1244
|
+
status: "failed",
|
|
1245
|
+
runId: options.runId,
|
|
1246
|
+
apiUrl,
|
|
1247
|
+
topology,
|
|
1248
|
+
serverMode,
|
|
1249
|
+
configPath: path.isAbsolute(configPath) ? configPath : path.resolve(repoRoot, configPath),
|
|
1250
|
+
session,
|
|
1251
|
+
threadId,
|
|
1252
|
+
threadIds,
|
|
1253
|
+
proposalThreadId,
|
|
1254
|
+
critiqueThreadId,
|
|
1255
|
+
agents: refineryCoralAgentNames,
|
|
1256
|
+
model: configuredModel,
|
|
1257
|
+
intent,
|
|
1258
|
+
request,
|
|
1259
|
+
readinessSnapshots,
|
|
1260
|
+
specialistMessages,
|
|
1261
|
+
transcriptExcerpts: threadIds.length > 0 ? transcriptFromSnapshot(finalSnapshot, threadIds) : [],
|
|
1262
|
+
serverLogExcerpt: logs.slice(-200),
|
|
1263
|
+
error: {
|
|
1264
|
+
code: refineryError.code,
|
|
1265
|
+
message: refineryError.message,
|
|
1266
|
+
phase: refineryError.phase,
|
|
1267
|
+
},
|
|
1268
|
+
});
|
|
1269
|
+
writeReviewFailureStatus({
|
|
1270
|
+
runDir,
|
|
1271
|
+
runId: options.runId,
|
|
1272
|
+
adapterName: options.adapter.name,
|
|
1273
|
+
scope: options.scope,
|
|
1274
|
+
mode: "coral",
|
|
1275
|
+
createdAt,
|
|
1276
|
+
error: refineryError,
|
|
1277
|
+
intent,
|
|
1278
|
+
request,
|
|
1279
|
+
});
|
|
1280
|
+
throw refineryError;
|
|
1281
|
+
}
|
|
1282
|
+
finally {
|
|
1283
|
+
if (logs.length > 0)
|
|
1284
|
+
fs.writeFileSync(path.join(runDir, "server.log"), `${logs.join("\n")}\n`);
|
|
1285
|
+
if (session && sessionCreated && !coral.noTeardown)
|
|
1286
|
+
await closeSession({ apiUrl, authKey }, session);
|
|
1287
|
+
await stopStartedServer(child);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
//# sourceMappingURL=review-conductor.js.map
|