@modelprofile.com/flexharness 11.3.0 → 12.0.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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.core.d.ts +4 -0
- package/dist_ts/classes.flexharness.core.js +69 -25
- package/dist_ts/classes.flexharness.imports.js +4 -3
- package/dist_ts/classes.flexharness.reversions.d.ts +7 -1
- package/dist_ts/classes.flexharness.reversions.js +43 -29
- package/dist_ts/classes.flexharness.sessions.js +2 -6
- package/dist_ts/classes.stores.js +20 -18
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/interfaces.d.ts +24 -2
- package/dist_ts/interfaces.flexharness.d.ts +8 -1
- package/dist_ts/interfaces.js +1 -1
- package/dist_ts/plugins.d.ts +3 -3
- package/dist_ts/plugins.js +3 -3
- package/dist_ts/utils.projectionchange.d.ts +34 -0
- package/dist_ts/utils.projectionchange.js +135 -0
- package/dist_ts/utils.storecodecs.d.ts +10 -3
- package/dist_ts/utils.storecodecs.js +29 -4
- package/dist_ts/utils.storerules.d.ts +1 -1
- package/dist_ts/utils.storerules.js +1 -1
- package/dist_ts_migration/v3_legacyflexharness.js +6 -5
- package/dist_ts_stores_nosqldb/classes.nosqldbstores.d.ts +5 -4
- package/dist_ts_stores_nosqldb/classes.nosqldbstores.js +56 -54
- package/dist_ts_stores_nosqldb/classes.recordstore.d.ts +176 -19
- package/dist_ts_stores_nosqldb/classes.recordstore.js +392 -67
- package/dist_ts_stores_nosqldb/index.d.ts +1 -1
- package/dist_ts_stores_nosqldb/index.js +2 -2
- package/dist_ts_stores_nosqldb/plugins.d.ts +1 -1
- package/dist_ts_stores_nosqldb/plugins.js +1 -1
- package/dist_ts_stores_testing/contract.d.ts +33 -0
- package/dist_ts_stores_testing/contract.js +566 -0
- package/dist_ts_stores_testing/fixtures.d.ts +20 -0
- package/dist_ts_stores_testing/fixtures.js +112 -0
- package/dist_ts_stores_testing/index.d.ts +2 -0
- package/dist_ts_stores_testing/index.js +3 -0
- package/dist_ts_stores_testing/plugins.d.ts +5 -0
- package/dist_ts_stores_testing/plugins.js +7 -0
- package/package.json +9 -3
- package/readme.md +6 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.core.ts +74 -24
- package/ts/classes.flexharness.imports.ts +3 -2
- package/ts/classes.flexharness.reversions.ts +53 -28
- package/ts/classes.flexharness.sessions.ts +2 -5
- package/ts/classes.stores.ts +40 -37
- package/ts/index.ts +5 -0
- package/ts/interfaces.flexharness.ts +8 -1
- package/ts/interfaces.ts +27 -2
- package/ts/plugins.ts +12 -7
- package/ts/readme.md +6 -3
- package/ts/utils.projectionchange.ts +190 -0
- package/ts/utils.storecodecs.ts +42 -7
- package/ts/utils.storerules.ts +2 -2
- package/ts_migration/v3_legacyflexharness.ts +11 -6
- package/ts_stores_nosqldb/classes.nosqldbstores.ts +79 -71
- package/ts_stores_nosqldb/classes.recordstore.ts +491 -66
- package/ts_stores_nosqldb/index.ts +1 -0
- package/ts_stores_nosqldb/plugins.ts +4 -3
- package/ts_stores_nosqldb/readme.md +6 -2
- package/ts_stores_testing/contract.ts +776 -0
- package/ts_stores_testing/fixtures.ts +127 -0
- package/ts_stores_testing/index.ts +17 -0
- package/ts_stores_testing/plugins.ts +20 -0
- package/ts_stores_testing/readme.md +62 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import type { IFlexMessage, IFlexPermissionSnapshot, IFlexProjectionSnapshotCurrent, IFlexReversionSegment, IFlexScopeSnapshot } from '../dist_ts/interfaces.js';
|
|
3
|
+
/** A scope snapshot with one idle session. */
|
|
4
|
+
export declare function scopeSnapshot(revision: number, sessionId?: string): IFlexScopeSnapshot;
|
|
5
|
+
/** An empty projection. */
|
|
6
|
+
export declare function projectionSnapshot(revision: number): IFlexProjectionSnapshotCurrent;
|
|
7
|
+
/** The user and assistant message of one completed run of `session-1`. */
|
|
8
|
+
export declare function runMessages(runId: string): IFlexMessage[];
|
|
9
|
+
/** The reversion segment of one completed run, without a workspace capture. */
|
|
10
|
+
export declare function runSegment(runId: string): IFlexReversionSegment;
|
|
11
|
+
/** A projection of completed runs of `session-1`, each with its messages and reversion segment. */
|
|
12
|
+
export declare function projectionWithRuns(revision: number, runIds: string[]): IFlexProjectionSnapshotCurrent;
|
|
13
|
+
/** A permission snapshot that remembers one approval. */
|
|
14
|
+
export declare function permissionSnapshot(revision: number): IFlexPermissionSnapshot;
|
|
15
|
+
/** A system message event. */
|
|
16
|
+
export declare function agentEvent(id?: string, content?: string): plugins.ISystemMessageEvent;
|
|
17
|
+
/** The Agent event change that appends `events`. */
|
|
18
|
+
export declare function appendEvents(...events: plugins.TAgentEvent[]): plugins.IAgentEventChange;
|
|
19
|
+
/** A finished shell job. */
|
|
20
|
+
export declare function finishedJob(executionId?: string): plugins.IToolJobState;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
/** A scope snapshot with one idle session. */
|
|
3
|
+
export function scopeSnapshot(revision, sessionId = 'session-1') {
|
|
4
|
+
return {
|
|
5
|
+
schemaVersion: 1,
|
|
6
|
+
revision,
|
|
7
|
+
sessions: [
|
|
8
|
+
{
|
|
9
|
+
scopeId: 'scope-1',
|
|
10
|
+
sessionId,
|
|
11
|
+
createdAt: '2026-08-05T00:00:00.000Z',
|
|
12
|
+
updatedAt: '2026-08-05T00:00:00.000Z',
|
|
13
|
+
status: 'idle',
|
|
14
|
+
activity: { status: 'idle' },
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
tombstones: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** An empty projection. */
|
|
21
|
+
export function projectionSnapshot(revision) {
|
|
22
|
+
return {
|
|
23
|
+
schemaVersion: 3,
|
|
24
|
+
revision,
|
|
25
|
+
messages: [],
|
|
26
|
+
stagedTerminals: [],
|
|
27
|
+
reversionSegments: [],
|
|
28
|
+
revertCursor: 0,
|
|
29
|
+
excludedRunIds: [],
|
|
30
|
+
pendingReversionReleases: [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** The user and assistant message of one completed run of `session-1`. */
|
|
34
|
+
export function runMessages(runId) {
|
|
35
|
+
return [{
|
|
36
|
+
messageId: `user-${runId}`,
|
|
37
|
+
sessionId: 'session-1',
|
|
38
|
+
runId,
|
|
39
|
+
role: 'user',
|
|
40
|
+
status: 'completed',
|
|
41
|
+
createdAt: '2026-08-05T00:00:00.000Z',
|
|
42
|
+
completedAt: '2026-08-05T00:00:01.000Z',
|
|
43
|
+
parts: [{ partId: `user-part-${runId}`, type: 'text', text: runId }],
|
|
44
|
+
}, {
|
|
45
|
+
messageId: `assistant-${runId}`,
|
|
46
|
+
sessionId: 'session-1',
|
|
47
|
+
runId,
|
|
48
|
+
role: 'assistant',
|
|
49
|
+
status: 'completed',
|
|
50
|
+
createdAt: '2026-08-05T00:00:00.000Z',
|
|
51
|
+
completedAt: '2026-08-05T00:00:01.000Z',
|
|
52
|
+
parts: [{ partId: `assistant-part-${runId}`, type: 'text', text: 'answer' }],
|
|
53
|
+
}];
|
|
54
|
+
}
|
|
55
|
+
/** The reversion segment of one completed run, without a workspace capture. */
|
|
56
|
+
export function runSegment(runId) {
|
|
57
|
+
return {
|
|
58
|
+
runId,
|
|
59
|
+
userMessageId: `user-${runId}`,
|
|
60
|
+
status: 'completed',
|
|
61
|
+
contextAvailable: true,
|
|
62
|
+
eventIds: [`event-${runId}`],
|
|
63
|
+
workspaceCaptured: false,
|
|
64
|
+
protocolVersion: 1,
|
|
65
|
+
provenance: 'transcript',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** A projection of completed runs of `session-1`, each with its messages and reversion segment. */
|
|
69
|
+
export function projectionWithRuns(revision, runIds) {
|
|
70
|
+
return {
|
|
71
|
+
...projectionSnapshot(revision),
|
|
72
|
+
messages: runIds.flatMap(runMessages),
|
|
73
|
+
reversionSegments: runIds.map(runSegment),
|
|
74
|
+
revertCursor: runIds.length,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** A permission snapshot that remembers one approval. */
|
|
78
|
+
export function permissionSnapshot(revision) {
|
|
79
|
+
return {
|
|
80
|
+
schemaVersion: 1,
|
|
81
|
+
revision,
|
|
82
|
+
rememberedPermissionKeys: ['shell:approved'],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** A system message event. */
|
|
86
|
+
export function agentEvent(id = 'event-1', content = 'System prompt') {
|
|
87
|
+
return plugins.createAgentEvent({
|
|
88
|
+
id,
|
|
89
|
+
timestamp: 1,
|
|
90
|
+
type: 'system-message',
|
|
91
|
+
content,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** The Agent event change that appends `events`. */
|
|
95
|
+
export function appendEvents(...events) {
|
|
96
|
+
return { remove: [], replace: [], append: events };
|
|
97
|
+
}
|
|
98
|
+
/** A finished shell job. */
|
|
99
|
+
export function finishedJob(executionId = 'job-1') {
|
|
100
|
+
return {
|
|
101
|
+
executionId,
|
|
102
|
+
type: 'shell',
|
|
103
|
+
state: 'finished',
|
|
104
|
+
exitCode: 0,
|
|
105
|
+
stdout: 'ok',
|
|
106
|
+
stderr: '',
|
|
107
|
+
startedAt: 1,
|
|
108
|
+
updatedAt: 2,
|
|
109
|
+
finishedAt: 2,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZml4dHVyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19zdG9yZXNfdGVzdGluZy9maXh0dXJlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQVN4Qyw4Q0FBOEM7QUFDOUMsTUFBTSxVQUFVLGFBQWEsQ0FBQyxRQUFnQixFQUFFLFNBQVMsR0FBRyxXQUFXO0lBQ3JFLE9BQU87UUFDTCxhQUFhLEVBQUUsQ0FBQztRQUNoQixRQUFRO1FBQ1IsUUFBUSxFQUFFO1lBQ1I7Z0JBQ0UsT0FBTyxFQUFFLFNBQVM7Z0JBQ2xCLFNBQVM7Z0JBQ1QsU0FBUyxFQUFFLDBCQUEwQjtnQkFDckMsU0FBUyxFQUFFLDBCQUEwQjtnQkFDckMsTUFBTSxFQUFFLE1BQU07Z0JBQ2QsUUFBUSxFQUFFLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRTthQUM3QjtTQUNGO1FBQ0QsVUFBVSxFQUFFLEVBQUU7S0FDZixDQUFDO0FBQ0osQ0FBQztBQUVELDJCQUEyQjtBQUMzQixNQUFNLFVBQVUsa0JBQWtCLENBQUMsUUFBZ0I7SUFDakQsT0FBTztRQUNMLGFBQWEsRUFBRSxDQUFDO1FBQ2hCLFFBQVE7UUFDUixRQUFRLEVBQUUsRUFBRTtRQUNaLGVBQWUsRUFBRSxFQUFFO1FBQ25CLGlCQUFpQixFQUFFLEVBQUU7UUFDckIsWUFBWSxFQUFFLENBQUM7UUFDZixjQUFjLEVBQUUsRUFBRTtRQUNsQix3QkFBd0IsRUFBRSxFQUFFO0tBQzdCLENBQUM7QUFDSixDQUFDO0FBRUQsMEVBQTBFO0FBQzFFLE1BQU0sVUFBVSxXQUFXLENBQUMsS0FBYTtJQUN2QyxPQUFPLENBQUM7WUFDTixTQUFTLEVBQUUsUUFBUSxLQUFLLEVBQUU7WUFDMUIsU0FBUyxFQUFFLFdBQVc7WUFDdEIsS0FBSztZQUNMLElBQUksRUFBRSxNQUFNO1lBQ1osTUFBTSxFQUFFLFdBQVc7WUFDbkIsU0FBUyxFQUFFLDBCQUEwQjtZ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
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { createFlexStoreContractTests, type IFlexStoreContractBackend, type IFlexStoreContractFixture, type IFlexStoreContractTest, } from './contract.js';
|
|
2
|
+
export { agentEvent, appendEvents, finishedJob, permissionSnapshot, projectionSnapshot, projectionWithRuns, runMessages, runSegment, scopeSnapshot, } from './fixtures.js';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createFlexStoreContractTests, } from './contract.js';
|
|
2
|
+
export { agentEvent, appendEvents, finishedJob, permissionSnapshot, projectionSnapshot, projectionWithRuns, runMessages, runSegment, scopeSnapshot, } from './fixtures.js';
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c19zdG9yZXNfdGVzdGluZy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsNEJBQTRCLEdBSTdCLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFDTCxVQUFVLEVBQ1YsWUFBWSxFQUNaLFdBQVcsRUFDWCxrQkFBa0IsRUFDbEIsa0JBQWtCLEVBQ2xCLGtCQUFrQixFQUNsQixXQUFXLEVBQ1gsVUFBVSxFQUNWLGFBQWEsR0FDZCxNQUFNLGVBQWUsQ0FBQyJ9
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { strict as assert } from 'node:assert';
|
|
2
|
+
export { assert };
|
|
3
|
+
import { AgentEventStoreConflictError, ToolJobStoreConflictError, createAgentEvent } from '@modelprofile.com/flexharness-agent';
|
|
4
|
+
export { AgentEventStoreConflictError, ToolJobStoreConflictError, createAgentEvent };
|
|
5
|
+
export type { IAgentEventArchive, IAgentEventChange, ISystemMessageEvent, IToolJobState, TAgentEvent, } from '@modelprofile.com/flexharness-agent';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// node native scope
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
export { assert };
|
|
4
|
+
// @modelprofile.com scope
|
|
5
|
+
import { AgentEventStoreConflictError, ToolJobStoreConflictError, createAgentEvent, } from '@modelprofile.com/flexharness-agent';
|
|
6
|
+
export { AgentEventStoreConflictError, ToolJobStoreConflictError, createAgentEvent };
|
|
7
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzX3N0b3Jlc190ZXN0aW5nL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sRUFBRSxNQUFNLElBQUksTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRS9DLE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQztBQUVsQiwwQkFBMEI7QUFDMUIsT0FBTyxFQUNMLDRCQUE0QixFQUM1Qix5QkFBeUIsRUFDekIsZ0JBQWdCLEdBQ2pCLE1BQU0scUNBQXFDLENBQUM7QUFFN0MsT0FBTyxFQUFFLDRCQUE0QixFQUFFLHlCQUF5QixFQUFFLGdCQUFnQixFQUFFLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Task Venture Capital GmbH",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@modelprofile.com/flexharness",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "12.0.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Managed agent sessions with tools, MCP, chat, OCR, documents and browser control through focused subpath imports.",
|
|
8
8
|
"main": "./dist_ts/index.js",
|
|
@@ -80,6 +80,10 @@
|
|
|
80
80
|
"types": "./dist_ts_stores_nosqldb/index.d.ts",
|
|
81
81
|
"import": "./dist_ts_stores_nosqldb/index.js"
|
|
82
82
|
},
|
|
83
|
+
"./stores/testing": {
|
|
84
|
+
"types": "./dist_ts_stores_testing/index.d.ts",
|
|
85
|
+
"import": "./dist_ts_stores_testing/index.js"
|
|
86
|
+
},
|
|
83
87
|
"./typedrequest": {
|
|
84
88
|
"types": "./dist_ts_typedrequest/index.d.ts",
|
|
85
89
|
"import": "./dist_ts_typedrequest/index.js"
|
|
@@ -87,8 +91,8 @@
|
|
|
87
91
|
},
|
|
88
92
|
"dependencies": {
|
|
89
93
|
"@cfworker/json-schema": "^4.1.1",
|
|
90
|
-
"@modelprofile.com/flexharness-agent": "
|
|
91
|
-
"@modelprofile.com/flexharness-models": "
|
|
94
|
+
"@modelprofile.com/flexharness-agent": "12.0.0",
|
|
95
|
+
"@modelprofile.com/flexharness-models": "12.0.0",
|
|
92
96
|
"@push.rocks/smartrequest": "^5.5.1",
|
|
93
97
|
"ai": "^7.0.111",
|
|
94
98
|
"zod": "^4.6.5"
|
|
@@ -216,6 +220,8 @@
|
|
|
216
220
|
"dist_ts_chat_harness_typedsocket",
|
|
217
221
|
"ts_stores_nosqldb",
|
|
218
222
|
"dist_ts_stores_nosqldb",
|
|
223
|
+
"ts_stores_testing",
|
|
224
|
+
"dist_ts_stores_testing",
|
|
219
225
|
"ts_typedrequest",
|
|
220
226
|
"dist_ts_typedrequest",
|
|
221
227
|
"readme.md",
|
package/readme.md
CHANGED
|
@@ -68,6 +68,7 @@ or provider SDKs.
|
|
|
68
68
|
| `/mcp` | MCP clients, AI SDK tool conversion and `createMcpToolCatalog`, the tool catalog of MCP tools | `@modelcontextprotocol/sdk` |
|
|
69
69
|
| `/migration` | Existing versioned harness migrations | None |
|
|
70
70
|
| `/stores/nosqldb` | `NoSqlFlexHarnessStores`: cross-process CAS stores on `@lossless.org/client/nosqldb` | `@lossless.org/client` |
|
|
71
|
+
| `/stores/testing` | `createFlexStoreContractTests`: the contract every built-in `IFlexHarnessStores` passes, to run against a custom store with any test runner | None |
|
|
71
72
|
|
|
72
73
|
The integrations in the last column use optional peer dependencies. Install them
|
|
73
74
|
only for the entrypoints your project imports, for example:
|
|
@@ -1128,7 +1129,9 @@ Current FlexHarness persistence is separated by trust and lifecycle domain throu
|
|
|
1128
1129
|
|
|
1129
1130
|
`InMemoryFlexHarnessStores` implements all six required domains with revision-based compare-and-swap behavior for tests and ephemeral processes. It is the default when `stores` is omitted. Custom `IFlexHarnessStores` implementations must provide `projectManagement` even when project-management tools are disabled, because deletion cleanup always writes the generation fence.
|
|
1130
1131
|
|
|
1131
|
-
A custom store validates what it loads and saves with the validators the built-in stores apply. Each throws `FlexHarnessStoreFormatError` for a malformed or wrong-schema snapshot: `assertFlexScopeSnapshot()`, `assertFlexProjectionSnapshot(value, sessionId)`, which also requires every message and staged terminal to belong to `sessionId`, `assertFlexPermissionSnapshot()`, `assertFlexProjectManagementRecord()` and `assertFlexToolJobSnapshot()`. Agent event snapshots and archives have `
|
|
1132
|
+
A custom store validates what it loads and saves with the validators the built-in stores apply. Each throws `FlexHarnessStoreFormatError` for a malformed or wrong-schema snapshot: `assertFlexScopeSnapshot()`, `assertFlexProjectionSnapshot(value, sessionId)`, which also requires every message and staged terminal to belong to `sessionId`, `assertFlexPermissionSnapshot()`, `assertFlexProjectManagementRecord()` and `assertFlexToolJobSnapshot()`. Agent event snapshots and archives have `validateAgentEventSnapshot()` and `validateAgentEventArchive()` in `@modelprofile.com/flexharness-agent`.
|
|
1133
|
+
|
|
1134
|
+
Projections and Agent event histories are saved as changes, so a save costs what changed rather than the whole session. `IFlexProjectionStore.save(storageKey, sessionId, change, expectedRevision)` receives an `IFlexProjectionChange`: the schema-3 projection with `messages` and `reversionSegments` given as `IFlexListChange`s `{ start, end, items }`, each of which keeps the stored entries from `start` up to `end` and appends `items`. A list change with `start` and `end` at 0 replaces the list whole; FlexHarness sends whole lists when it cannot know which entries the store holds: for a projection it loaded in an older schema and after a save with an uncertain outcome. The Agent event store of each session receives an `IAgentEventChange` of removed, replaced and appended events, as the `@modelprofile.com/flexharness-agent` readme describes. A store that keeps snapshots checks a change's form with `assertFlexProjectionChange()` or `assertAgentEventChange()`, applies it with `applyFlexProjectionChange()` or `applyAgentEventChange()`, and validates the result as above; `createWholeProjectionChange(snapshot)` makes the change that stores a projection whole. FlexHarness freezes every message and reversion segment it saved and replaces an entry instead of editing it, which is how it tells the entries the store already holds. `/stores/testing` exports `createFlexStoreContractTests(backend)`, the cases every built-in store passes, for a custom store to run with any test runner; its readme shows how.
|
|
1132
1135
|
|
|
1133
1136
|
Custom Agent event and job providers may implement `releaseSession(storageKey, sessionId)` to release session-bound wrappers, handles, or caches without deleting durable data. FlexHarness calls these hooks only after the corresponding AgentSession or execution context has released runtime ownership. A failed release remains owned for a later retirement or disposal retry. `deleteSession()` and `deleteSessionGenerationCohort()` are the separate destructive operations for durable session data.
|
|
1134
1137
|
|
|
@@ -1145,13 +1148,13 @@ After every harness using a `JsonFileFlexHarnessStores` instance has been dispos
|
|
|
1145
1148
|
|
|
1146
1149
|
The JSON stores are explicitly not cross-process safe. When several processes can access the same storage namespace, every core `IFlexHarnessStores` domain and `stores.projectManagement` must use database-backed or equivalent cross-process CAS. Process-local CAS for the core stores or for PM alone is insufficient: session generation creation, cleanup tombstones, PM replacement, and stale-writer rejection must all retain their respective atomic preconditions across processes.
|
|
1147
1150
|
|
|
1148
|
-
`NoSqlFlexHarnessStores` from `/stores/nosqldb` is that database-backed bundle for every domain. Pass it an initialized `SmartdataDb` and `await stores.prepare()` at startup, before the first harness uses it; the subpath readme describes chunked snapshots and failure reporting.
|
|
1151
|
+
`NoSqlFlexHarnessStores` from `/stores/nosqldb` is that database-backed bundle for every domain. Pass it an initialized `SmartdataDb` and `await stores.prepare()` at startup, before the first harness uses it; the subpath readme describes chunked snapshots, change logs and failure reporting.
|
|
1149
1152
|
|
|
1150
1153
|
Direct store operations and non-run session mutations surface conflicts as `FlexHarnessStoreConflictError` or the corresponding AgentSession store conflict. Malformed, wrong-schema, or non-JSON snapshots are surfaced as `FlexHarnessStoreFormatError`. A write or deletion that changed its target but cannot confirm parent-directory durability surfaces `FlexHarnessStoreCommitUncertainError` with the affected path, operation, and cause. Run persistence failures cross the external error boundary and therefore become `FlexHarnessExternalError`. FlexHarness does not merge conflicts.
|
|
1151
1154
|
|
|
1152
1155
|
Each domain serializes its own mutations. A successful run first persists a hidden completed projection, then finalizes the canonical Agent generation as `accepted`, then promotes the hidden projection publicly. Recovery uses the canonical generation outcome to promote an accepted stage or publish a failed/cancelled projection. Failed and cancelled generations remain auditable but never enter future model context.
|
|
1153
1156
|
|
|
1154
|
-
Projection stores accept schema version 1, 2, or 3 from `load()`, but every `save()` receives the current schema-3 shape. Schema 3 records explicit reversion protocol, transcript/workspace provenance, and V2 disposition. A terminal V2 segment must be conclusively `revertible`, `no-change`, or `nonrevertible`; a pending V2 capture remains owned by its capture WAL and is never treated as transcript history.
|
|
1157
|
+
Projection stores accept schema version 1, 2, or 3 from `load()`, but every `save()` receives a change that makes the current schema-3 shape. A change to a loaded schema-1 or schema-2 projection carries both lists whole. Schema 3 records explicit reversion protocol, transcript/workspace provenance, and V2 disposition. A terminal V2 segment must be conclusively `revertible`, `no-change`, or `nonrevertible`; a pending V2 capture remains owned by its capture WAL and is never treated as transcript history.
|
|
1155
1158
|
|
|
1156
1159
|
Current schema-3 projections also persist optional `metrics` state. Older strict readers that do not recognize this key, or the token breakdown keys inside it (`reportedLifetimeInputTokens`, `reportedLifetimeCacheReadTokens`, `reportedLifetimeCacheWriteTokens`, `lifetimeTokenBreakdownComplete`), cannot reopen a projection after they have been written; downgrade or mixed-version access to that session is unsupported.
|
|
1157
1160
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
IFlexMessage,
|
|
22
22
|
IFlexPartSnapshotEvent,
|
|
23
23
|
IFlexPromptQueueLimits,
|
|
24
|
+
IFlexProjectionChange,
|
|
24
25
|
IFlexProjectionSnapshotCurrent,
|
|
25
26
|
IFlexReversionLimits,
|
|
26
27
|
IFlexReversionSegment,
|
|
@@ -51,6 +52,7 @@ import {
|
|
|
51
52
|
resolveJsonLimits,
|
|
52
53
|
} from './utils.json.js';
|
|
53
54
|
import { hydrateAgentMessages } from './utils.prompt.js';
|
|
55
|
+
import { createProjectionChange, freezeProjectionEntries } from './utils.projectionchange.js';
|
|
54
56
|
import {
|
|
55
57
|
normalizeAgentSessionPolicy,
|
|
56
58
|
normalizeBuiltInTools,
|
|
@@ -550,7 +552,7 @@ export class FlexHarnessCore<TScope> {
|
|
|
550
552
|
rememberedPermissionKeys: new Set(),
|
|
551
553
|
permissionRevision: 0,
|
|
552
554
|
permissionQueue: Promise.resolve(),
|
|
553
|
-
eventStore: undefined as unknown as plugins.
|
|
555
|
+
eventStore: undefined as unknown as plugins.IAgentEventStore,
|
|
554
556
|
agentSession: unavailable,
|
|
555
557
|
agentSessionAbortCompleted: true,
|
|
556
558
|
agentSessionCloseCompleted: true,
|
|
@@ -699,6 +701,8 @@ export class FlexHarnessCore<TScope> {
|
|
|
699
701
|
terminalPromptQueueEntries: new Map(),
|
|
700
702
|
outstandingPromptBytes: 0,
|
|
701
703
|
};
|
|
704
|
+
freezeProjectionEntries(stored);
|
|
705
|
+
if (currentProjection) this.rememberSavedProjectionLists(stored);
|
|
702
706
|
if (currentProjection?.metrics === undefined
|
|
703
707
|
|| stored.metrics.pendingGenerationId !== undefined
|
|
704
708
|
|| stored.metrics.pendingCompactionId !== undefined
|
|
@@ -971,18 +975,20 @@ export class FlexHarnessCore<TScope> {
|
|
|
971
975
|
if (stored.stagedTerminals.length > 0) stored.stagedTerminals = [];
|
|
972
976
|
const activeEvents = stored.agentSession.getEvents();
|
|
973
977
|
const activeEventIds = new Set(activeEvents.map((event) => event.id));
|
|
974
|
-
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
if (segment.status !== 'capturing') {
|
|
978
|
+
stored.reversionSegments = stored.reversionSegments.map((segment) => {
|
|
979
|
+
const contextAvailable = segment.contextAvailable
|
|
980
|
+
&& (segment.eventIds.length === 0 || segment.eventIds.some((id) => activeEventIds.has(id)));
|
|
981
|
+
let status = segment.status;
|
|
982
|
+
if (status !== 'capturing') {
|
|
980
983
|
const outcome = outcomes.get(segment.runId);
|
|
981
|
-
if (outcome === 'accepted')
|
|
982
|
-
else if (outcome === 'rejected')
|
|
983
|
-
else if (outcome === 'interrupted' ||
|
|
984
|
+
if (outcome === 'accepted') status = 'completed';
|
|
985
|
+
else if (outcome === 'rejected') status = 'failed';
|
|
986
|
+
else if (outcome === 'interrupted' || contextAvailable) status = 'cancelled';
|
|
984
987
|
}
|
|
985
|
-
|
|
988
|
+
return contextAvailable === segment.contextAvailable && status === segment.status
|
|
989
|
+
? segment
|
|
990
|
+
: { ...segment, contextAvailable, status };
|
|
991
|
+
});
|
|
986
992
|
stored.revertCursor = stored.reversionSegments
|
|
987
993
|
.slice(0, reversionVisibilityBoundary)
|
|
988
994
|
.filter((segment) => segment.status === 'completed')
|
|
@@ -1032,17 +1038,19 @@ export class FlexHarnessCore<TScope> {
|
|
|
1032
1038
|
});
|
|
1033
1039
|
if (projectionChanged) {
|
|
1034
1040
|
const expected = stored.projectionRevision;
|
|
1035
|
-
|
|
1041
|
+
freezeProjectionEntries(stored);
|
|
1042
|
+
const change = this.createProjectionChange(stored, expected + 1);
|
|
1036
1043
|
await this.stores.projections.save(
|
|
1037
1044
|
storageKey,
|
|
1038
1045
|
stored.session.sessionId,
|
|
1039
|
-
|
|
1046
|
+
change,
|
|
1040
1047
|
expected,
|
|
1041
1048
|
);
|
|
1042
1049
|
stored.projectionSchemaVersion = 3;
|
|
1043
1050
|
delete stored.projectionBaseline;
|
|
1051
|
+
this.rememberSavedProjectionLists(stored);
|
|
1044
1052
|
stored.projectionReconciliationRequired = false;
|
|
1045
|
-
stored.projectionRevision =
|
|
1053
|
+
stored.projectionRevision = change.revision;
|
|
1046
1054
|
}
|
|
1047
1055
|
return beforeSession !== JSON.stringify(stored.session);
|
|
1048
1056
|
}
|
|
@@ -1239,9 +1247,10 @@ export class FlexHarnessCore<TScope> {
|
|
|
1239
1247
|
? undefined
|
|
1240
1248
|
: cloneSerializable(stored.projectionBaseline);
|
|
1241
1249
|
const beforeMetrics = cloneSerializable(stored.metrics);
|
|
1242
|
-
|
|
1250
|
+
// List entries are frozen and replaced rather than edited, so copies of the lists suffice.
|
|
1251
|
+
const beforeMessages = stored.messages.slice();
|
|
1243
1252
|
const beforeStages = cloneSerializable(stored.stagedTerminals);
|
|
1244
|
-
const beforeSegments =
|
|
1253
|
+
const beforeSegments = stored.reversionSegments.slice();
|
|
1245
1254
|
const beforeCursor = stored.revertCursor;
|
|
1246
1255
|
const beforeExcludedRunIds = cloneSerializable(stored.excludedRunIds);
|
|
1247
1256
|
const beforePendingReversion = stored.pendingReversion === undefined
|
|
@@ -1285,18 +1294,20 @@ export class FlexHarnessCore<TScope> {
|
|
|
1285
1294
|
restore();
|
|
1286
1295
|
throw error;
|
|
1287
1296
|
}
|
|
1288
|
-
|
|
1297
|
+
freezeProjectionEntries(stored);
|
|
1298
|
+
const revision = beforeRevision + 1;
|
|
1289
1299
|
try {
|
|
1290
1300
|
await this.stores.projections.save(
|
|
1291
1301
|
state.storageKey,
|
|
1292
1302
|
stored.session.sessionId,
|
|
1293
|
-
|
|
1303
|
+
this.createProjectionChange(stored, revision),
|
|
1294
1304
|
beforeRevision,
|
|
1295
1305
|
);
|
|
1296
1306
|
stored.projectionSchemaVersion = 3;
|
|
1297
1307
|
delete stored.projectionBaseline;
|
|
1308
|
+
this.rememberSavedProjectionLists(stored);
|
|
1298
1309
|
stored.projectionReconciliationRequired = false;
|
|
1299
|
-
stored.projectionRevision =
|
|
1310
|
+
stored.projectionRevision = revision;
|
|
1300
1311
|
return result;
|
|
1301
1312
|
} catch (error) {
|
|
1302
1313
|
if (error instanceof FlexHarnessStoreConflictError) {
|
|
@@ -1305,11 +1316,13 @@ export class FlexHarnessCore<TScope> {
|
|
|
1305
1316
|
}
|
|
1306
1317
|
if (error instanceof FlexHarnessStoreCommitUncertainError) {
|
|
1307
1318
|
stored.projectionSchemaVersion = 3;
|
|
1319
|
+
delete stored.savedProjectionLists;
|
|
1308
1320
|
stored.projectionReconciliationRequired = true;
|
|
1309
|
-
stored.projectionRevision =
|
|
1321
|
+
stored.projectionRevision = revision;
|
|
1310
1322
|
state.lifecycle = 'fenced';
|
|
1311
1323
|
throw error;
|
|
1312
1324
|
}
|
|
1325
|
+
const snapshot = this.createProjectionSnapshot(stored, revision);
|
|
1313
1326
|
let current: TFlexProjectionSnapshot | undefined;
|
|
1314
1327
|
let reconciliationError: unknown;
|
|
1315
1328
|
try {
|
|
@@ -1323,8 +1336,9 @@ export class FlexHarnessCore<TScope> {
|
|
|
1323
1336
|
if (current && JSON.stringify(current) === JSON.stringify(snapshot)) {
|
|
1324
1337
|
stored.projectionSchemaVersion = 3;
|
|
1325
1338
|
delete stored.projectionBaseline;
|
|
1339
|
+
this.rememberSavedProjectionLists(stored);
|
|
1326
1340
|
stored.projectionReconciliationRequired = false;
|
|
1327
|
-
stored.projectionRevision =
|
|
1341
|
+
stored.projectionRevision = revision;
|
|
1328
1342
|
throw error;
|
|
1329
1343
|
}
|
|
1330
1344
|
if (
|
|
@@ -1335,8 +1349,9 @@ export class FlexHarnessCore<TScope> {
|
|
|
1335
1349
|
throw error;
|
|
1336
1350
|
}
|
|
1337
1351
|
stored.projectionSchemaVersion = 3;
|
|
1352
|
+
delete stored.savedProjectionLists;
|
|
1338
1353
|
stored.projectionReconciliationRequired = true;
|
|
1339
|
-
stored.projectionRevision =
|
|
1354
|
+
stored.projectionRevision = revision;
|
|
1340
1355
|
state.lifecycle = 'fenced';
|
|
1341
1356
|
throw reconciliationError === undefined
|
|
1342
1357
|
? error
|
|
@@ -1380,6 +1395,35 @@ export class FlexHarnessCore<TScope> {
|
|
|
1380
1395
|
};
|
|
1381
1396
|
}
|
|
1382
1397
|
|
|
1398
|
+
/** The change from the lists the store holds to the session's projection at `revision`. */
|
|
1399
|
+
private createProjectionChange(
|
|
1400
|
+
stored: IStoredSessionState,
|
|
1401
|
+
revision: number,
|
|
1402
|
+
): IFlexProjectionChange {
|
|
1403
|
+
return createProjectionChange(stored.savedProjectionLists, {
|
|
1404
|
+
schemaVersion: 3,
|
|
1405
|
+
revision,
|
|
1406
|
+
metrics: cloneSerializable(stored.metrics),
|
|
1407
|
+
messages: stored.messages,
|
|
1408
|
+
stagedTerminals: cloneSerializable(stored.stagedTerminals),
|
|
1409
|
+
reversionSegments: stored.reversionSegments,
|
|
1410
|
+
revertCursor: stored.revertCursor,
|
|
1411
|
+
excludedRunIds: cloneSerializable(stored.excludedRunIds),
|
|
1412
|
+
...(stored.pendingReversion === undefined
|
|
1413
|
+
? {}
|
|
1414
|
+
: { pendingReversion: cloneSerializable(stored.pendingReversion) }),
|
|
1415
|
+
pendingReversionReleases: cloneSerializable(stored.pendingReversionReleases),
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/** Records the session's lists as the ones the store now holds. */
|
|
1420
|
+
private rememberSavedProjectionLists(stored: IStoredSessionState): void {
|
|
1421
|
+
stored.savedProjectionLists = {
|
|
1422
|
+
messages: stored.messages.slice(),
|
|
1423
|
+
reversionSegments: stored.reversionSegments.slice(),
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1383
1427
|
public upgradeProjectionSnapshot(
|
|
1384
1428
|
projection: TFlexProjectionSnapshot,
|
|
1385
1429
|
): IFlexProjectionSnapshotCurrent {
|
|
@@ -1443,8 +1487,14 @@ export class FlexHarnessCore<TScope> {
|
|
|
1443
1487
|
if (current.pendingReversion === undefined) delete stored.pendingReversion;
|
|
1444
1488
|
else stored.pendingReversion = current.pendingReversion;
|
|
1445
1489
|
stored.pendingReversionReleases = current.pendingReversionReleases;
|
|
1446
|
-
|
|
1447
|
-
|
|
1490
|
+
freezeProjectionEntries(stored);
|
|
1491
|
+
if (projection.schemaVersion === 3) {
|
|
1492
|
+
delete stored.projectionBaseline;
|
|
1493
|
+
this.rememberSavedProjectionLists(stored);
|
|
1494
|
+
} else {
|
|
1495
|
+
stored.projectionBaseline = cloneSerializable(projection);
|
|
1496
|
+
delete stored.savedProjectionLists;
|
|
1497
|
+
}
|
|
1448
1498
|
stored.projectionSchemaVersion = projection.schemaVersion;
|
|
1449
1499
|
stored.projectionRevision = projection.revision;
|
|
1450
1500
|
stored.projectionReconciliationRequired = false;
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type TFlexBeginSessionImportResult,
|
|
18
18
|
} from './interfaces.js';
|
|
19
19
|
import { cloneSerializable } from './utils.json.js';
|
|
20
|
+
import { createWholeProjectionChange } from './utils.projectionchange.js';
|
|
20
21
|
import { publicSnapshot } from './utils.transfer.js';
|
|
21
22
|
import {
|
|
22
23
|
normalizeBeginSessionImportOptions,
|
|
@@ -231,7 +232,7 @@ export class FlexHarnessSessionImports<TScope> {
|
|
|
231
232
|
});
|
|
232
233
|
});
|
|
233
234
|
reserved = true;
|
|
234
|
-
await this.core.stores.projections.save(state.storageKey, sessionId, {
|
|
235
|
+
await this.core.stores.projections.save(state.storageKey, sessionId, createWholeProjectionChange({
|
|
235
236
|
schemaVersion: 3,
|
|
236
237
|
revision: 1,
|
|
237
238
|
metrics: createSessionMetricsState(false),
|
|
@@ -241,7 +242,7 @@ export class FlexHarnessSessionImports<TScope> {
|
|
|
241
242
|
revertCursor: 0,
|
|
242
243
|
excludedRunIds: [],
|
|
243
244
|
pendingReversionReleases: [],
|
|
244
|
-
}, 0);
|
|
245
|
+
}), 0);
|
|
245
246
|
const runtime = await this.core.loadSessionRuntime(state, metadata, scopeId, scope);
|
|
246
247
|
loaded = runtime;
|
|
247
248
|
await this.core.mutateScope(state, () => {
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
type TJsonValue,
|
|
29
29
|
} from './interfaces.js';
|
|
30
30
|
import { cloneSerializable, normalizeJsonValue } from './utils.json.js';
|
|
31
|
+
import { createWholeProjectionChange } from './utils.projectionchange.js';
|
|
31
32
|
import { publicSnapshot } from './utils.transfer.js';
|
|
32
33
|
import type {
|
|
33
34
|
TCanonicalOutcome,
|
|
@@ -402,13 +403,8 @@ export class FlexHarnessReversions<TScope> {
|
|
|
402
403
|
&& !segment.eventIds.some((eventId) => activeEvents.has(eventId)))) return false;
|
|
403
404
|
let branchCommitted = false;
|
|
404
405
|
await this.core.mutateProjection(state, stored, () => {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
segment.contextAvailable
|
|
408
|
-
&& segment.eventIds.length > 0
|
|
409
|
-
&& !segment.eventIds.some((eventId) => activeEvents.has(eventId))
|
|
410
|
-
) segment.contextAvailable = false;
|
|
411
|
-
}
|
|
406
|
+
this.markReversionContextUnavailable(stored, (segment) =>
|
|
407
|
+
segment.eventIds.length > 0 && !segment.eventIds.some((eventId) => activeEvents.has(eventId)));
|
|
412
408
|
branchCommitted = this.commitArchivedHiddenBranchState(stored);
|
|
413
409
|
this.pruneReversionState(stored, false, true);
|
|
414
410
|
}, true);
|
|
@@ -618,16 +614,22 @@ export class FlexHarnessReversions<TScope> {
|
|
|
618
614
|
return;
|
|
619
615
|
}
|
|
620
616
|
await this.core.mutateProjection(state, stored, () => {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
: '
|
|
626
|
-
|
|
627
|
-
|
|
617
|
+
const index = stored.reversionSegments.findIndex((entry) => entry.runId === pending.runId);
|
|
618
|
+
if (index < 0) throw new FlexHarnessValidationError('Pending capture references a missing segment.');
|
|
619
|
+
const finalized: IFlexReversionSegment = {
|
|
620
|
+
...stored.reversionSegments[index],
|
|
621
|
+
status: outcome === 'accepted'
|
|
622
|
+
? 'completed'
|
|
623
|
+
: outcome === 'rejected'
|
|
624
|
+
? 'failed'
|
|
625
|
+
: 'cancelled',
|
|
626
|
+
};
|
|
627
|
+
this.applyFinalizedReversionOutcome(stored, finalized, finalizedOutcome);
|
|
628
|
+
finalized.eventIds = stored.agentSession.getEvents()
|
|
628
629
|
.filter((event) => event.generationId === pending.runId)
|
|
629
630
|
.map((event) => event.id);
|
|
630
|
-
|
|
631
|
+
stored.reversionSegments[index] = finalized;
|
|
632
|
+
if (finalized.status === 'completed') stored.revertCursor++;
|
|
631
633
|
if (outcome === undefined && !stored.excludedRunIds.includes(pending.runId)) {
|
|
632
634
|
stored.excludedRunIds.push(pending.runId);
|
|
633
635
|
}
|
|
@@ -1077,23 +1079,42 @@ export class FlexHarnessReversions<TScope> {
|
|
|
1077
1079
|
};
|
|
1078
1080
|
}
|
|
1079
1081
|
|
|
1080
|
-
/**
|
|
1082
|
+
/**
|
|
1083
|
+
* Replaces a run's segment with a copy that records its terminal status and events, moves the
|
|
1084
|
+
* revert cursor and returns the copy.
|
|
1085
|
+
*/
|
|
1081
1086
|
private closeRunReversionSegment(
|
|
1082
1087
|
run: IActiveRun,
|
|
1083
1088
|
status: 'completed' | 'failed' | 'cancelled',
|
|
1084
1089
|
): IFlexReversionSegment {
|
|
1085
|
-
const
|
|
1086
|
-
if (
|
|
1087
|
-
const wasCompleted =
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
.
|
|
1090
|
+
const index = run.stored.reversionSegments.findIndex((entry) => entry.runId === run.runId);
|
|
1091
|
+
if (index < 0) throw new FlexHarnessValidationError('Reversion segment changed before finalization.');
|
|
1092
|
+
const wasCompleted = run.stored.reversionSegments[index].status === 'completed';
|
|
1093
|
+
const closed: IFlexReversionSegment = {
|
|
1094
|
+
...run.stored.reversionSegments[index],
|
|
1095
|
+
status,
|
|
1096
|
+
eventIds: run.stored.agentSession.getEvents()
|
|
1097
|
+
.filter((event) => event.generationId === run.runId)
|
|
1098
|
+
.map((event) => event.id),
|
|
1099
|
+
};
|
|
1100
|
+
run.stored.reversionSegments[index] = closed;
|
|
1092
1101
|
if (!wasCompleted && status === 'completed') run.stored.revertCursor++;
|
|
1093
1102
|
else if (wasCompleted && status !== 'completed') run.stored.revertCursor--;
|
|
1094
|
-
return
|
|
1103
|
+
return closed;
|
|
1095
1104
|
}
|
|
1096
1105
|
|
|
1106
|
+
/** Replaces the segments `unavailable` selects with copies that have no context available. */
|
|
1107
|
+
public markReversionContextUnavailable(
|
|
1108
|
+
stored: IStoredSessionState,
|
|
1109
|
+
unavailable: (segment: IFlexReversionSegment) => boolean,
|
|
1110
|
+
): void {
|
|
1111
|
+
stored.reversionSegments = stored.reversionSegments.map((segment) =>
|
|
1112
|
+
segment.contextAvailable && unavailable(segment)
|
|
1113
|
+
? { ...segment, contextAvailable: false }
|
|
1114
|
+
: segment);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/** Records a finalized capture on `segment`, an unsaved copy the caller puts in place. */
|
|
1097
1118
|
private applyFinalizedReversionOutcome(
|
|
1098
1119
|
stored: IStoredSessionState,
|
|
1099
1120
|
segment: IFlexReversionSegment,
|
|
@@ -1270,9 +1291,8 @@ export class FlexHarnessReversions<TScope> {
|
|
|
1270
1291
|
const archivedIds = new Set(events.flatMap((event) => event.type === 'context-compaction' && event.automatic
|
|
1271
1292
|
? event.coveredEventIds.filter((id) => !activeIds.has(id)) : []));
|
|
1272
1293
|
if (archivedIds.size === 0) return;
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1294
|
+
this.markReversionContextUnavailable(stored, (segment) =>
|
|
1295
|
+
segment.eventIds.some((id) => archivedIds.has(id)));
|
|
1276
1296
|
this.commitArchivedHiddenBranchState(stored);
|
|
1277
1297
|
this.pruneReversionState(stored, false, true);
|
|
1278
1298
|
}
|
|
@@ -1394,7 +1414,12 @@ export class FlexHarnessReversions<TScope> {
|
|
|
1394
1414
|
const persistDirect = async (next: IFlexProjectionSnapshotCurrent): Promise<void> => {
|
|
1395
1415
|
const before = durableProjection;
|
|
1396
1416
|
try {
|
|
1397
|
-
await this.core.stores.projections.save(
|
|
1417
|
+
await this.core.stores.projections.save(
|
|
1418
|
+
storageKey,
|
|
1419
|
+
sessionId,
|
|
1420
|
+
createWholeProjectionChange(next),
|
|
1421
|
+
projection.revision,
|
|
1422
|
+
);
|
|
1398
1423
|
durableProjection = next;
|
|
1399
1424
|
projection = next;
|
|
1400
1425
|
} catch (error) {
|
|
@@ -456,11 +456,8 @@ export class FlexHarnessSessions<TScope> {
|
|
|
456
456
|
const archivedIds = new Set(archive.events.map((event) => event.id));
|
|
457
457
|
await this.core.mutateProjection(state, stored, () => {
|
|
458
458
|
branchCommitted = this.core.reversions.commitRevertedBranchState(stored);
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
segment.contextAvailable = false;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
459
|
+
this.core.reversions.markReversionContextUnavailable(stored, (segment) =>
|
|
460
|
+
segment.eventIds.some((eventId) => archivedIds.has(eventId)));
|
|
464
461
|
this.core.reversions.pruneReversionState(stored);
|
|
465
462
|
}, true);
|
|
466
463
|
} catch (error) {
|