omnius 1.0.667 → 1.0.668
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/dist/index.js +3076 -491
- package/dist/library.d.ts +7 -0
- package/dist/library.js +1981 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/library.js
CHANGED
|
@@ -288,6 +288,722 @@ async function getServiceVersion(baseUrl, options = {}) {
|
|
|
288
288
|
};
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
// packages/cli/src/tui/telegram-context-packet-v2.ts
|
|
292
|
+
import { createHash } from "node:crypto";
|
|
293
|
+
var TELEGRAM_CONTEXT_PACKET_SCHEMA_VERSION = 2;
|
|
294
|
+
var TelegramContextPacketValidationError = class extends Error {
|
|
295
|
+
constructor(message) {
|
|
296
|
+
super(message);
|
|
297
|
+
this.name = "TelegramContextPacketValidationError";
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
var TelegramContextPacketRevisionConflictError = class extends Error {
|
|
301
|
+
constructor(semanticId, revision) {
|
|
302
|
+
super(
|
|
303
|
+
`Conflicting Telegram context blocks use semanticId=${JSON.stringify(semanticId)} revision=${revision}`
|
|
304
|
+
);
|
|
305
|
+
this.name = "TelegramContextPacketRevisionConflictError";
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var TelegramContextPacketBudgetError = class extends Error {
|
|
309
|
+
segment;
|
|
310
|
+
budgetChars;
|
|
311
|
+
requiredChars;
|
|
312
|
+
constructor(segment, budgetChars, requiredChars) {
|
|
313
|
+
super(
|
|
314
|
+
`Telegram ${segment} context budget ${budgetChars} cannot fit the mandatory packet envelope and omission receipt (${requiredChars} chars required)`
|
|
315
|
+
);
|
|
316
|
+
this.name = "TelegramContextPacketBudgetError";
|
|
317
|
+
this.segment = segment;
|
|
318
|
+
this.budgetChars = budgetChars;
|
|
319
|
+
this.requiredChars = requiredChars;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$/;
|
|
323
|
+
function assertId(value, field) {
|
|
324
|
+
if (!ID_PATTERN.test(value)) {
|
|
325
|
+
throw new TelegramContextPacketValidationError(
|
|
326
|
+
`${field} must match ${ID_PATTERN.source}`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function normalizedInteger(value, fallback, field) {
|
|
331
|
+
const resolved = value ?? fallback;
|
|
332
|
+
if (!Number.isSafeInteger(resolved)) {
|
|
333
|
+
throw new TelegramContextPacketValidationError(
|
|
334
|
+
`${field} must be a safe integer`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
return resolved;
|
|
338
|
+
}
|
|
339
|
+
function normalizedBudget(value, field) {
|
|
340
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
341
|
+
throw new TelegramContextPacketValidationError(
|
|
342
|
+
`${field} must be a non-negative safe integer`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
function compareText(left, right) {
|
|
348
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
349
|
+
}
|
|
350
|
+
function compareBlocksForRender(left, right) {
|
|
351
|
+
return left.order - right.order || right.priority - left.priority || compareText(left.semanticId, right.semanticId);
|
|
352
|
+
}
|
|
353
|
+
function compareRecordsForRender(left, right) {
|
|
354
|
+
return left.blockOrder - right.blockOrder || right.blockPriority - left.blockPriority || compareText(left.semanticId, right.semanticId) || left.recordOrder - right.recordOrder || right.recordPriority - left.recordPriority || compareText(left.recordId, right.recordId);
|
|
355
|
+
}
|
|
356
|
+
function compareRecordsForAdmission(left, right) {
|
|
357
|
+
return right.blockPriority - left.blockPriority || right.recordPriority - left.recordPriority || left.blockOrder - right.blockOrder || left.recordOrder - right.recordOrder || compareText(left.semanticId, right.semanticId) || compareText(left.recordId, right.recordId);
|
|
358
|
+
}
|
|
359
|
+
function canonicalBlock(block) {
|
|
360
|
+
const records = [...block.records].map((record) => ({
|
|
361
|
+
recordId: record.recordId,
|
|
362
|
+
content: record.content,
|
|
363
|
+
order: record.order ?? 0,
|
|
364
|
+
priority: record.priority ?? 0
|
|
365
|
+
})).sort(
|
|
366
|
+
(left, right) => left.order - right.order || right.priority - left.priority || compareText(left.recordId, right.recordId)
|
|
367
|
+
);
|
|
368
|
+
return JSON.stringify({
|
|
369
|
+
semanticId: block.semanticId,
|
|
370
|
+
revision: block.revision,
|
|
371
|
+
stability: block.stability,
|
|
372
|
+
order: block.order ?? 0,
|
|
373
|
+
priority: block.priority ?? 0,
|
|
374
|
+
records
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
function normalizeBlock(block) {
|
|
378
|
+
assertId(block.semanticId, "semanticId");
|
|
379
|
+
const revision = normalizedInteger(block.revision, 0, "revision");
|
|
380
|
+
if (revision < 0) {
|
|
381
|
+
throw new TelegramContextPacketValidationError(
|
|
382
|
+
"revision must be non-negative"
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (block.stability !== "stable" && block.stability !== "dynamic") {
|
|
386
|
+
throw new TelegramContextPacketValidationError(
|
|
387
|
+
"stability must be stable or dynamic"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const order = normalizedInteger(block.order, 0, "block order");
|
|
391
|
+
const priority = normalizedInteger(block.priority, 0, "block priority");
|
|
392
|
+
const recordIds = /* @__PURE__ */ new Set();
|
|
393
|
+
const records = block.records.map((record) => {
|
|
394
|
+
assertId(record.recordId, "recordId");
|
|
395
|
+
if (recordIds.has(record.recordId)) {
|
|
396
|
+
throw new TelegramContextPacketValidationError(
|
|
397
|
+
`Duplicate recordId=${JSON.stringify(record.recordId)} in semanticId=${JSON.stringify(block.semanticId)}`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
recordIds.add(record.recordId);
|
|
401
|
+
if (typeof record.content !== "string") {
|
|
402
|
+
throw new TelegramContextPacketValidationError(
|
|
403
|
+
`content for recordId=${JSON.stringify(record.recordId)} must be a string`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
semanticId: block.semanticId,
|
|
408
|
+
revision,
|
|
409
|
+
stability: block.stability,
|
|
410
|
+
blockOrder: order,
|
|
411
|
+
blockPriority: priority,
|
|
412
|
+
recordId: record.recordId,
|
|
413
|
+
content: record.content,
|
|
414
|
+
recordOrder: normalizedInteger(record.order, 0, "record order"),
|
|
415
|
+
recordPriority: normalizedInteger(record.priority, 0, "record priority")
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
records.sort(compareRecordsForRender);
|
|
419
|
+
return {
|
|
420
|
+
semanticId: block.semanticId,
|
|
421
|
+
revision,
|
|
422
|
+
stability: block.stability,
|
|
423
|
+
order,
|
|
424
|
+
priority,
|
|
425
|
+
records
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function selectBlockRevisions(blocks) {
|
|
429
|
+
const selected = /* @__PURE__ */ new Map();
|
|
430
|
+
for (const block of blocks) {
|
|
431
|
+
const normalized2 = normalizeBlock(block);
|
|
432
|
+
const source = {
|
|
433
|
+
semanticId: normalized2.semanticId,
|
|
434
|
+
revision: normalized2.revision,
|
|
435
|
+
stability: normalized2.stability,
|
|
436
|
+
order: normalized2.order,
|
|
437
|
+
priority: normalized2.priority,
|
|
438
|
+
records: normalized2.records.map((record) => ({
|
|
439
|
+
recordId: record.recordId,
|
|
440
|
+
content: record.content,
|
|
441
|
+
order: record.recordOrder,
|
|
442
|
+
priority: record.recordPriority
|
|
443
|
+
}))
|
|
444
|
+
};
|
|
445
|
+
const canonical = canonicalBlock(source);
|
|
446
|
+
const current = selected.get(source.semanticId);
|
|
447
|
+
if (!current || source.revision > current.source.revision) {
|
|
448
|
+
selected.set(source.semanticId, { source, canonical });
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (source.revision < current.source.revision) continue;
|
|
452
|
+
if (canonical !== current.canonical) {
|
|
453
|
+
throw new TelegramContextPacketRevisionConflictError(
|
|
454
|
+
source.semanticId,
|
|
455
|
+
source.revision
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return [...selected.values()].map(({ source }) => normalizeBlock(source)).sort(compareBlocksForRender);
|
|
460
|
+
}
|
|
461
|
+
function segmentHeader(segment) {
|
|
462
|
+
return JSON.stringify({
|
|
463
|
+
type: "telegram_context_packet_v2",
|
|
464
|
+
schemaVersion: TELEGRAM_CONTEXT_PACKET_SCHEMA_VERSION,
|
|
465
|
+
segment
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
function segmentFooter(segment) {
|
|
469
|
+
return JSON.stringify({
|
|
470
|
+
type: "telegram_context_packet_end_v2",
|
|
471
|
+
schemaVersion: TELEGRAM_CONTEXT_PACKET_SCHEMA_VERSION,
|
|
472
|
+
segment
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function serializeRecord(record) {
|
|
476
|
+
return JSON.stringify({
|
|
477
|
+
type: "telegram_context_record_v2",
|
|
478
|
+
schemaVersion: TELEGRAM_CONTEXT_PACKET_SCHEMA_VERSION,
|
|
479
|
+
semanticId: record.semanticId,
|
|
480
|
+
revision: record.revision,
|
|
481
|
+
recordId: record.recordId,
|
|
482
|
+
content: record.content
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function omittedRecordRef(record) {
|
|
486
|
+
return {
|
|
487
|
+
semanticId: record.semanticId,
|
|
488
|
+
revision: record.revision,
|
|
489
|
+
recordId: record.recordId
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function buildOmissionReceipt(segment, omitted) {
|
|
493
|
+
if (omitted.length === 0) return void 0;
|
|
494
|
+
const omittedRecords = [...omitted].sort(compareRecordsForRender).map(omittedRecordRef);
|
|
495
|
+
const omittedSemanticIds = [
|
|
496
|
+
...new Set(omittedRecords.map((record) => record.semanticId))
|
|
497
|
+
].sort(compareText);
|
|
498
|
+
return {
|
|
499
|
+
type: "telegram_context_omission_receipt_v2",
|
|
500
|
+
schemaVersion: TELEGRAM_CONTEXT_PACKET_SCHEMA_VERSION,
|
|
501
|
+
segment,
|
|
502
|
+
reason: "char_budget",
|
|
503
|
+
omittedBlockCount: omittedSemanticIds.length,
|
|
504
|
+
omittedRecordCount: omittedRecords.length,
|
|
505
|
+
omittedDigest: createHash("sha256").update(JSON.stringify(omittedRecords)).digest("hex"),
|
|
506
|
+
omittedSemanticIds,
|
|
507
|
+
omittedRecords
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function serializeOmissionReceipt(receipt) {
|
|
511
|
+
return JSON.stringify({
|
|
512
|
+
type: receipt.type,
|
|
513
|
+
schemaVersion: receipt.schemaVersion,
|
|
514
|
+
segment: receipt.segment,
|
|
515
|
+
reason: receipt.reason,
|
|
516
|
+
omittedBlockCount: receipt.omittedBlockCount,
|
|
517
|
+
omittedRecordCount: receipt.omittedRecordCount,
|
|
518
|
+
omittedDigest: receipt.omittedDigest
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
function renderSegmentText(segment, included, omitted) {
|
|
522
|
+
const receipt = buildOmissionReceipt(segment, omitted);
|
|
523
|
+
const lines = [
|
|
524
|
+
segmentHeader(segment),
|
|
525
|
+
...[...included].sort(compareRecordsForRender).map(serializeRecord),
|
|
526
|
+
...receipt ? [serializeOmissionReceipt(receipt)] : [],
|
|
527
|
+
segmentFooter(segment)
|
|
528
|
+
];
|
|
529
|
+
return {
|
|
530
|
+
text: lines.join("\n"),
|
|
531
|
+
...receipt ? { receipt } : {}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function compileSegment(segment, blocks, budgetChars) {
|
|
535
|
+
const records = blocks.flatMap((block) => block.records);
|
|
536
|
+
const admissionOrder = [...records].sort(compareRecordsForAdmission);
|
|
537
|
+
const included = /* @__PURE__ */ new Set();
|
|
538
|
+
const empty = renderSegmentText(segment, [], admissionOrder);
|
|
539
|
+
if (empty.text.length > budgetChars) {
|
|
540
|
+
throw new TelegramContextPacketBudgetError(
|
|
541
|
+
segment,
|
|
542
|
+
budgetChars,
|
|
543
|
+
empty.text.length
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
for (const candidate of admissionOrder) {
|
|
547
|
+
const tentativeIncluded = [...included, candidate];
|
|
548
|
+
const tentativeOmitted = admissionOrder.filter(
|
|
549
|
+
(record) => !included.has(record) && record !== candidate
|
|
550
|
+
);
|
|
551
|
+
const tentative = renderSegmentText(
|
|
552
|
+
segment,
|
|
553
|
+
tentativeIncluded,
|
|
554
|
+
tentativeOmitted
|
|
555
|
+
);
|
|
556
|
+
if (tentative.text.length <= budgetChars) included.add(candidate);
|
|
557
|
+
}
|
|
558
|
+
const includedRecords = records.filter((record) => included.has(record));
|
|
559
|
+
const omittedRecords = records.filter((record) => !included.has(record));
|
|
560
|
+
const rendered = renderSegmentText(segment, includedRecords, omittedRecords);
|
|
561
|
+
const compiledBlocks = blocks.flatMap((block) => {
|
|
562
|
+
const admitted = block.records.filter((record) => included.has(record));
|
|
563
|
+
if (admitted.length === 0) return [];
|
|
564
|
+
return [
|
|
565
|
+
{
|
|
566
|
+
semanticId: block.semanticId,
|
|
567
|
+
revision: block.revision,
|
|
568
|
+
stability: block.stability,
|
|
569
|
+
order: block.order,
|
|
570
|
+
priority: block.priority,
|
|
571
|
+
records: admitted.sort(compareRecordsForRender).map((record) => ({
|
|
572
|
+
semanticId: record.semanticId,
|
|
573
|
+
revision: record.revision,
|
|
574
|
+
recordId: record.recordId,
|
|
575
|
+
content: record.content,
|
|
576
|
+
order: record.recordOrder,
|
|
577
|
+
priority: record.recordPriority,
|
|
578
|
+
serializedChars: serializeRecord(record).length
|
|
579
|
+
}))
|
|
580
|
+
}
|
|
581
|
+
];
|
|
582
|
+
});
|
|
583
|
+
return {
|
|
584
|
+
segment,
|
|
585
|
+
text: rendered.text,
|
|
586
|
+
charLength: rendered.text.length,
|
|
587
|
+
budgetChars,
|
|
588
|
+
blocks: compiledBlocks,
|
|
589
|
+
...rendered.receipt ? { omissionReceipt: rendered.receipt } : {}
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function compileTelegramContextPacketV2(input) {
|
|
593
|
+
const stableChars = normalizedBudget(
|
|
594
|
+
input.budgets.stableChars,
|
|
595
|
+
"stableChars"
|
|
596
|
+
);
|
|
597
|
+
const dynamicChars = normalizedBudget(
|
|
598
|
+
input.budgets.dynamicChars,
|
|
599
|
+
"dynamicChars"
|
|
600
|
+
);
|
|
601
|
+
const selected = selectBlockRevisions(input.blocks);
|
|
602
|
+
const stable = compileSegment(
|
|
603
|
+
"stable",
|
|
604
|
+
selected.filter((block) => block.stability === "stable"),
|
|
605
|
+
stableChars
|
|
606
|
+
);
|
|
607
|
+
const dynamic = compileSegment(
|
|
608
|
+
"dynamic",
|
|
609
|
+
selected.filter((block) => block.stability === "dynamic"),
|
|
610
|
+
dynamicChars
|
|
611
|
+
);
|
|
612
|
+
const selectedRevisions = Object.fromEntries(
|
|
613
|
+
[...selected].sort((left, right) => compareText(left.semanticId, right.semanticId)).map((block) => [block.semanticId, block.revision])
|
|
614
|
+
);
|
|
615
|
+
const combinedContext = `${stable.text}
|
|
616
|
+
${dynamic.text}`;
|
|
617
|
+
return {
|
|
618
|
+
schemaVersion: TELEGRAM_CONTEXT_PACKET_SCHEMA_VERSION,
|
|
619
|
+
stablePrefix: stable.text,
|
|
620
|
+
stablePrefixHash: createHash("sha256").update(stable.text).digest("hex"),
|
|
621
|
+
dynamicContext: dynamic.text,
|
|
622
|
+
combinedContext,
|
|
623
|
+
selectedRevisions,
|
|
624
|
+
stable,
|
|
625
|
+
dynamic
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// packages/cli/src/tui/telegram-cognition-queue.ts
|
|
630
|
+
var TELEGRAM_COGNITION_AUTHORITY = "internal_cognition_only";
|
|
631
|
+
var TelegramCognitionQueueValidationError = class extends Error {
|
|
632
|
+
constructor(message) {
|
|
633
|
+
super(message);
|
|
634
|
+
this.name = "TelegramCognitionQueueValidationError";
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
var JOB_KINDS = /* @__PURE__ */ new Set([
|
|
638
|
+
"social_state",
|
|
639
|
+
"memory",
|
|
640
|
+
"reflection",
|
|
641
|
+
"cognition_batch"
|
|
642
|
+
]);
|
|
643
|
+
function positiveSafeInteger(value, field) {
|
|
644
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
645
|
+
throw new TelegramCognitionQueueValidationError(
|
|
646
|
+
`${field} must be a positive safe integer`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
return value;
|
|
650
|
+
}
|
|
651
|
+
function boundedIdentity(value, field) {
|
|
652
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 300) {
|
|
653
|
+
throw new TelegramCognitionQueueValidationError(
|
|
654
|
+
`${field} must contain 1 to 300 characters`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
return value;
|
|
658
|
+
}
|
|
659
|
+
function errorMessage(error) {
|
|
660
|
+
return error instanceof Error ? error.message : String(error);
|
|
661
|
+
}
|
|
662
|
+
function deferredOutcome() {
|
|
663
|
+
let resolve2;
|
|
664
|
+
const promise = new Promise((done) => {
|
|
665
|
+
resolve2 = done;
|
|
666
|
+
});
|
|
667
|
+
return { promise, resolve: resolve2 };
|
|
668
|
+
}
|
|
669
|
+
var TelegramCognitionQueue = class {
|
|
670
|
+
maxConcurrentScopes;
|
|
671
|
+
maxQueuedJobs;
|
|
672
|
+
defaultTimeBudgetMs;
|
|
673
|
+
pending = [];
|
|
674
|
+
running = /* @__PURE__ */ new Map();
|
|
675
|
+
activeScopes = /* @__PURE__ */ new Set();
|
|
676
|
+
dedupe = /* @__PURE__ */ new Map();
|
|
677
|
+
jobsById = /* @__PURE__ */ new Map();
|
|
678
|
+
idleWaiters = /* @__PURE__ */ new Set();
|
|
679
|
+
totals = {
|
|
680
|
+
accepted: 0,
|
|
681
|
+
deduplicated: 0,
|
|
682
|
+
rejectedCapacity: 0,
|
|
683
|
+
rejectedClosed: 0,
|
|
684
|
+
started: 0,
|
|
685
|
+
succeeded: 0,
|
|
686
|
+
failed: 0,
|
|
687
|
+
timedOut: 0,
|
|
688
|
+
cancelled: 0
|
|
689
|
+
};
|
|
690
|
+
state = "open";
|
|
691
|
+
pumpScheduled = false;
|
|
692
|
+
nextJobSequence = 1;
|
|
693
|
+
constructor(options) {
|
|
694
|
+
this.maxConcurrentScopes = positiveSafeInteger(
|
|
695
|
+
options.maxConcurrentScopes,
|
|
696
|
+
"maxConcurrentScopes"
|
|
697
|
+
);
|
|
698
|
+
this.maxQueuedJobs = positiveSafeInteger(
|
|
699
|
+
options.maxQueuedJobs,
|
|
700
|
+
"maxQueuedJobs"
|
|
701
|
+
);
|
|
702
|
+
this.defaultTimeBudgetMs = positiveSafeInteger(
|
|
703
|
+
options.defaultTimeBudgetMs,
|
|
704
|
+
"defaultTimeBudgetMs"
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
enqueue(job) {
|
|
708
|
+
const scopeKey = boundedIdentity(job.scopeKey, "scopeKey");
|
|
709
|
+
const dedupeKey = boundedIdentity(job.dedupeKey, "dedupeKey");
|
|
710
|
+
if (!JOB_KINDS.has(job.kind)) {
|
|
711
|
+
throw new TelegramCognitionQueueValidationError(
|
|
712
|
+
`Unsupported cognition job kind: ${String(job.kind)}`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
if (typeof job.execute !== "function") {
|
|
716
|
+
throw new TelegramCognitionQueueValidationError(
|
|
717
|
+
"execute must be a function"
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
const timeBudgetMs = positiveSafeInteger(
|
|
721
|
+
job.timeBudgetMs ?? this.defaultTimeBudgetMs,
|
|
722
|
+
"timeBudgetMs"
|
|
723
|
+
);
|
|
724
|
+
const dedupeIdentity = `${scopeKey}\0${dedupeKey}`;
|
|
725
|
+
const existing = this.dedupe.get(dedupeIdentity);
|
|
726
|
+
if (existing) {
|
|
727
|
+
this.totals.deduplicated++;
|
|
728
|
+
return {
|
|
729
|
+
admission: "deduplicated",
|
|
730
|
+
jobId: existing.jobId,
|
|
731
|
+
scopeKey,
|
|
732
|
+
dedupeKey,
|
|
733
|
+
completion: existing.completion
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
const jobId = boundedIdentity(
|
|
737
|
+
job.jobId ?? `tg-cognition-${this.nextJobSequence++}`,
|
|
738
|
+
"jobId"
|
|
739
|
+
);
|
|
740
|
+
if (this.jobsById.has(jobId)) {
|
|
741
|
+
throw new TelegramCognitionQueueValidationError(
|
|
742
|
+
`jobId is already active: ${jobId}`
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
if (this.state !== "open") {
|
|
746
|
+
this.totals.rejectedClosed++;
|
|
747
|
+
return this.rejectedReceipt(
|
|
748
|
+
"rejected_closed",
|
|
749
|
+
jobId,
|
|
750
|
+
scopeKey,
|
|
751
|
+
dedupeKey,
|
|
752
|
+
job.kind,
|
|
753
|
+
"Telegram cognition queue is closing or closed"
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
this.pump();
|
|
757
|
+
const canStartNow = this.running.size < this.maxConcurrentScopes && !this.activeScopes.has(scopeKey);
|
|
758
|
+
if (!canStartNow && this.pending.length >= this.maxQueuedJobs) {
|
|
759
|
+
this.totals.rejectedCapacity++;
|
|
760
|
+
return this.rejectedReceipt(
|
|
761
|
+
"rejected_capacity",
|
|
762
|
+
jobId,
|
|
763
|
+
scopeKey,
|
|
764
|
+
dedupeKey,
|
|
765
|
+
job.kind,
|
|
766
|
+
"Telegram cognition queue capacity reached"
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
const completion = deferredOutcome();
|
|
770
|
+
const accepted = {
|
|
771
|
+
jobId,
|
|
772
|
+
scopeKey,
|
|
773
|
+
dedupeKey,
|
|
774
|
+
dedupeIdentity,
|
|
775
|
+
kind: job.kind,
|
|
776
|
+
timeBudgetMs,
|
|
777
|
+
execute: job.execute,
|
|
778
|
+
enqueuedAtMs: Date.now(),
|
|
779
|
+
completion: completion.promise,
|
|
780
|
+
resolve: completion.resolve
|
|
781
|
+
};
|
|
782
|
+
this.dedupe.set(dedupeIdentity, accepted);
|
|
783
|
+
this.jobsById.set(jobId, accepted);
|
|
784
|
+
this.totals.accepted++;
|
|
785
|
+
if (canStartNow) this.start(accepted);
|
|
786
|
+
else this.pending.push(accepted);
|
|
787
|
+
return {
|
|
788
|
+
admission: "accepted",
|
|
789
|
+
jobId,
|
|
790
|
+
scopeKey,
|
|
791
|
+
dedupeKey,
|
|
792
|
+
completion: completion.promise
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
cancel(jobId, reason = "cancelled") {
|
|
796
|
+
const job = this.jobsById.get(jobId);
|
|
797
|
+
if (!job) return false;
|
|
798
|
+
const pendingIndex = this.pending.indexOf(job);
|
|
799
|
+
if (pendingIndex >= 0) {
|
|
800
|
+
this.pending.splice(pendingIndex, 1);
|
|
801
|
+
this.finish(job, "cancelled", reason);
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
if (job.controller && !job.controller.signal.aborted) {
|
|
805
|
+
job.forcedStatus = "cancelled";
|
|
806
|
+
job.forcedReason = reason;
|
|
807
|
+
job.controller.abort(new Error(reason));
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
cancelScope(scopeKey, reason = "scope cancelled") {
|
|
813
|
+
let cancelled = 0;
|
|
814
|
+
for (const job of [...this.pending, ...this.running.values()]) {
|
|
815
|
+
if (job.scopeKey === scopeKey && this.cancel(job.jobId, reason)) {
|
|
816
|
+
cancelled++;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return cancelled;
|
|
820
|
+
}
|
|
821
|
+
snapshot() {
|
|
822
|
+
const now = Date.now();
|
|
823
|
+
const queuedByScope = {};
|
|
824
|
+
for (const job of this.pending) {
|
|
825
|
+
queuedByScope[job.scopeKey] = (queuedByScope[job.scopeKey] ?? 0) + 1;
|
|
826
|
+
}
|
|
827
|
+
const oldest = this.pending.reduce(
|
|
828
|
+
(value, job) => Math.min(value, job.enqueuedAtMs),
|
|
829
|
+
Number.POSITIVE_INFINITY
|
|
830
|
+
);
|
|
831
|
+
return {
|
|
832
|
+
state: this.state,
|
|
833
|
+
maxConcurrentScopes: this.maxConcurrentScopes,
|
|
834
|
+
maxQueuedJobs: this.maxQueuedJobs,
|
|
835
|
+
defaultTimeBudgetMs: this.defaultTimeBudgetMs,
|
|
836
|
+
queuedJobs: this.pending.length,
|
|
837
|
+
runningJobs: this.running.size,
|
|
838
|
+
activeScopes: [...this.activeScopes].sort(),
|
|
839
|
+
queuedByScope: Object.fromEntries(
|
|
840
|
+
Object.entries(queuedByScope).sort(
|
|
841
|
+
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
842
|
+
)
|
|
843
|
+
),
|
|
844
|
+
oldestQueuedAgeMs: Number.isFinite(oldest) ? Math.max(0, now - oldest) : 0,
|
|
845
|
+
totals: { ...this.totals }
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
waitForIdle() {
|
|
849
|
+
if (this.pending.length === 0 && this.running.size === 0) {
|
|
850
|
+
return Promise.resolve();
|
|
851
|
+
}
|
|
852
|
+
return new Promise((resolve2) => {
|
|
853
|
+
this.idleWaiters.add(resolve2);
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
async close(options = {}) {
|
|
857
|
+
if (this.state === "closed") return;
|
|
858
|
+
this.state = "closing";
|
|
859
|
+
const reason = options.reason ?? "queue closed";
|
|
860
|
+
if (options.cancelQueued) {
|
|
861
|
+
for (const job of [...this.pending]) this.cancel(job.jobId, reason);
|
|
862
|
+
}
|
|
863
|
+
if (options.cancelRunning) {
|
|
864
|
+
for (const job of [...this.running.values()]) {
|
|
865
|
+
this.cancel(job.jobId, reason);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
this.schedulePump();
|
|
869
|
+
await this.waitForIdle();
|
|
870
|
+
this.state = "closed";
|
|
871
|
+
}
|
|
872
|
+
rejectedReceipt(status, jobId, scopeKey, dedupeKey, kind, error) {
|
|
873
|
+
const now = Date.now();
|
|
874
|
+
return {
|
|
875
|
+
admission: status,
|
|
876
|
+
jobId,
|
|
877
|
+
scopeKey,
|
|
878
|
+
dedupeKey,
|
|
879
|
+
completion: Promise.resolve({
|
|
880
|
+
jobId,
|
|
881
|
+
scopeKey,
|
|
882
|
+
dedupeKey,
|
|
883
|
+
kind,
|
|
884
|
+
status,
|
|
885
|
+
enqueuedAtMs: now,
|
|
886
|
+
finishedAtMs: now,
|
|
887
|
+
errorMessage: error
|
|
888
|
+
})
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
schedulePump() {
|
|
892
|
+
if (this.pumpScheduled) return;
|
|
893
|
+
this.pumpScheduled = true;
|
|
894
|
+
queueMicrotask(() => {
|
|
895
|
+
this.pumpScheduled = false;
|
|
896
|
+
this.pump();
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
pump() {
|
|
900
|
+
while (this.running.size < this.maxConcurrentScopes) {
|
|
901
|
+
const nextIndex = this.pending.findIndex(
|
|
902
|
+
(job2) => !this.activeScopes.has(job2.scopeKey)
|
|
903
|
+
);
|
|
904
|
+
if (nextIndex < 0) break;
|
|
905
|
+
const [job] = this.pending.splice(nextIndex, 1);
|
|
906
|
+
if (!job) break;
|
|
907
|
+
this.start(job);
|
|
908
|
+
}
|
|
909
|
+
this.maybeResolveIdle();
|
|
910
|
+
}
|
|
911
|
+
start(job) {
|
|
912
|
+
job.startedAtMs = Date.now();
|
|
913
|
+
job.controller = new AbortController();
|
|
914
|
+
this.running.set(job.jobId, job);
|
|
915
|
+
this.activeScopes.add(job.scopeKey);
|
|
916
|
+
this.totals.started++;
|
|
917
|
+
const deadlineAtMs = job.startedAtMs + job.timeBudgetMs;
|
|
918
|
+
const timer = setTimeout(() => {
|
|
919
|
+
if (!job.controller || job.controller.signal.aborted) return;
|
|
920
|
+
job.forcedStatus = "timed_out";
|
|
921
|
+
job.forcedReason = `time budget exceeded after ${job.timeBudgetMs}ms`;
|
|
922
|
+
job.controller.abort(new Error(job.forcedReason));
|
|
923
|
+
}, job.timeBudgetMs);
|
|
924
|
+
if (typeof timer.unref === "function") {
|
|
925
|
+
timer.unref();
|
|
926
|
+
}
|
|
927
|
+
const abortOutcome = new Promise((resolve2) => {
|
|
928
|
+
job.controller.signal.addEventListener(
|
|
929
|
+
"abort",
|
|
930
|
+
() => resolve2("aborted"),
|
|
931
|
+
{
|
|
932
|
+
once: true
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
});
|
|
936
|
+
const execution = Promise.resolve().then(
|
|
937
|
+
() => job.execute({
|
|
938
|
+
authority: TELEGRAM_COGNITION_AUTHORITY,
|
|
939
|
+
jobId: job.jobId,
|
|
940
|
+
scopeKey: job.scopeKey,
|
|
941
|
+
kind: job.kind,
|
|
942
|
+
signal: job.controller.signal,
|
|
943
|
+
deadlineAtMs
|
|
944
|
+
})
|
|
945
|
+
).then(
|
|
946
|
+
() => ({ type: "succeeded" }),
|
|
947
|
+
(error) => ({ type: "failed", error })
|
|
948
|
+
);
|
|
949
|
+
void Promise.race([
|
|
950
|
+
execution,
|
|
951
|
+
abortOutcome.then(() => ({ type: "aborted" }))
|
|
952
|
+
]).then((result) => {
|
|
953
|
+
clearTimeout(timer);
|
|
954
|
+
if (result.type === "aborted") {
|
|
955
|
+
this.finish(
|
|
956
|
+
job,
|
|
957
|
+
job.forcedStatus ?? "cancelled",
|
|
958
|
+
job.forcedReason ?? "cancelled"
|
|
959
|
+
);
|
|
960
|
+
} else if (result.type === "failed") {
|
|
961
|
+
this.finish(job, "failed", errorMessage(result.error));
|
|
962
|
+
} else {
|
|
963
|
+
this.finish(job, "succeeded");
|
|
964
|
+
}
|
|
965
|
+
}).catch((error) => {
|
|
966
|
+
clearTimeout(timer);
|
|
967
|
+
this.finish(job, "failed", errorMessage(error));
|
|
968
|
+
});
|
|
969
|
+
void execution.then(() => void 0);
|
|
970
|
+
}
|
|
971
|
+
finish(job, status, error) {
|
|
972
|
+
if (!this.jobsById.has(job.jobId)) return;
|
|
973
|
+
this.running.delete(job.jobId);
|
|
974
|
+
this.activeScopes.delete(job.scopeKey);
|
|
975
|
+
this.jobsById.delete(job.jobId);
|
|
976
|
+
if (this.dedupe.get(job.dedupeIdentity) === job) {
|
|
977
|
+
this.dedupe.delete(job.dedupeIdentity);
|
|
978
|
+
}
|
|
979
|
+
this.totals[status === "timed_out" ? "timedOut" : status]++;
|
|
980
|
+
const finishedAtMs = Date.now();
|
|
981
|
+
const startedAtMs = job.startedAtMs;
|
|
982
|
+
job.resolve({
|
|
983
|
+
jobId: job.jobId,
|
|
984
|
+
scopeKey: job.scopeKey,
|
|
985
|
+
dedupeKey: job.dedupeKey,
|
|
986
|
+
kind: job.kind,
|
|
987
|
+
status,
|
|
988
|
+
enqueuedAtMs: job.enqueuedAtMs,
|
|
989
|
+
...startedAtMs !== void 0 ? {
|
|
990
|
+
startedAtMs,
|
|
991
|
+
queueWaitMs: Math.max(0, startedAtMs - job.enqueuedAtMs),
|
|
992
|
+
runDurationMs: Math.max(0, finishedAtMs - startedAtMs)
|
|
993
|
+
} : {},
|
|
994
|
+
finishedAtMs,
|
|
995
|
+
...error ? { errorMessage: error } : {}
|
|
996
|
+
});
|
|
997
|
+
this.schedulePump();
|
|
998
|
+
}
|
|
999
|
+
maybeResolveIdle() {
|
|
1000
|
+
if (this.pending.length > 0 || this.running.size > 0) return;
|
|
1001
|
+
for (const resolve2 of this.idleWaiters) resolve2();
|
|
1002
|
+
this.idleWaiters.clear();
|
|
1003
|
+
if (this.state === "closing") this.state = "closed";
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
|
|
291
1007
|
// packages/backend-vllm/dist/providerRegistry.js
|
|
292
1008
|
var CHAT_CAPABILITIES = [
|
|
293
1009
|
"chat",
|
|
@@ -989,13 +1705,1271 @@ var PROVIDER_PRESETS = [
|
|
|
989
1705
|
description: "Custom endpoint; an explicit ollama, openai-chat, or anthropic-messages protocol is required."
|
|
990
1706
|
}
|
|
991
1707
|
];
|
|
1708
|
+
|
|
1709
|
+
// packages/cli/src/tui/telegram-input-ownership.ts
|
|
1710
|
+
var TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
1711
|
+
"consumed",
|
|
1712
|
+
"superseded",
|
|
1713
|
+
"failed"
|
|
1714
|
+
]);
|
|
1715
|
+
var ALLOWED_TRANSITIONS = {
|
|
1716
|
+
received: /* @__PURE__ */ new Set([
|
|
1717
|
+
"classified",
|
|
1718
|
+
"queued",
|
|
1719
|
+
"dispatched",
|
|
1720
|
+
"decision_committed",
|
|
1721
|
+
"superseded",
|
|
1722
|
+
"failed"
|
|
1723
|
+
]),
|
|
1724
|
+
classified: /* @__PURE__ */ new Set([
|
|
1725
|
+
"queued",
|
|
1726
|
+
"dispatched",
|
|
1727
|
+
"decision_committed",
|
|
1728
|
+
"superseded",
|
|
1729
|
+
"failed"
|
|
1730
|
+
]),
|
|
1731
|
+
queued: /* @__PURE__ */ new Set([
|
|
1732
|
+
"dispatched",
|
|
1733
|
+
"deferred",
|
|
1734
|
+
"decision_committed",
|
|
1735
|
+
"superseded",
|
|
1736
|
+
"failed"
|
|
1737
|
+
]),
|
|
1738
|
+
dispatched: /* @__PURE__ */ new Set([
|
|
1739
|
+
"deferred",
|
|
1740
|
+
"decision_committed",
|
|
1741
|
+
"superseded",
|
|
1742
|
+
"failed"
|
|
1743
|
+
]),
|
|
1744
|
+
deferred: /* @__PURE__ */ new Set([
|
|
1745
|
+
"queued",
|
|
1746
|
+
"dispatched",
|
|
1747
|
+
"decision_committed",
|
|
1748
|
+
"superseded",
|
|
1749
|
+
"failed"
|
|
1750
|
+
]),
|
|
1751
|
+
decision_committed: /* @__PURE__ */ new Set([
|
|
1752
|
+
"deferred",
|
|
1753
|
+
"queued",
|
|
1754
|
+
"dispatched",
|
|
1755
|
+
"delivery_committed",
|
|
1756
|
+
"consumed",
|
|
1757
|
+
"superseded",
|
|
1758
|
+
"failed"
|
|
1759
|
+
]),
|
|
1760
|
+
delivery_committed: /* @__PURE__ */ new Set(["consumed"]),
|
|
1761
|
+
consumed: /* @__PURE__ */ new Set(),
|
|
1762
|
+
superseded: /* @__PURE__ */ new Set(),
|
|
1763
|
+
failed: /* @__PURE__ */ new Set()
|
|
1764
|
+
};
|
|
1765
|
+
function nonEmpty(value, label) {
|
|
1766
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1767
|
+
throw new TypeError(`${label} must be a non-empty string`);
|
|
1768
|
+
}
|
|
1769
|
+
return value;
|
|
1770
|
+
}
|
|
1771
|
+
function finiteInteger(value, label) {
|
|
1772
|
+
if (!Number.isSafeInteger(value)) {
|
|
1773
|
+
throw new TypeError(`${label} must be a safe integer`);
|
|
1774
|
+
}
|
|
1775
|
+
return value;
|
|
1776
|
+
}
|
|
1777
|
+
function timestamp(value) {
|
|
1778
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1779
|
+
throw new TypeError("atMs must be a non-negative finite number");
|
|
1780
|
+
}
|
|
1781
|
+
return value;
|
|
1782
|
+
}
|
|
1783
|
+
function createTelegramInputTargetBinding(target) {
|
|
1784
|
+
return Object.freeze({
|
|
1785
|
+
inputId: nonEmpty(target.inputId, "inputId"),
|
|
1786
|
+
sessionKey: nonEmpty(target.sessionKey, "sessionKey"),
|
|
1787
|
+
chatId: nonEmpty(target.chatId, "chatId"),
|
|
1788
|
+
messageId: finiteInteger(target.messageId, "messageId"),
|
|
1789
|
+
messageThreadId: finiteInteger(target.messageThreadId, "messageThreadId"),
|
|
1790
|
+
senderActorKey: nonEmpty(target.senderActorKey, "senderActorKey")
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
function telegramInputTargetsEqual(left, right) {
|
|
1794
|
+
return left.inputId === right.inputId && left.sessionKey === right.sessionKey && left.chatId === right.chatId && left.messageId === right.messageId && left.messageThreadId === right.messageThreadId && left.senderActorKey === right.senderActorKey;
|
|
1795
|
+
}
|
|
1796
|
+
function isTelegramInputTerminalLifecycle(state) {
|
|
1797
|
+
return TERMINAL_STATES.has(state);
|
|
1798
|
+
}
|
|
1799
|
+
function freezeOwner(owner) {
|
|
1800
|
+
const generation = finiteInteger(owner.generation, "owner.generation");
|
|
1801
|
+
if (generation < 0) {
|
|
1802
|
+
throw new TypeError("owner.generation must be non-negative");
|
|
1803
|
+
}
|
|
1804
|
+
return Object.freeze({
|
|
1805
|
+
ownerId: nonEmpty(owner.ownerId, "owner.ownerId"),
|
|
1806
|
+
generation
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
function ownersEqual(left, right) {
|
|
1810
|
+
return left.ownerId === right.ownerId && left.generation === right.generation;
|
|
1811
|
+
}
|
|
1812
|
+
function freezeEvent(state, atMs, owner) {
|
|
1813
|
+
return Object.freeze({ state, atMs: timestamp(atMs), owner });
|
|
1814
|
+
}
|
|
1815
|
+
function freezeRecord(record) {
|
|
1816
|
+
return Object.freeze({
|
|
1817
|
+
...record,
|
|
1818
|
+
history: Object.freeze([...record.history])
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
function decisionsEqual(left, right) {
|
|
1822
|
+
return left.decisionId === right.decisionId && left.contractFingerprint === right.contractFingerprint && left.contractVersion === right.contractVersion && left.disposition === right.disposition;
|
|
1823
|
+
}
|
|
1824
|
+
function deliveriesEqual(left, right) {
|
|
1825
|
+
return left.deliveryId === right.deliveryId && left.decisionId === right.decisionId && left.kind === right.kind && left.transportReceiptId === right.transportReceiptId;
|
|
1826
|
+
}
|
|
1827
|
+
var TelegramInputOwnershipLedger = class {
|
|
1828
|
+
records = /* @__PURE__ */ new Map();
|
|
1829
|
+
get(inputId) {
|
|
1830
|
+
return this.records.get(inputId);
|
|
1831
|
+
}
|
|
1832
|
+
register(targetInput, ownerInput, atMs = Date.now()) {
|
|
1833
|
+
const target = createTelegramInputTargetBinding(targetInput);
|
|
1834
|
+
const owner = freezeOwner(ownerInput);
|
|
1835
|
+
const existing = this.records.get(target.inputId);
|
|
1836
|
+
if (existing) {
|
|
1837
|
+
if (telegramInputTargetsEqual(existing.target, target)) {
|
|
1838
|
+
return { status: "duplicate", record: existing };
|
|
1839
|
+
}
|
|
1840
|
+
return {
|
|
1841
|
+
status: "contradiction",
|
|
1842
|
+
record: existing,
|
|
1843
|
+
reason: "inputId is already bound to a different immutable target"
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
const record = freezeRecord({
|
|
1847
|
+
target,
|
|
1848
|
+
owner,
|
|
1849
|
+
lifecycle: "received",
|
|
1850
|
+
revision: 0,
|
|
1851
|
+
history: Object.freeze([freezeEvent("received", atMs, owner)])
|
|
1852
|
+
});
|
|
1853
|
+
this.records.set(target.inputId, record);
|
|
1854
|
+
return { status: "applied", record };
|
|
1855
|
+
}
|
|
1856
|
+
claim(targetInput, ownerInput) {
|
|
1857
|
+
const bound = this.boundRecord(targetInput);
|
|
1858
|
+
if (bound.status !== "applied") return bound;
|
|
1859
|
+
const record = bound.record;
|
|
1860
|
+
const owner = freezeOwner(ownerInput);
|
|
1861
|
+
if (ownersEqual(record.owner, owner)) {
|
|
1862
|
+
return { status: "duplicate", record };
|
|
1863
|
+
}
|
|
1864
|
+
if (isTelegramInputTerminalLifecycle(record.lifecycle)) {
|
|
1865
|
+
return {
|
|
1866
|
+
status: "terminal",
|
|
1867
|
+
record,
|
|
1868
|
+
reason: "terminal input ownership cannot be transferred"
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
if (owner.generation < record.owner.generation) {
|
|
1872
|
+
return {
|
|
1873
|
+
status: "stale",
|
|
1874
|
+
record,
|
|
1875
|
+
reason: "owner generation is older than the authoritative claim"
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
if (owner.generation === record.owner.generation) {
|
|
1879
|
+
return {
|
|
1880
|
+
status: "contradiction",
|
|
1881
|
+
record,
|
|
1882
|
+
reason: "one owner generation cannot name two owners"
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
const next = freezeRecord({
|
|
1886
|
+
...record,
|
|
1887
|
+
owner,
|
|
1888
|
+
revision: record.revision + 1
|
|
1889
|
+
});
|
|
1890
|
+
this.records.set(targetInput.inputId, next);
|
|
1891
|
+
return { status: "applied", record: next };
|
|
1892
|
+
}
|
|
1893
|
+
transition(targetInput, ownerInput, nextState, atMs = Date.now()) {
|
|
1894
|
+
const authorized = this.authorizedRecord(targetInput, ownerInput);
|
|
1895
|
+
if (authorized.status !== "applied") return authorized;
|
|
1896
|
+
const record = authorized.record;
|
|
1897
|
+
if (record.lifecycle === nextState) {
|
|
1898
|
+
return { status: "duplicate", record };
|
|
1899
|
+
}
|
|
1900
|
+
if (isTelegramInputTerminalLifecycle(record.lifecycle)) {
|
|
1901
|
+
return {
|
|
1902
|
+
status: "terminal",
|
|
1903
|
+
record,
|
|
1904
|
+
reason: "terminal input lifecycle cannot advance"
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
if (!ALLOWED_TRANSITIONS[record.lifecycle].has(nextState)) {
|
|
1908
|
+
return {
|
|
1909
|
+
status: "invalid_transition",
|
|
1910
|
+
record,
|
|
1911
|
+
reason: `${record.lifecycle} cannot transition to ${nextState}`
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
return this.storeLifecycle(record, nextState, atMs);
|
|
1915
|
+
}
|
|
1916
|
+
commitDecision(targetInput, ownerInput, decisionInput, atMs = Date.now()) {
|
|
1917
|
+
const bound = this.boundRecord(targetInput);
|
|
1918
|
+
if (bound.status !== "applied") return bound;
|
|
1919
|
+
const record = bound.record;
|
|
1920
|
+
const decision = {
|
|
1921
|
+
decisionId: nonEmpty(decisionInput.decisionId, "decisionId"),
|
|
1922
|
+
contractFingerprint: nonEmpty(
|
|
1923
|
+
decisionInput.contractFingerprint,
|
|
1924
|
+
"contractFingerprint"
|
|
1925
|
+
),
|
|
1926
|
+
contractVersion: decisionInput.contractVersion,
|
|
1927
|
+
disposition: decisionInput.disposition
|
|
1928
|
+
};
|
|
1929
|
+
if (decision.contractVersion !== 2) {
|
|
1930
|
+
return {
|
|
1931
|
+
status: "invalid_transition",
|
|
1932
|
+
record,
|
|
1933
|
+
reason: "only Telegram turn contract version 2 can be committed"
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
if (record.decision) {
|
|
1937
|
+
return decisionsEqual(record.decision, decision) ? { status: "duplicate", record } : {
|
|
1938
|
+
status: "contradiction",
|
|
1939
|
+
record,
|
|
1940
|
+
reason: "an input can have only one committed decision"
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
const authorized = this.authorizedRecord(targetInput, ownerInput);
|
|
1944
|
+
if (authorized.status !== "applied") return authorized;
|
|
1945
|
+
if (isTelegramInputTerminalLifecycle(record.lifecycle)) {
|
|
1946
|
+
return {
|
|
1947
|
+
status: "terminal",
|
|
1948
|
+
record,
|
|
1949
|
+
reason: "a terminal input cannot accept a decision"
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
if (!ALLOWED_TRANSITIONS[record.lifecycle].has("decision_committed")) {
|
|
1953
|
+
return {
|
|
1954
|
+
status: "invalid_transition",
|
|
1955
|
+
record,
|
|
1956
|
+
reason: `${record.lifecycle} cannot commit a decision`
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
const committed = Object.freeze({
|
|
1960
|
+
...decision,
|
|
1961
|
+
committedBy: record.owner,
|
|
1962
|
+
committedAtMs: timestamp(atMs)
|
|
1963
|
+
});
|
|
1964
|
+
const next = freezeRecord({
|
|
1965
|
+
...record,
|
|
1966
|
+
decision: committed,
|
|
1967
|
+
lifecycle: "decision_committed",
|
|
1968
|
+
revision: record.revision + 1,
|
|
1969
|
+
history: [
|
|
1970
|
+
...record.history,
|
|
1971
|
+
freezeEvent("decision_committed", atMs, record.owner)
|
|
1972
|
+
]
|
|
1973
|
+
});
|
|
1974
|
+
this.records.set(record.target.inputId, next);
|
|
1975
|
+
return { status: "applied", record: next };
|
|
1976
|
+
}
|
|
1977
|
+
commitDelivery(targetInput, ownerInput, deliveryInput, atMs = Date.now()) {
|
|
1978
|
+
const bound = this.boundRecord(targetInput);
|
|
1979
|
+
if (bound.status !== "applied") return bound;
|
|
1980
|
+
const record = bound.record;
|
|
1981
|
+
const delivery = {
|
|
1982
|
+
deliveryId: nonEmpty(deliveryInput.deliveryId, "deliveryId"),
|
|
1983
|
+
decisionId: nonEmpty(deliveryInput.decisionId, "decisionId"),
|
|
1984
|
+
kind: deliveryInput.kind,
|
|
1985
|
+
transportReceiptId: nonEmpty(
|
|
1986
|
+
deliveryInput.transportReceiptId,
|
|
1987
|
+
"transportReceiptId"
|
|
1988
|
+
)
|
|
1989
|
+
};
|
|
1990
|
+
if (record.delivery) {
|
|
1991
|
+
return deliveriesEqual(record.delivery, delivery) ? { status: "duplicate", record } : {
|
|
1992
|
+
status: "contradiction",
|
|
1993
|
+
record,
|
|
1994
|
+
reason: "an input can have only one committed delivery"
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
const authorized = this.authorizedRecord(targetInput, ownerInput);
|
|
1998
|
+
if (authorized.status !== "applied") return authorized;
|
|
1999
|
+
if (isTelegramInputTerminalLifecycle(record.lifecycle)) {
|
|
2000
|
+
return {
|
|
2001
|
+
status: "terminal",
|
|
2002
|
+
record,
|
|
2003
|
+
reason: "a terminal input cannot accept a delivery"
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
if (!record.decision || record.decision.decisionId !== delivery.decisionId) {
|
|
2007
|
+
return {
|
|
2008
|
+
status: "invalid_transition",
|
|
2009
|
+
record,
|
|
2010
|
+
reason: "delivery must cite the committed decision"
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
if (record.decision.disposition === "no_reply") {
|
|
2014
|
+
return {
|
|
2015
|
+
status: "invalid_transition",
|
|
2016
|
+
record,
|
|
2017
|
+
reason: "a no_reply decision cannot have a delivery"
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
if (record.decision.disposition === "chat_reply" && delivery.kind !== "visible_reply" || record.decision.disposition === "action" && delivery.kind !== "action_effect") {
|
|
2021
|
+
return {
|
|
2022
|
+
status: "contradiction",
|
|
2023
|
+
record,
|
|
2024
|
+
reason: "delivery kind contradicts the committed disposition"
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
const committed = Object.freeze({
|
|
2028
|
+
...delivery,
|
|
2029
|
+
committedBy: record.owner,
|
|
2030
|
+
committedAtMs: timestamp(atMs)
|
|
2031
|
+
});
|
|
2032
|
+
const next = freezeRecord({
|
|
2033
|
+
...record,
|
|
2034
|
+
delivery: committed,
|
|
2035
|
+
lifecycle: "delivery_committed",
|
|
2036
|
+
revision: record.revision + 1,
|
|
2037
|
+
history: [
|
|
2038
|
+
...record.history,
|
|
2039
|
+
freezeEvent("delivery_committed", atMs, record.owner)
|
|
2040
|
+
]
|
|
2041
|
+
});
|
|
2042
|
+
this.records.set(record.target.inputId, next);
|
|
2043
|
+
return { status: "applied", record: next };
|
|
2044
|
+
}
|
|
2045
|
+
complete(targetInput, ownerInput, terminal, atMs = Date.now()) {
|
|
2046
|
+
const bound = this.boundRecord(targetInput);
|
|
2047
|
+
if (bound.status !== "applied") return bound;
|
|
2048
|
+
const record = bound.record;
|
|
2049
|
+
if (record.terminal) {
|
|
2050
|
+
return record.terminal === terminal ? { status: "duplicate", record } : {
|
|
2051
|
+
status: "contradiction",
|
|
2052
|
+
record,
|
|
2053
|
+
reason: "an input can have only one terminal lifecycle result"
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
const authorized = this.authorizedRecord(targetInput, ownerInput);
|
|
2057
|
+
if (authorized.status !== "applied") return authorized;
|
|
2058
|
+
if (terminal === "consumed") {
|
|
2059
|
+
if (!record.decision) {
|
|
2060
|
+
return {
|
|
2061
|
+
status: "invalid_transition",
|
|
2062
|
+
record,
|
|
2063
|
+
reason: "consumed input requires a committed decision"
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
if (record.decision.disposition !== "no_reply" && !record.delivery) {
|
|
2067
|
+
return {
|
|
2068
|
+
status: "invalid_transition",
|
|
2069
|
+
record,
|
|
2070
|
+
reason: "reply and action decisions require a delivery receipt"
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
if (!ALLOWED_TRANSITIONS[record.lifecycle].has(terminal)) {
|
|
2075
|
+
return {
|
|
2076
|
+
status: "invalid_transition",
|
|
2077
|
+
record,
|
|
2078
|
+
reason: `${record.lifecycle} cannot transition to ${terminal}`
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
return this.storeLifecycle(record, terminal, atMs, terminal);
|
|
2082
|
+
}
|
|
2083
|
+
boundRecord(targetInput) {
|
|
2084
|
+
const existing = this.records.get(targetInput.inputId);
|
|
2085
|
+
if (!existing) {
|
|
2086
|
+
return { status: "unknown_input", reason: "input is not registered" };
|
|
2087
|
+
}
|
|
2088
|
+
let target;
|
|
2089
|
+
try {
|
|
2090
|
+
target = createTelegramInputTargetBinding(targetInput);
|
|
2091
|
+
} catch (error) {
|
|
2092
|
+
return {
|
|
2093
|
+
status: "contradiction",
|
|
2094
|
+
record: existing,
|
|
2095
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
if (!telegramInputTargetsEqual(existing.target, target)) {
|
|
2099
|
+
return {
|
|
2100
|
+
status: "contradiction",
|
|
2101
|
+
record: existing,
|
|
2102
|
+
reason: "operation target contradicts the immutable input binding"
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
return { status: "applied", record: existing };
|
|
2106
|
+
}
|
|
2107
|
+
authorizedRecord(targetInput, ownerInput) {
|
|
2108
|
+
const bound = this.boundRecord(targetInput);
|
|
2109
|
+
if (bound.status !== "applied") return bound;
|
|
2110
|
+
const record = bound.record;
|
|
2111
|
+
let owner;
|
|
2112
|
+
try {
|
|
2113
|
+
owner = freezeOwner(ownerInput);
|
|
2114
|
+
} catch (error) {
|
|
2115
|
+
return {
|
|
2116
|
+
status: "contradiction",
|
|
2117
|
+
record,
|
|
2118
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
if (ownersEqual(record.owner, owner)) return bound;
|
|
2122
|
+
if (owner.generation < record.owner.generation) {
|
|
2123
|
+
return {
|
|
2124
|
+
status: "stale",
|
|
2125
|
+
record,
|
|
2126
|
+
reason: "operation belongs to an older owner generation"
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
return {
|
|
2130
|
+
status: "contradiction",
|
|
2131
|
+
record,
|
|
2132
|
+
reason: "operation owner does not match the authoritative claim"
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
storeLifecycle(record, state, atMs, terminal) {
|
|
2136
|
+
const next = freezeRecord({
|
|
2137
|
+
...record,
|
|
2138
|
+
lifecycle: state,
|
|
2139
|
+
terminal,
|
|
2140
|
+
revision: record.revision + 1,
|
|
2141
|
+
history: [...record.history, freezeEvent(state, atMs, record.owner)]
|
|
2142
|
+
});
|
|
2143
|
+
this.records.set(record.target.inputId, next);
|
|
2144
|
+
return { status: "applied", record: next };
|
|
2145
|
+
}
|
|
2146
|
+
};
|
|
2147
|
+
|
|
2148
|
+
// packages/cli/src/tui/telegram-turn-contract-v2.ts
|
|
2149
|
+
var TELEGRAM_TURN_CONTRACT_V2_VERSION = 2;
|
|
2150
|
+
var SELF_ROLES = /* @__PURE__ */ new Set([
|
|
2151
|
+
"addressee",
|
|
2152
|
+
"participant",
|
|
2153
|
+
"observer",
|
|
2154
|
+
"uncertain"
|
|
2155
|
+
]);
|
|
2156
|
+
var REPLY_BASES = /* @__PURE__ */ new Set([
|
|
2157
|
+
"direct_turn",
|
|
2158
|
+
"ongoing_self_task",
|
|
2159
|
+
"social_intervention",
|
|
2160
|
+
"none",
|
|
2161
|
+
"uncertain"
|
|
2162
|
+
]);
|
|
2163
|
+
var KINDS = /* @__PURE__ */ new Set([
|
|
2164
|
+
"no_reply",
|
|
2165
|
+
"chat_reply",
|
|
2166
|
+
"action"
|
|
2167
|
+
]);
|
|
2168
|
+
var COMMON_WIRE_KEYS = [
|
|
2169
|
+
"version",
|
|
2170
|
+
"kind",
|
|
2171
|
+
"input_id",
|
|
2172
|
+
"target",
|
|
2173
|
+
"confidence",
|
|
2174
|
+
"reason",
|
|
2175
|
+
"addressed_actor_keys",
|
|
2176
|
+
"self_role",
|
|
2177
|
+
"reply_basis",
|
|
2178
|
+
"evidence_ids"
|
|
2179
|
+
];
|
|
2180
|
+
function isRecord(value) {
|
|
2181
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2182
|
+
}
|
|
2183
|
+
function issue(code, path, message) {
|
|
2184
|
+
return Object.freeze({ code, path, message });
|
|
2185
|
+
}
|
|
2186
|
+
function collectUnknownFields(value, allowed, path, issues) {
|
|
2187
|
+
for (const key of Object.keys(value)) {
|
|
2188
|
+
if (!allowed.has(key)) {
|
|
2189
|
+
issues.push(
|
|
2190
|
+
issue(
|
|
2191
|
+
"unknown_field",
|
|
2192
|
+
`${path}.${key}`,
|
|
2193
|
+
"field is not part of the V2 contract"
|
|
2194
|
+
)
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
function readString(value, path, issues) {
|
|
2200
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2201
|
+
issues.push(issue("invalid_field", path, "must be a non-empty string"));
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
return value;
|
|
2205
|
+
}
|
|
2206
|
+
function readInteger(value, path, issues) {
|
|
2207
|
+
if (!Number.isSafeInteger(value)) {
|
|
2208
|
+
issues.push(issue("invalid_field", path, "must be a safe integer"));
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
return value;
|
|
2212
|
+
}
|
|
2213
|
+
function readStringArray(value, path, issues) {
|
|
2214
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.length === 0)) {
|
|
2215
|
+
issues.push(
|
|
2216
|
+
issue("invalid_field", path, "must be an array of non-empty strings")
|
|
2217
|
+
);
|
|
2218
|
+
return null;
|
|
2219
|
+
}
|
|
2220
|
+
if (new Set(value).size !== value.length) {
|
|
2221
|
+
issues.push(
|
|
2222
|
+
issue("invalid_field", path, "must not contain duplicate values")
|
|
2223
|
+
);
|
|
2224
|
+
return null;
|
|
2225
|
+
}
|
|
2226
|
+
return Object.freeze([...value]);
|
|
2227
|
+
}
|
|
2228
|
+
function validateWireTarget(raw, expected, issues) {
|
|
2229
|
+
if (!isRecord(raw)) {
|
|
2230
|
+
issues.push(issue("invalid_field", "$.target", "must be an object"));
|
|
2231
|
+
return null;
|
|
2232
|
+
}
|
|
2233
|
+
collectUnknownFields(
|
|
2234
|
+
raw,
|
|
2235
|
+
/* @__PURE__ */ new Set([
|
|
2236
|
+
"input_id",
|
|
2237
|
+
"session_key",
|
|
2238
|
+
"chat_id",
|
|
2239
|
+
"message_id",
|
|
2240
|
+
"message_thread_id",
|
|
2241
|
+
"sender_actor_key"
|
|
2242
|
+
]),
|
|
2243
|
+
"$.target",
|
|
2244
|
+
issues
|
|
2245
|
+
);
|
|
2246
|
+
const inputId = readString(raw["input_id"], "$.target.input_id", issues);
|
|
2247
|
+
const sessionKey = readString(
|
|
2248
|
+
raw["session_key"],
|
|
2249
|
+
"$.target.session_key",
|
|
2250
|
+
issues
|
|
2251
|
+
);
|
|
2252
|
+
const chatId = readString(raw["chat_id"], "$.target.chat_id", issues);
|
|
2253
|
+
const messageId = readInteger(
|
|
2254
|
+
raw["message_id"],
|
|
2255
|
+
"$.target.message_id",
|
|
2256
|
+
issues
|
|
2257
|
+
);
|
|
2258
|
+
const messageThreadId = readInteger(
|
|
2259
|
+
raw["message_thread_id"],
|
|
2260
|
+
"$.target.message_thread_id",
|
|
2261
|
+
issues
|
|
2262
|
+
);
|
|
2263
|
+
const senderActorKey = readString(
|
|
2264
|
+
raw["sender_actor_key"],
|
|
2265
|
+
"$.target.sender_actor_key",
|
|
2266
|
+
issues
|
|
2267
|
+
);
|
|
2268
|
+
if (inputId === null || sessionKey === null || chatId === null || messageId === null || messageThreadId === null || senderActorKey === null) {
|
|
2269
|
+
return null;
|
|
2270
|
+
}
|
|
2271
|
+
const target = createTelegramInputTargetBinding({
|
|
2272
|
+
inputId,
|
|
2273
|
+
sessionKey,
|
|
2274
|
+
chatId,
|
|
2275
|
+
messageId,
|
|
2276
|
+
messageThreadId,
|
|
2277
|
+
senderActorKey
|
|
2278
|
+
});
|
|
2279
|
+
if (!telegramInputTargetsEqual(target, expected)) {
|
|
2280
|
+
issues.push(
|
|
2281
|
+
issue(
|
|
2282
|
+
"target_mismatch",
|
|
2283
|
+
"$.target",
|
|
2284
|
+
"target does not match the host-owned input binding"
|
|
2285
|
+
)
|
|
2286
|
+
);
|
|
2287
|
+
return null;
|
|
2288
|
+
}
|
|
2289
|
+
return target;
|
|
2290
|
+
}
|
|
2291
|
+
function validateTelegramTurnContractV2(raw, context) {
|
|
2292
|
+
const issues = [];
|
|
2293
|
+
if (!isRecord(raw)) {
|
|
2294
|
+
return Object.freeze({
|
|
2295
|
+
ok: false,
|
|
2296
|
+
value: null,
|
|
2297
|
+
issues: Object.freeze([
|
|
2298
|
+
issue("not_object", "$", "turn must be an object")
|
|
2299
|
+
])
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
const rawKind = raw["kind"];
|
|
2303
|
+
const kind = typeof rawKind === "string" && KINDS.has(rawKind) ? rawKind : null;
|
|
2304
|
+
if (raw["version"] !== TELEGRAM_TURN_CONTRACT_V2_VERSION) {
|
|
2305
|
+
issues.push(issue("invalid_version", "$.version", "version must equal 2"));
|
|
2306
|
+
}
|
|
2307
|
+
if (!kind) {
|
|
2308
|
+
issues.push(
|
|
2309
|
+
issue(
|
|
2310
|
+
"invalid_kind",
|
|
2311
|
+
"$.kind",
|
|
2312
|
+
"kind must be no_reply, chat_reply, or action"
|
|
2313
|
+
)
|
|
2314
|
+
);
|
|
2315
|
+
}
|
|
2316
|
+
const allowed = new Set(COMMON_WIRE_KEYS);
|
|
2317
|
+
if (kind === "chat_reply") allowed.add("reply");
|
|
2318
|
+
if (kind === "action") allowed.add("objective");
|
|
2319
|
+
collectUnknownFields(raw, allowed, "$", issues);
|
|
2320
|
+
const expectedTarget = createTelegramInputTargetBinding(context.target);
|
|
2321
|
+
const inputId = readString(raw["input_id"], "$.input_id", issues);
|
|
2322
|
+
if (inputId !== null && inputId !== expectedTarget.inputId) {
|
|
2323
|
+
issues.push(
|
|
2324
|
+
issue(
|
|
2325
|
+
"target_mismatch",
|
|
2326
|
+
"$.input_id",
|
|
2327
|
+
"input_id does not match the host target"
|
|
2328
|
+
)
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
const target = validateWireTarget(raw["target"], expectedTarget, issues);
|
|
2332
|
+
if (inputId !== null && target && inputId !== target.inputId) {
|
|
2333
|
+
issues.push(
|
|
2334
|
+
issue(
|
|
2335
|
+
"target_mismatch",
|
|
2336
|
+
"$.input_id",
|
|
2337
|
+
"input_id contradicts target.input_id"
|
|
2338
|
+
)
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
const confidence = raw["confidence"];
|
|
2342
|
+
if (typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
|
2343
|
+
issues.push(
|
|
2344
|
+
issue(
|
|
2345
|
+
"invalid_field",
|
|
2346
|
+
"$.confidence",
|
|
2347
|
+
"confidence must be between 0 and 1"
|
|
2348
|
+
)
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
const reason = readString(raw["reason"], "$.reason", issues);
|
|
2352
|
+
const addressedActorKeys = readStringArray(
|
|
2353
|
+
raw["addressed_actor_keys"],
|
|
2354
|
+
"$.addressed_actor_keys",
|
|
2355
|
+
issues
|
|
2356
|
+
);
|
|
2357
|
+
const evidenceIds = readStringArray(
|
|
2358
|
+
raw["evidence_ids"],
|
|
2359
|
+
"$.evidence_ids",
|
|
2360
|
+
issues
|
|
2361
|
+
);
|
|
2362
|
+
const rawSelfRole = raw["self_role"];
|
|
2363
|
+
const selfRole = typeof rawSelfRole === "string" && SELF_ROLES.has(rawSelfRole) ? rawSelfRole : null;
|
|
2364
|
+
if (!selfRole) {
|
|
2365
|
+
issues.push(issue("invalid_field", "$.self_role", "self_role is invalid"));
|
|
2366
|
+
}
|
|
2367
|
+
const rawReplyBasis = raw["reply_basis"];
|
|
2368
|
+
const replyBasis = typeof rawReplyBasis === "string" && REPLY_BASES.has(rawReplyBasis) ? rawReplyBasis : null;
|
|
2369
|
+
if (!replyBasis) {
|
|
2370
|
+
issues.push(
|
|
2371
|
+
issue("invalid_field", "$.reply_basis", "reply_basis is invalid")
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
const knownActors = new Set(context.actorKeys);
|
|
2375
|
+
for (const actorKey of addressedActorKeys ?? []) {
|
|
2376
|
+
if (!knownActors.has(actorKey)) {
|
|
2377
|
+
issues.push(
|
|
2378
|
+
issue(
|
|
2379
|
+
"unknown_actor_key",
|
|
2380
|
+
"$.addressed_actor_keys",
|
|
2381
|
+
`unknown actor key: ${actorKey}`
|
|
2382
|
+
)
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
const knownEvidence = new Set(context.evidenceIds);
|
|
2387
|
+
for (const evidenceId of evidenceIds ?? []) {
|
|
2388
|
+
if (!knownEvidence.has(evidenceId)) {
|
|
2389
|
+
issues.push(
|
|
2390
|
+
issue(
|
|
2391
|
+
"unknown_evidence_id",
|
|
2392
|
+
"$.evidence_ids",
|
|
2393
|
+
`unknown evidence id: ${evidenceId}`
|
|
2394
|
+
)
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
if (kind === "chat_reply" || kind === "action") {
|
|
2399
|
+
if (selfRole === "observer" || selfRole === "uncertain") {
|
|
2400
|
+
issues.push(
|
|
2401
|
+
issue(
|
|
2402
|
+
"reply_not_authorized",
|
|
2403
|
+
"$.self_role",
|
|
2404
|
+
`${kind} requires an authorized self role`
|
|
2405
|
+
)
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
if (replyBasis === "none" || replyBasis === "uncertain") {
|
|
2409
|
+
issues.push(
|
|
2410
|
+
issue(
|
|
2411
|
+
"reply_not_authorized",
|
|
2412
|
+
"$.reply_basis",
|
|
2413
|
+
`${kind} requires an affirmative reply basis`
|
|
2414
|
+
)
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
if ((evidenceIds?.length ?? 0) === 0) {
|
|
2418
|
+
issues.push(
|
|
2419
|
+
issue(
|
|
2420
|
+
"reply_not_authorized",
|
|
2421
|
+
"$.evidence_ids",
|
|
2422
|
+
`${kind} requires host evidence`
|
|
2423
|
+
)
|
|
2424
|
+
);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
if (context.selfActorKey && selfRole === "addressee" && addressedActorKeys && !addressedActorKeys.includes(context.selfActorKey)) {
|
|
2428
|
+
issues.push(
|
|
2429
|
+
issue(
|
|
2430
|
+
"reply_not_authorized",
|
|
2431
|
+
"$.addressed_actor_keys",
|
|
2432
|
+
"addressee role must include the host self actor"
|
|
2433
|
+
)
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
if (context.selfActorKey && selfRole === "observer" && addressedActorKeys?.includes(context.selfActorKey)) {
|
|
2437
|
+
issues.push(
|
|
2438
|
+
issue(
|
|
2439
|
+
"reply_not_authorized",
|
|
2440
|
+
"$.addressed_actor_keys",
|
|
2441
|
+
"observer role contradicts a self addressee"
|
|
2442
|
+
)
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
let reply = null;
|
|
2446
|
+
let objective = null;
|
|
2447
|
+
if (kind === "chat_reply") {
|
|
2448
|
+
reply = readString(raw["reply"], "$.reply", issues);
|
|
2449
|
+
}
|
|
2450
|
+
if (kind === "action") {
|
|
2451
|
+
objective = readString(raw["objective"], "$.objective", issues);
|
|
2452
|
+
}
|
|
2453
|
+
if (issues.length > 0 || !kind || !target || inputId === null || reason === null || !addressedActorKeys || !selfRole || !replyBasis || !evidenceIds || typeof confidence !== "number") {
|
|
2454
|
+
return Object.freeze({
|
|
2455
|
+
ok: false,
|
|
2456
|
+
value: null,
|
|
2457
|
+
issues: Object.freeze([...issues])
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
const common = {
|
|
2461
|
+
version: TELEGRAM_TURN_CONTRACT_V2_VERSION,
|
|
2462
|
+
inputId,
|
|
2463
|
+
target,
|
|
2464
|
+
confidence,
|
|
2465
|
+
reason,
|
|
2466
|
+
addressedActorKeys,
|
|
2467
|
+
selfRole,
|
|
2468
|
+
replyBasis,
|
|
2469
|
+
evidenceIds
|
|
2470
|
+
};
|
|
2471
|
+
const value = kind === "chat_reply" ? Object.freeze({ ...common, kind, reply }) : kind === "action" ? Object.freeze({ ...common, kind, objective }) : Object.freeze({ ...common, kind });
|
|
2472
|
+
return Object.freeze({ ok: true, value, issues: Object.freeze([]) });
|
|
2473
|
+
}
|
|
2474
|
+
function telegramTurnContractV2Fingerprint(contract) {
|
|
2475
|
+
return JSON.stringify({
|
|
2476
|
+
version: contract.version,
|
|
2477
|
+
kind: contract.kind,
|
|
2478
|
+
inputId: contract.inputId,
|
|
2479
|
+
target: contract.target,
|
|
2480
|
+
confidence: contract.confidence,
|
|
2481
|
+
reason: contract.reason,
|
|
2482
|
+
addressedActorKeys: [...contract.addressedActorKeys].sort(),
|
|
2483
|
+
selfRole: contract.selfRole,
|
|
2484
|
+
replyBasis: contract.replyBasis,
|
|
2485
|
+
evidenceIds: [...contract.evidenceIds].sort(),
|
|
2486
|
+
reply: contract.kind === "chat_reply" ? contract.reply : void 0,
|
|
2487
|
+
objective: contract.kind === "action" ? contract.objective : void 0
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
function rawInputId(raw) {
|
|
2491
|
+
return isRecord(raw) && typeof raw["input_id"] === "string" ? raw["input_id"] : null;
|
|
2492
|
+
}
|
|
2493
|
+
function validateTelegramTurnBatchV2(raw, contexts) {
|
|
2494
|
+
const globalIssues = [];
|
|
2495
|
+
const expected = /* @__PURE__ */ new Map();
|
|
2496
|
+
for (const context of contexts) {
|
|
2497
|
+
const target = createTelegramInputTargetBinding(context.target);
|
|
2498
|
+
if (expected.has(target.inputId)) {
|
|
2499
|
+
throw new TypeError(`duplicate expected inputId: ${target.inputId}`);
|
|
2500
|
+
}
|
|
2501
|
+
expected.set(target.inputId, { ...context, target });
|
|
2502
|
+
}
|
|
2503
|
+
let turns = [];
|
|
2504
|
+
if (!isRecord(raw)) {
|
|
2505
|
+
globalIssues.push(issue("not_object", "$", "batch must be an object"));
|
|
2506
|
+
} else {
|
|
2507
|
+
collectUnknownFields(raw, /* @__PURE__ */ new Set(["version", "turns"]), "$", globalIssues);
|
|
2508
|
+
if (raw["version"] !== TELEGRAM_TURN_CONTRACT_V2_VERSION) {
|
|
2509
|
+
globalIssues.push(
|
|
2510
|
+
issue("invalid_version", "$.version", "version must equal 2")
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
if (!Array.isArray(raw["turns"])) {
|
|
2514
|
+
globalIssues.push(
|
|
2515
|
+
issue("invalid_field", "$.turns", "turns must be an array")
|
|
2516
|
+
);
|
|
2517
|
+
} else {
|
|
2518
|
+
turns = raw["turns"];
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2522
|
+
for (let index = 0; index < turns.length; index++) {
|
|
2523
|
+
const turn = turns[index];
|
|
2524
|
+
const inputId = rawInputId(turn);
|
|
2525
|
+
if (!inputId) {
|
|
2526
|
+
globalIssues.push(
|
|
2527
|
+
issue(
|
|
2528
|
+
"invalid_field",
|
|
2529
|
+
`$.turns[${index}].input_id`,
|
|
2530
|
+
"turn is not bound to an input"
|
|
2531
|
+
)
|
|
2532
|
+
);
|
|
2533
|
+
continue;
|
|
2534
|
+
}
|
|
2535
|
+
if (!expected.has(inputId)) {
|
|
2536
|
+
globalIssues.push(
|
|
2537
|
+
issue(
|
|
2538
|
+
"unexpected_input",
|
|
2539
|
+
`$.turns[${index}].input_id`,
|
|
2540
|
+
`unexpected input: ${inputId}`
|
|
2541
|
+
)
|
|
2542
|
+
);
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
const entries = grouped.get(inputId) ?? [];
|
|
2546
|
+
entries.push(turn);
|
|
2547
|
+
grouped.set(inputId, entries);
|
|
2548
|
+
}
|
|
2549
|
+
const dispositions = [];
|
|
2550
|
+
for (const [inputId, context] of expected) {
|
|
2551
|
+
const entries = grouped.get(inputId) ?? [];
|
|
2552
|
+
if (entries.length === 0) {
|
|
2553
|
+
const issues = Object.freeze([
|
|
2554
|
+
issue(
|
|
2555
|
+
"missing_input",
|
|
2556
|
+
"$.turns",
|
|
2557
|
+
`missing disposition for input: ${inputId}`
|
|
2558
|
+
)
|
|
2559
|
+
]);
|
|
2560
|
+
dispositions.push(
|
|
2561
|
+
Object.freeze({ inputId, status: "missing", contract: null, issues })
|
|
2562
|
+
);
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
const validations = entries.map(
|
|
2566
|
+
(entry) => validateTelegramTurnContractV2(entry, context)
|
|
2567
|
+
);
|
|
2568
|
+
const invalidIssues = validations.flatMap(
|
|
2569
|
+
(validation) => validation.issues
|
|
2570
|
+
);
|
|
2571
|
+
if (validations.some((validation) => !validation.ok || !validation.value)) {
|
|
2572
|
+
dispositions.push(
|
|
2573
|
+
Object.freeze({
|
|
2574
|
+
inputId,
|
|
2575
|
+
status: "invalid",
|
|
2576
|
+
contract: null,
|
|
2577
|
+
issues: Object.freeze(invalidIssues)
|
|
2578
|
+
})
|
|
2579
|
+
);
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
const contracts = validations.map((validation) => validation.value);
|
|
2583
|
+
const fingerprints = new Set(
|
|
2584
|
+
contracts.map(telegramTurnContractV2Fingerprint)
|
|
2585
|
+
);
|
|
2586
|
+
if (fingerprints.size > 1) {
|
|
2587
|
+
const issues = Object.freeze([
|
|
2588
|
+
issue(
|
|
2589
|
+
"contradictory_input",
|
|
2590
|
+
"$.turns",
|
|
2591
|
+
`input has contradictory dispositions: ${inputId}`
|
|
2592
|
+
)
|
|
2593
|
+
]);
|
|
2594
|
+
dispositions.push(
|
|
2595
|
+
Object.freeze({
|
|
2596
|
+
inputId,
|
|
2597
|
+
status: "contradiction",
|
|
2598
|
+
contract: null,
|
|
2599
|
+
issues
|
|
2600
|
+
})
|
|
2601
|
+
);
|
|
2602
|
+
continue;
|
|
2603
|
+
}
|
|
2604
|
+
if (contracts.length > 1) {
|
|
2605
|
+
const issues = Object.freeze([
|
|
2606
|
+
issue(
|
|
2607
|
+
"duplicate_input",
|
|
2608
|
+
"$.turns",
|
|
2609
|
+
`identical disposition repeated for input: ${inputId}`
|
|
2610
|
+
)
|
|
2611
|
+
]);
|
|
2612
|
+
dispositions.push(
|
|
2613
|
+
Object.freeze({
|
|
2614
|
+
inputId,
|
|
2615
|
+
status: "duplicate",
|
|
2616
|
+
contract: contracts[0],
|
|
2617
|
+
issues
|
|
2618
|
+
})
|
|
2619
|
+
);
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
dispositions.push(
|
|
2623
|
+
Object.freeze({
|
|
2624
|
+
inputId,
|
|
2625
|
+
status: "accepted",
|
|
2626
|
+
contract: contracts[0],
|
|
2627
|
+
issues: Object.freeze([])
|
|
2628
|
+
})
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
2631
|
+
const ok = globalIssues.length === 0 && dispositions.every(
|
|
2632
|
+
(disposition) => disposition.status === "accepted" || disposition.status === "duplicate"
|
|
2633
|
+
);
|
|
2634
|
+
return Object.freeze({
|
|
2635
|
+
ok,
|
|
2636
|
+
dispositions: Object.freeze(dispositions),
|
|
2637
|
+
issues: Object.freeze(globalIssues)
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
// packages/cli/src/tui/telegram-action-session-registry.ts
|
|
2642
|
+
var TelegramActionSessionLeaseError = class extends Error {
|
|
2643
|
+
constructor(code, message) {
|
|
2644
|
+
super(message);
|
|
2645
|
+
this.code = code;
|
|
2646
|
+
this.name = "TelegramActionSessionLeaseError";
|
|
2647
|
+
}
|
|
2648
|
+
code;
|
|
2649
|
+
};
|
|
2650
|
+
var DEFAULT_TTL_MS = 15 * 6e4;
|
|
2651
|
+
var DEFAULT_MAX_ENTRIES = 32;
|
|
2652
|
+
function nonEmpty2(value, label) {
|
|
2653
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2654
|
+
throw new TypeError(`${label} must be a non-empty string`);
|
|
2655
|
+
}
|
|
2656
|
+
return value;
|
|
2657
|
+
}
|
|
2658
|
+
function positiveInteger(value, label) {
|
|
2659
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
2660
|
+
throw new TypeError(`${label} must be a positive safe integer`);
|
|
2661
|
+
}
|
|
2662
|
+
return value;
|
|
2663
|
+
}
|
|
2664
|
+
function nonNegativeTime(value) {
|
|
2665
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
2666
|
+
throw new TypeError(
|
|
2667
|
+
"registry clock must return a non-negative finite number"
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
return value;
|
|
2671
|
+
}
|
|
2672
|
+
function createTelegramActionSessionTarget(target) {
|
|
2673
|
+
return Object.freeze({
|
|
2674
|
+
chatSessionKey: nonEmpty2(target.chatSessionKey, "target.chatSessionKey"),
|
|
2675
|
+
principalKey: nonEmpty2(target.principalKey, "target.principalKey")
|
|
2676
|
+
});
|
|
2677
|
+
}
|
|
2678
|
+
function telegramActionSessionKey(targetInput) {
|
|
2679
|
+
const target = createTelegramActionSessionTarget(targetInput);
|
|
2680
|
+
return JSON.stringify([target.chatSessionKey, target.principalKey]);
|
|
2681
|
+
}
|
|
2682
|
+
var TelegramActionSessionRegistry = class {
|
|
2683
|
+
entries = /* @__PURE__ */ new Map();
|
|
2684
|
+
create;
|
|
2685
|
+
ttlMs;
|
|
2686
|
+
maxEntries;
|
|
2687
|
+
now;
|
|
2688
|
+
onEvict;
|
|
2689
|
+
generationSequence = 0;
|
|
2690
|
+
leaseSequence = 0;
|
|
2691
|
+
closed = false;
|
|
2692
|
+
constructor(options) {
|
|
2693
|
+
this.create = options.create;
|
|
2694
|
+
this.ttlMs = positiveInteger(options.ttlMs ?? DEFAULT_TTL_MS, "ttlMs");
|
|
2695
|
+
this.maxEntries = positiveInteger(
|
|
2696
|
+
options.maxEntries ?? DEFAULT_MAX_ENTRIES,
|
|
2697
|
+
"maxEntries"
|
|
2698
|
+
);
|
|
2699
|
+
this.now = options.now ?? Date.now;
|
|
2700
|
+
this.onEvict = options.onEvict;
|
|
2701
|
+
}
|
|
2702
|
+
get size() {
|
|
2703
|
+
return this.entries.size;
|
|
2704
|
+
}
|
|
2705
|
+
acquire(options) {
|
|
2706
|
+
if (this.closed) {
|
|
2707
|
+
return {
|
|
2708
|
+
status: "closed",
|
|
2709
|
+
reason: "Telegram action session registry is closed"
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
const now = this.readNow();
|
|
2713
|
+
this.pruneExpiredAt(now);
|
|
2714
|
+
const target = createTelegramActionSessionTarget(options.target);
|
|
2715
|
+
const key = telegramActionSessionKey(target);
|
|
2716
|
+
const ownerId = nonEmpty2(options.ownerId, "ownerId");
|
|
2717
|
+
const compatibilityKey = nonEmpty2(
|
|
2718
|
+
options.compatibilityKey,
|
|
2719
|
+
"compatibilityKey"
|
|
2720
|
+
);
|
|
2721
|
+
let entry = this.entries.get(key);
|
|
2722
|
+
if (entry && entry.compatibilityKey !== compatibilityKey) {
|
|
2723
|
+
if (entry.lease) {
|
|
2724
|
+
return {
|
|
2725
|
+
status: "busy",
|
|
2726
|
+
reason: "active session has incompatible runner configuration",
|
|
2727
|
+
current: this.snapshotEntry(entry)
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
this.evictEntry(entry, "incompatible");
|
|
2731
|
+
entry = void 0;
|
|
2732
|
+
}
|
|
2733
|
+
if (!entry) {
|
|
2734
|
+
while (this.entries.size >= this.maxEntries) {
|
|
2735
|
+
const evicted = this.evictLeastRecentlyUsedIdle();
|
|
2736
|
+
if (!evicted) {
|
|
2737
|
+
return {
|
|
2738
|
+
status: "capacity",
|
|
2739
|
+
reason: "all bounded Telegram action sessions are active"
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
entry = this.createEntry(target, key, compatibilityKey, now);
|
|
2744
|
+
this.entries.set(key, entry);
|
|
2745
|
+
}
|
|
2746
|
+
if (entry.lease) {
|
|
2747
|
+
return {
|
|
2748
|
+
status: "busy",
|
|
2749
|
+
reason: "Telegram action session already has an active lease",
|
|
2750
|
+
current: this.snapshotEntry(entry)
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
const token = Object.freeze({
|
|
2754
|
+
key,
|
|
2755
|
+
generation: entry.generation,
|
|
2756
|
+
leaseId: ++this.leaseSequence,
|
|
2757
|
+
ownerId
|
|
2758
|
+
});
|
|
2759
|
+
entry.lease = {
|
|
2760
|
+
token,
|
|
2761
|
+
onEvent: options.onEvent,
|
|
2762
|
+
onTypedEvent: options.onTypedEvent,
|
|
2763
|
+
runStarted: false
|
|
2764
|
+
};
|
|
2765
|
+
entry.lastUsedAtMs = now;
|
|
2766
|
+
const lease = Object.freeze({
|
|
2767
|
+
token,
|
|
2768
|
+
target: entry.target,
|
|
2769
|
+
run: (input) => this.runLease(token, input),
|
|
2770
|
+
abort: () => this.abortLease(token),
|
|
2771
|
+
release: () => this.releaseLease(token)
|
|
2772
|
+
});
|
|
2773
|
+
return { status: "acquired", lease };
|
|
2774
|
+
}
|
|
2775
|
+
pruneExpired() {
|
|
2776
|
+
return this.pruneExpiredAt(this.readNow());
|
|
2777
|
+
}
|
|
2778
|
+
snapshot() {
|
|
2779
|
+
return Object.freeze(
|
|
2780
|
+
[...this.entries.values()].sort(
|
|
2781
|
+
(left, right) => left.lastUsedAtMs - right.lastUsedAtMs || left.generation - right.generation
|
|
2782
|
+
).map((entry) => this.snapshotEntry(entry))
|
|
2783
|
+
);
|
|
2784
|
+
}
|
|
2785
|
+
/** Abort active work, dispose every cached resource, and reject new leases. */
|
|
2786
|
+
shutdown() {
|
|
2787
|
+
if (this.closed) return;
|
|
2788
|
+
this.closed = true;
|
|
2789
|
+
for (const entry of [...this.entries.values()]) {
|
|
2790
|
+
this.evictEntry(entry, "shutdown");
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
createEntry(target, key, compatibilityKey, now) {
|
|
2794
|
+
const generation = ++this.generationSequence;
|
|
2795
|
+
let entry;
|
|
2796
|
+
const resource = this.create(
|
|
2797
|
+
Object.freeze({
|
|
2798
|
+
key,
|
|
2799
|
+
target,
|
|
2800
|
+
generation,
|
|
2801
|
+
onTypedEvent: (event) => {
|
|
2802
|
+
if (entry) this.dispatchTypedEvent(entry, event);
|
|
2803
|
+
}
|
|
2804
|
+
})
|
|
2805
|
+
);
|
|
2806
|
+
if (!resource || !resource.runner) {
|
|
2807
|
+
throw new TypeError("Telegram action session factory returned no runner");
|
|
2808
|
+
}
|
|
2809
|
+
entry = {
|
|
2810
|
+
key,
|
|
2811
|
+
target,
|
|
2812
|
+
generation,
|
|
2813
|
+
compatibilityKey,
|
|
2814
|
+
runner: resource.runner,
|
|
2815
|
+
dispose: resource.dispose,
|
|
2816
|
+
createdAtMs: now,
|
|
2817
|
+
lastUsedAtMs: now,
|
|
2818
|
+
lease: null,
|
|
2819
|
+
disposed: false
|
|
2820
|
+
};
|
|
2821
|
+
resource.runner.onEvent((event) => {
|
|
2822
|
+
if (entry) this.dispatchEvent(entry, event);
|
|
2823
|
+
});
|
|
2824
|
+
return entry;
|
|
2825
|
+
}
|
|
2826
|
+
async runLease(token, input) {
|
|
2827
|
+
const owned = this.ownedEntry(token);
|
|
2828
|
+
if (!owned) {
|
|
2829
|
+
throw new TelegramActionSessionLeaseError(
|
|
2830
|
+
"stale_lease",
|
|
2831
|
+
"Telegram action session lease is no longer authoritative"
|
|
2832
|
+
);
|
|
2833
|
+
}
|
|
2834
|
+
if (owned.lease.runStarted) {
|
|
2835
|
+
throw new TelegramActionSessionLeaseError(
|
|
2836
|
+
"already_started",
|
|
2837
|
+
"Telegram action session lease can run only once"
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
owned.lease.runStarted = true;
|
|
2841
|
+
try {
|
|
2842
|
+
await input.prepare?.(owned.entry.runner);
|
|
2843
|
+
return await owned.entry.runner.run(
|
|
2844
|
+
input.task,
|
|
2845
|
+
input.context,
|
|
2846
|
+
input.actualUserGoal
|
|
2847
|
+
);
|
|
2848
|
+
} finally {
|
|
2849
|
+
this.finishLease(token);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
abortLease(token) {
|
|
2853
|
+
const owned = this.ownedEntry(token);
|
|
2854
|
+
if (!owned) return false;
|
|
2855
|
+
try {
|
|
2856
|
+
owned.entry.runner.abort();
|
|
2857
|
+
} catch {
|
|
2858
|
+
}
|
|
2859
|
+
return true;
|
|
2860
|
+
}
|
|
2861
|
+
releaseLease(token) {
|
|
2862
|
+
const owned = this.ownedEntry(token);
|
|
2863
|
+
if (!owned) return "stale";
|
|
2864
|
+
if (owned.lease.runStarted) return "running";
|
|
2865
|
+
owned.entry.lease = null;
|
|
2866
|
+
owned.entry.lastUsedAtMs = this.readNow();
|
|
2867
|
+
return "released";
|
|
2868
|
+
}
|
|
2869
|
+
finishLease(token) {
|
|
2870
|
+
const owned = this.ownedEntry(token);
|
|
2871
|
+
if (!owned) return;
|
|
2872
|
+
owned.entry.lease = null;
|
|
2873
|
+
owned.entry.lastUsedAtMs = this.readNow();
|
|
2874
|
+
}
|
|
2875
|
+
ownedEntry(token) {
|
|
2876
|
+
const entry = this.entries.get(token.key);
|
|
2877
|
+
if (!entry || entry.generation !== token.generation || !entry.lease || entry.lease.token !== token) {
|
|
2878
|
+
return null;
|
|
2879
|
+
}
|
|
2880
|
+
return { entry, lease: entry.lease };
|
|
2881
|
+
}
|
|
2882
|
+
dispatchEvent(entry, event) {
|
|
2883
|
+
const lease = entry.lease;
|
|
2884
|
+
if (!lease?.runStarted || !lease.onEvent) return;
|
|
2885
|
+
try {
|
|
2886
|
+
lease.onEvent(event, lease.token);
|
|
2887
|
+
} catch {
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
dispatchTypedEvent(entry, event) {
|
|
2891
|
+
const lease = entry.lease;
|
|
2892
|
+
if (!lease?.runStarted || !lease.onTypedEvent) return;
|
|
2893
|
+
try {
|
|
2894
|
+
lease.onTypedEvent(event, lease.token);
|
|
2895
|
+
} catch {
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
pruneExpiredAt(now) {
|
|
2899
|
+
let evicted = 0;
|
|
2900
|
+
for (const entry of [...this.entries.values()]) {
|
|
2901
|
+
if (entry.lease || now - entry.lastUsedAtMs < this.ttlMs) continue;
|
|
2902
|
+
this.evictEntry(entry, "ttl");
|
|
2903
|
+
evicted++;
|
|
2904
|
+
}
|
|
2905
|
+
return evicted;
|
|
2906
|
+
}
|
|
2907
|
+
evictLeastRecentlyUsedIdle() {
|
|
2908
|
+
const candidate = [...this.entries.values()].filter((entry) => !entry.lease).sort(
|
|
2909
|
+
(left, right) => left.lastUsedAtMs - right.lastUsedAtMs || left.generation - right.generation
|
|
2910
|
+
)[0];
|
|
2911
|
+
if (!candidate) return false;
|
|
2912
|
+
this.evictEntry(candidate, "lru");
|
|
2913
|
+
return true;
|
|
2914
|
+
}
|
|
2915
|
+
evictEntry(entry, reason) {
|
|
2916
|
+
if (entry.disposed) return;
|
|
2917
|
+
entry.disposed = true;
|
|
2918
|
+
const snapshot = this.snapshotEntry(entry);
|
|
2919
|
+
entry.lease = null;
|
|
2920
|
+
this.entries.delete(entry.key);
|
|
2921
|
+
try {
|
|
2922
|
+
entry.runner.abort();
|
|
2923
|
+
} catch {
|
|
2924
|
+
}
|
|
2925
|
+
try {
|
|
2926
|
+
entry.dispose?.();
|
|
2927
|
+
} catch {
|
|
2928
|
+
}
|
|
2929
|
+
try {
|
|
2930
|
+
this.onEvict?.(snapshot, reason);
|
|
2931
|
+
} catch {
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
snapshotEntry(entry) {
|
|
2935
|
+
return Object.freeze({
|
|
2936
|
+
key: entry.key,
|
|
2937
|
+
target: entry.target,
|
|
2938
|
+
generation: entry.generation,
|
|
2939
|
+
compatibilityKey: entry.compatibilityKey,
|
|
2940
|
+
createdAtMs: entry.createdAtMs,
|
|
2941
|
+
lastUsedAtMs: entry.lastUsedAtMs,
|
|
2942
|
+
busy: Boolean(entry.lease),
|
|
2943
|
+
ownerId: entry.lease?.token.ownerId,
|
|
2944
|
+
leaseId: entry.lease?.token.leaseId,
|
|
2945
|
+
runStarted: entry.lease?.runStarted ?? false
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
readNow() {
|
|
2949
|
+
return nonNegativeTime(this.now());
|
|
2950
|
+
}
|
|
2951
|
+
};
|
|
992
2952
|
export {
|
|
993
2953
|
DISCOVERY_KINDS,
|
|
994
2954
|
OmniusVersionError,
|
|
2955
|
+
TELEGRAM_COGNITION_AUTHORITY,
|
|
2956
|
+
TELEGRAM_CONTEXT_PACKET_SCHEMA_VERSION,
|
|
2957
|
+
TELEGRAM_TURN_CONTRACT_V2_VERSION,
|
|
2958
|
+
TelegramActionSessionLeaseError,
|
|
2959
|
+
TelegramActionSessionRegistry,
|
|
2960
|
+
TelegramCognitionQueue,
|
|
2961
|
+
TelegramCognitionQueueValidationError,
|
|
2962
|
+
TelegramContextPacketBudgetError,
|
|
2963
|
+
TelegramContextPacketRevisionConflictError,
|
|
2964
|
+
TelegramContextPacketValidationError,
|
|
2965
|
+
TelegramInputOwnershipLedger,
|
|
995
2966
|
UnknownProviderProtocolError,
|
|
996
2967
|
assertServiceVersion,
|
|
997
2968
|
buildProviderHeaders,
|
|
998
2969
|
compareOmniusVersions,
|
|
2970
|
+
compileTelegramContextPacketV2,
|
|
2971
|
+
createTelegramActionSessionTarget,
|
|
2972
|
+
createTelegramInputTargetBinding,
|
|
999
2973
|
detectProviderDescriptor,
|
|
1000
2974
|
discoverCapabilities,
|
|
1001
2975
|
discoveryBootstrap,
|
|
@@ -1003,6 +2977,7 @@ export {
|
|
|
1003
2977
|
discoverySchemaVersion,
|
|
1004
2978
|
findDiscoveryCatalogPath,
|
|
1005
2979
|
getServiceVersion,
|
|
2980
|
+
isTelegramInputTerminalLifecycle,
|
|
1006
2981
|
isVersionGatedExecutionRequest,
|
|
1007
2982
|
listProviderDescriptors,
|
|
1008
2983
|
loadDiscoveryCatalog,
|
|
@@ -1011,5 +2986,10 @@ export {
|
|
|
1011
2986
|
rankCapabilities,
|
|
1012
2987
|
resolveProviderDescriptor,
|
|
1013
2988
|
resolveProviderTransport,
|
|
1014
|
-
showCapability
|
|
2989
|
+
showCapability,
|
|
2990
|
+
telegramActionSessionKey,
|
|
2991
|
+
telegramInputTargetsEqual,
|
|
2992
|
+
telegramTurnContractV2Fingerprint,
|
|
2993
|
+
validateTelegramTurnBatchV2,
|
|
2994
|
+
validateTelegramTurnContractV2
|
|
1015
2995
|
};
|