@modelprofile.com/flexharness 3.7.0 → 4.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.
@@ -0,0 +1,5 @@
1
+ import type { IFlexProjectManagementSnapshot, IFlexProjectManagementTombstone, TFlexProjectManagementRecord } from './interfaces.js';
2
+ export declare function createEmptyFlexProjectManagementSnapshot(sessionGenerationId: string, sessionGenerationSequence: number): IFlexProjectManagementSnapshot;
3
+ export declare function assertFlexProjectManagementSnapshot(value: unknown): asserts value is IFlexProjectManagementSnapshot;
4
+ export declare function assertFlexProjectManagementTombstone(value: unknown): asserts value is IFlexProjectManagementTombstone;
5
+ export declare function assertFlexProjectManagementRecord(value: unknown): asserts value is TFlexProjectManagementRecord;
@@ -0,0 +1,141 @@
1
+ import { FlexHarnessStoreFormatError, } from './errors.js';
2
+ import { FLEX_PROJECT_MANAGEMENT_LIMITS, FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION, FLEX_SESSION_GENERATION_ID_MAX_BYTES, } from './interfaces.js';
3
+ import { assertJsonSerializable } from './utils.json.js';
4
+ function requireRecord(value, path) {
5
+ if (!value
6
+ || typeof value !== 'object'
7
+ || Array.isArray(value)
8
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
9
+ throw new FlexHarnessStoreFormatError(`${path} must be a plain object.`);
10
+ }
11
+ return value;
12
+ }
13
+ function requireOnlyKeys(value, keys, path) {
14
+ const unsupported = Object.keys(value).find((key) => !keys.includes(key));
15
+ if (unsupported) {
16
+ throw new FlexHarnessStoreFormatError(`${path}.${unsupported} is not supported.`);
17
+ }
18
+ }
19
+ function requireBoundedString(value, path, maxBytes, nonEmpty = false) {
20
+ if (typeof value !== 'string'
21
+ || (nonEmpty && !value.trim())
22
+ || Buffer.byteLength(value, 'utf8') > maxBytes) {
23
+ throw new FlexHarnessStoreFormatError(`${path} must be ${nonEmpty ? 'a non-empty ' : 'a '}string of at most ${maxBytes} UTF-8 bytes.`);
24
+ }
25
+ }
26
+ function requireTimestamp(value, path) {
27
+ if (typeof value !== 'string') {
28
+ throw new FlexHarnessStoreFormatError(`${path} must be a canonical ISO timestamp.`);
29
+ }
30
+ const timestamp = Date.parse(value);
31
+ if (!Number.isFinite(timestamp) || new Date(timestamp).toISOString() !== value) {
32
+ throw new FlexHarnessStoreFormatError(`${path} must be a canonical ISO timestamp.`);
33
+ }
34
+ }
35
+ function requireSessionGeneration(value, path) {
36
+ requireBoundedString(value.sessionGenerationId, `${path}.sessionGenerationId`, FLEX_SESSION_GENERATION_ID_MAX_BYTES, true);
37
+ if (!Number.isSafeInteger(value.sessionGenerationSequence)
38
+ || Number(value.sessionGenerationSequence) < 1) {
39
+ throw new FlexHarnessStoreFormatError(`${path}.sessionGenerationSequence must be a positive integer.`);
40
+ }
41
+ }
42
+ function validateTask(value, path) {
43
+ const task = requireRecord(value, path);
44
+ requireOnlyKeys(task, ['id', 'content', 'status', 'priority', 'createdAt', 'updatedAt'], path);
45
+ requireBoundedString(task.id, `${path}.id`, FLEX_PROJECT_MANAGEMENT_LIMITS.maxTaskIdBytes, true);
46
+ requireBoundedString(task.content, `${path}.content`, FLEX_PROJECT_MANAGEMENT_LIMITS.maxTaskContentBytes, true);
47
+ if (!['pending', 'in_progress', 'completed', 'cancelled'].includes(String(task.status))) {
48
+ throw new FlexHarnessStoreFormatError(`${path}.status is invalid.`);
49
+ }
50
+ if (!['high', 'medium', 'low'].includes(String(task.priority))) {
51
+ throw new FlexHarnessStoreFormatError(`${path}.priority is invalid.`);
52
+ }
53
+ requireTimestamp(task.createdAt, `${path}.createdAt`);
54
+ requireTimestamp(task.updatedAt, `${path}.updatedAt`);
55
+ if (task.updatedAt < task.createdAt) {
56
+ throw new FlexHarnessStoreFormatError(`${path}.updatedAt cannot precede createdAt.`);
57
+ }
58
+ return task;
59
+ }
60
+ export function createEmptyFlexProjectManagementSnapshot(sessionGenerationId, sessionGenerationSequence) {
61
+ const snapshot = {
62
+ schemaVersion: FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION,
63
+ revision: 0,
64
+ sessionGenerationId,
65
+ sessionGenerationSequence,
66
+ scratchpad: '',
67
+ tasks: [],
68
+ };
69
+ assertFlexProjectManagementSnapshot(snapshot);
70
+ return snapshot;
71
+ }
72
+ export function assertFlexProjectManagementSnapshot(value) {
73
+ assertJsonSerializable(value, '$snapshot');
74
+ const snapshot = requireRecord(value, '$snapshot');
75
+ requireOnlyKeys(snapshot, [
76
+ 'schemaVersion',
77
+ 'revision',
78
+ 'sessionGenerationId',
79
+ 'sessionGenerationSequence',
80
+ 'goal',
81
+ 'scratchpad',
82
+ 'tasks',
83
+ ], '$snapshot');
84
+ if (snapshot.schemaVersion !== FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION) {
85
+ throw new FlexHarnessStoreFormatError(`Project management snapshot schemaVersion must be ${FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION}.`);
86
+ }
87
+ if (!Number.isSafeInteger(snapshot.revision) || Number(snapshot.revision) < 0) {
88
+ throw new FlexHarnessStoreFormatError('Project management snapshot revision must be a non-negative integer.');
89
+ }
90
+ requireSessionGeneration(snapshot, '$snapshot');
91
+ if (snapshot.goal !== undefined) {
92
+ requireBoundedString(snapshot.goal, '$snapshot.goal', FLEX_PROJECT_MANAGEMENT_LIMITS.maxGoalBytes, true);
93
+ }
94
+ requireBoundedString(snapshot.scratchpad, '$snapshot.scratchpad', FLEX_PROJECT_MANAGEMENT_LIMITS.maxScratchpadBytes);
95
+ if (!Array.isArray(snapshot.tasks)) {
96
+ throw new FlexHarnessStoreFormatError('$snapshot.tasks must be an array.');
97
+ }
98
+ if (snapshot.tasks.length > FLEX_PROJECT_MANAGEMENT_LIMITS.maxTasks) {
99
+ throw new FlexHarnessStoreFormatError('$snapshot.tasks exceeds its task-count limit.');
100
+ }
101
+ const taskIds = new Set();
102
+ for (let index = 0; index < snapshot.tasks.length; index++) {
103
+ const task = validateTask(snapshot.tasks[index], `$snapshot.tasks[${index}]`);
104
+ if (taskIds.has(task.id)) {
105
+ throw new FlexHarnessStoreFormatError(`$snapshot.tasks contains duplicate task "${task.id}".`);
106
+ }
107
+ taskIds.add(task.id);
108
+ }
109
+ if (Buffer.byteLength(JSON.stringify(snapshot), 'utf8') > FLEX_PROJECT_MANAGEMENT_LIMITS.maxSnapshotBytes) {
110
+ throw new FlexHarnessStoreFormatError('Project management snapshot exceeds its serialized byte limit.');
111
+ }
112
+ }
113
+ export function assertFlexProjectManagementTombstone(value) {
114
+ assertJsonSerializable(value, '$tombstone');
115
+ const tombstone = requireRecord(value, '$tombstone');
116
+ requireOnlyKeys(tombstone, [
117
+ 'schemaVersion',
118
+ 'revision',
119
+ 'sessionGenerationId',
120
+ 'sessionGenerationSequence',
121
+ 'deletedAt',
122
+ ], '$tombstone');
123
+ if (tombstone.schemaVersion !== FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION) {
124
+ throw new FlexHarnessStoreFormatError(`Project management tombstone schemaVersion must be ${FLEX_PROJECT_MANAGEMENT_SCHEMA_VERSION}.`);
125
+ }
126
+ if (!Number.isSafeInteger(tombstone.revision) || Number(tombstone.revision) < 1) {
127
+ throw new FlexHarnessStoreFormatError('Project management tombstone revision must be a positive integer.');
128
+ }
129
+ requireSessionGeneration(tombstone, '$tombstone');
130
+ requireTimestamp(tombstone.deletedAt, '$tombstone.deletedAt');
131
+ }
132
+ export function assertFlexProjectManagementRecord(value) {
133
+ const record = requireRecord(value, '$record');
134
+ if (Object.prototype.hasOwnProperty.call(record, 'deletedAt')) {
135
+ assertFlexProjectManagementTombstone(value);
136
+ }
137
+ else {
138
+ assertFlexProjectManagementSnapshot(value);
139
+ }
140
+ }
141
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMucHJvamVjdG1hbmFnZW1lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy91dGlscy5wcm9qZWN0bWFuYWdlbWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsMkJBQTJCLEdBQzVCLE1BQU0sYUFBYSxDQUFDO0FBQ3JCLE9BQU8sRUFDTCw4QkFBOEIsRUFDOUIsc0NBQXNDLEVBQ3RDLG9DQUFvQyxHQUNyQyxNQUFNLGlCQUFpQixDQUFDO0FBT3pCLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXpELFNBQVMsYUFBYSxDQUFDLEtBQWMsRUFBRSxJQUFZO0lBQ2pELElBQ0UsQ0FBQyxLQUFLO1dBQ0gsT0FBTyxLQUFLLEtBQUssUUFBUTtXQUN6QixLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztXQUNwQixDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUNuRSxDQUFDO1FBQ0QsTUFBTSxJQUFJLDJCQUEyQixDQUFDLEdBQUcsSUFBSSwwQkFBMEIsQ0FBQyxDQUFDO0lBQzNFLENBQUM7SUFDRCxPQUFPLEtBQWdDLENBQUM7QUFDMUMsQ0FBQztBQUVELFNBQVMsZUFBZSxDQUN0QixLQUE4QixFQUM5QixJQUF1QixFQUN2QixJQUFZO0lBRVosTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQzFFLElBQUksV0FBVyxFQUFFLENBQUM7UUFDaEIsTUFBTSxJQUFJLDJCQUEyQixDQUFDLEdBQUcsSUFBSSxJQUFJLFdBQVcsb0JBQW9CLENBQUMsQ0FBQztJQUNwRixD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
@@ -261,7 +261,7 @@ function createMigrationPlan(storageKey, legacySnapshot) {
261
261
  tombstones: [],
262
262
  };
263
263
  const projectionSnapshot = {
264
- schemaVersion: 2,
264
+ schemaVersion: 3,
265
265
  revision: 1,
266
266
  messages: stored.messages,
267
267
  stagedTerminals: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelprofile.com/flexharness",
3
- "version": "3.7.0",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
5
  "description": "Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.",
6
6
  "main": "dist_ts/index.js",
package/readme.hints.md CHANGED
@@ -9,6 +9,7 @@ Implementation findings for flexharness.
9
9
  - Prompt attachment payloads exist only in canonical private Agent events. Public messages, prompt results, projection snapshots, and events expose only `attachmentType`, source kind, optional media/name, and decoded size when determinable.
10
10
  - Resolver calls start in promise continuations so synchronous throws are observed. The first failure aborts the shared internal signal without awaiting an ignoring sibling; detached tool-provider settlement is observed and late handles are closed.
11
11
  - Resource tool composition resolves descriptors per run before provider acquisition, validates duplicate resources and derived namespaces as one set, preserves legacy tool names, and derives bounded resource names from canonical resource/revision and original-tool hashes. Permission identity uses the complete resource digest while provider metadata remains nested below harness-owned attachment metadata.
12
+ - Enabled harness-built-in names are checked on every application and resource handle before model execution. The reserved set is run-specific, so a disabled project tool or depth-disabled delegate does not reserve its wire name.
12
13
 
13
14
  ## Persistence boundary
14
15
 
@@ -16,7 +17,7 @@ Implementation findings for flexharness.
16
17
  - Every queued mutation snapshots its revision and domain state first. A thrown save is reconciled against the store before memory is restored: a proven prior snapshot rolls back, a proven new snapshot remains current, and an unknown or explicitly uncertain commit fences the namespace. Code that crosses the save `await` must re-fetch session records because a proven rollback intentionally restores them.
17
18
  - `JsonFileFlexHarnessStores` is intentionally not cross-process safe. It provides atomic rename and in-process CAS, not an operating-system lock.
18
19
  - Loaded state is repaired from canonical generation outcomes. Accepted hidden terminal stages are promoted, while interrupted or incomplete public messages and tool parts become cancelled so a restarted process never presents them as still running.
19
- - Session updates reject while runtime work is active. Deletion tombstones the session, cancels active work, and retains failed runtime cleanup for a later deletion, retirement, or disposal retry before removing every persisted domain.
20
+ - Session updates reject while runtime work is active. Deletion tombstones the session, cancels active work, and retains failed runtime cleanup for a later deletion, retirement, or disposal retry before removing destructive core domains and confirming the retained PM generation fence.
20
21
  - Tool close settles before canonical acceptance. A completed hidden terminal projection is staged first, the Agent generation is finalized second, and only then is the public projection promoted. Earlier execution failures finalize as interrupted and publish from that durable outcome.
21
22
  - Disposal waits admitted session initialization, finalizers, every loaded-state save tail, tombstone cleanup, provider release, and detached tool cleanup. Listeners always clear; state caches clear only after cleanup succeeds and otherwise remain owned for retry.
22
23
  - Canonical finalization is the cancellation linearization point. Abort returns false once a run starts committing, while the active-run entry continues to block new prompts until finalization and public promotion settle.
@@ -28,9 +29,11 @@ Implementation findings for flexharness.
28
29
  - Detached tool-provider cleanup and retained session cleanup are owned by the exact loaded storage state. Failed drains retain that ownership and the cached state for retry. Scope retirement cannot await or consume another namespace's cleanup, while full disposal settles all storage drains and orphaned initialization cleanup before aggregating failures.
29
30
  - JSON provider release hooks evict non-destructive session wrapper caches after AgentSession and execution-context ownership ends. Failed partial-initialization releases remain storage-scoped orphan ownership and are retried by retirement or disposal.
30
31
  - Turn-reversion capture release is idempotent and tombstone-owned. Session deletion aborts same-session commands, waits runtime settlement, releases every durable workspace capture, and only then removes projection data; a failed or unacknowledged release leaves the tombstone and projection available for retry.
31
- - Projection schema 1 remains read-only compatibility input. Every projection write uses schema 2; legacy and migrated messages have no retroactive undo segments, while new turns establish reversion metadata normally.
32
- - A loaded schema-1 projection retains its exact durable baseline until the first schema-2 save is confirmed, so a non-committing upgrade failure rolls back without fencing while a post-commit throw reconciles the new schema.
32
+ - Projection schemas 1 and 2 remain read-only compatibility inputs. Every projection write uses schema 3 with explicit protocol, provenance, and V2 disposition; schema-2 workspace references migrate as protocol-1 ownership without being discarded.
33
+ - A loaded schema-1 or schema-2 projection retains its exact durable baseline until the first schema-3 save is confirmed, so a non-committing upgrade failure rolls back without fencing while a post-commit throw reconciles the new schema.
34
+ - Workspace-required traversal keeps the cursor in completed-root group coordinates. Candidate operations may jump across trailing no-change groups, while pending, nonrevertible, mixed, and legacy transcript groups remain barriers without changing transcript-optional grouping.
33
35
  - `JsonFileFlexHarnessStores.dispose()` is the explicit final drain for file handles whose close failed on the last store operation; failed disposal retains the handle for another call.
36
+ - Project-management state is a required exact `(storageKey, sessionId)` member of `IFlexHarnessStores`; `builtInTools.projectManagement` only controls tool exposure. Harness-local operations serialize without retrying external mutation conflicts, no-op mutations do not save, and tool writes retain agent/run/tool-call attribution. Session cleanup always runs, waits the local queue, and boundedly reloads before writing a generation-owned tombstone. Random generation identity plus a monotonic core-scope generation sequence permits a newer session to replace only an older PM tombstone while fencing stale saves and tombstones. Normal cleanup never removes the fence; `purgeNamespace()` is reserved for serialized application-owned whole-namespace destruction after non-destructive retirement and core-scope purge.
34
37
 
35
38
  ## Tool output boundary
36
39
 
@@ -49,7 +52,7 @@ Implementation findings for flexharness.
49
52
  ## Subagent ownership
50
53
 
51
54
  - Subagents are first-class FlexHarness sessions. Public callers cannot assign relationship fields; private creation persists a complete parent/origin/agent/depth relation under the storage-key scope mutation before runtime initialization.
52
- - The built-in `task` tool consumes its per-run call slot when schema-valid execution starts, before semantic validation. Tool-schema rejection never enters execution and consumes no slot. Successful semantic validation reserves the child ID before requesting `subagent.start` permission; that ID remains consumed after rejection or failure, but permission rejection creates no child session. New child IDs hash the version marker, raw storage key, parent session, parent run, and parent tool call; the storage key itself is never exposed.
55
+ - The built-in `delegate` tool consumes its per-run call slot when schema-valid execution starts, before semantic validation. Tool-schema rejection never enters execution and consumes no slot. Successful semantic validation reserves the child ID before requesting `subagent.start` permission; that ID remains consumed after rejection or failure, but permission rejection creates no child session. New child IDs hash the version marker, raw storage key, parent session, parent run, and parent tool call; the storage key itself is never exposed.
53
56
  - SmartAgent's parent tool intent remains crash authority. A deterministic child with messages is treated as an uncertain prior execution, while explicit `taskId` resume is limited to a later run of the same parent and same configured agent.
54
57
  - The SmartAgent tool-start callback establishes the correlated running parent part before execution. Harness-owned child ID/model metadata mutates that part synchronously and emits cumulative `part.updated` snapshots within the existing callback budgets.
55
58
  - Parent abort listeners recheck immediately after registration and abort only the captured active child run ID. Listener removal is in `finally`, including child failures and cancellation.