@llblab/pi-kit 0.37.1 → 0.38.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 +10 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +3 -3
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +1 -1
- package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +2 -2
- package/node_modules/@llblab/pi-claude-usage/package.json +1 -1
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/index.ts +39 -9
- package/node_modules/@llblab/pi-clean-room/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -3
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +679 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-state-flow/README.md +30 -14
- package/node_modules/@llblab/pi-state-flow/api/modules.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.js +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +77 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +26 -27
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +13 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +27 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +144 -46
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +14 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +60 -50
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1049 -310
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +15 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +7 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +186 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.d.ts +17 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.js +158 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.js +108 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/operation.js +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.js +84 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +6 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +33 -16
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +21 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +53 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +5 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +10 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +84 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +570 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.d.ts +10 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.js +32 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.d.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.js +187 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +98 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +398 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +7 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +55 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +67 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +129 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +26 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +61 -19
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +14 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +59 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +111 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +728 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +10 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +50 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +40 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +247 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +41 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +132 -39
- package/node_modules/@llblab/pi-state-flow/dist/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +12 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +9 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +76 -43
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +9 -1
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -0
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +23 -19
- package/node_modules/@llblab/pi-state-flow/docs/protocol-composition-decision.md +66 -0
- package/node_modules/@llblab/pi-state-flow/docs/public-module-contract.md +73 -0
- package/node_modules/@llblab/pi-state-flow/docs/scope-selection-contract.md +814 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-compatibility-decision.md +212 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-extensibility-decision.md +75 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-handoff-contract.md +174 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-memory-evaluation.md +166 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-module-contract.md +84 -0
- package/node_modules/@llblab/pi-state-flow/docs/single-scope-interface-contract.md +201 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +22 -1
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -18
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +68 -31
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +21 -22
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +14 -1
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +142 -46
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +14 -17
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +53 -44
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +897 -297
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -7
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +10 -9
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +189 -0
- package/node_modules/@llblab/pi-state-flow/lib/model-interface.ts +136 -0
- package/node_modules/@llblab/pi-state-flow/lib/module-registry.ts +119 -0
- package/node_modules/@llblab/pi-state-flow/lib/operation.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/ownership.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/planes.ts +98 -0
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +37 -16
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +59 -18
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +5 -2
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +10 -1
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +574 -21
- package/node_modules/@llblab/pi-state-flow/lib/selection.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/lib/semantic-module.ts +239 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +387 -3
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +45 -4
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +156 -16
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +69 -28
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +65 -31
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +770 -18
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +58 -20
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +267 -50
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +184 -52
- package/node_modules/@llblab/pi-state-flow/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +12 -8
- package/package.json +4 -4
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StateScope } from "./state.ts";
|
|
2
|
+
export type ScopeSelection = readonly [StateScope, ...StateScope[]];
|
|
3
|
+
/** Missing global configuration defaults to all owners; this is not a retained-session fallback. */
|
|
4
|
+
export declare const DEFAULT_SCOPE_SELECTION: ScopeSelection;
|
|
5
|
+
/** Validate an explicit value and detach it in canonical overlay order, without granting owner acquisition. */
|
|
6
|
+
export declare function captureScopeSelection(value: unknown): ScopeSelection;
|
|
7
|
+
/** Complete only the next unused owner; values replace the full argument prefix. */
|
|
8
|
+
export declare function scopeSelectionArgumentCompletions(prefix: string): string[];
|
|
9
|
+
/** Blank command arguments preserve policy; callers must not reinterpret them as all scopes. */
|
|
10
|
+
export declare function parseScopeSelectionArguments(args: string): ScopeSelection | undefined;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const SCOPE_ORDER = ["global", "cwd", "session"];
|
|
2
|
+
/** Missing global configuration defaults to all owners; this is not a retained-session fallback. */
|
|
3
|
+
export const DEFAULT_SCOPE_SELECTION = Object.freeze([...SCOPE_ORDER]);
|
|
4
|
+
/** Validate an explicit value and detach it in canonical overlay order, without granting owner acquisition. */
|
|
5
|
+
export function captureScopeSelection(value) {
|
|
6
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > SCOPE_ORDER.length) {
|
|
7
|
+
throw new Error("State Flow scopes must be a nonempty unique array of global, cwd, session");
|
|
8
|
+
}
|
|
9
|
+
const requested = new Set();
|
|
10
|
+
for (let index = 0; index < value.length; index++) {
|
|
11
|
+
const scope = value[index];
|
|
12
|
+
if (!Object.hasOwn(value, index) || !SCOPE_ORDER.some((allowed) => allowed === scope) || requested.has(scope)) {
|
|
13
|
+
throw new Error("State Flow scopes must be a nonempty unique array of global, cwd, session");
|
|
14
|
+
}
|
|
15
|
+
requested.add(scope);
|
|
16
|
+
}
|
|
17
|
+
return Object.freeze(SCOPE_ORDER.filter((scope) => requested.has(scope)));
|
|
18
|
+
}
|
|
19
|
+
/** Complete only the next unused owner; values replace the full argument prefix. */
|
|
20
|
+
export function scopeSelectionArgumentCompletions(prefix) {
|
|
21
|
+
const tokens = prefix.trimStart().split(/\s+/);
|
|
22
|
+
const partial = tokens.pop();
|
|
23
|
+
if (new Set(tokens).size !== tokens.length || tokens.some(token => !SCOPE_ORDER.some(scope => scope === token)))
|
|
24
|
+
return [];
|
|
25
|
+
return SCOPE_ORDER.filter(scope => !tokens.includes(scope) && scope.startsWith(partial))
|
|
26
|
+
.map(scope => [...tokens, scope].join(" "));
|
|
27
|
+
}
|
|
28
|
+
/** Blank command arguments preserve policy; callers must not reinterpret them as all scopes. */
|
|
29
|
+
export function parseScopeSelectionArguments(args) {
|
|
30
|
+
const tokens = args.trim();
|
|
31
|
+
return tokens.length === 0 ? undefined : captureScopeSelection(tokens.split(/\s+/));
|
|
32
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { type JsonObject, type JsonValue } from "./json.ts";
|
|
3
|
+
export type ModuleContextContribution = {
|
|
4
|
+
kind: "data";
|
|
5
|
+
value: JsonValue;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "conversation";
|
|
8
|
+
indices: readonly number[];
|
|
9
|
+
};
|
|
10
|
+
export interface ModuleFrame<Input> {
|
|
11
|
+
mode: "active" | "passive";
|
|
12
|
+
conversation: readonly AgentMessage[];
|
|
13
|
+
input: Input;
|
|
14
|
+
}
|
|
15
|
+
/** Internal trusted package code, not an effects sandbox or a stored-plane registration. */
|
|
16
|
+
export interface SemanticModule<Content extends JsonValue, Input = undefined> {
|
|
17
|
+
readonly immutable: boolean;
|
|
18
|
+
readonly id: string;
|
|
19
|
+
validate(value: unknown): value is Content;
|
|
20
|
+
context(content: Content | undefined, frame: ModuleFrame<Input>): readonly ModuleContextContribution[];
|
|
21
|
+
produce?(content: Content | undefined, input: Input, signal: AbortSignal): Content | undefined | Promise<Content | undefined>;
|
|
22
|
+
}
|
|
23
|
+
/** Public host DTOs: shared owners receive neither native conversation nor current prompt. */
|
|
24
|
+
export interface StateFlowModuleSource {
|
|
25
|
+
readonly mode: "active" | "passive";
|
|
26
|
+
readonly scope: "global" | "cwd" | "session";
|
|
27
|
+
readonly conversation: readonly AgentMessage[];
|
|
28
|
+
readonly prompt?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface StateFlowModuleFrame<Input extends JsonValue = JsonValue> extends StateFlowModuleSource {
|
|
31
|
+
readonly input: Input | undefined;
|
|
32
|
+
}
|
|
33
|
+
export interface StateFlowModuleInput<Input extends JsonValue = JsonValue> {
|
|
34
|
+
readonly value: Input;
|
|
35
|
+
/** Verify only the captured source; throw if obsolete. No acquisition or async work. */
|
|
36
|
+
assertCurrent(): void;
|
|
37
|
+
}
|
|
38
|
+
/** Candidate public v1 contract; durable activation requires matching retained metadata. */
|
|
39
|
+
export interface StateFlowModule<Content extends JsonObject = JsonObject, Input extends JsonValue = JsonValue> {
|
|
40
|
+
readonly immutable: boolean;
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly apiVersion: 1;
|
|
43
|
+
readonly schemaVersion: number;
|
|
44
|
+
validate(value: unknown): value is Content;
|
|
45
|
+
captureInput?(frame: StateFlowModuleSource, signal: AbortSignal): StateFlowModuleInput<Input> | Promise<StateFlowModuleInput<Input>>;
|
|
46
|
+
context(content: Content | undefined, frame: StateFlowModuleFrame<Input>): readonly ModuleContextContribution[];
|
|
47
|
+
produce?(content: Content | undefined, frame: StateFlowModuleFrame<Input>, signal: AbortSignal): Content | undefined | Promise<Content | undefined>;
|
|
48
|
+
}
|
|
49
|
+
/** Durable identity only: retained even while a definition is unavailable or content deleted. */
|
|
50
|
+
export interface RetainedModuleDescriptor {
|
|
51
|
+
schemaVersion: number;
|
|
52
|
+
immutable: boolean;
|
|
53
|
+
}
|
|
54
|
+
export type RetainedModules = Record<string, RetainedModuleDescriptor>;
|
|
55
|
+
export declare function validateRetainedModules(value: unknown): asserts value is RetainedModules;
|
|
56
|
+
/** Descriptors cannot be removed, recycled or implicitly change schema/policy. */
|
|
57
|
+
export declare function assertRetainedModules(previous: unknown, next: unknown): void;
|
|
58
|
+
export type EvaluatedModuleContext = {
|
|
59
|
+
kind: "data";
|
|
60
|
+
value: JsonValue;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "conversation";
|
|
63
|
+
messages: readonly AgentMessage[];
|
|
64
|
+
};
|
|
65
|
+
/** Raw durable planes use JSON object values, independent of any definition callback. */
|
|
66
|
+
export declare function validateModuleObject(value: unknown): asserts value is JsonObject;
|
|
67
|
+
/** Context consumes captured data; conversation selections retain the host's exact original messages. */
|
|
68
|
+
export declare function evaluateModuleContext<Content extends JsonValue, Input>(module: SemanticModule<Content, Input>, content: unknown, frame: ModuleFrame<Input>): readonly EvaluatedModuleContext[];
|
|
69
|
+
/** Isolated public input/context capture; grants neither native nor publication authority. */
|
|
70
|
+
export interface PreparedModuleContext {
|
|
71
|
+
assertCurrent(): void;
|
|
72
|
+
evaluate(accepted?: {
|
|
73
|
+
content: unknown;
|
|
74
|
+
}): readonly EvaluatedModuleContext[];
|
|
75
|
+
produce(): Promise<JsonObject | undefined>;
|
|
76
|
+
}
|
|
77
|
+
export declare function prepareModuleContext<Content extends JsonObject, Input extends JsonValue>(module: StateFlowModule<Content, Input>, content: unknown, source: StateFlowModuleSource, signal: AbortSignal, checkCurrent: () => void): Promise<PreparedModuleContext>;
|
|
78
|
+
/** Evaluate only: the host must separately bind the captured basis to its existing atomic acceptance. */
|
|
79
|
+
export declare function produceModuleContent<Content extends JsonValue, Input>(module: SemanticModule<Content, Input>, content: unknown, input: Input, signal: AbortSignal, checkCurrent: () => void): Promise<Content | undefined>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { isJsonValue, isObject } from "./json.js";
|
|
2
|
+
export function validateRetainedModules(value) {
|
|
3
|
+
if (!isObject(value) || !isJsonValue(value) || !losslessStoredJson(value))
|
|
4
|
+
throw new Error("Invalid State Flow retained modules");
|
|
5
|
+
for (const [id, descriptor] of Object.entries(value)) {
|
|
6
|
+
if (!/^x_[a-z][a-z0-9_]*$/.test(id) || !isObject(descriptor)
|
|
7
|
+
|| Object.keys(descriptor).sort().join(",") !== "immutable,schemaVersion"
|
|
8
|
+
|| !Number.isSafeInteger(descriptor.schemaVersion) || descriptor.schemaVersion <= 0
|
|
9
|
+
|| typeof descriptor.immutable !== "boolean")
|
|
10
|
+
throw new Error(`Invalid State Flow retained module descriptor: ${id}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Descriptors cannot be removed, recycled or implicitly change schema/policy. */
|
|
14
|
+
export function assertRetainedModules(previous, next) {
|
|
15
|
+
validateRetainedModules(previous);
|
|
16
|
+
validateRetainedModules(next);
|
|
17
|
+
for (const [id, descriptor] of Object.entries(previous)) {
|
|
18
|
+
if (!Object.hasOwn(next, id) || next[id].schemaVersion !== descriptor.schemaVersion
|
|
19
|
+
|| next[id].immutable !== descriptor.immutable)
|
|
20
|
+
throw new Error(`State Flow retained module descriptor changed: ${id}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function dense(value) {
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
for (let index = 0; index < value.length; index++)
|
|
26
|
+
if (!Object.hasOwn(value, index) || !dense(value[index]))
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
else if (isObject(value))
|
|
30
|
+
return Object.values(value).every(dense);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
function losslessStoredJson(value) {
|
|
34
|
+
if (value === null || typeof value !== "object")
|
|
35
|
+
return true;
|
|
36
|
+
const keys = Reflect.ownKeys(value);
|
|
37
|
+
if (Array.isArray(value) && keys.length !== value.length + 1)
|
|
38
|
+
return false;
|
|
39
|
+
return keys.every(key => {
|
|
40
|
+
if (Array.isArray(value) && key === "length")
|
|
41
|
+
return true;
|
|
42
|
+
const property = Object.getOwnPropertyDescriptor(value, key);
|
|
43
|
+
return typeof key === "string" && property.enumerable === true && "value" in property && losslessStoredJson(property.value);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Raw durable planes use JSON object values, independent of any definition callback. */
|
|
47
|
+
export function validateModuleObject(value) {
|
|
48
|
+
if (!isObject(value) || !isJsonValue(value) || !dense(value) || !losslessStoredJson(value))
|
|
49
|
+
throw new Error("State Flow external content must be a finite dense JSON object");
|
|
50
|
+
}
|
|
51
|
+
function capture(value) {
|
|
52
|
+
try {
|
|
53
|
+
return structuredClone(value);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new Error("Semantic module inputs must be detachable data");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function captureJson(value) {
|
|
60
|
+
if (!isJsonValue(value) || !dense(value))
|
|
61
|
+
throw new Error("Semantic module content must be finite dense JSON data");
|
|
62
|
+
const copy = capture(value);
|
|
63
|
+
if (!isJsonValue(copy) || !dense(copy))
|
|
64
|
+
throw new Error("Semantic module content must be finite dense JSON data");
|
|
65
|
+
return copy;
|
|
66
|
+
}
|
|
67
|
+
function contentFor(module, value) {
|
|
68
|
+
if (value === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
const copy = captureJson(value), validation = capture(copy);
|
|
71
|
+
if (module.validate(validation) !== true || !isJsonValue(validation) || !dense(validation))
|
|
72
|
+
throw new Error(`Invalid content for semantic module ${module.id}`);
|
|
73
|
+
// Validators neither transform the content nor retain the returned candidate.
|
|
74
|
+
return capture(copy);
|
|
75
|
+
}
|
|
76
|
+
/** Context consumes captured data; conversation selections retain the host's exact original messages. */
|
|
77
|
+
export function evaluateModuleContext(module, content, frame) {
|
|
78
|
+
if (frame.mode !== "active" && frame.mode !== "passive")
|
|
79
|
+
throw new Error("Semantic module context requires Active or Passive mode");
|
|
80
|
+
const original = [...frame.conversation];
|
|
81
|
+
const result = module.context(contentFor(module, content), { mode: frame.mode, conversation: capture(original), input: capture(frame.input) });
|
|
82
|
+
if (!Array.isArray(result))
|
|
83
|
+
throw new Error("Semantic module context must return a contribution array");
|
|
84
|
+
let last = -1;
|
|
85
|
+
return Array.from(result, contribution => {
|
|
86
|
+
if (!isObject(contribution))
|
|
87
|
+
throw new Error("Invalid semantic module context contribution");
|
|
88
|
+
const keys = Object.keys(contribution).sort().join(",");
|
|
89
|
+
if (contribution.kind === "data" && keys === "kind,value")
|
|
90
|
+
return { kind: "data", value: captureJson(contribution.value) };
|
|
91
|
+
if (contribution.kind !== "conversation" || keys !== "indices,kind" || !Array.isArray(contribution.indices)) {
|
|
92
|
+
throw new Error("Invalid semantic module context contribution");
|
|
93
|
+
}
|
|
94
|
+
const messages = Array.from(contribution.indices, index => {
|
|
95
|
+
if (typeof index !== "number" || !Number.isSafeInteger(index) || index <= last || index >= original.length)
|
|
96
|
+
throw new Error("Semantic module conversation indices must be ordered, unique and in range");
|
|
97
|
+
last = index;
|
|
98
|
+
const message = original[index];
|
|
99
|
+
if (!message || !["user", "assistant", "toolResult"].includes(message.role))
|
|
100
|
+
throw new Error("Semantic modules may select conversation messages, not protocol frames");
|
|
101
|
+
return message;
|
|
102
|
+
});
|
|
103
|
+
return { kind: "conversation", messages };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async function cancelableModuleWork(work, signal, checkCurrent) {
|
|
107
|
+
const check = () => { signal.throwIfAborted(); checkCurrent(); signal.throwIfAborted(); };
|
|
108
|
+
check();
|
|
109
|
+
let abort;
|
|
110
|
+
const canceled = new Promise((_resolve, reject) => { abort = () => reject(signal.reason); signal.addEventListener("abort", abort, { once: true }); });
|
|
111
|
+
try {
|
|
112
|
+
return await Promise.race([Promise.resolve().then(() => { check(); return work(); }).then(value => { check(); return value; }), canceled]);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
signal.removeEventListener("abort", abort);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export async function prepareModuleContext(module, content, source, signal, checkCurrent) {
|
|
119
|
+
const { mode, scope, prompt } = source;
|
|
120
|
+
if (mode !== "active" && mode !== "passive" || !["global", "cwd", "session"].includes(scope))
|
|
121
|
+
throw new Error("Invalid State Flow module source frame");
|
|
122
|
+
if (scope !== "session" && (source.conversation.length || prompt !== undefined))
|
|
123
|
+
throw new Error("Shared module frames cannot acquire Session conversation or prompt");
|
|
124
|
+
const conversation = [...source.conversation];
|
|
125
|
+
const frameSource = { mode, scope, conversation, ...(prompt === undefined ? {} : { prompt }) };
|
|
126
|
+
const adapter = { id: module.id, immutable: module.immutable,
|
|
127
|
+
validate: value => module.validate(value),
|
|
128
|
+
context: (value, frame) => module.context(value, { mode, scope, conversation: frame.conversation, input: frame.input, ...(prompt === undefined ? {} : { prompt }) }) };
|
|
129
|
+
const check = () => { signal.throwIfAborted(); checkCurrent(); signal.throwIfAborted(); };
|
|
130
|
+
check();
|
|
131
|
+
const previous = contentFor(adapter, content);
|
|
132
|
+
const receipt = module.captureInput ? await cancelableModuleWork(() => module.captureInput(capture(frameSource), signal), signal, check) : undefined;
|
|
133
|
+
if (module.captureInput && receipt === undefined || receipt !== undefined && (!isObject(receipt) || typeof receipt.assertCurrent !== "function" || !Object.hasOwn(receipt, "value"))) {
|
|
134
|
+
throw new Error("State Flow module input requires value and synchronous assertCurrent");
|
|
135
|
+
}
|
|
136
|
+
const currentInput = receipt?.assertCurrent.bind(receipt);
|
|
137
|
+
const assertCurrent = () => {
|
|
138
|
+
check();
|
|
139
|
+
const result = currentInput?.();
|
|
140
|
+
if (result !== undefined) {
|
|
141
|
+
if (result && typeof result.then === "function")
|
|
142
|
+
void Promise.resolve(result).catch(() => { });
|
|
143
|
+
throw new Error("State Flow module input assertion must synchronously throw or return void");
|
|
144
|
+
}
|
|
145
|
+
check();
|
|
146
|
+
};
|
|
147
|
+
assertCurrent();
|
|
148
|
+
const input = receipt === undefined ? undefined : captureJson(receipt.value);
|
|
149
|
+
assertCurrent();
|
|
150
|
+
return Object.freeze({ assertCurrent, produce: async () => {
|
|
151
|
+
assertCurrent();
|
|
152
|
+
if (mode !== "active")
|
|
153
|
+
throw new Error("State Flow module producers require Active mode");
|
|
154
|
+
if (!module.produce)
|
|
155
|
+
return undefined;
|
|
156
|
+
const producing = { id: module.id, immutable: module.immutable,
|
|
157
|
+
validate: (value) => isObject(value) && (Object.keys(value).length === 0 || module.validate(value)),
|
|
158
|
+
context: () => [], produce: (value, frame, activeSignal) => module.produce(value, frame, activeSignal) };
|
|
159
|
+
const candidate = await produceModuleContent(producing, previous, { ...frameSource, input }, signal, assertCurrent);
|
|
160
|
+
if (candidate !== undefined)
|
|
161
|
+
validateModuleObject(candidate);
|
|
162
|
+
assertCurrent();
|
|
163
|
+
return candidate;
|
|
164
|
+
}, evaluate: (accepted) => {
|
|
165
|
+
assertCurrent();
|
|
166
|
+
const result = evaluateModuleContext(adapter, accepted === undefined ? previous : accepted.content, { mode, conversation, input });
|
|
167
|
+
if (scope !== "session" && result.some(item => item.kind === "conversation"))
|
|
168
|
+
throw new Error("Shared modules cannot replace Session conversation");
|
|
169
|
+
assertCurrent();
|
|
170
|
+
return result;
|
|
171
|
+
} });
|
|
172
|
+
}
|
|
173
|
+
/** Evaluate only: the host must separately bind the captured basis to its existing atomic acceptance. */
|
|
174
|
+
export async function produceModuleContent(module, content, input, signal, checkCurrent) {
|
|
175
|
+
const check = () => { signal.throwIfAborted(); checkCurrent(); signal.throwIfAborted(); };
|
|
176
|
+
check();
|
|
177
|
+
const produce = module.produce?.bind(module);
|
|
178
|
+
if (!produce)
|
|
179
|
+
return undefined;
|
|
180
|
+
const previous = contentFor(module, content), captured = capture(input);
|
|
181
|
+
check();
|
|
182
|
+
return cancelableModuleWork(async () => {
|
|
183
|
+
const value = await produce(previous, captured, signal);
|
|
184
|
+
check();
|
|
185
|
+
return contentFor(module, value);
|
|
186
|
+
}, signal, check);
|
|
187
|
+
}
|
|
@@ -1,5 +1,59 @@
|
|
|
1
|
-
import { type InactiveMode, type StateFlowMode } from "./snapshot.ts";
|
|
1
|
+
import { type InactiveMode, type StateFlowMode, type RetainedPolicyMigrationCandidate } from "./snapshot.ts";
|
|
2
|
+
import { type ScopeSelection } from "./selection.ts";
|
|
2
3
|
export declare const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
|
|
4
|
+
/** Requested native policy, not proof of accepted memory or acquisition permission. */
|
|
5
|
+
export interface SelectedNativeRequestPolicy {
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly owner: string;
|
|
8
|
+
readonly mode: StateFlowMode;
|
|
9
|
+
readonly scopes: ScopeSelection;
|
|
10
|
+
}
|
|
11
|
+
/** Cancelable pre-provider turn, including Off bookkeeping; not memory/acquisition authority. */
|
|
12
|
+
export type NativeContextRequest = Pick<SelectedPolicyRequest, "signal" | "revalidate">;
|
|
13
|
+
export interface SelectedPolicyRequest {
|
|
14
|
+
readonly policy: SelectedNativeRequestPolicy;
|
|
15
|
+
readonly signal: AbortSignal;
|
|
16
|
+
/** Synchronous in-memory recheck, suitable for the selected runtime's native-policy port. */
|
|
17
|
+
revalidate(): void;
|
|
18
|
+
}
|
|
19
|
+
/** Owns policy-bound cancellation only; no storage, attachment, source acquisition or slot registry. */
|
|
20
|
+
export declare class SelectedPolicyLifetime {
|
|
21
|
+
#private;
|
|
22
|
+
get policy(): SelectedNativeRequestPolicy | undefined;
|
|
23
|
+
/** Cancellation only, including Off; unlike capture(), this grants no enabled memory receipt. */
|
|
24
|
+
get signal(): AbortSignal;
|
|
25
|
+
/** Same captured policy preserves independent owners; a real choice revokes all old requests. */
|
|
26
|
+
select(value: SelectedNativeRequestPolicy): void;
|
|
27
|
+
/** Capture at admission, before any source acquisition, asynchronous wait or queued work. */
|
|
28
|
+
capture(): SelectedPolicyRequest;
|
|
29
|
+
/** Same-policy branch movement/Abort may revoke requests without inventing a different selection. */
|
|
30
|
+
invalidate(): void;
|
|
31
|
+
/** Shutdown cannot be reopened by a late selection or abort listener. */
|
|
32
|
+
end(): void;
|
|
33
|
+
}
|
|
34
|
+
/** One SDK turn and its finalized assistant batch; IDs are evidence only within this owned turn. */
|
|
35
|
+
export declare class NativeToolRequestCorrelation {
|
|
36
|
+
#private;
|
|
37
|
+
begin(request: SelectedPolicyRequest | undefined, signal: AbortSignal | undefined, currentIdentity: () => boolean, controlSignal?: AbortSignal): void;
|
|
38
|
+
/** Stable lease from turn_start through provider preparation, before assistant finalization. */
|
|
39
|
+
preparing(): NativeContextRequest;
|
|
40
|
+
/** Provider-only revalidation of captured context data; callbacks must neither acquire nor recurse into this request. */
|
|
41
|
+
bindPreparedContext(check: () => void): void;
|
|
42
|
+
finalize(calls: readonly {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
}[]): void;
|
|
46
|
+
get active(): boolean;
|
|
47
|
+
/** Identify the ambient model owner; deciding which Abort cause to forward belongs to the native port. */
|
|
48
|
+
ownsOperation(signal: AbortSignal | undefined): boolean;
|
|
49
|
+
has(id: string): boolean;
|
|
50
|
+
current(): SelectedPolicyRequest;
|
|
51
|
+
check(id: string, name: string): SelectedPolicyRequest;
|
|
52
|
+
/** Only explicit SDK parent evidence, never an ID prefix or branch-history guess. */
|
|
53
|
+
nested(id: string, name: string, parent: string): void;
|
|
54
|
+
take(id: string, name: string): SelectedPolicyRequest;
|
|
55
|
+
end(): void;
|
|
56
|
+
}
|
|
3
57
|
interface BranchEntry {
|
|
4
58
|
type?: unknown;
|
|
5
59
|
customType?: unknown;
|
|
@@ -40,6 +94,49 @@ export declare function findBranchPolicy(branch: readonly BranchEntry[], session
|
|
|
40
94
|
mode: StateFlowMode;
|
|
41
95
|
persistenceError?: string;
|
|
42
96
|
} | undefined;
|
|
97
|
+
/** Checkpoint-only preflight: no Stop/fork precedence, installation or authority. Invalid owned data cannot fall back. */
|
|
98
|
+
export declare function prepareBranchCheckpointPolicy(branch: readonly BranchEntry[], legacySelection: ScopeSelection, inactiveMode?: InactiveMode): RetainedPolicyMigrationCandidate | undefined;
|
|
99
|
+
/** Native-only policy/bookkeeping. This marker never grants canonical memory or parent-copy authority. */
|
|
100
|
+
export interface SelectedNativePolicyMarker {
|
|
101
|
+
version: 2;
|
|
102
|
+
owner: string;
|
|
103
|
+
mode: StateFlowMode;
|
|
104
|
+
scopes: ScopeSelection;
|
|
105
|
+
reset?: true;
|
|
106
|
+
forkPending?: true;
|
|
107
|
+
at?: number;
|
|
108
|
+
from?: number;
|
|
109
|
+
preserveContext?: true;
|
|
110
|
+
persistenceError?: string;
|
|
111
|
+
memoryDeferred?: true;
|
|
112
|
+
}
|
|
113
|
+
/** Closed v2 writer value. Legacy records are translated only in branch context, never given a fabricated owner. */
|
|
114
|
+
export declare function captureSelectedNativeMarker(value: unknown): SelectedNativePolicyMarker;
|
|
115
|
+
export interface SelectedBranchPolicyOptions {
|
|
116
|
+
owner: string;
|
|
117
|
+
markerEntryType: string;
|
|
118
|
+
/** The caller chooses new-session versus inactive fallback mode; absence itself grants no acquisition. */
|
|
119
|
+
defaults: {
|
|
120
|
+
mode: StateFlowMode;
|
|
121
|
+
scopes: ScopeSelection;
|
|
122
|
+
};
|
|
123
|
+
inactiveMode?: InactiveMode;
|
|
124
|
+
/** Native fresh-fork fact only; no parent header or store lookup belongs in this resolver. */
|
|
125
|
+
fork?: boolean;
|
|
126
|
+
}
|
|
127
|
+
export interface SelectedBranchPolicyCandidate {
|
|
128
|
+
mode: StateFlowMode;
|
|
129
|
+
scopes: ScopeSelection;
|
|
130
|
+
source: "default" | "checkpoint" | "marker";
|
|
131
|
+
forkPending: boolean;
|
|
132
|
+
stop?: PassiveStopBoundary;
|
|
133
|
+
persistenceError?: string;
|
|
134
|
+
requiresCapture: boolean;
|
|
135
|
+
/** Policy-only capture preserves fences; only a separately owned acceptance may append reset/checkpoint. */
|
|
136
|
+
capture: SelectedNativePolicyMarker;
|
|
137
|
+
}
|
|
138
|
+
/** Complete native branch admission only; never read canonical/parent files, attach policy or confer memory authority. */
|
|
139
|
+
export declare function prepareSelectedBranchPolicy(branch: readonly BranchEntry[], options: SelectedBranchPolicyOptions): SelectedBranchPolicyCandidate;
|
|
43
140
|
/** Native policy bookkeeping retains an unacquired fork across extension reloads. */
|
|
44
141
|
export declare function hasPendingFork(branch: readonly BranchEntry[], sessionId: string, entryType: string): boolean;
|
|
45
142
|
export declare function snapshotDataNewestFirst(branch: readonly BranchEntry[]): unknown[];
|