@llblab/pi-telegram 0.35.1 → 0.36.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/AGENTS.md +2 -1
- package/BACKLOG.md +11 -0
- package/CHANGELOG.md +18 -0
- package/README.md +36 -7
- package/docs/README.md +2 -1
- package/docs/architecture.md +29 -2
- package/docs/compact-matrix-literal.md +23 -13
- package/docs/generative-apps.md +310 -0
- package/docs/multi-instance-bus.md +1 -1
- package/docs/outbound.md +2 -2
- package/docs/public-api.md +3 -2
- package/docs/ui-style.md +14 -7
- package/index.ts +14 -0
- package/lib/bindings.ts +118 -8
- package/lib/generative-app-worker.mjs +103 -0
- package/lib/generative-apps.ts +953 -0
- package/lib/menu-queue.ts +105 -112
- package/lib/outbound-buttons.ts +51 -2
- package/lib/outbound-markup.ts +18 -14
- package/lib/outbound.ts +5 -1
- package/lib/prompts.ts +1 -0
- package/lib/queue.ts +98 -42
- package/lib/routing.ts +15 -0
- package/lib/runtime.ts +0 -23
- package/lib/updates.ts +49 -18
- package/package.json +1 -1
- package/skills/generated-control-surface/SKILL.md +13 -5
- package/skills/generative-apps/SKILL.md +110 -0
- package/skills/telegram-bridge/SKILL.md +5 -1
package/lib/queue.ts
CHANGED
|
@@ -34,7 +34,17 @@ export type TelegramQueueLane = "control" | "priority" | "default";
|
|
|
34
34
|
export type TelegramQueueReactionDisposition =
|
|
35
35
|
| { kind: "default" }
|
|
36
36
|
| { kind: "priority"; emoji: string }
|
|
37
|
-
| { kind: "suppressed"; emoji: string }
|
|
37
|
+
| { kind: "suppressed"; emoji: string }
|
|
38
|
+
| {
|
|
39
|
+
kind: "priority-suppressed";
|
|
40
|
+
priorityEmoji: string;
|
|
41
|
+
suppressionEmoji: string;
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
kind: "reaction-transition";
|
|
45
|
+
priorityEmoji?: string | null;
|
|
46
|
+
suppressionEmoji?: string | null;
|
|
47
|
+
};
|
|
38
48
|
|
|
39
49
|
export interface TelegramQueueAdmissionReceipt {
|
|
40
50
|
queueKind: TelegramQueueItemKind;
|
|
@@ -899,7 +909,7 @@ export function applyTelegramQueuePromptReactionDisposition<
|
|
|
899
909
|
items: TelegramQueueItem<TContext>[],
|
|
900
910
|
messageId: number,
|
|
901
911
|
disposition: TelegramQueueReactionDisposition,
|
|
902
|
-
|
|
912
|
+
destinationLaneOrder?: number,
|
|
903
913
|
scope?: TelegramQueueMessageScope,
|
|
904
914
|
): { items: TelegramQueueItem<TContext>[]; changed: boolean } {
|
|
905
915
|
let nextItems = items;
|
|
@@ -911,17 +921,38 @@ export function applyTelegramQueuePromptReactionDisposition<
|
|
|
911
921
|
) {
|
|
912
922
|
continue;
|
|
913
923
|
}
|
|
914
|
-
const
|
|
915
|
-
disposition.
|
|
916
|
-
|
|
917
|
-
|
|
924
|
+
const isPriority = disposition.kind === "reaction-transition"
|
|
925
|
+
? disposition.priorityEmoji === undefined
|
|
926
|
+
? item.queueLane === "priority"
|
|
927
|
+
: disposition.priorityEmoji !== null
|
|
928
|
+
: disposition.kind === "priority" ||
|
|
929
|
+
disposition.kind === "priority-suppressed";
|
|
930
|
+
const queueLane: TelegramQueueLane = isPriority ? "priority" : "default";
|
|
931
|
+
const laneOrder = item.queueLane === queueLane
|
|
932
|
+
? item.laneOrder
|
|
933
|
+
: destinationLaneOrder;
|
|
918
934
|
if (laneOrder === undefined) {
|
|
919
|
-
throw new Error("Telegram
|
|
935
|
+
throw new Error("Telegram destination lane order is unavailable.");
|
|
920
936
|
}
|
|
921
|
-
const priorityEmoji =
|
|
922
|
-
|
|
937
|
+
const priorityEmoji = disposition.kind === "reaction-transition"
|
|
938
|
+
? disposition.priorityEmoji === undefined
|
|
939
|
+
? item.priorityEmoji
|
|
940
|
+
: disposition.priorityEmoji ?? undefined
|
|
941
|
+
: disposition.kind === "priority"
|
|
942
|
+
? disposition.emoji
|
|
943
|
+
: disposition.kind === "priority-suppressed"
|
|
944
|
+
? disposition.priorityEmoji
|
|
945
|
+
: undefined;
|
|
923
946
|
const reactionSuppressionEmoji =
|
|
924
|
-
disposition.kind === "
|
|
947
|
+
disposition.kind === "reaction-transition"
|
|
948
|
+
? disposition.suppressionEmoji === undefined
|
|
949
|
+
? item.reactionSuppressionEmoji
|
|
950
|
+
: disposition.suppressionEmoji ?? undefined
|
|
951
|
+
: disposition.kind === "suppressed"
|
|
952
|
+
? disposition.emoji
|
|
953
|
+
: disposition.kind === "priority-suppressed"
|
|
954
|
+
? disposition.suppressionEmoji
|
|
955
|
+
: undefined;
|
|
925
956
|
if (
|
|
926
957
|
item.queueLane === queueLane &&
|
|
927
958
|
item.laneOrder === laneOrder &&
|
|
@@ -1984,7 +2015,6 @@ export interface TelegramSessionShutdownState<TQueueItem> {
|
|
|
1984
2015
|
queuedTelegramItems: TQueueItem[];
|
|
1985
2016
|
nextQueuedTelegramItemOrder: number;
|
|
1986
2017
|
nextQueuedTelegramControlOrder: number;
|
|
1987
|
-
nextPriorityReactionOrder: number;
|
|
1988
2018
|
currentTelegramModel: undefined;
|
|
1989
2019
|
activeTelegramToolExecutions: number;
|
|
1990
2020
|
pendingTelegramModelSwitch: undefined;
|
|
@@ -1996,7 +2026,6 @@ export interface TelegramSessionShutdownState<TQueueItem> {
|
|
|
1996
2026
|
export interface TelegramSessionRuntimeCounterState {
|
|
1997
2027
|
nextQueuedTelegramItemOrder?: number;
|
|
1998
2028
|
nextQueuedTelegramControlOrder?: number;
|
|
1999
|
-
nextPriorityReactionOrder?: number;
|
|
2000
2029
|
}
|
|
2001
2030
|
|
|
2002
2031
|
export interface TelegramSessionRuntimeFlagState {
|
|
@@ -2104,8 +2133,7 @@ export interface TelegramQueueMutationRuntimeDeps<
|
|
|
2104
2133
|
TContext,
|
|
2105
2134
|
> extends TelegramQueueStore<TContext>, TelegramRuntimeEventRecorderPort {
|
|
2106
2135
|
ctx: TContext;
|
|
2107
|
-
|
|
2108
|
-
incrementNextPriorityReactionOrder?: () => void;
|
|
2136
|
+
allocateLaneOrder?: () => number;
|
|
2109
2137
|
onItemsDiscarded?: (
|
|
2110
2138
|
items: readonly TelegramQueueItem<TContext>[],
|
|
2111
2139
|
ctx: TContext,
|
|
@@ -2116,8 +2144,7 @@ export interface TelegramQueueMutationRuntimeDeps<
|
|
|
2116
2144
|
export interface TelegramQueueMutationControllerDeps<
|
|
2117
2145
|
TContext,
|
|
2118
2146
|
> extends TelegramQueueStore<TContext>, TelegramRuntimeEventRecorderPort {
|
|
2119
|
-
|
|
2120
|
-
incrementNextPriorityReactionOrder?: () => void;
|
|
2147
|
+
allocateLaneOrder?: () => number;
|
|
2121
2148
|
onItemsDiscarded?: (
|
|
2122
2149
|
items: readonly TelegramQueueItem<TContext>[],
|
|
2123
2150
|
ctx: TContext,
|
|
@@ -2227,7 +2254,6 @@ export function buildTelegramSessionShutdownState<
|
|
|
2227
2254
|
queuedTelegramItems: [],
|
|
2228
2255
|
nextQueuedTelegramItemOrder: 0,
|
|
2229
2256
|
nextQueuedTelegramControlOrder: 0,
|
|
2230
|
-
nextPriorityReactionOrder: 0,
|
|
2231
2257
|
currentTelegramModel: undefined,
|
|
2232
2258
|
activeTelegramToolExecutions: 0,
|
|
2233
2259
|
pendingTelegramModelSwitch: undefined,
|
|
@@ -2492,24 +2518,40 @@ export function applyTelegramQueuePromptReactionDispositionRuntime<TContext>(
|
|
|
2492
2518
|
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
2493
2519
|
scope?: TelegramQueueMessageScope,
|
|
2494
2520
|
): boolean {
|
|
2495
|
-
const
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2521
|
+
const queuedItems = deps.getQueuedItems();
|
|
2522
|
+
const changesLane = queuedItems.some((item) => {
|
|
2523
|
+
if (
|
|
2524
|
+
!isPendingTelegramTurn(item) ||
|
|
2525
|
+
!isTelegramQueueItemInMessageScope(item, scope) ||
|
|
2526
|
+
!item.sourceMessageIds.includes(messageId)
|
|
2527
|
+
) {
|
|
2528
|
+
return false;
|
|
2529
|
+
}
|
|
2530
|
+
const queueLane: TelegramQueueLane =
|
|
2531
|
+
disposition.kind === "reaction-transition"
|
|
2532
|
+
? disposition.priorityEmoji === undefined
|
|
2533
|
+
? item.queueLane
|
|
2534
|
+
: disposition.priorityEmoji === null
|
|
2535
|
+
? "default"
|
|
2536
|
+
: "priority"
|
|
2537
|
+
: disposition.kind === "priority" ||
|
|
2538
|
+
disposition.kind === "priority-suppressed"
|
|
2539
|
+
? "priority"
|
|
2540
|
+
: "default";
|
|
2541
|
+
return item.queueLane !== queueLane;
|
|
2542
|
+
});
|
|
2543
|
+
const destinationLaneOrder = changesLane
|
|
2544
|
+
? deps.allocateLaneOrder?.()
|
|
2545
|
+
: undefined;
|
|
2546
|
+
if (changesLane && destinationLaneOrder === undefined) return false;
|
|
2502
2547
|
const { changed, items } = applyTelegramQueuePromptReactionDisposition(
|
|
2503
|
-
|
|
2548
|
+
queuedItems,
|
|
2504
2549
|
messageId,
|
|
2505
2550
|
disposition,
|
|
2506
|
-
|
|
2551
|
+
destinationLaneOrder,
|
|
2507
2552
|
scope,
|
|
2508
2553
|
);
|
|
2509
2554
|
if (!changed) return false;
|
|
2510
|
-
if (disposition.kind === "priority") {
|
|
2511
|
-
deps.incrementNextPriorityReactionOrder?.();
|
|
2512
|
-
}
|
|
2513
2555
|
commitReorderedTelegramQueueItemsRuntime(items, deps);
|
|
2514
2556
|
return true;
|
|
2515
2557
|
}
|
|
@@ -2931,18 +2973,33 @@ export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
|
2931
2973
|
{ phase: "transport-generation" },
|
|
2932
2974
|
);
|
|
2933
2975
|
}
|
|
2934
|
-
const
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2976
|
+
const canDispatch = deps.canDispatch(ctx);
|
|
2977
|
+
let nextActiveIndex = 0;
|
|
2978
|
+
if (canDispatch) {
|
|
2979
|
+
while (nextActiveIndex < activeItems.length) {
|
|
2980
|
+
const candidate = activeItems[nextActiveIndex];
|
|
2981
|
+
if (
|
|
2982
|
+
!candidate ||
|
|
2983
|
+
candidate.kind !== "prompt" ||
|
|
2984
|
+
candidate.reactionSuppressionEmoji === undefined
|
|
2985
|
+
) {
|
|
2986
|
+
break;
|
|
2987
|
+
}
|
|
2988
|
+
if (deps.hasPendingInboundQueueMutationForItem?.(candidate)) {
|
|
2989
|
+
deps.updateStatus(ctx);
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
if (
|
|
2993
|
+
deps.isQueueItemAdmissionReady &&
|
|
2994
|
+
!deps.isQueueItemAdmissionReady(candidate)
|
|
2995
|
+
) {
|
|
2996
|
+
deps.updateStatus(ctx);
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
nextActiveIndex += 1;
|
|
2944
3000
|
}
|
|
2945
3001
|
}
|
|
3002
|
+
const dispatchableItems = activeItems.slice(nextActiveIndex);
|
|
2946
3003
|
const nextItem = dispatchableItems[0];
|
|
2947
3004
|
if (
|
|
2948
3005
|
nextItem &&
|
|
@@ -2961,12 +3018,11 @@ export function createTelegramQueueDispatchController<TContext = unknown>(
|
|
|
2961
3018
|
}
|
|
2962
3019
|
const dispatchPlan = planNextTelegramQueueAction(
|
|
2963
3020
|
dispatchableItems,
|
|
2964
|
-
|
|
3021
|
+
canDispatch,
|
|
2965
3022
|
);
|
|
2966
|
-
if (dispatchPlan.kind !== "none") {
|
|
3023
|
+
if (nextActiveIndex > 0 || dispatchPlan.kind !== "none") {
|
|
2967
3024
|
deps.setQueuedItems([
|
|
2968
3025
|
...dispatchPlan.remainingItems,
|
|
2969
|
-
...suppressedActiveItems,
|
|
2970
3026
|
...protectedInactiveItems,
|
|
2971
3027
|
]);
|
|
2972
3028
|
}
|
package/lib/routing.ts
CHANGED
|
@@ -590,6 +590,11 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
590
590
|
ctx: TContext,
|
|
591
591
|
) => Promise<boolean>;
|
|
592
592
|
buttonActionStore?: OutboundHandlers.TelegramButtonActionStore;
|
|
593
|
+
invokeBoundButtonAction?: (
|
|
594
|
+
action: OutboundHandlers.TelegramOutboundButtonAction,
|
|
595
|
+
query: TCallbackQuery,
|
|
596
|
+
ctx: TContext,
|
|
597
|
+
) => Promise<false | "new" | "edit">;
|
|
593
598
|
inboundHandlerRuntime: TelegramInboundHandlerRuntime<TContext>;
|
|
594
599
|
threadStore?: Threads.TelegramTopicTargetStore;
|
|
595
600
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
@@ -1566,6 +1571,16 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1566
1571
|
{
|
|
1567
1572
|
resolveAction: deps.buttonActionStore.resolve,
|
|
1568
1573
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
1574
|
+
...(deps.invokeBoundButtonAction
|
|
1575
|
+
? {
|
|
1576
|
+
invokeBoundAction: (buttonQuery, action, context) =>
|
|
1577
|
+
deps.invokeBoundButtonAction!(
|
|
1578
|
+
action,
|
|
1579
|
+
buttonQuery as TCallbackQuery,
|
|
1580
|
+
context,
|
|
1581
|
+
),
|
|
1582
|
+
}
|
|
1583
|
+
: {}),
|
|
1569
1584
|
editMessageReplyMarkup: deps.editMessageReplyMarkup
|
|
1570
1585
|
? async (chatId, messageId, replyMarkup) => {
|
|
1571
1586
|
try {
|
package/lib/runtime.ts
CHANGED
|
@@ -10,7 +10,6 @@ const TELEGRAM_TYPING_IDLE_DRAIN_MAX_MS = 250;
|
|
|
10
10
|
export interface TelegramRuntimeQueueCounters {
|
|
11
11
|
nextQueuedTelegramItemOrder: number;
|
|
12
12
|
nextQueuedTelegramControlOrder: number;
|
|
13
|
-
nextPriorityReactionOrder: number;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export interface TelegramRuntimeLifecycleFlags {
|
|
@@ -34,8 +33,6 @@ export interface TelegramRuntimeQueuePort {
|
|
|
34
33
|
syncCounters: (counters: Partial<TelegramRuntimeQueueCounters>) => void;
|
|
35
34
|
allocateItemOrder: () => number;
|
|
36
35
|
allocateControlOrder: () => number;
|
|
37
|
-
getNextPriorityReactionOrder: () => number;
|
|
38
|
-
incrementNextPriorityReactionOrder: () => void;
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
export interface TelegramRuntimeLifecyclePort {
|
|
@@ -85,7 +82,6 @@ export function createTelegramBridgeRuntimeState(): TelegramBridgeRuntimeState {
|
|
|
85
82
|
return {
|
|
86
83
|
nextQueuedTelegramItemOrder: 0,
|
|
87
84
|
nextQueuedTelegramControlOrder: 0,
|
|
88
|
-
nextPriorityReactionOrder: 0,
|
|
89
85
|
activeTelegramToolExecutions: 0,
|
|
90
86
|
telegramTurnDispatchPending: false,
|
|
91
87
|
compactionInProgress: false,
|
|
@@ -104,10 +100,6 @@ export function createTelegramBridgeRuntime(
|
|
|
104
100
|
syncTelegramQueueRuntimeCounters(state, counters),
|
|
105
101
|
allocateItemOrder: () => allocateTelegramQueueItemOrder(state),
|
|
106
102
|
allocateControlOrder: () => allocateTelegramQueueControlOrder(state),
|
|
107
|
-
getNextPriorityReactionOrder: () =>
|
|
108
|
-
getNextTelegramPriorityReactionOrder(state),
|
|
109
|
-
incrementNextPriorityReactionOrder: () =>
|
|
110
|
-
incrementNextTelegramPriorityReactionOrder(state),
|
|
111
103
|
},
|
|
112
104
|
lifecycle: {
|
|
113
105
|
syncFlags: (flags) => syncTelegramLifecycleRuntimeFlags(state, flags),
|
|
@@ -159,9 +151,6 @@ export function syncTelegramQueueRuntimeCounters(
|
|
|
159
151
|
state.nextQueuedTelegramControlOrder =
|
|
160
152
|
counters.nextQueuedTelegramControlOrder;
|
|
161
153
|
}
|
|
162
|
-
if (counters.nextPriorityReactionOrder !== undefined) {
|
|
163
|
-
state.nextPriorityReactionOrder = counters.nextPriorityReactionOrder;
|
|
164
|
-
}
|
|
165
154
|
}
|
|
166
155
|
|
|
167
156
|
export function allocateTelegramQueueItemOrder(
|
|
@@ -176,18 +165,6 @@ export function allocateTelegramQueueControlOrder(
|
|
|
176
165
|
return state.nextQueuedTelegramControlOrder++;
|
|
177
166
|
}
|
|
178
167
|
|
|
179
|
-
export function getNextTelegramPriorityReactionOrder(
|
|
180
|
-
state: TelegramBridgeRuntimeState,
|
|
181
|
-
): number {
|
|
182
|
-
return state.nextPriorityReactionOrder;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function incrementNextTelegramPriorityReactionOrder(
|
|
186
|
-
state: TelegramBridgeRuntimeState,
|
|
187
|
-
): void {
|
|
188
|
-
state.nextPriorityReactionOrder += 1;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
168
|
export function syncTelegramLifecycleRuntimeFlags(
|
|
192
169
|
state: TelegramBridgeRuntimeState,
|
|
193
170
|
flags: Partial<TelegramRuntimeLifecycleFlags>,
|
package/lib/updates.ts
CHANGED
|
@@ -133,24 +133,61 @@ export function getTelegramQueueReactionDisposition(
|
|
|
133
133
|
emojis,
|
|
134
134
|
TELEGRAM_REMOVAL_REACTION_EMOJIS,
|
|
135
135
|
);
|
|
136
|
-
if (suppressionEmoji) return { kind: "suppressed", emoji: suppressionEmoji };
|
|
137
136
|
const priorityEmoji = getTelegramReactionEmoji(
|
|
138
137
|
emojis,
|
|
139
138
|
TELEGRAM_PRIORITY_REACTION_EMOJIS,
|
|
140
139
|
);
|
|
140
|
+
if (suppressionEmoji && priorityEmoji) {
|
|
141
|
+
return {
|
|
142
|
+
kind: "priority-suppressed",
|
|
143
|
+
priorityEmoji,
|
|
144
|
+
suppressionEmoji,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (suppressionEmoji) return { kind: "suppressed", emoji: suppressionEmoji };
|
|
141
148
|
if (priorityEmoji) return { kind: "priority", emoji: priorityEmoji };
|
|
142
149
|
return { kind: "default" };
|
|
143
150
|
}
|
|
144
151
|
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
):
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
function getTelegramQueueReactionTransition(
|
|
153
|
+
oldReactions: TelegramReactionType[],
|
|
154
|
+
newReactions: TelegramReactionType[],
|
|
155
|
+
): TelegramQueueReactionDisposition | undefined {
|
|
156
|
+
const oldEmojis = collectTelegramReactionEmojis(oldReactions);
|
|
157
|
+
const newEmojis = collectTelegramReactionEmojis(newReactions);
|
|
158
|
+
const oldPriorityEmoji = getTelegramReactionEmoji(
|
|
159
|
+
oldEmojis,
|
|
160
|
+
TELEGRAM_PRIORITY_REACTION_EMOJIS,
|
|
161
|
+
);
|
|
162
|
+
const newPriorityEmoji = getTelegramReactionEmoji(
|
|
163
|
+
newEmojis,
|
|
164
|
+
TELEGRAM_PRIORITY_REACTION_EMOJIS,
|
|
153
165
|
);
|
|
166
|
+
const oldSuppressionEmoji = getTelegramReactionEmoji(
|
|
167
|
+
oldEmojis,
|
|
168
|
+
TELEGRAM_REMOVAL_REACTION_EMOJIS,
|
|
169
|
+
);
|
|
170
|
+
const newSuppressionEmoji = getTelegramReactionEmoji(
|
|
171
|
+
newEmojis,
|
|
172
|
+
TELEGRAM_REMOVAL_REACTION_EMOJIS,
|
|
173
|
+
);
|
|
174
|
+
if (
|
|
175
|
+
oldPriorityEmoji === newPriorityEmoji &&
|
|
176
|
+
oldSuppressionEmoji === newSuppressionEmoji
|
|
177
|
+
) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const transition: Extract<
|
|
181
|
+
TelegramQueueReactionDisposition,
|
|
182
|
+
{ kind: "reaction-transition" }
|
|
183
|
+
> = { kind: "reaction-transition" };
|
|
184
|
+
if (oldPriorityEmoji !== newPriorityEmoji) {
|
|
185
|
+
transition.priorityEmoji = newPriorityEmoji ?? null;
|
|
186
|
+
}
|
|
187
|
+
if (oldSuppressionEmoji !== newSuppressionEmoji) {
|
|
188
|
+
transition.suppressionEmoji = newSuppressionEmoji ?? null;
|
|
189
|
+
}
|
|
190
|
+
return transition;
|
|
154
191
|
}
|
|
155
192
|
|
|
156
193
|
export function extractDeletedTelegramMessageIds(
|
|
@@ -1299,17 +1336,11 @@ export async function handleAuthorizedTelegramReactionUpdate<TContext>(
|
|
|
1299
1336
|
typeof reactionUpdate.chat.id === "number"
|
|
1300
1337
|
? { chatId: reactionUpdate.chat.id }
|
|
1301
1338
|
: undefined;
|
|
1302
|
-
const
|
|
1339
|
+
const reactionTransition = getTelegramQueueReactionTransition(
|
|
1303
1340
|
reactionUpdate.old_reaction,
|
|
1304
|
-
);
|
|
1305
|
-
const newDisposition = getTelegramQueueReactionDisposition(
|
|
1306
1341
|
reactionUpdate.new_reaction,
|
|
1307
1342
|
);
|
|
1308
|
-
if (
|
|
1309
|
-
areTelegramQueueReactionDispositionsEqual(oldDisposition, newDisposition)
|
|
1310
|
-
) {
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1343
|
+
if (!reactionTransition) return;
|
|
1313
1344
|
deps.assertExecutionCurrent?.();
|
|
1314
1345
|
await deps.flushPendingMediaGroupMessage?.(reactionUpdate.message_id);
|
|
1315
1346
|
deps.assertExecutionCurrent?.();
|
|
@@ -1317,7 +1348,7 @@ export async function handleAuthorizedTelegramReactionUpdate<TContext>(
|
|
|
1317
1348
|
deps.assertExecutionCurrent?.();
|
|
1318
1349
|
deps.applyQueuedTelegramTurnReactionByMessageId(
|
|
1319
1350
|
reactionUpdate.message_id,
|
|
1320
|
-
|
|
1351
|
+
reactionTransition,
|
|
1321
1352
|
deps.ctx,
|
|
1322
1353
|
reactionScope,
|
|
1323
1354
|
);
|
package/package.json
CHANGED
|
@@ -23,7 +23,9 @@ The primitive belongs to the Surface plane: it projects State, exposes Agency ca
|
|
|
23
23
|
|
|
24
24
|
## Scope
|
|
25
25
|
|
|
26
|
-
Use this Skill only to synthesize a state-derived prompt-button surface. Operating or modifying an existing Telegram bridge menu, callback interface, fixed frontend,
|
|
26
|
+
Use this Skill only to synthesize a state-derived prompt-button surface. Operating or modifying an existing Telegram bridge menu, callback interface, fixed frontend, runtime-owned control, or installed Generative App stays with that subsystem and does not route here merely because it contains buttons.
|
|
27
|
+
|
|
28
|
+
When a generated surface reveals a repeated stable interaction with bounded state and deterministic transitions, consider graduating it to the complementary `generative-apps` Skill. Keep one-off, interpretive, changing, and context-heavy interaction here; compile only when bypassing repeated model mediation has concrete latency, token, cost, reliability, or UX value.
|
|
27
29
|
|
|
28
30
|
On Telegram turns, evaluate this Skill proactively rather than waiting for the user to ask for buttons. Load and apply it when a likely next decision, approval, navigation step, inspection, or bounded action can be made materially easier through controls; its correct output may still contain zero buttons when the admission test fails.
|
|
29
31
|
|
|
@@ -80,7 +82,9 @@ A surface normally contains:
|
|
|
80
82
|
|
|
81
83
|
Prefer 2–6 controls for feedback and decisions; navigation collections may use up to 12 when the additional entries remain scannable. Split larger sets by category or page instead of building a button wall. Do not add navigation controls when the surface is a one-step decision.
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
When a logical grid has no semantic column headings but Markdown table syntax requires a header row, use the grid's first data row as the syntactic header and render each remaining row once beneath it. Do not insert blank, dash-only, duplicate, or invented placeholder headings: they add a false row to the projected topology. Use ordinary semantic headings when the data actually has named columns.
|
|
86
|
+
|
|
87
|
+
Present compact metadata as stacked key-value rows that reuse status-surface grammar: a short bold label, a colon, and an inline-code value when the value is path-like, numeric, an identifier, or machine state. In Telegram Rich Markdown, use an actual Markdown list or blank paragraph boundaries so soft line breaks cannot collapse several fields into one visual line. Prefer ``- **Path:** `/home/llb` `` and ``- **Entries:** `1–10 of 52` `` over prose fragments joined by a middle dot or other decorative section separator.
|
|
84
88
|
|
|
85
89
|
## Truth Modes
|
|
86
90
|
|
|
@@ -118,7 +122,7 @@ Re-check mutable targets immediately before execution. Access denial never autho
|
|
|
118
122
|
|
|
119
123
|
## Prompt Buttons
|
|
120
124
|
|
|
121
|
-
Use the transport's canonical prompt-button syntax. For pi-telegram, one top-level `telegram_button` comment accepts one JSON object, double-quoted attributes, a JSON matrix, or Compact Matrix Literal (CML). CML uses `{value}` or `{label|prompt}`, trims atom boundaries, preserves other printable text literally, and decodes only `\|`, `\}`, and
|
|
125
|
+
Use the transport's canonical prompt-button syntax. For pi-telegram, one top-level `telegram_button` comment accepts one JSON object, double-quoted attributes, a JSON matrix, or Compact Matrix Literal (CML). CML uses `{value}`, `{label|prompt}`, or `{label|prompt|selected_style}`; the optional third atom requires an explicit prompt and accepts only `primary`, `success`, or `danger`. It trims atom boundaries, preserves other printable text literally, and decodes only `\|`, `\}`, and `\\`. Prefer CML whenever the model authors the control and it can express the required surface; fall back to expanded JSON only for multiline prompts, non-positional metadata, or a concrete CML parse/render failure, never merely from implementation habit. Deterministic Generative App scripts may return ordinary JSON because their source payload does not consume model-output tokens; author and operate those adapters through the `generative-apps` Skill rather than growing a parallel app workflow here. A top-level cell becomes one full-width row, while a nested row groups one or more controls horizontally without a parser-level width cap. Prefer one layout comment for multiple controls instead of repeating the marker; `telegram_buttons` is a plural alias, not a different format.
|
|
122
126
|
|
|
123
127
|
### Semantic Row Composition
|
|
124
128
|
|
|
@@ -128,7 +132,9 @@ Model the control surface as an ordered ragged sequence of independently sized r
|
|
|
128
132
|
- Use a singleton full-width row for a structurally independent, pinned, primary, summary, or high-consequence action when separation improves comprehension.
|
|
129
133
|
- Vary row widths intentionally—for example `1 → 2 → 4 → 1 → 2`—and never pad a row with empty, duplicate, or no-op controls merely to produce uniform dimensions.
|
|
130
134
|
- Preserve reading order across rows: orientation and structural navigation first, primary content or choices next, secondary controls afterward, and destructive actions visibly separated when present.
|
|
131
|
-
- Use at most two columns when buttons carry words, phrases, icon-plus-text labels, or other text that must be read; move additional peer choices into more semantic rows rather than compressing four or more textual buttons across a phone-width line. Three through five columns are for short symbols, glyphs, coordinates, or compact codes whose position carries meaning. Six through eight may be used only for single-glyph or similarly minimal position-bearing labels whose grouping materially improves the interaction. Never shorten necessary wording merely to increase row density; regroup or use full-width rows when labels need explanation, wrap ambiguously, or lose meaning without prose.
|
|
135
|
+
- Use at most two columns when buttons carry words, phrases, icon-plus-text labels, or other text that must be read; move additional peer choices into more semantic rows rather than compressing four or more textual buttons across a phone-width line. Three through five columns are for short symbols, glyphs, coordinates, or compact codes whose position carries meaning. Six through eight may be used only for single-glyph or similarly minimal position-bearing labels whose grouping materially improves the interaction. Eight is the phone-width UX maximum: never generate a row of nine or more controls even though the parser has no artificial width cap. Never shorten necessary wording merely to increase row density; regroup or use full-width rows when labels need explanation, wrap ambiguously, or lose meaning without prose.
|
|
136
|
+
|
|
137
|
+
Treat vertical extent independently from horizontal density. A genuinely spatial surface may retain many rows—such as an `8×16` field—when vertical continuity, coordinates, and one-glance topology matter; do not paginate merely to make its height match its width. For non-spatial collections, however, a tall button wall should yield to semantic grouping, progressive disclosure, or pagination. Keep compact state and instructions above a tall surface, preserve stable coordinates across regeneration, and avoid repeating prose between rows.
|
|
132
138
|
|
|
133
139
|
Treat symmetry as an evidence claim about the task. Equal rectangular rows imply equal relationships and stable spatial correspondence; do not make that claim merely because the renderer supports a grid. Infer the task's independent, peer, ordered, hierarchical, and spatial relationships first, then let those relationships determine row boundaries and widths. A non-spatial task should be ragged by default, and its asymmetry should remain visible when that best communicates hierarchy or action weight.
|
|
134
140
|
|
|
@@ -143,7 +149,7 @@ Use this catalog as a shape vocabulary, not a fixed menu of demos:
|
|
|
143
149
|
- `1 → 2 → N×1`: Pinned structural navigation, compact traversal, then a vertical collection of independently readable items.
|
|
144
150
|
- `1 → 2 → 3 → 1`: Ragged staged control where context, local alternatives, denser short peers, and a separated terminal action have different semantic weight.
|
|
145
151
|
- Repeated `2`: Text-bearing choices distributed down the screen instead of compressed across it.
|
|
146
|
-
- `R×C`: A rectangular layout only when rows and columns map to genuine coordinates, repeated dimensions, or another stable spatial topology.
|
|
152
|
+
- `R×C`: A rectangular layout only when rows and columns map to genuine coordinates, repeated dimensions, or another stable spatial topology; keep `C ≤ 8`, while `R` may be substantially larger when preserving vertical continuity is useful.
|
|
147
153
|
|
|
148
154
|
Adapt a surface by identifying relationships, grouping only genuine peers, ordering groups by task hierarchy, applying label-pressure limits, and then choosing the least regular layout that remains immediately comprehensible. Do not select a catalog shape first and force the task into it.
|
|
149
155
|
|
|
@@ -156,6 +162,8 @@ Preserve the ordinary admission test: proactively offer an interactive surface e
|
|
|
156
162
|
- Keep trivial interaction state in the visible conversation. When state becomes too large, long-lived, or error-prone for reliable conversational reconstruction, persist a small human-auditable Markdown state artifact at a deterministic task-owned path and render from it. The artifact belongs to the underlying task or domain, not to this Skill as shadow application state.
|
|
157
163
|
- When transition rules are non-trivial or correctness-sensitive, use a small deterministic state-transition owner—script, module, tool, or existing domain API—that validates `current state + admitted action → next state`; let the model compile the surface from its result instead of informally simulating every transition. Do not create code or files for a trivial one-step interaction.
|
|
158
164
|
- Treat repeated clicks against current state, not stale button appearance. If an action is already consumed or unavailable, keep state unchanged and say so briefly. Preserve an occupied or selected button when spatial layout matters, using its label or selected style as the visual state; omit unavailable controls when layout does not matter. Transport-level disabled buttons are optional, not assumed.
|
|
165
|
+
- Preserve tap-ahead on transports where existing controls remain actionable and rapid clicks queue separate turns. In a source-then-destination interaction, persist the source selection but do not regenerate the board, enumerate destinations, or duplicate controls between the two prompts; emit at most a minimal acknowledgement and let the already visible surface carry the destination click. Regenerate after the completed transition, invalid input, or evidence that the transport cannot preserve the intermediate surface.
|
|
166
|
+
- Resolve coordinate selection by current state rather than rigid click parity. Clicking any currently selectable source selects or replaces the source and then waits; acknowledge a replacement tersely without regenerating the surface. Only a click that is not a selectable source becomes a destination attempt when a source is already selected, at which point the domain owner validates the transition. Without a selected source, a non-source coordinate is a no-op.
|
|
159
167
|
|
|
160
168
|
```html
|
|
161
169
|
<!-- telegram_button {"label":"🔍 Inspect run","prompt":"Inspect Run run:example read-only, summarize its current status and latest material evidence, then regenerate relevant supervision controls."} -->
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generative-apps
|
|
3
|
+
description: Design, author, review, install, replace, invoke, or debug reusable Generative Apps that combine generated button interfaces, deterministic bound methods, and optional model-mediated prompts. Use for standalone applications and bounded view/controller adapters rendered through pi-telegram.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Generative Apps
|
|
7
|
+
|
|
8
|
+
## Concept
|
|
9
|
+
|
|
10
|
+
A Generative App is a reusable application authored by the model for a concrete task. It combines persistent state, named methods, textual output, and a generated button interface. The model acts primarily as author/compiler; the installed program then renders evolving views and executes routine transitions without requiring inference on every click.
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
intent → model authors app → reusable state + methods + generated views
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
One surface may deliberately mix two action planes:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
bound method → deterministic local transition
|
|
20
|
+
ordinary prompt → model interpretation, explanation, teaching, or adaptation
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This is broader than one Telegram implementation. Telegram is the first renderer; a future TUI or web renderer may reuse the concept once a second implementation proves the common contract. Keep renderer-specific wire and lifecycle details with the owning runtime.
|
|
24
|
+
|
|
25
|
+
Generative Apps complement `generated-control-surface`:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
Generated Control Surface → current context → model → one ephemeral surface
|
|
29
|
+
Generative App → model → reusable program → many evolving surfaces
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `generated` / `generative` distinction is intentional. Do not rename `generated-control-surface` to a competing generative term.
|
|
33
|
+
|
|
34
|
+
## Ownership
|
|
35
|
+
|
|
36
|
+
This Skill owns agent operating judgment:
|
|
37
|
+
|
|
38
|
+
- Whether a Generative App is warranted.
|
|
39
|
+
- Standalone versus adapter selection.
|
|
40
|
+
- Authoring and capability review.
|
|
41
|
+
- Install, replace, invoke, and validation workflow.
|
|
42
|
+
- Safety boundaries and stop decisions.
|
|
43
|
+
|
|
44
|
+
This Skill also owns the transport-independent concept, vocabulary, application shapes, hybrid action model, and relationship to Generated Control Surface.
|
|
45
|
+
|
|
46
|
+
[`../../docs/generative-apps.md`](../../docs/generative-apps.md) owns only the concrete `pi-telegram` runtime reference: Telegram wire syntax, managed layout, executable ABI, state timeline, generation/revision fencing, worker execution, locking, installation transactions, callback routing, lifecycle behavior, and current limitations. Do not duplicate those implementation details here.
|
|
47
|
+
|
|
48
|
+
## When To Use
|
|
49
|
+
|
|
50
|
+
Use a Generative App when:
|
|
51
|
+
|
|
52
|
+
- Interaction repeats or contains several stable transitions.
|
|
53
|
+
- State and valid actions fit a small auditable contract.
|
|
54
|
+
- Direct methods materially improve latency, cost, reliability, or UX.
|
|
55
|
+
- Model judgment remains optional rather than required for every action.
|
|
56
|
+
- One clear owner exists for application or external domain state.
|
|
57
|
+
|
|
58
|
+
Prefer `generated-control-surface` for one-shot, interpretive, rapidly changing, or wholly model-mediated interaction. Do not create decorative apps, generic remote terminals, arbitrary command runners, or deterministic facades over ambiguous high-impact decisions.
|
|
59
|
+
|
|
60
|
+
## Choose The Shape
|
|
61
|
+
|
|
62
|
+
### Standalone deterministic application
|
|
63
|
+
|
|
64
|
+
The app owns a closed state machine such as a game, form, selector, simulation, or compact workflow. Its state must reconstruct the current view and explain the previous transition.
|
|
65
|
+
|
|
66
|
+
### View/controller adapter
|
|
67
|
+
|
|
68
|
+
Another capability remains the authoritative real owner. The app stores validated adapter configuration and a last-observed projection only. Re-read the owner before mutation or explicit status; never promote cached view state into domain authority.
|
|
69
|
+
|
|
70
|
+
## Authoring Workflow
|
|
71
|
+
|
|
72
|
+
1. Identify the repeated feedback loop, real state owner, and actions that are truly deterministic.
|
|
73
|
+
2. Choose one stable lowercase app and one self-contained `<app>.mjs` source outside the managed installation directory.
|
|
74
|
+
3. Keep `init` and every exported method small, named, bounded, shell-free, and capability-specific.
|
|
75
|
+
4. Render one complete next view after each action.
|
|
76
|
+
5. Mix action planes intentionally:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
app::method(argument) → bounded method without a model turn
|
|
80
|
+
ordinary prompt → model-mediated interpretation or explanation
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
6. Review state fields, arguments, process calls, rendered values, secrets, destructive effects, and failure paths.
|
|
84
|
+
7. Install with `telegram_bind({ app, script, argument })`.
|
|
85
|
+
8. Replace the same logical app only with explicit `replace: true`; never create `-v2` identities merely to reload code.
|
|
86
|
+
9. Invoke read-only diagnostics with `telegram_bind({ app, method, argument, display: false })` when agent-side evidence is needed.
|
|
87
|
+
10. Keep the maintained source with its capability owner; managed `genapps/` state is runtime installation, not source ownership.
|
|
88
|
+
|
|
89
|
+
## Safety Rules
|
|
90
|
+
|
|
91
|
+
- A direct click authorizes only its exact installed method and validated JSON argument.
|
|
92
|
+
- Use exact executable plus argv through the bounded process port; never expose generic `exec` or shell text.
|
|
93
|
+
- Keep credentials and unrelated private state out of source, state, output, and diagnostics.
|
|
94
|
+
- Route consequences requiring contextual judgment through an ordinary model prompt.
|
|
95
|
+
- Fail closed on unavailable owners, stale actions, malformed state, absent methods, process failures, or uncertain effects.
|
|
96
|
+
- Do not claim automatic refresh, removal, voice output, or other behavior still marked incomplete in the runtime document.
|
|
97
|
+
|
|
98
|
+
## Validation
|
|
99
|
+
|
|
100
|
+
Before presenting an app as working:
|
|
101
|
+
|
|
102
|
+
- Confirm app, source stem, installed identity, and bound prompts agree.
|
|
103
|
+
- Inspect the installed initial view and persisted bounded state.
|
|
104
|
+
- Exercise at least one real bound action and prove it bypasses Pi queue/model admission.
|
|
105
|
+
- Exercise at least one ordinary prompt when the app intentionally uses the model plane.
|
|
106
|
+
- Verify replacement rejects stale buttons and failed initialization preserves the prior app.
|
|
107
|
+
- For adapters, prove fresh external status and terminal mutation evidence.
|
|
108
|
+
- Confirm failures are bounded, redacted, and do not silently render success.
|
|
109
|
+
|
|
110
|
+
Stop and return to ordinary model interaction when the workflow cannot be represented safely as reviewed bounded methods plus explicit model prompts.
|
|
@@ -60,7 +60,7 @@ Button forms:
|
|
|
60
60
|
<!-- telegram_button [{⬆️ Up|/}[{⬅️|page-1}{➡️|page-3}]{📁 etc|/etc}] -->
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
- `telegram_button` accepts one JSON object, a JSON matrix, Compact Matrix Literal (CML), or double-quoted attributes; `telegram_buttons` is an exact plural alias. CML uses `{value}` or `{label|prompt}`, trims atom boundaries, preserves other printable text literally, and decodes only `\|`, `\}`, and `\\`. In a matrix, each top-level cell becomes a full-width row and each nested row groups one or more buttons horizontally without a parser-level width cap. Prefer one matrix comment for multiple buttons, normally keep generated rows at five columns or fewer, and use six through eight only for short position-bearing labels. Keep the complete action in one top-level comment and encode multiline content with JSON `\n`.
|
|
63
|
+
- `telegram_button` accepts one JSON object, a JSON matrix, Compact Matrix Literal (CML), or double-quoted attributes; `telegram_buttons` is an exact plural alias. CML uses `{value}`, `{label|prompt}`, or `{label|prompt|selected_style}`; the optional third atom requires an explicit prompt and accepts only `primary`, `success`, or `danger`. It trims atom boundaries, preserves other printable text literally, and decodes only `\|`, `\}`, and `\\`. Prefer CML for model-authored controls whenever it can express the required surface; use expanded JSON only for multiline prompts, non-positional metadata, or a concrete parse/render failure fallback, never merely from implementation habit. Deterministic Generative App scripts may use ordinary JSON because their output does not spend model tokens. In a matrix, each top-level cell becomes a full-width row and each nested row groups one or more buttons horizontally without a parser-level width cap. Prefer one matrix comment for multiple buttons, normally keep generated rows at five columns or fewer, and use six through eight only for short position-bearing labels. Keep the complete action in one top-level comment and encode multiline content with JSON `\n`.
|
|
64
64
|
- Use `label` plus a self-contained `prompt`, or non-empty `value` when both are identical.
|
|
65
65
|
- Optional `selected_style` is `primary` (default), `success`, or `danger`; style never suppresses prompt admission.
|
|
66
66
|
- If button comments form the whole reply, the bridge supplies the standard choice heading.
|
|
@@ -102,6 +102,10 @@ Threaded Mode operates in private chats when Telegram exposes thread support for
|
|
|
102
102
|
|
|
103
103
|
Cross-Thread delivery must preserve the concrete target and current registration authority. Use ordinary reply delivery for the source turn and `telegram_message(thread=...)` only for an explicitly requested different live Thread.
|
|
104
104
|
|
|
105
|
+
## Generative Apps
|
|
106
|
+
|
|
107
|
+
Load and follow the bundled `generative-apps` Skill when work designs, authors, reviews, installs, replaces, invokes, or diagnoses a Generative App. Generative Apps compile stable repeated interaction into generated button views that may mix bound methods executed before Pi queue admission with ordinary model prompts; this bridge Skill continues to own Telegram transport, target authority, delivery, general button syntax, and turn behavior rather than duplicating the application workflow.
|
|
108
|
+
|
|
105
109
|
## Configurable Handlers And Extensions
|
|
106
110
|
|
|
107
111
|
Prefer no-code command-template configuration in `telegram.json` before adding a companion extension:
|