@consensus-tools/consensus-tools 0.1.0 → 0.1.3
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/README.md +18 -15
- package/assets/consensus-tools.png +0 -0
- package/bin/consensus-tools.js +34 -0
- package/openclaw.plugin.json +3 -3
- package/package.json +12 -6
- package/src/cli.ts +404 -246
- package/src/cliConfig.ts +97 -0
- package/src/config.ts +8 -5
- package/src/initWizard.ts +237 -0
- package/src/jobs/consensus.ts +116 -1
- package/src/jobs/engine.ts +75 -4
- package/src/standalone.ts +409 -0
- package/src/testing/consensusTestRunner.ts +258 -0
- package/src/types.ts +27 -2
- /package/{LICENSE → LICENSE.txt} +0 -0
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
3
|
import type { ConsensusToolsConfig, Job } from './types';
|
|
5
4
|
import { renderTable } from './util/table';
|
|
6
|
-
import { runConsensusPolicyTests } from '
|
|
5
|
+
import { runConsensusPolicyTests } from './testing/consensusTestRunner';
|
|
6
|
+
import { runInitWizard } from './initWizard';
|
|
7
|
+
import {
|
|
8
|
+
defaultConsensusCliConfig,
|
|
9
|
+
getConfigValue,
|
|
10
|
+
loadCliConfig,
|
|
11
|
+
parseValue,
|
|
12
|
+
saveCliConfig,
|
|
13
|
+
setConfigValue,
|
|
14
|
+
type ConsensusCliConfig
|
|
15
|
+
} from './cliConfig';
|
|
7
16
|
|
|
8
17
|
export interface ConsensusToolsBackendCli {
|
|
9
18
|
postJob(agentId: string, input: any): Promise<Job>;
|
|
@@ -17,15 +26,67 @@ export interface ConsensusToolsBackendCli {
|
|
|
17
26
|
resolveJob(agentId: string, jobId: string, input: any): Promise<any>;
|
|
18
27
|
}
|
|
19
28
|
|
|
29
|
+
export async function initRepo(opts: {
|
|
30
|
+
rootDir?: string;
|
|
31
|
+
force?: boolean;
|
|
32
|
+
wizard?: boolean;
|
|
33
|
+
templatesOnly?: boolean;
|
|
34
|
+
}): Promise<void> {
|
|
35
|
+
const rootDir = opts.rootDir || process.cwd();
|
|
36
|
+
const force = Boolean(opts.force);
|
|
37
|
+
const templatesOnly = Boolean(opts.templatesOnly);
|
|
38
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
39
|
+
const wizard = typeof opts.wizard === 'boolean' ? opts.wizard : interactive;
|
|
40
|
+
|
|
41
|
+
if (templatesOnly || !wizard) {
|
|
42
|
+
await writeInitTemplates(rootDir, force, defaultConsensusCliConfig);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!interactive) {
|
|
47
|
+
throw new Error('init wizard requires a TTY. Re-run with --templates-only.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await runInitWizard(rootDir);
|
|
51
|
+
await writeInitTemplates(rootDir, force, result.config);
|
|
52
|
+
if (result.env) {
|
|
53
|
+
await writeEnvFile(rootDir, force, result.env);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
20
57
|
export function registerCli(program: any, backend: ConsensusToolsBackendCli, config: ConsensusToolsConfig, agentId: string) {
|
|
21
58
|
const consensus = program.command('consensus').description('Consensus tools');
|
|
59
|
+
registerConsensusSubcommands(consensus, backend, config, agentId);
|
|
60
|
+
}
|
|
22
61
|
|
|
62
|
+
export function registerStandaloneCli(
|
|
63
|
+
program: any,
|
|
64
|
+
backend: ConsensusToolsBackendCli,
|
|
65
|
+
config: ConsensusToolsConfig,
|
|
66
|
+
agentId: string
|
|
67
|
+
) {
|
|
68
|
+
registerConsensusSubcommands(program, backend, config, agentId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function registerConsensusSubcommands(
|
|
72
|
+
consensus: any,
|
|
73
|
+
backend: ConsensusToolsBackendCli,
|
|
74
|
+
_config: ConsensusToolsConfig,
|
|
75
|
+
agentId: string
|
|
76
|
+
) {
|
|
23
77
|
consensus
|
|
24
78
|
.command('init')
|
|
25
|
-
.description('
|
|
79
|
+
.description('Initialize consensus-tools in this repo (.consensus/)')
|
|
26
80
|
.option('--force', 'Overwrite existing files')
|
|
81
|
+
.option('--wizard', 'Run an interactive wizard (default when TTY)')
|
|
82
|
+
.option('--templates-only', 'Only generate templates; skip prompts')
|
|
27
83
|
.action(async (opts: any) => {
|
|
28
|
-
await
|
|
84
|
+
await initRepo({
|
|
85
|
+
rootDir: process.cwd(),
|
|
86
|
+
force: Boolean(opts.force),
|
|
87
|
+
wizard: typeof opts.wizard === 'boolean' ? opts.wizard : undefined,
|
|
88
|
+
templatesOnly: Boolean(opts.templatesOnly)
|
|
89
|
+
});
|
|
29
90
|
console.log('Created .consensus templates.');
|
|
30
91
|
});
|
|
31
92
|
|
|
@@ -34,7 +95,7 @@ export function registerCli(program: any, backend: ConsensusToolsBackendCli, con
|
|
|
34
95
|
.command('get <key>')
|
|
35
96
|
.description('Get a config value')
|
|
36
97
|
.action(async (key: string) => {
|
|
37
|
-
const cfg = await
|
|
98
|
+
const cfg = await loadCliConfig();
|
|
38
99
|
const value = getConfigValue(cfg, key);
|
|
39
100
|
output(value ?? null, true);
|
|
40
101
|
});
|
|
@@ -43,10 +104,10 @@ export function registerCli(program: any, backend: ConsensusToolsBackendCli, con
|
|
|
43
104
|
.command('set <key> <value>')
|
|
44
105
|
.description('Set a config value')
|
|
45
106
|
.action(async (key: string, value: string) => {
|
|
46
|
-
const cfg = await
|
|
107
|
+
const cfg = await loadCliConfig();
|
|
47
108
|
const parsed = parseValue(value);
|
|
48
109
|
setConfigValue(cfg, key, parsed);
|
|
49
|
-
await
|
|
110
|
+
await saveCliConfig(cfg);
|
|
50
111
|
output({ ok: true }, true);
|
|
51
112
|
});
|
|
52
113
|
|
|
@@ -55,7 +116,7 @@ export function registerCli(program: any, backend: ConsensusToolsBackendCli, con
|
|
|
55
116
|
.command('use <type> [url]')
|
|
56
117
|
.description('Select local or remote board')
|
|
57
118
|
.action(async (type: string, url?: string) => {
|
|
58
|
-
const cfg = await
|
|
119
|
+
const cfg = await loadCliConfig();
|
|
59
120
|
if (type !== 'local' && type !== 'remote') {
|
|
60
121
|
throw new Error('board type must be local or remote');
|
|
61
122
|
}
|
|
@@ -63,7 +124,7 @@ export function registerCli(program: any, backend: ConsensusToolsBackendCli, con
|
|
|
63
124
|
if (type === 'remote' && url) {
|
|
64
125
|
cfg.boards.remote.url = url;
|
|
65
126
|
}
|
|
66
|
-
await
|
|
127
|
+
await saveCliConfig(cfg);
|
|
67
128
|
output({ activeBoard: cfg.activeBoard, url: cfg.boards.remote.url }, true);
|
|
68
129
|
});
|
|
69
130
|
|
|
@@ -78,7 +139,7 @@ export function registerCli(program: any, backend: ConsensusToolsBackendCli, con
|
|
|
78
139
|
.option('--policy <policy>', 'Policy key')
|
|
79
140
|
.option('--reward <n>', 'Reward amount', parseFloat)
|
|
80
141
|
.option('--stake <n>', 'Stake amount', parseFloat)
|
|
81
|
-
.option('--
|
|
142
|
+
.option('--expires <seconds>', 'Expires seconds', parseInt)
|
|
82
143
|
.option('--json', 'JSON output')
|
|
83
144
|
.action(async (opts: any) => {
|
|
84
145
|
const input = opts.input ?? (await readStdinIfAny());
|
|
@@ -93,7 +154,7 @@ export function registerCli(program: any, backend: ConsensusToolsBackendCli, con
|
|
|
93
154
|
stakeAmount: opts.stake,
|
|
94
155
|
reward: opts.reward,
|
|
95
156
|
stakeRequired: opts.stake,
|
|
96
|
-
expiresSeconds: opts.
|
|
157
|
+
expiresSeconds: opts.expires
|
|
97
158
|
});
|
|
98
159
|
output(job, opts.json);
|
|
99
160
|
});
|
|
@@ -176,16 +237,23 @@ export function registerCli(program: any, backend: ConsensusToolsBackendCli, con
|
|
|
176
237
|
.description('Cast a vote')
|
|
177
238
|
.option('--submission <id>', 'Submission id to vote for')
|
|
178
239
|
.option('--choice <key>', 'Choice key to vote for')
|
|
240
|
+
.option('--yes', 'Approval vote: yes (+1)')
|
|
241
|
+
.option('--no', 'Approval vote: no (-1)')
|
|
179
242
|
.option('--weight <n>', 'Vote weight', parseFloat)
|
|
243
|
+
.option('--stake <n>', 'Stake amount locked for this vote (staked settlement)', parseFloat)
|
|
180
244
|
.option('--json', 'JSON output')
|
|
181
245
|
.action(async (jobId: string, opts: any) => {
|
|
246
|
+
const hasYesNo = Boolean(opts.yes || opts.no);
|
|
247
|
+
const score = hasYesNo ? (opts.no ? -1 : 1) : (opts.weight ?? 1);
|
|
248
|
+
|
|
182
249
|
const vote = await backend.vote(agentId, jobId, {
|
|
183
250
|
submissionId: opts.submission,
|
|
184
251
|
choiceKey: opts.choice,
|
|
185
252
|
targetType: opts.submission ? 'SUBMISSION' : opts.choice ? 'CHOICE' : undefined,
|
|
186
253
|
targetId: opts.submission ?? opts.choice,
|
|
187
|
-
weight: opts.weight ?? 1,
|
|
188
|
-
score
|
|
254
|
+
weight: opts.weight ?? (hasYesNo ? 1 : undefined),
|
|
255
|
+
score,
|
|
256
|
+
stakeAmount: opts.stake
|
|
189
257
|
});
|
|
190
258
|
output(vote, opts.json);
|
|
191
259
|
});
|
|
@@ -228,7 +296,7 @@ export function registerCli(program: any, backend: ConsensusToolsBackendCli, con
|
|
|
228
296
|
.command('run')
|
|
229
297
|
.description('Run consensus policy tests with generation script')
|
|
230
298
|
.option('--agents <n>', 'Number of agent personalities', parseInt)
|
|
231
|
-
.option('--script <path>', 'Path to generation script', '
|
|
299
|
+
.option('--script <path>', 'Path to generation script', '.consensus/generation.ts')
|
|
232
300
|
.option('--openai-key <key>', 'OpenAI API key (or set OPENAI_API_KEY)')
|
|
233
301
|
.option('--model <name>', 'Model name', 'gpt-5.2')
|
|
234
302
|
.action(async (opts: any) => {
|
|
@@ -270,89 +338,14 @@ async function readStdinIfAny(): Promise<string | undefined> {
|
|
|
270
338
|
return text || undefined;
|
|
271
339
|
}
|
|
272
340
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
};
|
|
279
|
-
defaults: { policy: string; reward: number; stake: number; leaseSeconds: number };
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const defaultConsensusConfig: ConsensusConfig = {
|
|
283
|
-
activeBoard: 'local',
|
|
284
|
-
boards: {
|
|
285
|
-
local: {
|
|
286
|
-
type: 'local',
|
|
287
|
-
root: '~/.openclaw/workplace/consensus-board',
|
|
288
|
-
jobsPath: 'jobs',
|
|
289
|
-
ledgerPath: 'ledger.json'
|
|
290
|
-
},
|
|
291
|
-
remote: {
|
|
292
|
-
type: 'remote',
|
|
293
|
-
url: 'https://api.consensus.tools',
|
|
294
|
-
boardId: 'board_replace_me',
|
|
295
|
-
auth: { type: 'apiKey', apiKeyEnv: 'CONSENSUS_API_KEY' }
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
defaults: {
|
|
299
|
-
policy: 'HIGHEST_CONFIDENCE_SINGLE',
|
|
300
|
-
reward: 8,
|
|
301
|
-
stake: 4,
|
|
302
|
-
leaseSeconds: 180
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
function configPath(): string {
|
|
307
|
-
const envPath = process.env.CONSENSUS_CONFIG;
|
|
308
|
-
if (envPath) return expandHome(envPath);
|
|
309
|
-
return path.join(os.homedir(), '.consensus', 'config.json');
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function expandHome(input: string): string {
|
|
313
|
-
if (!input.startsWith('~')) return input;
|
|
314
|
-
return path.join(os.homedir(), input.slice(1));
|
|
341
|
+
async function writeEnvFile(rootDir: string, force: boolean, env: Record<string, string>): Promise<void> {
|
|
342
|
+
const filePath = path.join(rootDir, '.consensus', '.env');
|
|
343
|
+
const lines = Object.entries(env).map(([k, v]) => `export ${k}=${v}`);
|
|
344
|
+
const content = [...lines, ''].join('\n');
|
|
345
|
+
await writeFile(filePath, content, force);
|
|
315
346
|
}
|
|
316
347
|
|
|
317
|
-
async function
|
|
318
|
-
const filePath = configPath();
|
|
319
|
-
try {
|
|
320
|
-
const raw = await fs.readFile(filePath, 'utf8');
|
|
321
|
-
return JSON.parse(raw) as ConsensusConfig;
|
|
322
|
-
} catch {
|
|
323
|
-
return JSON.parse(JSON.stringify(defaultConsensusConfig)) as ConsensusConfig;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
async function saveConfigFile(config: ConsensusConfig): Promise<void> {
|
|
328
|
-
const filePath = configPath();
|
|
329
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
330
|
-
await fs.writeFile(filePath, JSON.stringify(config, null, 2), 'utf8');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function getConfigValue(config: any, key: string): any {
|
|
334
|
-
return key.split('.').reduce((acc, part) => (acc ? acc[part] : undefined), config);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function setConfigValue(config: any, key: string, value: any): void {
|
|
338
|
-
const parts = key.split('.');
|
|
339
|
-
let cur = config as any;
|
|
340
|
-
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
341
|
-
if (!cur[parts[i]]) cur[parts[i]] = {};
|
|
342
|
-
cur = cur[parts[i]];
|
|
343
|
-
}
|
|
344
|
-
cur[parts[parts.length - 1]] = value;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function parseValue(input: string): any {
|
|
348
|
-
try {
|
|
349
|
-
return JSON.parse(input);
|
|
350
|
-
} catch {
|
|
351
|
-
return input;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
async function writeInitTemplates(rootDir: string, force: boolean): Promise<void> {
|
|
348
|
+
async function writeInitTemplates(rootDir: string, force: boolean, config: ConsensusCliConfig): Promise<void> {
|
|
356
349
|
const baseDir = path.join(rootDir, '.consensus');
|
|
357
350
|
const apiDir = path.join(baseDir, 'api');
|
|
358
351
|
|
|
@@ -360,8 +353,10 @@ async function writeInitTemplates(rootDir: string, force: boolean): Promise<void
|
|
|
360
353
|
|
|
361
354
|
const files: Array<{ path: string; content: string; executable?: boolean }> = [
|
|
362
355
|
{ path: path.join(baseDir, 'README.md'), content: consensusReadme() },
|
|
363
|
-
{ path: path.join(baseDir, 'env.example'), content: envExample() },
|
|
364
|
-
{ path: path.join(baseDir, '
|
|
356
|
+
{ path: path.join(baseDir, 'env.example'), content: envExample(config) },
|
|
357
|
+
{ path: path.join(baseDir, '.gitignore'), content: ['.env', ''].join('\n') },
|
|
358
|
+
{ path: path.join(baseDir, 'config.json'), content: JSON.stringify(config, null, 2) },
|
|
359
|
+
{ path: path.join(baseDir, 'generation.ts'), content: generationScriptTemplate() },
|
|
365
360
|
{ path: path.join(apiDir, 'common.sh'), content: commonSh(), executable: true },
|
|
366
361
|
{ path: path.join(apiDir, 'jobs_post.sh'), content: jobsPostSh(), executable: true },
|
|
367
362
|
{ path: path.join(apiDir, 'jobs_get.sh'), content: jobsGetSh(), executable: true },
|
|
@@ -397,7 +392,7 @@ function consensusReadme(): string {
|
|
|
397
392
|
return [
|
|
398
393
|
'# consensus.tools shell templates',
|
|
399
394
|
'',
|
|
400
|
-
'This folder is generated by `consensus init
|
|
395
|
+
'This folder is generated by `consensus-tools init` (or `openclaw consensus init`).',
|
|
401
396
|
'',
|
|
402
397
|
'## Quick start',
|
|
403
398
|
'',
|
|
@@ -426,6 +421,13 @@ function consensusReadme(): string {
|
|
|
426
421
|
'bash .consensus/api/jobs_post.sh "Title" "Desc" "Input"',
|
|
427
422
|
'```',
|
|
428
423
|
'',
|
|
424
|
+
'Try the CLI:',
|
|
425
|
+
'',
|
|
426
|
+
'```bash',
|
|
427
|
+
'consensus-tools jobs list',
|
|
428
|
+
'consensus-tools jobs post --title "Hello" --desc "World" --input "Test"',
|
|
429
|
+
'```',
|
|
430
|
+
'',
|
|
429
431
|
'Notes',
|
|
430
432
|
'',
|
|
431
433
|
'Local mode writes to CONSENSUS_ROOT (defaults in env.example).',
|
|
@@ -437,24 +439,129 @@ function consensusReadme(): string {
|
|
|
437
439
|
].join('\n');
|
|
438
440
|
}
|
|
439
441
|
|
|
440
|
-
function envExample(): string {
|
|
442
|
+
function envExample(config: ConsensusCliConfig): string {
|
|
443
|
+
const apiKeyEnv = config.boards.remote.auth.apiKeyEnv || 'CONSENSUS_API_KEY';
|
|
441
444
|
return [
|
|
442
445
|
'# Mode: "local" or "remote"',
|
|
443
|
-
|
|
446
|
+
`export CONSENSUS_MODE=${config.activeBoard === 'remote' ? 'remote' : 'local'}`,
|
|
447
|
+
'',
|
|
448
|
+
'# Agent id (used by the CLI; optional)',
|
|
449
|
+
'export CONSENSUS_AGENT_ID="cli@your-machine"',
|
|
444
450
|
'',
|
|
445
451
|
'# Local board root (JSON filesystem board)',
|
|
446
|
-
|
|
452
|
+
`export CONSENSUS_ROOT="${config.boards.local.root}"`,
|
|
447
453
|
'',
|
|
448
454
|
'# Remote board settings',
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
455
|
+
`export CONSENSUS_URL="${config.boards.remote.url}"`,
|
|
456
|
+
`export CONSENSUS_BOARD_ID="${config.boards.remote.boardId}"`,
|
|
457
|
+
`export CONSENSUS_API_KEY_ENV="${apiKeyEnv}"`,
|
|
458
|
+
`export ${apiKeyEnv}="replace_me"`,
|
|
452
459
|
'',
|
|
453
460
|
'# Defaults (used by jobs_post.sh if not provided)',
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
461
|
+
`export CONSENSUS_DEFAULT_POLICY="${config.defaults.policy}"`,
|
|
462
|
+
`export CONSENSUS_DEFAULT_REWARD="${config.defaults.reward}"`,
|
|
463
|
+
`export CONSENSUS_DEFAULT_STAKE="${config.defaults.stake}"`,
|
|
464
|
+
`export CONSENSUS_DEFAULT_LEASE_SECONDS="${config.defaults.leaseSeconds}"`,
|
|
465
|
+
''
|
|
466
|
+
].join('\n');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function generationScriptTemplate(): string {
|
|
470
|
+
return [
|
|
471
|
+
'// Generated by consensus-tools init.',
|
|
472
|
+
'// Customize this file, then run:',
|
|
473
|
+
'// consensus-tools tests run --agents 6 --script .consensus/generation.ts',
|
|
474
|
+
'',
|
|
475
|
+
'/**',
|
|
476
|
+
' * This script is intentionally deterministic by default (mockResponse).',
|
|
477
|
+
' * If you provide OPENAI_API_KEY (or --openai-key), the runner will call OpenAI.',
|
|
478
|
+
' */',
|
|
479
|
+
'const script = {',
|
|
480
|
+
" name: 'rx_negation_demo',",
|
|
481
|
+
' task: {',
|
|
482
|
+
" title: 'Negation test: prescription advancement',",
|
|
483
|
+
" desc: 'Tell us why this insulin amount is NOT enough based on patient file, insurance reqs, and doctor notes.',",
|
|
484
|
+
' input: [',
|
|
485
|
+
" 'We want to negation test prescription advancement for diabetic patients.',",
|
|
486
|
+
" 'Explain why the insulin amount is not enough based on:',",
|
|
487
|
+
" '- patient file',",
|
|
488
|
+
" '- insurance info/requirements',",
|
|
489
|
+
" '- doctor notes',",
|
|
490
|
+
" '',",
|
|
491
|
+
" 'Patient file: (paste here)',",
|
|
492
|
+
" '',",
|
|
493
|
+
" 'Insurance info/requirements: (paste here)',",
|
|
494
|
+
" '',",
|
|
495
|
+
" 'Doctor notes: (paste here)'",
|
|
496
|
+
' ].join(\"\\n\")',
|
|
497
|
+
' },',
|
|
498
|
+
" expectedAnswer: 'INSUFFICIENT',",
|
|
499
|
+
' personas: [],',
|
|
500
|
+
' getPersonas(count) {',
|
|
501
|
+
' const n = Math.max(3, Number(count || 3));',
|
|
502
|
+
' const third = Math.ceil(n / 3);',
|
|
503
|
+
'',
|
|
504
|
+
' const mk = (role, i, systemPrompt, personaRole) => ({',
|
|
505
|
+
" id: `${role}_${i + 1}`,",
|
|
506
|
+
" name: `${role.toUpperCase()} Agent ${i + 1}`,",
|
|
507
|
+
' systemPrompt,',
|
|
508
|
+
' role: personaRole',
|
|
509
|
+
' });',
|
|
510
|
+
'',
|
|
511
|
+
' const doctors = Array.from({ length: third }, (_, i) =>',
|
|
512
|
+
' mk(',
|
|
513
|
+
" 'doctor',",
|
|
514
|
+
' i,',
|
|
515
|
+
" 'You are a practicing clinician. Be precise. Use only the provided case context. Focus on medical necessity.',",
|
|
516
|
+
" 'accurate'",
|
|
517
|
+
' )',
|
|
518
|
+
' );',
|
|
519
|
+
' const support = Array.from({ length: third }, (_, i) =>',
|
|
520
|
+
' mk(',
|
|
521
|
+
" 'support',",
|
|
522
|
+
' i,',
|
|
523
|
+
" 'You are customer support at a pharmacy benefits manager. Focus on process, eligibility, required docs, and next steps.',",
|
|
524
|
+
" 'accurate'",
|
|
525
|
+
' )',
|
|
526
|
+
' );',
|
|
527
|
+
' const insurance = Array.from({ length: n - 2 * third }, (_, i) =>',
|
|
528
|
+
' mk(',
|
|
529
|
+
" 'insurance',",
|
|
530
|
+
' i,',
|
|
531
|
+
" 'You are an insurance reviewer. Apply coverage criteria and utilization management rules. Be skeptical and cite requirements.',",
|
|
532
|
+
// Make the last insurance persona contrarian to ensure policies see disagreement.
|
|
533
|
+
" i === (n - 2 * third) - 1 ? 'contrarian' : 'accurate'",
|
|
534
|
+
' )',
|
|
535
|
+
' );',
|
|
536
|
+
'',
|
|
537
|
+
' return [...doctors, ...support, ...insurance].slice(0, n);',
|
|
538
|
+
' },',
|
|
539
|
+
' buildPrompt(persona, task, expectedAnswer) {',
|
|
540
|
+
' return {',
|
|
541
|
+
' system: persona.systemPrompt,',
|
|
542
|
+
' user: [',
|
|
543
|
+
" 'Return JSON: {\"answer\": string, \"confidence\": number, \"evidence\": string[]}',",
|
|
544
|
+
" '',",
|
|
545
|
+
" `TASK: ${task.title}`,",
|
|
546
|
+
" task.desc ? `DESC: ${task.desc}` : '',",
|
|
547
|
+
" '',",
|
|
548
|
+
" `INPUT:\\n${task.input}`,",
|
|
549
|
+
" '',",
|
|
550
|
+
" `EXPECTED (for negation testing): ${expectedAnswer}`",
|
|
551
|
+
' ].filter(Boolean).join(\"\\n\")',
|
|
552
|
+
' };',
|
|
553
|
+
' },',
|
|
554
|
+
' mockResponse(persona, task, expectedAnswer) {',
|
|
555
|
+
' const answer = persona.role === \"contrarian\" ? \"SUFFICIENT\" : expectedAnswer;',
|
|
556
|
+
' return JSON.stringify({',
|
|
557
|
+
' answer,',
|
|
558
|
+
' confidence: persona.role === \"contrarian\" ? 0.2 : 0.9,',
|
|
559
|
+
' evidence: [persona.name, task.title]',
|
|
560
|
+
' });',
|
|
561
|
+
' }',
|
|
562
|
+
'};',
|
|
563
|
+
'',
|
|
564
|
+
'export default script;',
|
|
458
565
|
''
|
|
459
566
|
].join('\n');
|
|
460
567
|
}
|
|
@@ -482,6 +589,16 @@ function commonSh(): string {
|
|
|
482
589
|
' echo "$CONSENSUS_ROOT"',
|
|
483
590
|
'}',
|
|
484
591
|
'',
|
|
592
|
+
'local_state_file() {',
|
|
593
|
+
' # Engine-parity local state file (JsonStorage)',
|
|
594
|
+
' if [[ -n "${CONSENSUS_STATE_FILE:-}" ]]; then',
|
|
595
|
+
' echo "${CONSENSUS_STATE_FILE}"',
|
|
596
|
+
' else',
|
|
597
|
+
' echo "$(local_root)/state.json"',
|
|
598
|
+
' fi',
|
|
599
|
+
'}',
|
|
600
|
+
'',
|
|
601
|
+
'',
|
|
485
602
|
'ensure_local_board() {',
|
|
486
603
|
' local root; root="$(local_root)"',
|
|
487
604
|
' mkdir -p "$root/jobs"',
|
|
@@ -509,9 +626,14 @@ function commonSh(): string {
|
|
|
509
626
|
' echo "${CONSENSUS_URL%/}/v1/boards/${CONSENSUS_BOARD_ID}"',
|
|
510
627
|
'}',
|
|
511
628
|
'',
|
|
629
|
+
'api_key_env() {',
|
|
630
|
+
' echo "${CONSENSUS_API_KEY_ENV:-CONSENSUS_API_KEY}"',
|
|
631
|
+
'}',
|
|
632
|
+
'',
|
|
512
633
|
'remote_auth_header() {',
|
|
513
|
-
'
|
|
514
|
-
'
|
|
634
|
+
' local name; name="$(api_key_env)"',
|
|
635
|
+
' require_env "$name"',
|
|
636
|
+
' echo "Authorization: Bearer ${!name}"',
|
|
515
637
|
'}',
|
|
516
638
|
'',
|
|
517
639
|
'curl_json() {',
|
|
@@ -578,39 +700,35 @@ function jobsPostSh(): string {
|
|
|
578
700
|
'',
|
|
579
701
|
'POLICY="${CONSENSUS_DEFAULT_POLICY:-HIGHEST_CONFIDENCE_SINGLE}"',
|
|
580
702
|
'REWARD="${CONSENSUS_DEFAULT_REWARD:-8}"',
|
|
581
|
-
'
|
|
582
|
-
'
|
|
703
|
+
'STAKE_REQUIRED="${CONSENSUS_DEFAULT_STAKE:-4}"',
|
|
704
|
+
'EXPIRES_SECONDS="${CONSENSUS_DEFAULT_EXPIRES_SECONDS:-86400}"',
|
|
583
705
|
'MODE="$(mode)"',
|
|
584
706
|
'',
|
|
585
707
|
'if [[ "$MODE" == "local" ]]; then',
|
|
586
708
|
' ensure_local_board',
|
|
587
|
-
'
|
|
588
|
-
'',
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
'',
|
|
594
|
-
'
|
|
595
|
-
'
|
|
596
|
-
'
|
|
597
|
-
|
|
598
|
-
'
|
|
599
|
-
'
|
|
600
|
-
'
|
|
601
|
-
'
|
|
602
|
-
'
|
|
603
|
-
'
|
|
604
|
-
'
|
|
605
|
-
'
|
|
606
|
-
'
|
|
607
|
-
'
|
|
608
|
-
'
|
|
609
|
-
'
|
|
610
|
-
')"',
|
|
611
|
-
' write_json_file "$(job_file "$id")" "$job_json"',
|
|
612
|
-
' ensure_job_dir "$id"',
|
|
613
|
-
' echo "$job_json"',
|
|
709
|
+
' state_file="$(local_state_file)"',
|
|
710
|
+
' node --import tsx --input-type=module - "$state_file" "$TITLE" "$DESC" "$INPUT" "$POLICY" "$REWARD" "$STAKE_REQUIRED" "$EXPIRES_SECONDS" <<\'NODE\'',
|
|
711
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
712
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
713
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
714
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
715
|
+
'const [stateFile,title,desc,input,policy,reward,stake,expires]=process.argv.slice(2);',
|
|
716
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
717
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
718
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
719
|
+
"const job = await engine.postJob('cli@local', {",
|
|
720
|
+
' title,',
|
|
721
|
+
' desc,',
|
|
722
|
+
' inputRef: input,',
|
|
723
|
+
' policyKey: policy,',
|
|
724
|
+
' rewardAmount: Number(reward),',
|
|
725
|
+
' reward: Number(reward),',
|
|
726
|
+
' stakeRequired: Number(stake),',
|
|
727
|
+
' stakeAmount: Number(stake),',
|
|
728
|
+
' expiresSeconds: Number(expires)',
|
|
729
|
+
'});',
|
|
730
|
+
'console.log(JSON.stringify(job, null, 2));',
|
|
731
|
+
'NODE',
|
|
614
732
|
' exit 0',
|
|
615
733
|
'fi',
|
|
616
734
|
'',
|
|
@@ -624,14 +742,13 @@ function jobsPostSh(): string {
|
|
|
624
742
|
' "mode": "SUBMISSION",',
|
|
625
743
|
' "policyKey": "$POLICY",',
|
|
626
744
|
' "rewardAmount": $REWARD,',
|
|
627
|
-
' "stakeAmount": $
|
|
628
|
-
' "leaseSeconds": $
|
|
745
|
+
' "stakeAmount": $STAKE_REQUIRED,',
|
|
746
|
+
' "leaseSeconds": ${CONSENSUS_DEFAULT_LEASE_SECONDS:-180}',
|
|
629
747
|
'}',
|
|
630
748
|
'JSON',
|
|
631
749
|
')"',
|
|
632
750
|
'curl_json "POST" "$base/jobs" "$payload"',
|
|
633
751
|
'echo',
|
|
634
|
-
''
|
|
635
752
|
].join('\n');
|
|
636
753
|
}
|
|
637
754
|
|
|
@@ -651,14 +768,26 @@ function jobsGetSh(): string {
|
|
|
651
768
|
'',
|
|
652
769
|
'if [[ "$MODE" == "local" ]]; then',
|
|
653
770
|
' ensure_local_board',
|
|
654
|
-
'
|
|
771
|
+
' state_file="$(local_state_file)"',
|
|
772
|
+
' node --import tsx --input-type=module - "$state_file" "$JOB_ID" <<\'NODE\'',
|
|
773
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
774
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
775
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
776
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
777
|
+
'const [stateFile, jobId] = process.argv.slice(2);',
|
|
778
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
779
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
780
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
781
|
+
'const job = await engine.getJob(jobId);',
|
|
782
|
+
"if (!job) { console.error('Job not found'); process.exit(1); }",
|
|
783
|
+
'console.log(JSON.stringify(job, null, 2));',
|
|
784
|
+
'NODE',
|
|
655
785
|
' exit 0',
|
|
656
786
|
'fi',
|
|
657
787
|
'',
|
|
658
788
|
'base="$(remote_base)"',
|
|
659
789
|
'curl -sS "$base/jobs/$JOB_ID" -H "$(remote_auth_header)"',
|
|
660
790
|
'echo',
|
|
661
|
-
''
|
|
662
791
|
].join('\n');
|
|
663
792
|
}
|
|
664
793
|
|
|
@@ -672,13 +801,23 @@ function jobsListSh(): string {
|
|
|
672
801
|
'',
|
|
673
802
|
'if [[ "$MODE" == "local" ]]; then',
|
|
674
803
|
' ensure_local_board',
|
|
675
|
-
'
|
|
676
|
-
'
|
|
804
|
+
' state_file="$(local_state_file)"',
|
|
805
|
+
' node --import tsx --input-type=module - "$state_file" <<\'NODE\'',
|
|
806
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
807
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
808
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
809
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
810
|
+
'const [stateFile]=process.argv.slice(2);',
|
|
811
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
812
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
813
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
814
|
+
'const jobs = await engine.listJobs({});',
|
|
815
|
+
'for (const j of jobs) console.log(j.id);',
|
|
816
|
+
'NODE',
|
|
677
817
|
' exit 0',
|
|
678
818
|
'fi',
|
|
679
819
|
'',
|
|
680
820
|
'base="$(remote_base)"',
|
|
681
|
-
'# Optional: pass query string as $1, e.g. "status=OPEN&mode=SUBMISSION"',
|
|
682
821
|
'QS="${1:-}"',
|
|
683
822
|
'url="$base/jobs"',
|
|
684
823
|
'if [[ -n "$QS" ]]; then',
|
|
@@ -686,7 +825,6 @@ function jobsListSh(): string {
|
|
|
686
825
|
'fi',
|
|
687
826
|
'curl -sS "$url" -H "$(remote_auth_header)"',
|
|
688
827
|
'echo',
|
|
689
|
-
''
|
|
690
828
|
].join('\n');
|
|
691
829
|
}
|
|
692
830
|
|
|
@@ -699,10 +837,10 @@ function submissionsCreateSh(): string {
|
|
|
699
837
|
'JOB_ID="${1:-}"',
|
|
700
838
|
'ARTIFACT_JSON="${2:-}"',
|
|
701
839
|
'SUMMARY="${3:-}"',
|
|
840
|
+
'CONFIDENCE="${4:-0.5}"',
|
|
702
841
|
'',
|
|
703
842
|
'if [[ -z "$JOB_ID" || -z "$ARTIFACT_JSON" ]]; then',
|
|
704
|
-
' echo "Usage: submissions_create.sh <jobId> <artifact_json> [summary]" >&2',
|
|
705
|
-
' echo "Example: submissions_create.sh job_... {\\\"toxic\\\":false,\\\"confidence\\\":0.98,\\\"brief_reason\\\":\\\"...\\\"}" >&2',
|
|
843
|
+
' echo "Usage: submissions_create.sh <jobId> <artifact_json> [summary] [confidence]" >&2',
|
|
706
844
|
' exit 2',
|
|
707
845
|
'fi',
|
|
708
846
|
'',
|
|
@@ -710,23 +848,20 @@ function submissionsCreateSh(): string {
|
|
|
710
848
|
'',
|
|
711
849
|
'if [[ "$MODE" == "local" ]]; then',
|
|
712
850
|
' ensure_local_board',
|
|
713
|
-
'
|
|
714
|
-
'',
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
'
|
|
720
|
-
'
|
|
721
|
-
'
|
|
722
|
-
'
|
|
723
|
-
'
|
|
724
|
-
'
|
|
725
|
-
'
|
|
726
|
-
'
|
|
727
|
-
')"',
|
|
728
|
-
' write_json_file "$(job_dir "$JOB_ID")/submissions/${sid}.json" "$sub_json"',
|
|
729
|
-
' echo "$sub_json"',
|
|
851
|
+
' state_file="$(local_state_file)"',
|
|
852
|
+
' node --import tsx --input-type=module - "$state_file" "$JOB_ID" "$ARTIFACT_JSON" "${SUMMARY:-}" "$CONFIDENCE" <<\'NODE\'',
|
|
853
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
854
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
855
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
856
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
857
|
+
'const [stateFile, jobId, artifactJson, summary, conf] = process.argv.slice(2);',
|
|
858
|
+
'const artifacts = JSON.parse(artifactJson);',
|
|
859
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
860
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
861
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
862
|
+
"const sub = await engine.submitJob('cli@local', jobId, { summary: summary || '', artifacts, confidence: Number(conf) });",
|
|
863
|
+
'console.log(JSON.stringify(sub, null, 2));',
|
|
864
|
+
'NODE',
|
|
730
865
|
' exit 0',
|
|
731
866
|
'fi',
|
|
732
867
|
'',
|
|
@@ -740,7 +875,6 @@ function submissionsCreateSh(): string {
|
|
|
740
875
|
')"',
|
|
741
876
|
'curl_json "POST" "$base/jobs/$JOB_ID/submissions" "$payload"',
|
|
742
877
|
'echo',
|
|
743
|
-
''
|
|
744
878
|
].join('\n');
|
|
745
879
|
}
|
|
746
880
|
|
|
@@ -760,15 +894,25 @@ function submissionsListSh(): string {
|
|
|
760
894
|
'',
|
|
761
895
|
'if [[ "$MODE" == "local" ]]; then',
|
|
762
896
|
' ensure_local_board',
|
|
763
|
-
'
|
|
764
|
-
'
|
|
897
|
+
' state_file="$(local_state_file)"',
|
|
898
|
+
' node --import tsx --input-type=module - "$state_file" "$JOB_ID" <<\'NODE\'',
|
|
899
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
900
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
901
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
902
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
903
|
+
'const [stateFile, jobId] = process.argv.slice(2);',
|
|
904
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
905
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
906
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
907
|
+
'const list = await engine.listSubmissions(jobId);',
|
|
908
|
+
'for (const s of list) console.log(JSON.stringify(s, null, 2));',
|
|
909
|
+
'NODE',
|
|
765
910
|
' exit 0',
|
|
766
911
|
'fi',
|
|
767
912
|
'',
|
|
768
913
|
'base="$(remote_base)"',
|
|
769
914
|
'curl -sS "$base/jobs/$JOB_ID/submissions" -H "$(remote_auth_header)"',
|
|
770
915
|
'echo',
|
|
771
|
-
''
|
|
772
916
|
].join('\n');
|
|
773
917
|
}
|
|
774
918
|
|
|
@@ -779,14 +923,27 @@ function votesCastSh(): string {
|
|
|
779
923
|
'source "$(dirname "$0")/common.sh"',
|
|
780
924
|
'',
|
|
781
925
|
'JOB_ID="${1:-}"',
|
|
782
|
-
'
|
|
783
|
-
'
|
|
784
|
-
'
|
|
926
|
+
'shift || true',
|
|
927
|
+
'TARGET_TYPE=""',
|
|
928
|
+
'TARGET_ID=""',
|
|
929
|
+
'WEIGHT="1"',
|
|
930
|
+
'',
|
|
931
|
+
'if [[ "${1:-}" == "SUBMISSION" || "${1:-}" == "CHOICE" ]]; then',
|
|
932
|
+
' TARGET_TYPE="$1"; TARGET_ID="${2:-}"; WEIGHT="${3:-1}"',
|
|
933
|
+
'else',
|
|
934
|
+
' while [[ $# -gt 0 ]]; do',
|
|
935
|
+
' case "$1" in',
|
|
936
|
+
' --submission) TARGET_TYPE="SUBMISSION"; TARGET_ID="${2:-}"; shift 2 ;;',
|
|
937
|
+
' --choice) TARGET_TYPE="CHOICE"; TARGET_ID="${2:-}"; shift 2 ;;',
|
|
938
|
+
' --weight) WEIGHT="${2:-1}"; shift 2 ;;',
|
|
939
|
+
' *) echo "Unknown arg: $1" >&2; exit 2 ;;',
|
|
940
|
+
' esac',
|
|
941
|
+
' done',
|
|
942
|
+
'fi',
|
|
785
943
|
'',
|
|
786
944
|
'if [[ -z "$JOB_ID" || -z "$TARGET_TYPE" || -z "$TARGET_ID" ]]; then',
|
|
787
|
-
' echo "Usage: votes_cast.sh <jobId>
|
|
788
|
-
' echo "
|
|
789
|
-
' echo "Example: votes_cast.sh job_... CHOICE TOXIC_FALSE 1" >&2',
|
|
945
|
+
' echo "Usage: votes_cast.sh <jobId> SUBMISSION <submissionId> [weight]" >&2',
|
|
946
|
+
' echo " or: votes_cast.sh <jobId> --submission <id> [--weight <n>]" >&2',
|
|
790
947
|
' exit 2',
|
|
791
948
|
'fi',
|
|
792
949
|
'',
|
|
@@ -794,22 +951,19 @@ function votesCastSh(): string {
|
|
|
794
951
|
'',
|
|
795
952
|
'if [[ "$MODE" == "local" ]]; then',
|
|
796
953
|
' ensure_local_board',
|
|
797
|
-
'
|
|
798
|
-
'',
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
'
|
|
804
|
-
'
|
|
805
|
-
'
|
|
806
|
-
'
|
|
807
|
-
'
|
|
808
|
-
'
|
|
809
|
-
'
|
|
810
|
-
')"',
|
|
811
|
-
' write_json_file "$(job_dir "$JOB_ID")/votes/${vid}.json" "$vote_json"',
|
|
812
|
-
' echo "$vote_json"',
|
|
954
|
+
' state_file="$(local_state_file)"',
|
|
955
|
+
' node --import tsx --input-type=module - "$state_file" "$JOB_ID" "$TARGET_TYPE" "$TARGET_ID" "$WEIGHT" <<\'NODE\'',
|
|
956
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
957
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
958
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
959
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
960
|
+
'const [stateFile, jobId, targetType, targetId, weight] = process.argv.slice(2);',
|
|
961
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
962
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
963
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
964
|
+
"const vote = await engine.vote('cli@local', jobId, { targetType, targetId, submissionId: targetType==='SUBMISSION'?targetId:undefined, choiceKey: targetType==='CHOICE'?targetId:undefined, weight: Number(weight), score: Number(weight) });",
|
|
965
|
+
'console.log(JSON.stringify(vote, null, 2));',
|
|
966
|
+
'NODE',
|
|
813
967
|
' exit 0',
|
|
814
968
|
'fi',
|
|
815
969
|
'',
|
|
@@ -824,7 +978,6 @@ function votesCastSh(): string {
|
|
|
824
978
|
')"',
|
|
825
979
|
'curl_json "POST" "$base/jobs/$JOB_ID/votes" "$payload"',
|
|
826
980
|
'echo',
|
|
827
|
-
''
|
|
828
981
|
].join('\n');
|
|
829
982
|
}
|
|
830
983
|
|
|
@@ -844,15 +997,25 @@ function votesListSh(): string {
|
|
|
844
997
|
'',
|
|
845
998
|
'if [[ "$MODE" == "local" ]]; then',
|
|
846
999
|
' ensure_local_board',
|
|
847
|
-
'
|
|
848
|
-
'
|
|
1000
|
+
' state_file="$(local_state_file)"',
|
|
1001
|
+
' node --import tsx --input-type=module - "$state_file" "$JOB_ID" <<\'NODE\'',
|
|
1002
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
1003
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
1004
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
1005
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
1006
|
+
'const [stateFile, jobId] = process.argv.slice(2);',
|
|
1007
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
1008
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
1009
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
1010
|
+
'const list = await engine.listVotes(jobId);',
|
|
1011
|
+
'for (const v of list) console.log(JSON.stringify(v, null, 2));',
|
|
1012
|
+
'NODE',
|
|
849
1013
|
' exit 0',
|
|
850
1014
|
'fi',
|
|
851
1015
|
'',
|
|
852
1016
|
'base="$(remote_base)"',
|
|
853
1017
|
'curl -sS "$base/jobs/$JOB_ID/votes" -H "$(remote_auth_header)"',
|
|
854
1018
|
'echo',
|
|
855
|
-
''
|
|
856
1019
|
].join('\n');
|
|
857
1020
|
}
|
|
858
1021
|
|
|
@@ -863,8 +1026,20 @@ function resolveSh(): string {
|
|
|
863
1026
|
'source "$(dirname "$0")/common.sh"',
|
|
864
1027
|
'',
|
|
865
1028
|
'JOB_ID="${1:-}"',
|
|
1029
|
+
'shift || true',
|
|
1030
|
+
'MANUAL_WINNER=""',
|
|
1031
|
+
'MANUAL_SUB=""',
|
|
1032
|
+
'',
|
|
1033
|
+
'while [[ $# -gt 0 ]]; do',
|
|
1034
|
+
' case "$1" in',
|
|
1035
|
+
' --winner) MANUAL_WINNER="${2:-}"; shift 2 ;;',
|
|
1036
|
+
' --submission) MANUAL_SUB="${2:-}"; shift 2 ;;',
|
|
1037
|
+
' *) echo "Unknown arg: $1" >&2; exit 2 ;;',
|
|
1038
|
+
' esac',
|
|
1039
|
+
'done',
|
|
1040
|
+
'',
|
|
866
1041
|
'if [[ -z "$JOB_ID" ]]; then',
|
|
867
|
-
' echo "Usage: resolve.sh <jobId>" >&2',
|
|
1042
|
+
' echo "Usage: resolve.sh <jobId> [--winner <agentId>] [--submission <submissionId>]" >&2',
|
|
868
1043
|
' exit 2',
|
|
869
1044
|
'fi',
|
|
870
1045
|
'',
|
|
@@ -872,56 +1047,29 @@ function resolveSh(): string {
|
|
|
872
1047
|
'',
|
|
873
1048
|
'if [[ "$MODE" == "local" ]]; then',
|
|
874
1049
|
' ensure_local_board',
|
|
875
|
-
'
|
|
876
|
-
'',
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
'',
|
|
881
|
-
'
|
|
882
|
-
'
|
|
883
|
-
'
|
|
884
|
-
'
|
|
885
|
-
'
|
|
886
|
-
'',
|
|
887
|
-
'
|
|
888
|
-
|
|
889
|
-
'
|
|
890
|
-
'
|
|
891
|
-
'
|
|
892
|
-
' with open(p,"r") as f:',
|
|
893
|
-
' s=json.load(f)',
|
|
894
|
-
' conf=None',
|
|
895
|
-
' try:',
|
|
896
|
-
' conf=float(s.get("artifact",{}).get("confidence"))',
|
|
897
|
-
' except Exception:',
|
|
898
|
-
' conf=None',
|
|
899
|
-
' subs.append((conf,s.get("createdAt",""),s,p))',
|
|
900
|
-
'# sort: confidence desc (None last), then createdAt desc',
|
|
901
|
-
'def key(t):',
|
|
902
|
-
' conf,created,_,_ = t',
|
|
903
|
-
' return (conf is not None, conf if conf is not None else -1.0, created)',
|
|
904
|
-
'subs_sorted=sorted(subs, key=key, reverse=True)',
|
|
905
|
-
'conf,created,s,p=subs_sorted[0]',
|
|
906
|
-
'result={',
|
|
907
|
-
' "jobId": job_id,',
|
|
908
|
-
' "mode": "SUBMISSION",',
|
|
909
|
-
' "selectedSubmissionId": s.get("id"),',
|
|
910
|
-
' "selectedSubmissionPath": p,',
|
|
911
|
-
' "resolvedAt": __import__("datetime").datetime.utcnow().isoformat()+"Z",',
|
|
912
|
-
' "artifact": s.get("artifact"),',
|
|
913
|
-
' "summary": s.get("summary","")',
|
|
914
|
-
'}',
|
|
915
|
-
'print(json.dumps(result, indent=2))',
|
|
916
|
-
'PY',
|
|
917
|
-
'',
|
|
1050
|
+
' state_file="$(local_state_file)"',
|
|
1051
|
+
' node --import tsx --input-type=module - "$state_file" "$JOB_ID" "$MANUAL_WINNER" "$MANUAL_SUB" <<\'NODE\'',
|
|
1052
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
1053
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
1054
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
1055
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
1056
|
+
'const [stateFile, jobId, winner, subId] = process.argv.slice(2);',
|
|
1057
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
1058
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
1059
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
1060
|
+
'const input:any = {};',
|
|
1061
|
+
'if (winner) input.manualWinners = [winner];',
|
|
1062
|
+
'if (subId) input.manualSubmissionId = subId;',
|
|
1063
|
+
"const actor = winner || 'cli@local';",
|
|
1064
|
+
'const res = await engine.resolveJob(actor, jobId, input);',
|
|
1065
|
+
'console.log(JSON.stringify(res, null, 2));',
|
|
1066
|
+
'NODE',
|
|
918
1067
|
' exit 0',
|
|
919
1068
|
'fi',
|
|
920
1069
|
'',
|
|
921
1070
|
'base="$(remote_base)"',
|
|
922
1071
|
'curl -sS -X POST "$base/jobs/$JOB_ID/resolve" -H "$(remote_auth_header)"',
|
|
923
1072
|
'echo',
|
|
924
|
-
''
|
|
925
1073
|
].join('\n');
|
|
926
1074
|
}
|
|
927
1075
|
|
|
@@ -941,14 +1089,24 @@ function resultGetSh(): string {
|
|
|
941
1089
|
'',
|
|
942
1090
|
'if [[ "$MODE" == "local" ]]; then',
|
|
943
1091
|
' ensure_local_board',
|
|
944
|
-
'
|
|
945
|
-
'
|
|
1092
|
+
' state_file="$(local_state_file)"',
|
|
1093
|
+
' node --import tsx --input-type=module - "$state_file" "$JOB_ID" <<\'NODE\'',
|
|
1094
|
+
"import { JsonStorage } from '@consensus-tools/consensus-tools/src/storage/JsonStorage.ts';",
|
|
1095
|
+
"import { LedgerEngine } from '@consensus-tools/consensus-tools/src/ledger/ledger.ts';",
|
|
1096
|
+
"import { JobEngine } from '@consensus-tools/consensus-tools/src/jobs/engine.ts';",
|
|
1097
|
+
"import { defaultConfig } from '@consensus-tools/consensus-tools/src/config.ts';",
|
|
1098
|
+
'const [stateFile, jobId] = process.argv.slice(2);',
|
|
1099
|
+
'const storage=new JsonStorage(stateFile); await storage.init();',
|
|
1100
|
+
'const ledger=new LedgerEngine(storage, defaultConfig);',
|
|
1101
|
+
'const engine=new JobEngine(storage, ledger, defaultConfig);',
|
|
1102
|
+
'const status = await engine.getStatus(jobId);',
|
|
1103
|
+
'console.log(JSON.stringify(status.resolution ?? null, null, 2));',
|
|
1104
|
+
'NODE',
|
|
946
1105
|
' exit 0',
|
|
947
1106
|
'fi',
|
|
948
1107
|
'',
|
|
949
1108
|
'base="$(remote_base)"',
|
|
950
1109
|
'curl -sS "$base/jobs/$JOB_ID/result" -H "$(remote_auth_header)"',
|
|
951
1110
|
'echo',
|
|
952
|
-
''
|
|
953
1111
|
].join('\n');
|
|
954
1112
|
}
|