@llblab/pi-kit 0.14.1 → 0.16.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/CHANGELOG.md +11 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +6 -0
- package/node_modules/@llblab/pi-codex-usage/index.ts +81 -7
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +9 -9
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +0 -13
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +15 -0
- package/node_modules/@llblab/pi-state-flow/README.md +7 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +5 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +17 -16
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +24 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +8 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +4 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +5 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +93 -35
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +20 -23
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +8 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +29 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +34 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +14 -16
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +27 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +182 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +34 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +20 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +6 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +5 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +55 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +8 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +10 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +7 -3
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +11 -3
- package/node_modules/@llblab/pi-state-flow/docs/README.md +1 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +5 -3
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +3 -2
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +504 -0
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +11 -8
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +18 -15
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +24 -0
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -4
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +5 -0
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +86 -34
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +23 -22
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +8 -4
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +31 -3
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +33 -16
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -16
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +173 -9
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +32 -4
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +22 -25
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +11 -6
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +46 -18
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +19 -6
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +7 -3
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +11 -3
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -0
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +12 -0
- package/node_modules/@llblab/pi-telegram/README.md +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +7 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +134 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +297 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +11 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +68 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +17 -13
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +59 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +23 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +20 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +18 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +152 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +5 -3
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +3 -3
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +4 -3
- package/node_modules/@llblab/pi-telegram/docs/sections.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +2 -1
- package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/activity.ts +14 -0
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +10 -2
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +466 -21
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +15 -0
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +77 -9
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +25 -10
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +3 -0
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +7 -1
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +90 -19
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +30 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +199 -6
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +5 -2
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +4 -4
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Excludes: temporal algebra, Pi lifecycle, Git objects/remotes, and backend fallback policy.
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { closeSync, lstatSync, mkdirSync, openSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { closeSync, lstatSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, relative, resolve } from "node:path";
|
|
7
7
|
import {
|
|
8
8
|
assertOwnedFileUpdates, captureTemporalFileBases, parseScopeProvenance, parseScopeStream, restoreDurableFileBases,
|
|
@@ -44,17 +44,42 @@ export function initializeFileStore(root: string): void {
|
|
|
44
44
|
mkdirSync(resolve(root), { recursive: true });
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
const PUBLICATION_LOCK_WAIT_MS = 2_000;
|
|
48
|
+
const PUBLICATION_LOCK_POLL_MS = 25;
|
|
49
|
+
const publicationLockWait = new Int32Array(new SharedArrayBuffer(4));
|
|
50
|
+
|
|
51
|
+
function liveForeignLockOwner(path: string): boolean {
|
|
52
|
+
let owner: string;
|
|
53
|
+
try { owner = readFileSync(path, "utf8").trim(); }
|
|
54
|
+
catch { return true; }
|
|
55
|
+
if (owner.length === 0) return true;
|
|
56
|
+
if (!/^[1-9]\d*$/.test(owner)) return false;
|
|
57
|
+
const pid = Number(owner);
|
|
58
|
+
if (!Number.isSafeInteger(pid) || pid === process.pid) return false;
|
|
59
|
+
try { process.kill(pid, 0); return true; }
|
|
60
|
+
catch (error) { return (error as NodeJS.ErrnoException).code === "EPERM"; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Wait only for a cooperating live owner; interrupted or malformed locks remain explicit recovery errors. */
|
|
64
|
+
export function acquirePublicationLock(path: string, unavailable: (cause: unknown) => Error): number {
|
|
65
|
+
const deadline = Date.now() + PUBLICATION_LOCK_WAIT_MS;
|
|
66
|
+
while (true) {
|
|
67
|
+
try { return openSync(path, "wx", 0o600); }
|
|
68
|
+
catch (error) {
|
|
69
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST" || !liveForeignLockOwner(path) || Date.now() >= deadline) throw unavailable(error);
|
|
70
|
+
Atomics.wait(publicationLockWait, 0, 0, Math.min(PUBLICATION_LOCK_POLL_MS, deadline - Date.now()));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
47
75
|
/** Git writers also acquire this lock before their common-Git-directory lock. */
|
|
48
76
|
export function withStoragePublicationLock<T>(repositoryRoot: string, action: (root: string) => T): T {
|
|
49
77
|
const root = resolve(repositoryRoot);
|
|
50
78
|
assertStorageDirectory(root);
|
|
51
79
|
const path = resolve(root, ".state-flow-publication.lock");
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} catch (error) {
|
|
56
|
-
throw new RevisionUnavailableError(`State Flow publication lock is unavailable at ${path}; reconcile the active or interrupted publisher before retrying`, { cause: error });
|
|
57
|
-
}
|
|
80
|
+
const descriptor = acquirePublicationLock(path, (cause) => new RevisionUnavailableError(
|
|
81
|
+
`State Flow publication lock is unavailable at ${path}; reconcile the active or interrupted publisher before retrying`, { cause },
|
|
82
|
+
));
|
|
58
83
|
try {
|
|
59
84
|
writeFileSync(descriptor, `${process.pid}\n`);
|
|
60
85
|
return action(root);
|
|
@@ -91,9 +116,9 @@ export function planTemporalPublication(
|
|
|
91
116
|
changedScopes.push(scope);
|
|
92
117
|
}
|
|
93
118
|
const provenanceUpdates: OwnedFileUpdate[] = [];
|
|
94
|
-
//
|
|
119
|
+
// Every scope metadata file owns provenance and temporal boundaries beside semantic files.
|
|
95
120
|
if (!runtimeOnly) {
|
|
96
|
-
for (const scope of
|
|
121
|
+
for (const scope of SCOPES) {
|
|
97
122
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
98
123
|
const registry = provenance?.[scope] ?? parseScopeProvenance(files.get(paths.meta)!.content, paths.meta);
|
|
99
124
|
const currentFile = files.get(paths.meta)!;
|
|
@@ -104,18 +129,21 @@ export function planTemporalPublication(
|
|
|
104
129
|
}
|
|
105
130
|
}
|
|
106
131
|
const runtimePaths = sessionRuntimePaths(cwd, sessionId, root, sessionKey);
|
|
107
|
-
const previousRuntime = parseSessionRuntime(files.get(runtimePaths.config)!.content, files.get(runtimePaths.
|
|
132
|
+
const previousRuntime = parseSessionRuntime(files.get(runtimePaths.config)!.content, files.get(runtimePaths.runtime)!.content, cwd, sessionId, files.get(runtimePaths.meta)!.content);
|
|
108
133
|
if (previousRuntime !== undefined && changedScopes.length > 0 && runtime === undefined) throw new Error("Temporal semantic publication requires its session runtime cohort");
|
|
109
134
|
const runtimeUpdates: OwnedFileUpdate[] = [];
|
|
110
135
|
if (runtime !== undefined) {
|
|
111
|
-
const sources = serializeSessionRuntime(runtime, cwd, sessionId
|
|
136
|
+
const sources = serializeSessionRuntime(runtime, cwd, sessionId);
|
|
112
137
|
if (!sameJson(runtime.meta.lineage, view.lineage)) throw new Error("Runtime lineage does not match the temporal cohort");
|
|
113
|
-
if (files.get(runtimePaths.config)!.content !== sources.config || files.get(runtimePaths.
|
|
114
|
-
runtimeUpdates.push({ path: runtimePaths.config, content: sources.config }, { path: runtimePaths.
|
|
138
|
+
if (files.get(runtimePaths.config)!.content !== sources.config || files.get(runtimePaths.runtime)!.content !== sources.runtime) {
|
|
139
|
+
runtimeUpdates.push({ path: runtimePaths.config, content: sources.config }, { path: runtimePaths.runtime, content: sources.runtime });
|
|
140
|
+
}
|
|
141
|
+
if (files.get(runtimePaths.runtime)!.identity === "missing" && files.get(runtimePaths.meta)!.content !== undefined
|
|
142
|
+
&& previousRuntime !== undefined && !provenanceUpdates.some(({ path }) => path === runtimePaths.meta)) {
|
|
143
|
+
const registry = provenance?.session ?? parseArtifactProvenanceRegistry(previousRuntime.meta.artifacts, "State Flow session artifact provenance");
|
|
144
|
+
const content = serializeScopeMetadata(registry, view.scopes.session, "session", undefined, files.get(runtimePaths.meta)!.content);
|
|
145
|
+
runtimeUpdates.push({ path: runtimePaths.meta, content });
|
|
115
146
|
}
|
|
116
|
-
} else if (!runtimeOnly && changedScopes.includes("session")) {
|
|
117
|
-
const content = serializeScopeMetadata(undefined, view.scopes.session, "session", undefined, files.get(runtimePaths.meta)!.content);
|
|
118
|
-
if (files.get(runtimePaths.meta)!.content !== content) runtimeUpdates.push({ path: runtimePaths.meta, content });
|
|
119
147
|
}
|
|
120
148
|
const changedPaths = new Set(changedScopes.flatMap((scope) => {
|
|
121
149
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
@@ -153,14 +181,14 @@ function decodeFileCohort(cwd: string, sessionId: string, root: string, base: Te
|
|
|
153
181
|
scopes[scope] = stream;
|
|
154
182
|
}
|
|
155
183
|
const paths = sessionRuntimePaths(cwd, sessionId, root, sessionKey);
|
|
156
|
-
const runtime = parseSessionRuntime(files.get(paths.config), files.get(paths.
|
|
184
|
+
const runtime = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), cwd, sessionId, files.get(paths.meta));
|
|
157
185
|
if (!runtime || runtime.meta.publication !== "files") throw new Error("File-only recovery requires file publication provenance, not a Git self reference");
|
|
158
186
|
const view = { scopes, lineage: runtime.meta.lineage };
|
|
159
187
|
validateTemporalState(view);
|
|
160
188
|
const provenance: Record<StateScope, ArtifactProvenanceRegistry> = {
|
|
161
189
|
global: parseScopeProvenance(files.get(temporalScopePaths(cwd, sessionId, "global", root, sessionKey).meta), temporalScopePaths(cwd, sessionId, "global", root, sessionKey).meta),
|
|
162
190
|
cwd: parseScopeProvenance(files.get(temporalScopePaths(cwd, sessionId, "cwd", root, sessionKey).meta), temporalScopePaths(cwd, sessionId, "cwd", root, sessionKey).meta),
|
|
163
|
-
session:
|
|
191
|
+
session: parseScopeProvenance(files.get(paths.meta), paths.meta),
|
|
164
192
|
};
|
|
165
193
|
return { runtime, view, provenance };
|
|
166
194
|
}
|
|
@@ -27,12 +27,18 @@ export interface StateFlowTelegramState {
|
|
|
27
27
|
contract: Record<string, unknown>;
|
|
28
28
|
working: Record<string, unknown>;
|
|
29
29
|
response: string;
|
|
30
|
+
lazy?: unknown;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
export type StateFlowTelegramRichText =
|
|
34
|
+
| string
|
|
35
|
+
| StateFlowTelegramRichText[]
|
|
36
|
+
| { type: "bold" | "code"; text: StateFlowTelegramRichText };
|
|
37
|
+
|
|
32
38
|
export type StateFlowTelegramRichBlock =
|
|
33
|
-
| { type: "heading"; text:
|
|
34
|
-
| { type: "pre"; text:
|
|
35
|
-
| { type: "details"; summary:
|
|
39
|
+
| { type: "heading"; text: StateFlowTelegramRichText; size: 3 }
|
|
40
|
+
| { type: "pre"; text: StateFlowTelegramRichText; language?: string }
|
|
41
|
+
| { type: "details"; summary: StateFlowTelegramRichText; blocks: StateFlowTelegramRichBlock[]; is_open?: true };
|
|
36
42
|
|
|
37
43
|
export interface StateFlowTelegramRichMessage {
|
|
38
44
|
blocks: StateFlowTelegramRichBlock[];
|
|
@@ -168,6 +174,9 @@ function renderStateFlowTelegramField(value: unknown): string {
|
|
|
168
174
|
const length = Math.floor((low + high) / 2);
|
|
169
175
|
const candidate = JSON.stringify({
|
|
170
176
|
truncated: true,
|
|
177
|
+
...(value !== null && typeof value === "object" && !Array.isArray(value)
|
|
178
|
+
? { keys: Object.keys(value) }
|
|
179
|
+
: {}),
|
|
171
180
|
preview: json.slice(0, length),
|
|
172
181
|
omittedChars: json.length - length,
|
|
173
182
|
}, null, 2);
|
|
@@ -182,14 +191,18 @@ function renderStateFlowTelegramField(value: unknown): string {
|
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
export function renderStateFlowRichState(scope: StateFlowTelegramScope, step: number, state: StateFlowTelegramState): StateFlowTelegramRichMessage {
|
|
185
|
-
const fields = ["artifacts", "contract", "working", "response"] as const;
|
|
194
|
+
const fields = ["artifacts", "contract", "working", "response", "lazy"] as const;
|
|
186
195
|
return {
|
|
187
196
|
blocks: [
|
|
188
|
-
{
|
|
197
|
+
{
|
|
198
|
+
type: "heading",
|
|
199
|
+
text: [`${STATE_FLOW_SCOPE_LABELS[scope]}: `, { type: "code", text: `#${step}` }],
|
|
200
|
+
size: 3,
|
|
201
|
+
},
|
|
189
202
|
...fields.map((field) => ({
|
|
190
203
|
type: "details" as const,
|
|
191
204
|
summary: { type: "code" as const, text: field },
|
|
192
|
-
blocks: [{ type: "pre" as const, language: "json", text: renderStateFlowTelegramField(state[field]) }],
|
|
205
|
+
blocks: [{ type: "pre" as const, language: "json", text: renderStateFlowTelegramField(state[field] ?? {}) }],
|
|
193
206
|
})),
|
|
194
207
|
],
|
|
195
208
|
skip_entity_detection: true,
|
|
@@ -35,7 +35,7 @@ export interface StagedScopedTransition {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const SCOPES = new Set<StateScope>(["global", "cwd", "session"]);
|
|
38
|
-
const PATCH_KEYS = new Set(["artifacts", "contract", "working"]);
|
|
38
|
+
const PATCH_KEYS = new Set(["artifacts", "contract", "working", "lazy"]);
|
|
39
39
|
|
|
40
40
|
function compileReadArtifacts(
|
|
41
41
|
nextState: StateDocument,
|
|
@@ -123,14 +123,17 @@ function validateScopePatch(scope: unknown, patch: unknown): asserts patch is Sc
|
|
|
123
123
|
validatePatch(patch);
|
|
124
124
|
for (const key of Object.keys(patch)) {
|
|
125
125
|
if (!PATCH_KEYS.has(key)) {
|
|
126
|
-
throw new Error(`Scoped State Flow patches cannot modify ${key}; only artifacts, contract, and
|
|
126
|
+
throw new Error(`Scoped State Flow patches cannot modify ${key}; only artifacts, contract, working, and lazy are model-owned`);
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
-
for (const key of
|
|
129
|
+
for (const key of ["artifacts", "contract", "working"] as const) {
|
|
130
130
|
if (Object.hasOwn(patch, key) && !isObject(patch[key])) {
|
|
131
131
|
throw new Error(`Scoped State Flow patch field ${key} must be a JSON object`);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
if (Object.hasOwn(patch, "lazy") && patch.lazy === null) {
|
|
135
|
+
throw new Error("Scoped State Flow patch field lazy cannot be null");
|
|
136
|
+
}
|
|
134
137
|
if (isObject(patch.artifacts)) validateModelArtifactPatch(patch.artifacts);
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -140,6 +143,7 @@ function completePatch(patch: ScopePatch, response: string): StatePatch {
|
|
|
140
143
|
contract: patch.contract ?? {},
|
|
141
144
|
working: patch.working ?? {},
|
|
142
145
|
response,
|
|
146
|
+
...(Object.hasOwn(patch, "lazy") ? { lazy: structuredClone(patch.lazy!) } : {}),
|
|
143
147
|
};
|
|
144
148
|
}
|
|
145
149
|
|
|
@@ -46,6 +46,8 @@ Separate a binding requirement from the method currently proposed to satisfy it.
|
|
|
46
46
|
|
|
47
47
|
When it affects continuation, retain what was proposed, accepted, rejected, corrected, explained, or left unresolved, and what the next response or action must address. Preserve enough referents for pending follow-ups to make sense.
|
|
48
48
|
|
|
49
|
+
Treat every completed work slice as a possible restart boundary. Its checkpoint should let a fresh executor recover the achieved outcome, surviving evidence, active commitments, decision-relevant uncertainty, and exact continuation without replaying the prior reasoning trajectory. Optimize decomposition for resumability as well as functional completion; if the next safe action depends on transient context that will disappear, the slice is not yet at a sufficient boundary.
|
|
50
|
+
|
|
49
51
|
Keep consequences, not a transcript or a personality dossier. Do not invent shared history or claim subjective continuity. A fresh run should not unnecessarily reopen a settled exchange or treat an unanswered proposal as approved.
|
|
50
52
|
|
|
51
53
|
### Preserve learning at its demonstrated boundary
|
|
@@ -70,11 +72,17 @@ A locator supports later retrieval; it does not replace content needed for the n
|
|
|
70
72
|
|
|
71
73
|
Do not rerun the underlying project merely to curate its memory. Leave an exact unresolved check when verification falls outside the requested boundary.
|
|
72
74
|
|
|
73
|
-
###
|
|
75
|
+
### Preserve priority and keep Lazy shallow
|
|
76
|
+
|
|
77
|
+
Treat array order in Lazy as semantic priority: earlier entries are higher priority. Preserve that order deliberately; do not reorder entries for aesthetics, incidental grouping, or normalization.
|
|
78
|
+
|
|
79
|
+
Minimize Lazy nesting, especially for top-level collections. Keep a top-level collection as a direct array when its members are the domain values. Represent a standalone item directly, normally as a string; use an object only when that item genuinely owns structured or nested fields. Do not add `items`, `owner`, `source`, or similar wrapper objects merely to describe the collection, and do not introduce nested arrays unless the domain itself requires a matrix or grouped sequence.
|
|
80
|
+
|
|
81
|
+
### Compact without flattening meaning
|
|
74
82
|
|
|
75
83
|
Merge redundant fragments and remove obsolete scaffolding, repeated argumentation, and routine progress. Do not rewrite unchanged state merely to normalize wording.
|
|
76
84
|
|
|
77
|
-
Do not erase a meaningful correction, uncertainty, commitment, negative result, or continuation dependency to make state shorter. Do not retain the previous chain of reasoning solely to steer the next run toward the same method.
|
|
85
|
+
Do not erase a meaningful correction, uncertainty, commitment, negative result, priority order, or continuation dependency to make state shorter. Do not retain the previous chain of reasoning solely to steer the next run toward the same method.
|
|
78
86
|
|
|
79
87
|
### Reconcile phase boundaries
|
|
80
88
|
|
|
@@ -90,7 +98,7 @@ Effective state does not prove which scope owns a value. When ownership matters
|
|
|
90
98
|
|
|
91
99
|
Before writing, review the proposed changes once within the requested boundary:
|
|
92
100
|
|
|
93
|
-
- Would a fresh executor know what must still hold, what changed, what remains unresolved, and
|
|
101
|
+
- Would a fresh executor know what must still hold, what changed, what remains unresolved, and the exact next action without replaying the prior cognitive trajectory?
|
|
94
102
|
- Could an omission cause a known failed attempt, an unnecessary repeated explanation, or loss of an active commitment?
|
|
95
103
|
- Could a retained claim impose an unapproved method, overgeneralize a result, or hide a live alternative?
|
|
96
104
|
|
|
@@ -121,7 +121,7 @@ The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md
|
|
|
121
121
|
- Command templates remain compact and shell-free. Use string leaves or ordered `template` arrays; shell operators are not an execution contract. Examples use portable executable placeholders, never machine-local paths.
|
|
122
122
|
- `telegram_attach` is the canonical file path and `telegram_message` the direct Markdown text/buttons path. Both require current direct or registered-follower authority and must not replace the normal active-turn reply.
|
|
123
123
|
- Inbound handlers transform text/media before queueing; outbound handlers precede programmatic/provider fallbacks. Public contracts and ordering live in `docs/inbound.md`, `docs/outbound.md`, and `docs/public-api.md`.
|
|
124
|
-
- Pi integration uses public hooks and APIs.
|
|
124
|
+
- Pi integration uses public hooks and APIs. Telegram `/new` is scheduled against the exact durable update, dispatched only after that update is removed from the journal, and then routed through an internal Pi command via `pi.sendUserMessage(..., { expandPromptTemplates: true })`; the command handler receives the real `ExtensionCommandContext` and calls `ctx.newSession()`. Before replacement, CAS-publish one exact expiring handoff in the profile target snapshot. `workspace-thread` successors re-key the matching Workspace binding; `classic-chat` successors preserve Profile/CWD/session/chat continuity without creating a binding or invoking topic APIs. Both atomically claim the intent before one terminal result, and the old `withSession` path never publishes the same success. Never replace the session while inbound authority is unsettled, store stale command contexts, accept an expired or mismatched handoff, inject terminal input, spawn a shadow Pi process, or mutate session files.
|
|
125
125
|
|
|
126
126
|
## 7. Engineering Conventions
|
|
127
127
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
_This file owns unresolved project work only. Completed behavior belongs in `CHANGELOG.md`; durable contracts belong in `AGENTS.md` and `/docs`._
|
|
4
4
|
|
|
5
|
+
- [ ] `Unified Telegram /new continuity` (`local-actionable`, current priority; closes [#81](https://github.com/llblab/pi-telegram/issues/81)): The existing one-shot intent now has strict `workspace-thread | classic-chat` continuity. Classic preparation persists exact Profile/CWD/session/chat identity without requiring or fabricating a Workspace binding; its codec round-trip and claim path leave Workspace bindings untouched. Threaded preparation and successor re-key retain the existing slot/name path. Local implementation and docs are complete. Classic preparation-to-successor settlement passes with one terminal send and no Workspace mutation against both the same store instance and a freshly reopened process store; duplicate settlement sends nothing. Threaded re-key, mismatch, expiry, failed-claim, full-suite, invariants, and Domain DAG regressions pass under the discriminated codec. Final `dist` is rebuilt; Classic Settings also hides both Thread-specific controls rather than exposing only cleanup. Remaining: operator-authorized Classic and Threaded live smoke. Do not close or post to #81 before release authority. Both modes delete confirmation before replacement, CAS-claim before terminal send, and preserve at-most-once cross-process result semantics; do not close or post to #81 before release authority.
|
|
5
6
|
- [ ] `Prompt enqueue hotfix` (`optional`, `operator-gated`): Optional nonblocking operator-authorized disposable-follower smoke: overlap delayed voice processing with turn completion and confirm one-time ordered consumption and truthful counts against the [queue contract](./docs/architecture.md#queue-and-dispatch-safety). Separately authorized supported recovery investigation remains open: prevention does not repair an already-wedged in-memory queue; establish the exact recovery path and preservation/discard consequences before mutation, otherwise report the blocker. No journal/ownership edits, replay of settled input, implicit queue clearing, or restart; live activation requires separate operator authorization.
|
|
6
7
|
- [ ] `Channel multimedia posts` (`0.45.1`, live-acceptance-gated): `telegram_message` channel delivery accepts one local `.jpg`/`.jpeg`/`.png`/`.webp` photo or `.mp4` video, uploads it through the multipart transport as `sendPhoto`/`sendVideo` with `text` as the HTML caption, validates kind and size (photo ≤ 10 MiB, video ≤ 50 MiB) plus ≤ 1024 visible caption characters before issuance, and rejects unsupported types and albums instead of downgrading them to links. The channel-post journal binds kind/file name/byte size/SHA-256 and caption, so duplicate requests and lost acknowledgements never re-upload; media-post edits replace the caption through `editMessageCaption`, and Markdown spoilers render as `<tg-spoiler>`. Live image publication passed on `@llb_log`. Regressions cover confirmed publication, duplicate requests, lost ACK, pre-issuance rejection, caption edits, and reconnect replacement. Remaining: operator-authorized disposable-channel acceptance of rejected upload, duplicate request, and caption edit.
|
|
7
8
|
- [ ] `Manual Thread naming` (`gated-but-preparable`, release priority): Local bot-owned `/name Name` and bare `/name` flows avoid model dispatch. One expiring exact-target input dialog immediately accepts the next valid name, always offers cancel, and offers **Reset to automatic** only while a manual override exists; duplicate/stale callbacks cannot repeat mutation. Durable manual override supersedes every automatic display mode, reset is leader/follower generation- and target-fenced, and Letters remains the default without rewriting recovery identity. Local review findings are remediated, including Bot-API-wait target-replacement regressions for leader/follower rename and reset. Remaining: disposable live acceptance for command-menu ordering, dialog, invalid input, duplicate callbacks, leader/follower rename and reset.
|
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.49.0: Unified fresh-session continuity
|
|
8
|
+
|
|
9
|
+
- `/new`: Classic and Threaded Mode now share one confirmed fresh-session flow. The settled callback deletes its dialog and publishes an expiring exact-target intent; a same- or cross-process successor preserves the classic chat or re-keys the Thread/slot/name, atomically claims once, then sends one terminal result. Identity mismatches fail closed.
|
|
10
|
+
- `Command menus`: Both Telegram command menus now keep the primary sequence `/start`, `/compact`, `/new`, `/continue`, `/next`; the niche `/name` command remains available but moves out of menus into the Thread display Settings guidance.
|
|
11
|
+
- `Classic Settings`: Hides both Thread display and Thread cleanup controls when Threaded Mode is unavailable, rather than exposing only half of the Thread-specific surface.
|
|
12
|
+
- `Thread display`: A current Thread with a manual `/name` override now appears as `custom` on the Settings row and detail heading; choosing any automatic mode clears the current Thread's override, reapplies that projection, and selects it again. Successful interactive rename/reset now settles its source before asynchronously publishing the result, so immediate `/new` cannot replay the consumed name after replacement; cancellation reports only its completed result.
|
|
13
|
+
|
|
14
|
+
## 0.48.3: Assistant publication identity hotfix
|
|
15
|
+
|
|
16
|
+
- `Thread restore display identity`: Restoring or reclaiming a Thread now renames the destination from its current Workspace display title when available, rather than leaking the internal baked slot name such as `Moss` into directory-title mode.
|
|
17
|
+
- `Assistant publication identity`: An empty terminal assistant message may recover an earlier preserved answer, but it no longer republishes that text when the same Telegram intermediate output was already admitted; the pre-send turn fence also prevents lost-acknowledgement retries while allowing later turns.
|
|
18
|
+
|
|
7
19
|
## 0.48.2: Follower promotion recovery
|
|
8
20
|
|
|
9
21
|
- `Follower promotion recovery`: A failed follower-to-leader promotion, including unavailable Thread slot authority, is now contained as a retryable bus recovery event instead of escaping a detached heartbeat recovery promise and terminating Pi.
|
|
@@ -164,6 +164,7 @@ Use these in the bot DM.
|
|
|
164
164
|
| --- | --- |
|
|
165
165
|
| `/start` | Pair when needed and open the main operator menu |
|
|
166
166
|
| `/name [Name]` | Set a manual Thread title immediately, or open rename/reset controls when Name is omitted |
|
|
167
|
+
| `/new` | Start a new Pi session in the current classic chat or Thread after confirming the bridge is idle |
|
|
167
168
|
| `/compact` | Confirm and run session compaction when safe |
|
|
168
169
|
| `/next` | Dispatch the next queued turn, aborting first if needed |
|
|
169
170
|
| `/continue` | Enqueue a priority continuation prompt |
|
|
@@ -201,7 +201,9 @@ export interface TelegramActivityPublicationRuntime {
|
|
|
201
201
|
export declare function createTelegramActivityPublicationRuntime(): TelegramActivityPublicationRuntime;
|
|
202
202
|
export interface TelegramAssistantOutputRuntime {
|
|
203
203
|
start: () => void;
|
|
204
|
+
beginTurn: () => void;
|
|
204
205
|
accept: (event: TelegramAssistantSegmentEvent) => void;
|
|
206
|
+
hasAdmittedTelegramIntermediate: (text: string) => boolean;
|
|
205
207
|
waitForIdle: () => Promise<void>;
|
|
206
208
|
stop: () => void;
|
|
207
209
|
}
|
|
@@ -520,6 +520,7 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
520
520
|
let running = false;
|
|
521
521
|
let tail = Promise.resolve();
|
|
522
522
|
const admitted = new Set();
|
|
523
|
+
const admittedTelegramIntermediateText = new Set();
|
|
523
524
|
const isEligibleEvent = (event) => (event.source === "telegram" && event.placement === "intermediate") ||
|
|
524
525
|
event.source === "local" ||
|
|
525
526
|
event.source === "autonomous" ||
|
|
@@ -529,8 +530,12 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
529
530
|
generation += 1;
|
|
530
531
|
running = true;
|
|
531
532
|
admitted.clear();
|
|
533
|
+
admittedTelegramIntermediateText.clear();
|
|
532
534
|
tail = Promise.resolve();
|
|
533
535
|
},
|
|
536
|
+
beginTurn() {
|
|
537
|
+
admittedTelegramIntermediateText.clear();
|
|
538
|
+
},
|
|
534
539
|
accept(event) {
|
|
535
540
|
if (!running || !isEligibleEvent(event) || !event.text.trim())
|
|
536
541
|
return;
|
|
@@ -538,6 +543,9 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
538
543
|
if (admitted.has(key))
|
|
539
544
|
return;
|
|
540
545
|
admitted.add(key);
|
|
546
|
+
if (event.source === "telegram" && event.placement === "intermediate") {
|
|
547
|
+
admittedTelegramIntermediateText.add(event.text.trim());
|
|
548
|
+
}
|
|
541
549
|
const admittedGeneration = generation;
|
|
542
550
|
const admittedAuthority = deps.captureAuthority?.();
|
|
543
551
|
const preparation = deps.prepareSend?.(event);
|
|
@@ -562,6 +570,9 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
562
570
|
}
|
|
563
571
|
}).finally(() => preparation?.settle());
|
|
564
572
|
},
|
|
573
|
+
hasAdmittedTelegramIntermediate(text) {
|
|
574
|
+
return admittedTelegramIntermediateText.has(text.trim());
|
|
575
|
+
},
|
|
565
576
|
waitForIdle() {
|
|
566
577
|
return tail;
|
|
567
578
|
},
|
|
@@ -569,6 +580,7 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
569
580
|
generation += 1;
|
|
570
581
|
running = false;
|
|
571
582
|
admitted.clear();
|
|
583
|
+
admittedTelegramIntermediateText.clear();
|
|
572
584
|
},
|
|
573
585
|
};
|
|
574
586
|
}
|
|
@@ -202,7 +202,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
202
202
|
publicationRuntime: TelegramBridgePublicationRuntime;
|
|
203
203
|
activityRuntime: Activity.TelegramActivityRuntime;
|
|
204
204
|
activityVerbosityRuntime?: ActivityVerbosity.TelegramActivityVerbosityRuntime;
|
|
205
|
-
assistantOutputRuntime: Pick<Activity.TelegramAssistantOutputRuntime, "start" | "waitForIdle" | "stop">;
|
|
205
|
+
assistantOutputRuntime: Pick<Activity.TelegramAssistantOutputRuntime, "start" | "beginTurn" | "hasAdmittedTelegramIntermediate" | "waitForIdle" | "stop">;
|
|
206
206
|
sessionLifecycleRuntime: Pick<Lifecycle.TelegramLifecycleRegistrationDeps, "onSessionStart" | "onSessionShutdown" | "onModelSelect">;
|
|
207
207
|
configStore: Pick<Config.TelegramConfigStore, "get" | "getOutboundHandlers" | "hasBotToken" | "load">;
|
|
208
208
|
abort: Runtime.TelegramRuntimeAbortPort;
|
|
@@ -216,6 +216,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
216
216
|
deferredQueueDispatchRuntime: Queue.TelegramDeferredQueueDispatchRuntime<Pi.ExtensionContext>;
|
|
217
217
|
modelContextAvailabilityRuntime: Prompts.TelegramModelContextAvailabilityRuntime;
|
|
218
218
|
disconnectOnQuit?: () => Promise<unknown>;
|
|
219
|
+
onSessionStarted?: (event: Pi.SessionStartEvent, ctx: Pi.ExtensionContext) => void;
|
|
219
220
|
shutdownGenerativeAppLiveSurfaces?: () => void;
|
|
220
221
|
resolveAutomaticThreadCleanupEnabled?: () => boolean | Promise<boolean>;
|
|
221
222
|
buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
|
|
@@ -246,5 +247,5 @@ interface TelegramLifecycleBindingDeps {
|
|
|
246
247
|
updateStatus: TelegramBridgeStatusUpdater;
|
|
247
248
|
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
248
249
|
}
|
|
249
|
-
export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, isTurnTransportActive, updateStatus, recordRuntimeEvent, }: TelegramLifecycleBindingDeps): void;
|
|
250
|
+
export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, isTurnTransportActive, updateStatus, recordRuntimeEvent, }: TelegramLifecycleBindingDeps): void;
|
|
250
251
|
export {};
|
|
@@ -499,7 +499,7 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
|
|
|
499
499
|
},
|
|
500
500
|
});
|
|
501
501
|
}
|
|
502
|
-
export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, isTurnTransportActive, updateStatus, recordRuntimeEvent, }) {
|
|
502
|
+
export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, isTurnTransportActive, updateStatus, recordRuntimeEvent, }) {
|
|
503
503
|
const agentEndResetter = Runtime.createTelegramAgentEndResetter({
|
|
504
504
|
abort,
|
|
505
505
|
typing,
|
|
@@ -615,7 +615,10 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
615
615
|
clearDispatchPending: lifecycle.clearDispatchPending,
|
|
616
616
|
setFoldQueuedPromptsIntoHistory: lifecycle.setFoldQueuedPromptsIntoHistory,
|
|
617
617
|
setActiveTurn: activeTurnRuntime.set,
|
|
618
|
-
onPromptHandedOff,
|
|
618
|
+
onPromptHandedOff: (turn, ctx) => {
|
|
619
|
+
assistantOutputRuntime.beginTurn();
|
|
620
|
+
onPromptHandedOff?.(turn, ctx);
|
|
621
|
+
},
|
|
619
622
|
createPreviewState: previewRuntime.resetState,
|
|
620
623
|
startTypingLoop: (ctx) => {
|
|
621
624
|
const turn = activeTurnRuntime.get();
|
|
@@ -627,6 +630,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
627
630
|
getActiveTurn: activeTurnRuntime.get,
|
|
628
631
|
loadConfig: configStore.load,
|
|
629
632
|
extractAssistant: Replies.extractRunAssistantMessage,
|
|
633
|
+
isRecoveredAssistantAlreadyPublished: (assistant) => !!assistant.text && assistantOutputRuntime.hasAdmittedTelegramIntermediate(assistant.text),
|
|
630
634
|
getFoldQueuedPromptsIntoHistory: lifecycle.shouldFoldQueuedPromptsIntoHistory,
|
|
631
635
|
resetRuntimeState: agentEndResetter,
|
|
632
636
|
isSessionActive: isSessionContextActive,
|
|
@@ -751,6 +755,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
751
755
|
activityVerbosityRuntime?.reset();
|
|
752
756
|
modelContextAvailabilityRuntime.reconcile();
|
|
753
757
|
await sessionLifecycleRuntime.onSessionStart(event, ctx);
|
|
758
|
+
onSessionStarted?.(event, ctx);
|
|
754
759
|
},
|
|
755
760
|
async onSessionShutdown(event, ctx) {
|
|
756
761
|
if (!isSessionContextActive(ctx))
|