@llblab/pi-kit 0.18.2 → 0.19.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 +11 -0
- package/README.md +3 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +23 -1
- package/node_modules/@llblab/pi-state-flow/README.md +102 -48
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
- package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +160 -255
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
- package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
- package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +155 -254
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
- package/node_modules/@llblab/pi-state-flow/package.json +9 -6
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
- package/package.json +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
export { ArtifactReadTracker, decideArtifactAcquisition, type ArtifactAcquisitionDecision, type ArtifactAcquisitionIntent, type ArtifactAcquisitionOptions, type ArtifactAcquisitionReason, type SuccessfulArtifactRead } from "./lib/acquisition.ts";
|
|
2
|
-
export {
|
|
2
|
+
export { classifyArtifactCompilationNeed, compileArtifact, hashArtifactSource, isArtifactHash, isArtifactMetadata, isArtifactRegistry, ORDINARY_ARTIFACT_COMPILER, parseArtifactProvenanceRegistry, projectArtifactForModel, projectArtifactsForModel, pruneArtifactProvenance, selectArtifactsByTags, serializeArtifactProvenanceRegistry, updateArtifactProvenance, updateArtifactRegistry, validateArtifactMetadata, validateArtifactRegistry, type ArtifactCompilationUpdate, type ArtifactCompilerOutput, type ArtifactCompilationNeed, type ArtifactInvalidationNotice, type ArtifactInvalidationReason, type ArtifactInvalidationRequest, type ArtifactMetadata, type ArtifactProvenance, type ArtifactProvenanceRegistry, type ArtifactRegistry, type ArtifactSourceIdentity, type CompiledArtifact } from "./lib/artifact.ts";
|
|
3
3
|
export { buildContinuationCandidates, discoverNativeSessionHeaders, inspectStateFlowContinuationProvenance, readNativeSessionHeader, recommendContinuationFromProvenance, resolveContinuationStartup, type ContinuationCandidateProvenance, type ContinuationCandidateSummary, type ContinuationHostContext, type ContinuationHostIntent, type ContinuationProjectIdentity, type ContinuationRecommendation, type ContinuationRecommender, type ContinuationSessionCandidate, type ContinuationStartupDecision, type ContinuationTransport, type NativeSessionHeader } from "./lib/continuation.ts";
|
|
4
|
-
export { discoverGlobalMarkdownSources, getKnowledgeRoot, GlobalMarkdownDiscovery, type ArtifactSourceCandidate, type GlobalMarkdownDiscoveryResult } from "./lib/discovery.ts";
|
|
5
4
|
export { captureTemporalFileBases, cwdScopeKey, getDurableRepositoryRoot, isStateFlowOwnedPath, loadScopeStream, parseScopeProvenance, parseScopeStream, serializeScopeProvenance, serializeScopeStream, sessionRuntimePaths, sessionScopeKey, sessionStorageKey, temporalScopePaths, temporalStateFileUpdates, type ScopeStreamSources, type TemporalScopePaths } from "./lib/durable.ts";
|
|
6
|
-
export { default,
|
|
7
|
-
export {
|
|
8
|
-
export { createAcceptedTransition,
|
|
5
|
+
export { default, PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME, type StateFlowExtensionOptions } from "./lib/extension.ts";
|
|
6
|
+
export { backupCurrentStateFlowFiles } from "./lib/git.ts";
|
|
7
|
+
export { createAcceptedTransition, DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT, projectRecentTransitionsWithLimit, validateRecentTransition, type AcceptedTransition, type RecentScopePatch, type RecentTransition, type RecentTransitionWindow } from "./lib/history.ts";
|
|
9
8
|
export { applyPatch, canonicalJson, hashJson, isObject, validatePatch } from "./lib/json.ts";
|
|
10
9
|
export type { JsonObject, JsonValue } from "./lib/json.ts";
|
|
11
10
|
export { appendStateFlowDiagnostic, projectDiagnosticContent, stateFlowLogPath, type StateFlowDiagnosticBlock, type StateFlowDiagnosticCategory, type StateFlowDiagnosticRecord } from "./lib/logging.ts";
|
|
12
|
-
export {
|
|
13
|
-
export { inspectMemoryPromotions, MEMORY_PROMOTION_STATUSES, MEMORY_PROMOTIONS_KEY, retainedMemoryScopes, type MemoryPromotionDiagnostic, type MemoryPromotionStatus } from "./lib/memory.ts";
|
|
14
|
-
export { acquirePublicationWorkerLease, beginPublicationAttempt, coalescePublicationTarget, confirmPublicationTarget, createPublicationQueue, failPublicationAttempt, loadPublicationQueue, parsePublicationQueue, parseRemotePublicationPolicyDocument, publicationQueuePath, recoverPublicationQueue, remotePublicationDestinationKey, removePublicationQueue, resolveRemotePublicationPolicy, runPublicationWorker, savePublicationQueue, serializePublicationQueue, serializeRemotePublicationPolicyDocument, validatePublicationQueue, type CommitAncestor, type PublicationPush, type PublicationQueueReceipt, type PublicationQueueState, type PublicationQueueStatus, type PublicationWorkerLease, type PublicationWorkerResult, type RemotePublicationDestination, type RemotePublicationMode, type RemotePublicationPolicy, type RemotePublicationPolicyDocument } from "./lib/publication.ts";
|
|
11
|
+
export { retainedMemoryScopes } from "./lib/memory.ts";
|
|
15
12
|
export { planKnowledgeRehydration, type RehydrationOptions, type RehydrationPhase, type RehydrationPlan, type RehydrationRead, type RehydrationRoute } from "./lib/rehydration.ts";
|
|
16
13
|
export { TemporalRuntime } from "./lib/runtime.ts";
|
|
17
|
-
export { hasCompiledSkillArtifact, hashSkillSource,
|
|
14
|
+
export { hasCompiledSkillArtifact, hashSkillSource, SKILL_ARTIFACT_COMPILER, type SuccessfulSkillRead } from "./lib/skills.ts";
|
|
18
15
|
export { emptyState, isMaterializedState, isStateDocument, overlayStates, projectStateForModel, updateMaterializedArtifacts, type AtomicScopePatches, type MaterializedState, type ScopedPatch, type ScopedStates, type ScopePatch, type SemanticTransition, type StateDocument, type StatePatch, type StateScope, type TerminalTransition } from "./lib/state.ts";
|
|
19
16
|
export { buildStateFlowSectionView, createStateFlowTelegramAdapter, formatStateFlowSectionLabel, loadStateFlowTelegramModules, STATE_FLOW_TELEGRAM_ID, type StateFlowTelegramAdapter, type StateFlowTelegramButton, type StateFlowTelegramCallbackContext, type StateFlowTelegramControlResult, type StateFlowTelegramLoader, type StateFlowTelegramModules, type StateFlowTelegramPort, type StateFlowTelegramSectionContext, type StateFlowTelegramSectionModule, type StateFlowTelegramSnapshot, type StateFlowTelegramView } from "./lib/telegram.ts";
|
|
20
17
|
export { advanceTemporalState, readTemporalState, type TemporalState } from "./lib/temporal.ts";
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
export { ArtifactReadTracker, decideArtifactAcquisition } from "./lib/acquisition.js";
|
|
2
|
-
export {
|
|
2
|
+
export { classifyArtifactCompilationNeed, compileArtifact, hashArtifactSource, isArtifactHash, isArtifactMetadata, isArtifactRegistry, ORDINARY_ARTIFACT_COMPILER, parseArtifactProvenanceRegistry, projectArtifactForModel, projectArtifactsForModel, pruneArtifactProvenance, selectArtifactsByTags, serializeArtifactProvenanceRegistry, updateArtifactProvenance, updateArtifactRegistry, validateArtifactMetadata, validateArtifactRegistry } from "./lib/artifact.js";
|
|
3
3
|
export { buildContinuationCandidates, discoverNativeSessionHeaders, inspectStateFlowContinuationProvenance, readNativeSessionHeader, recommendContinuationFromProvenance, resolveContinuationStartup } from "./lib/continuation.js";
|
|
4
|
-
export { discoverGlobalMarkdownSources, getKnowledgeRoot, GlobalMarkdownDiscovery } from "./lib/discovery.js";
|
|
5
4
|
export { captureTemporalFileBases, cwdScopeKey, getDurableRepositoryRoot, isStateFlowOwnedPath, loadScopeStream, parseScopeProvenance, parseScopeStream, serializeScopeProvenance, serializeScopeStream, sessionRuntimePaths, sessionScopeKey, sessionStorageKey, temporalScopePaths, temporalStateFileUpdates } from "./lib/durable.js";
|
|
6
|
-
export { default,
|
|
7
|
-
export {
|
|
8
|
-
export { createAcceptedTransition,
|
|
5
|
+
export { default, PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME } from "./lib/extension.js";
|
|
6
|
+
export { backupCurrentStateFlowFiles } from "./lib/git.js";
|
|
7
|
+
export { createAcceptedTransition, DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT, projectRecentTransitionsWithLimit, validateRecentTransition } from "./lib/history.js";
|
|
9
8
|
export { applyPatch, canonicalJson, hashJson, isObject, validatePatch } from "./lib/json.js";
|
|
10
9
|
export { appendStateFlowDiagnostic, projectDiagnosticContent, stateFlowLogPath } from "./lib/logging.js";
|
|
11
|
-
export {
|
|
12
|
-
export { inspectMemoryPromotions, MEMORY_PROMOTION_STATUSES, MEMORY_PROMOTIONS_KEY, retainedMemoryScopes } from "./lib/memory.js";
|
|
13
|
-
export { acquirePublicationWorkerLease, beginPublicationAttempt, coalescePublicationTarget, confirmPublicationTarget, createPublicationQueue, failPublicationAttempt, loadPublicationQueue, parsePublicationQueue, parseRemotePublicationPolicyDocument, publicationQueuePath, recoverPublicationQueue, remotePublicationDestinationKey, removePublicationQueue, resolveRemotePublicationPolicy, runPublicationWorker, savePublicationQueue, serializePublicationQueue, serializeRemotePublicationPolicyDocument, validatePublicationQueue } from "./lib/publication.js";
|
|
10
|
+
export { retainedMemoryScopes } from "./lib/memory.js";
|
|
14
11
|
export { planKnowledgeRehydration } from "./lib/rehydration.js";
|
|
15
12
|
export { TemporalRuntime } from "./lib/runtime.js";
|
|
16
|
-
export { hasCompiledSkillArtifact, hashSkillSource,
|
|
13
|
+
export { hasCompiledSkillArtifact, hashSkillSource, SKILL_ARTIFACT_COMPILER } from "./lib/skills.js";
|
|
17
14
|
export { emptyState, isMaterializedState, isStateDocument, overlayStates, projectStateForModel, updateMaterializedArtifacts } from "./lib/state.js";
|
|
18
15
|
export { buildStateFlowSectionView, createStateFlowTelegramAdapter, formatStateFlowSectionLabel, loadStateFlowTelegramModules, STATE_FLOW_TELEGRAM_ID } from "./lib/telegram.js";
|
|
19
16
|
export { advanceTemporalState, readTemporalState } from "./lib/temporal.js";
|
|
@@ -14,7 +14,7 @@ export interface ArtifactAcquisitionOptions {
|
|
|
14
14
|
/** Caller-assessed semantic sufficiency; only relevant to a concrete relevant gap. */
|
|
15
15
|
materializedSufficient?: boolean;
|
|
16
16
|
explicitRefresh?: boolean;
|
|
17
|
-
/** Runtime-owned
|
|
17
|
+
/** Runtime-owned compilation evidence retained beside the semantic artifact. */
|
|
18
18
|
provenance?: unknown;
|
|
19
19
|
}
|
|
20
20
|
/** A successful read correlated to a runtime-observed invalidation candidate. */
|
|
@@ -33,7 +33,7 @@ export declare class ArtifactReadTracker {
|
|
|
33
33
|
/**
|
|
34
34
|
* Apply one materialized-first source acquisition policy.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
36
|
+
* Required recompilation always wins. Otherwise routine use and a new session
|
|
37
37
|
* stay on materialized state; only a concrete source need permits rereading.
|
|
38
38
|
*/
|
|
39
39
|
export declare function decideArtifactAcquisition(source: ArtifactSourceIdentity, metadata: unknown, compiler: string, options: ArtifactAcquisitionOptions): ArtifactAcquisitionDecision;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { classifyArtifactCompilationNeed, inspectRegisteredArtifactPaths, sameArtifactSourceFingerprint, } from "./artifact.js";
|
|
2
2
|
import { isObject } from "./json.js";
|
|
3
3
|
function readPath(toolName, args) {
|
|
4
4
|
return toolName === "read" && isObject(args) && typeof args.path === "string"
|
|
@@ -34,27 +34,40 @@ export class ArtifactReadTracker {
|
|
|
34
34
|
if (path === undefined)
|
|
35
35
|
return;
|
|
36
36
|
const candidate = this.#candidates.get(path);
|
|
37
|
-
if (candidate
|
|
38
|
-
|
|
37
|
+
if (candidate === undefined || pending.fingerprint === undefined)
|
|
38
|
+
return;
|
|
39
|
+
const observation = inspectRegisteredArtifactPaths([path])[0];
|
|
40
|
+
if (observation?.kind !== "present" || !sameArtifactSourceFingerprint(pending.fingerprint, observation.fingerprint))
|
|
41
|
+
return;
|
|
42
|
+
const finalObservation = inspectRegisteredArtifactPaths([path])[0];
|
|
43
|
+
if (finalObservation?.kind !== "present" || !sameArtifactSourceFingerprint(observation.fingerprint, finalObservation.fingerprint))
|
|
44
|
+
return;
|
|
45
|
+
this.successful.set(path, structuredClone({ ...candidate, sourceFingerprint: finalObservation.fingerprint }));
|
|
39
46
|
}
|
|
40
47
|
#record(toolCallId, toolName, args) {
|
|
41
48
|
if (toolName !== "read") {
|
|
42
49
|
this.#pending.delete(toolCallId);
|
|
43
50
|
return;
|
|
44
51
|
}
|
|
45
|
-
|
|
52
|
+
const path = readPath(toolName, args);
|
|
53
|
+
const observation = path !== undefined && this.#candidates.has(path) ? inspectRegisteredArtifactPaths([path])[0] : undefined;
|
|
54
|
+
this.#pending.set(toolCallId, {
|
|
55
|
+
toolName,
|
|
56
|
+
args,
|
|
57
|
+
...(observation?.kind === "present" ? { fingerprint: observation.fingerprint } : {}),
|
|
58
|
+
});
|
|
46
59
|
}
|
|
47
60
|
}
|
|
48
61
|
/**
|
|
49
62
|
* Apply one materialized-first source acquisition policy.
|
|
50
63
|
*
|
|
51
|
-
*
|
|
64
|
+
* Required recompilation always wins. Otherwise routine use and a new session
|
|
52
65
|
* stay on materialized state; only a concrete source need permits rereading.
|
|
53
66
|
*/
|
|
54
67
|
export function decideArtifactAcquisition(source, metadata, compiler, options) {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
return { kind: "read-source", reason:
|
|
68
|
+
const need = classifyArtifactCompilationNeed(source, metadata, compiler, options.explicitRefresh ?? false, options.provenance);
|
|
69
|
+
if (need.kind === "requires-compilation") {
|
|
70
|
+
return { kind: "read-source", reason: need.reason };
|
|
58
71
|
}
|
|
59
72
|
switch (options.intent) {
|
|
60
73
|
case "routine":
|
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
import { type JsonObject } from "./json.ts";
|
|
2
|
-
|
|
2
|
+
export interface ArtifactSourceFingerprint {
|
|
3
|
+
size: number;
|
|
4
|
+
mtimeNs: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseArtifactSourceFingerprint(value: unknown): ArtifactSourceFingerprint | undefined;
|
|
7
|
+
export declare function sameArtifactSourceFingerprint(left: ArtifactSourceFingerprint, right: ArtifactSourceFingerprint): boolean;
|
|
8
|
+
export type ArtifactSourceObservation = {
|
|
9
|
+
path: string;
|
|
10
|
+
kind: "present";
|
|
11
|
+
fingerprint: ArtifactSourceFingerprint;
|
|
12
|
+
} | {
|
|
13
|
+
path: string;
|
|
14
|
+
kind: "missing";
|
|
15
|
+
} | {
|
|
16
|
+
path: string;
|
|
17
|
+
kind: "unavailable";
|
|
18
|
+
reason: string;
|
|
19
|
+
};
|
|
20
|
+
/** Inspect only exact registered artifact paths without reading source bodies or traversing directories. */
|
|
21
|
+
export declare function inspectRegisteredArtifactPaths(paths: Iterable<string>): ArtifactSourceObservation[];
|
|
22
|
+
/** Current compiler protocol for ordinary registered source artifacts. */
|
|
3
23
|
export declare const ORDINARY_ARTIFACT_COMPILER = "artifact-v1";
|
|
4
|
-
/**
|
|
24
|
+
/** Compilation evidence that runtime-owned scope metadata may retain per artifact path. */
|
|
5
25
|
export interface ArtifactProvenance {
|
|
6
26
|
/** Retained value; absent means unavailable, malformed means fail closed. */
|
|
7
27
|
sourceHash?: unknown;
|
|
28
|
+
sourceFingerprint?: unknown;
|
|
8
29
|
compilerRevision?: unknown;
|
|
9
30
|
compiledAt?: unknown;
|
|
10
31
|
/** Internal marker for an uninterpretable retained entry; every dependent capability fails closed. */
|
|
@@ -17,7 +38,7 @@ export type ArtifactProvenanceRegistry = Record<string, ArtifactProvenance>;
|
|
|
17
38
|
* `hash`, `compiler`, and `compiled_at` remain accepted only as retired embedded
|
|
18
39
|
* provenance input from pre-0.7 state; they are consumed as compatibility evidence
|
|
19
40
|
* and stripped from model projection. New compilations keep semantic fields here and
|
|
20
|
-
* runtime-owned
|
|
41
|
+
* runtime-owned compilation evidence in the scope `meta.json` provenance registry.
|
|
21
42
|
*/
|
|
22
43
|
export type ArtifactMetadata = JsonObject & {
|
|
23
44
|
description: string;
|
|
@@ -30,14 +51,20 @@ export type ArtifactMetadata = JsonObject & {
|
|
|
30
51
|
};
|
|
31
52
|
/** Artifact source paths are the canonical registry keys. */
|
|
32
53
|
export type ArtifactRegistry = JsonObject & Record<string, ArtifactMetadata>;
|
|
33
|
-
/** Source identity is sufficient for
|
|
54
|
+
/** Source identity is sufficient for compilation decisions; it never contains the source body. */
|
|
55
|
+
export type ArtifactScope = "global" | "cwd" | "session";
|
|
34
56
|
export interface ArtifactSourceIdentity {
|
|
35
57
|
path: string;
|
|
36
|
-
|
|
58
|
+
/** Exact semantic owner selected from the effective global → CWD → session overlay. */
|
|
59
|
+
scope?: ArtifactScope;
|
|
60
|
+
/** Transitional legacy content identity; generic maintenance uses the filesystem fingerprint instead. */
|
|
61
|
+
hash?: string;
|
|
62
|
+
/** Stable filesystem evidence observed across the successful source read. */
|
|
63
|
+
sourceFingerprint?: ArtifactSourceFingerprint;
|
|
37
64
|
}
|
|
38
65
|
export type ArtifactInvalidationReason = "new" | "source-changed" | "compiler-changed" | "invalid-metadata" | "explicit-refresh";
|
|
39
|
-
export type
|
|
40
|
-
kind: "
|
|
66
|
+
export type ArtifactCompilationNeed = {
|
|
67
|
+
kind: "current";
|
|
41
68
|
} | {
|
|
42
69
|
kind: "requires-compilation";
|
|
43
70
|
reason: ArtifactInvalidationReason;
|
|
@@ -48,19 +75,9 @@ export interface ArtifactInvalidationRequest extends ArtifactSourceIdentity {
|
|
|
48
75
|
/** Model-visible invalidation projection: paths and reasons only, never source identity. */
|
|
49
76
|
export interface ArtifactInvalidationNotice {
|
|
50
77
|
path: string;
|
|
78
|
+
scope?: ArtifactScope;
|
|
51
79
|
reason: ArtifactInvalidationReason;
|
|
52
80
|
}
|
|
53
|
-
export interface ArtifactInvalidationPlan {
|
|
54
|
-
fresh: ArtifactSourceIdentity[];
|
|
55
|
-
requiresCompilation: ArtifactInvalidationRequest[];
|
|
56
|
-
removed: string[];
|
|
57
|
-
}
|
|
58
|
-
export interface ArtifactInvalidationOptions {
|
|
59
|
-
/** Refresh every source, or only paths in the supplied set. */
|
|
60
|
-
explicitRefresh?: boolean | ReadonlySet<string>;
|
|
61
|
-
/** Explicit absence evidence supplied by the source owner, not inferred from a partial candidate set. */
|
|
62
|
-
removed?: readonly string[];
|
|
63
|
-
}
|
|
64
81
|
/** Trusted compilation input; embedded timestamps are accepted here, but never in model patches. */
|
|
65
82
|
export type ArtifactCompilerOutput = JsonObject & {
|
|
66
83
|
description: string;
|
|
@@ -90,10 +107,8 @@ export declare function isArtifactRegistry(value: unknown): value is ArtifactReg
|
|
|
90
107
|
export declare function parseArtifactProvenanceRegistry(value: unknown, context?: string): ArtifactProvenanceRegistry;
|
|
91
108
|
/** Canonical retained form; uninterpretable entries cannot round-trip and are omitted. */
|
|
92
109
|
export declare function serializeArtifactProvenanceRegistry(registry: Readonly<ArtifactProvenanceRegistry>): JsonObject;
|
|
93
|
-
/**
|
|
94
|
-
export declare function
|
|
95
|
-
/** Produce a deterministic acquisition plan from path/hash candidates and retained evidence. */
|
|
96
|
-
export declare function planArtifactInvalidation(sources: readonly ArtifactSourceIdentity[], registry: Readonly<Record<string, unknown>>, compiler: string, options?: ArtifactInvalidationOptions, provenance?: Readonly<ArtifactProvenanceRegistry>): ArtifactInvalidationPlan;
|
|
110
|
+
/** Decide whether retained compilation evidence requires source reacquisition. */
|
|
111
|
+
export declare function classifyArtifactCompilationNeed(source: ArtifactSourceIdentity, metadata: unknown, compiler: string, explicitRefresh?: boolean, provenance?: unknown): ArtifactCompilationNeed;
|
|
97
112
|
/** Split one compiler output into model-visible semantics and runtime-owned provenance. */
|
|
98
113
|
export declare function compileArtifact(update: ArtifactCompilationUpdate): CompiledArtifact;
|
|
99
114
|
/** Merge one compilation/removal cohort into the runtime-owned provenance registry. */
|
|
@@ -106,5 +121,6 @@ export declare function updateArtifactRegistry(registry: ArtifactRegistry, updat
|
|
|
106
121
|
export declare function validateModelArtifactPatch(patch: JsonObject): void;
|
|
107
122
|
/** Strip retained runtime bookkeeping from one model-visible artifact entry. */
|
|
108
123
|
export declare function projectArtifactForModel(entry: unknown): unknown;
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
export type ArtifactModelHints = Readonly<Record<string, string | readonly string[]>>;
|
|
125
|
+
/** Strip retained runtime bookkeeping and add deterministic runtime-only guidance. */
|
|
126
|
+
export declare function projectArtifactsForModel(registry: Readonly<ArtifactRegistry>, hints?: ArtifactModelHints): ArtifactRegistry;
|
|
@@ -1,8 +1,57 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstatSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, resolve } from "node:path";
|
|
2
4
|
import { containsNull, isJsonValue, isObject } from "./json.js";
|
|
3
5
|
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
4
|
-
const MODEL_FORBIDDEN_PROVENANCE_FIELDS = ["hash", "compiler", "sourceHash", "compilerRevision", "compiledAt", "source_hash_verified"];
|
|
5
|
-
|
|
6
|
+
const MODEL_FORBIDDEN_PROVENANCE_FIELDS = ["hash", "compiler", "sourceHash", "sourceFingerprint", "compilerRevision", "compiledAt", "source_hash_verified"];
|
|
7
|
+
export function parseArtifactSourceFingerprint(value) {
|
|
8
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
9
|
+
return undefined;
|
|
10
|
+
const candidate = value;
|
|
11
|
+
if (Object.keys(candidate).sort().join(",") !== "mtimeNs,size"
|
|
12
|
+
|| !Number.isSafeInteger(candidate.size) || candidate.size < 0
|
|
13
|
+
|| typeof candidate.mtimeNs !== "string" || !/^-?\d+$/.test(candidate.mtimeNs))
|
|
14
|
+
return undefined;
|
|
15
|
+
return { size: candidate.size, mtimeNs: candidate.mtimeNs };
|
|
16
|
+
}
|
|
17
|
+
export function sameArtifactSourceFingerprint(left, right) {
|
|
18
|
+
return left.size === right.size && left.mtimeNs === right.mtimeNs;
|
|
19
|
+
}
|
|
20
|
+
function artifactSourceErrorCode(error) {
|
|
21
|
+
return error instanceof Error && "code" in error
|
|
22
|
+
? error.code
|
|
23
|
+
: undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Inspect only exact registered artifact paths without reading source bodies or traversing directories. */
|
|
26
|
+
export function inspectRegisteredArtifactPaths(paths) {
|
|
27
|
+
return [...new Set(paths)].sort().map((path) => {
|
|
28
|
+
if (!isAbsolute(path) || resolve(path) !== path) {
|
|
29
|
+
return { path, kind: "unavailable", reason: "artifact path is not canonical and absolute" };
|
|
30
|
+
}
|
|
31
|
+
let metadata;
|
|
32
|
+
try {
|
|
33
|
+
metadata = lstatSync(path, { bigint: true });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const code = artifactSourceErrorCode(error);
|
|
37
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
38
|
+
return { path, kind: "missing" };
|
|
39
|
+
return { path, kind: "unavailable", reason: error instanceof Error ? error.message : String(error) };
|
|
40
|
+
}
|
|
41
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
42
|
+
return { path, kind: "unavailable", reason: "artifact source is not a regular non-symlink file" };
|
|
43
|
+
}
|
|
44
|
+
if (metadata.size > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
45
|
+
return { path, kind: "unavailable", reason: "artifact source size exceeds the supported range" };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
path,
|
|
49
|
+
kind: "present",
|
|
50
|
+
fingerprint: { size: Number(metadata.size), mtimeNs: metadata.mtimeNs.toString() },
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Current compiler protocol for ordinary registered source artifacts. */
|
|
6
55
|
export const ORDINARY_ARTIFACT_COMPILER = "artifact-v1";
|
|
7
56
|
export function hashArtifactSource(source) {
|
|
8
57
|
return `sha256:${createHash("sha256").update(source).digest("hex")}`;
|
|
@@ -88,9 +137,12 @@ function validateSourceIdentity(source) {
|
|
|
88
137
|
if (typeof source.path !== "string" || source.path.trim().length === 0) {
|
|
89
138
|
throw new Error("Artifact source path must be non-empty");
|
|
90
139
|
}
|
|
91
|
-
if (!isArtifactHash(source.hash)) {
|
|
140
|
+
if (source.hash !== undefined && !isArtifactHash(source.hash)) {
|
|
92
141
|
throw new Error(`Artifact source at ${source.path} must have a sha256:<64 lowercase hex characters> hash`);
|
|
93
142
|
}
|
|
143
|
+
if (source.sourceFingerprint !== undefined && parseArtifactSourceFingerprint(source.sourceFingerprint) === undefined) {
|
|
144
|
+
throw new Error(`Artifact source fingerprint at ${source.path} is invalid`);
|
|
145
|
+
}
|
|
94
146
|
}
|
|
95
147
|
function validateCompilerRevision(compiler, path) {
|
|
96
148
|
if (typeof compiler !== "string" || compiler.trim().length === 0) {
|
|
@@ -99,13 +151,6 @@ function validateCompilerRevision(compiler, path) {
|
|
|
99
151
|
: `Artifact compiler revision at ${path} must be non-empty`);
|
|
100
152
|
}
|
|
101
153
|
}
|
|
102
|
-
function refreshRequested(path, explicitRefresh) {
|
|
103
|
-
if (explicitRefresh === true)
|
|
104
|
-
return true;
|
|
105
|
-
if (!explicitRefresh || typeof explicitRefresh !== "object" || typeof explicitRefresh.has !== "function")
|
|
106
|
-
return false;
|
|
107
|
-
return explicitRefresh.has(path);
|
|
108
|
-
}
|
|
109
154
|
function isProvenanceEntry(value) {
|
|
110
155
|
return isObject(value) && (value.malformed === undefined || value.malformed === true);
|
|
111
156
|
}
|
|
@@ -123,13 +168,14 @@ export function parseArtifactProvenanceRegistry(value, context = "Artifact prove
|
|
|
123
168
|
registry[path] = { malformed: true };
|
|
124
169
|
continue;
|
|
125
170
|
}
|
|
126
|
-
const known = new Set(["sourceHash", "compilerRevision", "compiledAt"]);
|
|
171
|
+
const known = new Set(["sourceHash", "sourceFingerprint", "compilerRevision", "compiledAt"]);
|
|
127
172
|
if (Object.keys(entry).some((key) => !known.has(key))) {
|
|
128
173
|
registry[path] = { malformed: true };
|
|
129
174
|
continue;
|
|
130
175
|
}
|
|
131
176
|
registry[path] = {
|
|
132
177
|
...(Object.hasOwn(entry, "sourceHash") ? { sourceHash: entry.sourceHash } : {}),
|
|
178
|
+
...(Object.hasOwn(entry, "sourceFingerprint") ? { sourceFingerprint: entry.sourceFingerprint } : {}),
|
|
133
179
|
...(Object.hasOwn(entry, "compilerRevision") ? { compilerRevision: entry.compilerRevision } : {}),
|
|
134
180
|
...(Object.hasOwn(entry, "compiledAt") ? { compiledAt: entry.compiledAt } : {}),
|
|
135
181
|
};
|
|
@@ -145,6 +191,8 @@ export function serializeArtifactProvenanceRegistry(registry) {
|
|
|
145
191
|
const fields = {};
|
|
146
192
|
if (entry.sourceHash !== undefined)
|
|
147
193
|
fields.sourceHash = entry.sourceHash;
|
|
194
|
+
if (entry.sourceFingerprint !== undefined)
|
|
195
|
+
fields.sourceFingerprint = entry.sourceFingerprint;
|
|
148
196
|
if (entry.compilerRevision !== undefined)
|
|
149
197
|
fields.compilerRevision = entry.compilerRevision;
|
|
150
198
|
if (entry.compiledAt !== undefined)
|
|
@@ -158,17 +206,14 @@ export function serializeArtifactProvenanceRegistry(registry) {
|
|
|
158
206
|
function invalidField(value, validate) {
|
|
159
207
|
return value !== undefined && !validate(value);
|
|
160
208
|
}
|
|
161
|
-
const INVALID_EVIDENCE = Symbol("invalid-evidence");
|
|
162
209
|
/** Later authority wins per field; absent runtime evidence falls back to retired embedded values. */
|
|
163
210
|
function fieldEvidence(entry, field, legacy) {
|
|
164
|
-
if (entry?.malformed === true)
|
|
165
|
-
return INVALID_EVIDENCE;
|
|
166
211
|
if (entry !== undefined && Object.hasOwn(entry, field))
|
|
167
212
|
return entry[field];
|
|
168
213
|
return legacy;
|
|
169
214
|
}
|
|
170
|
-
/**
|
|
171
|
-
export function
|
|
215
|
+
/** Decide whether retained compilation evidence requires source reacquisition. */
|
|
216
|
+
export function classifyArtifactCompilationNeed(source, metadata, compiler, explicitRefresh = false, provenance) {
|
|
172
217
|
validateSourceIdentity(source);
|
|
173
218
|
validateCompilerRevision(compiler);
|
|
174
219
|
if (metadata === undefined)
|
|
@@ -176,16 +221,27 @@ export function classifyArtifactFreshness(source, metadata, compiler, explicitRe
|
|
|
176
221
|
if (!isArtifactMetadata(metadata))
|
|
177
222
|
return { kind: "requires-compilation", reason: "invalid-metadata" };
|
|
178
223
|
const entry = isProvenanceEntry(provenance) ? provenance : undefined;
|
|
179
|
-
|
|
180
|
-
const sourceHash = runtime ? INVALID_EVIDENCE : fieldEvidence(entry, "sourceHash", metadata.hash);
|
|
181
|
-
if (sourceHash === INVALID_EVIDENCE || invalidField(sourceHash, (value) => isArtifactHash(value))) {
|
|
224
|
+
if ((provenance !== undefined && entry === undefined) || entry?.malformed === true) {
|
|
182
225
|
return { kind: "requires-compilation", reason: "invalid-metadata" };
|
|
183
226
|
}
|
|
184
|
-
if (
|
|
185
|
-
|
|
227
|
+
if (source.sourceFingerprint !== undefined) {
|
|
228
|
+
const retained = parseArtifactSourceFingerprint(entry?.sourceFingerprint);
|
|
229
|
+
if (retained === undefined)
|
|
230
|
+
return { kind: "requires-compilation", reason: "invalid-metadata" };
|
|
231
|
+
if (!sameArtifactSourceFingerprint(source.sourceFingerprint, retained))
|
|
232
|
+
return { kind: "requires-compilation", reason: "source-changed" };
|
|
233
|
+
}
|
|
234
|
+
if (source.sourceFingerprint === undefined || source.hash !== undefined) {
|
|
235
|
+
// Explicit current-hash observations, including Skills, retain their identity contract.
|
|
236
|
+
const sourceHash = fieldEvidence(entry, "sourceHash", metadata.hash);
|
|
237
|
+
if (invalidField(sourceHash, isArtifactHash))
|
|
238
|
+
return { kind: "requires-compilation", reason: "invalid-metadata" };
|
|
239
|
+
if (source.hash !== undefined && typeof sourceHash === "string" && sourceHash !== source.hash) {
|
|
240
|
+
return { kind: "requires-compilation", reason: "source-changed" };
|
|
241
|
+
}
|
|
186
242
|
}
|
|
187
|
-
const compilerRevision =
|
|
188
|
-
if (
|
|
243
|
+
const compilerRevision = fieldEvidence(entry, "compilerRevision", metadata.compiler);
|
|
244
|
+
if (invalidField(compilerRevision, (value) => typeof value === "string" && value.trim().length > 0)) {
|
|
189
245
|
return { kind: "requires-compilation", reason: "invalid-metadata" };
|
|
190
246
|
}
|
|
191
247
|
if (typeof compilerRevision === "string" && compilerRevision !== compiler) {
|
|
@@ -193,40 +249,7 @@ export function classifyArtifactFreshness(source, metadata, compiler, explicitRe
|
|
|
193
249
|
}
|
|
194
250
|
if (explicitRefresh)
|
|
195
251
|
return { kind: "requires-compilation", reason: "explicit-refresh" };
|
|
196
|
-
return { kind: "
|
|
197
|
-
}
|
|
198
|
-
/** Produce a deterministic acquisition plan from path/hash candidates and retained evidence. */
|
|
199
|
-
export function planArtifactInvalidation(sources, registry, compiler, options = {}, provenance = {}) {
|
|
200
|
-
if (!isObject(registry))
|
|
201
|
-
throw new Error("Artifacts must be a path-keyed JSON object");
|
|
202
|
-
validateCompilerRevision(compiler);
|
|
203
|
-
const ordered = [...sources].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
204
|
-
const seen = new Set();
|
|
205
|
-
const fresh = [];
|
|
206
|
-
const requiresCompilation = [];
|
|
207
|
-
for (const source of ordered) {
|
|
208
|
-
if (seen.has(source.path))
|
|
209
|
-
throw new Error(`Duplicate artifact source path: ${source.path}`);
|
|
210
|
-
seen.add(source.path);
|
|
211
|
-
const metadata = Object.hasOwn(registry, source.path) ? registry[source.path] : undefined;
|
|
212
|
-
const entry = Object.hasOwn(provenance, source.path) ? provenance[source.path] : undefined;
|
|
213
|
-
const freshness = classifyArtifactFreshness(source, metadata, compiler, refreshRequested(source.path, options.explicitRefresh), entry);
|
|
214
|
-
const identity = { path: source.path, hash: source.hash };
|
|
215
|
-
if (freshness.kind === "fresh")
|
|
216
|
-
fresh.push(identity);
|
|
217
|
-
else
|
|
218
|
-
requiresCompilation.push({ ...identity, reason: freshness.reason });
|
|
219
|
-
}
|
|
220
|
-
if (options.removed !== undefined && !Array.isArray(options.removed))
|
|
221
|
-
throw new Error("Removed artifact paths must be an array");
|
|
222
|
-
const removed = [...new Set(options.removed ?? [])];
|
|
223
|
-
for (const path of removed) {
|
|
224
|
-
if (typeof path !== "string" || path.trim().length === 0)
|
|
225
|
-
throw new Error("Removed artifact paths must be non-empty");
|
|
226
|
-
if (seen.has(path))
|
|
227
|
-
throw new Error(`Artifact source cannot be both present and removed: ${path}`);
|
|
228
|
-
}
|
|
229
|
-
return { fresh, requiresCompilation, removed: removed.filter((path) => Object.hasOwn(registry, path)).sort() };
|
|
252
|
+
return { kind: "current" };
|
|
230
253
|
}
|
|
231
254
|
/** Split one compiler output into model-visible semantics and runtime-owned provenance. */
|
|
232
255
|
export function compileArtifact(update) {
|
|
@@ -242,7 +265,8 @@ export function compileArtifact(update) {
|
|
|
242
265
|
return {
|
|
243
266
|
semantic,
|
|
244
267
|
provenance: {
|
|
245
|
-
sourceHash: update.source.hash,
|
|
268
|
+
...(update.source.hash === undefined ? {} : { sourceHash: update.source.hash }),
|
|
269
|
+
...(update.source.sourceFingerprint === undefined ? {} : { sourceFingerprint: structuredClone(update.source.sourceFingerprint) }),
|
|
246
270
|
compilerRevision: update.compiler,
|
|
247
271
|
...(typeof update.output.compiled_at === "string" ? { compiledAt: update.output.compiled_at } : {}),
|
|
248
272
|
},
|
|
@@ -299,7 +323,7 @@ export function updateArtifactRegistry(registry, updates, removed = []) {
|
|
|
299
323
|
return next;
|
|
300
324
|
}
|
|
301
325
|
/** Runtime-owned artifact fields that never belong in ordinary model context. */
|
|
302
|
-
const RUNTIME_ARTIFACT_FIELDS = [...MODEL_FORBIDDEN_PROVENANCE_FIELDS, "compiled_at"];
|
|
326
|
+
const RUNTIME_ARTIFACT_FIELDS = [...MODEL_FORBIDDEN_PROVENANCE_FIELDS, "compiled_at", "hint"];
|
|
303
327
|
/** Validate authored fields only: legacy retained evidence stays readable but cannot be model-edited. */
|
|
304
328
|
export function validateModelArtifactPatch(patch) {
|
|
305
329
|
for (const [path, entry] of Object.entries(patch)) {
|
|
@@ -307,7 +331,7 @@ export function validateModelArtifactPatch(patch) {
|
|
|
307
331
|
continue; // Whole-artifact deletion and materialized shape belong to the transition owner.
|
|
308
332
|
const field = RUNTIME_ARTIFACT_FIELDS.find((field) => Object.hasOwn(entry, field));
|
|
309
333
|
if (field !== undefined)
|
|
310
|
-
throw new Error(`Artifact patch at ${path} cannot set runtime-owned
|
|
334
|
+
throw new Error(`Artifact patch at ${path} cannot set runtime-owned field ${field}`);
|
|
311
335
|
}
|
|
312
336
|
}
|
|
313
337
|
/** Strip retained runtime bookkeeping from one model-visible artifact entry. */
|
|
@@ -319,12 +343,18 @@ export function projectArtifactForModel(entry) {
|
|
|
319
343
|
delete projected[field];
|
|
320
344
|
return projected;
|
|
321
345
|
}
|
|
322
|
-
/** Strip retained runtime bookkeeping
|
|
323
|
-
export function projectArtifactsForModel(registry) {
|
|
346
|
+
/** Strip retained runtime bookkeeping and add deterministic runtime-only guidance. */
|
|
347
|
+
export function projectArtifactsForModel(registry, hints = {}) {
|
|
324
348
|
const projected = {};
|
|
325
349
|
for (const [path, entry] of Object.entries(registry)) {
|
|
350
|
+
const modelEntry = projectArtifactForModel(entry);
|
|
351
|
+
const values = (Array.isArray(hints[path]) ? hints[path] : [hints[path]])
|
|
352
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
353
|
+
const hint = [...new Set(values)].sort().join("\n");
|
|
354
|
+
if (hint.length > 0 && isObject(modelEntry))
|
|
355
|
+
modelEntry.hint = hint;
|
|
326
356
|
Object.defineProperty(projected, path, {
|
|
327
|
-
value:
|
|
357
|
+
value: modelEntry,
|
|
328
358
|
enumerable: true,
|
|
329
359
|
configurable: true,
|
|
330
360
|
writable: true,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { CompactionResult } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
export declare const STATE_FLOW_COMPACTION_SUMMARY = "State Flow accepted the completed work before this boundary. Current memory is restored from its
|
|
2
|
+
export declare const STATE_FLOW_COMPACTION_SUMMARY = "State Flow accepted the completed work before this boundary. Current memory is restored from its retained semantic boundary and projected separately; use the retained native entries for subsequent work.";
|
|
3
3
|
/** A modest margin above Pi's default 20k retained suffix absorbs estimation drift. */
|
|
4
4
|
export declare const STATE_FLOW_COMPACTION_MIN_CONTEXT_TOKENS = 24000;
|
|
5
5
|
export interface StateFlowCompactionDetails {
|
|
6
6
|
version: 1;
|
|
7
7
|
owner: "state-flow";
|
|
8
|
-
|
|
8
|
+
boundary: string;
|
|
9
9
|
step: number;
|
|
10
10
|
}
|
|
11
11
|
export interface StateFlowCompactionPlan {
|
|
@@ -21,6 +21,7 @@ type ActiveEntry = {
|
|
|
21
21
|
role?: unknown;
|
|
22
22
|
stopReason?: unknown;
|
|
23
23
|
content?: unknown;
|
|
24
|
+
timestamp?: unknown;
|
|
24
25
|
};
|
|
25
26
|
};
|
|
26
27
|
export declare function shouldRequestStateFlowCompaction(usage: {
|
|
@@ -33,7 +34,7 @@ export declare function shouldRequestStateFlowCompaction(usage: {
|
|
|
33
34
|
*/
|
|
34
35
|
export declare function hasCompactionSizedTranscript(entries: readonly ActiveEntry[]): boolean;
|
|
35
36
|
/** Retain the complete latest accepted user iteration without hiding foreign extension context. */
|
|
36
|
-
export declare function planStateFlowCompaction(entries: readonly ActiveEntry[],
|
|
37
|
+
export declare function planStateFlowCompaction(entries: readonly ActiveEntry[], boundary: string, step: number, runAnchorTimestamp: number | undefined): StateFlowCompactionPlan | undefined;
|
|
37
38
|
/** Customize only the extension-owned manual request and only while its planned leaf remains selected. */
|
|
38
39
|
export declare function stateFlowCompactionResult(plan: StateFlowCompactionPlan, marker: string, event: {
|
|
39
40
|
reason: "manual" | "threshold" | "overflow";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { estimateTokens } from "@earendil-works/pi-agent-core";
|
|
2
|
-
export const STATE_FLOW_COMPACTION_SUMMARY = "State Flow accepted the completed work before this boundary. Current memory is restored from its
|
|
2
|
+
export const STATE_FLOW_COMPACTION_SUMMARY = "State Flow accepted the completed work before this boundary. Current memory is restored from its retained semantic boundary and projected separately; use the retained native entries for subsequent work.";
|
|
3
3
|
/** A modest margin above Pi's default 20k retained suffix absorbs estimation drift. */
|
|
4
4
|
export const STATE_FLOW_COMPACTION_MIN_CONTEXT_TOKENS = 24_000;
|
|
5
5
|
function stateFlowEntry(entry) {
|
|
@@ -29,8 +29,8 @@ export function hasCompactionSizedTranscript(entries) {
|
|
|
29
29
|
return false;
|
|
30
30
|
}
|
|
31
31
|
/** Retain the complete latest accepted user iteration without hiding foreign extension context. */
|
|
32
|
-
export function planStateFlowCompaction(entries,
|
|
33
|
-
if (
|
|
32
|
+
export function planStateFlowCompaction(entries, boundary, step, runAnchorTimestamp) {
|
|
33
|
+
if (boundary.trim().length === 0 || typeof runAnchorTimestamp !== "number" || !Number.isFinite(runAnchorTimestamp))
|
|
34
34
|
return undefined;
|
|
35
35
|
if (!Number.isSafeInteger(step) || step < 0 || entries.length === 0)
|
|
36
36
|
return undefined;
|
|
@@ -39,18 +39,18 @@ export function planStateFlowCompaction(entries, revision, step) {
|
|
|
39
39
|
|| entries[terminal]?.message?.stopReason === "error"
|
|
40
40
|
|| entries[terminal]?.message?.stopReason === "length")
|
|
41
41
|
return undefined;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (keep < 0)
|
|
42
|
+
// Steering is part of the same run; an absent or colliding timestamp cannot prove its first entry.
|
|
43
|
+
const isRunAnchor = (entry) => entry.type === "message" && entry.message?.role === "user" && entry.message.timestamp === runAnchorTimestamp;
|
|
44
|
+
const keep = entries.findIndex(isRunAnchor);
|
|
45
|
+
if (keep < 0 || keep > terminal || entries.findLastIndex(isRunAnchor) !== keep)
|
|
46
46
|
return undefined;
|
|
47
|
-
if (entries.slice(0, keep).some((entry) => entry.type === "custom" && !stateFlowEntry(entry)))
|
|
47
|
+
if (entries.slice(0, keep).some((entry) => entry.type === "custom_message" || (entry.type === "custom" && !stateFlowEntry(entry))))
|
|
48
48
|
return undefined;
|
|
49
49
|
const firstKeptEntryId = entries[keep]?.id;
|
|
50
50
|
const leafId = entries.at(-1)?.id;
|
|
51
51
|
if (typeof firstKeptEntryId !== "string" || typeof leafId !== "string")
|
|
52
52
|
return undefined;
|
|
53
|
-
return { leafId, firstKeptEntryId, details: { version: 1, owner: "state-flow",
|
|
53
|
+
return { leafId, firstKeptEntryId, details: { version: 1, owner: "state-flow", boundary, step } };
|
|
54
54
|
}
|
|
55
55
|
/** Customize only the extension-owned manual request and only while its planned leaf remains selected. */
|
|
56
56
|
export function stateFlowCompactionResult(plan, marker, event) {
|
|
@@ -8,7 +8,7 @@ export interface StateFlowConfig {
|
|
|
8
8
|
logging: boolean;
|
|
9
9
|
/** Show successful patch_state arguments in the interactive tool row. */
|
|
10
10
|
showSuccessfulPatches: boolean;
|
|
11
|
-
|
|
11
|
+
historyLimit: number;
|
|
12
12
|
}
|
|
13
13
|
/** Read the repository-global config once at extension load/reload. Missing config uses defaults; invalid config never falls back. */
|
|
14
14
|
export declare function loadStateFlowConfig(agentDir?: string, repositoryRoot?: string): StateFlowConfig;
|