@llblab/pi-kit 0.15.0 → 0.17.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.
Files changed (120) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -2
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +12 -12
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +2 -13
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +31 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +11 -7
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +5 -2
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +17 -16
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -0
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +24 -0
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +4 -3
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +2 -1
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +15 -3
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +2 -0
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +4 -0
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +7 -4
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +124 -274
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +20 -23
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +8 -4
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +29 -3
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +18 -0
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +44 -1
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +2 -2
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +55 -21
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +7 -0
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +65 -17
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +17 -0
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +102 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +43 -1
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +268 -10
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +2 -0
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +34 -5
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +17 -0
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +38 -0
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +5 -5
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +20 -24
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +11 -3
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +14 -4
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +1 -1
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +2 -0
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +55 -20
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +9 -6
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +10 -3
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +8 -3
  45. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  46. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +79 -0
  47. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +23 -121
  48. package/node_modules/@llblab/pi-state-flow/docs/README.md +1 -0
  49. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +27 -20
  50. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +1 -1
  51. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +3 -2
  52. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +513 -0
  53. package/node_modules/@llblab/pi-state-flow/docs/usage.md +13 -10
  54. package/node_modules/@llblab/pi-state-flow/lib/config.ts +18 -15
  55. package/node_modules/@llblab/pi-state-flow/lib/context.ts +24 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +4 -3
  57. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +14 -5
  58. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +5 -0
  59. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +119 -262
  60. package/node_modules/@llblab/pi-state-flow/lib/git.ts +23 -22
  61. package/node_modules/@llblab/pi-state-flow/lib/history.ts +8 -4
  62. package/node_modules/@llblab/pi-state-flow/lib/json.ts +31 -3
  63. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +53 -1
  64. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +51 -20
  65. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +61 -17
  66. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +96 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/query.ts +261 -9
  68. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +32 -4
  69. package/node_modules/@llblab/pi-state-flow/lib/session.ts +45 -0
  70. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +22 -25
  71. package/node_modules/@llblab/pi-state-flow/lib/state.ts +22 -6
  72. package/node_modules/@llblab/pi-state-flow/lib/status.ts +1 -1
  73. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +46 -18
  74. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +20 -6
  75. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +8 -3
  76. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  77. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +79 -0
  78. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +23 -121
  79. package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
  80. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -0
  81. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +7 -0
  82. package/node_modules/@llblab/pi-telegram/README.md +1 -0
  83. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -1
  84. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +2 -1
  85. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +134 -2
  86. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +297 -16
  87. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -0
  88. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +11 -0
  89. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +68 -8
  90. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +4 -3
  91. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +17 -13
  92. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -1
  93. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +1 -0
  94. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
  95. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +55 -5
  96. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +23 -0
  97. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +20 -0
  98. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +18 -0
  99. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +152 -6
  100. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +1 -0
  101. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +5 -3
  102. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  103. package/node_modules/@llblab/pi-telegram/docs/architecture.md +3 -3
  104. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
  105. package/node_modules/@llblab/pi-telegram/docs/public-api.md +4 -3
  106. package/node_modules/@llblab/pi-telegram/docs/sections.md +2 -2
  107. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +2 -1
  108. package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
  109. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +3 -0
  110. package/node_modules/@llblab/pi-telegram/lib/commands.ts +466 -21
  111. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +15 -0
  112. package/node_modules/@llblab/pi-telegram/lib/extension.ts +77 -9
  113. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +25 -10
  114. package/node_modules/@llblab/pi-telegram/lib/pi.ts +3 -0
  115. package/node_modules/@llblab/pi-telegram/lib/routing.ts +84 -17
  116. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +30 -0
  117. package/node_modules/@llblab/pi-telegram/lib/threads.ts +199 -6
  118. package/node_modules/@llblab/pi-telegram/lib/updates.ts +5 -2
  119. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  120. package/package.json +3 -3
@@ -358,6 +358,20 @@ export interface TelegramBotStateSnapshot {
358
358
  lastReconcileAction?: string;
359
359
  }
360
360
 
361
+ export interface TelegramSessionReplacementIntent {
362
+ continuity: "workspace-thread" | "classic-chat";
363
+ cwd: string;
364
+ profileName: string;
365
+ sourceSessionId: string;
366
+ sourceUpdateId: number;
367
+ target: TelegramTarget;
368
+ messageId: number;
369
+ slot?: string;
370
+ threadName?: string;
371
+ createdAtMs: number;
372
+ expiresAtMs: number;
373
+ }
374
+
361
375
  export interface TelegramTopicTargetFile {
362
376
  version: 1;
363
377
  source: "snapshot";
@@ -370,6 +384,7 @@ export interface TelegramTopicTargetFile {
370
384
  identities?: TelegramThreadIdentityRecord[];
371
385
  workspaceBindings?: TelegramWorkspaceThreadBinding[];
372
386
  workspaceRetirements?: TelegramWorkspaceRetirementIntent[];
387
+ sessionReplacement?: TelegramSessionReplacementIntent;
373
388
  reservations?: TelegramThreadReservation[];
374
389
  pendingProvisions?: TelegramThreadPendingProvision[];
375
390
  pendingCleanups?: TelegramThreadCleanupIntent[];
@@ -478,6 +493,19 @@ export interface TelegramTopicTargetStore {
478
493
  ) => TelegramThreadIdentityRecord | undefined;
479
494
  forgetIdentityByProfileKey: (profileKey: string) => boolean;
480
495
  listWorkspaceBindings: () => TelegramWorkspaceThreadBinding[];
496
+ getWorkspaceBindingByTarget: (
497
+ target: TelegramTarget,
498
+ sessionId?: string,
499
+ ) => TelegramWorkspaceThreadBinding | undefined;
500
+ getSessionReplacementIntent: () => TelegramSessionReplacementIntent | undefined;
501
+ commitSessionReplacementIntent: (
502
+ intent: TelegramSessionReplacementIntent,
503
+ isCurrent: () => boolean,
504
+ ) => Promise<boolean>;
505
+ removeSessionReplacementIntent: (
506
+ expected: TelegramSessionReplacementIntent,
507
+ isCurrent: () => boolean,
508
+ ) => Promise<boolean>;
481
509
  listWorkspaceRetirementIntents: () => TelegramWorkspaceRetirementIntent[];
482
510
  commitWorkspaceJournalEvidence: (
483
511
  expected: TelegramWorkspaceThreadBinding,
@@ -1289,6 +1317,56 @@ function cloneWorkspaceBinding(
1289
1317
  return { ...binding, target: { ...binding.target } };
1290
1318
  }
1291
1319
 
1320
+ function cloneSessionReplacementIntent(
1321
+ intent: TelegramSessionReplacementIntent,
1322
+ ): TelegramSessionReplacementIntent {
1323
+ return { ...intent, target: { ...intent.target } };
1324
+ }
1325
+
1326
+ function normalizeSessionReplacementIntent(
1327
+ value: unknown,
1328
+ ): TelegramSessionReplacementIntent | undefined {
1329
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
1330
+ const record = value as Record<string, unknown>;
1331
+ const target = record.target as Record<string, unknown> | undefined;
1332
+ if (
1333
+ typeof record.cwd !== "string" || !normalizeTelegramWorkspacePath(record.cwd) ||
1334
+ typeof record.profileName !== "string" || !record.profileName ||
1335
+ typeof record.sourceSessionId !== "string" || !normalizeTelegramSessionId(record.sourceSessionId) ||
1336
+ typeof record.sourceUpdateId !== "number" || !Number.isSafeInteger(record.sourceUpdateId) ||
1337
+ !target || typeof target.chatId !== "number" ||
1338
+ (target.threadId !== undefined &&
1339
+ (typeof target.threadId !== "number" || !Number.isSafeInteger(target.threadId))) ||
1340
+ typeof record.messageId !== "number" || !Number.isSafeInteger(record.messageId) ||
1341
+ typeof record.createdAtMs !== "number" || !Number.isSafeInteger(record.createdAtMs) ||
1342
+ typeof record.expiresAtMs !== "number" || !Number.isSafeInteger(record.expiresAtMs) ||
1343
+ record.expiresAtMs <= record.createdAtMs
1344
+ ) return undefined;
1345
+ const continuity = record.continuity === "workspace-thread" ||
1346
+ record.continuity === "classic-chat"
1347
+ ? record.continuity
1348
+ : target.threadId !== undefined ? "workspace-thread" : "classic-chat";
1349
+ if ((continuity === "workspace-thread") !== (target.threadId !== undefined)) {
1350
+ return undefined;
1351
+ }
1352
+ return {
1353
+ continuity,
1354
+ cwd: normalizeTelegramWorkspacePath(record.cwd)!,
1355
+ profileName: record.profileName,
1356
+ sourceSessionId: normalizeTelegramSessionId(record.sourceSessionId)!,
1357
+ sourceUpdateId: record.sourceUpdateId,
1358
+ target: {
1359
+ chatId: target.chatId,
1360
+ ...(typeof target.threadId === "number" ? { threadId: target.threadId } : {}),
1361
+ },
1362
+ messageId: record.messageId,
1363
+ ...(typeof record.slot === "string" ? { slot: record.slot } : {}),
1364
+ ...(typeof record.threadName === "string" ? { threadName: record.threadName } : {}),
1365
+ createdAtMs: record.createdAtMs,
1366
+ expiresAtMs: record.expiresAtMs,
1367
+ };
1368
+ }
1369
+
1292
1370
  function normalizeWorkspaceRetirementIntent(
1293
1371
  value: unknown,
1294
1372
  ): TelegramWorkspaceRetirementIntent | undefined {
@@ -1596,6 +1674,7 @@ function parseTopicTargetFile(value: unknown): TelegramTopicTargetFile {
1596
1674
  return normalized ? [normalized] : [];
1597
1675
  })
1598
1676
  : [],
1677
+ sessionReplacement: normalizeSessionReplacementIntent(file.sessionReplacement),
1599
1678
  reservations: Array.isArray(file.reservations)
1600
1679
  ? file.reservations.flatMap((reservation) => {
1601
1680
  const normalized = normalizeReservation(reservation);
@@ -1740,6 +1819,7 @@ export function createTelegramTopicTargetStore(
1740
1819
  let identities = new Map<string, TelegramThreadIdentityRecord>();
1741
1820
  let workspaceBindings = new Map<string, TelegramWorkspaceThreadBinding>();
1742
1821
  let workspaceRetirements: TelegramWorkspaceRetirementIntent[] = [];
1822
+ let sessionReplacement: TelegramSessionReplacementIntent | undefined;
1743
1823
  let workspaceRetirementCommitInFlight = false;
1744
1824
  const hasWorkspaceRetirementConflict = (input: {
1745
1825
  bindingKey?: string;
@@ -1906,6 +1986,7 @@ export function createTelegramTopicTargetStore(
1906
1986
  identities = new Map();
1907
1987
  workspaceBindings = new Map();
1908
1988
  workspaceRetirements = [];
1989
+ sessionReplacement = undefined;
1909
1990
  workspaceRetirementCommitInFlight = false;
1910
1991
  workspaceClaims = new Map();
1911
1992
  reservations = [];
@@ -1928,6 +2009,7 @@ export function createTelegramTopicTargetStore(
1928
2009
  identities = new Map();
1929
2010
  workspaceBindings = new Map();
1930
2011
  workspaceRetirements = [];
2012
+ sessionReplacement = undefined;
1931
2013
  workspaceRetirementCommitInFlight = false;
1932
2014
  reservations = [];
1933
2015
  pendingProvisions = [];
@@ -1976,6 +2058,9 @@ export function createTelegramTopicTargetStore(
1976
2058
  workspaceRetirements = (file.workspaceRetirements ?? []).map(
1977
2059
  cloneWorkspaceRetirementIntent,
1978
2060
  );
2061
+ sessionReplacement = file.sessionReplacement
2062
+ ? cloneSessionReplacementIntent(file.sessionReplacement)
2063
+ : undefined;
1979
2064
  reconcileWorkspaceSuffixExposure();
1980
2065
  for (const record of records.values()) rememberIdentity(record);
1981
2066
  const nowMs = getNowMs();
@@ -2070,6 +2155,9 @@ export function createTelegramTopicTargetStore(
2070
2155
  workspaceRetirements: workspaceRetirements.map(
2071
2156
  cloneWorkspaceRetirementIntent,
2072
2157
  ),
2158
+ ...(sessionReplacement
2159
+ ? { sessionReplacement: cloneSessionReplacementIntent(sessionReplacement) }
2160
+ : {}),
2073
2161
  reservations: reservations.map((reservation) => ({ ...reservation })),
2074
2162
  pendingProvisions: pendingProvisions.map((provision) => ({
2075
2163
  ...provision,
@@ -2366,6 +2454,38 @@ export function createTelegramTopicTargetStore(
2366
2454
  listWorkspaceBindings() {
2367
2455
  return Array.from(workspaceBindings.values()).map(cloneWorkspaceBinding);
2368
2456
  },
2457
+ getWorkspaceBindingByTarget(target, sessionId) {
2458
+ const binding = Array.from(workspaceBindings.values()).find((candidate) =>
2459
+ targetMatches(candidate.target, target) &&
2460
+ (sessionId === undefined || candidate.sessionId === sessionId)
2461
+ );
2462
+ return binding ? cloneWorkspaceBinding(binding) : undefined;
2463
+ },
2464
+ getSessionReplacementIntent() {
2465
+ return sessionReplacement
2466
+ ? cloneSessionReplacementIntent(sessionReplacement)
2467
+ : undefined;
2468
+ },
2469
+ async commitSessionReplacementIntent(intent, isCurrent) {
2470
+ const next = normalizeSessionReplacementIntent(intent);
2471
+ if (!next || !isCurrent()) return false;
2472
+ await loadFromDisk();
2473
+ if (!isCurrent()) return false;
2474
+ const existing = sessionReplacement;
2475
+ if (existing && existing.expiresAtMs > getNowMs() &&
2476
+ !isDeepStrictEqual(existing, next)) return false;
2477
+ sessionReplacement = cloneSessionReplacementIntent(next);
2478
+ markDirty();
2479
+ return await persistSnapshot() && isCurrent();
2480
+ },
2481
+ async removeSessionReplacementIntent(expected, isCurrent) {
2482
+ await loadFromDisk();
2483
+ if (!isCurrent() || !sessionReplacement ||
2484
+ !isDeepStrictEqual(sessionReplacement, expected)) return false;
2485
+ sessionReplacement = undefined;
2486
+ markDirty();
2487
+ return await persistSnapshot() && isCurrent();
2488
+ },
2369
2489
  listWorkspaceRetirementIntents() {
2370
2490
  return workspaceRetirements.map(cloneWorkspaceRetirementIntent);
2371
2491
  },
@@ -2764,6 +2884,47 @@ export function createTelegramTopicTargetStore(
2764
2884
  if (!normalizedCwd || !instanceId ||
2765
2885
  (options?.sessionId !== undefined && !normalizedSessionId) ||
2766
2886
  hasWorkspaceRetirementConflict({ cwd: normalizedCwd })) return undefined;
2887
+ let replacementPreviousInstanceId: string | undefined;
2888
+ const replacement = sessionReplacement;
2889
+ if (
2890
+ replacement?.continuity === "workspace-thread" && normalizedSessionId &&
2891
+ replacement.expiresAtMs > getNowMs() &&
2892
+ replacement.profileName === (getTelegramProfile() ?? "default") &&
2893
+ replacement.cwd === normalizedCwd &&
2894
+ replacement.sourceSessionId !== normalizedSessionId
2895
+ ) {
2896
+ const sourceEntry = Array.from(workspaceBindings.entries()).find(([, binding]) =>
2897
+ binding.cwd === normalizedCwd &&
2898
+ binding.sessionId === replacement.sourceSessionId &&
2899
+ targetMatches(binding.target, replacement.target)
2900
+ );
2901
+ if (sourceEntry) {
2902
+ const [sourceKey, sourceBinding] = sourceEntry;
2903
+ const replacementIdentity = Array.from({ length: TELEGRAM_WORKSPACE_SLOTS.length })
2904
+ .map((_, ordinal) => createTelegramWorkspaceBindingIdentityWithKey(
2905
+ sourceBinding.cwd,
2906
+ sourceBinding.workspaceKey,
2907
+ ordinal,
2908
+ normalizedSessionId,
2909
+ ))
2910
+ .find((identity) => identity?.instanceSlot === sourceBinding.instanceSlot);
2911
+ if (replacementIdentity &&
2912
+ replacementIdentity.instanceSlot === sourceBinding.instanceSlot) {
2913
+ const existingTargetRecord = Array.from(records.values()).find((record) =>
2914
+ targetMatches(record.target, replacement.target)
2915
+ );
2916
+ replacementPreviousInstanceId = existingTargetRecord?.instanceId;
2917
+ workspaceBindings.delete(sourceKey);
2918
+ workspaceBindings.set(getWorkspaceBindingMapKey(replacementIdentity), {
2919
+ ...sourceBinding,
2920
+ ...replacementIdentity,
2921
+ updatedAtMs: getNowMs(),
2922
+ });
2923
+ markDirty();
2924
+ }
2925
+ }
2926
+ }
2927
+ const effectivePreviousInstanceId = previousInstanceId ?? replacementPreviousInstanceId;
2767
2928
  const externalReservedSlots = captureExternalReservedSlots();
2768
2929
  if (!externalReservedSlots) {
2769
2930
  options?.onCapacityUnavailable?.();
@@ -2810,9 +2971,15 @@ export function createTelegramTopicTargetStore(
2810
2971
  const mapKey = getWorkspaceBindingMapKey(identity);
2811
2972
  const existingClaim = workspaceClaims.get(mapKey);
2812
2973
  if (existingClaim) {
2813
- return existingClaim.instanceId === instanceId
2814
- ? { ...existingClaim.identity }
2815
- : undefined;
2974
+ if (existingClaim.instanceId === instanceId) {
2975
+ return { ...existingClaim.identity };
2976
+ }
2977
+ if (existingClaim.instanceId !== effectivePreviousInstanceId) return undefined;
2978
+ workspaceClaims.set(mapKey, {
2979
+ identity: existingClaim.identity,
2980
+ instanceId,
2981
+ });
2982
+ return { ...existingClaim.identity };
2816
2983
  }
2817
2984
  const binding = workspaceBindings.get(mapKey);
2818
2985
  const liveRecord = binding
@@ -2821,7 +2988,7 @@ export function createTelegramTopicTargetStore(
2821
2988
  if (
2822
2989
  liveRecord &&
2823
2990
  liveRecord.instanceId !== instanceId &&
2824
- liveRecord.instanceId !== previousInstanceId
2991
+ liveRecord.instanceId !== effectivePreviousInstanceId
2825
2992
  ) {
2826
2993
  return undefined;
2827
2994
  }
@@ -2963,9 +3130,35 @@ export function createTelegramTopicTargetStore(
2963
3130
  if (next && hasWorkspaceRetirementConflict(next)) return undefined;
2964
3131
  if (!next) return undefined;
2965
3132
  const nextMapKey = getWorkspaceBindingMapKey(next);
2966
- const claim = workspaceClaims.get(nextMapKey);
3133
+ let claim = workspaceClaims.get(nextMapKey);
3134
+ const replacedTargetBinding = Array.from(workspaceBindings.values()).find(
3135
+ (existing) =>
3136
+ existing.bindingKey !== next.bindingKey &&
3137
+ targetMatches(existing.target, next.target),
3138
+ );
3139
+ if (
3140
+ claimInstanceId && claim?.instanceId === claimInstanceId &&
3141
+ replacedTargetBinding?.slot
3142
+ ) {
3143
+ claim = {
3144
+ ...claim,
3145
+ identity: { ...claim.identity, slot: replacedTargetBinding.slot },
3146
+ };
3147
+ workspaceClaims.set(nextMapKey, claim);
3148
+ next.slot = replacedTargetBinding.slot;
3149
+ if (replacedTargetBinding.threadName && !next.threadName) {
3150
+ next.threadName = replacedTargetBinding.threadName;
3151
+ }
3152
+ if (replacedTargetBinding.manualThreadName && !next.manualThreadName) {
3153
+ next.manualThreadName = replacedTargetBinding.manualThreadName;
3154
+ }
3155
+ if (replacedTargetBinding.displayTitle && !next.displayTitle) {
3156
+ next.displayTitle = replacedTargetBinding.displayTitle;
3157
+ }
3158
+ }
2967
3159
  if (next.slot && Array.from(workspaceBindings.values()).some((existing) =>
2968
- existing.bindingKey !== next.bindingKey && existing.slot === next.slot,
3160
+ existing.bindingKey !== next.bindingKey && existing.slot === next.slot &&
3161
+ !targetMatches(existing.target, next.target),
2969
3162
  )) return undefined;
2970
3163
  for (const existing of workspaceBindings.values()) {
2971
3164
  if (
@@ -5517,6 +5517,7 @@ export interface TelegramUpdateWorkerOwnerRuntimeDeps<TContext> {
5517
5517
  isContextCurrent: (ctx: TContext) => boolean;
5518
5518
  dispatchNext: (ctx: TContext) => void;
5519
5519
  requestQueueHandoffReconciliation: (ctx: TContext) => void;
5520
+ afterUpdateCompleted?: (updateId: number) => void;
5520
5521
  }
5521
5522
 
5522
5523
  export function createTelegramUpdateWorkerOwnerRuntime<TContext>(
@@ -5536,8 +5537,10 @@ export function createTelegramUpdateWorkerOwnerRuntime<TContext>(
5536
5537
  deps.dispatchNext(ctx);
5537
5538
  deps.requestQueueHandoffReconciliation(ctx);
5538
5539
  },
5539
- onUpdateCompleted(_updateId, ctx) {
5540
- if (deps.isContextCurrent(ctx)) deps.dispatchNext(ctx);
5540
+ onUpdateCompleted(updateId, ctx) {
5541
+ if (!deps.isContextCurrent(ctx)) return;
5542
+ deps.dispatchNext(ctx);
5543
+ deps.afterUpdateCompleted?.(updateId);
5541
5544
  },
5542
5545
  };
5543
5546
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.48.3",
3
+ "version": "0.49.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-kit",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -44,8 +44,8 @@
44
44
  "@llblab/pi-clean-room": "0.1.1",
45
45
  "@llblab/pi-codex-usage": "0.10.0",
46
46
  "@llblab/pi-grow-loop": "0.8.1",
47
- "@llblab/pi-state-flow": "0.13.3",
48
- "@llblab/pi-telegram": "0.48.3",
47
+ "@llblab/pi-state-flow": "0.16.0",
48
+ "@llblab/pi-telegram": "0.49.0",
49
49
  "@llblab/skills": "1.15.0"
50
50
  },
51
51
  "bundledDependencies": [