@modelprofile.com/flexharness 3.7.0 → 3.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/changelog.md +9 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.flexharness.d.ts +14 -2
- package/dist_ts/classes.flexharness.js +667 -199
- package/dist_ts/interfaces.d.ts +90 -6
- package/dist_ts/interfaces.js +5 -1
- package/dist_ts/utils.json.d.ts +3 -2
- package/dist_ts/utils.json.js +243 -11
- package/dist_ts_migration/v3_legacyflexharness.js +1 -1
- package/package.json +1 -1
- package/readme.hints.md +3 -2
- package/readme.md +86 -10
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.flexharness.ts +757 -198
- package/ts/classes.stores.ts +3 -3
- package/ts/interfaces.ts +125 -6
- package/ts/utils.json.ts +271 -12
- package/ts_migration/v3_legacyflexharness.ts +1 -1
package/readme.md
CHANGED
|
@@ -282,6 +282,8 @@ await harness.reconcileToolExecution(scopeId, sessionId, intentId, {
|
|
|
282
282
|
resolution: 'executed',
|
|
283
283
|
output: { committed: true },
|
|
284
284
|
});
|
|
285
|
+
const reversion = await harness.getSessionReversionInfo(scopeId, sessionId);
|
|
286
|
+
console.log(reversion.undoAvailable, reversion.redoAvailable, reversion.groups);
|
|
285
287
|
const undone = await harness.undoSession(scopeId, sessionId);
|
|
286
288
|
console.log(undone.revertedRunId);
|
|
287
289
|
const redone = await harness.redoSession(scopeId, sessionId);
|
|
@@ -351,12 +353,14 @@ const harness = new FlexHarness({
|
|
|
351
353
|
|
|
352
354
|
At most 128 custom commands may be registered. Every registration must be a plain object with exactly one of `template` or `handler`; names must match `[a-z][a-z0-9-]{0,63}`, be unique, and not use a reserved name. Optional descriptions must be non-empty and at most 2048 UTF-8 bytes. Templates must be non-empty and at most 768 KiB, and the expanded prompt must also fit 768 KiB. Registrations are copied and frozen during construction.
|
|
353
355
|
|
|
354
|
-
`/undo` and `/redo`, plus `undoSession()` and `redoSession()`, move a durable
|
|
356
|
+
`/undo` and `/redo`, plus `undoSession()` and `redoSession()`, move a durable history cursor. The direct methods return `{ revertedRunId }` and `{ restoredRunId }`; the slash forms return `{ type: 'operation', name: 'undo' | 'redo' }`. Each committed cursor move emits one `session.history.changed` event with `direction`, `runId`, and the selected session identity. A committed branch emits the same event with `direction: 'branch'` and no `runId`, so controllers should refresh the complete selected session. Capture finalization, cleanup, and metadata-only changes do not emit this event.
|
|
355
357
|
|
|
356
|
-
A completed root-session turn defines an
|
|
358
|
+
A completed root-session turn defines an operation-group boundary. Failed and cancelled turns after it belong to that group; leading failed or cancelled turns belong to the first completed group. No completed boundary means there is nothing to undo. Undo applies selected segments in reverse order and redo applies them in forward order. Without `turnReversionProvider`, only transcript and future model context move. Hidden messages disappear from `getMessages()`, message pages, exact message lookup, and future model context. Starting a new prompt, template, handler, or compaction from an undone position commits a branch: hidden messages and segments are removed durably and cannot be redone. Successful event archival also commits hidden redo history; a missing compaction or failed archive leaves it intact.
|
|
357
359
|
|
|
358
360
|
Two horizons bound undo. Retention pruning removes the oldest complete visible units when `reversionLimits` is exceeded. Explicit event archival marks covered turns context-unavailable and prunes complete prefixes that can no longer be rebuilt; FlexHarness never crosses that archive horizon. Manual compaction without archival retains the original events and remains undoable. Schema-1 projection history and sessions migrated from `2.x` have no reversion segments, so historical turns are not retroactively undoable; newly written turns are tracked normally.
|
|
359
361
|
|
|
362
|
+
Session metadata archival through `updateSession(..., { archived: true })` only sets `archivedAt`. It does not archive Agent events, retire captures, or remove undo history.
|
|
363
|
+
|
|
360
364
|
`executeSlashCommand()` is the authoritative parser and lookup boundary. Its result distinguishes `not-command`, `malformed`, `unknown`, completed `operation`, bounded `handler-result`, and `prompt-admission`. A prompt admission contains the normal `{ queueId, runId, completion }`; await `admission.completion` for the model result. Unknown commands are never admitted as literal prompts. Known unavailable commands and invalid arguments throw typed FlexHarness errors. Options accept `modelHint`, `system`, `maxSteps`, and `signal`; commands do not accept attachments. Aborting a template or `init` execution cancels its exact queued or started prompt without affecting another queue entry.
|
|
361
365
|
|
|
362
366
|
Templates replace every `$ARGUMENTS` with untouched raw argument text. `$1` through the highest referenced positional placeholder use tokenized arguments, with the highest position receiving all remaining tokens joined by spaces. Missing positions become empty. A template with no placeholders appends non-empty raw arguments after a blank line. `/init` uses the OpenCode 1.18.15 `AGENTS.md` initialization prompt with provider-neutral active-workspace wording.
|
|
@@ -365,7 +369,9 @@ Handler context is frozen and contains only the resolved scope identity, session
|
|
|
365
369
|
|
|
366
370
|
### Workspace Reversion Provider
|
|
367
371
|
|
|
368
|
-
|
|
372
|
+
`reversionPolicy` defaults to `transcript-optional`, preserving the V1 behavior described above. Set it to `workspace-required` when transcript and workspace traversal must move together. This policy requires an `IFlexTurnReversionProviderV2` at construction.
|
|
373
|
+
|
|
374
|
+
Applications using the original protocol can continue to provide all six unchanged `IFlexTurnReversionProvider` operations:
|
|
369
375
|
|
|
370
376
|
```typescript
|
|
371
377
|
import type { IFlexTurnReversionProvider } from '@modelprofile.com/flexharness';
|
|
@@ -380,17 +386,85 @@ const turnReversionProvider: IFlexTurnReversionProvider<IProjectScope> = {
|
|
|
380
386
|
};
|
|
381
387
|
```
|
|
382
388
|
|
|
389
|
+
Protocol 2 adds the `protocolVersion` discriminant and a tagged finalized outcome. The prepare, apply, apply-inspection, and release contexts remain the V1 shapes:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import type {
|
|
393
|
+
IFlexTurnReversionProviderV2,
|
|
394
|
+
} from '@modelprofile.com/flexharness';
|
|
395
|
+
|
|
396
|
+
const turnReversionProvider: IFlexTurnReversionProviderV2<IProjectScope> = {
|
|
397
|
+
protocolVersion: 2,
|
|
398
|
+
prepare: (context) => workspaceHistory.prepare(context),
|
|
399
|
+
inspectCapture: (context) => workspaceHistory.inspectCapture(context),
|
|
400
|
+
async finalize(context) {
|
|
401
|
+
const capture = await workspaceHistory.finalize(context);
|
|
402
|
+
if (capture.changedPaths.length === 0) {
|
|
403
|
+
return {
|
|
404
|
+
disposition: 'no-change',
|
|
405
|
+
reference: capture.cleanupReference,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
if (!capture.revertible) {
|
|
409
|
+
return {
|
|
410
|
+
disposition: 'nonrevertible',
|
|
411
|
+
reference: capture.cleanupReference,
|
|
412
|
+
reasonCode: 'git.unmerged',
|
|
413
|
+
affectedWorkspaces: [{ id: capture.workspaceId, label: capture.workspaceLabel }],
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
disposition: 'revertible',
|
|
418
|
+
reference: capture.reference,
|
|
419
|
+
affectedWorkspaces: [{ id: capture.workspaceId, label: capture.workspaceLabel }],
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
inspectApply: (context) => workspaceHistory.inspectApply(context),
|
|
423
|
+
apply: (context) => workspaceHistory.apply(context),
|
|
424
|
+
release: (context) => workspaceHistory.release(context),
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const harness = new FlexHarness<IProjectScope>({
|
|
428
|
+
// scopeResolver, modelResolver, stores, and other options...
|
|
429
|
+
turnReversionProvider,
|
|
430
|
+
reversionPolicy: 'workspace-required',
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
V1 `finalize()` returns a JSON reference, and a finalized V1 `inspectCapture()` result is `{ status: 'finalized', reference }`. V2 `finalize()` returns `revertible`, `no-change`, or `nonrevertible`, and a finalized V2 inspection is `{ status: 'finalized', outcome }` with the same complete tagged outcome. Every V2 outcome carries a normalized cleanup reference. A revertible outcome also carries `affectedWorkspaces`. A no-change outcome omits that list or supplies an empty list. A nonrevertible outcome carries a stable `reasonCode` and may carry affected workspaces.
|
|
435
|
+
|
|
436
|
+
Affected workspace descriptors contain only stable, non-whitespace `id` and display `label` strings. A result accepts at most 64 unique descriptors; IDs are limited to 512 UTF-8 bytes, labels to 2048 bytes, and reason codes to 128 bytes matching `[A-Za-z0-9][A-Za-z0-9._-]*`. References use the configured JSON normalization depth and byte limit with an absolute 256 KiB cap.
|
|
437
|
+
|
|
438
|
+
Controllers query safe history metadata with one method:
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
const info = await harness.getSessionReversionInfo(scopeId, sessionId);
|
|
442
|
+
for (const group of info.groups) {
|
|
443
|
+
console.log(
|
|
444
|
+
group.runId,
|
|
445
|
+
group.kind,
|
|
446
|
+
group.visibility,
|
|
447
|
+
group.affectedWorkspaces,
|
|
448
|
+
group.affectedWorkspacesTruncated,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
The immutable result exposes `undoAvailable`, `redoAvailable`, and groups classified as `candidate`, `barrier`, or `no-change`. Group metadata contains at most 64 unique affected workspaces; `affectedWorkspacesTruncated` is `true` when additional unique descriptors were omitted. It never includes capture IDs or provider references.
|
|
454
|
+
|
|
383
455
|
`workspaceSnapshots` is application-owned. Every context contains `scopeId`, `scope`, `storageKey`, `sessionId`, `runId`, deterministic `captureId`, and an `AbortSignal`. Apply contexts additionally contain the normalized `reference`, deterministic per-segment `operationId`, and `direction`; release contexts contain the reference.
|
|
384
456
|
|
|
385
457
|
The protocol is durable and inspectable:
|
|
386
458
|
|
|
387
459
|
1. FlexHarness persists a `preparing` capture intent before calling `prepare()`, before model or tool execution. The provider must establish exclusive capture ownership for that `storageKey` and retain it until `release()` succeeds.
|
|
388
|
-
2. `inspectCapture()` returns `missing`, `prepared`, `finalized`, or `unknown`.
|
|
389
|
-
3. `finalize()` closes the capture and returns its JSON-safe reference. FlexHarness may call it during normal finalization or recovery after `inspectCapture()` reports `prepared`. Failed and cancelled root turns are captured too. A root capture spans foreground subagent effects, although child transcript records remain separate.
|
|
390
|
-
4. Before undo or redo, FlexHarness persists an apply write-ahead record. `inspectApply()`
|
|
391
|
-
5. `release()` relinquishes the capture after branch commitment, retention or archive pruning, session deletion, or other durable removal. It must be idempotent: an unacknowledged release remains persisted and is retried before FlexHarness discards the reference.
|
|
460
|
+
2. `inspectCapture()` returns `missing`, `prepared`, `finalized`, or `unknown`. A finalized V1 result includes `reference`; a finalized V2 result includes the complete tagged `outcome`. `missing` is safe only while the durable state is still `preparing`; a missing prepared or finalizing capture, an `unknown` result, or an inspection failure fences the namespace.
|
|
461
|
+
3. `finalize()` closes the capture and returns its JSON-safe V1 reference or complete V2 outcome. FlexHarness may call it during normal finalization or recovery after `inspectCapture()` reports `prepared`. Failed and cancelled root turns are captured too. A root capture spans foreground subagent effects, although child transcript records remain separate.
|
|
462
|
+
4. Before undo or redo, FlexHarness persists an apply write-ahead record. `inspectApply()` must report the exact durable outcome for the supplied `operationId`: `not-applied` means no effect occurred, `applied` means the complete effect occurred, and `unknown` means the provider cannot prove either result. FlexHarness calls `apply()` only for `not-applied`; after an apply error it inspects again, and an `unknown` result or inspection failure fences the namespace. Progress is persisted after each segment, and the transcript cursor moves only after the complete unit succeeds. Known zero-progress failures leave the cursor unchanged and can be retried; partial progress retains the write-ahead record and resumes after restart. Caller cancellation is honored until the first workspace segment makes progress; recovery then continues with fresh bounded maintenance signals until the unit and cursor commit. Providers must treat `operationId` idempotently.
|
|
463
|
+
5. `release()` relinquishes the capture after no-change or nonrevertible V2 finalization, branch commitment, retention or archive pruning, session deletion, or other durable removal. It must be idempotent: an unacknowledged release remains persisted and is retried before FlexHarness discards the reference.
|
|
464
|
+
|
|
465
|
+
Under `workspace-required`, a group is a barrier when any segment is pending, nonrevertible, or legacy transcript-only history. It is a candidate when at least one segment is revertible and none is a barrier; otherwise it is no-change. Only candidates can be traversed. No-change groups after a candidate travel with that candidate until the next candidate or barrier. Leading no-change groups remain visible. A retained barrier blocks older groups, while later candidates remain undoable. A mixed revertible/nonrevertible group is a barrier.
|
|
392
466
|
|
|
393
|
-
Inspection, recovery, finalization, and release use fresh maintenance signals bounded by `agentSessionPolicy.generationLeaseCleanupTimeoutMs`, which defaults to 30 seconds. Providers must observe every supplied signal and must serialize ownership for a storage namespace.
|
|
467
|
+
Inspection, recovery, finalization, and release use fresh maintenance signals bounded by `agentSessionPolicy.generationLeaseCleanupTimeoutMs`, which defaults to 30 seconds. Providers must observe every supplied signal and must serialize ownership for a storage namespace. A provider with the matching persisted `protocolVersion` must remain configured whenever a capture-backed session is reopened, retired, disposed, or deleted. Capture-backed recovery and deletion fail closed without it.
|
|
394
468
|
|
|
395
469
|
Workspace reversion is generic and application-defined. It does not reverse network, database, billing, or other side effects unless the provider deliberately captures them. References are normalized with `toolOutputLimits` and have an absolute 256 KiB encoded cap.
|
|
396
470
|
|
|
@@ -539,7 +613,9 @@ Direct store operations and non-run session mutations surface conflicts as `Flex
|
|
|
539
613
|
|
|
540
614
|
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.
|
|
541
615
|
|
|
542
|
-
Projection stores accept schema version 1 or
|
|
616
|
+
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.
|
|
617
|
+
|
|
618
|
+
A loaded schema-1 projection has no reversion state. Schema-2 `workspaceCaptured` segments migrate as protocol-1 workspace/revertible history without discarding references; transcript-only segments migrate as protocol-1 transcript provenance. Under `workspace-required`, that legacy transcript history is a barrier. The next projection mutation writes schema 3. Custom stores must preserve strict compare-and-swap revisions across schema-1 and schema-2 read-upgrade-write cycles, including uncertain-save reconciliation.
|
|
543
619
|
|
|
544
620
|
## Migrating From 2.x
|
|
545
621
|
|
|
@@ -563,7 +639,7 @@ const stores = new JsonFileFlexHarnessStores({
|
|
|
563
639
|
await migrateLegacyFlexHarnessSnapshot(storageKey, legacySnapshot, stores);
|
|
564
640
|
```
|
|
565
641
|
|
|
566
|
-
`loadLegacySnapshot()` is application-owned access to the snapshot written by the `2.x` store. The migration validates the complete source and every public run before writing. A run left streaming by a process crash is deterministically repaired to the same cancelled state that the `2.x` loader produced in memory. The migration then converts private model messages into generationless canonical Agent conversation events, records terminal SmartAgent transactions for completed, failed, and cancelled public runs, and writes schema-
|
|
642
|
+
`loadLegacySnapshot()` is application-owned access to the snapshot written by the `2.x` store. The migration validates the complete source and every public run before writing. A run left streaming by a process crash is deterministically repaired to the same cancelled state that the `2.x` loader produced in memory. The migration then converts private model messages into generationless canonical Agent conversation events, records terminal SmartAgent transactions for completed, failed, and cancelled public runs, and writes schema-3 projections with empty reversion state. Migrated history therefore remains visible and auditable but is not retroactively undoable; turns created after migration receive normal reversion segments and optional workspace captures. The migration preflights the scope, projection, permission, Agent event, and job destinations before any write, applies missing per-session domains first, and publishes scope discovery last. It is safe to rerun after no work, a completed prefix, or a complete migration when existing destination content is identical. It fails closed when a destination contains conflicting content or non-empty jobs. Keep the legacy snapshot until the migrated application has loaded and verified every storage namespace.
|
|
567
643
|
|
|
568
644
|
## Shutdown
|
|
569
645
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@modelprofile.com/flexharness',
|
|
6
|
-
version: '3.
|
|
6
|
+
version: '3.8.0',
|
|
7
7
|
description: 'Provider-neutral model-session runtime with durable history, permissions, typed events, and pluggable local or remote tool execution.'
|
|
8
8
|
}
|