@llblab/pi-telegram 0.45.11 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKLOG.md +5 -1
- package/CHANGELOG.md +12 -0
- package/README.md +6 -6
- package/dist/api/activity.d.ts +6 -0
- package/dist/api/activity.js +6 -0
- package/dist/api/commands.d.ts +6 -0
- package/dist/api/commands.js +6 -0
- package/dist/api/delivery.d.ts +6 -0
- package/dist/api/delivery.js +6 -0
- package/dist/api/inbound.d.ts +6 -0
- package/dist/api/inbound.js +6 -0
- package/dist/api/keyboard.d.ts +6 -0
- package/dist/api/keyboard.js +6 -0
- package/dist/api/outbound.d.ts +6 -0
- package/dist/api/outbound.js +6 -0
- package/dist/api/sections.d.ts +7 -0
- package/dist/api/sections.js +6 -0
- package/dist/api/status.d.ts +6 -0
- package/dist/api/status.js +6 -0
- package/dist/api/updates.d.ts +6 -0
- package/dist/api/updates.js +6 -0
- package/dist/api/voice.d.ts +6 -0
- package/dist/api/voice.js +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/activity-verbosity.d.ts +52 -0
- package/dist/lib/activity-verbosity.js +596 -0
- package/dist/lib/activity.d.ts +220 -0
- package/dist/lib/activity.js +574 -0
- package/dist/lib/agent-messages.d.ts +28 -0
- package/dist/lib/agent-messages.js +86 -0
- package/dist/lib/bindings.d.ts +250 -0
- package/dist/lib/bindings.js +936 -0
- package/dist/lib/bus-api.d.ts +18 -0
- package/dist/lib/bus-api.js +254 -0
- package/dist/lib/bus-follower.d.ts +464 -0
- package/dist/lib/bus-follower.js +1686 -0
- package/dist/lib/bus-leader.d.ts +292 -0
- package/dist/lib/bus-leader.js +2242 -0
- package/dist/lib/bus-transport.d.ts +64 -0
- package/dist/lib/bus-transport.js +140 -0
- package/dist/lib/bus.d.ts +518 -0
- package/dist/lib/bus.js +2055 -0
- package/dist/lib/channel-posts.d.ts +170 -0
- package/dist/lib/channel-posts.js +611 -0
- package/dist/lib/command-templates.d.ts +70 -0
- package/dist/lib/command-templates.js +744 -0
- package/dist/lib/commands.d.ts +541 -0
- package/dist/lib/commands.js +1155 -0
- package/dist/lib/config.d.ts +252 -0
- package/dist/lib/config.js +889 -0
- package/dist/lib/delivery.d.ts +163 -0
- package/dist/lib/delivery.js +542 -0
- package/dist/lib/extension.d.ts +7 -0
- package/dist/lib/extension.js +1608 -0
- package/dist/lib/generative-app-worker.mjs +104 -0
- package/dist/lib/generative-apps.d.ts +212 -0
- package/dist/lib/generative-apps.js +1006 -0
- package/dist/lib/inbound.d.ts +91 -0
- package/dist/lib/inbound.js +502 -0
- package/dist/lib/journal.d.ts +665 -0
- package/dist/lib/journal.js +3701 -0
- package/dist/lib/keyboard.d.ts +23 -0
- package/dist/lib/keyboard.js +37 -0
- package/dist/lib/lifecycle.d.ts +157 -0
- package/dist/lib/lifecycle.js +395 -0
- package/dist/lib/locks.d.ts +164 -0
- package/dist/lib/locks.js +1208 -0
- package/dist/lib/logging.d.ts +50 -0
- package/dist/lib/logging.js +274 -0
- package/dist/lib/media.d.ts +181 -0
- package/dist/lib/media.js +602 -0
- package/dist/lib/menu-model.d.ts +217 -0
- package/dist/lib/menu-model.js +663 -0
- package/dist/lib/menu-queue.d.ts +37 -0
- package/dist/lib/menu-queue.js +408 -0
- package/dist/lib/menu-settings.d.ts +116 -0
- package/dist/lib/menu-settings.js +595 -0
- package/dist/lib/menu-status.d.ts +32 -0
- package/dist/lib/menu-status.js +112 -0
- package/dist/lib/menu-thinking.d.ts +29 -0
- package/dist/lib/menu-thinking.js +82 -0
- package/dist/lib/menu.d.ts +171 -0
- package/dist/lib/menu.js +323 -0
- package/dist/lib/model.d.ts +127 -0
- package/dist/lib/model.js +409 -0
- package/dist/lib/outbound-attachments.d.ts +241 -0
- package/dist/lib/outbound-attachments.js +639 -0
- package/dist/lib/outbound-buttons.d.ts +69 -0
- package/dist/lib/outbound-buttons.js +248 -0
- package/dist/lib/outbound-markup.d.ts +42 -0
- package/dist/lib/outbound-markup.js +678 -0
- package/dist/lib/outbound-voice.d.ts +55 -0
- package/dist/lib/outbound-voice.js +152 -0
- package/dist/lib/outbound.d.ts +185 -0
- package/dist/lib/outbound.js +516 -0
- package/dist/lib/ownership.d.ts +77 -0
- package/dist/lib/ownership.js +174 -0
- package/dist/lib/paths.d.ts +40 -0
- package/dist/lib/paths.js +94 -0
- package/dist/lib/pi.d.ts +72 -0
- package/dist/lib/pi.js +121 -0
- package/dist/lib/polling.d.ts +351 -0
- package/dist/lib/polling.js +1196 -0
- package/dist/lib/preview.d.ts +192 -0
- package/dist/lib/preview.js +614 -0
- package/dist/lib/prompt-templates.d.ts +24 -0
- package/dist/lib/prompt-templates.js +118 -0
- package/dist/lib/prompts.d.ts +57 -0
- package/dist/lib/prompts.js +167 -0
- package/dist/lib/queue.d.ts +766 -0
- package/dist/lib/queue.js +1965 -0
- package/dist/lib/recovery.d.ts +86 -0
- package/dist/lib/recovery.js +285 -0
- package/dist/lib/rendering.d.ts +20 -0
- package/dist/lib/rendering.js +983 -0
- package/dist/lib/replies.d.ts +214 -0
- package/dist/lib/replies.js +737 -0
- package/dist/lib/routing.d.ts +207 -0
- package/dist/lib/routing.js +2282 -0
- package/dist/lib/runtime.d.ts +172 -0
- package/dist/lib/runtime.js +402 -0
- package/dist/lib/sections.d.ts +165 -0
- package/dist/lib/sections.js +327 -0
- package/dist/lib/setup.d.ts +82 -0
- package/dist/lib/setup.js +150 -0
- package/dist/lib/skills.d.ts +8 -0
- package/dist/lib/skills.js +12 -0
- package/dist/lib/status.d.ts +442 -0
- package/dist/lib/status.js +1006 -0
- package/dist/lib/sync.d.ts +179 -0
- package/dist/lib/sync.js +782 -0
- package/dist/lib/target.d.ts +20 -0
- package/dist/lib/target.js +27 -0
- package/dist/lib/telegram-api.d.ts +541 -0
- package/dist/lib/telegram-api.js +1159 -0
- package/dist/lib/text-groups.d.ts +89 -0
- package/dist/lib/text-groups.js +317 -0
- package/dist/lib/thread-cleanup-manager.d.ts +288 -0
- package/dist/lib/thread-cleanup-manager.js +560 -0
- package/dist/lib/thread-display.d.ts +46 -0
- package/dist/lib/thread-display.js +257 -0
- package/dist/lib/thread-naming.d.ts +46 -0
- package/dist/lib/thread-naming.js +78 -0
- package/dist/lib/thread-reconciler.d.ts +239 -0
- package/dist/lib/thread-reconciler.js +644 -0
- package/dist/lib/threads.d.ts +621 -0
- package/dist/lib/threads.js +3679 -0
- package/dist/lib/time-injection.d.ts +15 -0
- package/dist/lib/time-injection.js +56 -0
- package/dist/lib/turns.d.ts +109 -0
- package/dist/lib/turns.js +500 -0
- package/dist/lib/updates.d.ts +1290 -0
- package/dist/lib/updates.js +3634 -0
- package/dist/lib/voice.d.ts +117 -0
- package/dist/lib/voice.js +174 -0
- package/dist/lib/workspace-admission.d.ts +264 -0
- package/dist/lib/workspace-admission.js +1136 -0
- package/dist/lib/workspace-retirement.d.ts +219 -0
- package/dist/lib/workspace-retirement.js +587 -0
- package/dist/lib/workspace-slots.d.ts +30 -0
- package/dist/lib/workspace-slots.js +54 -0
- package/dist/package.json +126 -0
- package/dist/pi-telegram/index.js +1 -0
- package/dist/skills/generated-control-surface/SKILL.md +107 -0
- package/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
- package/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
- package/dist/skills/generative-apps/SKILL.md +115 -0
- package/dist/skills/show-me/SKILL.md +166 -0
- package/dist/skills/show-me/references/telegram-surfaces.md +43 -0
- package/dist/skills/telegram-bridge/SKILL.md +129 -0
- package/dist/skills/telegram-bridge/references/configuration.md +15 -0
- package/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
- package/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
- package/docs/architecture.md +1 -1
- package/docs/compact-matrix-literal.md +10 -3
- package/docs/generative-apps.md +15 -13
- package/docs/multi-instance-bus.md +17 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +2 -2
- package/lib/bindings.ts +89 -6
- package/lib/bus-follower.ts +11 -3
- package/lib/bus-leader.ts +57 -20
- package/lib/bus.ts +12 -2
- package/lib/commands.ts +2 -2
- package/lib/config.ts +14 -7
- package/lib/delivery.ts +38 -6
- package/lib/extension.ts +26 -1
- package/lib/generative-apps.ts +379 -13
- package/lib/menu-settings.ts +21 -5
- package/lib/outbound-markup.ts +16 -7
- package/lib/pi.ts +4 -0
- package/lib/sync.ts +3 -0
- package/lib/telegram-api.ts +12 -1
- package/lib/thread-cleanup-manager.ts +36 -6
- package/lib/thread-display.ts +96 -18
- package/lib/threads.ts +150 -30
- package/package.json +56 -13
- package/scripts/build-dist.mjs +44 -0
- package/scripts/measure-bus.mjs +8 -1
- package/scripts/measure-workspace.mjs +8 -1
- package/skills/generative-apps/SKILL.md +1 -1
- package/skills/show-me/SKILL.md +1 -1
- package/skills/telegram-bridge/SKILL.md +1 -1
package/lib/outbound-markup.ts
CHANGED
|
@@ -504,14 +504,23 @@ function parseTelegramAdaptiveActionPayloadRows(
|
|
|
504
504
|
return undefined;
|
|
505
505
|
};
|
|
506
506
|
|
|
507
|
+
const parseVerticalSequence = (): Record<string, unknown>[][] | undefined => {
|
|
508
|
+
const rows: Record<string, unknown>[][] = [];
|
|
509
|
+
while (offset < source.length) {
|
|
510
|
+
skipWhitespace();
|
|
511
|
+
if (source[offset] !== "{") break;
|
|
512
|
+
const cell = parseCell();
|
|
513
|
+
if (!cell) return undefined;
|
|
514
|
+
rows.push([cell]);
|
|
515
|
+
if (!consumeOptionalSeparator()) return undefined;
|
|
516
|
+
}
|
|
517
|
+
return rows.length > 0 ? rows : undefined;
|
|
518
|
+
};
|
|
519
|
+
|
|
507
520
|
skipWhitespace();
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
rows = cell ? [[cell]] : undefined;
|
|
512
|
-
} else {
|
|
513
|
-
rows = parseMatrix();
|
|
514
|
-
}
|
|
521
|
+
const rows = source[offset] === "{"
|
|
522
|
+
? parseVerticalSequence()
|
|
523
|
+
: parseMatrix();
|
|
515
524
|
if (!rows) return undefined;
|
|
516
525
|
skipWhitespace();
|
|
517
526
|
if (!options.allowTrailing && offset !== source.length) return undefined;
|
package/lib/pi.ts
CHANGED
|
@@ -260,6 +260,10 @@ export function getExtensionContextCwd(ctx: ExtensionContext): string {
|
|
|
260
260
|
return ctx.cwd;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
export function getExtensionContextSessionId(ctx: ExtensionContext): string {
|
|
264
|
+
return ctx.sessionManager.getSessionId();
|
|
265
|
+
}
|
|
266
|
+
|
|
263
267
|
export function isExtensionContextIdle(ctx: ExtensionContext): boolean {
|
|
264
268
|
return ctx.isIdle();
|
|
265
269
|
}
|
package/lib/sync.ts
CHANGED
|
@@ -46,6 +46,7 @@ export interface TelegramLeaderThreadSyncDeps {
|
|
|
46
46
|
getAllowedUserId: () => number | undefined;
|
|
47
47
|
instanceId: string;
|
|
48
48
|
cwd?: string;
|
|
49
|
+
sessionId?: string;
|
|
49
50
|
telegramProfile?: string;
|
|
50
51
|
forceFreshUnnamed?: boolean;
|
|
51
52
|
requestedThreadName?: string;
|
|
@@ -654,6 +655,7 @@ export async function ensureTelegramLeaderThreadBinding(
|
|
|
654
655
|
normalizedLeaderCwd,
|
|
655
656
|
deps.instanceId,
|
|
656
657
|
legacyLeaderRecord?.instanceId,
|
|
658
|
+
{ sessionId: deps.sessionId },
|
|
657
659
|
)
|
|
658
660
|
: undefined;
|
|
659
661
|
if (deps.cwd && !workspaceIdentity) {
|
|
@@ -715,6 +717,7 @@ export async function ensureTelegramLeaderThreadBinding(
|
|
|
715
717
|
? deps.topicTargetStore.getWorkspaceBinding(
|
|
716
718
|
workspaceIdentity.cwd,
|
|
717
719
|
workspaceIdentity.instanceSlot,
|
|
720
|
+
workspaceIdentity.sessionId,
|
|
718
721
|
)
|
|
719
722
|
: undefined;
|
|
720
723
|
const legacyWorkspaceBinding =
|
package/lib/telegram-api.ts
CHANGED
|
@@ -964,7 +964,7 @@ export function isTelegramApiMethodRetrySafe(method: string): boolean {
|
|
|
964
964
|
return TELEGRAM_RETRY_SAFE_METHODS.has(method);
|
|
965
965
|
}
|
|
966
966
|
|
|
967
|
-
function isRetryableTelegramApiError(error: unknown): boolean {
|
|
967
|
+
export function isRetryableTelegramApiError(error: unknown): boolean {
|
|
968
968
|
return (
|
|
969
969
|
error instanceof TelegramApiHttpError &&
|
|
970
970
|
(error.status === 429 ||
|
|
@@ -972,6 +972,17 @@ function isRetryableTelegramApiError(error: unknown): boolean {
|
|
|
972
972
|
);
|
|
973
973
|
}
|
|
974
974
|
|
|
975
|
+
export function getTelegramApiRetryAfterMs(error: unknown): number | undefined {
|
|
976
|
+
return error instanceof TelegramApiHttpError && error.retryAfterSeconds !== undefined
|
|
977
|
+
? Math.max(0, error.retryAfterSeconds * 1000)
|
|
978
|
+
: undefined;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
export function isTelegramMessageUnavailableError(error: unknown): boolean {
|
|
982
|
+
return error instanceof TelegramApiHttpError && error.status === 400 &&
|
|
983
|
+
/Bad Request: (message to edit not found|message not found|message_id_invalid)/iu.test(error.message);
|
|
984
|
+
}
|
|
985
|
+
|
|
975
986
|
function getTelegramRetryDelayMs(
|
|
976
987
|
error: unknown,
|
|
977
988
|
attempt: number,
|
|
@@ -17,6 +17,8 @@ import type { TelegramWorkspaceDeletionPermit,
|
|
|
17
17
|
export interface TelegramThreadCleanupBindingSnapshot {
|
|
18
18
|
cwd: string;
|
|
19
19
|
workspaceKey: string;
|
|
20
|
+
sessionId?: string;
|
|
21
|
+
sessionKey?: string;
|
|
20
22
|
instanceSlot: string;
|
|
21
23
|
slot?: string;
|
|
22
24
|
bindingKey: string;
|
|
@@ -40,6 +42,8 @@ export interface TelegramThreadCleanupCandidate {
|
|
|
40
42
|
bindingKey: string;
|
|
41
43
|
cwd: string;
|
|
42
44
|
workspaceKey: string;
|
|
45
|
+
sessionId?: string;
|
|
46
|
+
sessionKey?: string;
|
|
43
47
|
instanceSlot: string;
|
|
44
48
|
slot: string;
|
|
45
49
|
target: { chatId: number; threadId: number };
|
|
@@ -56,6 +60,21 @@ function validTarget(target: { chatId: number; threadId: number }): boolean {
|
|
|
56
60
|
target.threadId > 0;
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
function validSessionIdentity(value: {
|
|
64
|
+
sessionId?: unknown;
|
|
65
|
+
sessionKey?: unknown;
|
|
66
|
+
}): boolean {
|
|
67
|
+
if (value.sessionId === undefined && value.sessionKey === undefined) return true;
|
|
68
|
+
if (typeof value.sessionId !== "string" || !value.sessionId ||
|
|
69
|
+
value.sessionId !== value.sessionId.trim() ||
|
|
70
|
+
Buffer.byteLength(value.sessionId, "utf8") > 256 ||
|
|
71
|
+
typeof value.sessionKey !== "string" || !/^[a-f0-9]{64}$/u.test(value.sessionKey)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return createHash("sha256").update(value.sessionId).digest("hex") ===
|
|
75
|
+
value.sessionKey;
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
/** Returns no candidates when any identity/evidence ambiguity exists. */
|
|
60
79
|
export function planTelegramInactiveThreadCleanup(input: {
|
|
61
80
|
profileName: string;
|
|
@@ -72,7 +91,7 @@ export function planTelegramInactiveThreadCleanup(input: {
|
|
|
72
91
|
const key = targetKey(binding.target);
|
|
73
92
|
const slot = binding.slot;
|
|
74
93
|
if (!binding.bindingKey || !binding.cwd || !binding.workspaceKey || !binding.instanceSlot ||
|
|
75
|
-
typeof slot !== "string" || !slot ||
|
|
94
|
+
typeof slot !== "string" || !slot || !validSessionIdentity(binding) ||
|
|
76
95
|
!validTarget(binding.target) || bindingKeys.has(binding.bindingKey) || bindingTargets.has(key)) return [];
|
|
77
96
|
bindingKeys.add(binding.bindingKey);
|
|
78
97
|
bindingTargets.add(key);
|
|
@@ -101,8 +120,11 @@ export function planTelegramInactiveThreadCleanup(input: {
|
|
|
101
120
|
evidence.liveOwner !== "clear" || evidence.acceptedWork !== "clear" ||
|
|
102
121
|
evidence.deliveryAuthority !== "clear" || competing.has(targetKey(binding.target))) continue;
|
|
103
122
|
candidates.push({ profileName: input.profileName, bindingKey: binding.bindingKey,
|
|
104
|
-
cwd: binding.cwd, workspaceKey: binding.workspaceKey,
|
|
105
|
-
|
|
123
|
+
cwd: binding.cwd, workspaceKey: binding.workspaceKey,
|
|
124
|
+
...(binding.sessionId && binding.sessionKey
|
|
125
|
+
? { sessionId: binding.sessionId, sessionKey: binding.sessionKey }
|
|
126
|
+
: {}),
|
|
127
|
+
instanceSlot: binding.instanceSlot, slot, target: { ...binding.target }, inactiveSinceMs,
|
|
106
128
|
bindingUpdatedAtMs: binding.updatedAtMs });
|
|
107
129
|
}
|
|
108
130
|
return candidates.sort((left, right) => left.bindingKey.localeCompare(right.bindingKey));
|
|
@@ -124,7 +146,11 @@ export function captureTelegramInactiveThreadCleanupEvidence<
|
|
|
124
146
|
}): Parameters<typeof planTelegramInactiveThreadCleanup>[0] {
|
|
125
147
|
const sourceBindings = input.listBindings();
|
|
126
148
|
const bindings = sourceBindings.map(binding => ({
|
|
127
|
-
cwd: binding.cwd, workspaceKey: binding.workspaceKey,
|
|
149
|
+
cwd: binding.cwd, workspaceKey: binding.workspaceKey,
|
|
150
|
+
...(binding.sessionId && binding.sessionKey
|
|
151
|
+
? { sessionId: binding.sessionId, sessionKey: binding.sessionKey }
|
|
152
|
+
: {}),
|
|
153
|
+
instanceSlot: binding.instanceSlot,
|
|
128
154
|
...(binding.slot === undefined ? {} : { slot: binding.slot }), bindingKey: binding.bindingKey, target: { ...binding.target },
|
|
129
155
|
...(binding.inactiveSinceMs === undefined ? {} : { inactiveSinceMs: binding.inactiveSinceMs }),
|
|
130
156
|
updatedAtMs: binding.updatedAtMs,
|
|
@@ -237,16 +263,20 @@ function validateWorkSet(value: unknown, profileName: string): TelegramThreadCle
|
|
|
237
263
|
const createdAtMs = value.createdAtMs as number;
|
|
238
264
|
const entries = value.entries.map(raw => {
|
|
239
265
|
if (!isObject(raw) || !onlyKeys(raw, ["profileName", "bindingKey", "cwd", "workspaceKey",
|
|
240
|
-
"instanceSlot", "slot", "target", "inactiveSinceMs", "bindingUpdatedAtMs", "state", "updatedAtMs",
|
|
266
|
+
"sessionId", "sessionKey", "instanceSlot", "slot", "target", "inactiveSinceMs", "bindingUpdatedAtMs", "state", "updatedAtMs",
|
|
241
267
|
"issuedAtMs", "permitOperationId", "permitIntentId", "permitLeaderEpoch", "deletedAtMs"]) || raw.profileName !== profileName || typeof raw.bindingKey !== "string" ||
|
|
242
268
|
!raw.bindingKey || typeof raw.cwd !== "string" || !raw.cwd || typeof raw.workspaceKey !== "string" ||
|
|
243
|
-
!raw.workspaceKey ||
|
|
269
|
+
!raw.workspaceKey || !validSessionIdentity(raw) ||
|
|
270
|
+
typeof raw.instanceSlot !== "string" || !raw.instanceSlot ||
|
|
244
271
|
typeof raw.slot !== "string" || !raw.slot || !isObject(raw.target) ||
|
|
245
272
|
!onlyKeys(raw.target, ["chatId", "threadId"]) || !validTarget(raw.target as unknown as { chatId: number; threadId: number }) ||
|
|
246
273
|
!safeTime(raw.inactiveSinceMs) || !safeTime(raw.bindingUpdatedAtMs) ||
|
|
247
274
|
(raw.bindingUpdatedAtMs as number) < (raw.inactiveSinceMs as number) || !safeTime(raw.updatedAtMs))
|
|
248
275
|
throw new Error("Telegram Thread cleanup entry schema is invalid.");
|
|
249
276
|
const base = { profileName, bindingKey: raw.bindingKey, cwd: raw.cwd, workspaceKey: raw.workspaceKey,
|
|
277
|
+
...(typeof raw.sessionId === "string" && typeof raw.sessionKey === "string"
|
|
278
|
+
? { sessionId: raw.sessionId, sessionKey: raw.sessionKey }
|
|
279
|
+
: {}),
|
|
250
280
|
instanceSlot: raw.instanceSlot, slot: raw.slot, target: { chatId: raw.target.chatId as number,
|
|
251
281
|
threadId: raw.target.threadId as number }, inactiveSinceMs: raw.inactiveSinceMs as number,
|
|
252
282
|
bindingUpdatedAtMs: raw.bindingUpdatedAtMs as number, updatedAtMs: raw.updatedAtMs as number };
|
package/lib/thread-display.ts
CHANGED
|
@@ -26,30 +26,88 @@ function boundedLabel(base: string, suffix = ""): string {
|
|
|
26
26
|
return `${prefix}${suffix}`;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
function directorySegments(cwd: string): string[] {
|
|
30
|
+
return cwd.split("/").filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function distinguishingDirectorySegments(cwd: string, directories: readonly string[]): string[] {
|
|
34
|
+
const parts = directorySegments(cwd);
|
|
35
|
+
if (!parts.length) return [];
|
|
32
36
|
for (let depth = 1; depth <= parts.length; depth++) {
|
|
33
37
|
const candidate = labelText(parts.slice(-depth).join("/"));
|
|
34
38
|
const collides = directories.some((other) => other !== cwd &&
|
|
35
|
-
labelText(other
|
|
39
|
+
labelText(directorySegments(other).slice(-depth).join("/")).toLowerCase() ===
|
|
36
40
|
candidate.toLowerCase(),
|
|
37
41
|
);
|
|
38
|
-
if (!collides) return
|
|
42
|
+
if (!collides) return parts.slice(-depth);
|
|
39
43
|
}
|
|
40
|
-
return
|
|
44
|
+
return parts;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function directoryLabel(cwd: string, directories: readonly string[]): string {
|
|
48
|
+
const parts = distinguishingDirectorySegments(cwd, directories);
|
|
49
|
+
return parts.length ? labelText(parts.join("/")) : "/";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Pure directory tokenization shared by previews, initial titles, and reconciliation. */
|
|
53
|
+
export function tokenizeTelegramDirectorySegment(segment: string): string[] {
|
|
54
|
+
return segment
|
|
55
|
+
.replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, "$1\u0000$2")
|
|
56
|
+
.replace(/(\p{Lu})(\p{Lu}\p{Ll})/gu, "$1\u0000$2")
|
|
57
|
+
.split(/[^\p{L}\p{N}]+|\u0000/gu)
|
|
58
|
+
.filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function formatDirectoryLabel(
|
|
62
|
+
cwd: string,
|
|
63
|
+
directories: readonly string[],
|
|
64
|
+
mode: "directory-snake" | "directory-title",
|
|
65
|
+
): string {
|
|
66
|
+
const segments = distinguishingDirectorySegments(cwd, directories);
|
|
67
|
+
if (!segments.length) return "/";
|
|
68
|
+
const formatted = segments.map((segment) => {
|
|
69
|
+
const tokens = tokenizeTelegramDirectorySegment(segment);
|
|
70
|
+
if (!tokens.length) return "";
|
|
71
|
+
if (mode === "directory-snake") return tokens.map((token) => token.toLowerCase()).join("_");
|
|
72
|
+
return tokens.map((token) => /\p{L}/u.test(token) && token === token.toUpperCase()
|
|
73
|
+
? token
|
|
74
|
+
: `${token.slice(0, 1).toUpperCase()}${token.slice(1).toLowerCase()}`).join(" ");
|
|
75
|
+
}).filter(Boolean);
|
|
76
|
+
if (!formatted.length) return directoryLabel(cwd, directories);
|
|
77
|
+
return formatted.join(mode === "directory-snake" ? "_" : " / ");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Maps one leader-captured authenticated owner roster onto retained binding identities. */
|
|
81
|
+
export function resolveTelegramLiveWorkspaceBindingKeys(
|
|
82
|
+
bindings: readonly TelegramWorkspaceThreadBinding[],
|
|
83
|
+
leaderTarget: { chatId: number; threadId?: number } | undefined,
|
|
84
|
+
followers: readonly { target?: { chatId: number; threadId?: number } }[],
|
|
85
|
+
): ReadonlySet<string> {
|
|
86
|
+
const targets = new Set<string>();
|
|
87
|
+
const add = (target: { chatId: number; threadId?: number } | undefined) => {
|
|
88
|
+
if (target && typeof target.threadId === "number") {
|
|
89
|
+
targets.add(`${target.chatId}:${target.threadId}`);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
add(leaderTarget);
|
|
93
|
+
for (const follower of followers) add(follower.target);
|
|
94
|
+
return new Set(bindings.filter((binding) =>
|
|
95
|
+
targets.has(`${binding.target.chatId}:${binding.target.threadId}`),
|
|
96
|
+
).map((binding) => binding.bindingKey));
|
|
41
97
|
}
|
|
42
98
|
|
|
43
99
|
/** Missing or ambiguous metadata yields no label rather than inventing identity. */
|
|
44
100
|
export function resolveTelegramWorkspaceDisplayNames(
|
|
45
101
|
bindings: readonly TelegramWorkspaceDisplayBinding[],
|
|
46
102
|
mode: TelegramThreadDisplayMode,
|
|
103
|
+
liveBindingKeys: ReadonlySet<string> = new Set(),
|
|
47
104
|
): Map<string, string> {
|
|
48
105
|
const labels = new Map<string, string>();
|
|
49
106
|
const directories = Array.from(new Set(bindings.map((binding) => binding.cwd)));
|
|
50
|
-
const
|
|
107
|
+
const liveDirectoryCounts = new Map<string, number>();
|
|
51
108
|
for (const binding of bindings) {
|
|
52
|
-
|
|
109
|
+
if (!liveBindingKeys.has(binding.bindingKey)) continue;
|
|
110
|
+
liveDirectoryCounts.set(binding.cwd, (liveDirectoryCounts.get(binding.cwd) ?? 0) + 1);
|
|
53
111
|
}
|
|
54
112
|
const bases = new Map<string, string>();
|
|
55
113
|
for (const binding of bindings) {
|
|
@@ -65,18 +123,27 @@ export function resolveTelegramWorkspaceDisplayNames(
|
|
|
65
123
|
const name = binding.threadName ? labelText(binding.threadName) : slot;
|
|
66
124
|
if (name) labels.set(binding.bindingKey, boundedLabel(name));
|
|
67
125
|
} else {
|
|
68
|
-
const base =
|
|
126
|
+
const base = mode === "directories"
|
|
127
|
+
? directoryLabel(binding.cwd, directories)
|
|
128
|
+
: formatDirectoryLabel(binding.cwd, directories, mode);
|
|
69
129
|
bases.set(binding.bindingKey, base);
|
|
70
|
-
const showSuffix =
|
|
71
|
-
|
|
130
|
+
const showSuffix = mode === "directories"
|
|
131
|
+
? binding.showSlotSuffix || bindings.filter((candidate) => candidate.cwd === binding.cwd).length > 1
|
|
132
|
+
: (liveDirectoryCounts.get(binding.cwd) ?? 0) > 1;
|
|
72
133
|
if (showSuffix && !slot) continue;
|
|
73
|
-
|
|
134
|
+
const suffix = !showSuffix ? "" : mode === "directory-title"
|
|
135
|
+
? ` · ${slot}`
|
|
136
|
+
: `_${slot!.toLowerCase()}`;
|
|
137
|
+
labels.set(binding.bindingKey, boundedLabel(base, suffix));
|
|
74
138
|
}
|
|
75
139
|
}
|
|
76
140
|
// Long or whitespace-normalized paths can collide even after qualification.
|
|
77
|
-
if (mode === "directories") {
|
|
141
|
+
if (mode === "directories" || mode === "directory-snake" || mode === "directory-title") {
|
|
78
142
|
const counts = new Map<string, number>();
|
|
79
|
-
for (const
|
|
143
|
+
for (const binding of bindings) {
|
|
144
|
+
const label = labels.get(binding.bindingKey);
|
|
145
|
+
if (!label || ((mode === "directory-snake" || mode === "directory-title") &&
|
|
146
|
+
!liveBindingKeys.has(binding.bindingKey))) continue;
|
|
80
147
|
const key = label.toLowerCase();
|
|
81
148
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
82
149
|
}
|
|
@@ -84,16 +151,24 @@ export function resolveTelegramWorkspaceDisplayNames(
|
|
|
84
151
|
const label = labels.get(binding.bindingKey);
|
|
85
152
|
if (binding.manualThreadName || !label ||
|
|
86
153
|
(counts.get(label.toLowerCase()) ?? 0) < 2) continue;
|
|
154
|
+
if ((mode === "directory-snake" || mode === "directory-title") &&
|
|
155
|
+
(liveDirectoryCounts.get(binding.cwd) ?? 0) < 2) {
|
|
156
|
+
labels.delete(binding.bindingKey);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
87
159
|
if (!binding.slot || !/^[A-Z]$/u.test(binding.slot)) {
|
|
88
160
|
labels.delete(binding.bindingKey);
|
|
89
161
|
continue;
|
|
90
162
|
}
|
|
91
163
|
labels.set(binding.bindingKey, boundedLabel(bases.get(binding.bindingKey)!,
|
|
92
|
-
`_${binding.slot.toLowerCase()}`));
|
|
164
|
+
mode === "directory-title" ? ` · ${binding.slot}` : `_${binding.slot.toLowerCase()}`));
|
|
93
165
|
}
|
|
94
166
|
}
|
|
95
167
|
const counts = new Map<string, number>();
|
|
96
|
-
for (const
|
|
168
|
+
for (const binding of bindings) {
|
|
169
|
+
const label = labels.get(binding.bindingKey);
|
|
170
|
+
if (!label || ((mode === "directory-snake" || mode === "directory-title") &&
|
|
171
|
+
!liveBindingKeys.has(binding.bindingKey))) continue;
|
|
97
172
|
const key = label.toLowerCase();
|
|
98
173
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
99
174
|
}
|
|
@@ -108,6 +183,7 @@ export function resolveTelegramInitialWorkspaceDisplayName(input: {
|
|
|
108
183
|
binding: TelegramWorkspaceDisplayBinding;
|
|
109
184
|
mode: TelegramThreadDisplayMode;
|
|
110
185
|
preserveRetainedManualName?: boolean;
|
|
186
|
+
liveBindingKeys?: ReadonlySet<string>;
|
|
111
187
|
}): string | undefined {
|
|
112
188
|
const retained = input.bindings.find((binding) =>
|
|
113
189
|
binding.bindingKey === input.binding.bindingKey,
|
|
@@ -126,7 +202,7 @@ export function resolveTelegramInitialWorkspaceDisplayName(input: {
|
|
|
126
202
|
candidate.bindingKey !== binding.bindingKey,
|
|
127
203
|
),
|
|
128
204
|
binding,
|
|
129
|
-
], input.mode).get(binding.bindingKey);
|
|
205
|
+
], input.mode, new Set([...(input.liveBindingKeys ?? []), binding.bindingKey])).get(binding.bindingKey);
|
|
130
206
|
}
|
|
131
207
|
|
|
132
208
|
export async function applyTelegramThreadDisplaySetting(
|
|
@@ -155,6 +231,7 @@ export interface TelegramThreadDisplayReconcilerDeps {
|
|
|
155
231
|
getProfileKey(): string;
|
|
156
232
|
getLeaderEpoch(): string | number | undefined;
|
|
157
233
|
captureBindingAuthority(binding: TelegramWorkspaceThreadBinding): (() => boolean) | undefined;
|
|
234
|
+
captureLiveBindingKeys(bindings: readonly TelegramWorkspaceThreadBinding[]): ReadonlySet<string>;
|
|
158
235
|
callApi<TResponse>(
|
|
159
236
|
method: string,
|
|
160
237
|
body: Record<string, unknown>,
|
|
@@ -179,7 +256,8 @@ export function createTelegramThreadDisplayReconciler(
|
|
|
179
256
|
};
|
|
180
257
|
assertAuthority();
|
|
181
258
|
const bindings = deps.store.listWorkspaceBindings();
|
|
182
|
-
const
|
|
259
|
+
const liveBindingKeys = deps.captureLiveBindingKeys(bindings);
|
|
260
|
+
const titles = resolveTelegramWorkspaceDisplayNames(bindings, mode, liveBindingKeys);
|
|
183
261
|
let changed = 0;
|
|
184
262
|
for (const binding of bindings) {
|
|
185
263
|
const isBindingCurrent = deps.captureBindingAuthority(binding);
|