@llblab/pi-kit 0.6.0 → 0.7.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 +4 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
- package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
- package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
- package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
- package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
- package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
- package/node_modules/@llblab/pi-actors/package.json +1 -1
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
- package/node_modules/@llblab/pi-state-flow/README.md +203 -107
- package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
- package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
- package/node_modules/@llblab/pi-state-flow/package.json +8 -2
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
- package/node_modules/@llblab/pi-telegram/AGENTS.md +13 -8
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +20 -3
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-telegram/README.md +12 -8
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +220 -18
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +12 -5
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
- package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
- package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +28 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +350 -26
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +544 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +234 -11
- package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -24
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1792 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +38 -1
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +51 -10
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +52 -18
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +305 -112
- package/node_modules/@llblab/pi-telegram/lib/status.ts +10 -0
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +279 -4
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
- package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
- package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
- package/node_modules/@llblab/skills/package.json +2 -3
- package/package.json +6 -5
|
@@ -1,16 +1,139 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { RECENT_TRANSITION_LIMIT } from "./history.ts";
|
|
3
|
+
import { parseRemotePublicationPolicyDocument, serializeRemotePublicationPolicyDocument, type RemotePublicationPolicyDocument } from "./publication.ts";
|
|
4
|
+
import { applyPatch, canonicalJson, containsNull, isJsonValue, isObject, type JsonObject } from "./json.ts";
|
|
5
|
+
import { validateTemporalLineage, type TransitionBoundary } from "./temporal.ts";
|
|
6
|
+
import { migrateLegacySkillCompilations } from "./skills.ts";
|
|
7
|
+
import { isMaterializedState, type MaterializedState } from "./state.ts";
|
|
3
8
|
import { MAX_VALIDATION_RETRIES, type ValidationFeedback } from "./validation.ts";
|
|
4
9
|
|
|
5
10
|
const MAX_RESTORED_STEP = Number.MAX_SAFE_INTEGER - 1;
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
/** Missing operational capability is not evidence that a checkpoint target is invalid. */
|
|
13
|
+
export class RevisionUnavailableError extends Error {}
|
|
14
|
+
|
|
15
|
+
export interface SnapshotConfig {
|
|
8
16
|
enabled: boolean;
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
transitionWindow: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PendingPublicationState {
|
|
21
|
+
commit: string;
|
|
22
|
+
error: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SnapshotMeta {
|
|
26
|
+
durableBase?: string;
|
|
27
|
+
pendingPublication?: PendingPublicationState;
|
|
11
28
|
step: number;
|
|
29
|
+
specification?: string;
|
|
12
30
|
validation?: ValidationFeedback;
|
|
13
31
|
bootstrap?: boolean;
|
|
32
|
+
remotePublication?: RemotePublicationPolicyDocument;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LegacySessionMigration {
|
|
36
|
+
state: MaterializedState;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** In-memory runtime config/provenance; durable config/meta and scope files own restoration. */
|
|
40
|
+
export interface StateFlowSnapshot {
|
|
41
|
+
config: SnapshotConfig;
|
|
42
|
+
meta: SnapshotMeta;
|
|
43
|
+
/** Ephemeral one-way migration payload. persistSnapshot() never writes it to Pi checkpoints. */
|
|
44
|
+
legacySession?: LegacySessionMigration;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type Snapshot = StateFlowSnapshot;
|
|
48
|
+
|
|
49
|
+
export interface SessionRuntime {
|
|
50
|
+
config: SnapshotConfig;
|
|
51
|
+
meta: Omit<SnapshotMeta, "durableBase" | "pendingPublication"> & {
|
|
52
|
+
version: 1;
|
|
53
|
+
identity: { cwd: string; sessionId: string };
|
|
54
|
+
lineage: TransitionBoundary[];
|
|
55
|
+
/** Resolved against the commit that last wrote this runtime record, not arbitrary HEAD. */
|
|
56
|
+
revision: "self";
|
|
57
|
+
temporalRevision?: "self" | string;
|
|
58
|
+
/** Durable intent survives a crash before the push result can be observed. */
|
|
59
|
+
publication: "unconfirmed" | "files";
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function validateSessionRuntime(value: unknown, cwd: string, sessionId: string): asserts value is SessionRuntime {
|
|
64
|
+
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
|
|
65
|
+
|| !isObject(value.config) || !isObject(value.meta)) throw new Error("Invalid State Flow session runtime envelope");
|
|
66
|
+
const { version, identity, lineage, revision, temporalRevision, publication, ...fields } = value.meta;
|
|
67
|
+
if (temporalRevision !== undefined && temporalRevision !== "self"
|
|
68
|
+
&& !isExactRevision(temporalRevision)) throw new Error("Invalid temporal revision reference");
|
|
69
|
+
if (version !== 1 || revision !== "self" || (publication !== "unconfirmed" && publication !== "files")) throw new Error("Unsupported State Flow runtime provenance format");
|
|
70
|
+
if (publication === "files" && temporalRevision !== undefined && temporalRevision !== "self") throw new Error("File runtime cannot select a historical temporal revision");
|
|
71
|
+
if (!isObject(identity) || Object.keys(identity).sort().join(",") !== "cwd,sessionId"
|
|
72
|
+
|| identity.cwd !== resolve(cwd) || identity.sessionId !== sessionId || sessionId.trim().length === 0 || sessionId !== sessionId.trim()) {
|
|
73
|
+
throw new Error("State Flow runtime scope identity mismatch");
|
|
74
|
+
}
|
|
75
|
+
validateTemporalLineage(lineage);
|
|
76
|
+
const allowed = new Set(["step", "specification", "validation", "bootstrap", "remotePublication"]);
|
|
77
|
+
if (Object.keys(fields).some((key) => !allowed.has(key))) throw new Error("Unexpected State Flow runtime metadata field");
|
|
78
|
+
const normalized = migrateSnapshot({ config: value.config, meta: fields });
|
|
79
|
+
if (fields.bootstrap === false) normalized.meta.bootstrap = false;
|
|
80
|
+
if (fields.step === Number.MAX_SAFE_INTEGER) normalized.meta.step = Number.MAX_SAFE_INTEGER;
|
|
81
|
+
if (canonicalJson({ config: normalized.config, meta: normalized.meta }) !== canonicalJson({ config: value.config, meta: fields })) {
|
|
82
|
+
throw new Error("Invalid State Flow runtime configuration or counters");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createSessionRuntime(snapshot: Snapshot, cwd: string, sessionId: string, lineage: readonly TransitionBoundary[], publication: SessionRuntime["meta"]["publication"] = "unconfirmed"): SessionRuntime {
|
|
87
|
+
const { durableBase: _base, pendingPublication: _publication, ...fields } = snapshot.meta;
|
|
88
|
+
const runtime: SessionRuntime = {
|
|
89
|
+
config: structuredClone(snapshot.config),
|
|
90
|
+
meta: {
|
|
91
|
+
...Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined)) as Omit<SnapshotMeta, "durableBase" | "pendingPublication">,
|
|
92
|
+
version: 1,
|
|
93
|
+
identity: { cwd: resolve(cwd), sessionId },
|
|
94
|
+
lineage: structuredClone([...lineage]),
|
|
95
|
+
revision: "self",
|
|
96
|
+
publication,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
validateSessionRuntime(runtime, cwd, sessionId);
|
|
100
|
+
return runtime;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function serializeSessionRuntime(runtime: SessionRuntime, cwd: string, sessionId: string): { config: string; meta: string } {
|
|
104
|
+
validateSessionRuntime(runtime, cwd, sessionId);
|
|
105
|
+
return { config: `${canonicalJson(runtime.config)}\n`, meta: `${canonicalJson(runtime.meta)}\n` };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function parseSessionRuntime(config: string | undefined, meta: string | undefined, cwd: string, sessionId: string): SessionRuntime | undefined {
|
|
109
|
+
if (config === undefined && meta === undefined) return undefined;
|
|
110
|
+
if (config === undefined || meta === undefined) throw new Error("Incomplete State Flow config/meta pair");
|
|
111
|
+
let runtime: unknown;
|
|
112
|
+
try {
|
|
113
|
+
runtime = { config: JSON.parse(config), meta: JSON.parse(meta) };
|
|
114
|
+
} catch {
|
|
115
|
+
throw new Error("State Flow session runtime contains invalid JSON");
|
|
116
|
+
}
|
|
117
|
+
validateSessionRuntime(runtime, cwd, sessionId);
|
|
118
|
+
return runtime;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function resolveSessionRuntime(runtime: SessionRuntime, revision: string): { snapshot: Snapshot; lineage: TransitionBoundary[]; publicationTarget: string } {
|
|
122
|
+
validateSessionRuntime(runtime, runtime.meta.identity.cwd, runtime.meta.identity.sessionId);
|
|
123
|
+
if (!isExactRevision(revision) || runtime.meta.publication !== "unconfirmed") throw new Error("Runtime self reference requires its exact Git revision and Git publication provenance");
|
|
124
|
+
const { version: _version, identity: _identity, lineage, revision: _self, temporalRevision: _temporal, publication: _intent, ...fields } = runtime.meta;
|
|
125
|
+
return {
|
|
126
|
+
snapshot: { config: structuredClone(runtime.config), meta: { ...structuredClone(fields), durableBase: revision } },
|
|
127
|
+
lineage: structuredClone(lineage),
|
|
128
|
+
publicationTarget: revision,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function resolveFileSessionRuntime(runtime: SessionRuntime, revision: string): Snapshot {
|
|
133
|
+
validateSessionRuntime(runtime, runtime.meta.identity.cwd, runtime.meta.identity.sessionId);
|
|
134
|
+
if (!isFileRevision(revision) || runtime.meta.publication !== "files") throw new Error("File runtime requires its exact file revision and file publication provenance");
|
|
135
|
+
const { version: _version, identity: _identity, lineage: _lineage, revision: _self, temporalRevision: _temporal, publication: _intent, ...fields } = runtime.meta;
|
|
136
|
+
return { config: structuredClone(runtime.config), meta: { ...structuredClone(fields), durableBase: revision } };
|
|
14
137
|
}
|
|
15
138
|
|
|
16
139
|
function isLegacyTwoPartState(value: unknown): value is { contract: JsonObject; working: JsonObject } {
|
|
@@ -20,6 +143,14 @@ function isLegacyTwoPartState(value: unknown): value is { contract: JsonObject;
|
|
|
20
143
|
&& Object.keys(value).every((key) => key === "contract" || key === "working");
|
|
21
144
|
}
|
|
22
145
|
|
|
146
|
+
function isLegacyThreePartState(value: unknown): value is { contract: JsonObject; working: JsonObject; response: string } {
|
|
147
|
+
return isObject(value)
|
|
148
|
+
&& isObject(value.contract)
|
|
149
|
+
&& isObject(value.working)
|
|
150
|
+
&& typeof value.response === "string"
|
|
151
|
+
&& Object.keys(value).every((key) => key === "contract" || key === "working" || key === "response");
|
|
152
|
+
}
|
|
153
|
+
|
|
23
154
|
function restoredStep(value: unknown): number {
|
|
24
155
|
return typeof value === "number"
|
|
25
156
|
&& Number.isSafeInteger(value)
|
|
@@ -43,61 +174,187 @@ function restoredValidation(value: unknown): ValidationFeedback | undefined {
|
|
|
43
174
|
};
|
|
44
175
|
}
|
|
45
176
|
|
|
46
|
-
|
|
177
|
+
function restoredTransitionWindow(value: unknown): number {
|
|
178
|
+
return Number.isSafeInteger(value)
|
|
179
|
+
&& (value as number) >= 0
|
|
180
|
+
&& (value as number) <= RECENT_TRANSITION_LIMIT
|
|
181
|
+
? value as number
|
|
182
|
+
: RECENT_TRANSITION_LIMIT;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function restoredPendingPublication(value: unknown): PendingPublicationState | undefined {
|
|
186
|
+
if (!isObject(value)
|
|
187
|
+
|| typeof value.commit !== "string"
|
|
188
|
+
|| !/^[0-9a-f]{40,64}$/.test(value.commit)
|
|
189
|
+
|| typeof value.error !== "string"
|
|
190
|
+
|| value.error.trim().length === 0) return undefined;
|
|
191
|
+
return { commit: value.commit, error: value.error };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function restoredMeta(value: unknown, legacy: JsonObject = {}): SnapshotMeta {
|
|
195
|
+
const meta = isObject(value) ? value : legacy;
|
|
196
|
+
const pendingPublication = restoredPendingPublication(meta.pendingPublication);
|
|
197
|
+
const validation = restoredValidation(meta.validation);
|
|
198
|
+
let remotePublication: RemotePublicationPolicyDocument | undefined;
|
|
199
|
+
try {
|
|
200
|
+
if (meta.remotePublication !== undefined) remotePublication = serializeRemotePublicationPolicyDocument(
|
|
201
|
+
parseRemotePublicationPolicyDocument(meta.remotePublication, { legacyRuntime: false }),
|
|
202
|
+
);
|
|
203
|
+
} catch {
|
|
204
|
+
remotePublication = undefined;
|
|
205
|
+
}
|
|
47
206
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
207
|
+
...(isDurableRevision(meta.durableBase)
|
|
208
|
+
? { durableBase: meta.durableBase }
|
|
209
|
+
: {}),
|
|
210
|
+
...(pendingPublication === undefined ? {} : { pendingPublication }),
|
|
211
|
+
step: restoredStep(meta.step),
|
|
212
|
+
...(typeof meta.specification === "string" ? { specification: meta.specification } : {}),
|
|
213
|
+
...(validation === undefined ? {} : { validation }),
|
|
214
|
+
...(meta.bootstrap === true ? { bootstrap: true } : {}),
|
|
215
|
+
...(remotePublication === undefined ? {} : { remotePublication }),
|
|
57
216
|
};
|
|
58
217
|
}
|
|
59
218
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
219
|
+
function envelope(
|
|
220
|
+
enabled: boolean,
|
|
221
|
+
meta: SnapshotMeta,
|
|
222
|
+
transitionWindow = RECENT_TRANSITION_LIMIT,
|
|
223
|
+
legacySession?: LegacySessionMigration,
|
|
224
|
+
): Snapshot {
|
|
225
|
+
return {
|
|
226
|
+
config: { enabled, transitionWindow },
|
|
227
|
+
meta,
|
|
228
|
+
...(legacySession === undefined ? {} : { legacySession }),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function emptySnapshot(enabled = false): Snapshot {
|
|
233
|
+
return envelope(enabled, { step: 0 });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export type PiCheckpoint = { revision: string } | { disabled: true };
|
|
237
|
+
export type FileRevision = `file:${string}`;
|
|
238
|
+
|
|
239
|
+
export function isFileRevision(value: unknown): value is FileRevision {
|
|
240
|
+
return typeof value === "string" && /^file:[0-9a-f]{64}$/.test(value);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function isDurableRevision(value: unknown): value is string {
|
|
244
|
+
return isExactRevision(value) || isFileRevision(value);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function isExactRevision(value: unknown): value is string {
|
|
248
|
+
return typeof value === "string" && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(value);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function persistableSnapshot(snapshot: Snapshot): PiCheckpoint {
|
|
252
|
+
if (snapshot.meta.durableBase !== undefined) {
|
|
253
|
+
if (!isDurableRevision(snapshot.meta.durableBase)) throw new Error("Checkpoint requires an exact Git revision or file reference");
|
|
254
|
+
return { revision: snapshot.meta.durableBase };
|
|
255
|
+
}
|
|
256
|
+
if (snapshot.legacySession) throw new Error("Legacy semantic state requires migration before checkpoint publication");
|
|
257
|
+
if (snapshot.config.enabled) throw new Error("Enabled checkpoint requires a durable runtime revision");
|
|
258
|
+
return { disabled: true };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** New wire shapes are strict; predecessor envelopes remain one-way migration input. */
|
|
262
|
+
export function parsePiCheckpoint(value: unknown): PiCheckpoint | Snapshot {
|
|
263
|
+
if (!isObject(value)) throw new Error("Invalid State Flow checkpoint");
|
|
264
|
+
if (Object.hasOwn(value, "revision") || Object.hasOwn(value, "disabled")) {
|
|
265
|
+
if (Object.keys(value).length === 1) {
|
|
266
|
+
if (isDurableRevision(value.revision)) return { revision: value.revision };
|
|
267
|
+
if (value.disabled === true) return { disabled: true };
|
|
268
|
+
}
|
|
269
|
+
throw new Error("Invalid State Flow checkpoint pointer or disabled marker");
|
|
270
|
+
}
|
|
271
|
+
if (!Object.hasOwn(value, "config") && !Object.hasOwn(value, "enabled")) throw new Error("Unrecognized State Flow checkpoint");
|
|
272
|
+
const config = Object.hasOwn(value, "config") ? value.config : value;
|
|
273
|
+
if (!isObject(config) || typeof config.enabled !== "boolean") throw new Error("Invalid legacy State Flow configuration");
|
|
274
|
+
if (Object.hasOwn(value, "meta") && !isObject(value.meta)) throw new Error("Invalid legacy State Flow metadata");
|
|
275
|
+
const meta = isObject(value.meta) ? value.meta : value;
|
|
276
|
+
if (Object.hasOwn(meta, "durableBase") && !isDurableRevision(meta.durableBase)) throw new Error("Invalid legacy State Flow revision");
|
|
277
|
+
return migrateSnapshot(value);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function migrationFailure(data: JsonObject, error: string): Snapshot {
|
|
281
|
+
const meta = restoredMeta(data.meta, data);
|
|
282
|
+
meta.validation = {
|
|
283
|
+
attempt: 0,
|
|
284
|
+
error,
|
|
285
|
+
instruction: "Start a fresh State Flow episode; null is reserved for patch deletion.",
|
|
68
286
|
};
|
|
69
|
-
|
|
287
|
+
const config = isObject(data.config) ? data.config : data;
|
|
288
|
+
return envelope(false, meta, restoredTransitionWindow(config.transitionWindow));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function migratedSnapshot(
|
|
292
|
+
enabled: boolean,
|
|
293
|
+
meta: SnapshotMeta,
|
|
294
|
+
transitionWindow: number,
|
|
295
|
+
state: MaterializedState,
|
|
296
|
+
): Snapshot {
|
|
297
|
+
return envelope(enabled, meta, transitionWindow, {
|
|
298
|
+
state: migrateLegacySkillCompilations(state),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function migrateSnapshot(value: unknown): Snapshot {
|
|
303
|
+
if (!isObject(value)) return emptySnapshot();
|
|
304
|
+
const isEnvelope = Object.hasOwn(value, "config") || Object.hasOwn(value, "meta");
|
|
305
|
+
const config = isEnvelope && isObject(value.config) ? value.config : value;
|
|
306
|
+
const meta = restoredMeta(isEnvelope ? value.meta : undefined, value);
|
|
307
|
+
const enabled = config.enabled === true;
|
|
308
|
+
const transitionWindow = restoredTransitionWindow(config.transitionWindow);
|
|
309
|
+
if (isMaterializedState(value.state)) {
|
|
310
|
+
if (!isJsonValue(value.state)) return migrationFailure(value, "Restored state contains non-JSON data");
|
|
311
|
+
if (containsNull(value.state)) return migrationFailure(value, "Restored state contains null data");
|
|
312
|
+
return migratedSnapshot(enabled, meta, transitionWindow, structuredClone(value.state));
|
|
313
|
+
}
|
|
314
|
+
if (isLegacyThreePartState(value.state)) {
|
|
70
315
|
if (!isJsonValue(value.state)) return migrationFailure(value, "Restored state contains non-JSON data");
|
|
71
316
|
if (containsNull(value.state)) return migrationFailure(value, "Restored state contains null data");
|
|
72
|
-
return
|
|
317
|
+
return migratedSnapshot(enabled, meta, transitionWindow, {
|
|
318
|
+
artifacts: {},
|
|
319
|
+
...structuredClone(value.state),
|
|
320
|
+
});
|
|
73
321
|
}
|
|
74
322
|
if (isLegacyTwoPartState(value.state)) {
|
|
75
323
|
if (!isJsonValue(value.state)) return migrationFailure(value, "Restored state contains non-JSON data");
|
|
76
324
|
if (containsNull(value.state)) return migrationFailure(value, "Restored state contains null data");
|
|
77
|
-
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
};
|
|
325
|
+
return migratedSnapshot(enabled, meta, transitionWindow, {
|
|
326
|
+
artifacts: {},
|
|
327
|
+
contract: structuredClone(value.state.contract),
|
|
328
|
+
working: structuredClone(value.state.working),
|
|
329
|
+
response: "",
|
|
330
|
+
});
|
|
85
331
|
}
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
332
|
+
if (Object.hasOwn(value, "state")) {
|
|
333
|
+
if (isObject(value.state)
|
|
334
|
+
&& (Object.hasOwn(value.state, "response")
|
|
335
|
+
|| Object.hasOwn(value.state, "artifacts")
|
|
336
|
+
|| (Object.hasOwn(value.state, "contract") && Object.hasOwn(value.state, "working")))) {
|
|
337
|
+
return migrationFailure(value, "Restored state has an invalid materialized-state schema");
|
|
338
|
+
}
|
|
339
|
+
if (value.state !== undefined && !isObject(value.state)) {
|
|
340
|
+
return migrationFailure(value, "Restored state has an invalid materialized-state schema");
|
|
341
|
+
}
|
|
90
342
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
343
|
+
if (Object.hasOwn(value, "stateBasis") || Object.hasOwn(value, "previousStatePatch")) {
|
|
344
|
+
const legacyBasis = isObject(value.stateBasis)
|
|
345
|
+
? value.stateBasis
|
|
346
|
+
: isObject(value.state)
|
|
347
|
+
? value.state
|
|
348
|
+
: {};
|
|
349
|
+
const legacyPatch = isObject(value.previousStatePatch) ? value.previousStatePatch : undefined;
|
|
350
|
+
if (!isJsonValue(legacyBasis) || (legacyPatch !== undefined && !isJsonValue(legacyPatch))) {
|
|
351
|
+
return migrationFailure(value, "Legacy state contains non-JSON data");
|
|
352
|
+
}
|
|
353
|
+
const legacyState = legacyPatch === undefined ? legacyBasis : applyPatch(legacyBasis, legacyPatch);
|
|
354
|
+
if (containsNull(legacyState)) return migrationFailure(value, "Legacy state contains null data");
|
|
355
|
+
return migratedSnapshot(enabled, meta, transitionWindow, {
|
|
356
|
+
artifacts: {}, contract: {}, working: structuredClone(legacyState), response: "",
|
|
357
|
+
});
|
|
99
358
|
}
|
|
100
|
-
|
|
101
|
-
if (containsNull(legacyState)) return migrationFailure(value, "Legacy state contains null data");
|
|
102
|
-
return { ...base, state: { contract: {}, working: structuredClone(legacyState), response: "" } };
|
|
359
|
+
return envelope(enabled, meta, transitionWindow);
|
|
103
360
|
}
|
|
@@ -1,20 +1,87 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
isArtifactRegistry,
|
|
3
|
+
updateArtifactRegistry,
|
|
4
|
+
type ArtifactCompilationUpdate,
|
|
5
|
+
type ArtifactRegistry,
|
|
6
|
+
} from "./artifact.ts";
|
|
7
|
+
import { applyPatch, isObject, type JsonObject } from "./json.ts";
|
|
2
8
|
|
|
3
|
-
/** The
|
|
4
|
-
export interface
|
|
9
|
+
/** The canonical semantic state shape shared by global, CWD, and session scopes. */
|
|
10
|
+
export interface MaterializedState extends JsonObject {
|
|
11
|
+
artifacts: ArtifactRegistry;
|
|
5
12
|
contract: JsonObject;
|
|
6
13
|
working: JsonObject;
|
|
7
14
|
response: string;
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
/** Compatibility name for callers that still treat materialized state as a document. */
|
|
18
|
+
export type StateDocument = MaterializedState;
|
|
19
|
+
|
|
20
|
+
/** Model patch shape; artifact entries may be compiler outputs before trusted metadata is attached. */
|
|
21
|
+
export interface StatePatch extends JsonObject {
|
|
22
|
+
artifacts: JsonObject;
|
|
23
|
+
contract: JsonObject;
|
|
24
|
+
working: JsonObject;
|
|
25
|
+
response: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type StateScope = "global" | "cwd" | "session";
|
|
29
|
+
|
|
30
|
+
/** A model-authored patch for one scope. Response is captured by the runtime in session state. */
|
|
31
|
+
export interface ScopePatch {
|
|
32
|
+
artifacts?: JsonObject;
|
|
33
|
+
contract?: JsonObject;
|
|
34
|
+
working?: JsonObject;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ScopedPatch {
|
|
38
|
+
scope: StateScope;
|
|
39
|
+
patch: ScopePatch;
|
|
12
40
|
}
|
|
13
41
|
|
|
14
|
-
export
|
|
42
|
+
export interface SemanticTransition {
|
|
43
|
+
transitions: ScopedPatch[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface TerminalTransition extends SemanticTransition {
|
|
47
|
+
response: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ScopedStates {
|
|
51
|
+
global: MaterializedState;
|
|
52
|
+
cwd: MaterializedState;
|
|
53
|
+
session: MaterializedState;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function emptyState(): MaterializedState {
|
|
57
|
+
return { artifacts: {}, contract: {}, working: {}, response: "" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isMaterializedState(value: unknown): value is MaterializedState {
|
|
15
61
|
return isObject(value)
|
|
62
|
+
&& isArtifactRegistry(value.artifacts)
|
|
16
63
|
&& isObject(value.contract)
|
|
17
64
|
&& isObject(value.working)
|
|
18
65
|
&& typeof value.response === "string"
|
|
19
|
-
&& Object.keys(value).every((key) => key === "contract" || key === "working" || key === "response");
|
|
66
|
+
&& Object.keys(value).every((key) => key === "artifacts" || key === "contract" || key === "working" || key === "response");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const isStateDocument = isMaterializedState;
|
|
70
|
+
|
|
71
|
+
/** Atomically replace compiled and removed artifacts inside one materialized scope. */
|
|
72
|
+
export function updateMaterializedArtifacts(
|
|
73
|
+
state: MaterializedState,
|
|
74
|
+
updates: readonly ArtifactCompilationUpdate[],
|
|
75
|
+
removed: readonly string[] = [],
|
|
76
|
+
): MaterializedState {
|
|
77
|
+
if (!isMaterializedState(state)) throw new Error("Cannot update artifacts in an invalid materialized state");
|
|
78
|
+
const artifacts = updateArtifactRegistry(state.artifacts, updates, removed);
|
|
79
|
+
return { ...structuredClone(state), artifacts };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Overlay lower-to-higher scopes without mutating any scope document. */
|
|
83
|
+
export function overlayStates(...scopes: readonly MaterializedState[]): MaterializedState {
|
|
84
|
+
return scopes.reduce<MaterializedState>((effective, scope) => {
|
|
85
|
+
return applyPatch(effective, scope) as MaterializedState;
|
|
86
|
+
}, emptyState());
|
|
20
87
|
}
|
|
@@ -1,16 +1,135 @@
|
|
|
1
|
+
import type { ArtifactInvalidationReason } from "./artifact.ts";
|
|
2
|
+
import { projectRecentTransitionsWithLimit, type RecentTransitionWindow } from "./history.ts";
|
|
3
|
+
import { inspectMemoryPromotions, retainedMemoryScopes } from "./memory.ts";
|
|
4
|
+
import type { PublicationQueueState } from "./publication.ts";
|
|
1
5
|
import type { Snapshot } from "./snapshot.ts";
|
|
6
|
+
import { overlayStates, type ScopedStates, type StateScope } from "./state.ts";
|
|
7
|
+
import type { TransitionBoundary } from "./temporal.ts";
|
|
2
8
|
|
|
3
9
|
export const STATUS_KEY = "state-flow";
|
|
4
10
|
|
|
5
11
|
export type Colorize = (color: "accent" | "dim", text: string) => string;
|
|
6
12
|
|
|
13
|
+
export type StaleArtifactReason = ArtifactInvalidationReason | "source-removed";
|
|
14
|
+
|
|
15
|
+
export interface StaleArtifactDiagnostic {
|
|
16
|
+
scope: StateScope;
|
|
17
|
+
path: string;
|
|
18
|
+
reason: StaleArtifactReason;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PendingPublicationDiagnostic {
|
|
22
|
+
commit: string;
|
|
23
|
+
error: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StatusDiagnostics {
|
|
27
|
+
repositoryRoot: string;
|
|
28
|
+
cwdScopeKey: string;
|
|
29
|
+
sessionScopeKey: string;
|
|
30
|
+
scopeStates: ScopedStates;
|
|
31
|
+
recent: RecentTransitionWindow;
|
|
32
|
+
temporal?: { head: TransitionBoundary; historyDepth: number; tailCounts: Record<StateScope, number> };
|
|
33
|
+
staleArtifacts: readonly StaleArtifactDiagnostic[];
|
|
34
|
+
artifactFreshnessError?: string;
|
|
35
|
+
durableStateError?: string;
|
|
36
|
+
pendingPublication?: PendingPublicationDiagnostic;
|
|
37
|
+
retryQueued: boolean;
|
|
38
|
+
publicationQueue?: PublicationQueueState;
|
|
39
|
+
publicationQueueError?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
7
42
|
export function compactStatus(snapshot: Snapshot, colorize: Colorize): string | undefined {
|
|
8
|
-
if (!snapshot.enabled) return undefined;
|
|
9
|
-
return `${colorize("accent", "state-flow")} ${colorize("dim", `#${snapshot.step}`)}`;
|
|
43
|
+
if (!snapshot.config.enabled) return undefined;
|
|
44
|
+
return `${colorize("accent", "state-flow")} ${colorize("dim", `#${snapshot.meta.step}`)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function countArtifacts(states: ScopedStates, scope: StateScope): number {
|
|
48
|
+
return Object.keys(states[scope].artifacts).length;
|
|
10
49
|
}
|
|
11
50
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
51
|
+
function abbreviatedCommit(commit: string): string {
|
|
52
|
+
return commit.length > 12 ? commit.slice(0, 12) : commit;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function detailedStatus(snapshot: Snapshot, diagnostics: StatusDiagnostics): string {
|
|
56
|
+
const projectedRecent = projectRecentTransitionsWithLimit(
|
|
57
|
+
snapshot.config.transitionWindow,
|
|
58
|
+
diagnostics.recent,
|
|
59
|
+
);
|
|
60
|
+
const available = diagnostics.temporal !== undefined && diagnostics.durableStateError === undefined;
|
|
61
|
+
const materialized = !available ? undefined : {
|
|
62
|
+
global: diagnostics.scopeStates.global,
|
|
63
|
+
cwd: diagnostics.scopeStates.cwd,
|
|
64
|
+
session: diagnostics.scopeStates.session,
|
|
65
|
+
effective: overlayStates(
|
|
66
|
+
diagnostics.scopeStates.global,
|
|
67
|
+
diagnostics.scopeStates.cwd,
|
|
68
|
+
diagnostics.scopeStates.session,
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
const stateJson = materialized === undefined ? undefined : JSON.stringify(materialized, null, 2);
|
|
72
|
+
const freshnessError = diagnostics.artifactFreshnessError ?? (available ? undefined : "temporal global artifact registry is unavailable");
|
|
73
|
+
const stale = freshnessError === undefined
|
|
74
|
+
? String(diagnostics.staleArtifacts.length)
|
|
75
|
+
: "unknown";
|
|
76
|
+
const publication = diagnostics.pendingPublication === undefined
|
|
77
|
+
? "idle"
|
|
78
|
+
: `pending ${abbreviatedCommit(diagnostics.pendingPublication.commit)} — ${diagnostics.pendingPublication.error}`;
|
|
79
|
+
const retry = diagnostics.retryQueued
|
|
80
|
+
? `queued (attempt ${snapshot.meta.validation?.attempt ?? 0})`
|
|
81
|
+
: "idle";
|
|
82
|
+
const staleLines = freshnessError !== undefined
|
|
83
|
+
? [`Artifact freshness unavailable: ${freshnessError}`]
|
|
84
|
+
: diagnostics.staleArtifacts.length === 0
|
|
85
|
+
? ["Stale artifacts: none"]
|
|
86
|
+
: [
|
|
87
|
+
"Stale artifacts:",
|
|
88
|
+
...diagnostics.staleArtifacts.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`),
|
|
89
|
+
];
|
|
90
|
+
const temporal = available ? diagnostics.temporal : undefined;
|
|
91
|
+
const temporalLines = temporal === undefined
|
|
92
|
+
? [`Temporal materialization unavailable: ${diagnostics.durableStateError ?? "no selected branch runtime"}`,
|
|
93
|
+
"Hot history: unavailable; offsets beyond 7 require explicit cold Git inspection",
|
|
94
|
+
"Retained patch tails: unavailable"]
|
|
95
|
+
: [`Temporal head: ${JSON.stringify(temporal.head.id)}; branch-local position ${temporal.head.position}`,
|
|
96
|
+
`Hot history: offsets 0..${temporal.historyDepth}; maximum depth 7`,
|
|
97
|
+
`Retained patch tails: global ${temporal.tailCounts.global}; CWD ${temporal.tailCounts.cwd}; session ${temporal.tailCounts.session}`];
|
|
98
|
+
const artifacts = (scope: StateScope) => available ? countArtifacts(diagnostics.scopeStates, scope) : "unknown";
|
|
99
|
+
const promotions = available ? inspectMemoryPromotions(diagnostics.scopeStates.global) : [];
|
|
100
|
+
const promotionCounts = Object.fromEntries(["pending", "accepted", "failed", "unknown", "invalid"].map((status) => [status, promotions.filter((entry) => entry.status === status).length]));
|
|
101
|
+
const memoryScopes = available ? retainedMemoryScopes(diagnostics.scopeStates) : undefined;
|
|
102
|
+
const oneLine = (value: string) => value.replace(/\s+/g, " ").slice(0, 240);
|
|
103
|
+
const promotionLines = !available || promotions.length === 0 ? [] : [
|
|
104
|
+
"Memory promotions:",
|
|
105
|
+
...promotions.map((entry) => `- ${oneLine(entry.id)} — ${entry.status}; owner ${oneLine(entry.owner ?? "unavailable")}${entry.pointer ? `; pointer ${oneLine(entry.pointer)}` : ""}${entry.revision ? `; revision ${oneLine(entry.revision)}` : ""}${entry.error ? `; error ${oneLine(entry.error)}` : ""}`),
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
return [
|
|
109
|
+
`State Flow diagnostics — config.enabled=${snapshot.config.enabled}; config.transitionWindow=${snapshot.config.transitionWindow}; branch mode=${snapshot.config.enabled ? "active" : "inactive"}`,
|
|
110
|
+
`Repository: ${diagnostics.repositoryRoot}`,
|
|
111
|
+
`Scope keys: CWD ${diagnostics.cwdScopeKey}; session ${diagnostics.sessionScopeKey}`,
|
|
112
|
+
"Session files: config.json owns behavior; meta.json owns lineage and provenance",
|
|
113
|
+
`Runtime metadata: step #${snapshot.meta.step}; active revision ${snapshot.meta.durableBase ?? "none"}; bootstrap ${snapshot.meta.bootstrap === true}; validation attempts ${snapshot.meta.validation?.attempt ?? 0}`,
|
|
114
|
+
`Remote publication policy: ${snapshot.meta.remotePublication?.mode ?? "legacy-transition"}`,
|
|
115
|
+
diagnostics.publicationQueueError !== undefined
|
|
116
|
+
? `Remote queue: unavailable; error ${diagnostics.publicationQueueError}`
|
|
117
|
+
: diagnostics.publicationQueue === undefined
|
|
118
|
+
? "Remote queue: idle"
|
|
119
|
+
: `Remote queue: ${diagnostics.publicationQueue.status}; target ${abbreviatedCommit(diagnostics.publicationQueue.target)}; confirmed ${diagnostics.publicationQueue.confirmed ? abbreviatedCommit(diagnostics.publicationQueue.confirmed) : "none"}; attempt ${diagnostics.publicationQueue.attempt}${diagnostics.publicationQueue.error ? `; error ${diagnostics.publicationQueue.error}` : ""}`,
|
|
120
|
+
"Memory: owner state-flow; global retention enabled; global fallback active",
|
|
121
|
+
`Memory-bearing scopes: global ${memoryScopes?.global ?? "unknown"}; CWD ${memoryScopes?.cwd ?? "unknown"}; session ${memoryScopes?.session ?? "unknown"}`,
|
|
122
|
+
`Promotion status: pending ${promotionCounts.pending}; accepted ${promotionCounts.accepted}; failed ${promotionCounts.failed}; unknown ${promotionCounts.unknown}; invalid ${promotionCounts.invalid}`,
|
|
123
|
+
...promotionLines,
|
|
124
|
+
...temporalLines,
|
|
125
|
+
`Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")}; stale ${stale}`,
|
|
126
|
+
available ? `Recent transitions: global ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "global")).length}; CWD ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "cwd")).length}; session ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "session")).length}; active ${projectedRecent.length}` : "Recent transitions: unavailable",
|
|
127
|
+
`Publication policy: ${snapshot.meta.remotePublication?.mode ?? "legacy-unresolved"}`,
|
|
128
|
+
`Publication: ${publication}`,
|
|
129
|
+
`Terminal retry: ${retry}`,
|
|
130
|
+
...staleLines,
|
|
131
|
+
...(stateJson === undefined
|
|
132
|
+
? ["Materialized states: unavailable (global/CWD/session/effective)"]
|
|
133
|
+
: [`Materialized states (${Buffer.byteLength(stateJson, "utf8")} bytes; global/CWD/session Git-backed, effective overlay):`, "", stateJson]),
|
|
134
|
+
].join("\n");
|
|
16
135
|
}
|