chainlesschain 0.66.0 → 0.132.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 +1 -1
- package/src/commands/a2a.js +380 -0
- package/src/commands/agent-network.js +254 -1
- package/src/commands/audit.js +302 -0
- package/src/commands/automation.js +271 -1
- package/src/commands/bi.js +348 -0
- package/src/commands/codegen.js +224 -0
- package/src/commands/collab.js +341 -0
- package/src/commands/compliance.js +1035 -0
- package/src/commands/cowork.js +221 -0
- package/src/commands/crosschain.js +218 -0
- package/src/commands/dbevo.js +284 -0
- package/src/commands/dev.js +252 -0
- package/src/commands/did.js +358 -0
- package/src/commands/dlp.js +341 -0
- package/src/commands/encrypt.js +341 -0
- package/src/commands/evomap.js +394 -0
- package/src/commands/export.js +256 -1
- package/src/commands/federation.js +283 -0
- package/src/commands/fusion.js +258 -0
- package/src/commands/governance.js +325 -0
- package/src/commands/hardening.js +411 -0
- package/src/commands/hook.js +148 -0
- package/src/commands/import.js +252 -0
- package/src/commands/incentive.js +322 -0
- package/src/commands/inference.js +318 -0
- package/src/commands/infra.js +244 -0
- package/src/commands/instinct.js +260 -0
- package/src/commands/ipfs.js +318 -0
- package/src/commands/kg.js +387 -0
- package/src/commands/llm.js +263 -0
- package/src/commands/lowcode.js +356 -0
- package/src/commands/marketplace.js +256 -0
- package/src/commands/mcp.js +221 -0
- package/src/commands/memory.js +248 -0
- package/src/commands/multimodal.js +296 -0
- package/src/commands/nlprog.js +356 -0
- package/src/commands/note.js +244 -0
- package/src/commands/ops.js +354 -0
- package/src/commands/orchestrate.js +166 -0
- package/src/commands/org.js +277 -0
- package/src/commands/p2p.js +390 -0
- package/src/commands/perception.js +290 -0
- package/src/commands/permmem.js +251 -0
- package/src/commands/plugin-ecosystem.js +273 -0
- package/src/commands/pqc.js +393 -0
- package/src/commands/privacy.js +321 -0
- package/src/commands/quantization.js +351 -0
- package/src/commands/rcache.js +271 -0
- package/src/commands/recommend.js +340 -0
- package/src/commands/reputation.js +261 -0
- package/src/commands/runtime.js +307 -0
- package/src/commands/scim.js +262 -0
- package/src/commands/session.js +258 -0
- package/src/commands/siem.js +246 -0
- package/src/commands/skill.js +267 -1
- package/src/commands/sla.js +259 -0
- package/src/commands/social.js +256 -0
- package/src/commands/sso.js +186 -1
- package/src/commands/stress.js +230 -0
- package/src/commands/sync.js +256 -0
- package/src/commands/tech.js +338 -0
- package/src/commands/tenant.js +351 -0
- package/src/commands/terraform.js +245 -0
- package/src/commands/tokens.js +269 -0
- package/src/commands/trust.js +249 -0
- package/src/commands/wallet.js +277 -0
- package/src/commands/workflow.js +171 -0
- package/src/commands/zkp.js +335 -0
- package/src/index.js +4 -0
- package/src/lib/a2a-protocol.js +451 -0
- package/src/lib/agent-coordinator.js +325 -0
- package/src/lib/agent-network.js +387 -0
- package/src/lib/agent-router.js +395 -0
- package/src/lib/aiops.js +478 -0
- package/src/lib/app-builder.js +239 -0
- package/src/lib/audit-logger.js +379 -0
- package/src/lib/automation-engine.js +330 -0
- package/src/lib/autonomous-developer.js +350 -0
- package/src/lib/bi-engine.js +338 -0
- package/src/lib/code-agent.js +323 -0
- package/src/lib/collaboration-governance.js +364 -0
- package/src/lib/community-governance.js +436 -0
- package/src/lib/compliance-manager.js +434 -0
- package/src/lib/content-recommendation.js +469 -0
- package/src/lib/cross-chain.js +345 -0
- package/src/lib/crypto-manager.js +350 -0
- package/src/lib/dbevo.js +338 -0
- package/src/lib/decentral-infra.js +340 -0
- package/src/lib/did-manager.js +367 -0
- package/src/lib/dlp-engine.js +389 -0
- package/src/lib/evomap-federation.js +177 -0
- package/src/lib/evomap-governance.js +276 -0
- package/src/lib/federation-hardening.js +259 -0
- package/src/lib/hardening-manager.js +348 -0
- package/src/lib/hook-manager.js +380 -0
- package/src/lib/inference-network.js +330 -0
- package/src/lib/instinct-manager.js +332 -0
- package/src/lib/ipfs-storage.js +334 -0
- package/src/lib/knowledge-exporter.js +381 -0
- package/src/lib/knowledge-graph.js +432 -0
- package/src/lib/knowledge-importer.js +379 -0
- package/src/lib/llm-providers.js +391 -0
- package/src/lib/mcp-registry.js +333 -0
- package/src/lib/memory-manager.js +330 -0
- package/src/lib/multimodal.js +346 -0
- package/src/lib/nl-programming.js +343 -0
- package/src/lib/note-versioning.js +327 -0
- package/src/lib/org-manager.js +323 -0
- package/src/lib/p2p-manager.js +387 -0
- package/src/lib/perception.js +346 -0
- package/src/lib/perf-tuning.js +4 -1
- package/src/lib/permanent-memory.js +320 -0
- package/src/lib/plugin-ecosystem.js +377 -0
- package/src/lib/pqc-manager.js +368 -0
- package/src/lib/privacy-computing.js +427 -0
- package/src/lib/protocol-fusion.js +417 -0
- package/src/lib/quantization.js +325 -0
- package/src/lib/reputation-optimizer.js +299 -0
- package/src/lib/response-cache.js +327 -0
- package/src/lib/scim-manager.js +329 -0
- package/src/lib/session-manager.js +329 -0
- package/src/lib/siem-exporter.js +333 -0
- package/src/lib/skill-loader.js +377 -0
- package/src/lib/skill-marketplace.js +325 -0
- package/src/lib/sla-manager.js +275 -0
- package/src/lib/social-manager.js +326 -0
- package/src/lib/sso-manager.js +332 -0
- package/src/lib/stress-tester.js +330 -0
- package/src/lib/sync-manager.js +326 -0
- package/src/lib/tech-learning-engine.js +369 -0
- package/src/lib/tenant-saas.js +460 -0
- package/src/lib/terraform-manager.js +363 -0
- package/src/lib/threat-intel.js +335 -0
- package/src/lib/token-incentive.js +293 -0
- package/src/lib/token-tracker.js +329 -0
- package/src/lib/trust-security.js +390 -0
- package/src/lib/ueba.js +389 -0
- package/src/lib/universal-runtime.js +325 -0
- package/src/lib/wallet-manager.js +326 -0
- package/src/lib/workflow-engine.js +322 -0
- package/src/lib/zkp-engine.js +274 -0
|
@@ -262,3 +262,332 @@ export function exportSessionMarkdown(session) {
|
|
|
262
262
|
|
|
263
263
|
return lines.join("\n");
|
|
264
264
|
}
|
|
265
|
+
|
|
266
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
267
|
+
* V2 Surface — Session governance layer.
|
|
268
|
+
* Tracks conversation maturity + turn lifecycle independent of
|
|
269
|
+
* legacy llm_sessions SQLite store above.
|
|
270
|
+
* ═══════════════════════════════════════════════════════════════ */
|
|
271
|
+
|
|
272
|
+
export const CONVERSATION_MATURITY_V2 = Object.freeze({
|
|
273
|
+
DRAFT: "draft",
|
|
274
|
+
ACTIVE: "active",
|
|
275
|
+
PAUSED: "paused",
|
|
276
|
+
ARCHIVED: "archived",
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
export const TURN_LIFECYCLE_V2 = Object.freeze({
|
|
280
|
+
PENDING: "pending",
|
|
281
|
+
STREAMING: "streaming",
|
|
282
|
+
COMPLETED: "completed",
|
|
283
|
+
FAILED: "failed",
|
|
284
|
+
CANCELLED: "cancelled",
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const CONV_TRANSITIONS_V2 = new Map([
|
|
288
|
+
["draft", new Set(["active", "archived"])],
|
|
289
|
+
["active", new Set(["paused", "archived"])],
|
|
290
|
+
["paused", new Set(["active", "archived"])],
|
|
291
|
+
["archived", new Set()],
|
|
292
|
+
]);
|
|
293
|
+
const CONV_TERMINALS_V2 = new Set(["archived"]);
|
|
294
|
+
|
|
295
|
+
const TURN_TRANSITIONS_V2 = new Map([
|
|
296
|
+
["pending", new Set(["streaming", "cancelled"])],
|
|
297
|
+
["streaming", new Set(["completed", "failed", "cancelled"])],
|
|
298
|
+
["completed", new Set()],
|
|
299
|
+
["failed", new Set()],
|
|
300
|
+
["cancelled", new Set()],
|
|
301
|
+
]);
|
|
302
|
+
const TURN_TERMINALS_V2 = new Set(["completed", "failed", "cancelled"]);
|
|
303
|
+
|
|
304
|
+
export const SESSION_DEFAULT_MAX_ACTIVE_CONV_PER_USER = 50;
|
|
305
|
+
export const SESSION_DEFAULT_MAX_PENDING_TURNS_PER_CONV = 3;
|
|
306
|
+
export const SESSION_DEFAULT_CONV_IDLE_MS = 1000 * 60 * 60 * 24 * 14; // 14 days
|
|
307
|
+
export const SESSION_DEFAULT_TURN_STUCK_MS = 1000 * 60 * 5; // 5 min
|
|
308
|
+
|
|
309
|
+
const _conversationsV2 = new Map();
|
|
310
|
+
const _turnsV2 = new Map();
|
|
311
|
+
let _maxActiveConvPerUserV2 = SESSION_DEFAULT_MAX_ACTIVE_CONV_PER_USER;
|
|
312
|
+
let _maxPendingTurnsPerConvV2 = SESSION_DEFAULT_MAX_PENDING_TURNS_PER_CONV;
|
|
313
|
+
let _convIdleMsV2 = SESSION_DEFAULT_CONV_IDLE_MS;
|
|
314
|
+
let _turnStuckMsV2 = SESSION_DEFAULT_TURN_STUCK_MS;
|
|
315
|
+
|
|
316
|
+
function _posIntSessionV2(n, label) {
|
|
317
|
+
const v = Math.floor(Number(n));
|
|
318
|
+
if (!Number.isFinite(v) || v <= 0)
|
|
319
|
+
throw new Error(`${label} must be a positive integer`);
|
|
320
|
+
return v;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function getMaxActiveConvPerUserV2() {
|
|
324
|
+
return _maxActiveConvPerUserV2;
|
|
325
|
+
}
|
|
326
|
+
export function setMaxActiveConvPerUserV2(n) {
|
|
327
|
+
_maxActiveConvPerUserV2 = _posIntSessionV2(n, "maxActiveConvPerUser");
|
|
328
|
+
}
|
|
329
|
+
export function getMaxPendingTurnsPerConvV2() {
|
|
330
|
+
return _maxPendingTurnsPerConvV2;
|
|
331
|
+
}
|
|
332
|
+
export function setMaxPendingTurnsPerConvV2(n) {
|
|
333
|
+
_maxPendingTurnsPerConvV2 = _posIntSessionV2(n, "maxPendingTurnsPerConv");
|
|
334
|
+
}
|
|
335
|
+
export function getConvIdleMsV2() {
|
|
336
|
+
return _convIdleMsV2;
|
|
337
|
+
}
|
|
338
|
+
export function setConvIdleMsV2(n) {
|
|
339
|
+
_convIdleMsV2 = _posIntSessionV2(n, "convIdleMs");
|
|
340
|
+
}
|
|
341
|
+
export function getTurnStuckMsV2() {
|
|
342
|
+
return _turnStuckMsV2;
|
|
343
|
+
}
|
|
344
|
+
export function setTurnStuckMsV2(n) {
|
|
345
|
+
_turnStuckMsV2 = _posIntSessionV2(n, "turnStuckMs");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function getActiveConvCountV2(userId) {
|
|
349
|
+
let n = 0;
|
|
350
|
+
for (const c of _conversationsV2.values()) {
|
|
351
|
+
if (c.userId === userId && c.status === "active") n += 1;
|
|
352
|
+
}
|
|
353
|
+
return n;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function getPendingTurnCountV2(conversationId) {
|
|
357
|
+
let n = 0;
|
|
358
|
+
for (const t of _turnsV2.values()) {
|
|
359
|
+
if (
|
|
360
|
+
t.conversationId === conversationId &&
|
|
361
|
+
(t.status === "pending" || t.status === "streaming")
|
|
362
|
+
)
|
|
363
|
+
n += 1;
|
|
364
|
+
}
|
|
365
|
+
return n;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function _copyConvV2(c) {
|
|
369
|
+
return { ...c, metadata: { ...c.metadata } };
|
|
370
|
+
}
|
|
371
|
+
function _copyTurnV2(t) {
|
|
372
|
+
return { ...t, metadata: { ...t.metadata } };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function registerConversationV2(
|
|
376
|
+
id,
|
|
377
|
+
{ userId, model, metadata = {}, now = Date.now() } = {},
|
|
378
|
+
) {
|
|
379
|
+
if (!id || typeof id !== "string") throw new Error("id must be a string");
|
|
380
|
+
if (!userId || typeof userId !== "string")
|
|
381
|
+
throw new Error("userId must be a string");
|
|
382
|
+
if (!model || typeof model !== "string")
|
|
383
|
+
throw new Error("model must be a string");
|
|
384
|
+
if (_conversationsV2.has(id))
|
|
385
|
+
throw new Error(`conversation ${id} already exists`);
|
|
386
|
+
const c = {
|
|
387
|
+
id,
|
|
388
|
+
userId,
|
|
389
|
+
model,
|
|
390
|
+
status: "draft",
|
|
391
|
+
createdAt: now,
|
|
392
|
+
lastSeenAt: now,
|
|
393
|
+
activatedAt: null,
|
|
394
|
+
archivedAt: null,
|
|
395
|
+
metadata: { ...metadata },
|
|
396
|
+
};
|
|
397
|
+
_conversationsV2.set(id, c);
|
|
398
|
+
return _copyConvV2(c);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function getConversationV2(id) {
|
|
402
|
+
const c = _conversationsV2.get(id);
|
|
403
|
+
return c ? _copyConvV2(c) : null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function listConversationsV2({ userId, status } = {}) {
|
|
407
|
+
const out = [];
|
|
408
|
+
for (const c of _conversationsV2.values()) {
|
|
409
|
+
if (userId && c.userId !== userId) continue;
|
|
410
|
+
if (status && c.status !== status) continue;
|
|
411
|
+
out.push(_copyConvV2(c));
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function setConversationStatusV2(id, next, { now = Date.now() } = {}) {
|
|
417
|
+
const c = _conversationsV2.get(id);
|
|
418
|
+
if (!c) throw new Error(`conversation ${id} not found`);
|
|
419
|
+
if (!CONV_TRANSITIONS_V2.has(next))
|
|
420
|
+
throw new Error(`unknown conversation status: ${next}`);
|
|
421
|
+
if (CONV_TERMINALS_V2.has(c.status))
|
|
422
|
+
throw new Error(`conversation ${id} is in terminal state ${c.status}`);
|
|
423
|
+
const allowed = CONV_TRANSITIONS_V2.get(c.status);
|
|
424
|
+
if (!allowed.has(next))
|
|
425
|
+
throw new Error(
|
|
426
|
+
`cannot transition conversation from ${c.status} to ${next}`,
|
|
427
|
+
);
|
|
428
|
+
if (next === "active") {
|
|
429
|
+
if (c.status === "draft") {
|
|
430
|
+
const count = getActiveConvCountV2(c.userId);
|
|
431
|
+
if (count >= _maxActiveConvPerUserV2)
|
|
432
|
+
throw new Error(
|
|
433
|
+
`user ${c.userId} already at active-conversation cap (${_maxActiveConvPerUserV2})`,
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (!c.activatedAt) c.activatedAt = now;
|
|
437
|
+
}
|
|
438
|
+
if (next === "archived" && !c.archivedAt) c.archivedAt = now;
|
|
439
|
+
c.status = next;
|
|
440
|
+
c.lastSeenAt = now;
|
|
441
|
+
return _copyConvV2(c);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function activateConversationV2(id, opts) {
|
|
445
|
+
return setConversationStatusV2(id, "active", opts);
|
|
446
|
+
}
|
|
447
|
+
export function pauseConversationV2(id, opts) {
|
|
448
|
+
return setConversationStatusV2(id, "paused", opts);
|
|
449
|
+
}
|
|
450
|
+
export function archiveConversationV2(id, opts) {
|
|
451
|
+
return setConversationStatusV2(id, "archived", opts);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function touchConversationV2(id, { now = Date.now() } = {}) {
|
|
455
|
+
const c = _conversationsV2.get(id);
|
|
456
|
+
if (!c) throw new Error(`conversation ${id} not found`);
|
|
457
|
+
c.lastSeenAt = now;
|
|
458
|
+
return _copyConvV2(c);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function createTurnV2(
|
|
462
|
+
id,
|
|
463
|
+
{ conversationId, role = "user", metadata = {}, now = Date.now() } = {},
|
|
464
|
+
) {
|
|
465
|
+
if (!id || typeof id !== "string") throw new Error("id must be a string");
|
|
466
|
+
if (!conversationId || typeof conversationId !== "string")
|
|
467
|
+
throw new Error("conversationId must be a string");
|
|
468
|
+
if (_turnsV2.has(id)) throw new Error(`turn ${id} already exists`);
|
|
469
|
+
const count = getPendingTurnCountV2(conversationId);
|
|
470
|
+
if (count >= _maxPendingTurnsPerConvV2)
|
|
471
|
+
throw new Error(
|
|
472
|
+
`conversation ${conversationId} already at pending-turn cap (${_maxPendingTurnsPerConvV2})`,
|
|
473
|
+
);
|
|
474
|
+
const t = {
|
|
475
|
+
id,
|
|
476
|
+
conversationId,
|
|
477
|
+
role,
|
|
478
|
+
status: "pending",
|
|
479
|
+
createdAt: now,
|
|
480
|
+
lastSeenAt: now,
|
|
481
|
+
streamingStartedAt: null,
|
|
482
|
+
settledAt: null,
|
|
483
|
+
metadata: { ...metadata },
|
|
484
|
+
};
|
|
485
|
+
_turnsV2.set(id, t);
|
|
486
|
+
return _copyTurnV2(t);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function getTurnV2(id) {
|
|
490
|
+
const t = _turnsV2.get(id);
|
|
491
|
+
return t ? _copyTurnV2(t) : null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function listTurnsV2({ conversationId, status } = {}) {
|
|
495
|
+
const out = [];
|
|
496
|
+
for (const t of _turnsV2.values()) {
|
|
497
|
+
if (conversationId && t.conversationId !== conversationId) continue;
|
|
498
|
+
if (status && t.status !== status) continue;
|
|
499
|
+
out.push(_copyTurnV2(t));
|
|
500
|
+
}
|
|
501
|
+
return out;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function setTurnStatusV2(id, next, { now = Date.now() } = {}) {
|
|
505
|
+
const t = _turnsV2.get(id);
|
|
506
|
+
if (!t) throw new Error(`turn ${id} not found`);
|
|
507
|
+
if (!TURN_TRANSITIONS_V2.has(next))
|
|
508
|
+
throw new Error(`unknown turn status: ${next}`);
|
|
509
|
+
if (TURN_TERMINALS_V2.has(t.status))
|
|
510
|
+
throw new Error(`turn ${id} is in terminal state ${t.status}`);
|
|
511
|
+
const allowed = TURN_TRANSITIONS_V2.get(t.status);
|
|
512
|
+
if (!allowed.has(next))
|
|
513
|
+
throw new Error(`cannot transition turn from ${t.status} to ${next}`);
|
|
514
|
+
if (next === "streaming" && !t.streamingStartedAt) t.streamingStartedAt = now;
|
|
515
|
+
if (TURN_TERMINALS_V2.has(next) && !t.settledAt) t.settledAt = now;
|
|
516
|
+
t.status = next;
|
|
517
|
+
t.lastSeenAt = now;
|
|
518
|
+
return _copyTurnV2(t);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export function streamTurnV2(id, opts) {
|
|
522
|
+
return setTurnStatusV2(id, "streaming", opts);
|
|
523
|
+
}
|
|
524
|
+
export function completeTurnV2(id, opts) {
|
|
525
|
+
return setTurnStatusV2(id, "completed", opts);
|
|
526
|
+
}
|
|
527
|
+
export function failTurnV2(id, opts) {
|
|
528
|
+
return setTurnStatusV2(id, "failed", opts);
|
|
529
|
+
}
|
|
530
|
+
export function cancelTurnV2(id, opts) {
|
|
531
|
+
return setTurnStatusV2(id, "cancelled", opts);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function autoArchiveIdleConversationsV2({ now = Date.now() } = {}) {
|
|
535
|
+
const flipped = [];
|
|
536
|
+
for (const c of _conversationsV2.values()) {
|
|
537
|
+
if (c.status === "archived" || c.status === "draft") continue;
|
|
538
|
+
if (now - c.lastSeenAt > _convIdleMsV2) {
|
|
539
|
+
c.status = "archived";
|
|
540
|
+
c.lastSeenAt = now;
|
|
541
|
+
if (!c.archivedAt) c.archivedAt = now;
|
|
542
|
+
flipped.push(_copyConvV2(c));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return flipped;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function autoFailStuckTurnsV2({ now = Date.now() } = {}) {
|
|
549
|
+
const flipped = [];
|
|
550
|
+
for (const t of _turnsV2.values()) {
|
|
551
|
+
if (t.status !== "streaming") continue;
|
|
552
|
+
const ref = t.streamingStartedAt ?? t.lastSeenAt;
|
|
553
|
+
if (now - ref > _turnStuckMsV2) {
|
|
554
|
+
t.status = "failed";
|
|
555
|
+
t.lastSeenAt = now;
|
|
556
|
+
if (!t.settledAt) t.settledAt = now;
|
|
557
|
+
flipped.push(_copyTurnV2(t));
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return flipped;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function getSessionManagerStatsV2() {
|
|
564
|
+
const conversationsByStatus = {};
|
|
565
|
+
for (const v of Object.values(CONVERSATION_MATURITY_V2))
|
|
566
|
+
conversationsByStatus[v] = 0;
|
|
567
|
+
for (const c of _conversationsV2.values())
|
|
568
|
+
conversationsByStatus[c.status] += 1;
|
|
569
|
+
|
|
570
|
+
const turnsByStatus = {};
|
|
571
|
+
for (const v of Object.values(TURN_LIFECYCLE_V2)) turnsByStatus[v] = 0;
|
|
572
|
+
for (const t of _turnsV2.values()) turnsByStatus[t.status] += 1;
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
totalConversationsV2: _conversationsV2.size,
|
|
576
|
+
totalTurnsV2: _turnsV2.size,
|
|
577
|
+
maxActiveConvPerUser: _maxActiveConvPerUserV2,
|
|
578
|
+
maxPendingTurnsPerConv: _maxPendingTurnsPerConvV2,
|
|
579
|
+
convIdleMs: _convIdleMsV2,
|
|
580
|
+
turnStuckMs: _turnStuckMsV2,
|
|
581
|
+
conversationsByStatus,
|
|
582
|
+
turnsByStatus,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function _resetStateSessionManagerV2() {
|
|
587
|
+
_conversationsV2.clear();
|
|
588
|
+
_turnsV2.clear();
|
|
589
|
+
_maxActiveConvPerUserV2 = SESSION_DEFAULT_MAX_ACTIVE_CONV_PER_USER;
|
|
590
|
+
_maxPendingTurnsPerConvV2 = SESSION_DEFAULT_MAX_PENDING_TURNS_PER_CONV;
|
|
591
|
+
_convIdleMsV2 = SESSION_DEFAULT_CONV_IDLE_MS;
|
|
592
|
+
_turnStuckMsV2 = SESSION_DEFAULT_TURN_STUCK_MS;
|
|
593
|
+
}
|
package/src/lib/siem-exporter.js
CHANGED
|
@@ -135,3 +135,336 @@ export function _resetState() {
|
|
|
135
135
|
_targets.clear();
|
|
136
136
|
_exports.length = 0;
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
// ═══════════════════════════════════════════════════════════════
|
|
140
|
+
// V2 Canonical Surface (Phase 51)
|
|
141
|
+
// ═══════════════════════════════════════════════════════════════
|
|
142
|
+
|
|
143
|
+
export const SIEM_FORMAT = Object.freeze({
|
|
144
|
+
JSON: "json",
|
|
145
|
+
CEF: "cef",
|
|
146
|
+
LEEF: "leef",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
export const SIEM_TARGET_TYPE = Object.freeze({
|
|
150
|
+
SPLUNK_HEC: "splunk_hec",
|
|
151
|
+
ELASTICSEARCH: "elasticsearch",
|
|
152
|
+
AZURE_SENTINEL: "azure_sentinel",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export const SIEM_SEVERITY = Object.freeze({
|
|
156
|
+
DEBUG: "debug",
|
|
157
|
+
INFO: "info",
|
|
158
|
+
WARN: "warn",
|
|
159
|
+
ERROR: "error",
|
|
160
|
+
CRITICAL: "critical",
|
|
161
|
+
FATAL: "fatal",
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
export const SIEM_TARGET_STATUS = Object.freeze({
|
|
165
|
+
ACTIVE: "active",
|
|
166
|
+
PAUSED: "paused",
|
|
167
|
+
DISABLED: "disabled",
|
|
168
|
+
ERROR: "error",
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export const SIEM_DEFAULT_BATCH_SIZE = 100;
|
|
172
|
+
|
|
173
|
+
// Design §二 severity-to-CEF table (0-10 scale, per ArcSight spec).
|
|
174
|
+
const _severityCEF = Object.freeze({
|
|
175
|
+
debug: 1,
|
|
176
|
+
info: 3,
|
|
177
|
+
warn: 5,
|
|
178
|
+
error: 7,
|
|
179
|
+
critical: 9,
|
|
180
|
+
fatal: 10,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const _allowedTargetTransitions = new Map([
|
|
184
|
+
["active", new Set(["paused", "disabled", "error"])],
|
|
185
|
+
["paused", new Set(["active", "disabled", "error"])],
|
|
186
|
+
["disabled", new Set(["active", "error"])],
|
|
187
|
+
["error", new Set(["active", "paused", "disabled"])],
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
function _validateFormat(format) {
|
|
191
|
+
if (!Object.values(SIEM_FORMAT).includes(format)) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Invalid format: ${format}. Expected one of ${Object.values(SIEM_FORMAT).join("|")}`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function _validateTargetType(type) {
|
|
199
|
+
if (!Object.values(SIEM_TARGET_TYPE).includes(type)) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Invalid target type: ${type}. Expected one of ${Object.values(SIEM_TARGET_TYPE).join("|")}`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function _validateSeverity(severity) {
|
|
207
|
+
if (!Object.values(SIEM_SEVERITY).includes(severity)) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Invalid severity: ${severity}. Expected one of ${Object.values(SIEM_SEVERITY).join("|")}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function _validateStatus(status) {
|
|
215
|
+
if (!Object.values(SIEM_TARGET_STATUS).includes(status)) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Invalid status: ${status}. Expected one of ${Object.values(SIEM_TARGET_STATUS).join("|")}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Map a SIEM_SEVERITY value to a CEF severity integer (0-10).
|
|
224
|
+
*/
|
|
225
|
+
export function severityToCEF(severity) {
|
|
226
|
+
const key = (severity || "").toLowerCase();
|
|
227
|
+
const n = _severityCEF[key];
|
|
228
|
+
if (n === undefined) {
|
|
229
|
+
throw new Error(`Unknown severity: ${severity}`);
|
|
230
|
+
}
|
|
231
|
+
return n;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// CEF extension-value escape (| \ = → escaped).
|
|
235
|
+
function _escapeCEFExt(value) {
|
|
236
|
+
return String(value == null ? "" : value)
|
|
237
|
+
.replace(/\\/g, "\\\\")
|
|
238
|
+
.replace(/\|/g, "\\|")
|
|
239
|
+
.replace(/=/g, "\\=")
|
|
240
|
+
.replace(/\r?\n/g, "\\n");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function _flatExtensions(log) {
|
|
244
|
+
const pairs = [];
|
|
245
|
+
const md = log.metadata || {};
|
|
246
|
+
for (const [k, v] of Object.entries(md)) {
|
|
247
|
+
pairs.push(`${k}=${_escapeCEFExt(v)}`);
|
|
248
|
+
}
|
|
249
|
+
if (log.userId != null) pairs.push(`suser=${_escapeCEFExt(log.userId)}`);
|
|
250
|
+
if (log.ip != null) pairs.push(`src=${_escapeCEFExt(log.ip)}`);
|
|
251
|
+
if (log.message != null) pairs.push(`msg=${_escapeCEFExt(log.message)}`);
|
|
252
|
+
if (log.timestamp != null) pairs.push(`rt=${_escapeCEFExt(log.timestamp)}`);
|
|
253
|
+
return pairs;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Format a log record as CEF (ArcSight).
|
|
258
|
+
* CEF:0|Vendor|Product|Version|{eventId}|{eventName}|{severity}|{extensions}
|
|
259
|
+
*/
|
|
260
|
+
export function formatLogCEF(log, opts = {}) {
|
|
261
|
+
const vendor = opts.vendor || "ChainlessChain";
|
|
262
|
+
const product = opts.product || "CLI";
|
|
263
|
+
const version = opts.version || "1.0.0";
|
|
264
|
+
const eventId = log.eventId || log.id || "unknown";
|
|
265
|
+
const eventName = log.eventName || log.message || "event";
|
|
266
|
+
const severity = severityToCEF(log.severity || "info");
|
|
267
|
+
const pipeEscape = (s) => String(s).replace(/\|/g, "\\|");
|
|
268
|
+
const extensions = _flatExtensions(log).join(" ");
|
|
269
|
+
return `CEF:0|${pipeEscape(vendor)}|${pipeEscape(product)}|${pipeEscape(version)}|${pipeEscape(eventId)}|${pipeEscape(eventName)}|${severity}|${extensions}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Format a log record as LEEF 2.0 (IBM QRadar).
|
|
274
|
+
* LEEF:2.0|Vendor|Product|Version|{eventId}|<tab-separated key=value>
|
|
275
|
+
*/
|
|
276
|
+
export function formatLogLEEF(log, opts = {}) {
|
|
277
|
+
const vendor = opts.vendor || "ChainlessChain";
|
|
278
|
+
const product = opts.product || "CLI";
|
|
279
|
+
const version = opts.version || "1.0.0";
|
|
280
|
+
const eventId = log.eventId || log.id || "unknown";
|
|
281
|
+
const pipeEscape = (s) => String(s).replace(/\|/g, "\\|");
|
|
282
|
+
const pairs = [];
|
|
283
|
+
if (log.timestamp != null) pairs.push(`devTime=${log.timestamp}`);
|
|
284
|
+
if (log.userId != null) pairs.push(`usrName=${log.userId}`);
|
|
285
|
+
if (log.action != null) pairs.push(`action=${log.action}`);
|
|
286
|
+
if (log.ip != null) pairs.push(`src=${log.ip}`);
|
|
287
|
+
if (log.message != null) pairs.push(`msg=${log.message}`);
|
|
288
|
+
const md = log.metadata || {};
|
|
289
|
+
for (const [k, v] of Object.entries(md)) {
|
|
290
|
+
pairs.push(`${k}=${v}`);
|
|
291
|
+
}
|
|
292
|
+
return `LEEF:2.0|${pipeEscape(vendor)}|${pipeEscape(product)}|${pipeEscape(version)}|${pipeEscape(eventId)}|${pairs.join("\t")}`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Format a log record as the canonical JSON envelope (Phase 51 §二).
|
|
297
|
+
*/
|
|
298
|
+
export function formatLogJSON(log) {
|
|
299
|
+
return {
|
|
300
|
+
timestamp: log.timestamp || Date.now(),
|
|
301
|
+
severity: (log.severity || "info").toUpperCase(),
|
|
302
|
+
source: log.source || "chainlesschain-cli",
|
|
303
|
+
message: log.message || "",
|
|
304
|
+
metadata: {
|
|
305
|
+
...(log.metadata || {}),
|
|
306
|
+
eventId: log.eventId || log.id || null,
|
|
307
|
+
userId: log.userId || null,
|
|
308
|
+
action: log.action || null,
|
|
309
|
+
resource: log.resource || null,
|
|
310
|
+
ip: log.ip || null,
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Dispatch by format. Returns string for CEF/LEEF, object for JSON.
|
|
317
|
+
*/
|
|
318
|
+
export function formatLog(format, log, opts) {
|
|
319
|
+
_validateFormat(format);
|
|
320
|
+
if (format === SIEM_FORMAT.CEF) return formatLogCEF(log, opts);
|
|
321
|
+
if (format === SIEM_FORMAT.LEEF) return formatLogLEEF(log, opts);
|
|
322
|
+
return formatLogJSON(log);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Add a SIEM target with V2 canonical validation.
|
|
327
|
+
*/
|
|
328
|
+
export function addTargetV2(db, options) {
|
|
329
|
+
if (!options || typeof options !== "object") {
|
|
330
|
+
throw new Error("options object is required");
|
|
331
|
+
}
|
|
332
|
+
const { type, url, format = SIEM_FORMAT.JSON, config = {} } = options;
|
|
333
|
+
_validateTargetType(type);
|
|
334
|
+
if (!url) throw new Error("Target URL is required");
|
|
335
|
+
_validateFormat(format);
|
|
336
|
+
return addTarget(db, type, url, format, config);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Remove a target by id. Throws if unknown.
|
|
341
|
+
*/
|
|
342
|
+
export function removeTarget(db, targetId) {
|
|
343
|
+
const target = _targets.get(targetId);
|
|
344
|
+
if (!target) throw new Error(`Target not found: ${targetId}`);
|
|
345
|
+
_targets.delete(targetId);
|
|
346
|
+
db.prepare(`DELETE FROM siem_exports WHERE id = ?`).run(targetId);
|
|
347
|
+
return { success: true, targetId };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Transition a target's status using the allowed state machine.
|
|
352
|
+
* active ↔ paused/disabled/error; disabled → active/error; error → any.
|
|
353
|
+
*/
|
|
354
|
+
export function setTargetStatus(db, targetId, newStatus) {
|
|
355
|
+
const target = _targets.get(targetId);
|
|
356
|
+
if (!target) throw new Error(`Target not found: ${targetId}`);
|
|
357
|
+
_validateStatus(newStatus);
|
|
358
|
+
if (target.status === newStatus) {
|
|
359
|
+
return { ...target };
|
|
360
|
+
}
|
|
361
|
+
const allowed = _allowedTargetTransitions.get(target.status) || new Set();
|
|
362
|
+
if (!allowed.has(newStatus)) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
`Invalid status transition: ${target.status} → ${newStatus}`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
target.status = newStatus;
|
|
368
|
+
db.prepare(`UPDATE siem_exports SET status = ? WHERE id = ?`).run(
|
|
369
|
+
newStatus,
|
|
370
|
+
targetId,
|
|
371
|
+
);
|
|
372
|
+
return { ...target };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Batch-export logs to a target.
|
|
377
|
+
* - Skips when target.status !== "active" (returns { skipped: true, ... }).
|
|
378
|
+
* - Chunks logs into slices of batchSize (default 100).
|
|
379
|
+
* - Returns { batches, exported, skipped, lastId }.
|
|
380
|
+
*/
|
|
381
|
+
export function exportLogsV2(db, options) {
|
|
382
|
+
if (!options || typeof options !== "object") {
|
|
383
|
+
throw new Error("options object is required");
|
|
384
|
+
}
|
|
385
|
+
const { targetId, logs = [], batchSize = SIEM_DEFAULT_BATCH_SIZE } = options;
|
|
386
|
+
if (!Number.isInteger(batchSize) || batchSize <= 0) {
|
|
387
|
+
throw new Error("batchSize must be a positive integer");
|
|
388
|
+
}
|
|
389
|
+
const target = _targets.get(targetId);
|
|
390
|
+
if (!target) throw new Error(`Target not found: ${targetId}`);
|
|
391
|
+
if (target.status !== SIEM_TARGET_STATUS.ACTIVE) {
|
|
392
|
+
return {
|
|
393
|
+
skipped: true,
|
|
394
|
+
reason: `target status is ${target.status}`,
|
|
395
|
+
batches: 0,
|
|
396
|
+
exported: 0,
|
|
397
|
+
lastId: target.lastExportedLogId,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (!Array.isArray(logs)) {
|
|
401
|
+
throw new Error("logs must be an array");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let batches = 0;
|
|
405
|
+
let exported = 0;
|
|
406
|
+
for (let i = 0; i < logs.length; i += batchSize) {
|
|
407
|
+
const slice = logs.slice(i, i + batchSize);
|
|
408
|
+
exportLogs(db, targetId, slice);
|
|
409
|
+
batches += 1;
|
|
410
|
+
exported += slice.length;
|
|
411
|
+
}
|
|
412
|
+
// Handle zero-length input — register one empty batch for stats parity.
|
|
413
|
+
if (logs.length === 0) {
|
|
414
|
+
exportLogs(db, targetId, []);
|
|
415
|
+
batches = 0; // no real batch was sent
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
skipped: false,
|
|
419
|
+
batches,
|
|
420
|
+
exported,
|
|
421
|
+
lastId: target.lastExportedLogId,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Extended stats with byFormat / byType / byStatus breakdowns + totals.
|
|
427
|
+
*/
|
|
428
|
+
export function getSIEMStatsV2() {
|
|
429
|
+
const targets = [..._targets.values()];
|
|
430
|
+
const byFormat = {};
|
|
431
|
+
for (const v of Object.values(SIEM_FORMAT)) byFormat[v] = 0;
|
|
432
|
+
const byType = {};
|
|
433
|
+
for (const v of Object.values(SIEM_TARGET_TYPE)) byType[v] = 0;
|
|
434
|
+
const byStatus = {};
|
|
435
|
+
for (const v of Object.values(SIEM_TARGET_STATUS)) byStatus[v] = 0;
|
|
436
|
+
let totalExported = 0;
|
|
437
|
+
for (const t of targets) {
|
|
438
|
+
if (byFormat[t.format] !== undefined) byFormat[t.format] += 1;
|
|
439
|
+
if (byType[t.type] !== undefined) byType[t.type] += 1;
|
|
440
|
+
if (byStatus[t.status] !== undefined) byStatus[t.status] += 1;
|
|
441
|
+
totalExported += t.exportedCount || 0;
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
totalTargets: targets.length,
|
|
445
|
+
totalExported,
|
|
446
|
+
totalBatches: _exports.length,
|
|
447
|
+
byFormat,
|
|
448
|
+
byType,
|
|
449
|
+
byStatus,
|
|
450
|
+
targets: targets.map((t) => ({
|
|
451
|
+
id: t.id,
|
|
452
|
+
type: t.type,
|
|
453
|
+
url: t.url,
|
|
454
|
+
format: t.format,
|
|
455
|
+
status: t.status,
|
|
456
|
+
exportedCount: t.exportedCount,
|
|
457
|
+
lastExportAt: t.lastExportAt,
|
|
458
|
+
})),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* List targets filtered by status.
|
|
464
|
+
*/
|
|
465
|
+
export function listTargetsByStatus(status) {
|
|
466
|
+
_validateStatus(status);
|
|
467
|
+
return [..._targets.values()].filter((t) => t.status === status);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export { _severityCEF, _allowedTargetTransitions };
|