@consensus-tools/consensus-tools 0.1.5 → 0.1.6

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/index.ts CHANGED
@@ -9,28 +9,35 @@ import { registerTools } from './src/tools';
9
9
  import { createService } from './src/service';
10
10
  import type { ConsensusToolsConfig, Job } from './src/types';
11
11
 
12
- export default async function register(api: any) {
12
+ export default function register(api: any) {
13
13
  const logger = api?.logger?.child ? api.logger.child({ plugin: PLUGIN_ID }) : api?.logger;
14
14
  const loaded = loadConfig(api, logger);
15
15
  const { config } = validateConfig(loaded, logger);
16
16
  const agentId = resolveAgentId(api, config);
17
17
 
18
18
  const storage = createStorage(config);
19
- await storage.init();
20
-
21
19
  const ledger = new LedgerEngine(storage, config, logger);
22
20
  const engine = new JobEngine(storage, ledger, config, logger);
23
21
  const server = new ConsensusToolsServer(config, engine, ledger, logger);
24
22
  const client = new ConsensusToolsClient(config.global.baseUrl, config.global.accessToken, logger);
25
23
 
26
- if (config.mode === 'local') {
27
- await ledger.applyConfigBalances(
28
- config.local.ledger.balances,
29
- config.local.ledger.balancesMode ?? 'initial'
30
- );
31
- }
24
+ let readyPromise: Promise<void> | null = null;
25
+ const ensureReady = async () => {
26
+ if (!readyPromise) {
27
+ readyPromise = (async () => {
28
+ await storage.init();
29
+ if (config.mode === 'local') {
30
+ await ledger.applyConfigBalances(
31
+ config.local.ledger.balances,
32
+ config.local.ledger.balancesMode ?? 'initial'
33
+ );
34
+ }
35
+ })();
36
+ }
37
+ return readyPromise;
38
+ };
32
39
 
33
- const backend = createBackend(config, engine, ledger, client, server, storage, logger, agentId);
40
+ const backend = createBackend(config, ensureReady, engine, ledger, client, server, storage, logger, agentId);
34
41
 
35
42
  api.registerCli(
36
43
  ({ program }: any) => {
@@ -49,11 +56,19 @@ export default async function register(api: any) {
49
56
  getJob: backend.getJob
50
57
  };
51
58
  const service = createService(config, serviceBackend, agentId, capabilities, logger);
52
- api.registerService({ id: 'consensus-tools', start: service.start, stop: service.stop });
59
+ api.registerService({
60
+ id: 'consensus-tools',
61
+ start: async () => {
62
+ await ensureReady();
63
+ await service.start();
64
+ },
65
+ stop: service.stop
66
+ });
53
67
  }
54
68
 
55
69
  function createBackend(
56
70
  config: ConsensusToolsConfig,
71
+ ensureReady: () => Promise<void>,
57
72
  engine: JobEngine,
58
73
  ledger: LedgerEngine,
59
74
  client: ConsensusToolsClient,
@@ -90,6 +105,7 @@ function createBackend(
90
105
 
91
106
  const backend = {
92
107
  postJob: async (actorId: string, input: any): Promise<Job> => {
108
+ await ensureReady();
93
109
  if (config.mode === 'global') {
94
110
  ensureGlobalAccess('post jobs');
95
111
  ensureNetworkSideEffects('post jobs');
@@ -103,6 +119,7 @@ function createBackend(
103
119
  }
104
120
  },
105
121
  listJobs: async (filters?: Record<string, string | undefined>): Promise<Job[]> => {
122
+ await ensureReady();
106
123
  if (config.mode === 'global') {
107
124
  ensureGlobalAccess('list jobs');
108
125
  return client.listJobs(filters || {});
@@ -110,6 +127,7 @@ function createBackend(
110
127
  return engine.listJobs(filters || {});
111
128
  },
112
129
  getJob: async (jobId: string): Promise<Job | undefined> => {
130
+ await ensureReady();
113
131
  if (config.mode === 'global') {
114
132
  ensureGlobalAccess('get jobs');
115
133
  return client.getJob(jobId);
@@ -117,6 +135,7 @@ function createBackend(
117
135
  return engine.getJob(jobId);
118
136
  },
119
137
  getStatus: async (jobId: string): Promise<any> => {
138
+ await ensureReady();
120
139
  if (config.mode === 'global') {
121
140
  ensureGlobalAccess('get job status');
122
141
  return client.getStatus(jobId);
@@ -124,6 +143,7 @@ function createBackend(
124
143
  return engine.getStatus(jobId);
125
144
  },
126
145
  claimJob: async (actorId: string, jobId: string, stakeAmount: number, leaseSeconds: number): Promise<any> => {
146
+ await ensureReady();
127
147
  if (config.mode === 'global') {
128
148
  ensureGlobalAccess('claim jobs');
129
149
  ensureNetworkSideEffects('claim jobs');
@@ -137,12 +157,14 @@ function createBackend(
137
157
  }
138
158
  },
139
159
  heartbeat: async (actorId: string, jobId: string): Promise<void> => {
160
+ await ensureReady();
140
161
  if (config.mode === 'global') {
141
162
  return;
142
163
  }
143
164
  return engine.heartbeat(actorId, jobId);
144
165
  },
145
166
  submitJob: async (actorId: string, jobId: string, input: any): Promise<any> => {
167
+ await ensureReady();
146
168
  if (config.mode === 'global') {
147
169
  ensureGlobalAccess('submit jobs');
148
170
  ensureNetworkSideEffects('submit jobs');
@@ -156,6 +178,7 @@ function createBackend(
156
178
  }
157
179
  },
158
180
  listSubmissions: async (jobId: string): Promise<any[]> => {
181
+ await ensureReady();
159
182
  if (config.mode === 'global') {
160
183
  ensureGlobalAccess('list submissions');
161
184
  const status = await client.getStatus(jobId);
@@ -165,6 +188,7 @@ function createBackend(
165
188
  return status.submissions;
166
189
  },
167
190
  listVotes: async (jobId: string): Promise<any[]> => {
191
+ await ensureReady();
168
192
  if (config.mode === 'global') {
169
193
  ensureGlobalAccess('list votes');
170
194
  const status = await client.getStatus(jobId);
@@ -174,6 +198,7 @@ function createBackend(
174
198
  return status.votes;
175
199
  },
176
200
  vote: async (actorId: string, jobId: string, input: any): Promise<any> => {
201
+ await ensureReady();
177
202
  if (config.mode === 'global') {
178
203
  ensureGlobalAccess('vote');
179
204
  ensureNetworkSideEffects('vote');
@@ -187,6 +212,7 @@ function createBackend(
187
212
  }
188
213
  },
189
214
  resolveJob: async (actorId: string, jobId: string, input: any): Promise<any> => {
215
+ await ensureReady();
190
216
  if (config.mode === 'global') {
191
217
  ensureGlobalAccess('resolve jobs');
192
218
  ensureNetworkSideEffects('resolve jobs');
@@ -200,6 +226,7 @@ function createBackend(
200
226
  }
201
227
  },
202
228
  getLedgerBalance: async (target: string): Promise<number> => {
229
+ await ensureReady();
203
230
  if (config.mode === 'global') {
204
231
  ensureGlobalAccess('read ledger');
205
232
  const result = await client.getLedger(target);
@@ -208,6 +235,7 @@ function createBackend(
208
235
  return ledger.getBalance(target);
209
236
  },
210
237
  faucet: async (target: string, amount: number): Promise<any> => {
238
+ await ensureReady();
211
239
  if (!config.local.ledger.faucetEnabled) {
212
240
  throw new Error('Faucet disabled');
213
241
  }
@@ -219,6 +247,7 @@ function createBackend(
219
247
  return ledger.faucet(target, amount, `faucet:${agentId}`);
220
248
  },
221
249
  getDiagnostics: async () => {
250
+ await ensureReady();
222
251
  const errors = (await storage.getState()).errors.map((err: any) => ({ at: err.at, message: err.message }));
223
252
  let networkOk: boolean | undefined = undefined;
224
253
  if (config.mode === 'global') {
@@ -229,13 +258,16 @@ function createBackend(
229
258
  await client.listJobs({});
230
259
  networkOk = true;
231
260
  } catch (err) {
232
- logger?.warn?.({ err }, 'consensus-tools: diagnostics network check failed');
261
+ logger?.warn?.(`consensus-tools: diagnostics network check failed: ${err instanceof Error ? err.message : String(err)}`);
233
262
  networkOk = false;
234
263
  }
235
264
  }
236
265
  return { errors, networkOk };
237
266
  },
238
- startServer: async () => server.start(),
267
+ startServer: async () => {
268
+ await ensureReady();
269
+ return server.start();
270
+ },
239
271
  stopServer: async () => server.stop()
240
272
  };
241
273
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "consensus-tools",
3
3
  "name": "consensus-tools",
4
- "version": "0.1.1",
4
+ "version": "0.1.4",
5
5
  "main": "index.ts",
6
6
  "description": "consensus-tools distributed job board for OpenClaw agents",
7
7
  "configSchema": {
@@ -10,7 +10,10 @@
10
10
  "properties": {
11
11
  "mode": {
12
12
  "type": "string",
13
- "enum": ["local", "global"],
13
+ "enum": [
14
+ "local",
15
+ "global"
16
+ ],
14
17
  "default": "local",
15
18
  "description": "Choose local storage or a hosted global job board"
16
19
  },
@@ -24,7 +27,10 @@
24
27
  "properties": {
25
28
  "kind": {
26
29
  "type": "string",
27
- "enum": ["sqlite", "json"],
30
+ "enum": [
31
+ "sqlite",
32
+ "json"
33
+ ],
28
34
  "default": "json"
29
35
  },
30
36
  "path": {
@@ -32,51 +38,199 @@
32
38
  "default": "./.openclaw/consensus-tools.json"
33
39
  }
34
40
  },
35
- "required": ["kind", "path"]
41
+ "required": [
42
+ "kind",
43
+ "path"
44
+ ]
36
45
  },
37
46
  "server": {
38
47
  "type": "object",
39
48
  "additionalProperties": false,
40
49
  "properties": {
41
- "enabled": { "type": "boolean", "default": false },
42
- "host": { "type": "string", "default": "127.0.0.1" },
43
- "port": { "type": "integer", "default": 9888, "minimum": 1, "maximum": 65535 },
44
- "authToken": { "type": "string", "default": "" }
50
+ "enabled": {
51
+ "type": "boolean",
52
+ "default": false
53
+ },
54
+ "host": {
55
+ "type": "string",
56
+ "default": "127.0.0.1"
57
+ },
58
+ "port": {
59
+ "type": "integer",
60
+ "default": 9888,
61
+ "minimum": 1,
62
+ "maximum": 65535
63
+ },
64
+ "authToken": {
65
+ "type": "string",
66
+ "default": ""
67
+ }
45
68
  },
46
- "required": ["enabled", "host", "port", "authToken"]
69
+ "required": [
70
+ "enabled",
71
+ "host",
72
+ "port",
73
+ "authToken"
74
+ ]
75
+ },
76
+ "slashingEnabled": {
77
+ "type": "boolean",
78
+ "default": false
47
79
  },
48
- "slashingEnabled": { "type": "boolean", "default": false },
49
80
  "jobDefaults": {
50
81
  "type": "object",
51
82
  "additionalProperties": false,
52
83
  "properties": {
53
- "reward": { "type": "number", "default": 10, "minimum": 0 },
54
- "stakeRequired": { "type": "number", "default": 1, "minimum": 0 },
55
- "maxParticipants": { "type": "integer", "default": 3, "minimum": 1 },
56
- "minParticipants": { "type": "integer", "default": 1, "minimum": 1 },
57
- "expiresSeconds": { "type": "integer", "default": 86400, "minimum": 60 },
84
+ "reward": {
85
+ "type": "number",
86
+ "default": 10,
87
+ "minimum": 0
88
+ },
89
+ "stakeRequired": {
90
+ "type": "number",
91
+ "default": 1,
92
+ "minimum": 0
93
+ },
94
+ "maxParticipants": {
95
+ "type": "integer",
96
+ "default": 3,
97
+ "minimum": 1
98
+ },
99
+ "minParticipants": {
100
+ "type": "integer",
101
+ "default": 1,
102
+ "minimum": 1
103
+ },
104
+ "expiresSeconds": {
105
+ "type": "integer",
106
+ "default": 86400,
107
+ "minimum": 60
108
+ },
58
109
  "consensusPolicy": {
59
110
  "type": "object",
60
111
  "additionalProperties": false,
61
112
  "properties": {
62
113
  "type": {
63
114
  "type": "string",
64
- "enum": ["FIRST_SUBMISSION_WINS", "APPROVAL_VOTE", "MAJORITY_VOTE", "WEIGHTED_REPUTATION", "TRUSTED_ARBITER"],
115
+ "enum": [
116
+ "FIRST_SUBMISSION_WINS",
117
+ "SINGLE_WINNER",
118
+ "HIGHEST_CONFIDENCE_SINGLE",
119
+ "APPROVAL_VOTE",
120
+ "OWNER_PICK",
121
+ "TOP_K_SPLIT",
122
+ "MAJORITY_VOTE",
123
+ "WEIGHTED_VOTE_SIMPLE",
124
+ "WEIGHTED_REPUTATION",
125
+ "TRUSTED_ARBITER"
126
+ ],
65
127
  "default": "FIRST_SUBMISSION_WINS"
66
128
  },
67
- "trustedArbiterAgentId": { "type": "string", "default": "" }
129
+ "trustedArbiterAgentId": {
130
+ "type": "string",
131
+ "default": ""
132
+ },
133
+ "minConfidence": {
134
+ "type": "number",
135
+ "default": 0,
136
+ "minimum": 0,
137
+ "maximum": 1
138
+ },
139
+ "topK": {
140
+ "type": "integer",
141
+ "default": 2,
142
+ "minimum": 1
143
+ },
144
+ "ordering": {
145
+ "type": "string",
146
+ "enum": [
147
+ "confidence",
148
+ "score"
149
+ ],
150
+ "default": "confidence"
151
+ },
152
+ "quorum": {
153
+ "type": "integer",
154
+ "minimum": 1
155
+ },
156
+ "minScore": {
157
+ "type": "number"
158
+ },
159
+ "minMargin": {
160
+ "type": "number"
161
+ },
162
+ "tieBreak": {
163
+ "type": "string",
164
+ "enum": [
165
+ "earliest",
166
+ "confidence",
167
+ "arbiter"
168
+ ]
169
+ },
170
+ "approvalVote": {
171
+ "type": "object",
172
+ "additionalProperties": false,
173
+ "properties": {
174
+ "weightMode": {
175
+ "type": "string",
176
+ "enum": [
177
+ "equal",
178
+ "explicit",
179
+ "reputation"
180
+ ]
181
+ },
182
+ "settlement": {
183
+ "type": "string",
184
+ "enum": [
185
+ "immediate",
186
+ "staked",
187
+ "oracle"
188
+ ]
189
+ },
190
+ "oracle": {
191
+ "type": "string",
192
+ "enum": [
193
+ "trusted_arbiter"
194
+ ]
195
+ },
196
+ "voteSlashPercent": {
197
+ "type": "number",
198
+ "minimum": 0,
199
+ "maximum": 1
200
+ }
201
+ }
202
+ }
68
203
  },
69
- "required": ["type", "trustedArbiterAgentId"]
204
+ "required": [
205
+ "type",
206
+ "trustedArbiterAgentId"
207
+ ]
70
208
  },
71
209
  "slashingPolicy": {
72
210
  "type": "object",
73
211
  "additionalProperties": false,
74
212
  "properties": {
75
- "enabled": { "type": "boolean", "default": false },
76
- "slashPercent": { "type": "number", "default": 0, "minimum": 0, "maximum": 1 },
77
- "slashFlat": { "type": "number", "default": 0, "minimum": 0 }
213
+ "enabled": {
214
+ "type": "boolean",
215
+ "default": false
216
+ },
217
+ "slashPercent": {
218
+ "type": "number",
219
+ "default": 0,
220
+ "minimum": 0,
221
+ "maximum": 1
222
+ },
223
+ "slashFlat": {
224
+ "type": "number",
225
+ "default": 0,
226
+ "minimum": 0
227
+ }
78
228
  },
79
- "required": ["enabled", "slashPercent", "slashFlat"]
229
+ "required": [
230
+ "enabled",
231
+ "slashPercent",
232
+ "slashFlat"
233
+ ]
80
234
  }
81
235
  },
82
236
  "required": [
@@ -93,19 +247,145 @@
93
247
  "type": "object",
94
248
  "additionalProperties": false,
95
249
  "properties": {
96
- "faucetEnabled": { "type": "boolean", "default": false },
97
- "initialCreditsPerAgent": { "type": "number", "default": 0, "minimum": 0 },
98
- "balancesMode": { "type": "string", "enum": ["initial", "override"], "default": "initial" },
250
+ "faucetEnabled": {
251
+ "type": "boolean",
252
+ "default": false
253
+ },
254
+ "initialCreditsPerAgent": {
255
+ "type": "number",
256
+ "default": 0,
257
+ "minimum": 0
258
+ },
259
+ "balancesMode": {
260
+ "type": "string",
261
+ "enum": [
262
+ "initial",
263
+ "override"
264
+ ],
265
+ "default": "initial"
266
+ },
99
267
  "balances": {
100
268
  "type": "object",
101
- "additionalProperties": { "type": "number", "minimum": 0 },
269
+ "additionalProperties": {
270
+ "type": "number",
271
+ "minimum": 0
272
+ },
102
273
  "default": {}
103
274
  }
104
275
  },
105
- "required": ["faucetEnabled", "initialCreditsPerAgent", "balancesMode", "balances"]
276
+ "required": [
277
+ "faucetEnabled",
278
+ "initialCreditsPerAgent",
279
+ "balancesMode",
280
+ "balances"
281
+ ]
282
+ },
283
+ "consensusPolicies": {
284
+ "type": "object",
285
+ "additionalProperties": {
286
+ "type": "object",
287
+ "additionalProperties": false,
288
+ "properties": {
289
+ "type": {
290
+ "type": "string",
291
+ "enum": [
292
+ "FIRST_SUBMISSION_WINS",
293
+ "SINGLE_WINNER",
294
+ "HIGHEST_CONFIDENCE_SINGLE",
295
+ "APPROVAL_VOTE",
296
+ "OWNER_PICK",
297
+ "TOP_K_SPLIT",
298
+ "MAJORITY_VOTE",
299
+ "WEIGHTED_VOTE_SIMPLE",
300
+ "WEIGHTED_REPUTATION",
301
+ "TRUSTED_ARBITER"
302
+ ]
303
+ },
304
+ "trustedArbiterAgentId": {
305
+ "type": "string"
306
+ },
307
+ "minConfidence": {
308
+ "type": "number",
309
+ "minimum": 0,
310
+ "maximum": 1
311
+ },
312
+ "topK": {
313
+ "type": "integer",
314
+ "minimum": 1
315
+ },
316
+ "ordering": {
317
+ "type": "string",
318
+ "enum": [
319
+ "confidence",
320
+ "score"
321
+ ]
322
+ },
323
+ "quorum": {
324
+ "type": "integer",
325
+ "minimum": 1
326
+ },
327
+ "minScore": {
328
+ "type": "number"
329
+ },
330
+ "minMargin": {
331
+ "type": "number"
332
+ },
333
+ "tieBreak": {
334
+ "type": "string",
335
+ "enum": [
336
+ "earliest",
337
+ "confidence",
338
+ "arbiter"
339
+ ]
340
+ },
341
+ "approvalVote": {
342
+ "type": "object",
343
+ "additionalProperties": false,
344
+ "properties": {
345
+ "weightMode": {
346
+ "type": "string",
347
+ "enum": [
348
+ "equal",
349
+ "explicit",
350
+ "reputation"
351
+ ]
352
+ },
353
+ "settlement": {
354
+ "type": "string",
355
+ "enum": [
356
+ "immediate",
357
+ "staked",
358
+ "oracle"
359
+ ]
360
+ },
361
+ "oracle": {
362
+ "type": "string",
363
+ "enum": [
364
+ "trusted_arbiter"
365
+ ]
366
+ },
367
+ "voteSlashPercent": {
368
+ "type": "number",
369
+ "minimum": 0,
370
+ "maximum": 1
371
+ }
372
+ }
373
+ }
374
+ },
375
+ "required": [
376
+ "type"
377
+ ]
378
+ },
379
+ "default": {}
106
380
  }
107
381
  },
108
- "required": ["storage", "server", "slashingEnabled", "jobDefaults", "ledger"]
382
+ "required": [
383
+ "storage",
384
+ "server",
385
+ "slashingEnabled",
386
+ "jobDefaults",
387
+ "ledger"
388
+ ]
109
389
  },
110
390
  "global": {
111
391
  "type": "object",
@@ -120,7 +400,10 @@
120
400
  "default": ""
121
401
  }
122
402
  },
123
- "required": ["baseUrl", "accessToken"]
403
+ "required": [
404
+ "baseUrl",
405
+ "accessToken"
406
+ ]
124
407
  },
125
408
  "agentIdentity": {
126
409
  "type": "object",
@@ -128,7 +411,11 @@
128
411
  "properties": {
129
412
  "agentIdSource": {
130
413
  "type": "string",
131
- "enum": ["openclaw", "env", "manual"],
414
+ "enum": [
415
+ "openclaw",
416
+ "env",
417
+ "manual"
418
+ ],
132
419
  "default": "openclaw"
133
420
  },
134
421
  "manualAgentId": {
@@ -136,50 +423,108 @@
136
423
  "default": ""
137
424
  }
138
425
  },
139
- "required": ["agentIdSource", "manualAgentId"]
426
+ "required": [
427
+ "agentIdSource",
428
+ "manualAgentId"
429
+ ]
140
430
  },
141
431
  "safety": {
142
432
  "type": "object",
143
433
  "additionalProperties": false,
144
434
  "properties": {
145
- "requireOptionalToolsOptIn": { "type": "boolean", "default": true },
146
- "allowNetworkSideEffects": { "type": "boolean", "default": false }
435
+ "requireOptionalToolsOptIn": {
436
+ "type": "boolean",
437
+ "default": true
438
+ },
439
+ "allowNetworkSideEffects": {
440
+ "type": "boolean",
441
+ "default": false
442
+ }
147
443
  },
148
- "required": ["requireOptionalToolsOptIn", "allowNetworkSideEffects"]
444
+ "required": [
445
+ "requireOptionalToolsOptIn",
446
+ "allowNetworkSideEffects"
447
+ ]
149
448
  }
150
449
  },
151
- "required": ["mode", "agentIdentity", "safety"],
450
+ "required": [
451
+ "mode",
452
+ "agentIdentity",
453
+ "safety"
454
+ ],
152
455
  "allOf": [
153
456
  {
154
- "if": { "properties": { "mode": { "const": "local" } } },
155
- "then": { "required": ["local"] }
457
+ "if": {
458
+ "properties": {
459
+ "mode": {
460
+ "const": "local"
461
+ }
462
+ }
463
+ },
464
+ "then": {
465
+ "required": [
466
+ "local"
467
+ ]
468
+ }
156
469
  },
157
470
  {
158
- "if": { "properties": { "mode": { "const": "global" } } },
159
- "then": { "required": ["global"] }
471
+ "if": {
472
+ "properties": {
473
+ "mode": {
474
+ "const": "global"
475
+ }
476
+ }
477
+ },
478
+ "then": {
479
+ "required": [
480
+ "global"
481
+ ]
482
+ }
160
483
  }
161
484
  ]
162
485
  },
163
486
  "uiHints": {
164
487
  "local": {
165
488
  "storage": {
166
- "path": { "ui:widget": "file" }
489
+ "path": {
490
+ "ui:widget": "file"
491
+ }
167
492
  },
168
493
  "server": {
169
- "authToken": { "ui:widget": "password", "sensitive": true }
494
+ "authToken": {
495
+ "ui:widget": "password",
496
+ "sensitive": true
497
+ }
170
498
  },
171
499
  "ledger": {
172
- "faucetEnabled": { "ui:widget": "checkbox", "warning": "Dev-only faucet" },
173
- "balancesMode": { "label": "Balance mode", "ui:widget": "radio" },
174
- "balances": { "label": "Per-agent balances" }
500
+ "faucetEnabled": {
501
+ "ui:widget": "checkbox",
502
+ "warning": "Dev-only faucet"
503
+ },
504
+ "balancesMode": {
505
+ "label": "Balance mode",
506
+ "ui:widget": "radio"
507
+ },
508
+ "balances": {
509
+ "label": "Per-agent balances"
510
+ }
175
511
  }
176
512
  },
177
513
  "global": {
178
- "baseUrl": { "label": "Global board URL" },
179
- "accessToken": { "ui:widget": "password", "sensitive": true, "label": "Access Token" }
514
+ "baseUrl": {
515
+ "label": "Global board URL"
516
+ },
517
+ "accessToken": {
518
+ "ui:widget": "password",
519
+ "sensitive": true,
520
+ "label": "Access Token"
521
+ }
180
522
  },
181
523
  "safety": {
182
- "allowNetworkSideEffects": { "ui:widget": "checkbox", "warning": "Enables network mutations" }
524
+ "allowNetworkSideEffects": {
525
+ "ui:widget": "checkbox",
526
+ "warning": "Enables network mutations"
527
+ }
183
528
  }
184
529
  }
185
530
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consensus-tools/consensus-tools",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "bin": {
package/src/config.ts CHANGED
@@ -51,6 +51,7 @@ export const configSchema = {
51
51
  type: 'string',
52
52
  enum: [
53
53
  'FIRST_SUBMISSION_WINS',
54
+ 'SINGLE_WINNER',
54
55
  'HIGHEST_CONFIDENCE_SINGLE',
55
56
  'APPROVAL_VOTE',
56
57
  'OWNER_PICK',
@@ -66,7 +67,20 @@ export const configSchema = {
66
67
  minConfidence: { type: 'number', default: 0, minimum: 0, maximum: 1 },
67
68
  topK: { type: 'integer', default: 2, minimum: 1 },
68
69
  ordering: { type: 'string', enum: ['confidence', 'score'], default: 'confidence' },
69
- quorum: { type: 'integer', minimum: 1 }
70
+ quorum: { type: 'integer', minimum: 1 },
71
+ minScore: { type: 'number' },
72
+ minMargin: { type: 'number' },
73
+ tieBreak: { type: 'string', enum: ['earliest', 'confidence', 'arbiter'] },
74
+ approvalVote: {
75
+ type: 'object',
76
+ additionalProperties: false,
77
+ properties: {
78
+ weightMode: { type: 'string', enum: ['equal', 'explicit', 'reputation'] },
79
+ settlement: { type: 'string', enum: ['immediate', 'staked', 'oracle'] },
80
+ oracle: { type: 'string', enum: ['trusted_arbiter'] },
81
+ voteSlashPercent: { type: 'number', minimum: 0, maximum: 1 }
82
+ }
83
+ }
70
84
  },
71
85
  required: ['type', 'trustedArbiterAgentId']
72
86
  },
@@ -101,6 +115,7 @@ export const configSchema = {
101
115
  type: 'string',
102
116
  enum: [
103
117
  'FIRST_SUBMISSION_WINS',
118
+ 'SINGLE_WINNER',
104
119
  'HIGHEST_CONFIDENCE_SINGLE',
105
120
  'APPROVAL_VOTE',
106
121
  'OWNER_PICK',
@@ -115,7 +130,20 @@ export const configSchema = {
115
130
  minConfidence: { type: 'number', minimum: 0, maximum: 1 },
116
131
  topK: { type: 'integer', minimum: 1 },
117
132
  ordering: { type: 'string', enum: ['confidence', 'score'] },
118
- quorum: { type: 'integer', minimum: 1 }
133
+ quorum: { type: 'integer', minimum: 1 },
134
+ minScore: { type: 'number' },
135
+ minMargin: { type: 'number' },
136
+ tieBreak: { type: 'string', enum: ['earliest', 'confidence', 'arbiter'] },
137
+ approvalVote: {
138
+ type: 'object',
139
+ additionalProperties: false,
140
+ properties: {
141
+ weightMode: { type: 'string', enum: ['equal', 'explicit', 'reputation'] },
142
+ settlement: { type: 'string', enum: ['immediate', 'staked', 'oracle'] },
143
+ oracle: { type: 'string', enum: ['trusted_arbiter'] },
144
+ voteSlashPercent: { type: 'number', minimum: 0, maximum: 1 }
145
+ }
146
+ }
119
147
  },
120
148
  required: ['type']
121
149
  },
@@ -234,14 +262,14 @@ export function loadConfig(api: any, logger?: any): ConsensusToolsConfig {
234
262
  try {
235
263
  raw = api?.config?.getPluginConfig?.(PLUGIN_ID);
236
264
  } catch (err) {
237
- logger?.warn?.({ err }, 'consensus-tools: failed to read config via getPluginConfig');
265
+ logger?.warn?.(`consensus-tools: failed to read config via getPluginConfig: ${err instanceof Error ? err.message : String(err)}`);
238
266
  }
239
267
 
240
268
  if (!raw) {
241
269
  try {
242
270
  raw = api?.config?.get?.(`plugins.entries.${PLUGIN_ID}.config`);
243
271
  } catch (err) {
244
- logger?.warn?.({ err }, 'consensus-tools: failed to read config via config.get');
272
+ logger?.warn?.(`consensus-tools: failed to read config via config.get: ${err instanceof Error ? err.message : String(err)}`);
245
273
  }
246
274
  }
247
275
 
@@ -253,7 +281,30 @@ export function loadConfig(api: any, logger?: any): ConsensusToolsConfig {
253
281
  raw = api?.config?.entries?.[PLUGIN_ID]?.config;
254
282
  }
255
283
 
256
- return mergeDefaults(fallback, raw ?? {});
284
+ return normalizeLegacyPolicyAliases(mergeDefaults(fallback, raw ?? {}));
285
+ }
286
+
287
+ function normalizeLegacyPolicyAliases(config: ConsensusToolsConfig): ConsensusToolsConfig {
288
+ const normalized = deepCopy(config);
289
+
290
+ const normalizePolicyType = (value?: string) => {
291
+ if (value === 'SINGLE_WINNER') return 'FIRST_SUBMISSION_WINS';
292
+ return value;
293
+ };
294
+
295
+ const defaultsPolicy = normalized?.local?.jobDefaults?.consensusPolicy;
296
+ if (defaultsPolicy?.type) {
297
+ defaultsPolicy.type = normalizePolicyType(defaultsPolicy.type) as any;
298
+ }
299
+
300
+ const policyMap = normalized?.local?.consensusPolicies ?? {};
301
+ for (const policy of Object.values(policyMap)) {
302
+ if (policy?.type) {
303
+ policy.type = normalizePolicyType(policy.type) as any;
304
+ }
305
+ }
306
+
307
+ return normalized;
257
308
  }
258
309
 
259
310
  export function validateConfig(input: ConsensusToolsConfig, logger?: any): { config: ConsensusToolsConfig; errors: string[] } {
@@ -264,7 +315,7 @@ export function validateConfig(input: ConsensusToolsConfig, logger?: any): { con
264
315
  : (validate.errors || []).map((err) => `${err.instancePath || '/'} ${err.message || 'invalid'}`);
265
316
 
266
317
  if (!ok) {
267
- logger?.warn?.({ errors }, 'consensus-tools: config validation warnings');
318
+ logger?.warn?.(`consensus-tools: config validation warnings: ${JSON.stringify(errors)}`);
268
319
  }
269
320
 
270
321
  return { config: candidate, errors };
@@ -149,7 +149,7 @@ export class JobEngine {
149
149
  });
150
150
  });
151
151
 
152
- this.logger?.info?.({ jobId: job.id }, 'consensus-tools: job posted');
152
+ this.logger?.info?.(`consensus-tools: job posted (${job.id})`);
153
153
  return job;
154
154
  }
155
155
 
@@ -127,7 +127,7 @@ export class LedgerEngine {
127
127
  ensureNonNegative(nextBalance, `${entry.agentId} after ${entry.type}`);
128
128
  state.ledger.push(entry);
129
129
  });
130
- this.logger?.info?.({ entry }, 'consensus-tools: ledger entry');
130
+ this.logger?.info?.(`consensus-tools: ledger entry (${entry.type} ${entry.amount} agent=${entry.agentId}${entry.jobId ? ` job=${entry.jobId}` : ''})`);
131
131
  if (this.config.local.ledger.balancesMode === 'override') {
132
132
  await this.applyConfigBalances(this.config.local.ledger.balances, 'override');
133
133
  }
@@ -59,7 +59,7 @@ export class ConsensusToolsClient {
59
59
 
60
60
  if (!res.ok) {
61
61
  const text = await res.text();
62
- this.logger?.warn?.({ status: res.status, path }, 'consensus-tools: network request failed');
62
+ this.logger?.warn?.(`consensus-tools: network request failed (status=${res.status}, path=${path})`);
63
63
  throw new Error(`Network error ${res.status}: ${text || res.statusText}`);
64
64
  }
65
65
 
@@ -22,7 +22,7 @@ export class ConsensusToolsServer {
22
22
  this.server?.listen(port, host, () => resolve());
23
23
  });
24
24
 
25
- this.logger?.info?.({ host, port }, 'consensus-tools embedded server started');
25
+ this.logger?.info?.(`consensus-tools embedded server started (host=${host}, port=${port})`);
26
26
  }
27
27
 
28
28
  async stop(): Promise<void> {
@@ -113,7 +113,7 @@ export class ConsensusToolsServer {
113
113
 
114
114
  return this.reply(res, 404, { error: 'Not found' });
115
115
  } catch (err: any) {
116
- this.logger?.warn?.({ err }, 'consensus-tools server error');
116
+ this.logger?.warn?.(`consensus-tools server error: ${err instanceof Error ? err.message : String(err)}`);
117
117
  try {
118
118
  await this.engine.recordError?.(err?.message || 'Server error', { path: req.url, method: req.method });
119
119
  } catch {
package/src/service.ts CHANGED
@@ -16,7 +16,7 @@ export function createService(
16
16
  id: 'consensus-tools-service',
17
17
  start: async () => {
18
18
  if (config.mode === 'global') return;
19
- logger?.debug?.({ agentId, capabilities }, 'consensus-tools: service started');
19
+ logger?.debug?.(`consensus-tools: service started (agentId=${agentId}, capabilities=${Array.isArray(capabilities) ? capabilities.join(',') : ''})`);
20
20
  },
21
21
  stop: async () => {
22
22
  if (config.mode === 'global') return;