chainlesschain 0.47.8 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/chainlesschain.js +0 -0
- package/package.json +10 -8
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AppLayout-6SPt_8Y_.js → AppLayout-Rvi759IS.js} +1 -1
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
- package/src/assets/web-panel/assets/{Dashboard-Br7kCwKJ.js → Dashboard-DBhFxXYQ.js} +2 -2
- package/src/assets/web-panel/assets/{index-tN-8TosE.js → index-uL0cZ8N_.js} +2 -2
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/activitypub.js +533 -0
- package/src/commands/codegen.js +303 -0
- package/src/commands/collab.js +482 -0
- package/src/commands/compliance.js +597 -6
- package/src/commands/crosschain.js +382 -0
- package/src/commands/dbevo.js +388 -0
- package/src/commands/dev.js +411 -0
- package/src/commands/federation.js +427 -0
- package/src/commands/fusion.js +332 -0
- package/src/commands/governance.js +505 -0
- package/src/commands/hardening.js +110 -0
- package/src/commands/incentive.js +373 -0
- package/src/commands/inference.js +304 -0
- package/src/commands/infra.js +361 -0
- package/src/commands/kg.js +371 -0
- package/src/commands/marketplace.js +326 -0
- package/src/commands/matrix.js +283 -0
- package/src/commands/mcp.js +441 -18
- package/src/commands/nlprog.js +329 -0
- package/src/commands/nostr.js +196 -7
- package/src/commands/ops.js +408 -0
- package/src/commands/perception.js +385 -0
- package/src/commands/pqc.js +34 -0
- package/src/commands/privacy.js +345 -0
- package/src/commands/quantization.js +280 -0
- package/src/commands/recommend.js +336 -0
- package/src/commands/reputation.js +349 -0
- package/src/commands/runtime.js +500 -0
- package/src/commands/sla.js +352 -0
- package/src/commands/social.js +265 -0
- package/src/commands/stress.js +252 -0
- package/src/commands/tech.js +268 -0
- package/src/commands/tenant.js +576 -0
- package/src/commands/trust.js +366 -0
- package/src/harness/mcp-client.js +330 -54
- package/src/index.js +114 -0
- package/src/lib/activitypub-bridge.js +623 -0
- package/src/lib/aiops.js +523 -0
- package/src/lib/autonomous-developer.js +524 -0
- package/src/lib/code-agent.js +442 -0
- package/src/lib/collaboration-governance.js +556 -0
- package/src/lib/community-governance.js +649 -0
- package/src/lib/compliance-framework-reporter.js +600 -0
- package/src/lib/content-recommendation.js +600 -0
- package/src/lib/cross-chain.js +669 -0
- package/src/lib/dbevo.js +669 -0
- package/src/lib/decentral-infra.js +445 -0
- package/src/lib/federation-hardening.js +587 -0
- package/src/lib/hardening-manager.js +409 -0
- package/src/lib/inference-network.js +407 -0
- package/src/lib/knowledge-graph.js +530 -0
- package/src/lib/matrix-bridge.js +252 -0
- package/src/lib/mcp-client.js +3 -0
- package/src/lib/mcp-registry.js +347 -0
- package/src/lib/mcp-scaffold.js +385 -0
- package/src/lib/multimodal.js +698 -0
- package/src/lib/nl-programming.js +595 -0
- package/src/lib/nostr-bridge.js +214 -38
- package/src/lib/perception.js +500 -0
- package/src/lib/pqc-manager.js +141 -9
- package/src/lib/privacy-computing.js +575 -0
- package/src/lib/protocol-fusion.js +535 -0
- package/src/lib/quantization.js +362 -0
- package/src/lib/reputation-optimizer.js +509 -0
- package/src/lib/skill-marketplace.js +397 -0
- package/src/lib/sla-manager.js +484 -0
- package/src/lib/social-graph.js +408 -0
- package/src/lib/stix-parser.js +167 -0
- package/src/lib/stress-tester.js +383 -0
- package/src/lib/tech-learning-engine.js +651 -0
- package/src/lib/tenant-saas.js +831 -0
- package/src/lib/threat-intel.js +268 -0
- package/src/lib/token-incentive.js +513 -0
- package/src/lib/topic-classifier.js +400 -0
- package/src/lib/trust-security.js +473 -0
- package/src/lib/ueba.js +403 -0
- package/src/lib/universal-runtime.js +771 -0
- package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +0 -1
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collaboration Governance — CLI port of Phase 64
|
|
3
|
+
* (docs/design/modules/36_协作治理系统.md).
|
|
4
|
+
*
|
|
5
|
+
* The Desktop build drives long-running multi-agent coordination: raft/pbft
|
|
6
|
+
* consensus over P2P, ML-based auto-merge, real-time quality monitoring.
|
|
7
|
+
* The CLI can't host the P2P layer or the live agent mesh, so this port
|
|
8
|
+
* ships the tractable scaffolding:
|
|
9
|
+
*
|
|
10
|
+
* - Governance decisions with voting (quorum/threshold tally) +
|
|
11
|
+
* SQLite persistence.
|
|
12
|
+
* - Per-agent autonomy-level + permission tier management.
|
|
13
|
+
* - Pure-function task assignment helpers (skill match, load balance,
|
|
14
|
+
* priority score, optimize assignment).
|
|
15
|
+
* - Catalogs: decision types, conflict strategies, quality metrics,
|
|
16
|
+
* priority levels, permission tiers.
|
|
17
|
+
*
|
|
18
|
+
* Raft/PBFT/Paxos consensus, real-time quality monitoring and ML-driven
|
|
19
|
+
* auto-merge rules are Desktop-only.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import crypto from "crypto";
|
|
23
|
+
|
|
24
|
+
/* ── Constants ─────────────────────────────────────────────── */
|
|
25
|
+
|
|
26
|
+
export const DECISION_TYPES = Object.freeze({
|
|
27
|
+
TASK_ASSIGNMENT: "task_assignment",
|
|
28
|
+
RESOURCE_ALLOCATION: "resource_allocation",
|
|
29
|
+
CONFLICT_RESOLUTION: "conflict_resolution",
|
|
30
|
+
POLICY_UPDATE: "policy_update",
|
|
31
|
+
AUTONOMY_LEVEL: "autonomy_level",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const DECISION_STATUS = Object.freeze({
|
|
35
|
+
PENDING: "pending",
|
|
36
|
+
VOTING: "voting",
|
|
37
|
+
APPROVED: "approved",
|
|
38
|
+
REJECTED: "rejected",
|
|
39
|
+
EXECUTED: "executed",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const CONFLICT_STRATEGIES = Object.freeze({
|
|
43
|
+
VOTING: {
|
|
44
|
+
name: "voting",
|
|
45
|
+
types: ["majority", "weighted", "unanimous"],
|
|
46
|
+
},
|
|
47
|
+
ARBITRATION: {
|
|
48
|
+
name: "arbitration",
|
|
49
|
+
types: ["expert", "senior_agent", "human"],
|
|
50
|
+
},
|
|
51
|
+
CONSENSUS: {
|
|
52
|
+
name: "consensus",
|
|
53
|
+
algorithms: ["raft", "pbft", "paxos"],
|
|
54
|
+
},
|
|
55
|
+
AUTO_MERGE: {
|
|
56
|
+
name: "auto_merge",
|
|
57
|
+
rules: ["rule_based", "ml_based"],
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const QUALITY_METRICS = Object.freeze({
|
|
62
|
+
CODE_QUALITY: "code_quality",
|
|
63
|
+
COMMUNICATION_EFFICIENCY: "communication_efficiency",
|
|
64
|
+
TASK_COMPLETION_RATE: "task_completion_rate",
|
|
65
|
+
COLLABORATION_SATISFACTION: "collaboration_satisfaction",
|
|
66
|
+
CONFLICT_RESOLUTION_TIME: "conflict_resolution_time",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const PRIORITY_LEVELS = Object.freeze({
|
|
70
|
+
CRITICAL: 5,
|
|
71
|
+
HIGH: 4,
|
|
72
|
+
MEDIUM: 3,
|
|
73
|
+
LOW: 2,
|
|
74
|
+
TRIVIAL: 1,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const PERMISSION_TIERS = Object.freeze({
|
|
78
|
+
L0: ["read", "suggest"],
|
|
79
|
+
L1: ["read", "suggest", "write_simple"],
|
|
80
|
+
L2: ["read", "suggest", "write", "refactor", "test"],
|
|
81
|
+
L3: ["read", "suggest", "write", "refactor", "test", "design", "review"],
|
|
82
|
+
L4: ["all"],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const VALID_DECISION_TYPES = new Set(Object.values(DECISION_TYPES));
|
|
86
|
+
const VALID_DECISION_STATUS = new Set(Object.values(DECISION_STATUS));
|
|
87
|
+
const VALID_VOTES = new Set(["approve", "reject", "abstain"]);
|
|
88
|
+
const VALID_LEVELS = new Set([0, 1, 2, 3, 4]);
|
|
89
|
+
|
|
90
|
+
/* ── State ─────────────────────────────────────────────────── */
|
|
91
|
+
|
|
92
|
+
const _decisions = new Map();
|
|
93
|
+
const _agentLevels = new Map();
|
|
94
|
+
let _seq = 0;
|
|
95
|
+
|
|
96
|
+
/* ── Schema ────────────────────────────────────────────────── */
|
|
97
|
+
|
|
98
|
+
export function ensureGovernanceTables(db) {
|
|
99
|
+
if (!db) return;
|
|
100
|
+
db.exec(`
|
|
101
|
+
CREATE TABLE IF NOT EXISTS governance_decisions (
|
|
102
|
+
decision_id TEXT PRIMARY KEY,
|
|
103
|
+
type TEXT NOT NULL,
|
|
104
|
+
proposal TEXT NOT NULL,
|
|
105
|
+
status TEXT DEFAULT 'pending',
|
|
106
|
+
votes TEXT,
|
|
107
|
+
resolution TEXT,
|
|
108
|
+
executed_at INTEGER,
|
|
109
|
+
created_at INTEGER NOT NULL,
|
|
110
|
+
updated_at INTEGER NOT NULL
|
|
111
|
+
)
|
|
112
|
+
`);
|
|
113
|
+
db.exec(`
|
|
114
|
+
CREATE TABLE IF NOT EXISTS autonomy_levels (
|
|
115
|
+
agent_id TEXT PRIMARY KEY,
|
|
116
|
+
current_level INTEGER NOT NULL,
|
|
117
|
+
permissions TEXT NOT NULL,
|
|
118
|
+
adjustment_history TEXT,
|
|
119
|
+
last_review_at INTEGER,
|
|
120
|
+
created_at INTEGER NOT NULL,
|
|
121
|
+
updated_at INTEGER NOT NULL
|
|
122
|
+
)
|
|
123
|
+
`);
|
|
124
|
+
db.exec(
|
|
125
|
+
`CREATE INDEX IF NOT EXISTS idx_governance_decisions_status ON governance_decisions(status)`,
|
|
126
|
+
);
|
|
127
|
+
db.exec(
|
|
128
|
+
`CREATE INDEX IF NOT EXISTS idx_governance_decisions_type ON governance_decisions(type)`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ── Catalogs ──────────────────────────────────────────────── */
|
|
133
|
+
|
|
134
|
+
export function listDecisionTypes() {
|
|
135
|
+
return Object.values(DECISION_TYPES);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function listConflictStrategies() {
|
|
139
|
+
return Object.values(CONFLICT_STRATEGIES);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function listQualityMetrics() {
|
|
143
|
+
return Object.values(QUALITY_METRICS);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function listPriorityLevels() {
|
|
147
|
+
return Object.entries(PRIORITY_LEVELS).map(([name, value]) => ({
|
|
148
|
+
name,
|
|
149
|
+
value,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function listPermissionTiers() {
|
|
154
|
+
return Object.entries(PERMISSION_TIERS).map(([tier, permissions]) => ({
|
|
155
|
+
tier,
|
|
156
|
+
level: Number(tier.slice(1)),
|
|
157
|
+
permissions: [...permissions],
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function _strip(row) {
|
|
162
|
+
const { _seq: _omit, ...rest } = row;
|
|
163
|
+
void _omit;
|
|
164
|
+
return rest;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* ── Decisions ─────────────────────────────────────────────── */
|
|
168
|
+
|
|
169
|
+
export function createDecision(db, config = {}) {
|
|
170
|
+
const type = String(config.type || "").toLowerCase();
|
|
171
|
+
if (!VALID_DECISION_TYPES.has(type)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Unknown decision type: ${config.type} (known: ${[...VALID_DECISION_TYPES].join("/")})`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const proposal = String(config.proposal || "").trim();
|
|
177
|
+
if (!proposal) throw new Error("proposal is required");
|
|
178
|
+
|
|
179
|
+
const now = Number(config.now ?? Date.now());
|
|
180
|
+
const decisionId = config.decisionId || crypto.randomUUID();
|
|
181
|
+
const decision = {
|
|
182
|
+
decisionId,
|
|
183
|
+
type,
|
|
184
|
+
proposal,
|
|
185
|
+
status: DECISION_STATUS.PENDING,
|
|
186
|
+
votes: {},
|
|
187
|
+
resolution: null,
|
|
188
|
+
executedAt: null,
|
|
189
|
+
createdAt: now,
|
|
190
|
+
updatedAt: now,
|
|
191
|
+
_seq: ++_seq,
|
|
192
|
+
};
|
|
193
|
+
_decisions.set(decisionId, decision);
|
|
194
|
+
|
|
195
|
+
if (db) {
|
|
196
|
+
db.prepare(
|
|
197
|
+
`INSERT INTO governance_decisions (decision_id, type, proposal, status, votes, resolution, executed_at, created_at, updated_at)
|
|
198
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
199
|
+
).run(
|
|
200
|
+
decisionId,
|
|
201
|
+
type,
|
|
202
|
+
proposal,
|
|
203
|
+
decision.status,
|
|
204
|
+
JSON.stringify(decision.votes),
|
|
205
|
+
null,
|
|
206
|
+
null,
|
|
207
|
+
now,
|
|
208
|
+
now,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return _strip(decision);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function getDecision(decisionId) {
|
|
216
|
+
const d = _decisions.get(decisionId);
|
|
217
|
+
return d ? _strip(d) : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function listDecisions(opts = {}) {
|
|
221
|
+
let rows = [..._decisions.values()];
|
|
222
|
+
if (opts.type) {
|
|
223
|
+
const t = String(opts.type).toLowerCase();
|
|
224
|
+
rows = rows.filter((d) => d.type === t);
|
|
225
|
+
}
|
|
226
|
+
if (opts.status) {
|
|
227
|
+
const s = String(opts.status).toLowerCase();
|
|
228
|
+
if (!VALID_DECISION_STATUS.has(s)) {
|
|
229
|
+
throw new Error(`Unknown status: ${opts.status}`);
|
|
230
|
+
}
|
|
231
|
+
rows = rows.filter((d) => d.status === s);
|
|
232
|
+
}
|
|
233
|
+
rows.sort((a, b) => b.createdAt - a.createdAt || b._seq - a._seq);
|
|
234
|
+
const limit = opts.limit || 50;
|
|
235
|
+
return rows.slice(0, limit).map(_strip);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function _mustGet(decisionId) {
|
|
239
|
+
const d = _decisions.get(decisionId);
|
|
240
|
+
if (!d) throw new Error(`Decision not found: ${decisionId}`);
|
|
241
|
+
return d;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function _persistDecision(db, decisionId, fields) {
|
|
245
|
+
if (!db) return;
|
|
246
|
+
const setClauses = Object.keys(fields)
|
|
247
|
+
.map((k) => `${k} = ?`)
|
|
248
|
+
.join(", ");
|
|
249
|
+
const values = Object.values(fields).map((v) =>
|
|
250
|
+
v && typeof v === "object" ? JSON.stringify(v) : v,
|
|
251
|
+
);
|
|
252
|
+
db.prepare(
|
|
253
|
+
`UPDATE governance_decisions SET ${setClauses} WHERE decision_id = ?`,
|
|
254
|
+
).run(...values, decisionId);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function vote(db, decisionId, agentId, voteValue, reason = "") {
|
|
258
|
+
const decision = _mustGet(decisionId);
|
|
259
|
+
if (
|
|
260
|
+
decision.status !== DECISION_STATUS.PENDING &&
|
|
261
|
+
decision.status !== DECISION_STATUS.VOTING
|
|
262
|
+
) {
|
|
263
|
+
throw new Error(`Cannot vote on ${decision.status} decision`);
|
|
264
|
+
}
|
|
265
|
+
const normalizedVote = String(voteValue || "").toLowerCase();
|
|
266
|
+
if (!VALID_VOTES.has(normalizedVote)) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Invalid vote: ${voteValue} (must be approve|reject|abstain)`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
const aid = String(agentId || "").trim();
|
|
272
|
+
if (!aid) throw new Error("agentId is required");
|
|
273
|
+
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
decision.votes[aid] = { vote: normalizedVote, reason, votedAt: now };
|
|
276
|
+
decision.status = DECISION_STATUS.VOTING;
|
|
277
|
+
decision.updatedAt = now;
|
|
278
|
+
|
|
279
|
+
_persistDecision(db, decisionId, {
|
|
280
|
+
votes: decision.votes,
|
|
281
|
+
status: decision.status,
|
|
282
|
+
updated_at: now,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return _strip(decision);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function tallyDecision(db, decisionId, opts = {}) {
|
|
289
|
+
const decision = _mustGet(decisionId);
|
|
290
|
+
if (
|
|
291
|
+
decision.status !== DECISION_STATUS.PENDING &&
|
|
292
|
+
decision.status !== DECISION_STATUS.VOTING
|
|
293
|
+
) {
|
|
294
|
+
throw new Error(`Cannot tally ${decision.status} decision`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const quorum = opts.quorum ?? 0.5;
|
|
298
|
+
const threshold = opts.threshold ?? 0.6;
|
|
299
|
+
const totalVoters = opts.totalVoters ?? Object.keys(decision.votes).length;
|
|
300
|
+
|
|
301
|
+
const tallied = { approve: 0, reject: 0, abstain: 0 };
|
|
302
|
+
for (const v of Object.values(decision.votes)) {
|
|
303
|
+
if (tallied[v.vote] != null) tallied[v.vote]++;
|
|
304
|
+
}
|
|
305
|
+
const participation =
|
|
306
|
+
totalVoters > 0
|
|
307
|
+
? (tallied.approve + tallied.reject + tallied.abstain) / totalVoters
|
|
308
|
+
: 0;
|
|
309
|
+
const effectiveVotes = tallied.approve + tallied.reject;
|
|
310
|
+
const approvalRate =
|
|
311
|
+
effectiveVotes > 0 ? tallied.approve / effectiveVotes : 0;
|
|
312
|
+
|
|
313
|
+
let nextStatus;
|
|
314
|
+
let outcome;
|
|
315
|
+
if (participation < quorum) {
|
|
316
|
+
nextStatus = DECISION_STATUS.REJECTED;
|
|
317
|
+
outcome = `quorum not met (${(participation * 100).toFixed(1)}% < ${(quorum * 100).toFixed(0)}%)`;
|
|
318
|
+
} else if (approvalRate >= threshold) {
|
|
319
|
+
nextStatus = DECISION_STATUS.APPROVED;
|
|
320
|
+
outcome = `approved (${(approvalRate * 100).toFixed(1)}% ≥ ${(threshold * 100).toFixed(0)}%)`;
|
|
321
|
+
} else {
|
|
322
|
+
nextStatus = DECISION_STATUS.REJECTED;
|
|
323
|
+
outcome = `rejected (${(approvalRate * 100).toFixed(1)}% < ${(threshold * 100).toFixed(0)}%)`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
const resolution = {
|
|
328
|
+
tally: tallied,
|
|
329
|
+
totalVoters,
|
|
330
|
+
participation: Number(participation.toFixed(3)),
|
|
331
|
+
approvalRate: Number(approvalRate.toFixed(3)),
|
|
332
|
+
quorum,
|
|
333
|
+
threshold,
|
|
334
|
+
outcome,
|
|
335
|
+
tallyAt: now,
|
|
336
|
+
};
|
|
337
|
+
decision.status = nextStatus;
|
|
338
|
+
decision.resolution = resolution;
|
|
339
|
+
decision.updatedAt = now;
|
|
340
|
+
|
|
341
|
+
_persistDecision(db, decisionId, {
|
|
342
|
+
status: nextStatus,
|
|
343
|
+
resolution,
|
|
344
|
+
updated_at: now,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return _strip(decision);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function markExecuted(db, decisionId) {
|
|
351
|
+
const decision = _mustGet(decisionId);
|
|
352
|
+
if (decision.status !== DECISION_STATUS.APPROVED) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Can only execute approved decisions (status=${decision.status})`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const now = Date.now();
|
|
358
|
+
decision.status = DECISION_STATUS.EXECUTED;
|
|
359
|
+
decision.executedAt = now;
|
|
360
|
+
decision.updatedAt = now;
|
|
361
|
+
|
|
362
|
+
_persistDecision(db, decisionId, {
|
|
363
|
+
status: decision.status,
|
|
364
|
+
executed_at: now,
|
|
365
|
+
updated_at: now,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
return _strip(decision);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ── Agent autonomy levels ─────────────────────────────────── */
|
|
372
|
+
|
|
373
|
+
export function setAutonomyLevel(db, agentId, level, opts = {}) {
|
|
374
|
+
const aid = String(agentId || "").trim();
|
|
375
|
+
if (!aid) throw new Error("agentId is required");
|
|
376
|
+
const lvl = Number(level);
|
|
377
|
+
if (!VALID_LEVELS.has(lvl)) {
|
|
378
|
+
throw new Error(`Invalid autonomy level: ${level} (must be 0..4)`);
|
|
379
|
+
}
|
|
380
|
+
const permissions = PERMISSION_TIERS[`L${lvl}`];
|
|
381
|
+
const now = Number(opts.now ?? Date.now());
|
|
382
|
+
|
|
383
|
+
const existing = _agentLevels.get(aid);
|
|
384
|
+
const history = existing?.adjustmentHistory || [];
|
|
385
|
+
history.push({
|
|
386
|
+
level: lvl,
|
|
387
|
+
reason: opts.reason || "",
|
|
388
|
+
adjustedAt: now,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const record = {
|
|
392
|
+
agentId: aid,
|
|
393
|
+
currentLevel: lvl,
|
|
394
|
+
permissions: [...permissions],
|
|
395
|
+
adjustmentHistory: history,
|
|
396
|
+
lastReviewAt: now,
|
|
397
|
+
createdAt: existing?.createdAt || now,
|
|
398
|
+
updatedAt: now,
|
|
399
|
+
_seq: ++_seq,
|
|
400
|
+
};
|
|
401
|
+
_agentLevels.set(aid, record);
|
|
402
|
+
|
|
403
|
+
if (db) {
|
|
404
|
+
if (existing) {
|
|
405
|
+
db.prepare(
|
|
406
|
+
`UPDATE autonomy_levels SET current_level = ?, permissions = ?, adjustment_history = ?, last_review_at = ?, updated_at = ? WHERE agent_id = ?`,
|
|
407
|
+
).run(
|
|
408
|
+
lvl,
|
|
409
|
+
JSON.stringify(record.permissions),
|
|
410
|
+
JSON.stringify(history),
|
|
411
|
+
now,
|
|
412
|
+
now,
|
|
413
|
+
aid,
|
|
414
|
+
);
|
|
415
|
+
} else {
|
|
416
|
+
db.prepare(
|
|
417
|
+
`INSERT INTO autonomy_levels (agent_id, current_level, permissions, adjustment_history, last_review_at, created_at, updated_at)
|
|
418
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
419
|
+
).run(
|
|
420
|
+
aid,
|
|
421
|
+
lvl,
|
|
422
|
+
JSON.stringify(record.permissions),
|
|
423
|
+
JSON.stringify(history),
|
|
424
|
+
now,
|
|
425
|
+
now,
|
|
426
|
+
now,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return _strip(record);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export function getAutonomyLevel(agentId) {
|
|
435
|
+
const r = _agentLevels.get(agentId);
|
|
436
|
+
return r ? _strip(r) : null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function listAutonomyAgents(opts = {}) {
|
|
440
|
+
let rows = [..._agentLevels.values()];
|
|
441
|
+
if (opts.level != null) {
|
|
442
|
+
const lvl = Number(opts.level);
|
|
443
|
+
rows = rows.filter((r) => r.currentLevel === lvl);
|
|
444
|
+
}
|
|
445
|
+
rows.sort((a, b) => b.updatedAt - a.updatedAt || b._seq - a._seq);
|
|
446
|
+
const limit = opts.limit || 50;
|
|
447
|
+
return rows.slice(0, limit).map(_strip);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* ── Task assignment helpers ───────────────────────────────── */
|
|
451
|
+
|
|
452
|
+
export function calculateSkillMatch(requiredSkills = [], agentSkills = {}) {
|
|
453
|
+
if (!Array.isArray(requiredSkills) || requiredSkills.length === 0) return 0;
|
|
454
|
+
let totalWeight = 0;
|
|
455
|
+
let totalScore = 0;
|
|
456
|
+
for (const skill of requiredSkills) {
|
|
457
|
+
const weight = Number(skill.weight ?? 1);
|
|
458
|
+
const requiredLevel = Number(
|
|
459
|
+
skill.requiredLevel ?? skill.required_level ?? 1,
|
|
460
|
+
);
|
|
461
|
+
const agentLevel = Number(agentSkills[skill.name] ?? 0);
|
|
462
|
+
const match =
|
|
463
|
+
requiredLevel > 0 ? Math.min(agentLevel / requiredLevel, 1.0) : 0;
|
|
464
|
+
totalScore += match * weight;
|
|
465
|
+
totalWeight += weight;
|
|
466
|
+
}
|
|
467
|
+
return totalWeight > 0 ? totalScore / totalWeight : 0;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function balanceLoad(agents = []) {
|
|
471
|
+
if (!Array.isArray(agents) || agents.length === 0) return null;
|
|
472
|
+
const sorted = [...agents].sort((a, b) => {
|
|
473
|
+
const la = a.currentLoad / Math.max(a.maxCapacity, 1);
|
|
474
|
+
const lb = b.currentLoad / Math.max(b.maxCapacity, 1);
|
|
475
|
+
return la - lb;
|
|
476
|
+
});
|
|
477
|
+
return sorted[0];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function calculatePriority(task = {}) {
|
|
481
|
+
const urgency = Number(task.urgency ?? 0);
|
|
482
|
+
const importance = Number(task.importance ?? 0);
|
|
483
|
+
const complexity = Number(task.complexity ?? 0);
|
|
484
|
+
const dependencies = Number(task.dependencies ?? 0);
|
|
485
|
+
return (
|
|
486
|
+
urgency * 0.4 + importance * 0.3 + complexity * 0.2 + dependencies * 0.1
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export function optimizeTaskAssignment(tasks = [], agents = []) {
|
|
491
|
+
if (!Array.isArray(tasks) || !Array.isArray(agents)) {
|
|
492
|
+
throw new Error("tasks and agents must be arrays");
|
|
493
|
+
}
|
|
494
|
+
const sortedTasks = [...tasks].sort(
|
|
495
|
+
(a, b) => calculatePriority(b) - calculatePriority(a),
|
|
496
|
+
);
|
|
497
|
+
const agentLoad = new Map(
|
|
498
|
+
agents.map((a) => [
|
|
499
|
+
a.id,
|
|
500
|
+
{
|
|
501
|
+
...a,
|
|
502
|
+
currentLoad: Number(a.currentLoad ?? 0),
|
|
503
|
+
maxCapacity: Number(a.maxCapacity ?? 1),
|
|
504
|
+
},
|
|
505
|
+
]),
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
const assignments = [];
|
|
509
|
+
const unassigned = [];
|
|
510
|
+
|
|
511
|
+
for (const task of sortedTasks) {
|
|
512
|
+
const candidates = [...agentLoad.values()].filter(
|
|
513
|
+
(a) => a.currentLoad < a.maxCapacity,
|
|
514
|
+
);
|
|
515
|
+
if (candidates.length === 0) {
|
|
516
|
+
unassigned.push({ taskId: task.id, reason: "no capacity" });
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const required = task.requiredSkills || [];
|
|
520
|
+
const scored = candidates
|
|
521
|
+
.map((a) => ({
|
|
522
|
+
agent: a,
|
|
523
|
+
skillScore: calculateSkillMatch(required, a.skills || {}),
|
|
524
|
+
loadRatio: a.currentLoad / Math.max(a.maxCapacity, 1),
|
|
525
|
+
}))
|
|
526
|
+
.sort((a, b) => b.skillScore - a.skillScore || a.loadRatio - b.loadRatio);
|
|
527
|
+
const best = scored[0];
|
|
528
|
+
if (best.skillScore === 0 && required.length > 0) {
|
|
529
|
+
unassigned.push({ taskId: task.id, reason: "no skill match" });
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
assignments.push({
|
|
533
|
+
taskId: task.id,
|
|
534
|
+
agentId: best.agent.id,
|
|
535
|
+
skillScore: Number(best.skillScore.toFixed(3)),
|
|
536
|
+
priority: Number(calculatePriority(task).toFixed(3)),
|
|
537
|
+
});
|
|
538
|
+
best.agent.currentLoad += 1;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
assignments,
|
|
543
|
+
unassigned,
|
|
544
|
+
totalTasks: tasks.length,
|
|
545
|
+
assigned: assignments.length,
|
|
546
|
+
unassignedCount: unassigned.length,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* ── Reset (tests) ─────────────────────────────────────────── */
|
|
551
|
+
|
|
552
|
+
export function _resetState() {
|
|
553
|
+
_decisions.clear();
|
|
554
|
+
_agentLevels.clear();
|
|
555
|
+
_seq = 0;
|
|
556
|
+
}
|