@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.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +21 -21
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -122
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-state-flow/README.md +41 -35
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +36 -16
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +4 -4
- package/node_modules/@llblab/pi-state-flow/index.ts +23 -1
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +191 -29
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -1
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +42 -7
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +38 -8
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +1 -13
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +290 -134
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +32 -16
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +41 -0
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +12 -6
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +16 -0
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +167 -31
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +15 -6
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +40 -34
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +4 -9
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +25 -5
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +17 -147
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +49 -27
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +21 -19
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
- 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
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
52
|
-
|
|
52
|
+
output: output as ArtifactCompilerOutput,
|
|
53
|
+
});
|
|
54
|
+
validateArtifactMetadata(compiled.semantic, read.path);
|
|
53
55
|
Object.defineProperty(nextState.artifacts, read.path, {
|
|
54
|
-
value:
|
|
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
|
|
88
|
-
|
|
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
|
-
}
|
|
93
|
-
validateArtifactMetadata(
|
|
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:
|
|
98
|
+
value: compiled.semantic,
|
|
96
99
|
enumerable: true,
|
|
97
100
|
configurable: true,
|
|
98
101
|
writable: true,
|
|
99
102
|
});
|
|
100
|
-
|
|
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
|
-
|
|
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" &&
|
|
170
|
-
?
|
|
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("
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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.
|
|
@@ -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
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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(
|
|
897
|
-
|
|
898
|
-
|
|
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({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
48
|
-
"@llblab/pi-telegram": "0.45.
|
|
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
|
-
}
|