jfl 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +26 -0
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/migrate-tenet.d.ts +25 -0
- package/dist/commands/migrate-tenet.d.ts.map +1 -0
- package/dist/commands/migrate-tenet.js +252 -0
- package/dist/commands/migrate-tenet.js.map +1 -0
- package/dist/commands/peter.d.ts.map +1 -1
- package/dist/commands/peter.js +47 -5
- package/dist/commands/peter.js.map +1 -1
- package/dist/commands/pi.d.ts +1 -0
- package/dist/commands/pi.d.ts.map +1 -1
- package/dist/commands/pi.js +5 -1
- package/dist/commands/pi.js.map +1 -1
- package/dist/commands/pivot.d.ts +28 -0
- package/dist/commands/pivot.d.ts.map +1 -0
- package/dist/commands/pivot.js +219 -0
- package/dist/commands/pivot.js.map +1 -0
- package/dist/commands/services-create.js +348 -0
- package/dist/commands/services-create.js.map +1 -1
- package/dist/dashboard-static/assets/index-BVrmW-ZI.js +154 -0
- package/dist/dashboard-static/assets/{index-CW8oWAdr.css → index-DtruPD44.css} +1 -1
- package/dist/dashboard-static/index.html +2 -2
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-generator.d.ts.map +1 -1
- package/dist/lib/agent-generator.js +15 -0
- package/dist/lib/agent-generator.js.map +1 -1
- package/dist/lib/counterfactual-engine.d.ts +136 -0
- package/dist/lib/counterfactual-engine.d.ts.map +1 -0
- package/dist/lib/counterfactual-engine.js +417 -0
- package/dist/lib/counterfactual-engine.js.map +1 -0
- package/dist/lib/dynamics-model.d.ts +107 -0
- package/dist/lib/dynamics-model.d.ts.map +1 -0
- package/dist/lib/dynamics-model.js +363 -0
- package/dist/lib/dynamics-model.js.map +1 -0
- package/dist/lib/eval-snapshot.d.ts.map +1 -1
- package/dist/lib/eval-snapshot.js +15 -4
- package/dist/lib/eval-snapshot.js.map +1 -1
- package/dist/lib/invariant-monitor.d.ts +50 -0
- package/dist/lib/invariant-monitor.d.ts.map +1 -0
- package/dist/lib/invariant-monitor.js +400 -0
- package/dist/lib/invariant-monitor.js.map +1 -0
- package/dist/lib/meta-orchestrator.d.ts +40 -3
- package/dist/lib/meta-orchestrator.d.ts.map +1 -1
- package/dist/lib/meta-orchestrator.js +181 -2
- package/dist/lib/meta-orchestrator.js.map +1 -1
- package/dist/lib/openclaw-sdk.d.ts +8 -0
- package/dist/lib/openclaw-sdk.d.ts.map +1 -1
- package/dist/lib/openclaw-sdk.js +11 -0
- package/dist/lib/openclaw-sdk.js.map +1 -1
- package/dist/lib/peter-parker-bridge.d.ts +37 -1
- package/dist/lib/peter-parker-bridge.d.ts.map +1 -1
- package/dist/lib/peter-parker-bridge.js +201 -1
- package/dist/lib/peter-parker-bridge.js.map +1 -1
- package/dist/lib/service-detector.d.ts +1 -1
- package/dist/lib/service-detector.d.ts.map +1 -1
- package/dist/lib/service-detector.js +26 -6
- package/dist/lib/service-detector.js.map +1 -1
- package/dist/lib/service-gtm.d.ts +1 -1
- package/dist/lib/service-gtm.d.ts.map +1 -1
- package/dist/lib/state-capture.d.ts +36 -0
- package/dist/lib/state-capture.d.ts.map +1 -0
- package/dist/lib/state-capture.js +541 -0
- package/dist/lib/state-capture.js.map +1 -0
- package/dist/lib/stratus-client.d.ts +78 -2
- package/dist/lib/stratus-client.d.ts.map +1 -1
- package/dist/lib/stratus-client.js +432 -1
- package/dist/lib/stratus-client.js.map +1 -1
- package/dist/lib/world-model-store.d.ts +172 -0
- package/dist/lib/world-model-store.d.ts.map +1 -0
- package/dist/lib/world-model-store.js +487 -0
- package/dist/lib/world-model-store.js.map +1 -0
- package/dist/types/world-model.d.ts +478 -0
- package/dist/types/world-model.d.ts.map +1 -0
- package/dist/types/world-model.js +80 -0
- package/dist/types/world-model.js.map +1 -0
- package/dist/utils/jfl-config.d.ts +5 -0
- package/dist/utils/jfl-config.d.ts.map +1 -1
- package/dist/utils/jfl-config.js +13 -1
- package/dist/utils/jfl-config.js.map +1 -1
- package/package.json +1 -1
- package/packages/pi/extensions/hud-tool.ts +2 -24
- package/packages/pi/extensions/index.ts +48 -25
- package/packages/pi/extensions/onboarding-v1.ts +455 -0
- package/packages/pi/extensions/onboarding-v2.ts +516 -0
- package/packages/pi/extensions/onboarding-v3.ts +675 -0
- package/packages/pi/extensions/pivot-tool.ts +59 -0
- package/packages/pi/extensions/session.ts +58 -0
- package/packages/pi/extensions/types.ts +2 -0
- package/packages/pi/skills/pivot/SKILL.md +91 -0
- package/template/.claude/settings.json +9 -0
- package/dist/dashboard-static/assets/index-Ck8f9dcM.js +0 -121
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* World Model Storage Layer
|
|
3
|
+
*
|
|
4
|
+
* File-based JSONL storage for state transitions, predictions, invariant violations,
|
|
5
|
+
* and state snapshots. Follows the eval-store.ts pattern for consistency.
|
|
6
|
+
*
|
|
7
|
+
* Storage structure:
|
|
8
|
+
* world-model-data/
|
|
9
|
+
* ├── transitions/
|
|
10
|
+
* │ ├── YYYY-MM-DD.jsonl # Daily transition logs
|
|
11
|
+
* │ └── index.json # Fast lookup index
|
|
12
|
+
* ├── states/
|
|
13
|
+
* │ ├── snapshots-hourly.jsonl
|
|
14
|
+
* │ └── current-state.json
|
|
15
|
+
* ├── predictions/
|
|
16
|
+
* │ ├── dynamics-cache.jsonl
|
|
17
|
+
* │ └── counterfactuals.jsonl
|
|
18
|
+
* └── invariants/
|
|
19
|
+
* ├── violations.jsonl
|
|
20
|
+
* └── recovery-log.jsonl
|
|
21
|
+
*
|
|
22
|
+
* @purpose JSONL storage layer for world model data — transitions, predictions, invariants
|
|
23
|
+
* @spec world-model-roadmap.md#storage
|
|
24
|
+
*/
|
|
25
|
+
import type { StateTransition, WorldState, InvariantViolation, PredictionResult, CounterfactualScenario, AgentAction, CachedPrediction, StateSnapshot, WorldModelStorageConfig } from "../types/world-model.js";
|
|
26
|
+
/**
|
|
27
|
+
* WorldModelStore manages all persistent storage for the world model.
|
|
28
|
+
* Uses JSONL files for append-only logs and JSON for indexes/state.
|
|
29
|
+
*/
|
|
30
|
+
export declare class WorldModelStore {
|
|
31
|
+
private projectRoot;
|
|
32
|
+
private dataDir;
|
|
33
|
+
private config;
|
|
34
|
+
/**
|
|
35
|
+
* Create a new WorldModelStore
|
|
36
|
+
* @param projectRoot - Project root directory (auto-detected if not provided)
|
|
37
|
+
* @param config - Optional configuration overrides
|
|
38
|
+
*/
|
|
39
|
+
constructor(projectRoot?: string, config?: Partial<WorldModelStorageConfig>);
|
|
40
|
+
/**
|
|
41
|
+
* Record a new state transition
|
|
42
|
+
* @param transition - The transition to record (id will be generated if not provided)
|
|
43
|
+
* @returns The recorded transition with generated ID
|
|
44
|
+
*/
|
|
45
|
+
recordTransition(transition: Omit<StateTransition, "id"> & {
|
|
46
|
+
id?: string;
|
|
47
|
+
}): StateTransition;
|
|
48
|
+
/**
|
|
49
|
+
* Get transitions with optional filtering
|
|
50
|
+
* @param options - Filter options
|
|
51
|
+
* @returns Array of matching transitions
|
|
52
|
+
*/
|
|
53
|
+
getTransitions(options?: {
|
|
54
|
+
agentId?: string;
|
|
55
|
+
startTime?: number;
|
|
56
|
+
endTime?: number;
|
|
57
|
+
actionType?: string;
|
|
58
|
+
limit?: number;
|
|
59
|
+
dateRange?: {
|
|
60
|
+
start: string;
|
|
61
|
+
end: string;
|
|
62
|
+
};
|
|
63
|
+
}): StateTransition[];
|
|
64
|
+
/**
|
|
65
|
+
* Get a specific transition by ID
|
|
66
|
+
*/
|
|
67
|
+
getTransitionById(id: string): StateTransition | null;
|
|
68
|
+
/**
|
|
69
|
+
* Update transition index for fast lookups
|
|
70
|
+
*/
|
|
71
|
+
private updateTransitionIndex;
|
|
72
|
+
/**
|
|
73
|
+
* Read the transition index
|
|
74
|
+
*/
|
|
75
|
+
private readTransitionIndex;
|
|
76
|
+
/**
|
|
77
|
+
* Save a state snapshot
|
|
78
|
+
* @param state - The world state to snapshot
|
|
79
|
+
* @param trigger - What triggered this snapshot
|
|
80
|
+
* @returns The saved snapshot with ID
|
|
81
|
+
*/
|
|
82
|
+
snapshotState(state: WorldState, trigger?: "hourly" | "event" | "manual"): StateSnapshot;
|
|
83
|
+
/**
|
|
84
|
+
* Get the most recent state snapshot
|
|
85
|
+
*/
|
|
86
|
+
getCurrentState(): WorldState | null;
|
|
87
|
+
/**
|
|
88
|
+
* Get recent state snapshots
|
|
89
|
+
*/
|
|
90
|
+
getRecentSnapshots(limit?: number): StateSnapshot[];
|
|
91
|
+
/**
|
|
92
|
+
* Cache a prediction result
|
|
93
|
+
* @param state - The input state
|
|
94
|
+
* @param action - The input action
|
|
95
|
+
* @param prediction - The prediction result
|
|
96
|
+
*/
|
|
97
|
+
cachePrediction(state: WorldState, action: AgentAction, prediction: PredictionResult): void;
|
|
98
|
+
/**
|
|
99
|
+
* Look up a cached prediction
|
|
100
|
+
* @param state - The input state
|
|
101
|
+
* @param action - The input action
|
|
102
|
+
* @returns Cached prediction if found and not expired
|
|
103
|
+
*/
|
|
104
|
+
getCachedPrediction(state: WorldState, action: AgentAction): PredictionResult | null;
|
|
105
|
+
/**
|
|
106
|
+
* Get all cached predictions (for analysis/debugging)
|
|
107
|
+
*/
|
|
108
|
+
getAllCachedPredictions(): CachedPrediction[];
|
|
109
|
+
/**
|
|
110
|
+
* Record an invariant violation
|
|
111
|
+
* @param violation - The violation to record
|
|
112
|
+
* @param transitionId - Optional transition ID that caused the violation
|
|
113
|
+
*/
|
|
114
|
+
recordInvariantViolation(violation: InvariantViolation, transitionId?: string): void;
|
|
115
|
+
/**
|
|
116
|
+
* Get recent invariant violations
|
|
117
|
+
*/
|
|
118
|
+
getRecentViolations(options?: {
|
|
119
|
+
invariantId?: string;
|
|
120
|
+
severity?: string;
|
|
121
|
+
since?: number;
|
|
122
|
+
limit?: number;
|
|
123
|
+
}): Array<InvariantViolation & {
|
|
124
|
+
transitionId?: string;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Record a recovery action taken after a violation
|
|
128
|
+
*/
|
|
129
|
+
recordRecovery(violation: InvariantViolation, recoveryAction: string, success: boolean): void;
|
|
130
|
+
/**
|
|
131
|
+
* Store a counterfactual scenario
|
|
132
|
+
*/
|
|
133
|
+
recordCounterfactual(scenario: CounterfactualScenario): void;
|
|
134
|
+
/**
|
|
135
|
+
* Get counterfactuals for a transition
|
|
136
|
+
*/
|
|
137
|
+
getCounterfactuals(baseTransitionId: string): CounterfactualScenario[];
|
|
138
|
+
/**
|
|
139
|
+
* Get all counterfactuals (for analysis)
|
|
140
|
+
*/
|
|
141
|
+
getAllCounterfactuals(): CounterfactualScenario[];
|
|
142
|
+
/**
|
|
143
|
+
* Get storage statistics
|
|
144
|
+
*/
|
|
145
|
+
getStats(): {
|
|
146
|
+
transitionCount: number;
|
|
147
|
+
snapshotCount: number;
|
|
148
|
+
cachedPredictions: number;
|
|
149
|
+
violationCount: number;
|
|
150
|
+
counterfactualCount: number;
|
|
151
|
+
oldestTransition?: number;
|
|
152
|
+
newestTransition?: number;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Prune old data to keep storage bounded
|
|
156
|
+
*/
|
|
157
|
+
pruneOldData(options?: {
|
|
158
|
+
maxAgeDays?: number;
|
|
159
|
+
maxTransitions?: number;
|
|
160
|
+
}): {
|
|
161
|
+
pruned: number;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Get the data directory path
|
|
165
|
+
*/
|
|
166
|
+
getDataDir(): string;
|
|
167
|
+
/**
|
|
168
|
+
* Check if the store has any data
|
|
169
|
+
*/
|
|
170
|
+
hasData(): boolean;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=world-model-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"world-model-store.d.ts","sourceRoot":"","sources":["../../src/lib/world-model-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* World Model Storage Layer
|
|
3
|
+
*
|
|
4
|
+
* File-based JSONL storage for state transitions, predictions, invariant violations,
|
|
5
|
+
* and state snapshots. Follows the eval-store.ts pattern for consistency.
|
|
6
|
+
*
|
|
7
|
+
* Storage structure:
|
|
8
|
+
* world-model-data/
|
|
9
|
+
* ├── transitions/
|
|
10
|
+
* │ ├── YYYY-MM-DD.jsonl # Daily transition logs
|
|
11
|
+
* │ └── index.json # Fast lookup index
|
|
12
|
+
* ├── states/
|
|
13
|
+
* │ ├── snapshots-hourly.jsonl
|
|
14
|
+
* │ └── current-state.json
|
|
15
|
+
* ├── predictions/
|
|
16
|
+
* │ ├── dynamics-cache.jsonl
|
|
17
|
+
* │ └── counterfactuals.jsonl
|
|
18
|
+
* └── invariants/
|
|
19
|
+
* ├── violations.jsonl
|
|
20
|
+
* └── recovery-log.jsonl
|
|
21
|
+
*
|
|
22
|
+
* @purpose JSONL storage layer for world model data — transitions, predictions, invariants
|
|
23
|
+
* @spec world-model-roadmap.md#storage
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync, readdirSync } from "fs";
|
|
26
|
+
import { join, dirname } from "path";
|
|
27
|
+
import { createHash } from "crypto";
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Constants
|
|
30
|
+
// ============================================================================
|
|
31
|
+
const DEFAULT_CONFIG = {
|
|
32
|
+
dataDir: "world-model-data",
|
|
33
|
+
maxTransitionsPerFile: 10000,
|
|
34
|
+
predictionCacheTtl: 3600000, // 1 hour
|
|
35
|
+
maxBufferSize: 50000,
|
|
36
|
+
};
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Helpers
|
|
39
|
+
// ============================================================================
|
|
40
|
+
/**
|
|
41
|
+
* Find project root by looking for .jfl directory
|
|
42
|
+
*/
|
|
43
|
+
function findProjectRoot() {
|
|
44
|
+
let dir = process.cwd();
|
|
45
|
+
while (dir !== dirname(dir)) {
|
|
46
|
+
if (existsSync(join(dir, ".jfl", "config.json")))
|
|
47
|
+
return dir;
|
|
48
|
+
if (existsSync(join(dir, ".jfl")))
|
|
49
|
+
return dir;
|
|
50
|
+
dir = dirname(dir);
|
|
51
|
+
}
|
|
52
|
+
return process.cwd();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get today's date in YYYY-MM-DD format
|
|
56
|
+
*/
|
|
57
|
+
function getDateString(timestamp) {
|
|
58
|
+
const date = timestamp ? new Date(timestamp) : new Date();
|
|
59
|
+
return date.toISOString().split("T")[0];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate a unique ID from content hash
|
|
63
|
+
*/
|
|
64
|
+
function generateId(prefix, ...contents) {
|
|
65
|
+
const hash = createHash("sha256")
|
|
66
|
+
.update(JSON.stringify(contents))
|
|
67
|
+
.digest("hex")
|
|
68
|
+
.slice(0, 16);
|
|
69
|
+
return `${prefix}_${hash}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Safely parse a JSONL file, skipping malformed lines
|
|
73
|
+
*/
|
|
74
|
+
function parseJSONLFile(filePath) {
|
|
75
|
+
if (!existsSync(filePath))
|
|
76
|
+
return [];
|
|
77
|
+
const entries = [];
|
|
78
|
+
const lines = readFileSync(filePath, "utf-8").split("\n");
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
if (!line.trim())
|
|
81
|
+
continue;
|
|
82
|
+
try {
|
|
83
|
+
entries.push(JSON.parse(line));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Skip malformed lines silently
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return entries;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Ensure a directory exists, creating it if necessary
|
|
93
|
+
*/
|
|
94
|
+
function ensureDir(dirPath) {
|
|
95
|
+
if (!existsSync(dirPath)) {
|
|
96
|
+
mkdirSync(dirPath, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// WorldModelStore Class
|
|
101
|
+
// ============================================================================
|
|
102
|
+
/**
|
|
103
|
+
* WorldModelStore manages all persistent storage for the world model.
|
|
104
|
+
* Uses JSONL files for append-only logs and JSON for indexes/state.
|
|
105
|
+
*/
|
|
106
|
+
export class WorldModelStore {
|
|
107
|
+
projectRoot;
|
|
108
|
+
dataDir;
|
|
109
|
+
config;
|
|
110
|
+
/**
|
|
111
|
+
* Create a new WorldModelStore
|
|
112
|
+
* @param projectRoot - Project root directory (auto-detected if not provided)
|
|
113
|
+
* @param config - Optional configuration overrides
|
|
114
|
+
*/
|
|
115
|
+
constructor(projectRoot, config) {
|
|
116
|
+
this.projectRoot = projectRoot || findProjectRoot();
|
|
117
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
118
|
+
this.dataDir = join(this.projectRoot, this.config.dataDir);
|
|
119
|
+
}
|
|
120
|
+
// ==========================================================================
|
|
121
|
+
// Transitions
|
|
122
|
+
// ==========================================================================
|
|
123
|
+
/**
|
|
124
|
+
* Record a new state transition
|
|
125
|
+
* @param transition - The transition to record (id will be generated if not provided)
|
|
126
|
+
* @returns The recorded transition with generated ID
|
|
127
|
+
*/
|
|
128
|
+
recordTransition(transition) {
|
|
129
|
+
const id = transition.id || generateId("tr", transition.priorState.timestamp, transition.action.agentId, transition.action.actionType);
|
|
130
|
+
const full = { ...transition, id };
|
|
131
|
+
const transitionsDir = join(this.dataDir, "transitions");
|
|
132
|
+
ensureDir(transitionsDir);
|
|
133
|
+
const dateStr = getDateString(transition.posteriorState.timestamp);
|
|
134
|
+
const filePath = join(transitionsDir, `${dateStr}.jsonl`);
|
|
135
|
+
appendFileSync(filePath, JSON.stringify(full) + "\n");
|
|
136
|
+
// Update index
|
|
137
|
+
this.updateTransitionIndex(full);
|
|
138
|
+
return full;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get transitions with optional filtering
|
|
142
|
+
* @param options - Filter options
|
|
143
|
+
* @returns Array of matching transitions
|
|
144
|
+
*/
|
|
145
|
+
getTransitions(options = {}) {
|
|
146
|
+
const transitionsDir = join(this.dataDir, "transitions");
|
|
147
|
+
if (!existsSync(transitionsDir))
|
|
148
|
+
return [];
|
|
149
|
+
const files = readdirSync(transitionsDir)
|
|
150
|
+
.filter(f => f.endsWith(".jsonl"))
|
|
151
|
+
.sort();
|
|
152
|
+
// Filter files by date range if provided
|
|
153
|
+
let filteredFiles = files;
|
|
154
|
+
if (options.dateRange) {
|
|
155
|
+
filteredFiles = files.filter(f => {
|
|
156
|
+
const date = f.replace(".jsonl", "");
|
|
157
|
+
return date >= options.dateRange.start && date <= options.dateRange.end;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
let transitions = [];
|
|
161
|
+
for (const file of filteredFiles) {
|
|
162
|
+
const filePath = join(transitionsDir, file);
|
|
163
|
+
const fileTransitions = parseJSONLFile(filePath);
|
|
164
|
+
transitions.push(...fileTransitions);
|
|
165
|
+
}
|
|
166
|
+
// Apply filters
|
|
167
|
+
if (options.agentId) {
|
|
168
|
+
transitions = transitions.filter(t => t.action.agentId === options.agentId);
|
|
169
|
+
}
|
|
170
|
+
if (options.startTime) {
|
|
171
|
+
transitions = transitions.filter(t => t.priorState.timestamp >= options.startTime);
|
|
172
|
+
}
|
|
173
|
+
if (options.endTime) {
|
|
174
|
+
transitions = transitions.filter(t => t.posteriorState.timestamp <= options.endTime);
|
|
175
|
+
}
|
|
176
|
+
if (options.actionType) {
|
|
177
|
+
transitions = transitions.filter(t => t.action.actionType === options.actionType);
|
|
178
|
+
}
|
|
179
|
+
// Sort by timestamp (newest first) and limit
|
|
180
|
+
transitions.sort((a, b) => b.posteriorState.timestamp - a.posteriorState.timestamp);
|
|
181
|
+
if (options.limit) {
|
|
182
|
+
transitions = transitions.slice(0, options.limit);
|
|
183
|
+
}
|
|
184
|
+
return transitions;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get a specific transition by ID
|
|
188
|
+
*/
|
|
189
|
+
getTransitionById(id) {
|
|
190
|
+
const index = this.readTransitionIndex();
|
|
191
|
+
const entry = index[id];
|
|
192
|
+
if (!entry)
|
|
193
|
+
return null;
|
|
194
|
+
const filePath = join(this.dataDir, "transitions", `${entry.date}.jsonl`);
|
|
195
|
+
const transitions = parseJSONLFile(filePath);
|
|
196
|
+
return transitions.find(t => t.id === id) || null;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Update transition index for fast lookups
|
|
200
|
+
*/
|
|
201
|
+
updateTransitionIndex(transition) {
|
|
202
|
+
const indexPath = join(this.dataDir, "transitions", "index.json");
|
|
203
|
+
const index = this.readTransitionIndex();
|
|
204
|
+
index[transition.id] = {
|
|
205
|
+
date: getDateString(transition.posteriorState.timestamp),
|
|
206
|
+
agentId: transition.action.agentId,
|
|
207
|
+
actionType: transition.action.actionType,
|
|
208
|
+
timestamp: transition.posteriorState.timestamp,
|
|
209
|
+
};
|
|
210
|
+
writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Read the transition index
|
|
214
|
+
*/
|
|
215
|
+
readTransitionIndex() {
|
|
216
|
+
const indexPath = join(this.dataDir, "transitions", "index.json");
|
|
217
|
+
if (!existsSync(indexPath))
|
|
218
|
+
return {};
|
|
219
|
+
try {
|
|
220
|
+
return JSON.parse(readFileSync(indexPath, "utf-8"));
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return {};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ==========================================================================
|
|
227
|
+
// State Snapshots
|
|
228
|
+
// ==========================================================================
|
|
229
|
+
/**
|
|
230
|
+
* Save a state snapshot
|
|
231
|
+
* @param state - The world state to snapshot
|
|
232
|
+
* @param trigger - What triggered this snapshot
|
|
233
|
+
* @returns The saved snapshot with ID
|
|
234
|
+
*/
|
|
235
|
+
snapshotState(state, trigger = "manual") {
|
|
236
|
+
const snapshot = {
|
|
237
|
+
id: generateId("ss", state.timestamp, state.agentId),
|
|
238
|
+
timestamp: state.timestamp,
|
|
239
|
+
state,
|
|
240
|
+
trigger,
|
|
241
|
+
};
|
|
242
|
+
const statesDir = join(this.dataDir, "states");
|
|
243
|
+
ensureDir(statesDir);
|
|
244
|
+
// Append to hourly snapshots
|
|
245
|
+
const snapshotsPath = join(statesDir, "snapshots-hourly.jsonl");
|
|
246
|
+
appendFileSync(snapshotsPath, JSON.stringify(snapshot) + "\n");
|
|
247
|
+
// Update current state
|
|
248
|
+
const currentPath = join(statesDir, "current-state.json");
|
|
249
|
+
writeFileSync(currentPath, JSON.stringify(state, null, 2));
|
|
250
|
+
return snapshot;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get the most recent state snapshot
|
|
254
|
+
*/
|
|
255
|
+
getCurrentState() {
|
|
256
|
+
const currentPath = join(this.dataDir, "states", "current-state.json");
|
|
257
|
+
if (!existsSync(currentPath))
|
|
258
|
+
return null;
|
|
259
|
+
try {
|
|
260
|
+
return JSON.parse(readFileSync(currentPath, "utf-8"));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get recent state snapshots
|
|
268
|
+
*/
|
|
269
|
+
getRecentSnapshots(limit = 24) {
|
|
270
|
+
const snapshotsPath = join(this.dataDir, "states", "snapshots-hourly.jsonl");
|
|
271
|
+
const snapshots = parseJSONLFile(snapshotsPath);
|
|
272
|
+
return snapshots
|
|
273
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
274
|
+
.slice(0, limit);
|
|
275
|
+
}
|
|
276
|
+
// ==========================================================================
|
|
277
|
+
// Predictions Cache
|
|
278
|
+
// ==========================================================================
|
|
279
|
+
/**
|
|
280
|
+
* Cache a prediction result
|
|
281
|
+
* @param state - The input state
|
|
282
|
+
* @param action - The input action
|
|
283
|
+
* @param prediction - The prediction result
|
|
284
|
+
*/
|
|
285
|
+
cachePrediction(state, action, prediction) {
|
|
286
|
+
const key = generateId("pc", state.timestamp, state.agentId, action.actionType);
|
|
287
|
+
const cached = {
|
|
288
|
+
key,
|
|
289
|
+
prediction,
|
|
290
|
+
cachedAt: Date.now(),
|
|
291
|
+
hitCount: 0,
|
|
292
|
+
};
|
|
293
|
+
const predictionsDir = join(this.dataDir, "predictions");
|
|
294
|
+
ensureDir(predictionsDir);
|
|
295
|
+
const cachePath = join(predictionsDir, "dynamics-cache.jsonl");
|
|
296
|
+
appendFileSync(cachePath, JSON.stringify(cached) + "\n");
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Look up a cached prediction
|
|
300
|
+
* @param state - The input state
|
|
301
|
+
* @param action - The input action
|
|
302
|
+
* @returns Cached prediction if found and not expired
|
|
303
|
+
*/
|
|
304
|
+
getCachedPrediction(state, action) {
|
|
305
|
+
const cachePath = join(this.dataDir, "predictions", "dynamics-cache.jsonl");
|
|
306
|
+
if (!existsSync(cachePath))
|
|
307
|
+
return null;
|
|
308
|
+
const entries = parseJSONLFile(cachePath);
|
|
309
|
+
const now = Date.now();
|
|
310
|
+
// Find matching entry that hasn't expired
|
|
311
|
+
const key = generateId("pc", state.timestamp, state.agentId, action.actionType);
|
|
312
|
+
const entry = entries.find(e => e.key === key &&
|
|
313
|
+
(now - e.cachedAt) < this.config.predictionCacheTtl);
|
|
314
|
+
return entry?.prediction || null;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get all cached predictions (for analysis/debugging)
|
|
318
|
+
*/
|
|
319
|
+
getAllCachedPredictions() {
|
|
320
|
+
const cachePath = join(this.dataDir, "predictions", "dynamics-cache.jsonl");
|
|
321
|
+
return parseJSONLFile(cachePath);
|
|
322
|
+
}
|
|
323
|
+
// ==========================================================================
|
|
324
|
+
// Invariant Violations
|
|
325
|
+
// ==========================================================================
|
|
326
|
+
/**
|
|
327
|
+
* Record an invariant violation
|
|
328
|
+
* @param violation - The violation to record
|
|
329
|
+
* @param transitionId - Optional transition ID that caused the violation
|
|
330
|
+
*/
|
|
331
|
+
recordInvariantViolation(violation, transitionId) {
|
|
332
|
+
const entry = {
|
|
333
|
+
...violation,
|
|
334
|
+
detectedAt: violation.detectedAt || Date.now(),
|
|
335
|
+
transitionId,
|
|
336
|
+
};
|
|
337
|
+
const invariantsDir = join(this.dataDir, "invariants");
|
|
338
|
+
ensureDir(invariantsDir);
|
|
339
|
+
const violationsPath = join(invariantsDir, "violations.jsonl");
|
|
340
|
+
appendFileSync(violationsPath, JSON.stringify(entry) + "\n");
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get recent invariant violations
|
|
344
|
+
*/
|
|
345
|
+
getRecentViolations(options = {}) {
|
|
346
|
+
const violationsPath = join(this.dataDir, "invariants", "violations.jsonl");
|
|
347
|
+
let violations = parseJSONLFile(violationsPath);
|
|
348
|
+
if (options.invariantId) {
|
|
349
|
+
violations = violations.filter(v => v.invariantId === options.invariantId);
|
|
350
|
+
}
|
|
351
|
+
if (options.severity) {
|
|
352
|
+
violations = violations.filter(v => v.severity === options.severity);
|
|
353
|
+
}
|
|
354
|
+
if (options.since) {
|
|
355
|
+
violations = violations.filter(v => (v.detectedAt || 0) >= options.since);
|
|
356
|
+
}
|
|
357
|
+
violations.sort((a, b) => (b.detectedAt || 0) - (a.detectedAt || 0));
|
|
358
|
+
if (options.limit) {
|
|
359
|
+
violations = violations.slice(0, options.limit);
|
|
360
|
+
}
|
|
361
|
+
return violations;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Record a recovery action taken after a violation
|
|
365
|
+
*/
|
|
366
|
+
recordRecovery(violation, recoveryAction, success) {
|
|
367
|
+
const entry = {
|
|
368
|
+
violationId: generateId("vr", violation.invariantId, violation.detectedAt),
|
|
369
|
+
invariantId: violation.invariantId,
|
|
370
|
+
recoveryAction,
|
|
371
|
+
success,
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
};
|
|
374
|
+
const invariantsDir = join(this.dataDir, "invariants");
|
|
375
|
+
ensureDir(invariantsDir);
|
|
376
|
+
const recoveryPath = join(invariantsDir, "recovery-log.jsonl");
|
|
377
|
+
appendFileSync(recoveryPath, JSON.stringify(entry) + "\n");
|
|
378
|
+
}
|
|
379
|
+
// ==========================================================================
|
|
380
|
+
// Counterfactuals
|
|
381
|
+
// ==========================================================================
|
|
382
|
+
/**
|
|
383
|
+
* Store a counterfactual scenario
|
|
384
|
+
*/
|
|
385
|
+
recordCounterfactual(scenario) {
|
|
386
|
+
const predictionsDir = join(this.dataDir, "predictions");
|
|
387
|
+
ensureDir(predictionsDir);
|
|
388
|
+
const counterfactualsPath = join(predictionsDir, "counterfactuals.jsonl");
|
|
389
|
+
appendFileSync(counterfactualsPath, JSON.stringify(scenario) + "\n");
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get counterfactuals for a transition
|
|
393
|
+
*/
|
|
394
|
+
getCounterfactuals(baseTransitionId) {
|
|
395
|
+
const counterfactualsPath = join(this.dataDir, "predictions", "counterfactuals.jsonl");
|
|
396
|
+
const all = parseJSONLFile(counterfactualsPath);
|
|
397
|
+
return all.filter(c => c.baseTransitionId === baseTransitionId);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get all counterfactuals (for analysis)
|
|
401
|
+
*/
|
|
402
|
+
getAllCounterfactuals() {
|
|
403
|
+
const counterfactualsPath = join(this.dataDir, "predictions", "counterfactuals.jsonl");
|
|
404
|
+
return parseJSONLFile(counterfactualsPath);
|
|
405
|
+
}
|
|
406
|
+
// ==========================================================================
|
|
407
|
+
// Statistics and Maintenance
|
|
408
|
+
// ==========================================================================
|
|
409
|
+
/**
|
|
410
|
+
* Get storage statistics
|
|
411
|
+
*/
|
|
412
|
+
getStats() {
|
|
413
|
+
const transitions = this.getTransitions({ limit: 100000 });
|
|
414
|
+
const snapshots = this.getRecentSnapshots(100000);
|
|
415
|
+
const predictions = this.getAllCachedPredictions();
|
|
416
|
+
const violations = this.getRecentViolations({ limit: 100000 });
|
|
417
|
+
const counterfactuals = this.getAllCounterfactuals();
|
|
418
|
+
return {
|
|
419
|
+
transitionCount: transitions.length,
|
|
420
|
+
snapshotCount: snapshots.length,
|
|
421
|
+
cachedPredictions: predictions.length,
|
|
422
|
+
violationCount: violations.length,
|
|
423
|
+
counterfactualCount: counterfactuals.length,
|
|
424
|
+
oldestTransition: transitions.length > 0
|
|
425
|
+
? Math.min(...transitions.map(t => t.priorState.timestamp))
|
|
426
|
+
: undefined,
|
|
427
|
+
newestTransition: transitions.length > 0
|
|
428
|
+
? Math.max(...transitions.map(t => t.posteriorState.timestamp))
|
|
429
|
+
: undefined,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Prune old data to keep storage bounded
|
|
434
|
+
*/
|
|
435
|
+
pruneOldData(options = {}) {
|
|
436
|
+
const maxAgeDays = options.maxAgeDays || 30;
|
|
437
|
+
const cutoffTime = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
|
|
438
|
+
const cutoffDate = getDateString(cutoffTime);
|
|
439
|
+
// Prune old transition files
|
|
440
|
+
const transitionsDir = join(this.dataDir, "transitions");
|
|
441
|
+
if (existsSync(transitionsDir)) {
|
|
442
|
+
const files = readdirSync(transitionsDir)
|
|
443
|
+
.filter(f => f.endsWith(".jsonl"));
|
|
444
|
+
let pruned = 0;
|
|
445
|
+
for (const file of files) {
|
|
446
|
+
const date = file.replace(".jsonl", "");
|
|
447
|
+
if (date < cutoffDate) {
|
|
448
|
+
const fs = require("fs");
|
|
449
|
+
fs.unlinkSync(join(transitionsDir, file));
|
|
450
|
+
pruned++;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Rebuild index
|
|
454
|
+
if (pruned > 0) {
|
|
455
|
+
const remaining = this.getTransitions();
|
|
456
|
+
const index = {};
|
|
457
|
+
for (const t of remaining) {
|
|
458
|
+
index[t.id] = {
|
|
459
|
+
date: getDateString(t.posteriorState.timestamp),
|
|
460
|
+
agentId: t.action.agentId,
|
|
461
|
+
actionType: t.action.actionType,
|
|
462
|
+
timestamp: t.posteriorState.timestamp,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const indexPath = join(transitionsDir, "index.json");
|
|
466
|
+
writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
467
|
+
return { pruned };
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return { pruned: 0 };
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Get the data directory path
|
|
474
|
+
*/
|
|
475
|
+
getDataDir() {
|
|
476
|
+
return this.dataDir;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Check if the store has any data
|
|
480
|
+
*/
|
|
481
|
+
hasData() {
|
|
482
|
+
return existsSync(this.dataDir) &&
|
|
483
|
+
(existsSync(join(this.dataDir, "transitions")) ||
|
|
484
|
+
existsSync(join(this.dataDir, "states")));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
//# sourceMappingURL=world-model-store.js.map
|