@ci-kit/roundtable 0.0.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,194 @@
1
+ import { access, mkdir, writeFile } from 'node:fs/promises';
2
+ import { resolve, join } from 'node:path';
3
+
4
+ const TABLE = `---
5
+ title: \u6BCF\u65E5\u4F8B\u4F1A
6
+ schedule: daily
7
+ budget:
8
+ total: 12
9
+ perSeat: 4
10
+
11
+ phases:
12
+ - id: propose
13
+ seats: [editor]
14
+ - id: discuss
15
+ seats: [writer, guard]
16
+ - id: produce
17
+ seats: [writer]
18
+ - id: review
19
+ seats: [guard]
20
+ vetoable: true
21
+ - id: converge
22
+ seats: [host]
23
+
24
+ onVeto: re-propose
25
+ maxAttempts: 3
26
+ # Optional: how long one seat's model call may take. Seat work is slower than a
27
+ # short request, so a reviewing table usually wants more than the transport
28
+ # default. Omit to keep that default.
29
+ # timeoutMs: 180000
30
+ ---
31
+
32
+ \u8FD9\u662F\u4E00\u4E2A\u793A\u4F8B\u5706\u684C\u3002\u6539\u5B8C frontmatter \u5C31\u80FD\u7528\uFF0C\u6B63\u6587\u53EF\u4EE5\u7559\u7A7A\u3002
33
+
34
+ ## propose
35
+
36
+ \u4ECE\u7ED9\u5B9A\u6750\u6599\u91CC\u63D0\u51FA\u4E00\u4E2A\u5177\u4F53\u3001\u53EF\u6267\u884C\u3001\u503C\u5F97\u73B0\u5728\u505A\u7684\u8BAE\u9898\u3002
37
+
38
+ ## converge
39
+
40
+ \u6C47\u603B\u672C\u8F6E\u7684\u7ED3\u8BBA\u3001\u9057\u7559\u5206\u6B67\u548C\u4E0B\u4E00\u6B65\u52A8\u4F5C\u3002
41
+ `;
42
+ const SEATS = [
43
+ {
44
+ name: "editor.md",
45
+ content: `---
46
+ id: editor
47
+ role: \u9009\u9898\u7F16\u8F91
48
+
49
+ identity:
50
+ name: \u6797\u5C0F\u6EE1
51
+ avatar: \u270D\uFE0F
52
+ title: \u8DD1\u4E86\u516D\u5E74\u4E00\u7EBF\u7684\u8001\u7F16\u8F91
53
+ background: \u89C1\u8FC7\u592A\u591A\u7EB8\u4E0A\u65B9\u6848\uFF0C\u4E60\u60EF\u5148\u95EE\u95E8\u5E97\u771F\u80FD\u8FD9\u4E48\u505A\u5417
54
+
55
+ persona:
56
+ stance: \u5B81\u53EF\u9009\u5C0F\uFF0C\u53EF\u9009\u5177\u4F53
57
+ personality: [\u8F83\u771F, \u5BF9\u6A21\u7CCA\u8BF4\u8F9E\u654F\u611F]
58
+ style: \u76F4\u63A5\u3001\u77ED\u53E5
59
+ speech: \u5148\u7ED9\u7ED3\u8BBA\u518D\u7ED9\u7406\u7531\uFF0C\u4E00\u6B21\u53EA\u95EE\u4E00\u4E2A\u95EE\u9898
60
+ values: \u4E00\u4E2A\u5199\u4E0D\u51FA\u52A8\u4F5C\u7684\u9009\u9898\uFF0C\u4E0D\u662F\u9009\u9898
61
+ quirks: \u63D0\u5230\u6570\u636E\u5FC5\u8FFD\u95EE\u53E3\u5F84
62
+ avoid: \u4E0D\u8BC4\u4EF7\u6587\u98CE
63
+
64
+ seat:
65
+ phase: [propose]
66
+ canPropose: true
67
+ budget: 2
68
+ ---
69
+
70
+ \u4F60\u8D1F\u8D23\u4ECE\u6750\u6599\u91CC\u6311\u51FA\u4E00\u4E2A\u503C\u5F97\u505A\u7684\u5177\u4F53\u8BAE\u9898\u3002
71
+
72
+ ## \u804C\u8D23
73
+ - \u7ED9\u51FA\u4E00\u4E2A\u8BAE\u9898\u548C\u9009\u62E9\u5B83\u7684\u7406\u7531
74
+ - \u8BF4\u660E\u4E3A\u4EC0\u4E48\u662F\u73B0\u5728
75
+ - \u4E0D\u5199\u6B63\u6587\uFF0C\u53EA\u5B9A\u65B9\u5411
76
+ `
77
+ },
78
+ {
79
+ name: "writer.md",
80
+ content: `---
81
+ id: writer
82
+ role: \u4F5C\u8005
83
+
84
+ identity:
85
+ name: \u9648\u9ED8
86
+ avatar: \u270F\uFE0F
87
+ title: \u5199\u4E1C\u897F\u7684\u4EBA
88
+
89
+ persona:
90
+ stance: \u5148\u505A\u51FA\u6765\u518D\u8BA8\u8BBA
91
+ style: \u5177\u4F53\u3001\u4E0D\u7ED5\u5F2F
92
+
93
+ seat:
94
+ phase: [discuss, produce]
95
+ budget: 4
96
+ ---
97
+
98
+ \u4F60\u8D1F\u8D23\u628A\u65B9\u5411\u843D\u6210\u5B9E\u9645\u7ED3\u679C\u3002
99
+
100
+ ## \u804C\u8D23
101
+ - \u8BA8\u8BBA\u9636\u6BB5\uFF1A\u5BF9\u8BAE\u9898\u7ED9\u51FA\u53EF\u884C\u6027\u4E0E\u98CE\u9669\u5224\u65AD
102
+ - \u4EA7\u51FA\u9636\u6BB5\uFF1A\u6309\u5DF2\u8FBE\u6210\u7684\u65B9\u5411\u5199\u51FA\u5B8C\u6574\u7ED3\u679C
103
+ `
104
+ },
105
+ {
106
+ name: "guard.md",
107
+ content: `---
108
+ id: guard
109
+ role: \u4E8B\u5B9E\u5BA1\u6838
110
+
111
+ identity:
112
+ name: \u5468\u614E
113
+ avatar: \u{1F6E1}\uFE0F
114
+ title: \u505A\u4E86\u5341\u5E74\u4E8B\u5B9E\u6838\u67E5\u7684\u8001\u5BA1\u6838
115
+
116
+ persona:
117
+ stance: \u5B81\u53EF\u9519\u6740\uFF0C\u4E0D\u53EF\u653E\u8FC7
118
+ personality: [\u8F83\u771F, \u4E0D\u7559\u60C5\u9762]
119
+ style: \u51B7\u9759\u3001\u9010\u6761
120
+
121
+ seat:
122
+ phase: [discuss, review]
123
+ sees: none
124
+ canVeto: true
125
+ weight: 2
126
+ budget: 3
127
+ output: json
128
+ ---
129
+
130
+ \u4F60\u8D1F\u8D23\u6838\u5BF9\u4EA7\u51FA\u662F\u5426\u6709\u4F9D\u636E\u3002
131
+
132
+ ## \u8F93\u51FA
133
+ \u53EA\u8FD4\u56DE JSON\uFF1A{"verdict":"pass \u6216 reject","score":0,"issues":[],"reason":"\u5426\u51B3\u7406\u7531\uFF0C\u901A\u8FC7\u65F6\u7559\u7A7A"}
134
+ `
135
+ },
136
+ {
137
+ name: "host.md",
138
+ content: `---
139
+ id: host
140
+ role: \u4E3B\u6301
141
+
142
+ identity:
143
+ name: \u4F55\u4E00
144
+ avatar: \u{1F3AF}
145
+ title: \u8D1F\u8D23\u6536\u53E3\u7684\u4EBA
146
+
147
+ persona:
148
+ stance: \u8BA9\u5171\u8BC6\u53EF\u6267\u884C
149
+
150
+ seat:
151
+ phase: [converge]
152
+ lead: true
153
+ budget: 2
154
+ ---
155
+
156
+ \u4F60\u8D1F\u8D23\u6536\u53E3\u3002
157
+
158
+ ## \u804C\u8D23
159
+ - \u7ED9\u51FA\u7ED3\u8BBA
160
+ - \u5217\u51FA\u4ECD\u672A\u89E3\u51B3\u7684\u5206\u6B67
161
+ - \u7ED9\u51FA\u4E0B\u4E00\u6B65\u52A8\u4F5C
162
+ `
163
+ }
164
+ ];
165
+ async function writeExample(dir, force = false) {
166
+ const target = resolve(dir);
167
+ const files = [
168
+ {
169
+ path: join(target, "table.md"),
170
+ content: TABLE
171
+ },
172
+ ...SEATS.map((seat) => ({
173
+ path: join(target, "seats", seat.name),
174
+ content: seat.content
175
+ }))
176
+ ];
177
+ if (!force) {
178
+ for (const file of files) {
179
+ const exists = await access(file.path).then(() => true).catch(() => false);
180
+ if (exists) {
181
+ throw new Error(`${file.path} already exists; pass --force to overwrite`);
182
+ }
183
+ }
184
+ }
185
+ const written = [];
186
+ for (const file of files) {
187
+ await mkdir(join(file.path, ".."), { recursive: true });
188
+ await writeFile(file.path, file.content, "utf8");
189
+ written.push(file.path);
190
+ }
191
+ return written;
192
+ }
193
+
194
+ export { writeExample };
package/dist/cli.mjs CHANGED
@@ -1,26 +1,84 @@
1
1
  #!/usr/bin/env node
2
2
  import process from 'node:process';
3
3
  import { cac } from 'cac';
4
+ import { loadRoundtable, defaultOutDir, runRoundtable, resumeRoundtable, RoundtableConfigError } from './index.mjs';
5
+ import 'node:fs/promises';
6
+ import 'node:path';
7
+ import 'node:crypto';
8
+ import '@ci-kit/ai';
4
9
 
5
- const version = "0.0.1";
10
+ const version = "0.3.1";
6
11
  const pkg = {
7
12
  version: version};
8
13
 
9
14
  const cli = cac("ci-kit-roundtable");
15
+ function reportError(error) {
16
+ const message = error instanceof RoundtableConfigError ? `config: ${error.message}` : error instanceof Error ? error.message : String(error);
17
+ process.stderr.write(`ci-kit-roundtable: ${message}
18
+ `);
19
+ process.exit(2);
20
+ }
10
21
  cli.command("check", "Validate table.md and seats without calling a model").option("--config <dir>", "Roundtable directory", { default: ".ci-kit/roundtable" }).action(async (options) => {
11
- const { checkRoundtable } = await import('./index.mjs');
12
- await checkRoundtable({ configDir: options.config ?? ".ci-kit/roundtable" });
22
+ try {
23
+ const loaded = await loadRoundtable({ configDir: options.config ?? ".ci-kit/roundtable" });
24
+ const phases = loaded.phases.map((phase) => `${phase.id}(${phase.seats.join(", ")})`).join(" \u2192 ");
25
+ process.stdout.write(`roundtable: ok \xB7 ${loaded.seats.length} seats \xB7 ${loaded.phases.length} phases
26
+ `);
27
+ process.stdout.write(` ${phases}
28
+ `);
29
+ process.stdout.write(` title: ${loaded.table.title}
30
+ `);
31
+ } catch (error) {
32
+ reportError(error);
33
+ }
13
34
  });
14
- cli.command("run", "Run one meeting").option("--config <dir>", "Roundtable directory", { default: ".ci-kit/roundtable" }).option("--out <dir>", "Directory for run folders", { default: "roundtable-out" }).option("--topic <text>", "Optional topic override; seats propose their own by default").action(async (options) => {
15
- const { runRoundtable } = await import('./index.mjs');
16
- const result = await runRoundtable({
17
- configDir: options.config ?? ".ci-kit/roundtable",
18
- outDir: options.out,
19
- topic: options.topic
20
- });
21
- process.stdout.write(`roundtable: ${result.status} \xB7 ${result.recordPath}
22
- `);
23
- process.exitCode = result.status === "passed" ? 0 : 1;
35
+ cli.command("run", "Run one meeting").option("--config <dir>", "Roundtable directory", { default: ".ci-kit/roundtable" }).option("--out <dir>", "Directory for run folders", {
36
+ default: defaultOutDir()
37
+ }).option("--topic <text>", "Optional topic override; seats propose their own by default").option("--context <text>", "Material for the seats, repeatable").option("--resume <dir>", "Continue an interrupted run directory").action(async (options) => {
38
+ const rawContext = options.context;
39
+ const context = rawContext === void 0 ? [] : Array.isArray(rawContext) ? rawContext : [rawContext];
40
+ try {
41
+ const common = {
42
+ configDir: String(options.config ?? ".ci-kit/roundtable"),
43
+ topic: options.topic === void 0 ? void 0 : String(options.topic),
44
+ context,
45
+ onProgress: (line) => process.stderr.write(`${line}
46
+ `)
47
+ };
48
+ const result = options.resume === void 0 ? await runRoundtable({
49
+ ...common,
50
+ outDir: options.out === void 0 ? void 0 : String(options.out)
51
+ }) : await resumeRoundtable({
52
+ ...common,
53
+ runDir: String(options.resume)
54
+ });
55
+ const total = result.budgetTotal === void 0 ? "unlimited" : String(result.budgetTotal);
56
+ const summary = `roundtable: ${result.status} \xB7 ${result.record.attempts} attempt \xB7 ${result.used}/${total} calls \xB7 ${result.runDir}`;
57
+ if (result.status === "passed") {
58
+ process.stdout.write(`${summary}
59
+ `);
60
+ } else {
61
+ process.stderr.write(`${summary}
62
+ `);
63
+ }
64
+ process.exitCode = result.status === "passed" ? 0 : 1;
65
+ } catch (error) {
66
+ reportError(error);
67
+ }
68
+ });
69
+ cli.command("init [dir]", "Write an example roundtable you can edit").option("--force", "Overwrite existing files").action(async (dir, options) => {
70
+ try {
71
+ const { writeExample } = await import('./chunks/example.mjs');
72
+ const written = await writeExample(dir ?? ".ci-kit/roundtable", options.force === true);
73
+ process.stdout.write(`roundtable: wrote ${written.length} files
74
+ `);
75
+ for (const path of written) {
76
+ process.stdout.write(` ${path}
77
+ `);
78
+ }
79
+ } catch (error) {
80
+ reportError(error);
81
+ }
24
82
  });
25
83
  cli.help();
26
84
  cli.version(pkg.version);
package/dist/index.d.mts CHANGED
@@ -1,16 +1,240 @@
1
+ import { text } from '@ci-kit/ai';
2
+
3
+ type Sees = 'all' | 'none' | 'lead-only' | string[];
4
+ type Fallback = 'skip' | 'retry' | 'abort';
5
+ type SeatOutput = 'text' | 'json' | 'markdown';
6
+ interface SeatIdentity {
7
+ name: string;
8
+ avatar?: string;
9
+ title?: string;
10
+ background?: string;
11
+ }
12
+ interface SeatPersona {
13
+ /** Decision tendency. Kept apart from expression so verdicts stay reproducible. */
14
+ stance?: string;
15
+ personality: string[];
16
+ style?: string;
17
+ speech?: string;
18
+ values?: string;
19
+ quirks?: string;
20
+ avoid?: string;
21
+ }
22
+ interface SeatPowers {
23
+ /** Phase ids this seat attends; empty means every phase. */
24
+ phase: string[];
25
+ sees: Sees;
26
+ canPropose: boolean;
27
+ canVeto: boolean;
28
+ lead: boolean;
29
+ weight: number;
30
+ priority: number;
31
+ budget?: number;
32
+ fallback: Fallback;
33
+ output: SeatOutput;
34
+ model?: string;
35
+ temperature?: number;
36
+ maxTokens?: number;
37
+ }
38
+ interface Seat {
39
+ id: string;
40
+ role: string;
41
+ enabled: boolean;
42
+ identity: SeatIdentity;
43
+ persona: SeatPersona;
44
+ seat: SeatPowers;
45
+ /** Markdown body, used verbatim as the seat's instructions. */
46
+ body: string;
47
+ /** File the seat came from, for error messages only. */
48
+ source: string;
49
+ }
50
+
51
+ type PhaseMode = 'serial' | 'parallel';
52
+ type OnVeto = 're-propose' | 'revise' | 'abort';
53
+ interface Phase {
54
+ id: string;
55
+ /** Seats named here; empty means "derive from every seat's seat.phase". */
56
+ seats: string[];
57
+ vetoable: boolean;
58
+ requires: string[];
59
+ mode: PhaseMode;
60
+ }
61
+ interface Budget {
62
+ total?: number;
63
+ perSeat?: number;
64
+ }
65
+ interface Table {
66
+ title: string;
67
+ schedule?: string;
68
+ budget: Budget;
69
+ onVeto: OnVeto;
70
+ maxAttempts: number;
71
+ threshold: number;
72
+ /**
73
+ * How long one seat's model call may take. Undefined keeps the http
74
+ * package's default.
75
+ *
76
+ * A seat reads a whole article and replies with paragraphs, which routinely
77
+ * needs more than the transport default meant for short requests. The table
78
+ * owns this because "how long may a seat think" is a property of the review,
79
+ * not of the provider.
80
+ */
81
+ timeoutMs?: number;
82
+ phases: Phase[];
83
+ /** Phase ids given the special roles, resolved during validation. */
84
+ special: {
85
+ propose: string;
86
+ converge: string;
87
+ };
88
+ }
89
+
90
+ declare class RoundtableConfigError extends Error {
91
+ constructor(message: string);
92
+ }
93
+ interface LoadedRoundtable {
94
+ table: Table;
95
+ seats: Seat[];
96
+ /** Seats after `enabled` filtering, keyed by id. */
97
+ byId: Map<string, Seat>;
98
+ /** Phases with their participant lists resolved. */
99
+ phases: Phase[];
100
+ configDir: string;
101
+ /** Content fingerprint over table, seats and the roundtable schema. */
102
+ fingerprint: string;
103
+ }
1
104
  /**
2
- * @ci-kit/roundtable
3
- *
4
- * Placeholder package. The design is settled in `ai-docs/design-docs/roundtable/`;
5
- * the implementation has not started yet, so this module exports only the
6
- * names and types that downstream code may already reference.
105
+ * Read and validate a roundtable directory without calling any model.
7
106
  *
8
- * Do not add behaviour here before the design document is followed end to end:
9
- * seat parsing, phase scheduling, programmatic adjudication and resumable runs
10
- * are one unit, and half of them is worse than none.
107
+ * Everything a meeting needs to be well defined is checked here: unknown
108
+ * fields, missing required fields, duplicate ids, phase references and the
109
+ * table/seat membership intersection. A meeting that starts is a meeting that
110
+ * can finish.
11
111
  */
12
- declare const ROUNDTABLE_SCHEMA: 1;
13
- type RoundtableStatus = 'passed' | 'rejected' | 'aborted' | 'failed';
112
+ declare function loadRoundtable(options: {
113
+ configDir: string;
114
+ schema?: number;
115
+ }): Promise<LoadedRoundtable>;
116
+ /** Convenience for the CLI: validate and report, never call a model. */
117
+ declare function checkRoundtable(options: {
118
+ configDir: string;
119
+ env?: NodeJS.ProcessEnv;
120
+ }): Promise<LoadedRoundtable>;
121
+ /** Resolved default output directory. */
122
+ declare function defaultOutDir(cwd?: string): string;
123
+
124
+ /** One seat's turn in a phase, as seen by later seats. */
125
+ interface Statement {
126
+ seat: string;
127
+ name: string;
128
+ role: string;
129
+ phase: string;
130
+ status: StatementStatus;
131
+ content: string;
132
+ structured?: unknown;
133
+ durationMs?: number;
134
+ model?: string;
135
+ usage?: {
136
+ input: number;
137
+ output: number;
138
+ };
139
+ /** One log-safe line when the seat failed. */
140
+ error?: string;
141
+ artifacts?: ArtifactRef[];
142
+ }
143
+ type StatementStatus = 'completed' | 'skipped' | 'failed' | 'budget-exhausted' | 'invalid-veto';
144
+ interface ArtifactRef {
145
+ path: string;
146
+ bytes: number;
147
+ }
148
+
149
+ interface Vote {
150
+ seatId: string;
151
+ weight: number;
152
+ /** `undefined` when the seat failed or was skipped, so it cannot count. */
153
+ vote?: 'pass' | 'reject';
154
+ /** Set when a veto-capable seat wrote `reject` with a reason. */
155
+ veto?: boolean;
156
+ /** Whether the seat actually wrote text for its reason. */
157
+ hasReason?: boolean;
158
+ }
159
+ interface Judgement {
160
+ passed: boolean;
161
+ /** Seats whose valid veto blocked the phase. */
162
+ vetoed: string[];
163
+ /** Seats that voted reject without a valid veto. */
164
+ rejected: string[];
165
+ /** Weighted pass ratio over seats that actually voted. */
166
+ score: number;
167
+ /** Seats whose veto had no reason and was therefore ignored. */
168
+ invalidVetoes: string[];
169
+ }
170
+
171
+ type RoundtableStatus = 'running' | 'passed' | 'rejected' | 'aborted' | 'failed';
172
+ type PhaseStatus = 'completed' | 'skipped' | 'failed' | 'budget-exhausted';
173
+ interface PhaseRecord {
174
+ id: string;
175
+ status: PhaseStatus;
176
+ statements: Statement[];
177
+ judgement?: Judgement;
178
+ /** True when a completed phase had no input from its dependencies. */
179
+ pruned?: boolean;
180
+ }
181
+ interface RoundRecord {
182
+ attempt: number;
183
+ phases: PhaseRecord[];
184
+ }
185
+ interface BudgetState {
186
+ total?: number;
187
+ used: number;
188
+ perSeat: Record<string, number>;
189
+ perSeatLimit: Record<string, number | undefined>;
190
+ }
191
+ /** The AI call the engine depends on; injectable so meetings can be tested offline. */
192
+ type TextCaller = typeof text;
193
+
194
+ declare const RECORD_SCHEMA = 1;
195
+ interface RoundtableRecord {
196
+ schema: number;
197
+ title: string;
198
+ startedAt: string;
199
+ finishedAt?: string;
200
+ status: RoundtableStatus;
201
+ attempts: number;
202
+ verdict: {
203
+ passed: boolean;
204
+ vetoed: string[];
205
+ rejected: string[];
206
+ score: number;
207
+ invalidVetoes: string[];
208
+ };
209
+ rounds: RoundRecord[];
210
+ budget: {
211
+ total?: number;
212
+ used: number;
213
+ perSeat: Record<string, number>;
214
+ };
215
+ /** Files produced under artifacts/, referenced by statements. */
216
+ artifacts?: string[];
217
+ error?: string;
218
+ }
219
+ interface RunState {
220
+ schema: number;
221
+ runId: string;
222
+ /** `running` until the loop settles on a terminal status. */
223
+ status: RoundtableStatus | 'running';
224
+ attempt: number;
225
+ phaseIndex: number;
226
+ phaseId?: string;
227
+ budget: BudgetState;
228
+ fingerprint: string;
229
+ resumable: boolean;
230
+ rounds: RoundRecord[];
231
+ startedAt: string;
232
+ finishedAt?: string;
233
+ topic?: string;
234
+ context: string[];
235
+ error?: string;
236
+ }
237
+
14
238
  interface RoundtableOptions {
15
239
  /** Directory holding `table.md` and `seats/` */
16
240
  configDir: string;
@@ -21,17 +245,45 @@ interface RoundtableOptions {
21
245
  /** Where run directories are written */
22
246
  outDir?: string;
23
247
  env?: NodeJS.ProcessEnv;
248
+ /** Emit one line per statement while the meeting runs. */
249
+ onProgress?: (line: string) => void;
250
+ /** Defaults to `@ci-kit/ai`'s text(); tests pass a stub. */
251
+ callText?: TextCaller;
24
252
  }
25
253
  interface RoundtableResult {
26
254
  status: RoundtableStatus;
27
255
  recordPath: string;
256
+ /** Directory holding record.json, record.md, state.json. */
257
+ runDir: string;
258
+ record: RoundtableRecord;
259
+ /** Total AI calls consumed. */
260
+ used: number;
261
+ budgetTotal?: number;
262
+ }
263
+ /**
264
+ * Run one meeting.
265
+ *
266
+ * Business failure is reported through `status`, never thrown: a rejected
267
+ * meeting is a normal result. Only configuration and usage errors throw,
268
+ * because those mean nothing could start.
269
+ */
270
+ declare function runRoundtable(options: RoundtableOptions): Promise<RoundtableResult>;
271
+ interface ResumeOptions extends Omit<RoundtableOptions, 'outDir'> {
272
+ /** Run directory produced by a previous, interrupted execution. */
273
+ runDir: string;
28
274
  }
29
- /** Validate `table.md` and `seats/*.md` without calling a model. */
30
- declare function loadRoundtable(_options: RoundtableOptions): Promise<never>;
31
- /** Alias kept for the CLI `check` command: no paid calls, config errors only. */
32
- declare function checkRoundtable(_options: RoundtableOptions): Promise<never>;
33
- /** Run one meeting and return its verdict. Business failure is reported via `status`. */
34
- declare function runRoundtable(_options: RoundtableOptions): Promise<RoundtableResult>;
275
+ /**
276
+ * Continue an interrupted run from the phase that did not finish.
277
+ *
278
+ * Rules are fingerprinted: if a seat or the table changed, the previous
279
+ * progress described a different meeting and cannot be resumed. That mirrors
280
+ * "editing a prompt never rewrites an already-published draft".
281
+ *
282
+ * A run that already reached a terminal status is returned as-is rather than
283
+ * re-executed: repeating a finished meeting would spend money to reach the same
284
+ * answer.
285
+ */
286
+ declare function resumeRoundtable(options: ResumeOptions): Promise<RoundtableResult>;
35
287
 
36
- export { ROUNDTABLE_SCHEMA, checkRoundtable, loadRoundtable, runRoundtable };
37
- export type { RoundtableOptions, RoundtableResult, RoundtableStatus };
288
+ export { RECORD_SCHEMA as ROUNDTABLE_SCHEMA, RoundtableConfigError, checkRoundtable, defaultOutDir, loadRoundtable, resumeRoundtable, runRoundtable };
289
+ export type { ArtifactRef, BudgetState, Judgement, LoadedRoundtable, Phase, PhaseMode, PhaseRecord, ResumeOptions, RoundRecord, RoundtableOptions, RoundtableRecord, RoundtableResult, RoundtableStatus, RunState, Seat, SeatIdentity, SeatOutput, SeatPersona, SeatPowers, Statement, StatementStatus, Table, TextCaller, Vote };