voratiq 0.1.0-beta.20 → 0.1.0-beta.21

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 (85) hide show
  1. package/dist/agents/runtime/policy.d.ts +1 -1
  2. package/dist/bin.js +50 -19
  3. package/dist/cli/contract.d.ts +35 -5
  4. package/dist/cli/contract.js +42 -5
  5. package/dist/cli/list.js +4 -1
  6. package/dist/cli/message.d.ts +28 -0
  7. package/dist/cli/message.js +147 -0
  8. package/dist/cli/operator-envelope.d.ts +22 -1
  9. package/dist/cli/operator-envelope.js +48 -3
  10. package/dist/cli/reduce.js +5 -2
  11. package/dist/cli/verify.js +6 -2
  12. package/dist/commands/list/command.d.ts +1 -0
  13. package/dist/commands/list/command.js +117 -7
  14. package/dist/commands/message/command.d.ts +23 -0
  15. package/dist/commands/message/command.js +215 -0
  16. package/dist/commands/message/errors.d.ts +9 -0
  17. package/dist/commands/message/errors.js +20 -0
  18. package/dist/commands/message/lifecycle.d.ts +14 -0
  19. package/dist/commands/message/lifecycle.js +128 -0
  20. package/dist/commands/reduce/command.d.ts +1 -0
  21. package/dist/commands/reduce/command.js +3 -1
  22. package/dist/commands/reduce/targets.d.ts +1 -0
  23. package/dist/commands/reduce/targets.js +48 -2
  24. package/dist/commands/shared/resolve-stage-competitors.js +4 -1
  25. package/dist/commands/verify/command.d.ts +1 -0
  26. package/dist/commands/verify/command.js +2 -1
  27. package/dist/commands/verify/max-parallel.d.ts +1 -1
  28. package/dist/commands/verify/max-parallel.js +3 -1
  29. package/dist/commands/verify/targets.d.ts +2 -1
  30. package/dist/commands/verify/targets.js +154 -22
  31. package/dist/configs/agents/defaults.js +25 -25
  32. package/dist/configs/orchestration/bootstrap.d.ts +6 -1
  33. package/dist/configs/orchestration/bootstrap.js +27 -6
  34. package/dist/configs/orchestration/types.d.ts +25 -10
  35. package/dist/configs/orchestration/types.js +10 -6
  36. package/dist/configs/verification/types.d.ts +5 -0
  37. package/dist/configs/verification/types.js +3 -0
  38. package/dist/contracts/list.d.ts +38 -4
  39. package/dist/contracts/list.js +30 -1
  40. package/dist/domain/message/competition/adapter.d.ts +36 -0
  41. package/dist/domain/message/competition/adapter.js +197 -0
  42. package/dist/domain/message/competition/prompt.d.ts +8 -0
  43. package/dist/domain/message/competition/prompt.js +29 -0
  44. package/dist/domain/message/model/mutators.d.ts +17 -0
  45. package/dist/domain/message/model/mutators.js +107 -0
  46. package/dist/domain/message/model/types.d.ts +100 -0
  47. package/dist/domain/message/model/types.js +87 -0
  48. package/dist/domain/message/persistence/adapter.d.ts +43 -0
  49. package/dist/domain/message/persistence/adapter.js +124 -0
  50. package/dist/domain/reduce/competition/adapter.d.ts +1 -0
  51. package/dist/domain/reduce/competition/adapter.js +57 -4
  52. package/dist/domain/reduce/competition/prompt.d.ts +1 -1
  53. package/dist/domain/reduce/model/types.d.ts +3 -0
  54. package/dist/domain/reduce/model/types.js +2 -1
  55. package/dist/domain/verify/competition/adapter.js +8 -5
  56. package/dist/domain/verify/competition/programmatic.js +3 -0
  57. package/dist/domain/verify/competition/prompt.js +6 -0
  58. package/dist/domain/verify/competition/rubric.js +7 -1
  59. package/dist/domain/verify/competition/shared-layout.d.ts +42 -2
  60. package/dist/domain/verify/competition/shared-layout.js +92 -21
  61. package/dist/domain/verify/competition/target.d.ts +17 -0
  62. package/dist/domain/verify/model/types.d.ts +20 -2
  63. package/dist/domain/verify/model/types.js +15 -2
  64. package/dist/mcp/server.d.ts +2 -2
  65. package/dist/mcp/server.js +30 -13
  66. package/dist/policy/verification.js +2 -0
  67. package/dist/preflight/index.d.ts +2 -0
  68. package/dist/preflight/index.js +3 -1
  69. package/dist/render/transcripts/list.d.ts +2 -0
  70. package/dist/render/transcripts/list.js +24 -0
  71. package/dist/render/transcripts/message.d.ts +72 -0
  72. package/dist/render/transcripts/message.js +362 -0
  73. package/dist/render/transcripts/stage-progress.d.ts +1 -1
  74. package/dist/render/transcripts/verify.d.ts +4 -0
  75. package/dist/render/transcripts/verify.js +7 -1
  76. package/dist/render/utils/transcript-shell.d.ts +1 -1
  77. package/dist/render/utils/transcript-shell.js +6 -0
  78. package/dist/status/index.d.ts +21 -0
  79. package/dist/status/index.js +30 -0
  80. package/dist/workspace/setup.js +13 -7
  81. package/dist/workspace/structure.d.ts +14 -6
  82. package/dist/workspace/structure.js +25 -9
  83. package/dist/workspace/verification-defaults.d.ts +1 -1
  84. package/dist/workspace/verification-defaults.js +255 -0
  85. package/package.json +15 -16
@@ -0,0 +1,215 @@
1
+ import { verifyAgentProviders } from "../../agents/runtime/auth.js";
2
+ import { executeCompetitionWithAdapter } from "../../competition/command-adapter.js";
3
+ import { createTeardownController } from "../../competition/shared/teardown.js";
4
+ import { AgentNotFoundError } from "../../configs/agents/errors.js";
5
+ import { loadEnvironmentConfig } from "../../configs/environment/loader.js";
6
+ import { createMessageCompetitionAdapter, } from "../../domain/message/competition/adapter.js";
7
+ import { createMessageRecordMutators } from "../../domain/message/model/mutators.js";
8
+ import { deriveMessageStatusFromRecipients, } from "../../domain/message/model/types.js";
9
+ import { appendMessageRecord, flushMessageRecordBuffer, } from "../../domain/message/persistence/adapter.js";
10
+ import { buildPersistedExtraContextFields } from "../../extra-context/contract.js";
11
+ import { toErrorMessage } from "../../utils/errors.js";
12
+ import { getHeadRevision } from "../../utils/git.js";
13
+ import { VORATIQ_MESSAGE_DIR, VORATIQ_REDUCTION_DIR, VORATIQ_RUN_DIR, VORATIQ_SPEC_DIR, VORATIQ_VERIFICATION_DIR, } from "../../workspace/structure.js";
14
+ import { resolveEffectiveMaxParallel } from "../shared/max-parallel.js";
15
+ import { resolveStageCompetitors } from "../shared/resolve-stage-competitors.js";
16
+ import { generateSessionId } from "../shared/session-id.js";
17
+ import { MessageAgentNotFoundError, MessageGenerationFailedError, MessageInvocationContextError, } from "./errors.js";
18
+ import { finalizeActiveMessage, registerActiveMessage } from "./lifecycle.js";
19
+ export async function executeMessageCommand(input) {
20
+ assertMessageInvocationContext();
21
+ const { root, messagesFilePath, prompt, agentIds, agentOverrideFlag, profileName, maxParallel: requestedMaxParallel, extraContextFiles = [], sourceInteractiveSessionId, renderer, } = input;
22
+ let competitors;
23
+ try {
24
+ const resolution = resolveStageCompetitors({
25
+ root,
26
+ stageId: "message",
27
+ cliAgentIds: agentIds,
28
+ cliOverrideFlag: agentOverrideFlag,
29
+ profileName,
30
+ });
31
+ competitors = resolution.competitors;
32
+ }
33
+ catch (error) {
34
+ if (error instanceof AgentNotFoundError) {
35
+ throw new MessageAgentNotFoundError(error.agentId);
36
+ }
37
+ throw error;
38
+ }
39
+ await assertMessagePreflight(competitors);
40
+ const environment = loadEnvironmentConfig({ root });
41
+ const baseRevisionSha = await getHeadRevision(root);
42
+ const messageId = generateSessionId();
43
+ const createdAt = new Date().toISOString();
44
+ const startedAt = createdAt;
45
+ const effectiveMaxParallel = resolveEffectiveMaxParallel({
46
+ competitorCount: competitors.length,
47
+ requestedMaxParallel,
48
+ });
49
+ const initialRecipients = competitors.map((agent) => ({
50
+ agentId: agent.id,
51
+ status: "queued",
52
+ }));
53
+ await appendMessageRecord({
54
+ root,
55
+ messagesFilePath,
56
+ record: {
57
+ sessionId: messageId,
58
+ createdAt,
59
+ startedAt,
60
+ status: "running",
61
+ baseRevisionSha,
62
+ prompt,
63
+ recipients: initialRecipients,
64
+ ...buildPersistedExtraContextFields(extraContextFiles),
65
+ ...(sourceInteractiveSessionId ? { sourceInteractiveSessionId } : {}),
66
+ },
67
+ });
68
+ renderer?.begin({
69
+ messageId,
70
+ createdAt,
71
+ startedAt,
72
+ workspacePath: `.voratiq/message/sessions/${messageId}`,
73
+ status: "running",
74
+ });
75
+ const teardown = createTeardownController(`message \`${messageId}\``);
76
+ registerActiveMessage({
77
+ root,
78
+ messagesFilePath,
79
+ messageId,
80
+ teardown,
81
+ });
82
+ const mutators = createMessageRecordMutators({
83
+ root,
84
+ messagesFilePath,
85
+ messageId,
86
+ });
87
+ let executions;
88
+ try {
89
+ const baseAdapter = createMessageCompetitionAdapter({
90
+ root,
91
+ messageId,
92
+ prompt,
93
+ environment,
94
+ extraContextFiles,
95
+ teardown,
96
+ });
97
+ executions = await executeCompetitionWithAdapter({
98
+ candidates: [...competitors],
99
+ maxParallel: effectiveMaxParallel,
100
+ adapter: {
101
+ ...baseAdapter,
102
+ onPreparationFailure: async (result) => {
103
+ const recipient = toRecipientEntry(result);
104
+ await mutators.recordRecipientSnapshot(recipient);
105
+ renderer?.update(recipient);
106
+ },
107
+ onCandidateRunning: async (prepared) => {
108
+ const runningAt = new Date().toISOString();
109
+ const recipient = {
110
+ agentId: prepared.candidate.id,
111
+ status: "running",
112
+ startedAt: runningAt,
113
+ };
114
+ await mutators.recordRecipientRunning(recipient);
115
+ renderer?.update(recipient);
116
+ },
117
+ onCandidateCompleted: async (_prepared, result) => {
118
+ const recipient = toRecipientEntry(result);
119
+ await mutators.recordRecipientSnapshot(recipient);
120
+ renderer?.update(recipient);
121
+ },
122
+ },
123
+ });
124
+ const persistedRecord = await mutators.readRecord();
125
+ if (!persistedRecord) {
126
+ throw new MessageGenerationFailedError([
127
+ `Message session \`${messageId}\` record not found after execution.`,
128
+ ]);
129
+ }
130
+ const status = deriveMessageStatusFromRecipients(persistedRecord.recipients.map((recipient) => recipient.status));
131
+ const completedRecord = await mutators.completeMessage({
132
+ status,
133
+ error: status === "failed"
134
+ ? collectRecipientErrors(persistedRecord.recipients)
135
+ : undefined,
136
+ });
137
+ await flushMessageRecordBuffer({
138
+ messagesFilePath,
139
+ sessionId: messageId,
140
+ });
141
+ renderer?.complete(completedRecord.status, {
142
+ startedAt: completedRecord.startedAt,
143
+ completedAt: completedRecord.completedAt,
144
+ });
145
+ return {
146
+ messageId,
147
+ record: completedRecord,
148
+ recipients: completedRecord.recipients,
149
+ executions,
150
+ };
151
+ }
152
+ catch (error) {
153
+ await mutators
154
+ .completeMessage({
155
+ status: "failed",
156
+ error: toErrorMessage(error),
157
+ })
158
+ .then((failedRecord) => {
159
+ renderer?.complete(failedRecord.status, {
160
+ startedAt: failedRecord.startedAt,
161
+ completedAt: failedRecord.completedAt,
162
+ });
163
+ })
164
+ .catch(() => {
165
+ renderer?.complete("failed");
166
+ });
167
+ await flushMessageRecordBuffer({
168
+ messagesFilePath,
169
+ sessionId: messageId,
170
+ }).catch(() => { });
171
+ throw new MessageGenerationFailedError([toErrorMessage(error)]);
172
+ }
173
+ finally {
174
+ await finalizeActiveMessage(messageId);
175
+ }
176
+ }
177
+ function toRecipientEntry(execution) {
178
+ return {
179
+ agentId: execution.agentId,
180
+ status: execution.status,
181
+ startedAt: execution.startedAt,
182
+ completedAt: execution.completedAt,
183
+ outputPath: execution.outputPath,
184
+ stdoutPath: execution.stdoutPath,
185
+ stderrPath: execution.stderrPath,
186
+ tokenUsage: execution.tokenUsage,
187
+ error: execution.error ?? null,
188
+ };
189
+ }
190
+ async function assertMessagePreflight(agents) {
191
+ const providerIssues = await verifyAgentProviders(agents.map((agent) => ({ id: agent.id, provider: agent.provider })));
192
+ if (providerIssues.length > 0) {
193
+ throw new MessageGenerationFailedError(providerIssues.map((issue) => `${issue.agentId}: ${issue.message}`));
194
+ }
195
+ }
196
+ function assertMessageInvocationContext() {
197
+ const cwd = process.cwd().replace(/\\/gu, "/");
198
+ const batchDomains = [
199
+ VORATIQ_SPEC_DIR,
200
+ VORATIQ_RUN_DIR,
201
+ VORATIQ_REDUCTION_DIR,
202
+ VORATIQ_VERIFICATION_DIR,
203
+ VORATIQ_MESSAGE_DIR,
204
+ ];
205
+ const batchWorkspacePattern = new RegExp(`/\\.voratiq/(?:${batchDomains.join("|")})/sessions/[^/]+/[^/]+/workspace(?:/|$)`, "u");
206
+ if (batchWorkspacePattern.test(cwd)) {
207
+ throw new MessageInvocationContextError();
208
+ }
209
+ }
210
+ function collectRecipientErrors(recipients) {
211
+ const details = recipients
212
+ .filter((recipient) => recipient.error)
213
+ .map((recipient) => `${recipient.agentId}: ${recipient.error}`);
214
+ return details.length > 0 ? details.join("; ") : undefined;
215
+ }
@@ -0,0 +1,9 @@
1
+ export declare class MessageAgentNotFoundError extends Error {
2
+ constructor(agentId: string);
3
+ }
4
+ export declare class MessageGenerationFailedError extends Error {
5
+ constructor(details: readonly string[]);
6
+ }
7
+ export declare class MessageInvocationContextError extends Error {
8
+ constructor();
9
+ }
@@ -0,0 +1,20 @@
1
+ export class MessageAgentNotFoundError extends Error {
2
+ constructor(agentId) {
3
+ super(`Message agent not found: ${agentId}`);
4
+ this.name = "MessageAgentNotFoundError";
5
+ }
6
+ }
7
+ export class MessageGenerationFailedError extends Error {
8
+ constructor(details) {
9
+ super(details.length > 0
10
+ ? `Message execution failed: ${details.join("; ")}`
11
+ : "Message execution failed.");
12
+ this.name = "MessageGenerationFailedError";
13
+ }
14
+ }
15
+ export class MessageInvocationContextError extends Error {
16
+ constructor() {
17
+ super("`message` cannot be invoked from inside a batch agent workspace.");
18
+ this.name = "MessageInvocationContextError";
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ import type { TeardownController } from "../../competition/shared/teardown.js";
2
+ export declare const MESSAGE_ABORT_DETAIL = "Message generation aborted before completion.";
3
+ export declare const MESSAGE_FAILURE_DETAIL = "Message generation failed.";
4
+ interface ActiveMessageContext {
5
+ root: string;
6
+ messagesFilePath: string;
7
+ messageId: string;
8
+ teardown?: TeardownController;
9
+ }
10
+ export declare function registerActiveMessage(context: ActiveMessageContext): void;
11
+ export declare function clearActiveMessage(messageId: string): void;
12
+ export declare function terminateActiveMessage(status: "failed" | "aborted"): Promise<void>;
13
+ export declare function finalizeActiveMessage(messageId: string): Promise<void>;
14
+ export {};
@@ -0,0 +1,128 @@
1
+ import { runTeardown } from "../../competition/shared/teardown.js";
2
+ import { flushMessageRecordBuffer, readMessageRecords, rewriteMessageRecord, } from "../../domain/message/persistence/adapter.js";
3
+ import { buildOperationLifecycleCompleteFields, buildRecordLifecycleCompleteFields, } from "../../domain/shared/lifecycle.js";
4
+ import { toErrorMessage } from "../../utils/errors.js";
5
+ export const MESSAGE_ABORT_DETAIL = "Message generation aborted before completion.";
6
+ export const MESSAGE_FAILURE_DETAIL = "Message generation failed.";
7
+ let activeMessage;
8
+ let terminationInFlight = false;
9
+ export function registerActiveMessage(context) {
10
+ activeMessage = context;
11
+ }
12
+ export function clearActiveMessage(messageId) {
13
+ if (activeMessage?.messageId !== messageId) {
14
+ return;
15
+ }
16
+ if (!terminationInFlight) {
17
+ activeMessage = undefined;
18
+ }
19
+ }
20
+ export async function terminateActiveMessage(status) {
21
+ if (!activeMessage || terminationInFlight) {
22
+ return;
23
+ }
24
+ terminationInFlight = true;
25
+ const context = activeMessage;
26
+ let persistenceError;
27
+ try {
28
+ const existingRecord = await readMessageRecords({
29
+ root: context.root,
30
+ messagesFilePath: context.messagesFilePath,
31
+ limit: 1,
32
+ predicate: (record) => record.sessionId === context.messageId,
33
+ }).then((records) => records[0]);
34
+ if (!existingRecord) {
35
+ return;
36
+ }
37
+ const completedAt = new Date().toISOString();
38
+ const detail = status === "aborted" ? MESSAGE_ABORT_DETAIL : MESSAGE_FAILURE_DETAIL;
39
+ await rewriteMessageRecord({
40
+ root: context.root,
41
+ messagesFilePath: context.messagesFilePath,
42
+ sessionId: context.messageId,
43
+ mutate: (existing) => {
44
+ const recipients = existing.recipients.map((recipient) => finalizeMessageRecipient({
45
+ recipient,
46
+ status,
47
+ completedAt,
48
+ detail,
49
+ }));
50
+ const inProgress = existing.status === "queued" || existing.status === "running";
51
+ if (!inProgress) {
52
+ if (recipients.every((recipient, index) => recipient === existing.recipients[index])) {
53
+ return existing;
54
+ }
55
+ return {
56
+ ...existing,
57
+ recipients,
58
+ };
59
+ }
60
+ return {
61
+ ...existing,
62
+ status,
63
+ recipients,
64
+ ...buildRecordLifecycleCompleteFields({
65
+ existing,
66
+ startedAt: existing.startedAt ?? completedAt,
67
+ completedAt,
68
+ }),
69
+ error: existing.error ?? detail,
70
+ };
71
+ },
72
+ forceFlush: true,
73
+ });
74
+ await flushMessageRecordBuffer({
75
+ messagesFilePath: context.messagesFilePath,
76
+ sessionId: context.messageId,
77
+ });
78
+ }
79
+ catch (error) {
80
+ persistenceError =
81
+ error instanceof Error ? error : new Error(toErrorMessage(error));
82
+ console.error(`[voratiq] Failed to finalize message ${context.messageId}: ${toErrorMessage(error)}`);
83
+ }
84
+ finally {
85
+ try {
86
+ await finalizeRegisteredMessageTeardown(context);
87
+ }
88
+ finally {
89
+ terminationInFlight = false;
90
+ activeMessage = undefined;
91
+ }
92
+ }
93
+ if (persistenceError) {
94
+ throw persistenceError;
95
+ }
96
+ }
97
+ export async function finalizeActiveMessage(messageId) {
98
+ if (!activeMessage || activeMessage.messageId !== messageId) {
99
+ clearActiveMessage(messageId);
100
+ return;
101
+ }
102
+ const context = activeMessage;
103
+ try {
104
+ await finalizeRegisteredMessageTeardown(context);
105
+ }
106
+ finally {
107
+ clearActiveMessage(messageId);
108
+ }
109
+ }
110
+ function finalizeMessageRecipient(options) {
111
+ const { recipient, status, completedAt, detail } = options;
112
+ if (recipient.status !== "queued" && recipient.status !== "running") {
113
+ return recipient;
114
+ }
115
+ return {
116
+ ...recipient,
117
+ status,
118
+ ...buildOperationLifecycleCompleteFields({
119
+ existing: recipient,
120
+ startedAt: recipient.startedAt ?? completedAt,
121
+ completedAt,
122
+ }),
123
+ error: recipient.error ?? detail,
124
+ };
125
+ }
126
+ async function finalizeRegisteredMessageTeardown(context) {
127
+ await runTeardown(context.teardown);
128
+ }
@@ -7,6 +7,7 @@ export interface ReduceCommandInput {
7
7
  specsFilePath: string;
8
8
  runsFilePath: string;
9
9
  reductionsFilePath: string;
10
+ messagesFilePath: string;
10
11
  verificationsFilePath: string;
11
12
  target: ReductionTarget;
12
13
  agentIds?: readonly string[];
@@ -11,12 +11,13 @@ import { generateSessionId } from "../shared/session-id.js";
11
11
  import { ReduceAgentNotFoundError, ReduceGenerationFailedError, ReducePreflightError, } from "./errors.js";
12
12
  import { assertReductionTargetEligible } from "./targets.js";
13
13
  export async function executeReduceCommand(input) {
14
- const { root, specsFilePath, runsFilePath, reductionsFilePath, verificationsFilePath, target, agentIds, agentOverrideFlag, profileName, maxParallel: requestedMaxParallel, extraContextFiles = [], renderer, } = input;
14
+ const { root, specsFilePath, runsFilePath, reductionsFilePath, messagesFilePath, verificationsFilePath, target, agentIds, agentOverrideFlag, profileName, maxParallel: requestedMaxParallel, extraContextFiles = [], renderer, } = input;
15
15
  await assertReductionTargetEligible({
16
16
  root,
17
17
  specsFilePath,
18
18
  runsFilePath,
19
19
  reductionsFilePath,
20
+ messagesFilePath,
20
21
  verificationsFilePath,
21
22
  target,
22
23
  });
@@ -53,6 +54,7 @@ export async function executeReduceCommand(input) {
53
54
  reductionsFilePath,
54
55
  specsFilePath,
55
56
  runsFilePath,
57
+ messagesFilePath,
56
58
  verificationsFilePath,
57
59
  target,
58
60
  environment,
@@ -4,6 +4,7 @@ export interface ReductionTargetValidationInput {
4
4
  specsFilePath: string;
5
5
  runsFilePath: string;
6
6
  reductionsFilePath: string;
7
+ messagesFilePath: string;
7
8
  verificationsFilePath: string;
8
9
  target: ReductionTarget;
9
10
  }
@@ -1,6 +1,8 @@
1
1
  import { dirname } from "node:path";
2
2
  import { CliError } from "../../cli/errors.js";
3
3
  import { RunNotFoundCliError } from "../../cli/errors.js";
4
+ import { TERMINAL_MESSAGE_STATUSES } from "../../domain/message/model/types.js";
5
+ import { readMessageRecords } from "../../domain/message/persistence/adapter.js";
4
6
  import { TERMINAL_REDUCTION_STATUSES, } from "../../domain/reduce/model/types.js";
5
7
  import { readReductionRecords } from "../../domain/reduce/persistence/adapter.js";
6
8
  import { buildRunRecordView } from "../../domain/run/model/enhanced.js";
@@ -23,11 +25,14 @@ export async function assertReductionTargetEligible(input) {
23
25
  case "run":
24
26
  await assertRunTargetEligible(input);
25
27
  return;
28
+ case "reduce":
29
+ await assertReductionTargetEligibleInternal(input);
30
+ return;
26
31
  case "verify":
27
32
  await assertVerificationTargetEligible(input);
28
33
  return;
29
- case "reduce":
30
- await assertReductionTargetEligibleInternal(input);
34
+ case "message":
35
+ await assertMessageTargetEligible(input);
31
36
  return;
32
37
  }
33
38
  }
@@ -162,6 +167,35 @@ async function assertReductionTargetEligibleInternal(input) {
162
167
  throw new CliError(`Reduction session \`${target.id}\` is missing required artifacts.`, missing.map((path) => `Missing: \`${path}\`.`), ["Re-run `voratiq reduce` to regenerate reduction artifacts."]);
163
168
  }
164
169
  }
170
+ async function assertMessageTargetEligible(input) {
171
+ const { root, messagesFilePath, target } = input;
172
+ const [record] = await readMessageRecords({
173
+ root,
174
+ messagesFilePath,
175
+ limit: 1,
176
+ predicate: (entry) => entry.sessionId === target.id,
177
+ });
178
+ if (!record) {
179
+ throw new CliError(`Message session \`${target.id}\` not found.`, [], [
180
+ "Re-run `voratiq message` or confirm the session id in `.voratiq/message/index.json`.",
181
+ ]);
182
+ }
183
+ if (!TERMINAL_MESSAGE_STATUSES.includes(record.status)) {
184
+ throw new CliError(`Message session \`${target.id}\` is not complete.`, [`Status: \`${record.status}\`.`], ["Wait for the message to finish before reducing."]);
185
+ }
186
+ if (record.status !== "succeeded") {
187
+ throw new CliError(`Message session \`${target.id}\` did not complete.`, [`Status: \`${record.status}\`.`], ["Re-run `voratiq message` to generate a complete artifact set."]);
188
+ }
189
+ const generatedRecipients = record.recipients.filter((recipient) => recipient.status === "succeeded" &&
190
+ typeof recipient.outputPath === "string");
191
+ if (generatedRecipients.length === 0) {
192
+ throw new CliError(`Message session \`${target.id}\` has no generated artifacts.`, [], ["Re-run `voratiq message` to regenerate the message artifacts."]);
193
+ }
194
+ const missing = await findMissingMessageArtifacts(root, generatedRecipients);
195
+ if (missing.length > 0) {
196
+ throw new CliError(`Message session \`${target.id}\` is missing required artifacts.`, missing.map((path) => `Missing: \`${path}\`.`), ["Re-run `voratiq message` to regenerate the message artifacts."]);
197
+ }
198
+ }
165
199
  async function findMissingVerificationArtifacts(root, methods) {
166
200
  const missing = [];
167
201
  const seen = new Set();
@@ -195,6 +229,18 @@ async function findMissingReductionArtifacts(root, reducers) {
195
229
  }
196
230
  return missing;
197
231
  }
232
+ async function findMissingMessageArtifacts(root, recipients) {
233
+ const missing = [];
234
+ const seen = new Set();
235
+ for (const recipient of recipients) {
236
+ const outputPath = recipient.outputPath;
237
+ const outputAbsolute = resolvePath(root, outputPath);
238
+ if (!(await pathExists(outputAbsolute))) {
239
+ registerMissing(missing, seen, outputPath);
240
+ }
241
+ }
242
+ return missing;
243
+ }
198
244
  async function findMissingRunArtifacts(root, run) {
199
245
  const missing = [];
200
246
  const seen = new Set();
@@ -118,6 +118,9 @@ function assertResolvedAgentCount(options) {
118
118
  const configInstruction = enforceSingleCompetitor
119
119
  ? `Configure exactly one agent under \`${stageAgentsPath}\` in \`${ORCHESTRATION_CONFIG_DISPLAY_PATH}\`.`
120
120
  : `Configure at least one agent under \`${stageAgentsPath}\` in \`${ORCHESTRATION_CONFIG_DISPLAY_PATH}\`.`;
121
+ const overrideInstruction = enforceSingleCompetitor || stageId === "reduce"
122
+ ? []
123
+ : ["Or pass one or more `--agent` overrides."];
121
124
  throw new HintedError(missingAgentsHeadline(stageId), {
122
125
  detailLines: stageId === "reduce"
123
126
  ? []
@@ -126,7 +129,7 @@ function assertResolvedAgentCount(options) {
126
129
  ],
127
130
  hintLines: stageId === "reduce"
128
131
  ? [configInstruction, "Or pass one or more `--agent` overrides."]
129
- : [configInstruction],
132
+ : [configInstruction, ...overrideInstruction],
130
133
  });
131
134
  }
132
135
  if (!enforceSingleCompetitor || agentIds.length === 1) {
@@ -6,6 +6,7 @@ export interface VerifyCommandInput {
6
6
  specsFilePath: string;
7
7
  runsFilePath: string;
8
8
  reductionsFilePath: string;
9
+ messagesFilePath: string;
9
10
  verificationsFilePath: string;
10
11
  target: VerifyTargetSelection;
11
12
  agentIds?: readonly string[];
@@ -18,12 +18,13 @@ import { finalizeActiveVerification, registerActiveVerification, } from "./lifec
18
18
  import { resolveVerifyRubricMaxParallel } from "./max-parallel.js";
19
19
  import { resolveVerifyTarget } from "./targets.js";
20
20
  export async function executeVerifyCommand(input) {
21
- const { root, specsFilePath, runsFilePath, reductionsFilePath, verificationsFilePath, target, agentIds, agentOverrideFlag, profileName, maxParallel, extraContextFiles = [], renderer, } = input;
21
+ const { root, specsFilePath, runsFilePath, reductionsFilePath, messagesFilePath, verificationsFilePath, target, agentIds, agentOverrideFlag, profileName, maxParallel, extraContextFiles = [], renderer, } = input;
22
22
  const resolvedTarget = await resolveVerifyTarget({
23
23
  root,
24
24
  specsFilePath,
25
25
  runsFilePath,
26
26
  reductionsFilePath,
27
+ messagesFilePath,
27
28
  verificationsFilePath,
28
29
  target,
29
30
  });
@@ -1,6 +1,6 @@
1
1
  import type { VerificationConfig } from "../../configs/verification/types.js";
2
2
  export declare function resolveVerifyRubricMaxParallel(options: {
3
- targetKind: "spec" | "run" | "reduce";
3
+ targetKind: "spec" | "run" | "reduce" | "message";
4
4
  verificationConfig: VerificationConfig;
5
5
  verifierAgentCount: number;
6
6
  requestedMaxParallel?: number;
@@ -5,7 +5,9 @@ export function resolveVerifyRubricMaxParallel(options) {
5
5
  ? verificationConfig.spec.rubric.length
6
6
  : targetKind === "run"
7
7
  ? verificationConfig.run.rubric.length
8
- : verificationConfig.reduce.rubric.length;
8
+ : targetKind === "reduce"
9
+ ? verificationConfig.reduce.rubric.length
10
+ : verificationConfig.message.rubric.length;
9
11
  return resolveEffectiveMaxParallel({
10
12
  competitorCount: verifierAgentCount * rubricTemplateCount,
11
13
  requestedMaxParallel,
@@ -1,5 +1,5 @@
1
1
  import type { ResolvedVerificationTarget, VerificationCompetitiveCandidate } from "../../domain/verify/competition/target.js";
2
- export type VerifyTargetKind = "spec" | "run" | "reduce";
2
+ export type VerifyTargetKind = "spec" | "run" | "reduce" | "message";
3
3
  export interface VerifyTargetSelection {
4
4
  kind: VerifyTargetKind;
5
5
  sessionId: string;
@@ -9,6 +9,7 @@ export interface ResolveVerifyTargetInput {
9
9
  specsFilePath: string;
10
10
  runsFilePath: string;
11
11
  reductionsFilePath: string;
12
+ messagesFilePath: string;
12
13
  verificationsFilePath: string;
13
14
  target: VerifyTargetSelection;
14
15
  }