@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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -0
  3. package/coral/agents/claim-scout/coral-agent.toml +23 -0
  4. package/coral/agents/decision-synthesizer/coral-agent.toml +23 -0
  5. package/coral/agents/evidence-auditor/coral-agent.toml +23 -0
  6. package/coral/agents/memory-cartographer/coral-agent.toml +23 -0
  7. package/coral/agents/proposal-editor/coral-agent.toml +23 -0
  8. package/coral/agents/run-worker.sh +19 -0
  9. package/coral/refinery-config.toml +16 -0
  10. package/dist/adapters/codex-memory.d.ts +6 -0
  11. package/dist/adapters/codex-memory.js +264 -0
  12. package/dist/adapters/codex-memory.js.map +1 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +671 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/coral/client.d.ts +107 -0
  17. package/dist/coral/client.js +214 -0
  18. package/dist/coral/client.js.map +1 -0
  19. package/dist/coral/definitions.d.ts +25 -0
  20. package/dist/coral/definitions.js +53 -0
  21. package/dist/coral/definitions.js.map +1 -0
  22. package/dist/coral/mcp.d.ts +17 -0
  23. package/dist/coral/mcp.js +71 -0
  24. package/dist/coral/mcp.js.map +1 -0
  25. package/dist/coral/review-conductor.d.ts +120 -0
  26. package/dist/coral/review-conductor.js +1290 -0
  27. package/dist/coral/review-conductor.js.map +1 -0
  28. package/dist/coral/smoke.d.ts +1 -0
  29. package/dist/coral/smoke.js +265 -0
  30. package/dist/coral/smoke.js.map +1 -0
  31. package/dist/coral/topology.d.ts +5 -0
  32. package/dist/coral/topology.js +15 -0
  33. package/dist/coral/topology.js.map +1 -0
  34. package/dist/coral/worker.d.ts +34 -0
  35. package/dist/coral/worker.js +671 -0
  36. package/dist/coral/worker.js.map +1 -0
  37. package/dist/core/adapter.d.ts +93 -0
  38. package/dist/core/adapter.js +112 -0
  39. package/dist/core/adapter.js.map +1 -0
  40. package/dist/core/artifacts.d.ts +93 -0
  41. package/dist/core/artifacts.js +200 -0
  42. package/dist/core/artifacts.js.map +1 -0
  43. package/dist/core/deliberation.d.ts +89 -0
  44. package/dist/core/deliberation.js +385 -0
  45. package/dist/core/deliberation.js.map +1 -0
  46. package/dist/core/errors.d.ts +26 -0
  47. package/dist/core/errors.js +50 -0
  48. package/dist/core/errors.js.map +1 -0
  49. package/dist/core/intents.d.ts +5 -0
  50. package/dist/core/intents.js +34 -0
  51. package/dist/core/intents.js.map +1 -0
  52. package/dist/core/live-review.d.ts +93 -0
  53. package/dist/core/live-review.js +269 -0
  54. package/dist/core/live-review.js.map +1 -0
  55. package/dist/core/model-client.d.ts +19 -0
  56. package/dist/core/model-client.js +45 -0
  57. package/dist/core/model-client.js.map +1 -0
  58. package/dist/core/paths.d.ts +16 -0
  59. package/dist/core/paths.js +43 -0
  60. package/dist/core/paths.js.map +1 -0
  61. package/dist/core/review.d.ts +93 -0
  62. package/dist/core/review.js +102 -0
  63. package/dist/core/review.js.map +1 -0
  64. package/dist/core/specialists/claim-scout.d.ts +2 -0
  65. package/dist/core/specialists/claim-scout.js +26 -0
  66. package/dist/core/specialists/claim-scout.js.map +1 -0
  67. package/dist/core/specialists/decision-synthesizer.d.ts +2 -0
  68. package/dist/core/specialists/decision-synthesizer.js +35 -0
  69. package/dist/core/specialists/decision-synthesizer.js.map +1 -0
  70. package/dist/core/specialists/evidence-auditor.d.ts +2 -0
  71. package/dist/core/specialists/evidence-auditor.js +35 -0
  72. package/dist/core/specialists/evidence-auditor.js.map +1 -0
  73. package/dist/core/specialists/harness.d.ts +2 -0
  74. package/dist/core/specialists/harness.js +13 -0
  75. package/dist/core/specialists/harness.js.map +1 -0
  76. package/dist/core/specialists/index.d.ts +8 -0
  77. package/dist/core/specialists/index.js +8 -0
  78. package/dist/core/specialists/index.js.map +1 -0
  79. package/dist/core/specialists/memory-cartographer.d.ts +2 -0
  80. package/dist/core/specialists/memory-cartographer.js +33 -0
  81. package/dist/core/specialists/memory-cartographer.js.map +1 -0
  82. package/dist/core/specialists/prompt.d.ts +3 -0
  83. package/dist/core/specialists/prompt.js +25 -0
  84. package/dist/core/specialists/prompt.js.map +1 -0
  85. package/dist/core/specialists/proposal-editor.d.ts +2 -0
  86. package/dist/core/specialists/proposal-editor.js +37 -0
  87. package/dist/core/specialists/proposal-editor.js.map +1 -0
  88. package/dist/core/specialists/types.d.ts +20 -0
  89. package/dist/core/specialists/types.js +2 -0
  90. package/dist/core/specialists/types.js.map +1 -0
  91. package/dist/env.d.ts +11 -0
  92. package/dist/env.js +53 -0
  93. package/dist/env.js.map +1 -0
  94. package/dist/mcp.d.ts +9 -0
  95. package/dist/mcp.js +147 -0
  96. package/dist/mcp.js.map +1 -0
  97. package/package.json +50 -0
  98. package/skills/refinery/SKILL.md +117 -0
  99. package/skills/refinery/agents/openai.yaml +4 -0
@@ -0,0 +1,671 @@
1
+ import { loadLocalEnv } from "../env.js";
2
+ import { parseModelMaxTokens } from "../env.js";
3
+ import { parseClaimScout, parseDecisionSynthesizer, parseEvidenceFindings, parseProposalEditor, buildPrompt, redactModel, } from "../core/live-review.js";
4
+ import { refineryReviewSchemaVersion } from "../core/adapter.js";
5
+ import { claimScoutSpecialist, decisionSynthesizerSpecialist, evidenceAuditorSpecialist, memoryCartographerSpecialist, proposalEditorSpecialist, } from "../core/specialists/index.js";
6
+ import { callOpenRouterChatWithMetadata } from "../core/model-client.js";
7
+ import { getCoralAgentBySpecialistName, getSpecialistNameArg, refineryCoralAgentNames, refineryCoralModelDefaults, } from "./definitions.js";
8
+ import { connectCoralMcp, parseWaitForMentionResult, readCoralState } from "./mcp.js";
9
+ import { defaultReviewTopology, isReviewTopology } from "./topology.js";
10
+ const coralSpecialistPromptVersion = "refinery.coral-specialist-prompt.v1";
11
+ function readEnv(name, localEnv) {
12
+ return process.env[name] ?? localEnv[name];
13
+ }
14
+ export function loadWorkerModelConfig(cwd = process.cwd()) {
15
+ const localEnv = loadLocalEnv(cwd);
16
+ const apiKey = readEnv("MODEL_API_KEY", localEnv) ?? readEnv("OPENROUTER_API_KEY", localEnv);
17
+ return {
18
+ provider: readEnv("MODEL_PROVIDER", localEnv) ?? readEnv("REFINERY_MODEL_PROVIDER", localEnv) ?? "openrouter",
19
+ modelName: readEnv("MODEL_NAME", localEnv) ?? readEnv("REFINERY_MODEL_NAME", localEnv) ?? refineryCoralModelDefaults.modelName,
20
+ baseUrl: readEnv("MODEL_BASE_URL", localEnv) ?? readEnv("REFINERY_MODEL_BASE_URL", localEnv) ?? refineryCoralModelDefaults.baseUrl,
21
+ apiKey: apiKey ?? "",
22
+ reasoningEffort: readEnv("REASONING_EFFORT", localEnv) ?? refineryCoralModelDefaults.reasoningEffort,
23
+ maxTokens: parseModelMaxTokens(readEnv("MODEL_MAX_TOKENS", localEnv) ?? readEnv("REFINERY_MODEL_MAX_TOKENS", localEnv)),
24
+ apiKeyPresent: Boolean(apiKey),
25
+ };
26
+ }
27
+ function log(agentName, message) {
28
+ console.log(`[${new Date().toISOString()}] [${agentName}] ${message}`);
29
+ }
30
+ function parseMaxTurns() {
31
+ const raw = process.env.REFINERY_CORAL_MAX_TURNS ?? "2";
32
+ const parsed = Number.parseInt(raw, 10);
33
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 2;
34
+ }
35
+ export function isCoralWaitTimeout(error) {
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ return /request timed out|timeout of .* occurred waiting|timed out/i.test(message);
38
+ }
39
+ function isRecord(value) {
40
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
41
+ }
42
+ function parseMessageEnvelope(text) {
43
+ try {
44
+ const parsed = JSON.parse(text);
45
+ if (!isRecord(parsed))
46
+ return null;
47
+ if (parsed.type !== "refinery-ping" && parsed.type !== "refinery-pong")
48
+ return null;
49
+ if (typeof parsed.runId !== "string" || !Array.isArray(parsed.sequence) || typeof parsed.index !== "number") {
50
+ return null;
51
+ }
52
+ return {
53
+ runId: parsed.runId,
54
+ sequence: parsed.sequence.filter((item) => typeof item === "string"),
55
+ index: parsed.index,
56
+ nextAgent: typeof parsed.nextAgent === "string" ? parsed.nextAgent : null,
57
+ };
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ function parseReviewEnvelope(text) {
64
+ try {
65
+ const parsed = JSON.parse(text);
66
+ if (!isRecord(parsed))
67
+ return null;
68
+ if (parsed.type !== "refinery-review-intake" &&
69
+ parsed.type !== "refinery-review-output" &&
70
+ parsed.type !== "refinery-review-merge")
71
+ return null;
72
+ if (typeof parsed.runId !== "string")
73
+ return null;
74
+ return parsed;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ function topologyFrom(envelope) {
81
+ return isReviewTopology(envelope.topology) ? envelope.topology : defaultReviewTopology;
82
+ }
83
+ function phaseFrom(envelope) {
84
+ return typeof envelope.phase === "string" ? envelope.phase : null;
85
+ }
86
+ function outputPhaseFor(args) {
87
+ if (args.topology !== "debate-critique")
88
+ return "pipeline";
89
+ const incomingPhase = phaseFrom(args.envelope);
90
+ switch (args.specialistName) {
91
+ case "claim-scout":
92
+ return "candidate-proposal";
93
+ case "memory-cartographer":
94
+ return "memory-cartography";
95
+ case "evidence-auditor":
96
+ return incomingPhase === "critique-intake" ? "preflight-critique" : "evidence-review";
97
+ case "proposal-editor":
98
+ return "typed-proposal";
99
+ case "decision-synthesizer":
100
+ return "proposal-synthesis";
101
+ }
102
+ }
103
+ function contextFrom(envelope) {
104
+ const base = isRecord(envelope.context)
105
+ ? envelope.context
106
+ : {
107
+ source_chunks: Array.isArray(envelope.source_chunks) ? envelope.source_chunks : [],
108
+ active_memory_hints: Array.isArray(envelope.active_memory_hints) ? envelope.active_memory_hints : [],
109
+ };
110
+ return {
111
+ ...base,
112
+ topology: topologyFrom(envelope),
113
+ phase: phaseFrom(envelope),
114
+ review_intent: typeof envelope.intent === "string"
115
+ ? envelope.intent
116
+ : typeof base.review_intent === "string"
117
+ ? base.review_intent
118
+ : "general-review",
119
+ review_request: typeof envelope.request === "string"
120
+ ? envelope.request
121
+ : typeof base.review_request === "string"
122
+ ? base.review_request
123
+ : null,
124
+ intent_description: typeof envelope.intentDescription === "string"
125
+ ? envelope.intentDescription
126
+ : typeof base.intent_description === "string"
127
+ ? base.intent_description
128
+ : null,
129
+ claim_cards: Array.isArray(envelope.claim_cards)
130
+ ? envelope.claim_cards
131
+ : Array.isArray(base.claim_cards)
132
+ ? base.claim_cards
133
+ : [],
134
+ };
135
+ }
136
+ function arrayFrom(record, field) {
137
+ const value = record[field];
138
+ if (!Array.isArray(value))
139
+ return [];
140
+ return value.filter((item) => isRecord(item));
141
+ }
142
+ function nextReviewAgent(agentName) {
143
+ const index = refineryCoralAgentNames.indexOf(agentName);
144
+ return index >= 0 ? refineryCoralAgentNames[index + 1] ?? null : null;
145
+ }
146
+ export function expectedReviewAgent(envelope, senderName) {
147
+ if (envelope.type === "refinery-review-intake") {
148
+ return topologyFrom(envelope) === "debate-critique" && phaseFrom(envelope) === "critique-intake"
149
+ ? "refinery-evidence-auditor"
150
+ : "refinery-claim-scout";
151
+ }
152
+ if (envelope.type === "refinery-review-merge")
153
+ return "refinery-decision-synthesizer";
154
+ return nextReviewAgent(String(envelope.agent ?? senderName));
155
+ }
156
+ function nextReviewMentions(outputEnvelope, currentAgent) {
157
+ const topology = topologyFrom(outputEnvelope);
158
+ if (topology !== "debate-critique") {
159
+ const next = nextReviewAgent(currentAgent);
160
+ return next ? [next] : [];
161
+ }
162
+ switch (phaseFrom(outputEnvelope)) {
163
+ case "candidate-proposal":
164
+ return ["refinery-memory-cartographer"];
165
+ case "memory-cartography":
166
+ return ["refinery-proposal-editor"];
167
+ case "typed-proposal":
168
+ return [];
169
+ case "preflight-critique":
170
+ return [];
171
+ case "proposal-synthesis":
172
+ return [];
173
+ default: {
174
+ const next = nextReviewAgent(currentAgent);
175
+ return next ? [next] : [];
176
+ }
177
+ }
178
+ }
179
+ function specialistForName(name) {
180
+ switch (name) {
181
+ case "claim-scout":
182
+ return claimScoutSpecialist;
183
+ case "memory-cartographer":
184
+ return memoryCartographerSpecialist;
185
+ case "evidence-auditor":
186
+ return evidenceAuditorSpecialist;
187
+ case "proposal-editor":
188
+ return proposalEditorSpecialist;
189
+ case "decision-synthesizer":
190
+ return decisionSynthesizerSpecialist;
191
+ }
192
+ }
193
+ function outputShapeForSpecialist(name) {
194
+ switch (name) {
195
+ case "claim-scout":
196
+ return `{"candidates":[{"claim":"...","source_refs":[],"why_future_useful":"..."}]}`;
197
+ case "memory-cartographer":
198
+ case "evidence-auditor":
199
+ return `{"findings":[{"body":"...","relation":"novel","target_memory_id":null,"confidence":0.8,"rationale":"...","source_refs":[],"memory_refs":[{"memory_id":"memory:1","provenance_kind":"fixture"}]}]}`;
200
+ case "proposal-editor":
201
+ return `{"typed":[{"body":"...","memory_type":"semantic","primary_type":"semantic","secondary_type":null,"type_confidence":0.8,"type_rationale":"...","ambiguities":[],"durability":"durable","ttl":null,"proposed_scope":"project","action":"create","target_memory_id":null,"target_memory_ids":[],"source_refs":[]}]}`;
202
+ case "decision-synthesizer":
203
+ return `{"proposals":[{"memory_type":"semantic","proposed_scope":"project","body":"...","confidence":0.8,"rationale":"...","source_refs":[],"action":"create","target_memory_id":null,"target_memory_ids":[],"staleness_reason":null,"forget_reason":null,"update_reason":null,"conflict_reason":null,"scope_reason":null,"replacement_body":null,"ambiguities":[]}],"rejected":[{"body":"...","reason":"..."}]}`;
204
+ }
205
+ }
206
+ function instructionForSpecialist(name) {
207
+ switch (name) {
208
+ case "claim-scout":
209
+ return "Emit at most three durable, evidence-bound candidate memories. Prefer fewer high-signal candidates over broad extraction.";
210
+ case "memory-cartographer":
211
+ return "Classify each claim exactly once against active-memory candidates. memory_refs must be objects, never bare strings.";
212
+ case "evidence-auditor":
213
+ return "Audit each claim card exactly once. Prefer challenge relations for duplicate, weak, stale, unsupported, or scope-risk claims; use novel only when the evidence and memory context justify endorsement.";
214
+ case "proposal-editor":
215
+ return "Use project scope for this slice. Set memory_type equal to primary_type. Use canonical action, not mutation_op. Preserve source_refs and target_memory_id from cartography when applicable. For merge or supersede across multiple memories, set target_memory_id to the primary target and target_memory_ids to the full list.";
216
+ case "decision-synthesizer":
217
+ return "Emit proposal-shaped records only for durable future-useful candidates that survive critique. Include rejected[] for filtered candidates and every rejected item must include reason. Use canonical action enum values only; include intent-specific rationale fields when relevant and null otherwise. For multi-target merge or supersede proposals, preserve target_memory_ids and put the primary target in target_memory_id.";
218
+ }
219
+ }
220
+ function intentInstruction(context) {
221
+ const intent = typeof context.review_intent === "string" ? context.review_intent : "general-review";
222
+ const request = typeof context.review_request === "string" && context.review_request.trim()
223
+ ? ` User request: ${context.review_request.trim()}`
224
+ : "";
225
+ switch (intent) {
226
+ case "stale-audit":
227
+ return `Intent guidance: this is a stale audit. Treat active_memory_hints as primary audit targets, use source_chunks as evidence, and prefer update, archive, supersede, ttl_update, or contradiction_review over create when a memory appears outdated, misleading, or over-broad. If no stale target is supported, reject rather than inventing unrelated new memory.${request}`;
228
+ case "forget-candidates":
229
+ return `Intent guidance: identify active memories that may be obsolete, redundant, too noisy, or low-value. Prefer archive, quarantine, merge, demote, or ttl_update proposals with explicit target_memory_id. If evidence is insufficient, reject.${request}`;
230
+ case "update-candidates":
231
+ return `Intent guidance: identify active memories that remain useful but need refreshed wording, replacement body, corrected scope, or newer evidence. Prefer update, supersede, retag, or ttl_update and include replacement_body when useful.${request}`;
232
+ case "conflict-audit":
233
+ return `Intent guidance: identify contradictions between active memories and source evidence. Prefer contradiction_review, update, or supersede with clear evidence refs and target_memory_id.${request}`;
234
+ case "scope-audit":
235
+ return `Intent guidance: identify memories whose scope is too broad, too narrow, or attached to the wrong project/user/org context. Prefer retag, update, demote, or promote with scope_reason.${request}`;
236
+ case "general-review":
237
+ return `Intent guidance: perform a general dry-run memory review and emit only evidence-backed proposals.${request}`;
238
+ default:
239
+ return `Intent guidance: perform a dry-run memory review for intent ${intent} and emit only evidence-backed proposals.${request}`;
240
+ }
241
+ }
242
+ function topologyInstructionForSpecialist(args) {
243
+ if (args.topology !== "debate-critique")
244
+ return "";
245
+ switch (args.phase) {
246
+ case "candidate-proposal":
247
+ return "Topology guidance: this is the Claim Scout phase of the default debate-critique run. Produce source-grounded claims that can become claim cards. Keep each claim durable, evidence-bound, and suitable for local critique.";
248
+ case "memory-cartography":
249
+ return "Topology guidance: this is the Memory Cartographer phase. Map claim cards/candidates to nearby active memories, duplicate targets, supersession targets, and conflicts. Leave final acceptance to debate-critique synthesis.";
250
+ case "preflight-critique":
251
+ return "Topology guidance: this is the Evidence/Provenance Auditor local critique thread. Treat claim_cards as the deliberation unit. For each claim card, make one small structured move using the findings JSON shape: novel is an endorsement; duplicate, too_weak, contradiction, refinement, and supersession are challenges. Ground each challenge in source or active-memory evidence and avoid broad global debate.";
252
+ case "typed-proposal":
253
+ return "Topology guidance: this is the Proposal Editor phase. Turn surviving claims and cartography into typed proposal packets. Preserve evidence so final challenges can target the claim precisely.";
254
+ case "proposal-synthesis":
255
+ return "Topology guidance: this is the Decision Synthesizer merge point. Synthesize typed claims together with debate_critique.claim_cards and debate_critique.challenge_ledger. Final proposal or rejection rationale must explicitly account for relevant challenges, endorsements, or unresolved questions.";
256
+ default:
257
+ return `Topology guidance: debate/critique phase ${args.phase}. Keep reasoning evidence-bound and do not write memory.`;
258
+ }
259
+ }
260
+ function compactMemoryHints(value, limit = 10) {
261
+ if (!Array.isArray(value))
262
+ return [];
263
+ return value.slice(0, limit);
264
+ }
265
+ function claimCards(context) {
266
+ return arrayFrom(context, "claim_cards");
267
+ }
268
+ function activeMemoryCandidates(context, proposalOutput) {
269
+ const proposals = arrayFrom(proposalOutput, "proposals");
270
+ const memories = compactMemoryHints(context.active_memory_hints, 8);
271
+ return proposals.map((proposal, proposalIndex) => ({
272
+ proposal_index: proposalIndex,
273
+ proposal_body: typeof proposal.body === "string" ? proposal.body : null,
274
+ memories,
275
+ }));
276
+ }
277
+ function preflightMemoryCandidates(context) {
278
+ const claims = claimCards(context);
279
+ if (claims.length > 0) {
280
+ const memories = compactMemoryHints(context.active_memory_hints, 10);
281
+ return claims.map((claim, index) => ({
282
+ claim_id: typeof claim.claimId === "string" ? claim.claimId : `claim:${index + 1}`,
283
+ proposal_index: index,
284
+ proposal_body: typeof claim.body === "string" ? claim.body : null,
285
+ source_refs: Array.isArray(claim.sourceRefs) ? claim.sourceRefs : [],
286
+ memories,
287
+ }));
288
+ }
289
+ return compactMemoryHints(context.active_memory_hints, 10).map((memory, index) => ({
290
+ proposal_index: index,
291
+ proposal_body: isRecord(memory) && typeof memory.body === "string" ? memory.body : null,
292
+ memories: [memory],
293
+ }));
294
+ }
295
+ function mergeProposalEditorOutput(envelope) {
296
+ return isRecord(envelope.proposal_editor_output) ? envelope.proposal_editor_output : {};
297
+ }
298
+ function critiqueBundle(envelope, context) {
299
+ if (isRecord(envelope.critique))
300
+ return envelope.critique;
301
+ return isRecord(context.debate_critique) ? context.debate_critique : null;
302
+ }
303
+ function coralThreadContext(args) {
304
+ return {
305
+ threadId: args.message.threadId,
306
+ receivedMessageId: args.message.id,
307
+ senderName: args.message.senderName,
308
+ mentionNames: args.message.mentionNames,
309
+ previousAgent: typeof args.envelope.agent === "string" ? args.envelope.agent : args.message.senderName,
310
+ previousStep: typeof args.envelope.step === "string" ? args.envelope.step : null,
311
+ };
312
+ }
313
+ function payloadForSpecialist(args) {
314
+ const context = contextFrom(args.envelope);
315
+ const topology = topologyFrom(args.envelope);
316
+ const phase = phaseFrom(args.envelope);
317
+ const intentContext = {
318
+ review_intent: context.review_intent,
319
+ review_request: context.review_request,
320
+ intent_description: context.intent_description,
321
+ topology,
322
+ phase,
323
+ };
324
+ const previousOutput = isRecord(args.envelope.output) ? args.envelope.output : {};
325
+ const threadContext = coralThreadContext({ message: args.message, envelope: args.envelope });
326
+ switch (args.specialistName) {
327
+ case "claim-scout":
328
+ return {
329
+ context,
330
+ payload: {
331
+ ...intentContext,
332
+ source_chunks: Array.isArray(context.source_chunks) ? context.source_chunks : [],
333
+ active_memory_hints: compactMemoryHints(context.active_memory_hints),
334
+ coral_thread_context: threadContext,
335
+ },
336
+ };
337
+ case "memory-cartographer":
338
+ return {
339
+ context: {
340
+ ...context,
341
+ claim_candidates: arrayFrom(previousOutput, "candidates"),
342
+ },
343
+ payload: {
344
+ ...intentContext,
345
+ candidates: arrayFrom(previousOutput, "candidates"),
346
+ active_memory_hints: compactMemoryHints(context.active_memory_hints),
347
+ coral_thread_context: threadContext,
348
+ },
349
+ };
350
+ case "proposal-editor":
351
+ return {
352
+ context,
353
+ payload: {
354
+ ...intentContext,
355
+ claim_cards: claimCards(context),
356
+ candidates: arrayFrom(context, "claim_candidates"),
357
+ memory_map: previousOutput,
358
+ cartography_findings: arrayFrom(previousOutput, "findings"),
359
+ active_memory_hints: compactMemoryHints(context.active_memory_hints),
360
+ coral_thread_context: threadContext,
361
+ },
362
+ };
363
+ case "decision-synthesizer":
364
+ if (topology === "debate-critique" && args.envelope.type === "refinery-review-merge") {
365
+ return {
366
+ context: {
367
+ ...context,
368
+ debate_critique: critiqueBundle(args.envelope, context),
369
+ },
370
+ payload: {
371
+ ...intentContext,
372
+ typed: arrayFrom(mergeProposalEditorOutput(args.envelope), "typed"),
373
+ debate_critique: critiqueBundle(args.envelope, context),
374
+ claim_cards: claimCards(context),
375
+ coral_thread_context: threadContext,
376
+ },
377
+ };
378
+ }
379
+ return {
380
+ context,
381
+ payload: {
382
+ ...intentContext,
383
+ typed: arrayFrom(previousOutput, "typed"),
384
+ coral_thread_context: threadContext,
385
+ },
386
+ };
387
+ case "evidence-auditor":
388
+ if (topology === "debate-critique" && phase === "critique-intake") {
389
+ return {
390
+ context,
391
+ payload: {
392
+ ...intentContext,
393
+ claim_cards: claimCards(context),
394
+ source_chunks: Array.isArray(context.source_chunks) ? context.source_chunks : [],
395
+ active_memory_candidates: preflightMemoryCandidates(context),
396
+ coral_thread_context: threadContext,
397
+ },
398
+ };
399
+ }
400
+ return {
401
+ context,
402
+ payload: {
403
+ ...intentContext,
404
+ proposal_synthesis: previousOutput,
405
+ active_memory_candidates: activeMemoryCandidates(context, previousOutput),
406
+ debate_critique: critiqueBundle(args.envelope, context),
407
+ claim_cards: claimCards(context),
408
+ coral_thread_context: threadContext,
409
+ },
410
+ };
411
+ }
412
+ }
413
+ function parseSpecialistOutput(name, raw) {
414
+ switch (name) {
415
+ case "claim-scout":
416
+ return parseClaimScout(raw);
417
+ case "memory-cartographer":
418
+ case "evidence-auditor":
419
+ return parseEvidenceFindings(raw);
420
+ case "proposal-editor":
421
+ return parseProposalEditor(raw);
422
+ case "decision-synthesizer":
423
+ return parseDecisionSynthesizer(raw);
424
+ }
425
+ }
426
+ function failureEnvelope(args) {
427
+ return {
428
+ schemaVersion: refineryReviewSchemaVersion,
429
+ type: "refinery-review-output",
430
+ status: "failed",
431
+ runId: args.runId,
432
+ topology: args.topology,
433
+ phase: args.phase,
434
+ step: args.step,
435
+ agent: args.agentName,
436
+ specialist: args.step,
437
+ receivedMessageId: args.receivedMessageId,
438
+ promptVersion: coralSpecialistPromptVersion,
439
+ model: redactModel(args.model),
440
+ providerMetadata: args.providerMetadata ?? null,
441
+ prompt: args.prompt ?? null,
442
+ rawOutput: args.rawOutput ?? "",
443
+ error: {
444
+ code: args.code,
445
+ message: args.message,
446
+ },
447
+ };
448
+ }
449
+ export async function buildLiveReviewEnvelope(args) {
450
+ const runId = String(args.envelope.runId);
451
+ const specialist = specialistForName(args.specialistName);
452
+ const topology = topologyFrom(args.envelope);
453
+ const phase = outputPhaseFor({ topology, specialistName: args.specialistName, envelope: args.envelope });
454
+ const { payload, context } = payloadForSpecialist(args);
455
+ const prompt = buildPrompt({
456
+ specialist,
457
+ shape: outputShapeForSpecialist(args.specialistName),
458
+ instruction: [
459
+ instructionForSpecialist(args.specialistName),
460
+ intentInstruction(context),
461
+ topologyInstructionForSpecialist({ topology, phase, specialistName: args.specialistName }),
462
+ ].filter(Boolean).join(" "),
463
+ payload,
464
+ });
465
+ if (!args.model.apiKey) {
466
+ return failureEnvelope({
467
+ runId,
468
+ step: args.specialistName,
469
+ topology,
470
+ phase,
471
+ agentName: args.agentName,
472
+ receivedMessageId: args.message.id,
473
+ code: "MODEL_CONFIG_MISSING",
474
+ message: "OPENROUTER_API_KEY or MODEL_API_KEY is required for live Coral specialist execution.",
475
+ model: args.model,
476
+ prompt,
477
+ });
478
+ }
479
+ let rawOutput = "";
480
+ let providerMetadata;
481
+ try {
482
+ const callModel = args.callModel ?? callOpenRouterChatWithMetadata;
483
+ const response = await callModel({
484
+ model: args.model,
485
+ system: prompt.system,
486
+ user: prompt.user,
487
+ });
488
+ rawOutput = response.content;
489
+ providerMetadata = response.metadata;
490
+ }
491
+ catch (error) {
492
+ return failureEnvelope({
493
+ runId,
494
+ step: args.specialistName,
495
+ topology,
496
+ phase,
497
+ agentName: args.agentName,
498
+ receivedMessageId: args.message.id,
499
+ code: "MODEL_CALL_FAILED",
500
+ message: error instanceof Error ? error.message : String(error),
501
+ model: args.model,
502
+ providerMetadata,
503
+ prompt,
504
+ });
505
+ }
506
+ let parsed;
507
+ try {
508
+ parsed = parseSpecialistOutput(args.specialistName, rawOutput);
509
+ }
510
+ catch (error) {
511
+ return failureEnvelope({
512
+ runId,
513
+ step: args.specialistName,
514
+ topology,
515
+ phase,
516
+ agentName: args.agentName,
517
+ receivedMessageId: args.message.id,
518
+ code: "MODEL_OUTPUT_INVALID",
519
+ message: error instanceof Error ? error.message : String(error),
520
+ rawOutput,
521
+ model: args.model,
522
+ providerMetadata,
523
+ prompt,
524
+ });
525
+ }
526
+ return {
527
+ schemaVersion: refineryReviewSchemaVersion,
528
+ type: "refinery-review-output",
529
+ status: "succeeded",
530
+ runId,
531
+ topology,
532
+ phase,
533
+ step: args.specialistName,
534
+ agent: args.agentName,
535
+ specialist: args.specialistName,
536
+ receivedMessageId: args.message.id,
537
+ promptVersion: coralSpecialistPromptVersion,
538
+ model: redactModel(args.model),
539
+ providerMetadata: providerMetadata ?? null,
540
+ prompt,
541
+ rawOutput,
542
+ output: parsed,
543
+ context,
544
+ };
545
+ }
546
+ async function main() {
547
+ const specialistName = getSpecialistNameArg(process.argv.slice(2));
548
+ const definition = getCoralAgentBySpecialistName(specialistName);
549
+ const model = loadWorkerModelConfig();
550
+ const coralConnectionUrl = process.env.CORAL_CONNECTION_URL;
551
+ if (!coralConnectionUrl)
552
+ throw new Error("CORAL_CONNECTION_URL is required for executable Coral agents");
553
+ log(definition.agentName, `booted specialist=${definition.specialistName} session=${process.env.CORAL_SESSION_ID ?? "unknown"}`);
554
+ log(definition.agentName, `model=${model.modelName} baseUrl=${model.baseUrl} reasoning=${model.reasoningEffort} apiKey=${model.apiKeyPresent ? "present" : "missing"}`);
555
+ const connection = await connectCoralMcp(coralConnectionUrl, `refinery-${definition.specialistName}-worker`);
556
+ log(definition.agentName, `mcp connected tools=${connection.toolNames.join(",")}`);
557
+ try {
558
+ const state = await readCoralState(connection.client);
559
+ log(definition.agentName, `state readable=${isRecord(state) && !("error" in state) ? "yes" : "partial"}`);
560
+ }
561
+ catch (error) {
562
+ log(definition.agentName, `state read failed: ${error.message}`);
563
+ }
564
+ let cursorMs = 0;
565
+ let handled = 0;
566
+ const handledIds = new Set();
567
+ const maxTurns = parseMaxTurns();
568
+ while (handled < maxTurns) {
569
+ const beforeWait = Date.now();
570
+ let waitResult;
571
+ try {
572
+ waitResult = await connection.client.callTool({
573
+ name: connection.waitForMentionToolName,
574
+ arguments: { currentUnixTime: cursorMs, maxWaitMs: 60_000 },
575
+ });
576
+ }
577
+ catch (error) {
578
+ if (isCoralWaitTimeout(error)) {
579
+ cursorMs = beforeWait;
580
+ log(definition.agentName, `wait_for_mention timed out; continuing idle wait`);
581
+ continue;
582
+ }
583
+ log(definition.agentName, `wait_for_mention failed: ${error.message}`);
584
+ await connection.client.close();
585
+ process.exit(0);
586
+ }
587
+ cursorMs = beforeWait;
588
+ const message = parseWaitForMentionResult(waitResult);
589
+ if (!message)
590
+ continue;
591
+ if (handledIds.has(message.id))
592
+ continue;
593
+ handledIds.add(message.id);
594
+ const reviewEnvelope = parseReviewEnvelope(message.text);
595
+ if (reviewEnvelope) {
596
+ const expectedAgent = expectedReviewAgent(reviewEnvelope, message.senderName);
597
+ if (expectedAgent !== definition.agentName && !message.mentionNames.includes(definition.agentName)) {
598
+ log(definition.agentName, `ignored review message expected=${expectedAgent ?? "none"}`);
599
+ continue;
600
+ }
601
+ handled += 1;
602
+ const outputEnvelope = await buildLiveReviewEnvelope({
603
+ specialistName: definition.specialistName,
604
+ agentName: definition.agentName,
605
+ envelope: reviewEnvelope,
606
+ message,
607
+ model,
608
+ });
609
+ const mentions = outputEnvelope.status === "succeeded" ? nextReviewMentions(outputEnvelope, definition.agentName) : [];
610
+ const content = JSON.stringify(outputEnvelope);
611
+ await connection.client.callTool({
612
+ name: connection.sendMessageToolName,
613
+ arguments: {
614
+ threadId: message.threadId,
615
+ content,
616
+ mentions,
617
+ },
618
+ });
619
+ log(definition.agentName, `review output sent status=${String(outputEnvelope.status)} phase=${String(outputEnvelope.phase ?? "none")} thread=${message.threadId} next=${mentions.join(",") || "none"}`);
620
+ continue;
621
+ }
622
+ const envelope = parseMessageEnvelope(message.text);
623
+ if (!envelope) {
624
+ log(definition.agentName, `ignored non-ping message from ${message.senderName}`);
625
+ continue;
626
+ }
627
+ const expectedAgent = envelope.sequence[envelope.index];
628
+ const ownIndex = envelope.sequence.indexOf(definition.agentName);
629
+ if (expectedAgent !== definition.agentName && envelope.nextAgent !== definition.agentName) {
630
+ log(definition.agentName, `ignored ping index=${envelope.index} expected=${expectedAgent} next=${envelope.nextAgent ?? "none"}`);
631
+ continue;
632
+ }
633
+ if (ownIndex < 0) {
634
+ log(definition.agentName, "ignored ping because this agent is not in the sequence");
635
+ continue;
636
+ }
637
+ handled += 1;
638
+ const nextPingAgent = envelope.sequence[ownIndex + 1] ?? null;
639
+ const content = JSON.stringify({
640
+ type: "refinery-pong",
641
+ runId: envelope.runId,
642
+ sequence: envelope.sequence,
643
+ index: ownIndex,
644
+ agent: definition.agentName,
645
+ specialist: definition.specialistName,
646
+ receivedMessageId: message.id,
647
+ nextAgent: nextPingAgent,
648
+ purpose: definition.specialist.purpose,
649
+ });
650
+ await connection.client.callTool({
651
+ name: connection.sendMessageToolName,
652
+ arguments: {
653
+ threadId: message.threadId,
654
+ content,
655
+ mentions: nextPingAgent ? [nextPingAgent] : [],
656
+ },
657
+ });
658
+ log(definition.agentName, `responded in thread=${message.threadId} next=${nextPingAgent ?? "none"}`);
659
+ }
660
+ log(definition.agentName, `max turns reached (${maxTurns}); exiting cleanly`);
661
+ await connection.client.close();
662
+ }
663
+ if (import.meta.url === `file://${process.argv[1]}`) {
664
+ main().catch((error) => {
665
+ const label = process.env.CORAL_AGENT_ID ?? "refinery-worker";
666
+ console.error(`[${new Date().toISOString()}] [${label}] FATAL: ${error.message}`);
667
+ console.error(error);
668
+ process.exit(1);
669
+ });
670
+ }
671
+ //# sourceMappingURL=worker.js.map