chainlesschain 0.81.0 → 0.143.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 +62 -0
- package/src/commands/activitypub.js +61 -0
- package/src/commands/agent-network.js +254 -1
- package/src/commands/agent.js +117 -0
- package/src/commands/audit.js +302 -0
- package/src/commands/automation.js +271 -1
- package/src/commands/bi.js +61 -0
- package/src/commands/bm25.js +78 -0
- package/src/commands/browse.js +64 -0
- package/src/commands/ccron.js +78 -0
- package/src/commands/codegen.js +224 -0
- package/src/commands/collab.js +341 -0
- package/src/commands/compliance.js +1075 -0
- package/src/commands/compt.js +78 -0
- package/src/commands/consol.js +231 -0
- package/src/commands/cowork.js +263 -0
- package/src/commands/crosschain.js +62 -0
- package/src/commands/dao.js +62 -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 +61 -0
- package/src/commands/economy.js +56 -0
- package/src/commands/encrypt.js +341 -0
- package/src/commands/evolution.js +56 -0
- package/src/commands/evomap.js +61 -0
- package/src/commands/export.js +256 -1
- package/src/commands/fflag.js +178 -0
- package/src/commands/fusion.js +258 -0
- package/src/commands/git.js +45 -0
- package/src/commands/governance.js +325 -0
- package/src/commands/hardening.js +411 -0
- package/src/commands/hmemory.js +56 -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 +42 -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/itbudget.js +45 -0
- package/src/commands/kg.js +387 -0
- package/src/commands/llm.js +263 -0
- package/src/commands/lowcode.js +44 -0
- package/src/commands/matrix.js +62 -0
- package/src/commands/mcp.js +221 -0
- package/src/commands/mcpscaf.js +41 -0
- package/src/commands/meminj.js +41 -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/nostr.js +62 -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/orchgov.js +45 -0
- package/src/commands/org.js +277 -0
- package/src/commands/p2p.js +390 -0
- package/src/commands/pdfp.js +78 -0
- package/src/commands/perception.js +290 -0
- package/src/commands/perf.js +39 -0
- package/src/commands/perm.js +45 -0
- package/src/commands/permmem.js +251 -0
- package/src/commands/pipeline.js +57 -1
- package/src/commands/planmode.js +45 -0
- package/src/commands/plugin-ecosystem.js +273 -0
- package/src/commands/pqc.js +393 -0
- package/src/commands/promcomp.js +82 -0
- package/src/commands/quantization.js +351 -0
- package/src/commands/rcache.js +271 -0
- package/src/commands/recommend.js +382 -0
- package/src/commands/runtime.js +307 -0
- package/src/commands/scim.js +262 -0
- package/src/commands/seshhook.js +41 -0
- package/src/commands/seshsearch.js +41 -0
- package/src/commands/seshtail.js +41 -0
- package/src/commands/seshu.js +41 -0
- package/src/commands/session.js +258 -0
- package/src/commands/sganal.js +78 -0
- package/src/commands/siem.js +40 -0
- package/src/commands/skill.js +267 -1
- package/src/commands/slotfill.js +41 -0
- package/src/commands/social.js +290 -0
- package/src/commands/sso.js +186 -1
- package/src/commands/svccont.js +45 -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/tms.js +45 -0
- package/src/commands/tokens.js +269 -0
- package/src/commands/topiccls.js +45 -0
- package/src/commands/trust.js +249 -0
- package/src/commands/uprof.js +45 -0
- package/src/commands/vcheck.js +78 -0
- package/src/commands/wallet.js +277 -0
- package/src/commands/webfetch.js +41 -0
- package/src/commands/workflow.js +171 -0
- package/src/commands/zkp.js +62 -0
- package/src/harness/prompt-compressor.js +331 -0
- package/src/index.js +65 -1
- package/src/lib/a2a-protocol.js +105 -0
- package/src/lib/activitypub-bridge.js +105 -0
- package/src/lib/agent-coordinator.js +325 -0
- package/src/lib/agent-economy.js +105 -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 +105 -0
- package/src/lib/audit-logger.js +379 -0
- package/src/lib/automation-engine.js +330 -0
- package/src/lib/autonomous-agent.js +105 -0
- package/src/lib/autonomous-developer.js +350 -0
- package/src/lib/bi-engine.js +105 -0
- package/src/lib/bm25-search.js +81 -0
- package/src/lib/browser-automation.js +105 -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-framework-reporter.js +105 -0
- package/src/lib/compliance-manager.js +434 -0
- package/src/lib/compression-telemetry.js +81 -0
- package/src/lib/content-recommendation.js +469 -0
- package/src/lib/content-recommender.js +105 -0
- package/src/lib/cowork-cron.js +81 -0
- package/src/lib/cowork-task-runner.js +105 -0
- package/src/lib/cross-chain.js +105 -0
- package/src/lib/crypto-manager.js +350 -0
- package/src/lib/dao-governance.js +105 -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 +105 -0
- package/src/lib/evolution-system.js +105 -0
- package/src/lib/evomap-manager.js +105 -0
- package/src/lib/execution-backend.js +105 -0
- package/src/lib/feature-flags.js +85 -0
- package/src/lib/git-integration.js +105 -0
- package/src/lib/hardening-manager.js +348 -0
- package/src/lib/hierarchical-memory.js +105 -0
- package/src/lib/hook-manager.js +380 -0
- package/src/lib/inference-network.js +105 -0
- package/src/lib/instinct-manager.js +332 -0
- package/src/lib/ipfs-storage.js +334 -0
- package/src/lib/iteration-budget.js +105 -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/matrix-bridge.js +105 -0
- package/src/lib/mcp-registry.js +333 -0
- package/src/lib/mcp-scaffold.js +81 -0
- package/src/lib/memory-injection.js +81 -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/nostr-bridge.js +105 -0
- package/src/lib/note-versioning.js +327 -0
- package/src/lib/orchestrator.js +105 -0
- package/src/lib/org-manager.js +323 -0
- package/src/lib/p2p-manager.js +387 -0
- package/src/lib/pdf-parser.js +81 -0
- package/src/lib/perception.js +346 -0
- package/src/lib/perf-tuning.js +109 -1
- package/src/lib/permanent-memory.js +320 -0
- package/src/lib/permission-engine.js +81 -0
- package/src/lib/pipeline-orchestrator.js +105 -0
- package/src/lib/plan-mode.js +81 -0
- package/src/lib/plugin-ecosystem.js +377 -0
- package/src/lib/pqc-manager.js +368 -0
- package/src/lib/prompt-compressor.js +1 -10
- package/src/lib/protocol-fusion.js +417 -0
- package/src/lib/quantization.js +325 -0
- package/src/lib/response-cache.js +327 -0
- package/src/lib/scim-manager.js +329 -0
- package/src/lib/service-container.js +81 -0
- package/src/lib/session-consolidator.js +105 -0
- package/src/lib/session-hooks.js +81 -0
- package/src/lib/session-manager.js +329 -0
- package/src/lib/session-search.js +81 -0
- package/src/lib/session-tail.js +81 -0
- package/src/lib/session-usage.js +83 -0
- package/src/lib/siem-exporter.js +105 -0
- package/src/lib/skill-loader.js +377 -0
- package/src/lib/slot-filler.js +81 -0
- package/src/lib/social-graph-analytics.js +81 -0
- package/src/lib/social-graph.js +81 -0
- package/src/lib/social-manager.js +326 -0
- package/src/lib/sso-manager.js +332 -0
- package/src/lib/sub-agent-registry.js +110 -0
- package/src/lib/sync-manager.js +326 -0
- package/src/lib/task-model-selector.js +81 -0
- package/src/lib/tech-learning-engine.js +369 -0
- package/src/lib/tenant-saas.js +460 -0
- package/src/lib/threat-intel.js +335 -0
- package/src/lib/todo-manager.js +105 -0
- package/src/lib/token-incentive.js +293 -0
- package/src/lib/token-tracker.js +329 -0
- package/src/lib/topic-classifier.js +105 -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/user-profile.js +81 -0
- package/src/lib/version-checker.js +81 -0
- package/src/lib/wallet-manager.js +326 -0
- package/src/lib/web-fetch.js +81 -0
- package/src/lib/workflow-engine.js +322 -0
- package/src/lib/zkp-engine.js +105 -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
|
+
}
|
|
@@ -191,3 +191,84 @@ export class SessionSearchIndex {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
+
|
|
195
|
+
// ===== V2 Surface: Session Search governance overlay (CLI v0.142.0) =====
|
|
196
|
+
export const SSCH_PROFILE_MATURITY_V2 = Object.freeze({
|
|
197
|
+
PENDING: "pending", ACTIVE: "active", STALE: "stale", ARCHIVED: "archived",
|
|
198
|
+
});
|
|
199
|
+
export const SSCH_QUERY_LIFECYCLE_V2 = Object.freeze({
|
|
200
|
+
QUEUED: "queued", SEARCHING: "searching", COMPLETED: "completed", FAILED: "failed", CANCELLED: "cancelled",
|
|
201
|
+
});
|
|
202
|
+
const _sschPTrans = new Map([
|
|
203
|
+
[SSCH_PROFILE_MATURITY_V2.PENDING, new Set([SSCH_PROFILE_MATURITY_V2.ACTIVE, SSCH_PROFILE_MATURITY_V2.ARCHIVED])],
|
|
204
|
+
[SSCH_PROFILE_MATURITY_V2.ACTIVE, new Set([SSCH_PROFILE_MATURITY_V2.STALE, SSCH_PROFILE_MATURITY_V2.ARCHIVED])],
|
|
205
|
+
[SSCH_PROFILE_MATURITY_V2.STALE, new Set([SSCH_PROFILE_MATURITY_V2.ACTIVE, SSCH_PROFILE_MATURITY_V2.ARCHIVED])],
|
|
206
|
+
[SSCH_PROFILE_MATURITY_V2.ARCHIVED, new Set()],
|
|
207
|
+
]);
|
|
208
|
+
const _sschPTerminal = new Set([SSCH_PROFILE_MATURITY_V2.ARCHIVED]);
|
|
209
|
+
const _sschQTrans = new Map([
|
|
210
|
+
[SSCH_QUERY_LIFECYCLE_V2.QUEUED, new Set([SSCH_QUERY_LIFECYCLE_V2.SEARCHING, SSCH_QUERY_LIFECYCLE_V2.CANCELLED])],
|
|
211
|
+
[SSCH_QUERY_LIFECYCLE_V2.SEARCHING, new Set([SSCH_QUERY_LIFECYCLE_V2.COMPLETED, SSCH_QUERY_LIFECYCLE_V2.FAILED, SSCH_QUERY_LIFECYCLE_V2.CANCELLED])],
|
|
212
|
+
[SSCH_QUERY_LIFECYCLE_V2.COMPLETED, new Set()],
|
|
213
|
+
[SSCH_QUERY_LIFECYCLE_V2.FAILED, new Set()],
|
|
214
|
+
[SSCH_QUERY_LIFECYCLE_V2.CANCELLED, new Set()],
|
|
215
|
+
]);
|
|
216
|
+
const _sschPsV2 = new Map();
|
|
217
|
+
const _sschQsV2 = new Map();
|
|
218
|
+
let _sschMaxActive = 8, _sschMaxPending = 20, _sschIdleMs = 30 * 24 * 60 * 60 * 1000, _sschStuckMs = 30 * 1000;
|
|
219
|
+
function _sschPos(n, label) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${label} must be positive integer`); return v; }
|
|
220
|
+
function _sschCheckP(from, to) { const a = _sschPTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid ssch profile transition ${from} → ${to}`); }
|
|
221
|
+
function _sschCheckQ(from, to) { const a = _sschQTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid ssch query transition ${from} → ${to}`); }
|
|
222
|
+
export function setMaxActiveSschProfilesPerOwnerV2(n) { _sschMaxActive = _sschPos(n, "maxActiveSschProfilesPerOwner"); }
|
|
223
|
+
export function getMaxActiveSschProfilesPerOwnerV2() { return _sschMaxActive; }
|
|
224
|
+
export function setMaxPendingSschQueriesPerProfileV2(n) { _sschMaxPending = _sschPos(n, "maxPendingSschQueriesPerProfile"); }
|
|
225
|
+
export function getMaxPendingSschQueriesPerProfileV2() { return _sschMaxPending; }
|
|
226
|
+
export function setSschProfileIdleMsV2(n) { _sschIdleMs = _sschPos(n, "sschProfileIdleMs"); }
|
|
227
|
+
export function getSschProfileIdleMsV2() { return _sschIdleMs; }
|
|
228
|
+
export function setSschQueryStuckMsV2(n) { _sschStuckMs = _sschPos(n, "sschQueryStuckMs"); }
|
|
229
|
+
export function getSschQueryStuckMsV2() { return _sschStuckMs; }
|
|
230
|
+
export function _resetStateSessionSearchV2() { _sschPsV2.clear(); _sschQsV2.clear(); _sschMaxActive = 8; _sschMaxPending = 20; _sschIdleMs = 30 * 24 * 60 * 60 * 1000; _sschStuckMs = 30 * 1000; }
|
|
231
|
+
export function registerSschProfileV2({ id, owner, scope, metadata } = {}) {
|
|
232
|
+
if (!id) throw new Error("ssch profile id required"); if (!owner) throw new Error("ssch profile owner required");
|
|
233
|
+
if (_sschPsV2.has(id)) throw new Error(`ssch profile ${id} already registered`);
|
|
234
|
+
const now = Date.now();
|
|
235
|
+
const p = { id, owner, scope: scope || "all", status: SSCH_PROFILE_MATURITY_V2.PENDING, createdAt: now, updatedAt: now, activatedAt: null, archivedAt: null, lastTouchedAt: now, metadata: { ...(metadata || {}) } };
|
|
236
|
+
_sschPsV2.set(id, p); return { ...p, metadata: { ...p.metadata } };
|
|
237
|
+
}
|
|
238
|
+
function _sschCountActive(owner) { let n = 0; for (const p of _sschPsV2.values()) if (p.owner === owner && p.status === SSCH_PROFILE_MATURITY_V2.ACTIVE) n++; return n; }
|
|
239
|
+
export function activateSschProfileV2(id) {
|
|
240
|
+
const p = _sschPsV2.get(id); if (!p) throw new Error(`ssch profile ${id} not found`);
|
|
241
|
+
_sschCheckP(p.status, SSCH_PROFILE_MATURITY_V2.ACTIVE);
|
|
242
|
+
const recovery = p.status === SSCH_PROFILE_MATURITY_V2.STALE;
|
|
243
|
+
if (!recovery && _sschCountActive(p.owner) >= _sschMaxActive) throw new Error(`max active ssch profiles for owner ${p.owner} reached`);
|
|
244
|
+
const now = Date.now(); p.status = SSCH_PROFILE_MATURITY_V2.ACTIVE; p.updatedAt = now; p.lastTouchedAt = now; if (!p.activatedAt) p.activatedAt = now;
|
|
245
|
+
return { ...p, metadata: { ...p.metadata } };
|
|
246
|
+
}
|
|
247
|
+
export function staleSschProfileV2(id) { const p = _sschPsV2.get(id); if (!p) throw new Error(`ssch profile ${id} not found`); _sschCheckP(p.status, SSCH_PROFILE_MATURITY_V2.STALE); p.status = SSCH_PROFILE_MATURITY_V2.STALE; p.updatedAt = Date.now(); return { ...p, metadata: { ...p.metadata } }; }
|
|
248
|
+
export function archiveSschProfileV2(id) { const p = _sschPsV2.get(id); if (!p) throw new Error(`ssch profile ${id} not found`); _sschCheckP(p.status, SSCH_PROFILE_MATURITY_V2.ARCHIVED); const now = Date.now(); p.status = SSCH_PROFILE_MATURITY_V2.ARCHIVED; p.updatedAt = now; if (!p.archivedAt) p.archivedAt = now; return { ...p, metadata: { ...p.metadata } }; }
|
|
249
|
+
export function touchSschProfileV2(id) { const p = _sschPsV2.get(id); if (!p) throw new Error(`ssch profile ${id} not found`); if (_sschPTerminal.has(p.status)) throw new Error(`cannot touch terminal ssch profile ${id}`); const now = Date.now(); p.lastTouchedAt = now; p.updatedAt = now; return { ...p, metadata: { ...p.metadata } }; }
|
|
250
|
+
export function getSschProfileV2(id) { const p = _sschPsV2.get(id); if (!p) return null; return { ...p, metadata: { ...p.metadata } }; }
|
|
251
|
+
export function listSschProfilesV2() { return [..._sschPsV2.values()].map((p) => ({ ...p, metadata: { ...p.metadata } })); }
|
|
252
|
+
function _sschCountPending(profileId) { let n = 0; for (const q of _sschQsV2.values()) if (q.profileId === profileId && (q.status === SSCH_QUERY_LIFECYCLE_V2.QUEUED || q.status === SSCH_QUERY_LIFECYCLE_V2.SEARCHING)) n++; return n; }
|
|
253
|
+
export function createSschQueryV2({ id, profileId, q, metadata } = {}) {
|
|
254
|
+
if (!id) throw new Error("ssch query id required"); if (!profileId) throw new Error("ssch query profileId required");
|
|
255
|
+
if (_sschQsV2.has(id)) throw new Error(`ssch query ${id} already exists`);
|
|
256
|
+
if (!_sschPsV2.has(profileId)) throw new Error(`ssch profile ${profileId} not found`);
|
|
257
|
+
if (_sschCountPending(profileId) >= _sschMaxPending) throw new Error(`max pending ssch queries for profile ${profileId} reached`);
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
const r = { id, profileId, q: q || "", status: SSCH_QUERY_LIFECYCLE_V2.QUEUED, createdAt: now, updatedAt: now, startedAt: null, settledAt: null, metadata: { ...(metadata || {}) } };
|
|
260
|
+
_sschQsV2.set(id, r); return { ...r, metadata: { ...r.metadata } };
|
|
261
|
+
}
|
|
262
|
+
export function searchingSschQueryV2(id) { const r = _sschQsV2.get(id); if (!r) throw new Error(`ssch query ${id} not found`); _sschCheckQ(r.status, SSCH_QUERY_LIFECYCLE_V2.SEARCHING); const now = Date.now(); r.status = SSCH_QUERY_LIFECYCLE_V2.SEARCHING; r.updatedAt = now; if (!r.startedAt) r.startedAt = now; return { ...r, metadata: { ...r.metadata } }; }
|
|
263
|
+
export function completeSschQueryV2(id) { const r = _sschQsV2.get(id); if (!r) throw new Error(`ssch query ${id} not found`); _sschCheckQ(r.status, SSCH_QUERY_LIFECYCLE_V2.COMPLETED); const now = Date.now(); r.status = SSCH_QUERY_LIFECYCLE_V2.COMPLETED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; return { ...r, metadata: { ...r.metadata } }; }
|
|
264
|
+
export function failSschQueryV2(id, reason) { const r = _sschQsV2.get(id); if (!r) throw new Error(`ssch query ${id} not found`); _sschCheckQ(r.status, SSCH_QUERY_LIFECYCLE_V2.FAILED); const now = Date.now(); r.status = SSCH_QUERY_LIFECYCLE_V2.FAILED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; if (reason) r.metadata.failReason = String(reason); return { ...r, metadata: { ...r.metadata } }; }
|
|
265
|
+
export function cancelSschQueryV2(id, reason) { const r = _sschQsV2.get(id); if (!r) throw new Error(`ssch query ${id} not found`); _sschCheckQ(r.status, SSCH_QUERY_LIFECYCLE_V2.CANCELLED); const now = Date.now(); r.status = SSCH_QUERY_LIFECYCLE_V2.CANCELLED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; if (reason) r.metadata.cancelReason = String(reason); return { ...r, metadata: { ...r.metadata } }; }
|
|
266
|
+
export function getSschQueryV2(id) { const r = _sschQsV2.get(id); if (!r) return null; return { ...r, metadata: { ...r.metadata } }; }
|
|
267
|
+
export function listSschQueriesV2() { return [..._sschQsV2.values()].map((r) => ({ ...r, metadata: { ...r.metadata } })); }
|
|
268
|
+
export function autoStaleIdleSschProfilesV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const p of _sschPsV2.values()) if (p.status === SSCH_PROFILE_MATURITY_V2.ACTIVE && (t - p.lastTouchedAt) >= _sschIdleMs) { p.status = SSCH_PROFILE_MATURITY_V2.STALE; p.updatedAt = t; flipped.push(p.id); } return { flipped, count: flipped.length }; }
|
|
269
|
+
export function autoFailStuckSschQueriesV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const r of _sschQsV2.values()) if (r.status === SSCH_QUERY_LIFECYCLE_V2.SEARCHING && r.startedAt != null && (t - r.startedAt) >= _sschStuckMs) { r.status = SSCH_QUERY_LIFECYCLE_V2.FAILED; r.updatedAt = t; if (!r.settledAt) r.settledAt = t; r.metadata.failReason = "auto-fail-stuck"; flipped.push(r.id); } return { flipped, count: flipped.length }; }
|
|
270
|
+
export function getSessionSearchGovStatsV2() {
|
|
271
|
+
const profilesByStatus = {}; for (const v of Object.values(SSCH_PROFILE_MATURITY_V2)) profilesByStatus[v] = 0; for (const p of _sschPsV2.values()) profilesByStatus[p.status]++;
|
|
272
|
+
const queriesByStatus = {}; for (const v of Object.values(SSCH_QUERY_LIFECYCLE_V2)) queriesByStatus[v] = 0; for (const r of _sschQsV2.values()) queriesByStatus[r.status]++;
|
|
273
|
+
return { totalSschProfilesV2: _sschPsV2.size, totalSschQueriesV2: _sschQsV2.size, maxActiveSschProfilesPerOwner: _sschMaxActive, maxPendingSschQueriesPerProfile: _sschMaxPending, sschProfileIdleMs: _sschIdleMs, sschQueryStuckMs: _sschStuckMs, profilesByStatus, queriesByStatus };
|
|
274
|
+
}
|
package/src/lib/session-tail.js
CHANGED
|
@@ -126,3 +126,84 @@ export async function* followSession(sessionId, options = {}) {
|
|
|
126
126
|
await new Promise((r) => setTimeout(r, pollMs));
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
// ===== V2 Surface: Session Tail governance overlay (CLI v0.142.0) =====
|
|
131
|
+
export const STAIL_SUB_MATURITY_V2 = Object.freeze({
|
|
132
|
+
PENDING: "pending", ACTIVE: "active", PAUSED: "paused", CLOSED: "closed",
|
|
133
|
+
});
|
|
134
|
+
export const STAIL_EVENT_LIFECYCLE_V2 = Object.freeze({
|
|
135
|
+
QUEUED: "queued", TAILING: "tailing", COMPLETED: "completed", FAILED: "failed", CANCELLED: "cancelled",
|
|
136
|
+
});
|
|
137
|
+
const _stailSTrans = new Map([
|
|
138
|
+
[STAIL_SUB_MATURITY_V2.PENDING, new Set([STAIL_SUB_MATURITY_V2.ACTIVE, STAIL_SUB_MATURITY_V2.CLOSED])],
|
|
139
|
+
[STAIL_SUB_MATURITY_V2.ACTIVE, new Set([STAIL_SUB_MATURITY_V2.PAUSED, STAIL_SUB_MATURITY_V2.CLOSED])],
|
|
140
|
+
[STAIL_SUB_MATURITY_V2.PAUSED, new Set([STAIL_SUB_MATURITY_V2.ACTIVE, STAIL_SUB_MATURITY_V2.CLOSED])],
|
|
141
|
+
[STAIL_SUB_MATURITY_V2.CLOSED, new Set()],
|
|
142
|
+
]);
|
|
143
|
+
const _stailSTerminal = new Set([STAIL_SUB_MATURITY_V2.CLOSED]);
|
|
144
|
+
const _stailETrans = new Map([
|
|
145
|
+
[STAIL_EVENT_LIFECYCLE_V2.QUEUED, new Set([STAIL_EVENT_LIFECYCLE_V2.TAILING, STAIL_EVENT_LIFECYCLE_V2.CANCELLED])],
|
|
146
|
+
[STAIL_EVENT_LIFECYCLE_V2.TAILING, new Set([STAIL_EVENT_LIFECYCLE_V2.COMPLETED, STAIL_EVENT_LIFECYCLE_V2.FAILED, STAIL_EVENT_LIFECYCLE_V2.CANCELLED])],
|
|
147
|
+
[STAIL_EVENT_LIFECYCLE_V2.COMPLETED, new Set()],
|
|
148
|
+
[STAIL_EVENT_LIFECYCLE_V2.FAILED, new Set()],
|
|
149
|
+
[STAIL_EVENT_LIFECYCLE_V2.CANCELLED, new Set()],
|
|
150
|
+
]);
|
|
151
|
+
const _stailSsV2 = new Map();
|
|
152
|
+
const _stailEsV2 = new Map();
|
|
153
|
+
let _stailMaxActive = 10, _stailMaxPending = 30, _stailIdleMs = 24 * 60 * 60 * 1000, _stailStuckMs = 60 * 1000;
|
|
154
|
+
function _stailPos(n, label) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${label} must be positive integer`); return v; }
|
|
155
|
+
function _stailCheckS(from, to) { const a = _stailSTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid stail subscription transition ${from} → ${to}`); }
|
|
156
|
+
function _stailCheckE(from, to) { const a = _stailETrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid stail event transition ${from} → ${to}`); }
|
|
157
|
+
export function setMaxActiveStailSubsPerOwnerV2(n) { _stailMaxActive = _stailPos(n, "maxActiveStailSubsPerOwner"); }
|
|
158
|
+
export function getMaxActiveStailSubsPerOwnerV2() { return _stailMaxActive; }
|
|
159
|
+
export function setMaxPendingStailEventsPerSubV2(n) { _stailMaxPending = _stailPos(n, "maxPendingStailEventsPerSub"); }
|
|
160
|
+
export function getMaxPendingStailEventsPerSubV2() { return _stailMaxPending; }
|
|
161
|
+
export function setStailSubIdleMsV2(n) { _stailIdleMs = _stailPos(n, "stailSubIdleMs"); }
|
|
162
|
+
export function getStailSubIdleMsV2() { return _stailIdleMs; }
|
|
163
|
+
export function setStailEventStuckMsV2(n) { _stailStuckMs = _stailPos(n, "stailEventStuckMs"); }
|
|
164
|
+
export function getStailEventStuckMsV2() { return _stailStuckMs; }
|
|
165
|
+
export function _resetStateSessionTailV2() { _stailSsV2.clear(); _stailEsV2.clear(); _stailMaxActive = 10; _stailMaxPending = 30; _stailIdleMs = 24 * 60 * 60 * 1000; _stailStuckMs = 60 * 1000; }
|
|
166
|
+
export function registerStailSubV2({ id, owner, sessionId, metadata } = {}) {
|
|
167
|
+
if (!id) throw new Error("stail sub id required"); if (!owner) throw new Error("stail sub owner required");
|
|
168
|
+
if (_stailSsV2.has(id)) throw new Error(`stail sub ${id} already registered`);
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
const s = { id, owner, sessionId: sessionId || "*", status: STAIL_SUB_MATURITY_V2.PENDING, createdAt: now, updatedAt: now, activatedAt: null, closedAt: null, lastTouchedAt: now, metadata: { ...(metadata || {}) } };
|
|
171
|
+
_stailSsV2.set(id, s); return { ...s, metadata: { ...s.metadata } };
|
|
172
|
+
}
|
|
173
|
+
function _stailCountActive(owner) { let n = 0; for (const s of _stailSsV2.values()) if (s.owner === owner && s.status === STAIL_SUB_MATURITY_V2.ACTIVE) n++; return n; }
|
|
174
|
+
export function activateStailSubV2(id) {
|
|
175
|
+
const s = _stailSsV2.get(id); if (!s) throw new Error(`stail sub ${id} not found`);
|
|
176
|
+
_stailCheckS(s.status, STAIL_SUB_MATURITY_V2.ACTIVE);
|
|
177
|
+
const recovery = s.status === STAIL_SUB_MATURITY_V2.PAUSED;
|
|
178
|
+
if (!recovery && _stailCountActive(s.owner) >= _stailMaxActive) throw new Error(`max active stail subs for owner ${s.owner} reached`);
|
|
179
|
+
const now = Date.now(); s.status = STAIL_SUB_MATURITY_V2.ACTIVE; s.updatedAt = now; s.lastTouchedAt = now; if (!s.activatedAt) s.activatedAt = now;
|
|
180
|
+
return { ...s, metadata: { ...s.metadata } };
|
|
181
|
+
}
|
|
182
|
+
export function pauseStailSubV2(id) { const s = _stailSsV2.get(id); if (!s) throw new Error(`stail sub ${id} not found`); _stailCheckS(s.status, STAIL_SUB_MATURITY_V2.PAUSED); s.status = STAIL_SUB_MATURITY_V2.PAUSED; s.updatedAt = Date.now(); return { ...s, metadata: { ...s.metadata } }; }
|
|
183
|
+
export function closeStailSubV2(id) { const s = _stailSsV2.get(id); if (!s) throw new Error(`stail sub ${id} not found`); _stailCheckS(s.status, STAIL_SUB_MATURITY_V2.CLOSED); const now = Date.now(); s.status = STAIL_SUB_MATURITY_V2.CLOSED; s.updatedAt = now; if (!s.closedAt) s.closedAt = now; return { ...s, metadata: { ...s.metadata } }; }
|
|
184
|
+
export function touchStailSubV2(id) { const s = _stailSsV2.get(id); if (!s) throw new Error(`stail sub ${id} not found`); if (_stailSTerminal.has(s.status)) throw new Error(`cannot touch terminal stail sub ${id}`); const now = Date.now(); s.lastTouchedAt = now; s.updatedAt = now; return { ...s, metadata: { ...s.metadata } }; }
|
|
185
|
+
export function getStailSubV2(id) { const s = _stailSsV2.get(id); if (!s) return null; return { ...s, metadata: { ...s.metadata } }; }
|
|
186
|
+
export function listStailSubsV2() { return [..._stailSsV2.values()].map((s) => ({ ...s, metadata: { ...s.metadata } })); }
|
|
187
|
+
function _stailCountPending(subId) { let n = 0; for (const e of _stailEsV2.values()) if (e.subId === subId && (e.status === STAIL_EVENT_LIFECYCLE_V2.QUEUED || e.status === STAIL_EVENT_LIFECYCLE_V2.TAILING)) n++; return n; }
|
|
188
|
+
export function createStailEventV2({ id, subId, cursor, metadata } = {}) {
|
|
189
|
+
if (!id) throw new Error("stail event id required"); if (!subId) throw new Error("stail event subId required");
|
|
190
|
+
if (_stailEsV2.has(id)) throw new Error(`stail event ${id} already exists`);
|
|
191
|
+
if (!_stailSsV2.has(subId)) throw new Error(`stail sub ${subId} not found`);
|
|
192
|
+
if (_stailCountPending(subId) >= _stailMaxPending) throw new Error(`max pending stail events for sub ${subId} reached`);
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
const e = { id, subId, cursor: cursor || "0", status: STAIL_EVENT_LIFECYCLE_V2.QUEUED, createdAt: now, updatedAt: now, startedAt: null, settledAt: null, metadata: { ...(metadata || {}) } };
|
|
195
|
+
_stailEsV2.set(id, e); return { ...e, metadata: { ...e.metadata } };
|
|
196
|
+
}
|
|
197
|
+
export function tailingStailEventV2(id) { const e = _stailEsV2.get(id); if (!e) throw new Error(`stail event ${id} not found`); _stailCheckE(e.status, STAIL_EVENT_LIFECYCLE_V2.TAILING); const now = Date.now(); e.status = STAIL_EVENT_LIFECYCLE_V2.TAILING; e.updatedAt = now; if (!e.startedAt) e.startedAt = now; return { ...e, metadata: { ...e.metadata } }; }
|
|
198
|
+
export function completeStailEventV2(id) { const e = _stailEsV2.get(id); if (!e) throw new Error(`stail event ${id} not found`); _stailCheckE(e.status, STAIL_EVENT_LIFECYCLE_V2.COMPLETED); const now = Date.now(); e.status = STAIL_EVENT_LIFECYCLE_V2.COMPLETED; e.updatedAt = now; if (!e.settledAt) e.settledAt = now; return { ...e, metadata: { ...e.metadata } }; }
|
|
199
|
+
export function failStailEventV2(id, reason) { const e = _stailEsV2.get(id); if (!e) throw new Error(`stail event ${id} not found`); _stailCheckE(e.status, STAIL_EVENT_LIFECYCLE_V2.FAILED); const now = Date.now(); e.status = STAIL_EVENT_LIFECYCLE_V2.FAILED; e.updatedAt = now; if (!e.settledAt) e.settledAt = now; if (reason) e.metadata.failReason = String(reason); return { ...e, metadata: { ...e.metadata } }; }
|
|
200
|
+
export function cancelStailEventV2(id, reason) { const e = _stailEsV2.get(id); if (!e) throw new Error(`stail event ${id} not found`); _stailCheckE(e.status, STAIL_EVENT_LIFECYCLE_V2.CANCELLED); const now = Date.now(); e.status = STAIL_EVENT_LIFECYCLE_V2.CANCELLED; e.updatedAt = now; if (!e.settledAt) e.settledAt = now; if (reason) e.metadata.cancelReason = String(reason); return { ...e, metadata: { ...e.metadata } }; }
|
|
201
|
+
export function getStailEventV2(id) { const e = _stailEsV2.get(id); if (!e) return null; return { ...e, metadata: { ...e.metadata } }; }
|
|
202
|
+
export function listStailEventsV2() { return [..._stailEsV2.values()].map((e) => ({ ...e, metadata: { ...e.metadata } })); }
|
|
203
|
+
export function autoPauseIdleStailSubsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const s of _stailSsV2.values()) if (s.status === STAIL_SUB_MATURITY_V2.ACTIVE && (t - s.lastTouchedAt) >= _stailIdleMs) { s.status = STAIL_SUB_MATURITY_V2.PAUSED; s.updatedAt = t; flipped.push(s.id); } return { flipped, count: flipped.length }; }
|
|
204
|
+
export function autoFailStuckStailEventsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const e of _stailEsV2.values()) if (e.status === STAIL_EVENT_LIFECYCLE_V2.TAILING && e.startedAt != null && (t - e.startedAt) >= _stailStuckMs) { e.status = STAIL_EVENT_LIFECYCLE_V2.FAILED; e.updatedAt = t; if (!e.settledAt) e.settledAt = t; e.metadata.failReason = "auto-fail-stuck"; flipped.push(e.id); } return { flipped, count: flipped.length }; }
|
|
205
|
+
export function getSessionTailGovStatsV2() {
|
|
206
|
+
const subsByStatus = {}; for (const v of Object.values(STAIL_SUB_MATURITY_V2)) subsByStatus[v] = 0; for (const s of _stailSsV2.values()) subsByStatus[s.status]++;
|
|
207
|
+
const eventsByStatus = {}; for (const v of Object.values(STAIL_EVENT_LIFECYCLE_V2)) eventsByStatus[v] = 0; for (const e of _stailEsV2.values()) eventsByStatus[e.status]++;
|
|
208
|
+
return { totalStailSubsV2: _stailSsV2.size, totalStailEventsV2: _stailEsV2.size, maxActiveStailSubsPerOwner: _stailMaxActive, maxPendingStailEventsPerSub: _stailMaxPending, stailSubIdleMs: _stailIdleMs, stailEventStuckMs: _stailStuckMs, subsByStatus, eventsByStatus };
|
|
209
|
+
}
|
package/src/lib/session-usage.js
CHANGED
|
@@ -164,3 +164,86 @@ export function allSessionsUsage({ limit = 1000 } = {}) {
|
|
|
164
164
|
),
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
|
+
|
|
168
|
+
// ===== V2 Surface: Session Usage governance overlay (CLI v0.142.0) =====
|
|
169
|
+
export const SUSE_BUDGET_MATURITY_V2 = Object.freeze({
|
|
170
|
+
PENDING: "pending", ACTIVE: "active", EXHAUSTED: "exhausted", ARCHIVED: "archived",
|
|
171
|
+
});
|
|
172
|
+
export const SUSE_RECORD_LIFECYCLE_V2 = Object.freeze({
|
|
173
|
+
QUEUED: "queued", RECORDING: "recording", RECORDED: "recorded", REJECTED: "rejected", CANCELLED: "cancelled",
|
|
174
|
+
});
|
|
175
|
+
const _suseBTrans = new Map([
|
|
176
|
+
[SUSE_BUDGET_MATURITY_V2.PENDING, new Set([SUSE_BUDGET_MATURITY_V2.ACTIVE, SUSE_BUDGET_MATURITY_V2.ARCHIVED])],
|
|
177
|
+
[SUSE_BUDGET_MATURITY_V2.ACTIVE, new Set([SUSE_BUDGET_MATURITY_V2.EXHAUSTED, SUSE_BUDGET_MATURITY_V2.ARCHIVED])],
|
|
178
|
+
[SUSE_BUDGET_MATURITY_V2.EXHAUSTED, new Set([SUSE_BUDGET_MATURITY_V2.ACTIVE, SUSE_BUDGET_MATURITY_V2.ARCHIVED])],
|
|
179
|
+
[SUSE_BUDGET_MATURITY_V2.ARCHIVED, new Set()],
|
|
180
|
+
]);
|
|
181
|
+
const _suseBTerminal = new Set([SUSE_BUDGET_MATURITY_V2.ARCHIVED]);
|
|
182
|
+
const _suseRTrans = new Map([
|
|
183
|
+
[SUSE_RECORD_LIFECYCLE_V2.QUEUED, new Set([SUSE_RECORD_LIFECYCLE_V2.RECORDING, SUSE_RECORD_LIFECYCLE_V2.CANCELLED])],
|
|
184
|
+
[SUSE_RECORD_LIFECYCLE_V2.RECORDING, new Set([SUSE_RECORD_LIFECYCLE_V2.RECORDED, SUSE_RECORD_LIFECYCLE_V2.REJECTED, SUSE_RECORD_LIFECYCLE_V2.CANCELLED])],
|
|
185
|
+
[SUSE_RECORD_LIFECYCLE_V2.RECORDED, new Set()],
|
|
186
|
+
[SUSE_RECORD_LIFECYCLE_V2.REJECTED, new Set()],
|
|
187
|
+
[SUSE_RECORD_LIFECYCLE_V2.CANCELLED, new Set()],
|
|
188
|
+
]);
|
|
189
|
+
const _suseBsV2 = new Map();
|
|
190
|
+
const _suseRsV2 = new Map();
|
|
191
|
+
let _suseMaxActive = 5, _suseMaxPending = 50, _suseIdleMs = 30 * 24 * 60 * 60 * 1000, _suseStuckMs = 30 * 1000;
|
|
192
|
+
function _susePos(n, label) { const v = Math.floor(Number(n)); if (!Number.isFinite(v) || v <= 0) throw new Error(`${label} must be positive integer`); return v; }
|
|
193
|
+
function _suseCheckB(from, to) { const a = _suseBTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid suse budget transition ${from} → ${to}`); }
|
|
194
|
+
function _suseCheckR(from, to) { const a = _suseRTrans.get(from); if (!a || !a.has(to)) throw new Error(`invalid suse record transition ${from} → ${to}`); }
|
|
195
|
+
export function setMaxActiveSuseBudgetsPerOwnerV2(n) { _suseMaxActive = _susePos(n, "maxActiveSuseBudgetsPerOwner"); }
|
|
196
|
+
export function getMaxActiveSuseBudgetsPerOwnerV2() { return _suseMaxActive; }
|
|
197
|
+
export function setMaxPendingSuseRecordsPerBudgetV2(n) { _suseMaxPending = _susePos(n, "maxPendingSuseRecordsPerBudget"); }
|
|
198
|
+
export function getMaxPendingSuseRecordsPerBudgetV2() { return _suseMaxPending; }
|
|
199
|
+
export function setSuseBudgetIdleMsV2(n) { _suseIdleMs = _susePos(n, "suseBudgetIdleMs"); }
|
|
200
|
+
export function getSuseBudgetIdleMsV2() { return _suseIdleMs; }
|
|
201
|
+
export function setSuseRecordStuckMsV2(n) { _suseStuckMs = _susePos(n, "suseRecordStuckMs"); }
|
|
202
|
+
export function getSuseRecordStuckMsV2() { return _suseStuckMs; }
|
|
203
|
+
export function _resetStateSessionUsageV2() { _suseBsV2.clear(); _suseRsV2.clear(); _suseMaxActive = 5; _suseMaxPending = 50; _suseIdleMs = 30 * 24 * 60 * 60 * 1000; _suseStuckMs = 30 * 1000; }
|
|
204
|
+
export function registerSuseBudgetV2({ id, owner, limit, metadata } = {}) {
|
|
205
|
+
if (!id) throw new Error("suse budget id required"); if (!owner) throw new Error("suse budget owner required");
|
|
206
|
+
if (_suseBsV2.has(id)) throw new Error(`suse budget ${id} already registered`);
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
const lim = limit == null ? 1000 : Math.max(1, Math.floor(Number(limit)) || 1);
|
|
209
|
+
const b = { id, owner, limit: lim, status: SUSE_BUDGET_MATURITY_V2.PENDING, createdAt: now, updatedAt: now, activatedAt: null, archivedAt: null, lastTouchedAt: now, metadata: { ...(metadata || {}) } };
|
|
210
|
+
_suseBsV2.set(id, b); return { ...b, metadata: { ...b.metadata } };
|
|
211
|
+
}
|
|
212
|
+
function _suseCountActive(owner) { let n = 0; for (const b of _suseBsV2.values()) if (b.owner === owner && b.status === SUSE_BUDGET_MATURITY_V2.ACTIVE) n++; return n; }
|
|
213
|
+
export function activateSuseBudgetV2(id) {
|
|
214
|
+
const b = _suseBsV2.get(id); if (!b) throw new Error(`suse budget ${id} not found`);
|
|
215
|
+
_suseCheckB(b.status, SUSE_BUDGET_MATURITY_V2.ACTIVE);
|
|
216
|
+
const recovery = b.status === SUSE_BUDGET_MATURITY_V2.EXHAUSTED;
|
|
217
|
+
if (!recovery && _suseCountActive(b.owner) >= _suseMaxActive) throw new Error(`max active suse budgets for owner ${b.owner} reached`);
|
|
218
|
+
const now = Date.now(); b.status = SUSE_BUDGET_MATURITY_V2.ACTIVE; b.updatedAt = now; b.lastTouchedAt = now; if (!b.activatedAt) b.activatedAt = now;
|
|
219
|
+
return { ...b, metadata: { ...b.metadata } };
|
|
220
|
+
}
|
|
221
|
+
export function exhaustSuseBudgetV2(id) { const b = _suseBsV2.get(id); if (!b) throw new Error(`suse budget ${id} not found`); _suseCheckB(b.status, SUSE_BUDGET_MATURITY_V2.EXHAUSTED); b.status = SUSE_BUDGET_MATURITY_V2.EXHAUSTED; b.updatedAt = Date.now(); return { ...b, metadata: { ...b.metadata } }; }
|
|
222
|
+
export function archiveSuseBudgetV2(id) { const b = _suseBsV2.get(id); if (!b) throw new Error(`suse budget ${id} not found`); _suseCheckB(b.status, SUSE_BUDGET_MATURITY_V2.ARCHIVED); const now = Date.now(); b.status = SUSE_BUDGET_MATURITY_V2.ARCHIVED; b.updatedAt = now; if (!b.archivedAt) b.archivedAt = now; return { ...b, metadata: { ...b.metadata } }; }
|
|
223
|
+
export function touchSuseBudgetV2(id) { const b = _suseBsV2.get(id); if (!b) throw new Error(`suse budget ${id} not found`); if (_suseBTerminal.has(b.status)) throw new Error(`cannot touch terminal suse budget ${id}`); const now = Date.now(); b.lastTouchedAt = now; b.updatedAt = now; return { ...b, metadata: { ...b.metadata } }; }
|
|
224
|
+
export function getSuseBudgetV2(id) { const b = _suseBsV2.get(id); if (!b) return null; return { ...b, metadata: { ...b.metadata } }; }
|
|
225
|
+
export function listSuseBudgetsV2() { return [..._suseBsV2.values()].map((b) => ({ ...b, metadata: { ...b.metadata } })); }
|
|
226
|
+
function _suseCountPending(budgetId) { let n = 0; for (const r of _suseRsV2.values()) if (r.budgetId === budgetId && (r.status === SUSE_RECORD_LIFECYCLE_V2.QUEUED || r.status === SUSE_RECORD_LIFECYCLE_V2.RECORDING)) n++; return n; }
|
|
227
|
+
export function createSuseRecordV2({ id, budgetId, amount, metadata } = {}) {
|
|
228
|
+
if (!id) throw new Error("suse record id required"); if (!budgetId) throw new Error("suse record budgetId required");
|
|
229
|
+
if (_suseRsV2.has(id)) throw new Error(`suse record ${id} already exists`);
|
|
230
|
+
if (!_suseBsV2.has(budgetId)) throw new Error(`suse budget ${budgetId} not found`);
|
|
231
|
+
if (_suseCountPending(budgetId) >= _suseMaxPending) throw new Error(`max pending suse records for budget ${budgetId} reached`);
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
const amt = amount == null ? 0 : Math.max(0, Number(amount) || 0);
|
|
234
|
+
const r = { id, budgetId, amount: amt, status: SUSE_RECORD_LIFECYCLE_V2.QUEUED, createdAt: now, updatedAt: now, startedAt: null, settledAt: null, metadata: { ...(metadata || {}) } };
|
|
235
|
+
_suseRsV2.set(id, r); return { ...r, metadata: { ...r.metadata } };
|
|
236
|
+
}
|
|
237
|
+
export function recordingSuseRecordV2(id) { const r = _suseRsV2.get(id); if (!r) throw new Error(`suse record ${id} not found`); _suseCheckR(r.status, SUSE_RECORD_LIFECYCLE_V2.RECORDING); const now = Date.now(); r.status = SUSE_RECORD_LIFECYCLE_V2.RECORDING; r.updatedAt = now; if (!r.startedAt) r.startedAt = now; return { ...r, metadata: { ...r.metadata } }; }
|
|
238
|
+
export function recordSuseRecordV2(id) { const r = _suseRsV2.get(id); if (!r) throw new Error(`suse record ${id} not found`); _suseCheckR(r.status, SUSE_RECORD_LIFECYCLE_V2.RECORDED); const now = Date.now(); r.status = SUSE_RECORD_LIFECYCLE_V2.RECORDED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; return { ...r, metadata: { ...r.metadata } }; }
|
|
239
|
+
export function rejectSuseRecordV2(id, reason) { const r = _suseRsV2.get(id); if (!r) throw new Error(`suse record ${id} not found`); _suseCheckR(r.status, SUSE_RECORD_LIFECYCLE_V2.REJECTED); const now = Date.now(); r.status = SUSE_RECORD_LIFECYCLE_V2.REJECTED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; if (reason) r.metadata.rejectReason = String(reason); return { ...r, metadata: { ...r.metadata } }; }
|
|
240
|
+
export function cancelSuseRecordV2(id, reason) { const r = _suseRsV2.get(id); if (!r) throw new Error(`suse record ${id} not found`); _suseCheckR(r.status, SUSE_RECORD_LIFECYCLE_V2.CANCELLED); const now = Date.now(); r.status = SUSE_RECORD_LIFECYCLE_V2.CANCELLED; r.updatedAt = now; if (!r.settledAt) r.settledAt = now; if (reason) r.metadata.cancelReason = String(reason); return { ...r, metadata: { ...r.metadata } }; }
|
|
241
|
+
export function getSuseRecordV2(id) { const r = _suseRsV2.get(id); if (!r) return null; return { ...r, metadata: { ...r.metadata } }; }
|
|
242
|
+
export function listSuseRecordsV2() { return [..._suseRsV2.values()].map((r) => ({ ...r, metadata: { ...r.metadata } })); }
|
|
243
|
+
export function autoExhaustIdleSuseBudgetsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const b of _suseBsV2.values()) if (b.status === SUSE_BUDGET_MATURITY_V2.ACTIVE && (t - b.lastTouchedAt) >= _suseIdleMs) { b.status = SUSE_BUDGET_MATURITY_V2.EXHAUSTED; b.updatedAt = t; flipped.push(b.id); } return { flipped, count: flipped.length }; }
|
|
244
|
+
export function autoRejectStuckSuseRecordsV2({ now } = {}) { const t = now ?? Date.now(); const flipped = []; for (const r of _suseRsV2.values()) if (r.status === SUSE_RECORD_LIFECYCLE_V2.RECORDING && r.startedAt != null && (t - r.startedAt) >= _suseStuckMs) { r.status = SUSE_RECORD_LIFECYCLE_V2.REJECTED; r.updatedAt = t; if (!r.settledAt) r.settledAt = t; r.metadata.rejectReason = "auto-reject-stuck"; flipped.push(r.id); } return { flipped, count: flipped.length }; }
|
|
245
|
+
export function getSessionUsageGovStatsV2() {
|
|
246
|
+
const budgetsByStatus = {}; for (const v of Object.values(SUSE_BUDGET_MATURITY_V2)) budgetsByStatus[v] = 0; for (const b of _suseBsV2.values()) budgetsByStatus[b.status]++;
|
|
247
|
+
const recordsByStatus = {}; for (const v of Object.values(SUSE_RECORD_LIFECYCLE_V2)) recordsByStatus[v] = 0; for (const r of _suseRsV2.values()) recordsByStatus[r.status]++;
|
|
248
|
+
return { totalSuseBudgetsV2: _suseBsV2.size, totalSuseRecordsV2: _suseRsV2.size, maxActiveSuseBudgetsPerOwner: _suseMaxActive, maxPendingSuseRecordsPerBudget: _suseMaxPending, suseBudgetIdleMs: _suseIdleMs, suseRecordStuckMs: _suseStuckMs, budgetsByStatus, recordsByStatus };
|
|
249
|
+
}
|