@llblab/pi-kit 0.13.0 → 0.14.1
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 +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -7
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +8 -9
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +30 -0
- package/node_modules/@llblab/pi-state-flow/README.md +1 -3
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/index.js +20 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +78 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +110 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +334 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +49 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +67 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +53 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +23 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +109 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.d.ts +111 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +189 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +125 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +102 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +507 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +8 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +27 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +22 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1263 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +72 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +565 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +22 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +12 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +109 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +25 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +24 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +36 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +98 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +15 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +42 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +13 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +133 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +7 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +59 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +69 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +335 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +27 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +35 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +8 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +27 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +36 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +38 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +142 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +529 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +44 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +25 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +131 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +255 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +55 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +31 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +38 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +46 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +217 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +100 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +234 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +203 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +25 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +204 -0
- package/node_modules/@llblab/pi-state-flow/dist/package.json +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/pi-state-flow/index.js +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +138 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +11 -5
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +5 -1
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +3 -3
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +6 -4
- package/node_modules/@llblab/pi-state-flow/index.ts +1 -0
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +17 -1
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -1
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +88 -96
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +64 -12
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +32 -188
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +84 -48
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +40 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +7 -30
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +48 -50
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +41 -97
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +17 -12
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +13 -9
- package/node_modules/@llblab/pi-state-flow/package.json +23 -6
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +3 -1
- package/package.json +4 -4
|
@@ -3,7 +3,7 @@ import { lstatSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { classifyScopeStream, parseScopeProvenance, parseScopeStream, sessionRuntimePaths, temporalScopePaths, type SessionAddress } from "./durable.ts";
|
|
5
5
|
import { parseArtifactProvenanceRegistry, pruneArtifactProvenance, type ArtifactProvenance, type ArtifactProvenanceRegistry } from "./artifact.ts";
|
|
6
|
-
import { adoptFileStateToGit, initializeGitRepository, isLocalGitRepository, captureTemporalGitBase,
|
|
6
|
+
import { adoptFileStateToGit, initializeGitRepository, isLocalGitRepository, captureTemporalGitBase, loadTemporalRevision, migrateLegacyStorageToGit, publishTemporalStateToGit, type TemporalGitBase } from "./git.ts";
|
|
7
7
|
import { captureTemporalFileBase, detectGitCapability, initializeFileStore, loadTemporalFileRevision, migrateLegacyStorageToFiles, publishTemporalStateToFiles, type TemporalFileBase } from "./storage.ts";
|
|
8
8
|
import { type AcceptedTransition, type RecentTransitionWindow } from "./history.ts";
|
|
9
9
|
import { hashJson, sameJson } from "./json.ts";
|
|
@@ -73,26 +73,18 @@ export function inspectRuntimeRevision(cwd: string, sessionId: string, root: str
|
|
|
73
73
|
if (!loaded.scopes.global || !loaded.scopes.cwd || !loaded.scopes.session) throw new Error("Incomplete temporal scope cohort");
|
|
74
74
|
const view = { lineage: resolved.lineage, scopes: { global: loaded.scopes.global, cwd: loaded.scopes.cwd, session: loaded.scopes.session } };
|
|
75
75
|
validateTemporalState(view);
|
|
76
|
-
return { runtime: loaded.runtime, resolved, view, provenance: loaded.provenance
|
|
76
|
+
return { runtime: loaded.runtime, resolved, view, provenance: loaded.provenance };
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/**
|
|
80
|
-
export function inspectSnapshotRevision(cwd: string, sessionId: string, root: string, revision: string,
|
|
79
|
+
/** Validate one supported immutable target before any live migration/publication. */
|
|
80
|
+
export function inspectSnapshotRevision(cwd: string, sessionId: string, root: string, revision: string, _legacySnapshot?: Snapshot, sessionKey = sessionId) {
|
|
81
81
|
if (isFileRevision(revision)) {
|
|
82
82
|
const file = loadTemporalFileRevision(cwd, sessionId, root, revision, sessionKey);
|
|
83
83
|
return { snapshot: resolveFileSessionRuntime(file.runtime, revision), file };
|
|
84
84
|
}
|
|
85
85
|
if (detectGitCapability() === "files") throw new RevisionUnavailableError("Git is unavailable; cannot restore a Git-linked revision");
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return { snapshot: temporal.resolved.snapshot, temporal };
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (!legacySnapshot || !(error instanceof Error) || !error.message.includes("Historical legacy storage requires explicit migration")) throw error;
|
|
91
|
-
const historical = loadLegacyStatesAtRevision(cwd, sessionId, root, revision, sessionKey);
|
|
92
|
-
const snapshot = structuredClone(legacySnapshot);
|
|
93
|
-
if (historical.session !== undefined) snapshot.legacySession = { state: historical.session };
|
|
94
|
-
return { snapshot, shared: { global: historical.global ?? emptyState(), cwd: historical.cwd ?? emptyState() } };
|
|
95
|
-
}
|
|
86
|
+
const temporal = inspectRuntimeRevision(cwd, sessionId, root, revision, sessionKey);
|
|
87
|
+
return { snapshot: temporal.resolved.snapshot, temporal };
|
|
96
88
|
}
|
|
97
89
|
|
|
98
90
|
/** Cached branch-selected temporal state and publication basis; excludes Pi event policy. */
|
|
@@ -130,6 +122,40 @@ export class TemporalRuntime {
|
|
|
130
122
|
this.backend = backend;
|
|
131
123
|
}
|
|
132
124
|
|
|
125
|
+
/** Explicit start migrates a predecessor envelope even when passive restore already populated the cache. */
|
|
126
|
+
migrateLegacyStorage(): void {
|
|
127
|
+
if (!this.view || !this.backend || !hasLegacyStateSources(this.cwd, this.sessionId, this.root, this.sessionKey)) return undefined;
|
|
128
|
+
const publication = this.backend === "git"
|
|
129
|
+
? migrateLegacyStorageToGit(this.cwd, this.sessionId, this.root, this.sessionKey)
|
|
130
|
+
: (migrateLegacyStorageToFiles(this.cwd, this.sessionId, this.root, this.sessionKey), undefined);
|
|
131
|
+
const base = this.backend === "files"
|
|
132
|
+
? captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey)
|
|
133
|
+
: captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
134
|
+
const files = new Map(base.files.map((file) => [file.path, file.content]));
|
|
135
|
+
const scopes = Object.fromEntries(SCOPES.map((scope) => {
|
|
136
|
+
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
137
|
+
const stream = parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope,
|
|
138
|
+
scope === "cwd" ? this.cwd : undefined, files.get(paths.meta));
|
|
139
|
+
if (!stream) throw new Error(`State Flow ${scopeLabel(scope)} scope disappeared during migration`);
|
|
140
|
+
return [scope, stream];
|
|
141
|
+
})) as TemporalState["scopes"];
|
|
142
|
+
const migrated = { lineage: this.view.lineage, scopes };
|
|
143
|
+
validateTemporalState(migrated);
|
|
144
|
+
if (!sameJson(this.states(), {
|
|
145
|
+
global: readTemporalState(migrated, 0, "global"),
|
|
146
|
+
cwd: readTemporalState(migrated, 0, "cwd"),
|
|
147
|
+
session: readTemporalState(migrated, 0, "session"),
|
|
148
|
+
})) throw new Error("State Flow migration changed materialized semantic state");
|
|
149
|
+
this.view = migrated;
|
|
150
|
+
this.base = base;
|
|
151
|
+
this.provenanceByScope = {
|
|
152
|
+
global: parseScopeProvenance(files.get(temporalScopePaths(this.cwd, this.sessionId, "global", this.root, this.sessionKey).meta), "State Flow global metadata"),
|
|
153
|
+
cwd: parseScopeProvenance(files.get(temporalScopePaths(this.cwd, this.sessionId, "cwd", this.root, this.sessionKey).meta), "State Flow CWD metadata"),
|
|
154
|
+
session: this.provenanceByScope.session,
|
|
155
|
+
};
|
|
156
|
+
if (publication?.commit) this.semanticRevision = publication.commit;
|
|
157
|
+
}
|
|
158
|
+
|
|
133
159
|
/** Explicit start can upgrade a proven file cohort; failed adoption leaves the cache in file mode. */
|
|
134
160
|
promote(snapshot: Snapshot): RuntimePublication | undefined {
|
|
135
161
|
if (this.backend !== "files" || !this.view || detectGitCapability() === "files") return undefined;
|
|
@@ -244,45 +270,16 @@ export class TemporalRuntime {
|
|
|
244
270
|
this.absentSharedScopes.clear();
|
|
245
271
|
return inspected.snapshot;
|
|
246
272
|
}
|
|
247
|
-
if (!inspected.temporal) {
|
|
248
|
-
const migrated = inspected.snapshot;
|
|
249
|
-
const publication = this.initialize(migrated, true, inspected.shared);
|
|
250
|
-
if (publication?.commit) migrated.meta.durableBase = publication.commit;
|
|
251
|
-
if (publication?.push?.status === "pending") migrated.meta.pendingPublication = { commit: publication.push.commit, error: publication.push.error ?? "Unconfirmed publication" };
|
|
252
|
-
else delete migrated.meta.pendingPublication;
|
|
253
|
-
delete migrated.legacySession;
|
|
254
|
-
return migrated;
|
|
255
|
-
}
|
|
256
273
|
const loaded = inspected.temporal;
|
|
257
274
|
const { resolved, view } = loaded;
|
|
258
275
|
let base = captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
259
276
|
const reference = loaded.runtime.document.meta.temporalRevision;
|
|
260
277
|
let semanticRevision = reference === undefined || reference === "self" ? loaded.runtime.revision : reference;
|
|
261
278
|
let publicationTarget = resolved.publicationTarget;
|
|
262
|
-
let migrationPush;
|
|
263
|
-
if (loaded.legacyLayout && base.head) {
|
|
264
|
-
const liveRevision = base.head;
|
|
265
|
-
const migration = migrateHashedLayoutAtHead(this.cwd, this.sessionId, this.root, liveRevision, this.sessionKey);
|
|
266
|
-
if (migration?.commit) {
|
|
267
|
-
base = migration.base;
|
|
268
|
-
if (liveRevision === revision) {
|
|
269
|
-
semanticRevision = migration.commit;
|
|
270
|
-
publicationTarget = migration.commit;
|
|
271
|
-
migrationPush = migration.push;
|
|
272
|
-
}
|
|
273
|
-
} else {
|
|
274
|
-
migrateHashedCwdAtHead(this.cwd, this.root);
|
|
275
|
-
base = captureTemporalGitBase(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
279
|
resolved.snapshot.meta.durableBase = publicationTarget;
|
|
279
280
|
const savedRuntime = hashJson(createSessionRuntime(resolved.snapshot, this.cwd, this.sessionId, view.lineage));
|
|
280
281
|
resolved.snapshot.meta.pendingPublication = { commit: publicationTarget, error: "Durable publication intent is unconfirmed" };
|
|
281
|
-
|
|
282
|
-
resolved.snapshot.meta.pendingPublication = { commit: migrationPush.commit, error: migrationPush.error ?? "Unconfirmed publication" };
|
|
283
|
-
} else if (migrationPush !== undefined) {
|
|
284
|
-
delete resolved.snapshot.meta.pendingPublication;
|
|
285
|
-
} else {
|
|
282
|
+
{
|
|
286
283
|
try {
|
|
287
284
|
if (isLocalGitRepository(this.root)) delete resolved.snapshot.meta.pendingPublication;
|
|
288
285
|
} catch {
|
|
@@ -314,7 +311,6 @@ export class TemporalRuntime {
|
|
|
314
311
|
if (!allowCreateCwd && !hasCwd) return undefined;
|
|
315
312
|
const backend = copy?.backend ?? this.backend ?? (detectGitCapability() === "git" && lstatSync(join(this.root, ".git"), { throwIfNoEntry: false }) ? "git" : "files");
|
|
316
313
|
if (!copy && backend === "git") {
|
|
317
|
-
if (!hasCwd) migrateHashedCwdAtHead(this.cwd, this.root);
|
|
318
314
|
if (hasLegacyStateSources(this.cwd, this.sessionId, this.root, this.sessionKey)) {
|
|
319
315
|
migrateLegacyStorageToGit(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
320
316
|
}
|
|
@@ -342,17 +338,18 @@ export class TemporalRuntime {
|
|
|
342
338
|
})) throw new Error("Legacy State Flow storage changed during initialization; retry migration from a fresh basis");
|
|
343
339
|
const streams = Object.fromEntries(SCOPES.map((scope) => {
|
|
344
340
|
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
345
|
-
return [scope, parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope,
|
|
341
|
+
return [scope, parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope,
|
|
342
|
+
scope === "cwd" ? this.cwd : undefined, files.get(paths.meta))];
|
|
346
343
|
})) as Record<StateScope, TemporalState["scopes"][StateScope] | undefined>;
|
|
347
344
|
if (!streams.cwd && !allowCreateCwd) return undefined;
|
|
348
345
|
if (copy && !streams.global) throw new Error("State Flow fork requires existing shared scope storage");
|
|
349
346
|
const paths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
350
347
|
const existingRuntime = parseSessionRuntime(files.get(paths.config), files.get(paths.meta), this.cwd, this.sessionId);
|
|
351
|
-
if (existingRuntime && !
|
|
348
|
+
if (existingRuntime && !newSessionOrigin) throw new Error("Existing session runtime requires a branch revision pointer");
|
|
352
349
|
// Explicit start before any branch runtime is a new origin, never inheritance of a later session layer.
|
|
353
|
-
if (
|
|
350
|
+
if (newSessionOrigin) streams.session = undefined;
|
|
354
351
|
if (copy) streams.session = copy.stream;
|
|
355
|
-
const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session:
|
|
352
|
+
const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, randomUUID());
|
|
356
353
|
const candidate = new TemporalRuntime(this.cwd, this.session, this.root);
|
|
357
354
|
candidate.backend = backend;
|
|
358
355
|
candidate.base = base;
|
|
@@ -431,7 +428,8 @@ export class TemporalRuntime {
|
|
|
431
428
|
const reconciliation = `${head ?? "files"}:reconcile:${randomUUID()}`;
|
|
432
429
|
for (const scope of SHARED_SCOPES) {
|
|
433
430
|
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
434
|
-
const presence = classifyScopeStream(liveFiles.get(paths.checkpoint)?.content, liveFiles.get(paths.patches)?.content, scope,
|
|
431
|
+
const presence = classifyScopeStream(liveFiles.get(paths.checkpoint)?.content, liveFiles.get(paths.patches)?.content, scope,
|
|
432
|
+
scope === "cwd" ? this.cwd : undefined, liveFiles.get(paths.meta)?.content);
|
|
435
433
|
if (presence.kind === "absent") {
|
|
436
434
|
absentScopes.push(scope);
|
|
437
435
|
provenance[scope] = {};
|
|
@@ -2,7 +2,7 @@ import { resolve } from "node:path";
|
|
|
2
2
|
import { parseArtifactProvenanceRegistry, type ArtifactProvenanceRegistry } from "./artifact.ts";
|
|
3
3
|
import { parseRemotePublicationPolicyDocument, serializeRemotePublicationPolicyDocument, type RemotePublicationPolicyDocument } from "./publication.ts";
|
|
4
4
|
import { applyPatch, canonicalJson, containsNull, isJsonValue, isObject, type JsonObject } from "./json.ts";
|
|
5
|
-
import { validateTemporalLineage, type TransitionBoundary } from "./temporal.ts";
|
|
5
|
+
import { validateTemporalLineage, type ScopeStream, type TransitionBoundary } from "./temporal.ts";
|
|
6
6
|
import { migrateLegacySkillCompilations } from "./skills.ts";
|
|
7
7
|
import { isMaterializedState, type MaterializedState } from "./state.ts";
|
|
8
8
|
const MAX_RESTORED_STEP = Number.MAX_SAFE_INTEGER - 1;
|
|
@@ -37,16 +37,10 @@ export interface SnapshotMeta {
|
|
|
37
37
|
remotePublication?: RemotePublicationPolicyDocument;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export interface LegacySessionMigration {
|
|
41
|
-
state: MaterializedState;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
40
|
/** In-memory runtime config/provenance; durable config/meta and scope files own restoration. */
|
|
45
41
|
export interface StateFlowSnapshot {
|
|
46
42
|
config: SnapshotConfig;
|
|
47
43
|
meta: SnapshotMeta;
|
|
48
|
-
/** Ephemeral one-way migration payload. persistSnapshot() never writes it to Pi checkpoints. */
|
|
49
|
-
legacySession?: LegacySessionMigration;
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
export type Snapshot = StateFlowSnapshot;
|
|
@@ -64,13 +58,15 @@ export interface SessionRuntime {
|
|
|
64
58
|
temporalRevision?: "self" | string;
|
|
65
59
|
/** Durable intent survives a crash before the push result can be observed. */
|
|
66
60
|
publication: "unconfirmed" | "files";
|
|
61
|
+
temporal?: { checkpoint: TransitionBoundary; patches: TransitionBoundary[] };
|
|
62
|
+
[key: string]: unknown;
|
|
67
63
|
};
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
export function validateSessionRuntime(value: unknown, cwd: string, sessionId: string): asserts value is SessionRuntime {
|
|
71
67
|
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
|
|
72
68
|
|| !isObject(value.config) || !isObject(value.meta)) throw new Error("Invalid State Flow session runtime envelope");
|
|
73
|
-
const { version, identity, lineage, revision, temporalRevision, publication, artifacts, ...fields } = value.meta;
|
|
69
|
+
const { version, identity, lineage, revision, temporalRevision, publication, artifacts, temporal: _temporalScope, ...fields } = value.meta;
|
|
74
70
|
if (artifacts !== undefined) parseArtifactProvenanceRegistry(artifacts, "State Flow session artifact provenance");
|
|
75
71
|
if (temporalRevision !== undefined && temporalRevision !== "self"
|
|
76
72
|
&& !isExactRevision(temporalRevision)) throw new Error("Invalid temporal revision reference");
|
|
@@ -81,13 +77,16 @@ export function validateSessionRuntime(value: unknown, cwd: string, sessionId: s
|
|
|
81
77
|
throw new Error("State Flow runtime scope identity mismatch");
|
|
82
78
|
}
|
|
83
79
|
validateTemporalLineage(lineage);
|
|
84
|
-
const
|
|
85
|
-
if (Object.keys(fields).some((key) =>
|
|
80
|
+
const known = new Set(["step", "specification", "validation", "bootstrap", "remotePublication"]);
|
|
81
|
+
if (Object.keys(fields).some((key) => ["state", "contract", "working", "response"].includes(key))) {
|
|
82
|
+
throw new Error("Semantic state does not belong in State Flow runtime metadata");
|
|
83
|
+
}
|
|
84
|
+
const runtimeFields = Object.fromEntries(Object.entries(fields).filter(([key]) => known.has(key)));
|
|
86
85
|
const { transitionWindow: _retiredWindow, ...supportedConfig } = value.config;
|
|
87
|
-
const normalized = migrateSnapshot({ config: supportedConfig, meta:
|
|
88
|
-
if (
|
|
89
|
-
if (
|
|
90
|
-
if (canonicalJson({ config: normalized.config, meta: normalized.meta }) !== canonicalJson({ config: supportedConfig, meta:
|
|
86
|
+
const normalized = migrateSnapshot({ config: supportedConfig, meta: runtimeFields });
|
|
87
|
+
if (runtimeFields.bootstrap === false) normalized.meta.bootstrap = false;
|
|
88
|
+
if (runtimeFields.step === Number.MAX_SAFE_INTEGER) normalized.meta.step = Number.MAX_SAFE_INTEGER;
|
|
89
|
+
if (canonicalJson({ config: normalized.config, meta: normalized.meta }) !== canonicalJson({ config: supportedConfig, meta: runtimeFields })) {
|
|
91
90
|
throw new Error("Invalid State Flow runtime configuration or counters");
|
|
92
91
|
}
|
|
93
92
|
}
|
|
@@ -117,13 +116,31 @@ export function createSessionRuntime(
|
|
|
117
116
|
return runtime;
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
export function serializeSessionRuntime(
|
|
119
|
+
export function serializeSessionRuntime(
|
|
120
|
+
runtime: SessionRuntime, cwd: string, sessionId: string, stream?: ScopeStream, existingSource?: string,
|
|
121
|
+
): { config: string; meta: string } {
|
|
121
122
|
validateSessionRuntime(runtime, cwd, sessionId);
|
|
122
|
-
|
|
123
|
+
let existing: Record<string, unknown> = {};
|
|
124
|
+
if (existingSource !== undefined) {
|
|
125
|
+
try { existing = JSON.parse(existingSource) as Record<string, unknown>; }
|
|
126
|
+
catch { throw new Error("State Flow session runtime contains invalid JSON"); }
|
|
127
|
+
if (!isObject(existing) || !isJsonValue(existing)) throw new Error("Invalid State Flow session runtime metadata");
|
|
128
|
+
}
|
|
129
|
+
const temporal = stream === undefined ? runtime.meta.temporal : {
|
|
130
|
+
checkpoint: structuredClone(stream.checkpoint.through),
|
|
131
|
+
patches: stream.patches.map((record) => structuredClone(record.transition)),
|
|
132
|
+
};
|
|
133
|
+
return { config: `${canonicalJson(runtime.config)}\n`, meta: `${canonicalJson({ ...existing, ...runtime.meta, ...(temporal ? { temporal } : {}) })}\n` };
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
export function parseSessionRuntime(config: string | undefined, meta: string | undefined, cwd: string, sessionId: string): SessionRuntime | undefined {
|
|
126
137
|
if (config === undefined && meta === undefined) return undefined;
|
|
138
|
+
if (config === undefined && meta !== undefined) {
|
|
139
|
+
let document: unknown;
|
|
140
|
+
try { document = JSON.parse(meta); } catch { throw new Error("State Flow session runtime contains invalid JSON"); }
|
|
141
|
+
if (isObject(document) && !Object.hasOwn(document, "identity") && !Object.hasOwn(document, "lineage")
|
|
142
|
+
&& Object.keys(document).every((key) => ["version", "artifacts", "temporal", "owner"].includes(key))) return undefined;
|
|
143
|
+
}
|
|
127
144
|
if (config === undefined || meta === undefined) throw new Error("Incomplete State Flow config/meta pair");
|
|
128
145
|
let runtime: unknown;
|
|
129
146
|
try {
|
|
@@ -132,7 +149,8 @@ export function parseSessionRuntime(config: string | undefined, meta: string | u
|
|
|
132
149
|
throw new Error("State Flow session runtime contains invalid JSON");
|
|
133
150
|
}
|
|
134
151
|
validateSessionRuntime(runtime, cwd, sessionId);
|
|
135
|
-
|
|
152
|
+
const { temporal: _temporal, ...runtimeMeta } = runtime.meta;
|
|
153
|
+
return { config: { enabled: runtime.config.enabled }, meta: runtimeMeta };
|
|
136
154
|
}
|
|
137
155
|
|
|
138
156
|
export function resolveSessionRuntime(runtime: SessionRuntime, revision: string): { snapshot: Snapshot; lineage: TransitionBoundary[]; publicationTarget: string; artifacts: ArtifactProvenanceRegistry } {
|
|
@@ -226,16 +244,8 @@ function restoredMeta(value: unknown, legacy: JsonObject = {}): SnapshotMeta {
|
|
|
226
244
|
};
|
|
227
245
|
}
|
|
228
246
|
|
|
229
|
-
function envelope(
|
|
230
|
-
|
|
231
|
-
meta: SnapshotMeta,
|
|
232
|
-
legacySession?: LegacySessionMigration,
|
|
233
|
-
): Snapshot {
|
|
234
|
-
return {
|
|
235
|
-
config: { enabled },
|
|
236
|
-
meta,
|
|
237
|
-
...(legacySession === undefined ? {} : { legacySession }),
|
|
238
|
-
};
|
|
247
|
+
function envelope(enabled: boolean, meta: SnapshotMeta): Snapshot {
|
|
248
|
+
return { config: { enabled }, meta };
|
|
239
249
|
}
|
|
240
250
|
|
|
241
251
|
export function emptySnapshot(enabled = false): Snapshot {
|
|
@@ -262,13 +272,12 @@ export function persistableSnapshot(snapshot: Snapshot): PiCheckpoint {
|
|
|
262
272
|
if (!isDurableRevision(snapshot.meta.durableBase)) throw new Error("Checkpoint requires an exact Git revision or file reference");
|
|
263
273
|
return { revision: snapshot.meta.durableBase };
|
|
264
274
|
}
|
|
265
|
-
if (snapshot.legacySession) throw new Error("Legacy semantic state requires migration before checkpoint publication");
|
|
266
275
|
if (snapshot.config.enabled) throw new Error("Enabled checkpoint requires a durable runtime revision");
|
|
267
276
|
return { disabled: true };
|
|
268
277
|
}
|
|
269
278
|
|
|
270
|
-
/**
|
|
271
|
-
export function parsePiCheckpoint(value: unknown): PiCheckpoint
|
|
279
|
+
/** Pi checkpoints admit only strict revision pointers or the disabled marker. */
|
|
280
|
+
export function parsePiCheckpoint(value: unknown): PiCheckpoint {
|
|
272
281
|
if (!isObject(value)) throw new Error("Invalid State Flow checkpoint");
|
|
273
282
|
if (Object.hasOwn(value, "revision") || Object.hasOwn(value, "disabled")) {
|
|
274
283
|
if (Object.keys(value).length === 1) {
|
|
@@ -277,13 +286,7 @@ export function parsePiCheckpoint(value: unknown): PiCheckpoint | Snapshot {
|
|
|
277
286
|
}
|
|
278
287
|
throw new Error("Invalid State Flow checkpoint pointer or disabled marker");
|
|
279
288
|
}
|
|
280
|
-
|
|
281
|
-
const config = Object.hasOwn(value, "config") ? value.config : value;
|
|
282
|
-
if (!isObject(config) || typeof config.enabled !== "boolean") throw new Error("Invalid legacy State Flow configuration");
|
|
283
|
-
if (Object.hasOwn(value, "meta") && !isObject(value.meta)) throw new Error("Invalid legacy State Flow metadata");
|
|
284
|
-
const meta = isObject(value.meta) ? value.meta : value;
|
|
285
|
-
if (Object.hasOwn(meta, "durableBase") && !isDurableRevision(meta.durableBase)) throw new Error("Invalid legacy State Flow revision");
|
|
286
|
-
return migrateSnapshot(value);
|
|
289
|
+
throw new Error("Unrecognized State Flow checkpoint");
|
|
287
290
|
}
|
|
288
291
|
|
|
289
292
|
export function migrationFailure(data: JsonObject, error: string): Snapshot {
|
|
@@ -296,71 +299,12 @@ export function migrationFailure(data: JsonObject, error: string): Snapshot {
|
|
|
296
299
|
return envelope(false, meta);
|
|
297
300
|
}
|
|
298
301
|
|
|
299
|
-
function migratedSnapshot(
|
|
300
|
-
enabled: boolean,
|
|
301
|
-
meta: SnapshotMeta,
|
|
302
|
-
state: MaterializedState,
|
|
303
|
-
): Snapshot {
|
|
304
|
-
return envelope(enabled, meta, {
|
|
305
|
-
state: migrateLegacySkillCompilations(state),
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
302
|
export function migrateSnapshot(value: unknown): Snapshot {
|
|
310
303
|
if (!isObject(value)) return emptySnapshot();
|
|
311
304
|
const isEnvelope = Object.hasOwn(value, "config") || Object.hasOwn(value, "meta");
|
|
312
305
|
const config = isEnvelope && isObject(value.config) ? value.config : value;
|
|
313
306
|
const meta = restoredMeta(isEnvelope ? value.meta : undefined, value);
|
|
314
307
|
const enabled = config.enabled === true;
|
|
315
|
-
if (
|
|
316
|
-
if (!isJsonValue(value.state)) return migrationFailure(value, "Restored state contains non-JSON data");
|
|
317
|
-
if (containsNull(value.state)) return migrationFailure(value, "Restored state contains null data");
|
|
318
|
-
return migratedSnapshot(enabled, meta, structuredClone(value.state));
|
|
319
|
-
}
|
|
320
|
-
if (isLegacyThreePartState(value.state)) {
|
|
321
|
-
if (!isJsonValue(value.state)) return migrationFailure(value, "Restored state contains non-JSON data");
|
|
322
|
-
if (containsNull(value.state)) return migrationFailure(value, "Restored state contains null data");
|
|
323
|
-
return migratedSnapshot(enabled, meta, {
|
|
324
|
-
artifacts: {},
|
|
325
|
-
...structuredClone(value.state),
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
if (isLegacyTwoPartState(value.state)) {
|
|
329
|
-
if (!isJsonValue(value.state)) return migrationFailure(value, "Restored state contains non-JSON data");
|
|
330
|
-
if (containsNull(value.state)) return migrationFailure(value, "Restored state contains null data");
|
|
331
|
-
return migratedSnapshot(enabled, meta, {
|
|
332
|
-
artifacts: {},
|
|
333
|
-
contract: structuredClone(value.state.contract),
|
|
334
|
-
working: structuredClone(value.state.working),
|
|
335
|
-
response: "",
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
if (Object.hasOwn(value, "state")) {
|
|
339
|
-
if (isObject(value.state)
|
|
340
|
-
&& (Object.hasOwn(value.state, "response")
|
|
341
|
-
|| Object.hasOwn(value.state, "artifacts")
|
|
342
|
-
|| (Object.hasOwn(value.state, "contract") && Object.hasOwn(value.state, "working")))) {
|
|
343
|
-
return migrationFailure(value, "Restored state has an invalid materialized-state schema");
|
|
344
|
-
}
|
|
345
|
-
if (value.state !== undefined && !isObject(value.state)) {
|
|
346
|
-
return migrationFailure(value, "Restored state has an invalid materialized-state schema");
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (Object.hasOwn(value, "stateBasis") || Object.hasOwn(value, "previousStatePatch")) {
|
|
350
|
-
const legacyBasis = isObject(value.stateBasis)
|
|
351
|
-
? value.stateBasis
|
|
352
|
-
: isObject(value.state)
|
|
353
|
-
? value.state
|
|
354
|
-
: {};
|
|
355
|
-
const legacyPatch = isObject(value.previousStatePatch) ? value.previousStatePatch : undefined;
|
|
356
|
-
if (!isJsonValue(legacyBasis) || (legacyPatch !== undefined && !isJsonValue(legacyPatch))) {
|
|
357
|
-
return migrationFailure(value, "Legacy state contains non-JSON data");
|
|
358
|
-
}
|
|
359
|
-
const legacyState = legacyPatch === undefined ? legacyBasis : applyPatch(legacyBasis, legacyPatch);
|
|
360
|
-
if (containsNull(legacyState)) return migrationFailure(value, "Legacy state contains null data");
|
|
361
|
-
return migratedSnapshot(enabled, meta, {
|
|
362
|
-
artifacts: {}, contract: {}, working: structuredClone(legacyState), response: "",
|
|
363
|
-
});
|
|
364
|
-
}
|
|
308
|
+
if (Object.hasOwn(value, "state") || Object.hasOwn(value, "stateBasis") || Object.hasOwn(value, "previousStatePatch")) return envelope(false, meta);
|
|
365
309
|
return envelope(enabled, meta);
|
|
366
310
|
}
|
|
@@ -6,7 +6,7 @@ import { closeSync, lstatSync, mkdirSync, openSync, rmSync, writeFileSync } from
|
|
|
6
6
|
import { dirname, relative, resolve } from "node:path";
|
|
7
7
|
import {
|
|
8
8
|
assertOwnedFileUpdates, captureTemporalFileBases, parseScopeProvenance, parseScopeStream, restoreDurableFileBases,
|
|
9
|
-
|
|
9
|
+
serializeScopeMetadata, sessionRuntimePaths, temporalScopePaths, temporalStateFileUpdates, writeOwnedFileUpdates,
|
|
10
10
|
type DurableFileBase, type OwnedFileUpdate,
|
|
11
11
|
} from "./durable.ts";
|
|
12
12
|
import { parseArtifactProvenanceRegistry, type ArtifactProvenanceRegistry } from "./artifact.ts";
|
|
@@ -84,22 +84,23 @@ export function planTemporalPublication(
|
|
|
84
84
|
for (const scope of SCOPES) {
|
|
85
85
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
86
86
|
if (files.get(resolve(paths.directory, "state.json"))!.identity !== "missing") throw new Error("Legacy State Flow storage requires explicit migration");
|
|
87
|
-
const previous = parseScopeStream(files.get(paths.checkpoint)!.content, files.get(paths.patches)!.content, scope,
|
|
87
|
+
const previous = parseScopeStream(files.get(paths.checkpoint)!.content, files.get(paths.patches)!.content, scope,
|
|
88
|
+
scope === "cwd" ? cwd : undefined, files.get(paths.meta)!.content);
|
|
88
89
|
if (runtimeOnly || (previous !== undefined && sameJson(previous, view.scopes[scope]))) continue;
|
|
89
90
|
if (!scopes.includes(scope)) throw new Error(`Temporal scope update omitted a changed stream: ${scope}`);
|
|
90
91
|
changedScopes.push(scope);
|
|
91
92
|
}
|
|
92
93
|
const provenanceUpdates: OwnedFileUpdate[] = [];
|
|
93
|
-
// Shared
|
|
94
|
-
if (!runtimeOnly
|
|
94
|
+
// Shared metadata owns provenance, temporal boundaries, and CWD identity beside semantic files.
|
|
95
|
+
if (!runtimeOnly) {
|
|
95
96
|
for (const scope of ["global", "cwd"] as const) {
|
|
96
97
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
97
|
-
const registry = provenance[scope];
|
|
98
|
+
const registry = provenance?.[scope] ?? parseScopeProvenance(files.get(paths.meta)!.content, paths.meta);
|
|
98
99
|
const currentFile = files.get(paths.meta)!;
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
100
|
+
if (!changedScopes.includes(scope)
|
|
101
|
+
&& (provenance === undefined || sameJson(parseScopeProvenance(currentFile.content, paths.meta), registry))) continue;
|
|
102
|
+
const content = serializeScopeMetadata(registry, view.scopes[scope], scope, scope === "cwd" ? cwd : undefined, currentFile.content);
|
|
103
|
+
if (currentFile.content !== content) provenanceUpdates.push({ path: paths.meta, content });
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
const runtimePaths = sessionRuntimePaths(cwd, sessionId, root, sessionKey);
|
|
@@ -107,11 +108,14 @@ export function planTemporalPublication(
|
|
|
107
108
|
if (previousRuntime !== undefined && changedScopes.length > 0 && runtime === undefined) throw new Error("Temporal semantic publication requires its session runtime cohort");
|
|
108
109
|
const runtimeUpdates: OwnedFileUpdate[] = [];
|
|
109
110
|
if (runtime !== undefined) {
|
|
110
|
-
const sources = serializeSessionRuntime(runtime, cwd, sessionId);
|
|
111
|
+
const sources = serializeSessionRuntime(runtime, cwd, sessionId, runtimeOnly ? undefined : view.scopes.session, files.get(runtimePaths.meta)!.content);
|
|
111
112
|
if (!sameJson(runtime.meta.lineage, view.lineage)) throw new Error("Runtime lineage does not match the temporal cohort");
|
|
112
|
-
if (
|
|
113
|
+
if (files.get(runtimePaths.config)!.content !== sources.config || files.get(runtimePaths.meta)!.content !== sources.meta) {
|
|
113
114
|
runtimeUpdates.push({ path: runtimePaths.config, content: sources.config }, { path: runtimePaths.meta, content: sources.meta });
|
|
114
115
|
}
|
|
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 });
|
|
115
119
|
}
|
|
116
120
|
const changedPaths = new Set(changedScopes.flatMap((scope) => {
|
|
117
121
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
@@ -143,7 +147,8 @@ function decodeFileCohort(cwd: string, sessionId: string, root: string, base: Te
|
|
|
143
147
|
for (const scope of SCOPES) {
|
|
144
148
|
const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
|
|
145
149
|
if (files.get(resolve(paths.directory, "state.json")) !== undefined) throw new Error("Legacy State Flow storage requires explicit migration");
|
|
146
|
-
const stream = parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope,
|
|
150
|
+
const stream = parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope,
|
|
151
|
+
scope === "cwd" ? cwd : undefined, files.get(paths.meta));
|
|
147
152
|
if (!stream) throw new Error("Incomplete file-only temporal scope cohort");
|
|
148
153
|
scopes[scope] = stream;
|
|
149
154
|
}
|
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
// pi-telegram is absent or its registry is not ready, registration fails open and retries.
|
|
5
5
|
|
|
6
6
|
export const STATE_FLOW_TELEGRAM_ID = "@llblab/pi-state-flow";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
|
|
8
|
+
/** Resolve the package export or the compiled sibling-extension layout used in local development. */
|
|
9
|
+
export function stateFlowTelegramSectionSpecifiers(moduleUrl = import.meta.url): string[] {
|
|
10
|
+
return [
|
|
11
|
+
"@llblab/pi-telegram/sections",
|
|
12
|
+
new URL("../../../pi-telegram/dist/api/sections.js", moduleUrl).href,
|
|
13
|
+
];
|
|
14
|
+
}
|
|
11
15
|
|
|
12
16
|
export interface StateFlowTelegramSnapshot {
|
|
13
17
|
enabled: boolean;
|
|
@@ -26,7 +30,7 @@ export interface StateFlowTelegramState {
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export type StateFlowTelegramRichBlock =
|
|
29
|
-
| { type: "heading"; text: string; size:
|
|
33
|
+
| { type: "heading"; text: string; size: 3 }
|
|
30
34
|
| { type: "pre"; text: string; language?: string }
|
|
31
35
|
| { type: "details"; summary: string | { type: "bold" | "code"; text: string }; blocks: StateFlowTelegramRichBlock[]; is_open?: true };
|
|
32
36
|
|
|
@@ -177,11 +181,11 @@ function renderStateFlowTelegramField(value: unknown): string {
|
|
|
177
181
|
return rendered;
|
|
178
182
|
}
|
|
179
183
|
|
|
180
|
-
export function renderStateFlowRichState(scope: StateFlowTelegramScope, state: StateFlowTelegramState): StateFlowTelegramRichMessage {
|
|
184
|
+
export function renderStateFlowRichState(scope: StateFlowTelegramScope, step: number, state: StateFlowTelegramState): StateFlowTelegramRichMessage {
|
|
181
185
|
const fields = ["artifacts", "contract", "working", "response"] as const;
|
|
182
186
|
return {
|
|
183
187
|
blocks: [
|
|
184
|
-
{ type: "heading", text: STATE_FLOW_SCOPE_LABELS[scope]
|
|
188
|
+
{ type: "heading", text: `${STATE_FLOW_SCOPE_LABELS[scope]}: \`#${step}\``, size: 3 },
|
|
185
189
|
...fields.map((field) => ({
|
|
186
190
|
type: "details" as const,
|
|
187
191
|
summary: { type: "code" as const, text: field },
|
|
@@ -215,7 +219,7 @@ function buildStateFlowTelegramSection(port: StateFlowTelegramPort) {
|
|
|
215
219
|
}
|
|
216
220
|
if (ctx.action === "inspect") {
|
|
217
221
|
if (!isStateFlowTelegramScope(ctx.payload)) throw new Error("Unknown State Flow scope");
|
|
218
|
-
await ctx.openRich(renderStateFlowRichState(ctx.payload, port.state(ctx.payload)));
|
|
222
|
+
await ctx.openRich(renderStateFlowRichState(ctx.payload, port.snapshot().step, port.state(ctx.payload)));
|
|
219
223
|
await ctx.answerCallback();
|
|
220
224
|
return "handled" as const;
|
|
221
225
|
}
|
|
@@ -259,7 +263,7 @@ async function importTelegramModule<TModule>(
|
|
|
259
263
|
/** Default loader; injectable so tests and embedded hosts can control transport presence. */
|
|
260
264
|
export async function loadStateFlowTelegramModules(): Promise<StateFlowTelegramModules> {
|
|
261
265
|
const sections = await importTelegramModule<StateFlowTelegramSectionModule>(
|
|
262
|
-
|
|
266
|
+
stateFlowTelegramSectionSpecifiers(),
|
|
263
267
|
(module): module is StateFlowTelegramSectionModule =>
|
|
264
268
|
typeof (module as StateFlowTelegramSectionModule | undefined)?.registerTelegramSection === "function",
|
|
265
269
|
);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-state-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Incremental scoped state/context compiler for Pi, inspired by SKILL.state",
|
|
5
|
+
"description": "Incremental scoped state/context/memory compiler for Pi, inspired by SKILL.state",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
8
|
"pi-extension",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"index.ts",
|
|
22
22
|
"lib",
|
|
23
|
+
"dist",
|
|
23
24
|
"skills",
|
|
24
25
|
"docs",
|
|
25
26
|
"README.md",
|
|
@@ -28,18 +29,32 @@
|
|
|
28
29
|
"AGENTS.md"
|
|
29
30
|
],
|
|
30
31
|
"scripts": {
|
|
31
|
-
"check": "node
|
|
32
|
+
"check": "node -e \"await import('./dist/pi-state-flow/index.js'); console.log('pi-state-flow: extension import ok')\"",
|
|
32
33
|
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
33
34
|
"benchmark": "node --experimental-strip-types benchmarks/benchmark.ts",
|
|
34
35
|
"typecheck": "tsc --noEmit",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
36
|
+
"build": "node scripts/build-dist.mjs",
|
|
37
|
+
"pack:check": "npm pack --dry-run",
|
|
38
|
+
"validate": "npm run build && npm run typecheck && npm test && npm run check && npm run pack:check",
|
|
39
|
+
"prepack": "npm run build"
|
|
40
|
+
},
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"default": "./dist/index.js"
|
|
45
|
+
}
|
|
37
46
|
},
|
|
38
47
|
"pi": {
|
|
39
48
|
"extensions": [
|
|
49
|
+
"./dist/pi-state-flow/index.js"
|
|
50
|
+
],
|
|
51
|
+
"sourceExtensions": [
|
|
40
52
|
"./index.ts"
|
|
41
53
|
],
|
|
42
54
|
"skills": [
|
|
55
|
+
"./dist/skills"
|
|
56
|
+
],
|
|
57
|
+
"sourceSkills": [
|
|
43
58
|
"./skills"
|
|
44
59
|
],
|
|
45
60
|
"image": "https://raw.githubusercontent.com/llblab/pi-state-flow/main/banner.jpg"
|
|
@@ -53,9 +68,11 @@
|
|
|
53
68
|
"peerDependencies": {
|
|
54
69
|
"@earendil-works/pi-agent-core": "^0.84.4 || ^0.85.1",
|
|
55
70
|
"@earendil-works/pi-ai": "^0.84.4 || ^0.85.1",
|
|
56
|
-
"@earendil-works/pi-coding-agent": "^0.84.4 || ^0.85.1"
|
|
71
|
+
"@earendil-works/pi-coding-agent": "^0.84.4 || ^0.85.1",
|
|
72
|
+
"@earendil-works/pi-tui": "^0.84.4 || ^0.85.1"
|
|
57
73
|
},
|
|
58
74
|
"devDependencies": {
|
|
75
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
59
76
|
"@types/node": "latest",
|
|
60
77
|
"typescript": "latest"
|
|
61
78
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: state-flow-memory
|
|
3
|
-
description: Audit and reconcile State Flow durable memory across global, CWD, and session scopes. Preserve commitments, established learning, and the point of continuation without freezing provisional approaches. Use for explicit memory curation, ownership migration, contradiction cleanup, stale continuation review, externally evidenced promotion,
|
|
3
|
+
description: Audit and reconcile State Flow durable memory across global, CWD, and session scopes. Preserve commitments, established learning, and the point of continuation without freezing provisional approaches. Use after completing a major feature, important release, large body of work, campaign, project phase, or meaningful checkpoint—even when the user did not explicitly ask for memory work—as well as for explicit memory curation, ownership migration, contradiction cleanup, stale continuation review, externally evidenced promotion, and active-version boundaries; not for unrelated routine turns or background maintenance.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# State Flow Memory Curation
|
|
@@ -78,6 +78,8 @@ Do not erase a meaningful correction, uncertainty, commitment, negative result,
|
|
|
78
78
|
|
|
79
79
|
### Reconcile phase boundaries
|
|
80
80
|
|
|
81
|
+
After a major feature, important release, large body of work, or meaningful checkpoint reaches completion or its final stage, proactively optimize the affected State Flow scopes. Distill implementation-specific detail into durable consequences, remove trajectory-bound scaffolding, and rebalance knowledge across global, CWD, and session ownership so the resulting state stays alive, reusable, and open to better future methods rather than preserving the shape of the finished effort.
|
|
82
|
+
|
|
81
83
|
A completed feature, release, campaign, project switch, or active-version change is evidence that its working set needs one bounded review. Remove completed task lists, obsolete release/version state, run identifiers, timings, incident chronology, dead experiments, and stale continuation. Retain shipped status only when it remains a prerequisite, durable rule, open risk, or useful retrieval pointer.
|
|
82
84
|
|
|
83
85
|
State branches may move as applicability changes. Global is limited to established cross-project, user, or environment knowledge; CWD owns reusable project truth; session owns branch/run continuation. Narrow project-specific global material into CWD, promote genuinely cross-project learning only when evidence supports the broader boundary, and move reusable session learning into CWD without carrying its transient run shell.
|