@llblab/pi-telegram 0.36.2 → 0.36.4
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/CHANGELOG.md +8 -0
- package/README.md +2 -0
- package/docs/architecture.md +10 -0
- package/lib/lifecycle.ts +19 -3
- package/lib/menu-model.ts +4 -2
- package/lib/pi.ts +70 -4
- package/lib/prompts.ts +50 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
+
## 0.36.4: Async Host Settings Compatibility
|
|
6
|
+
|
|
7
|
+
- `Async Host Settings`: Normalizes synchronous or asynchronous settings construction plus legacy enabled-model methods or generic `get` / `set` services at the Pi adapter boundary before model-menu reads and scoped-model persistence, improving compatible-host interoperability without changing native Pi behavior or weakening required `agent_settled` semantics.
|
|
8
|
+
|
|
9
|
+
## 0.36.3: Ordered Prompt Compatibility
|
|
10
|
+
|
|
11
|
+
- `Prompt Compatibility`: Preserves plain and ordered-block system prompts across Pi-compatible runtimes; Telegram guidance remains a distinct block when the host supplies blocks, while unavailable transport strips only Telegram tool metadata without collapsing unrelated prompt context.
|
|
12
|
+
|
|
5
13
|
## 0.36.2: Provider-Compatible Controls
|
|
6
14
|
|
|
7
15
|
- `Provider-Compatible Bind Schema`: Exposes `telegram_bind` through one top-level JSON object schema while retaining runtime enforcement of mutually exclusive install and invocation forms, avoiding providers that reject top-level union schemas before any tool call can run.
|
package/README.md
CHANGED
|
@@ -28,6 +28,8 @@ pi install git:github.com/llblab/pi-telegram
|
|
|
28
28
|
|
|
29
29
|
The 0.21 extension platform requires Pi `0.80.6` or newer. Its Activity API uses the public `agent_settled` lifecycle event to keep retries/continuations under one activity identity and release that identity only after the run fully settles.
|
|
30
30
|
|
|
31
|
+
Pi is the primary and only officially supported host. Narrow host-neutral adapters preserve ordered prompt blocks and normalize synchronous or asynchronous legacy/generic settings services for Pi-compatible hosts, but this is best-effort compatibility rather than an OMP support guarantee. Alternate-host shims must still reproduce required Pi lifecycle semantics—especially `agent_settled`—and their maintainers own ongoing validation.
|
|
32
|
+
|
|
31
33
|
## Quick Start
|
|
32
34
|
|
|
33
35
|
### 1. Create a Telegram bot
|
package/docs/architecture.md
CHANGED
|
@@ -88,6 +88,16 @@ The repository uses a **Flat Domain DAG**:
|
|
|
88
88
|
- `bindings` / `lifecycle` / `prompts` / `prompt-templates` / `pi`: Pi-facing command/tool/hook registration and cohesive cross-domain binding assembly, including queue mutation/dispatch/watchdog composition over admission and transport ports; session-generation fencing and start/shutdown sequencing across Queue, grouped input, Delivery, polling, capability monitor, follower refresh, and assistant-output projection; Telegram prompt guidance; prompt-template discovery/expansion; and centralized direct Pi SDK imports.
|
|
89
89
|
- `command-templates`: shell-free command-template helpers, composition expansion, placeholder substitution, executable resolution, warnings, and retry/timeout semantics.
|
|
90
90
|
|
|
91
|
+
### Host Compatibility Boundary
|
|
92
|
+
|
|
93
|
+
Pi is the primary and only officially supported host. `pi-telegram` may still accept narrow, host-neutral representation differences at its existing Pi-facing boundary when they preserve native Pi behavior and do not create a second runtime policy layer:
|
|
94
|
+
|
|
95
|
+
- `prompts` preserves either Pi's plain system-prompt string or an ordered block array supplied by a compatible host, appending Telegram guidance without collapsing host-owned blocks.
|
|
96
|
+
- `pi` normalizes settings-manager construction that is either synchronous or asynchronous, then adapts either Pi's legacy enabled-model methods or a generic `get` / `set` settings service before model-menu reads and scoped-model persistence use it. Hosts without an explicit reload method rely on fresh asynchronous construction; durable writes still require `flush`.
|
|
97
|
+
- `lifecycle` continues to require Pi's semantic `agent_settled` boundary. It does not infer terminal settlement from host-specific `agent_end`, retry, or stop events; a compatibility shim must reproduce that contract before it can safely support activity identity and unrecovered-error finalization.
|
|
98
|
+
|
|
99
|
+
This boundary uses no host-name detection, host package dependency, prototype patching, hidden agent process, PTY, or terminal forwarding. Representation adapters are best-effort compatibility rather than an OMP support guarantee. Alternate hosts and community contributors own validation of their compatibility shims and must supply every lifecycle semantic that the bridge requires.
|
|
100
|
+
|
|
91
101
|
### Guarded Invariants
|
|
92
102
|
|
|
93
103
|
Architecture invariant tests protect:
|
package/lib/lifecycle.ts
CHANGED
|
@@ -40,8 +40,15 @@ export function createAgentStartDedupHook(
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
type TelegramBeforeAgentStartEvent = Omit<
|
|
44
|
+
BeforeAgentStartEvent,
|
|
45
|
+
"systemPrompt"
|
|
46
|
+
> & {
|
|
47
|
+
systemPrompt: string | string[];
|
|
48
|
+
};
|
|
49
|
+
|
|
43
50
|
export interface TelegramBeforeAgentStartResult {
|
|
44
|
-
systemPrompt?: string;
|
|
51
|
+
systemPrompt?: string | string[];
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
type TelegramBeforeAgentStartReturn =
|
|
@@ -72,7 +79,7 @@ export interface TelegramLifecycleRegistrationDeps {
|
|
|
72
79
|
ctx: ExtensionContext,
|
|
73
80
|
) => Promise<void> | void;
|
|
74
81
|
onBeforeAgentStart: (
|
|
75
|
-
event:
|
|
82
|
+
event: TelegramBeforeAgentStartEvent,
|
|
76
83
|
ctx: ExtensionContext,
|
|
77
84
|
) => TelegramBeforeAgentStartReturn;
|
|
78
85
|
onModelSelect: (
|
|
@@ -581,7 +588,16 @@ export function registerTelegramLifecycleHooks(
|
|
|
581
588
|
if (!isActive(ctx)) return;
|
|
582
589
|
await deps.onSessionCompact?.(event, ctx);
|
|
583
590
|
});
|
|
584
|
-
|
|
591
|
+
// The Pi SDK still types this result as a string; compatible runtimes may
|
|
592
|
+
// preserve ordered system prompt blocks through the same public hook.
|
|
593
|
+
const registerBeforeAgentStart = pi.on.bind(pi) as unknown as (
|
|
594
|
+
event: "before_agent_start",
|
|
595
|
+
handler: (
|
|
596
|
+
event: TelegramBeforeAgentStartEvent,
|
|
597
|
+
ctx: ExtensionContext,
|
|
598
|
+
) => TelegramBeforeAgentStartReturn,
|
|
599
|
+
) => void;
|
|
600
|
+
registerBeforeAgentStart("before_agent_start", async (event, ctx) => {
|
|
585
601
|
return deps.onBeforeAgentStart(event, ctx);
|
|
586
602
|
});
|
|
587
603
|
pi.on("model_select", async (event, ctx) => {
|
package/lib/menu-model.ts
CHANGED
|
@@ -119,7 +119,9 @@ export interface TelegramModelMenuStateBuilderDeps<
|
|
|
119
119
|
TelegramModelMenuStateBuilderContext<TModel>,
|
|
120
120
|
> {
|
|
121
121
|
runtime: TelegramModelMenuRuntime<TModel>;
|
|
122
|
-
createSettingsManager: (
|
|
122
|
+
createSettingsManager: (
|
|
123
|
+
cwd: string,
|
|
124
|
+
) => MenuSettingsManager | PromiseLike<MenuSettingsManager>;
|
|
123
125
|
getActiveModel: (ctx: TContext) => TModel | undefined;
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -490,7 +492,7 @@ export function createTelegramModelMenuStateBuilder<
|
|
|
490
492
|
threadId?: number,
|
|
491
493
|
) => Promise<TelegramModelMenuState<TModel>> {
|
|
492
494
|
return async (chatId, ctx, threadId) => {
|
|
493
|
-
const settingsManager = deps.createSettingsManager(ctx.cwd);
|
|
495
|
+
const settingsManager = await deps.createSettingsManager(ctx.cwd);
|
|
494
496
|
return deps.runtime.buildState({
|
|
495
497
|
chatId,
|
|
496
498
|
threadId,
|
package/lib/pi.ts
CHANGED
|
@@ -151,16 +151,82 @@ export function createExtensionApiRuntimePorts(
|
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
type PiSettingsManagerFactory = {
|
|
155
|
+
create: (cwd: string) => unknown | PromiseLike<unknown>;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
type HostSettingsManager = {
|
|
159
|
+
reload?: () => void | PromiseLike<void>;
|
|
160
|
+
flush?: () => void | PromiseLike<void>;
|
|
161
|
+
getEnabledModels?: () => unknown;
|
|
162
|
+
setEnabledModels?: (patterns: string[] | undefined) => void;
|
|
163
|
+
get?: (key: string) => unknown;
|
|
164
|
+
set?: (key: string, value: unknown) => void;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function readEnabledModels(value: unknown): string[] | undefined {
|
|
168
|
+
if (value === undefined) return undefined;
|
|
169
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
170
|
+
return [...value];
|
|
171
|
+
}
|
|
172
|
+
throw new TypeError("Host settings enabledModels must be a string array or undefined.");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function normalizeSettingsManager(manager: unknown): PiSettingsManager {
|
|
176
|
+
if (typeof manager !== "object" || manager === null) {
|
|
177
|
+
throw new TypeError("Host settings manager must be an object.");
|
|
178
|
+
}
|
|
179
|
+
const host = manager as HostSettingsManager;
|
|
180
|
+
if (typeof host.flush !== "function") {
|
|
181
|
+
throw new TypeError("Host settings manager must provide flush().");
|
|
182
|
+
}
|
|
183
|
+
const read = typeof host.getEnabledModels === "function"
|
|
184
|
+
? () => host.getEnabledModels!.call(host)
|
|
185
|
+
: typeof host.get === "function"
|
|
186
|
+
? () => host.get!.call(host, "enabledModels")
|
|
187
|
+
: undefined;
|
|
188
|
+
const write = typeof host.setEnabledModels === "function"
|
|
189
|
+
? (patterns: string[] | undefined) =>
|
|
190
|
+
host.setEnabledModels!.call(host, patterns)
|
|
191
|
+
: typeof host.set === "function"
|
|
192
|
+
? (patterns: string[] | undefined) =>
|
|
193
|
+
host.set!.call(host, "enabledModels", patterns ?? [])
|
|
194
|
+
: undefined;
|
|
195
|
+
if (!read || !write) {
|
|
196
|
+
throw new TypeError(
|
|
197
|
+
"Host settings manager must provide enabled-model read and write capabilities.",
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
reload: async () => {
|
|
202
|
+
await host.reload?.call(host);
|
|
203
|
+
},
|
|
204
|
+
flush: async () => {
|
|
205
|
+
await host.flush!.call(host);
|
|
206
|
+
},
|
|
207
|
+
getEnabledModels: () => readEnabledModels(read()),
|
|
208
|
+
setEnabledModels: write,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function createSettingsManager(
|
|
213
|
+
cwd: string,
|
|
214
|
+
): Promise<PiSettingsManager> {
|
|
215
|
+
// Pi returns its legacy settings surface synchronously. Compatible hosts may
|
|
216
|
+
// resolve a generic settings service asynchronously; normalize both once at
|
|
217
|
+
// the SDK boundary instead of leaking host distinctions into menu domains.
|
|
218
|
+
const factory = SettingsManager as unknown as PiSettingsManagerFactory;
|
|
219
|
+
return normalizeSettingsManager(await factory.create(cwd));
|
|
156
220
|
}
|
|
157
221
|
|
|
158
222
|
export function createScopedModelPatternPersister(deps: {
|
|
159
|
-
createSettingsManager: (
|
|
223
|
+
createSettingsManager: (
|
|
224
|
+
cwd: string,
|
|
225
|
+
) => PiSettingsManager | PromiseLike<PiSettingsManager>;
|
|
160
226
|
clearCachedModelMenuInputs: () => void;
|
|
161
227
|
}): (patterns: string[], ctx: ExtensionContext) => Promise<void> {
|
|
162
228
|
return async (patterns, ctx) => {
|
|
163
|
-
const settingsManager = deps.createSettingsManager(ctx.cwd);
|
|
229
|
+
const settingsManager = await deps.createSettingsManager(ctx.cwd);
|
|
164
230
|
settingsManager.setEnabledModels(
|
|
165
231
|
patterns.length > 0 ? patterns : undefined,
|
|
166
232
|
);
|
package/lib/prompts.ts
CHANGED
|
@@ -31,6 +31,15 @@ export const TELEGRAM_MESSAGE_PROMPT_GUIDELINES = [
|
|
|
31
31
|
"During an active Telegram turn, omit telegram_message for the current target and answer normally; use thread only when the user requests delivery to a different live Pi thread.",
|
|
32
32
|
] as const;
|
|
33
33
|
|
|
34
|
+
const TELEGRAM_TOOL_METADATA_LINES = Object.fromEntries(
|
|
35
|
+
[
|
|
36
|
+
`- telegram_attach: ${TELEGRAM_ATTACH_PROMPT_SNIPPET}`,
|
|
37
|
+
`- telegram_message: ${TELEGRAM_MESSAGE_PROMPT_SNIPPET}`,
|
|
38
|
+
...TELEGRAM_ATTACH_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
39
|
+
...TELEGRAM_MESSAGE_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
40
|
+
].map((line) => [line, true]),
|
|
41
|
+
) as Record<string, true>;
|
|
42
|
+
|
|
34
43
|
const TELEGRAM_MODEL_CONTEXT_TOOL_NAMES = new Set([
|
|
35
44
|
"telegram_attach",
|
|
36
45
|
"telegram_bind",
|
|
@@ -132,13 +141,30 @@ export function createTelegramModelContextAvailabilityRuntime(deps: {
|
|
|
132
141
|
};
|
|
133
142
|
}
|
|
134
143
|
|
|
144
|
+
export type TelegramSystemPrompt = string | string[];
|
|
145
|
+
|
|
146
|
+
type TelegramBeforeAgentStartEvent = Omit<
|
|
147
|
+
BeforeAgentStartEvent,
|
|
148
|
+
"systemPrompt"
|
|
149
|
+
> & {
|
|
150
|
+
systemPrompt: TelegramSystemPrompt;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
type TelegramBeforeAgentStartResult = {
|
|
154
|
+
systemPrompt: TelegramSystemPrompt;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
type TelegramBeforeAgentStartHook = (
|
|
158
|
+
event: TelegramBeforeAgentStartEvent,
|
|
159
|
+
) => TelegramBeforeAgentStartResult;
|
|
160
|
+
|
|
135
161
|
export function buildTelegramBridgeSystemPrompt(options: {
|
|
136
162
|
prompt: string;
|
|
137
|
-
systemPrompt:
|
|
163
|
+
systemPrompt: TelegramSystemPrompt;
|
|
138
164
|
telegramPrefix?: string;
|
|
139
165
|
localSystemPromptSuffix: string;
|
|
140
166
|
telegramTurnSystemPromptSuffix: string;
|
|
141
|
-
}):
|
|
167
|
+
}): TelegramBeforeAgentStartResult {
|
|
142
168
|
const telegramPrefix = options.telegramPrefix ?? TELEGRAM_PREFIX;
|
|
143
169
|
const telegramHead = telegramPrefix.endsWith("]")
|
|
144
170
|
? telegramPrefix.slice(0, -1)
|
|
@@ -151,8 +177,14 @@ export function buildTelegramBridgeSystemPrompt(options: {
|
|
|
151
177
|
? `${options.telegramTurnSystemPromptSuffix}\n- The current user message came from Telegram.`
|
|
152
178
|
: "";
|
|
153
179
|
return {
|
|
154
|
-
systemPrompt:
|
|
155
|
-
|
|
180
|
+
systemPrompt: Array.isArray(options.systemPrompt)
|
|
181
|
+
? [
|
|
182
|
+
...options.systemPrompt,
|
|
183
|
+
options.localSystemPromptSuffix + telegramSuffix,
|
|
184
|
+
]
|
|
185
|
+
: options.systemPrompt +
|
|
186
|
+
options.localSystemPromptSuffix +
|
|
187
|
+
telegramSuffix,
|
|
156
188
|
};
|
|
157
189
|
}
|
|
158
190
|
|
|
@@ -162,7 +194,7 @@ export function createTelegramBeforeAgentStartHook(
|
|
|
162
194
|
localSystemPromptSuffix?: string;
|
|
163
195
|
telegramTurnSystemPromptSuffix?: string;
|
|
164
196
|
} = {},
|
|
165
|
-
):
|
|
197
|
+
): TelegramBeforeAgentStartHook {
|
|
166
198
|
return (event) =>
|
|
167
199
|
buildTelegramBridgeSystemPrompt({
|
|
168
200
|
prompt: event.prompt,
|
|
@@ -176,23 +208,23 @@ export function createTelegramBeforeAgentStartHook(
|
|
|
176
208
|
});
|
|
177
209
|
}
|
|
178
210
|
|
|
179
|
-
function
|
|
180
|
-
systemPrompt: string,
|
|
181
|
-
): string {
|
|
182
|
-
const telegramLines = new Set([
|
|
183
|
-
`- telegram_attach: ${TELEGRAM_ATTACH_PROMPT_SNIPPET}`,
|
|
184
|
-
`- telegram_message: ${TELEGRAM_MESSAGE_PROMPT_SNIPPET}`,
|
|
185
|
-
...TELEGRAM_ATTACH_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
186
|
-
...TELEGRAM_MESSAGE_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
187
|
-
]);
|
|
211
|
+
function stripTelegramToolMetadataFromString(systemPrompt: string): string {
|
|
188
212
|
return systemPrompt
|
|
189
213
|
.split("\n")
|
|
190
|
-
.filter((line) =>
|
|
214
|
+
.filter((line) => TELEGRAM_TOOL_METADATA_LINES[line] !== true)
|
|
191
215
|
.join("\n");
|
|
192
216
|
}
|
|
193
217
|
|
|
218
|
+
function stripTelegramToolMetadataFromSystemPrompt(
|
|
219
|
+
systemPrompt: TelegramSystemPrompt,
|
|
220
|
+
): TelegramSystemPrompt {
|
|
221
|
+
return Array.isArray(systemPrompt)
|
|
222
|
+
? systemPrompt.map(stripTelegramToolMetadataFromString)
|
|
223
|
+
: stripTelegramToolMetadataFromString(systemPrompt);
|
|
224
|
+
}
|
|
225
|
+
|
|
194
226
|
export interface TelegramProactivePromptHookDeps<TContext> {
|
|
195
|
-
baseHook?:
|
|
227
|
+
baseHook?: TelegramBeforeAgentStartHook;
|
|
196
228
|
reconcileAvailability?: () => void;
|
|
197
229
|
isAvailable: (ctx: TContext) => boolean;
|
|
198
230
|
}
|
|
@@ -200,9 +232,9 @@ export interface TelegramProactivePromptHookDeps<TContext> {
|
|
|
200
232
|
export function createTelegramProactiveBeforeAgentStartHook<TContext>(
|
|
201
233
|
deps: TelegramProactivePromptHookDeps<TContext>,
|
|
202
234
|
): (
|
|
203
|
-
event:
|
|
235
|
+
event: TelegramBeforeAgentStartEvent,
|
|
204
236
|
ctx: TContext,
|
|
205
|
-
) => Promise<
|
|
237
|
+
) => Promise<TelegramBeforeAgentStartResult> {
|
|
206
238
|
const baseHook = deps.baseHook ?? createTelegramBeforeAgentStartHook();
|
|
207
239
|
return async (event, ctx) => {
|
|
208
240
|
deps.reconcileAvailability?.();
|