@llblab/pi-kit 0.7.1 → 0.8.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +2 -2
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +21 -21
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -122
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +11 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +41 -35
  7. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +36 -16
  8. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +4 -4
  9. package/node_modules/@llblab/pi-state-flow/index.ts +23 -1
  10. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +3 -0
  11. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +191 -29
  12. package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -1
  13. package/node_modules/@llblab/pi-state-flow/lib/context.ts +42 -7
  14. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +38 -8
  15. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +1 -13
  16. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +290 -134
  17. package/node_modules/@llblab/pi-state-flow/lib/git.ts +32 -16
  18. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +41 -0
  19. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +12 -6
  20. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +16 -0
  21. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +3 -0
  22. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +167 -31
  23. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +15 -6
  24. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +40 -34
  25. package/node_modules/@llblab/pi-state-flow/lib/state.ts +6 -0
  26. package/node_modules/@llblab/pi-state-flow/lib/status.ts +4 -9
  27. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +25 -5
  28. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +17 -147
  29. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +49 -27
  30. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  31. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +4 -0
  32. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  33. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +21 -19
  34. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  35. package/package.json +3 -3
  36. package/node_modules/@llblab/pi-state-flow/lib/validation.ts +0 -27
@@ -1,8 +1,10 @@
1
1
  import {
2
+ compileArtifact,
2
3
  ORDINARY_ARTIFACT_COMPILER,
3
4
  validateArtifactMetadata,
4
5
  validateArtifactRegistry,
5
- type ArtifactMetadata,
6
+ type ArtifactCompilerOutput,
7
+ type ArtifactProvenance,
6
8
  } from "./artifact.ts";
7
9
  import type { SuccessfulArtifactRead } from "./acquisition.ts";
8
10
  import { createAcceptedTransition, type AcceptedTransition } from "./history.ts";
@@ -24,6 +26,8 @@ import type {
24
26
  export interface StagedScopedTransition {
25
27
  nextStates: ScopedStates;
26
28
  stateHashes: Record<StateScope, string>;
29
+ /** Fresh runtime-owned provenance for artifacts compiled in this transition. */
30
+ provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>>;
27
31
  causalBasis: string;
28
32
  committed: boolean;
29
33
  }
@@ -35,27 +39,26 @@ function compileReadArtifacts(
35
39
  nextState: StateDocument,
36
40
  patch: Pick<StatePatch, "artifacts">,
37
41
  successfulArtifactReads: Iterable<SuccessfulArtifactRead>,
42
+ provenance: Record<string, ArtifactProvenance>,
38
43
  ): void {
39
44
  for (const read of successfulArtifactReads) {
40
45
  const output = patch.artifacts[read.path];
41
46
  if (!isObject(output)) {
42
47
  throw new Error(`Every successfully read invalidated artifact must have a global compiler output at artifacts[exact candidate path]; missing: ${read.path}`);
43
48
  }
44
- if (Object.hasOwn(output, "hash") || Object.hasOwn(output, "compiler")) {
45
- throw new Error(`Artifact compiler output at ${read.path} cannot set runtime-owned hash or compiler fields`);
46
- }
47
- const metadata = {
48
- ...structuredClone(output),
49
- hash: read.hash,
49
+ const compiled = compileArtifact({
50
+ source: { path: read.path, hash: read.hash },
50
51
  compiler: ORDINARY_ARTIFACT_COMPILER,
51
- } as ArtifactMetadata;
52
- validateArtifactMetadata(metadata, read.path);
52
+ output: output as ArtifactCompilerOutput,
53
+ });
54
+ validateArtifactMetadata(compiled.semantic, read.path);
53
55
  Object.defineProperty(nextState.artifacts, read.path, {
54
- value: metadata,
56
+ value: compiled.semantic,
55
57
  enumerable: true,
56
58
  configurable: true,
57
59
  writable: true,
58
60
  });
61
+ provenance[read.path] = compiled.provenance;
59
62
  }
60
63
  }
61
64
 
@@ -63,6 +66,7 @@ function compileReadSkills(
63
66
  nextState: StateDocument,
64
67
  patch: Pick<StatePatch, "artifacts">,
65
68
  successfulSkillReads: Iterable<SuccessfulSkillRead>,
69
+ provenance: Record<string, ArtifactProvenance>,
66
70
  ): void {
67
71
  for (const read of successfulSkillReads) {
68
72
  if (read.hash === undefined) {
@@ -84,20 +88,20 @@ function compileReadSkills(
84
88
  if (!isObject(output.compilation) || Object.keys(output.compilation).length === 0) {
85
89
  throw new Error(`Skill artifact compiler output at ${read.path} must have a non-empty compilation object`);
86
90
  }
87
- const metadata = {
88
- ...structuredClone(output),
89
- hash: read.hash,
91
+ const compiled = compileArtifact({
92
+ source: { path: read.path, hash: read.hash },
90
93
  compiler: SKILL_ARTIFACT_COMPILER,
91
- kind: "skill",
92
- } as ArtifactMetadata;
93
- validateArtifactMetadata(metadata, read.path);
94
+ output: { ...structuredClone(output), kind: "skill" } as ArtifactCompilerOutput,
95
+ });
96
+ validateArtifactMetadata(compiled.semantic, read.path);
94
97
  Object.defineProperty(nextState.artifacts, read.path, {
95
- value: metadata,
98
+ value: compiled.semantic,
96
99
  enumerable: true,
97
100
  configurable: true,
98
101
  writable: true,
99
102
  });
100
- if (!hasCompiledSkillArtifact(nextState.artifacts, read.path, read.hash)) {
103
+ provenance[read.path] = compiled.provenance;
104
+ if (!hasCompiledSkillArtifact(nextState.artifacts, provenance[read.path], read.path, read.hash)) {
101
105
  throw new Error(`Skill artifact compilation at ${read.path} is not locally materialized for its executed source identity`);
102
106
  }
103
107
  }
@@ -146,7 +150,7 @@ function stageScopedSemanticTransition(
146
150
  successfulSkillReads: Iterable<SuccessfulSkillRead>,
147
151
  causalBasis: string,
148
152
  successfulArtifactReads: Iterable<SuccessfulArtifactRead>,
149
- terminalResponse?: string,
153
+ acceptedResponse?: string,
150
154
  ): StagedScopedTransition {
151
155
  if (!Array.isArray(transition.transitions)) throw new Error("State Flow transitions must be an array");
152
156
  const patches = new Map<StateScope, ScopePatch>();
@@ -164,20 +168,22 @@ function stageScopedSemanticTransition(
164
168
 
165
169
  const cwdPatch = patches.get("cwd") ?? {};
166
170
  const nextStates = structuredClone(currentStates);
171
+ const provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>> = { global: {}, cwd: {}, session: {} };
167
172
  for (const scope of SCOPES) {
168
173
  const authored = patches.get(scope) ?? {};
169
- const response = scope === "session" && terminalResponse !== undefined
170
- ? terminalResponse
174
+ const response = scope === "session" && acceptedResponse !== undefined
175
+ ? acceptedResponse
171
176
  : currentStates[scope].response;
172
177
  const patch = completePatch(authored, response);
173
178
  const nextState = applyPatch(structuredClone(currentStates[scope]), patch) as MaterializedState;
174
- compileReadArtifacts(nextState, { artifacts: scope === "global" ? authored.artifacts ?? {} : {} }, scope === "global" ? successfulArtifactReads : []);
175
- compileReadSkills(nextState, { artifacts: scope === "cwd" ? cwdPatch.artifacts ?? {} : {} }, scope === "cwd" ? successfulSkillReads : []);
179
+ compileReadArtifacts(nextState, { artifacts: scope === "global" ? authored.artifacts ?? {} : {} }, scope === "global" ? successfulArtifactReads : [], provenanceUpdates.global);
180
+ compileReadSkills(nextState, { artifacts: scope === "cwd" ? cwdPatch.artifacts ?? {} : {} }, scope === "cwd" ? successfulSkillReads : [], provenanceUpdates.cwd);
176
181
  validateMaterializedTransition(nextState);
177
182
  nextStates[scope] = nextState;
178
183
  }
179
184
  return {
180
185
  nextStates,
186
+ provenanceUpdates,
181
187
  stateHashes: {
182
188
  global: hashJson(currentStates.global),
183
189
  cwd: hashJson(currentStates.cwd),
@@ -188,6 +194,22 @@ function stageScopedSemanticTransition(
188
194
  };
189
195
  }
190
196
 
197
+ /** Validate that explicit unchanged resolution has no pending acquisition/compilation obligation. */
198
+ export function validateUnchangedResolution(
199
+ currentStates: ScopedStates,
200
+ successfulSkillReads: Iterable<SuccessfulSkillRead>,
201
+ causalBasis: string,
202
+ successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
203
+ ): void {
204
+ stageScopedSemanticTransition(
205
+ currentStates,
206
+ { transitions: [] },
207
+ successfulSkillReads,
208
+ causalBasis,
209
+ successfulArtifactReads,
210
+ );
211
+ }
212
+
191
213
  /** Stage one intermediate state barrier without changing the finalized response. */
192
214
  export function stageScopedPatch(
193
215
  currentStates: ScopedStates,
@@ -213,7 +235,7 @@ export function stageScopedTransition(
213
235
  successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
214
236
  ): StagedScopedTransition {
215
237
  if (typeof transition.response !== "string" || transition.response.trim().length === 0) {
216
- throw new Error("Terminal State Flow response body must be non-empty");
238
+ throw new Error("Accepted State Flow response body must be non-empty");
217
239
  }
218
240
  return stageScopedSemanticTransition(
219
241
  currentStates,
@@ -227,7 +249,7 @@ export function stageScopedTransition(
227
249
 
228
250
  /** Commit one accepted transition; durable publication receives all changed scopes as one cohort. */
229
251
  export interface CommitScopedTransitionOptions {
230
- /** Terminal reconciliation finalizes bootstrap and validation lifecycle state. */
252
+ /** Runtime response reconciliation finalizes bootstrap lifecycle state. */
231
253
  finalizeRun?: boolean;
232
254
  }
233
255
 
@@ -240,10 +262,10 @@ export function commitScopedTransition(
240
262
  options: CommitScopedTransitionOptions = {},
241
263
  ): boolean {
242
264
  if (stage.committed) return false;
243
- if (causalBasis !== stage.causalBasis) throw new Error("State Flow causal basis changed after response validation; regenerate the terminal response");
265
+ if (causalBasis !== stage.causalBasis) throw new Error("State Flow causal basis changed before response reconciliation; rematerialize state before retrying");
244
266
  for (const scope of SCOPES) {
245
267
  if (hashJson(states[scope]) !== stage.stateHashes[scope]) {
246
- throw new Error(`State Flow ${scope} scope changed after response validation; regenerate the terminal response`);
268
+ throw new Error(`State Flow ${scope} scope changed before response reconciliation; rematerialize state before retrying`);
247
269
  }
248
270
  }
249
271
  // The finalized response may differ from message_end after chained handlers.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-state-flow",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Incremental scoped state/context compiler for Pi, inspired by SKILL.state",
6
6
  "keywords": [
@@ -4,6 +4,10 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.45.2: Provider-Compatible Bind Schema Hotfix
8
+
9
+ - `Provider-Compatible Bind Argument`: Serializes the `telegram_bind` `argument` schema as an inline builder-made JSON-value union bounded to four container levels, with no `$ref`/`$defs` recursion or raw TypeBox marker leakage; OpenAI no longer rejects every request with "Recursive JSON schemas are not currently supported" (#273) and Gemini no longer rejects the unknown `~optional` field (#269) while the tool is registered.
10
+
7
11
  ## 0.45.1: Guest Mode And Channel Media Hotfixes
8
12
 
9
13
  - `Environment-backed bot tokens`: `telegram.json` profiles may store an exact `$NAME`/`${NAME}` reference instead of a copied token. Resolution happens only at validation/activation boundaries, including pairing identity hashing; setup prefills the first supported alias, validates the resolved value, and persists the alias; literals stay compatible; unresolved references fail closed with a redacted named-variable diagnostic in setup, connect, and status.
@@ -98,7 +98,7 @@ An absent, stale, or invalid bound app fails closed and never degrades into an a
98
98
 
99
99
  ## `telegram_bind` Tool
100
100
 
101
- One agent Tool owns installation and deliberate invocation through two mutually exclusive shapes. Its optional `argument` schema explicitly describes recursive JSON values (`null`, boolean, number, string, array, or object) rather than using an unconstrained subschema. Pi keeps the same JSON semantics, while schema aggregators cannot lower this field to a bare `true` schema that some llama-server grammars reject. The recursive definition lives in the tool root `$defs` and uses only local `#/$defs/TelegramBindJsonValue` references; it does not rely on named/remote reference resolution or newer TypeBox runtime helpers.
101
+ One agent Tool owns installation and deliberate invocation through two mutually exclusive shapes. Its optional `argument` schema explicitly describes JSON values (`null`, boolean, number, string, array, or object) as a bounded union nested four container levels deep rather than using an unconstrained subschema. Pi keeps the same JSON semantics, while schema aggregators cannot lower this field to a bare `true` schema that some llama-server grammars reject. The union is built through standard schema builders and serialized inline with no `$ref`/`$defs` recursion, so provider tool APIs that reject recursive or reference-resolved schemas (OpenAI) accept it alongside providers strict about unknown schema fields (Gemini).
102
102
 
103
103
  Install an external self-contained module and initialize it:
104
104
 
@@ -862,21 +862,23 @@ export function formatGenerativeAppToolError(error: unknown): Error {
862
862
  return new Error(`\n${message.replace(/^\n+/u, "") || "Generative App operation failed."}`);
863
863
  }
864
864
 
865
- const TELEGRAM_BIND_JSON_ARGUMENT_REFERENCE = "#/$defs/TelegramBindJsonValue";
866
- const TELEGRAM_BIND_JSON_ARGUMENT_SCHEMA = {
867
- $ref: TELEGRAM_BIND_JSON_ARGUMENT_REFERENCE,
868
- } as unknown as ReturnType<typeof Type.Unknown>;
869
- const TELEGRAM_BIND_JSON_ARGUMENT_DEFINITION = {
870
- anyOf: [
871
- { type: "null" },
872
- { type: "boolean" },
873
- { type: "number" },
874
- { type: "string" },
875
- { type: "array", items: { $ref: TELEGRAM_BIND_JSON_ARGUMENT_REFERENCE } },
876
- { type: "object", properties: {},
877
- additionalProperties: { $ref: TELEGRAM_BIND_JSON_ARGUMENT_REFERENCE } },
878
- ],
879
- };
865
+ // Provider tool APIs reject recursive $ref schemas (OpenAI) and raw TypeBox
866
+ // optional markers on non-builder objects (Gemini), while llama-server rejects
867
+ // unconstrained subschemas; one bounded builder-made JSON-value union satisfies all.
868
+ const TELEGRAM_BIND_JSON_ARGUMENT_MAX_DEPTH = 4;
869
+
870
+ function createTelegramBindJsonArgumentSchema(
871
+ depth: number,
872
+ ): ReturnType<typeof Type.Union> {
873
+ const scalars = [Type.Null(), Type.Boolean(), Type.Number(), Type.String()];
874
+ if (depth <= 0) return Type.Union(scalars);
875
+ const nested = createTelegramBindJsonArgumentSchema(depth - 1);
876
+ return Type.Union([
877
+ ...scalars,
878
+ Type.Array(nested),
879
+ Type.Object({}, { additionalProperties: nested }),
880
+ ]);
881
+ }
880
882
 
881
883
  export function registerTelegramBindTool(
882
884
  pi: ExtensionAPI,
@@ -893,10 +895,10 @@ export function registerTelegramBindTool(
893
895
  method: Type.Optional(Type.String()),
894
896
  replace: Type.Optional(Type.Boolean()),
895
897
  display: Type.Optional(Type.Boolean()),
896
- argument: Type.Optional(TELEGRAM_BIND_JSON_ARGUMENT_SCHEMA),
897
- }, { additionalProperties: false,
898
- $defs: { TelegramBindJsonValue: TELEGRAM_BIND_JSON_ARGUMENT_DEFINITION },
899
- }),
898
+ argument: Type.Optional(
899
+ createTelegramBindJsonArgumentSchema(TELEGRAM_BIND_JSON_ARGUMENT_MAX_DEPTH),
900
+ ),
901
+ }, { additionalProperties: false }),
900
902
  async execute(_toolCallId, params) {
901
903
  try {
902
904
  const result = await bindGenerativeApp({
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.45.1",
3
+ "version": "0.45.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-kit",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -44,8 +44,8 @@
44
44
  "@llblab/pi-clean-room": "0.1.1",
45
45
  "@llblab/pi-codex-usage": "0.9.4",
46
46
  "@llblab/pi-grow-loop": "0.7.5",
47
- "@llblab/pi-state-flow": "0.6.0",
48
- "@llblab/pi-telegram": "0.45.1",
47
+ "@llblab/pi-state-flow": "0.7.0",
48
+ "@llblab/pi-telegram": "0.45.2",
49
49
  "@llblab/skills": "1.15.0"
50
50
  },
51
51
  "bundledDependencies": [
@@ -1,27 +0,0 @@
1
- import { terminalRegenerationInstruction } from "./terminal.ts";
2
-
3
- export const MAX_VALIDATION_RETRIES = 3;
4
-
5
- export interface ValidationFeedback {
6
- attempt: number;
7
- error: string;
8
- instruction: string;
9
- }
10
-
11
- export type ValidationDecision =
12
- | { kind: "retry"; feedback: ValidationFeedback }
13
- | { kind: "exhausted"; error: string };
14
-
15
- /** Decide retry progression without mutating persistent runtime state. */
16
- export function nextValidation(previous: ValidationFeedback | undefined, error: string): ValidationDecision {
17
- const attempt = (previous?.attempt ?? 0) + 1;
18
- if (attempt > MAX_VALIDATION_RETRIES) return { kind: "exhausted", error };
19
- return {
20
- kind: "retry",
21
- feedback: {
22
- attempt,
23
- error,
24
- instruction: terminalRegenerationInstruction(error),
25
- },
26
- };
27
- }