@llblab/pi-kit 0.37.1 → 0.38.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 +5 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -3
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +679 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-state-flow/README.md +30 -14
- package/node_modules/@llblab/pi-state-flow/api/modules.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.js +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +77 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +26 -27
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +13 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +27 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +144 -46
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +14 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +60 -50
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1049 -310
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +15 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +7 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +186 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.d.ts +17 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.js +158 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.js +108 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/operation.js +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.js +84 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +6 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +33 -16
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +21 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +53 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +5 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +10 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +84 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +570 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.d.ts +10 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.js +32 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.d.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.js +187 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +98 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +398 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +7 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +55 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +67 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +129 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +26 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +61 -19
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +14 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +59 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +111 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +728 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +10 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +50 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +40 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +247 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +41 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +132 -39
- package/node_modules/@llblab/pi-state-flow/dist/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +12 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +9 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +76 -43
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +9 -1
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -0
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +23 -19
- package/node_modules/@llblab/pi-state-flow/docs/protocol-composition-decision.md +66 -0
- package/node_modules/@llblab/pi-state-flow/docs/public-module-contract.md +73 -0
- package/node_modules/@llblab/pi-state-flow/docs/scope-selection-contract.md +814 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-compatibility-decision.md +212 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-extensibility-decision.md +75 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-handoff-contract.md +174 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-memory-evaluation.md +166 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-module-contract.md +84 -0
- package/node_modules/@llblab/pi-state-flow/docs/single-scope-interface-contract.md +201 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +22 -1
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -18
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +68 -31
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +21 -22
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +14 -1
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +142 -46
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +14 -17
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +53 -44
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +897 -297
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -7
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +10 -9
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +189 -0
- package/node_modules/@llblab/pi-state-flow/lib/model-interface.ts +136 -0
- package/node_modules/@llblab/pi-state-flow/lib/module-registry.ts +119 -0
- package/node_modules/@llblab/pi-state-flow/lib/operation.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/ownership.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/planes.ts +98 -0
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +37 -16
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +59 -18
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +5 -2
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +10 -1
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +574 -21
- package/node_modules/@llblab/pi-state-flow/lib/selection.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/lib/semantic-module.ts +239 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +387 -3
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +45 -4
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +156 -16
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +69 -28
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +65 -31
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +770 -18
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +58 -20
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +267 -50
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +184 -52
- package/node_modules/@llblab/pi-state-flow/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +12 -8
- package/package.json +2 -2
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 0.27.1: Selected Scope Read Diagnostics
|
|
8
|
+
|
|
9
|
+
- `Read availability`: Explicit reads now distinguish a selected owner with no available read view from an excluded owner. In pre-runtime Passive sessions, an unacquired Session no longer incorrectly reports itself excluded. Reads still do not create private memory, widen the selection or return partial multi-path results; normal accepted writes retain their acquisition rules.
|
|
10
|
+
|
|
11
|
+
## 0.27.0: Semantic Memory and Scope Selection
|
|
12
|
+
|
|
13
|
+
- `Semantic API (breaking)`: `strategy`/`tactics` replace `contract`/`working` in runtime fields, public types, schemas, queries, receipts and Skills. Strategy retains binding requirements, accepted approaches and rejection reasons; tactics holds operational evidence. Proposals are not commitments. New-format retired-root writes reject; ordinary nested keys remain legal.
|
|
14
|
+
- `Scope selection and tools (breaking)`: All seven nonempty Global/CWD/Session choices work independently of Active/Passive/Off. Commands accept explicit owners and completion; Telegram preserves the choice, filters inspection/help, and shows Effective only for2+ owners. One WANTED owner uses direct semantic planes and relative current refs; multiple owners use explicit atomic patches. Wanted selection is distinct from available memory. New accepted answers remain Session-only.
|
|
15
|
+
- `Intent ownership (breaking)`: Structured refs in intents now own same-scope strategy/tactics/lazy object keys. Fixed phases apply intents, clear unprotected targets, then other fields; remaining/new related refs protect targets. Later writes recreate cleared paths without old fields, independent of JSON key order. Existing strategy refs acquire ownership for future intent deletions; accepted history replays net patches without re-running cleanup.
|
|
16
|
+
- `Recoverable conversion`: Legacy1/2 translate to Strategy/Tactics before filtering. Candidate metadata4 retains declared external object planes/descriptors without definitions through core writes, folding and fork; core-only streams keep3. Explicit upgrades reject raw namespace collisions and require private exact-byte backup/CAS with foreign-edit-safe rollback. Reads/mode persistence never upgrade. Quiesce old writers; universal old-writer safety is not promised. Legacy support remains throughout0.x.
|
|
17
|
+
- `Request and acceptance authority`: Original-ID receipts, physical runtime/CWD/storage identity, captured selection and operation signals gate tools/context/answers before semantic interpretation or source callbacks. Real scope changes revoke stale work without ABA revival; equal choices preserve eligible work. Pending/failed Active acceptance fences inference, Off stays native-only, controls remain independent, and accepted publication survives later cancellation or bookkeeping failure.
|
|
18
|
+
- `Current memory and private forks`: One accepted/observed runtime preserves sparse current state, independent revisions and private causal proof. Observation cannot acquire or publish. Shared drift yields a validated current-only origin without private reset. Deferred forks copy only the direct parent’s current state under exact source/target proof; acquired children never recopy. Opaque future/malformed metadata stays intact, and single-use callback publishers expire.
|
|
19
|
+
- `Source-bound artifacts and Skills`: Selected owner/provenance gates maintenance and compilation; excluded owners do not become flat destinations. Ordinary artifacts retain fingerprints, Skills retain read-driven hashes, and foreign native reads stay volatile rather than being relocated. Compilation/provenance publish with semantic output; caller-forged provenance rejects. Source reads stay outside canonical exclusion; exact CAS is rechecked before publication.
|
|
20
|
+
- `Context and modules`: Built-ins use executable immutable/context and async-producer contracts. Candidate public modules have revocable native-host lifetimes, retained metadata4 identities and matched mutable roots and captured Active/Passive user-level context; Active producers commit cohort-atomic replacements. Sparse history/IDs, special keys/provenance and one atomic engine retained. No measured quality/cost/cache gain. Calibration: `769eed097736760360cae612d2b331c7549787d15617d90c065df510a507544c`.
|
|
21
|
+
|
|
5
22
|
## 0.26.5: Telegram Mode Receipt Hotfix
|
|
6
23
|
|
|
7
24
|
- `Telegram mode controls`: Passive now refreshes the selected button on the first accepted click after Off, including existing-memory restoration and write-fence recovery during an active run. Accepted controls retain their renewed presentation receipt instead of being mistaken for canceled work; later mode choices, branch changes and shutdown still suppress obsolete updates. Memory storage and mode semantics are unchanged; no migration is required.
|
|
@@ -100,20 +100,24 @@ Memory has three ownership scopes:
|
|
|
100
100
|
- `cwd`: Knowledge shared by sessions in the same working directory.
|
|
101
101
|
- `session`: State belonging to the current session and its selected branch.
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
Participation is configured with a nonempty `scopes` array in [global configuration](docs/usage.md#configuration); omission selects all three. Each native branch captures that choice once, and forks inherit it. All seven nonempty selections use the same runtime. The existing mode commands accept optional whitespace-separated owners, for example `/state-flow-active session`, `/state-flow-passive cwd global`, or `/state-flow-off cwd`. Blank/mode-only commands and Telegram controls preserve the captured choice. Duplicate/unknown tokens reject before changing policy; Off records a new choice without canonical memory I/O. No settings chooser is required. Excluded owners receive no automatic memory contribution or semantic writes; new answers are stored only when Active and Session participates. Mode-only Telegram buttons retain that choice; explicit operator inspection is separate from model participation. See the [functional boundary and legacy proof dependencies](docs/scope-selection-contract.md#m2-functional-acceptance-boundary).
|
|
104
|
+
|
|
105
|
+
Participating owners compose recursively in **global → CWD → session** order:
|
|
104
106
|
|
|
105
107
|
- More-specific values override broader ones, while object fields merge.
|
|
106
108
|
- Removing a local value can reveal an inherited value again.
|
|
107
109
|
|
|
108
110
|
The agent receives the **effective view** of this composition, not three unrelated memory dumps. It can read that view, or inspect one scope when ownership matters. `effective` is a computed view, not a fourth storage scope. Scope precedence does not turn memory into system-level instructions.
|
|
109
111
|
|
|
112
|
+
With one captured owner, current same-owner structured references in Active/Passive heads and projection-bound updates are relative, lazy navigation points to `lazy`, and Active recent context records use `{id,at,patch}` without an owner wrapper. Multi-owner records stay explicit. Canonical storage, internal comparisons and causal IDs remain scoped; provenance and historical/cross-owner/resource references retain their identities. Skill acquisition targets and deletion text also follow wanted arity; new Stop/reload/bootstrap handoffs render eligible refs relatively. Source keys and canonical provenance/compiler diagnostics retain their identities, not extra flat patch wrappers. These DTOs are data, not authoring permission. See the [context presentation boundary](docs/single-scope-interface-contract.md#runtime-context-presentation).
|
|
113
|
+
|
|
110
114
|
### Semantic planes
|
|
111
115
|
|
|
112
116
|
Runtime views provide these documented planes:
|
|
113
117
|
|
|
114
|
-
- `intents`: The queue of chosen actions. Work from intents: a structured `{"$ref"}` inside an intent owns a same-scope `
|
|
115
|
-
- `
|
|
116
|
-
- `
|
|
118
|
+
- `intents`: The queue of chosen actions. Work from intents: a structured `{"$ref"}` inside an intent owns a same-scope `strategy`/`tactics`/`lazy` object key. Fixed phases ignore JSON insertion order: intents first, ownership cleanup with final remaining/new intents protecting direct and ancestor/descendant targets, then remaining authored planes and existing compilation/normalization/atomic publication. Rewrite survivors at the same/new path in that patch; cleared targets start fresh without old fields. Textual `$path` mentions only use; unowned entries remain legal.
|
|
119
|
+
- `strategy`: Binding requirements, accepted approaches/plans, decisions, rejected alternatives and interface commitments. A proposal is not a commitment.
|
|
120
|
+
- `tactics`: Temporary execution context: verified progress, consequential failures, blockers, uncertainties and next checks.
|
|
117
121
|
- `artifacts`: Source-addressed descriptions and compiled knowledge.
|
|
118
122
|
- `response`: The exact latest accepted answer, including an empty string. The runtime captures it only in Session; Global/CWD receive no newly accepted answers, and stored scopes may omit it entirely. Effective uses the highest-priority nonempty value. Active automatically projects it; Passive omits it from automatic context while keeping storage and explicit reads intact.
|
|
119
123
|
- `lazy`: Supporting memory available through explicit reads; its body is omitted from baseline model context.
|
|
@@ -122,7 +126,7 @@ Every plane is optional on disk:
|
|
|
122
126
|
|
|
123
127
|
- Stored checkpoints and patches may omit any documented plane, including `intents`, `lazy` or `response`.
|
|
124
128
|
- Current and historical views assemble only the known fields actually present in the selected scopes; absent fields are not filled in. Empty `response` counts as absent.
|
|
125
|
-
- Readers ignore unknown top-level fields, and writers emit only known fields.
|
|
129
|
+
- Readers ignore unrelated unknown top-level fields, and writers emit only known fields. Retired `contract`/`working` roots explicitly reject in the target format; nested ordinary keys remain unrestricted.
|
|
126
130
|
- Reading or starting never rewrites data just to normalize it, and a missing field is not a storage-format error.
|
|
127
131
|
|
|
128
132
|
These planes organize ordinary JSON; there is no project-specific schema. The model updates every plane except `response`, which is runtime-owned.
|
|
@@ -130,7 +134,7 @@ These planes organize ordinary JSON; there is no project-specific schema. The mo
|
|
|
130
134
|
Revisions:
|
|
131
135
|
|
|
132
136
|
- Global and CWD revisions are shared by their canonical stores; Session has its own revision.
|
|
133
|
-
- Effective
|
|
137
|
+
- Effective combines participating owners: with multiple owners its identity is their revision vector (for example `g#c#s#` when all three participate), not one universal revision. Excluded owners do not become fabricated vector entries.
|
|
134
138
|
- One atomic patch advances each materially changed scope once.
|
|
135
139
|
|
|
136
140
|
Memory remains fallible: storing an observation does not make it current or correct.
|
|
@@ -139,7 +143,13 @@ Registered Pi Skills may be compiled into source-addressed artifacts when durabl
|
|
|
139
143
|
|
|
140
144
|
## Incremental updates and history
|
|
141
145
|
|
|
142
|
-
`patch_state`
|
|
146
|
+
`patch_state` uses the captured wanted interface, not the number of currently available views. With **one participating owner**, supply semantic planes directly, without an owner wrapper:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{ "strategy": { "verification": { "command": "npm test" } } }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
With **multiple participating owners**, name the supplied owners explicitly; they commit atomically. Canonical ownership remains explicit internally in both cases:
|
|
143
153
|
|
|
144
154
|
- It waits cancelably for the store lock, then applies authored Global/CWD patches to the current canonical values, so other writers' untouched fields survive.
|
|
145
155
|
- Overlapping assignments follow successful acceptance order. Correct repeats succeed without new semantic revisions.
|
|
@@ -147,28 +157,32 @@ Registered Pi Skills may be compiled into source-addressed artifacts when durabl
|
|
|
147
157
|
- Object patches merge recursively, and `null` deletes an object key instead of being stored.
|
|
148
158
|
- Accepted supplied scopes recursively drop empty object fields and planes, including explicit `{}`; empty ancestors disappear. Array slots and empty arrays stay intact. Cleanup is scope-local and may reveal inherited values. Model context hides legacy empty branches, but reading never rewrites history. See [patch semantics](docs/architecture.md#model-tools).
|
|
149
159
|
|
|
160
|
+
Multi-owner example, requiring CWD and Session to participate:
|
|
161
|
+
|
|
150
162
|
```json
|
|
151
163
|
{
|
|
152
164
|
"cwd": {
|
|
153
|
-
"
|
|
165
|
+
"strategy": { "verification": { "command": "npm test" } }
|
|
154
166
|
},
|
|
155
167
|
"session": {
|
|
156
168
|
"intents": { "verify": { "action": "Run the checks before publishing" } },
|
|
157
|
-
"
|
|
169
|
+
"tactics": { "checks": "Pending" }
|
|
158
170
|
}
|
|
159
171
|
}
|
|
160
172
|
```
|
|
161
173
|
|
|
162
174
|
During an active episode, a material patch is an inference barrier: sibling tool calls are blocked, and the next inference sees the accepted effective state. Ordinary completion needs no finalization patch or extra State Flow reasoning loop.
|
|
163
175
|
|
|
164
|
-
`read_state`
|
|
176
|
+
`read_state` accepts flat ordinary paths with one owner (for example `{ "path": "tactics" }`); `effective[N]` selects that owner's retained temporal boundary and scalar `patches[N]` its retained patch. Current same-owner structured refs may use `{ "$ref": "lazy.plan" }`; persistence anchors them to the exact owner. Never reinterpret different-owner/historical/effective refs, resources or text as local permission.
|
|
177
|
+
|
|
178
|
+
With multiple owners, unscoped reads use the effective overlay. This multi-owner example requires every explicit owner to participate:
|
|
165
179
|
|
|
166
180
|
```json
|
|
167
|
-
{ "paths": ["effective.
|
|
181
|
+
{ "paths": ["effective.strategy", "cwd.tactics", "session.intents"] }
|
|
168
182
|
```
|
|
169
183
|
|
|
170
|
-
- `
|
|
171
|
-
- `effective[1].
|
|
184
|
+
- `tactics`: Current sole-owner memory, or the effective overlay when multiple owners participate.
|
|
185
|
+
- `effective[1].tactics`: Working memory at the preceding accepted transition boundary, when retained.
|
|
172
186
|
- `cwd.patches[0]`: The latest retained CWD semantic patch.
|
|
173
187
|
- `effective.lazy.memory[0..3]`: A bounded slice of a stored collection.
|
|
174
188
|
|
|
@@ -225,7 +239,7 @@ How mode changes interact with storage:
|
|
|
225
239
|
- Explicit Off inspection may read current stored values through a disposable validated reader. It does not activate memory, install cache or change pending fork acquisition. Missing private authority stays unavailable.
|
|
226
240
|
- Active waits for a coherent capture and acceptance; another mode or selection can withdraw its wait.
|
|
227
241
|
|
|
228
|
-
|
|
242
|
+
Canonical scope metadata v3 uses `strategy` and `tactics`. Supported v1/v2 checkpoint/tail cohorts are read through a declared translation and converted once on an owned semantic acceptance, after a verified exact-byte backup outside the canonical store. Upgrade/quiesce all writers first; unsupported layouts are not repaired. See the [format and backup boundary](docs/usage.md#moving-a-store-and-supported-formats) before upgrading or moving a store.
|
|
229
243
|
|
|
230
244
|
## Operational boundaries
|
|
231
245
|
|
|
@@ -244,6 +258,8 @@ Treat state, diagnostic logs and backups as private data. Revalidate consequenti
|
|
|
244
258
|
- [Acceptance map](docs/temporal-acceptance.md) — Properties tied to concrete tests.
|
|
245
259
|
- [Documentation index](docs/README.md) — All maintained guides.
|
|
246
260
|
|
|
261
|
+
The six built-in planes use an [internal semantic-module contract](docs/semantic-module-contract.md) for authoring policy and mode-aware context. It also supplies isolated context/producer executors. The candidate [public module entry](docs/public-module-contract.md) adds native-host-scoped registration/lifetime binding; metadata4 opaque-retention codecs have bounded local acceptance, and registered mutable external roots now use the existing read/patch tools in the local candidate; captured inputs and Active/Passive module context now execute with source and native-identity fences, and Active async producers commit complete replacements through the same atomic engine. Ordinary public consumers now have SOURCE/built-SDK acceptance evidence for current-only forks and opaque retention across folding, reload, absence and re-registration. This remains an unpublished local candidate, not a new independent activation approval. Neither contract implies a new loader or automatic background reasoning.
|
|
262
|
+
|
|
247
263
|
For development, run `npm install` and `npm run validate`. `npm run benchmark` is opt-in and separate from the normal suite; see `benchmarks/README.md` in a source checkout.
|
|
248
264
|
|
|
249
265
|
Project context: [AGENTS.md](AGENTS.md), [BACKLOG.md](BACKLOG.md), [CHANGELOG.md](CHANGELOG.md).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Domain: public State Flow module registration membrane.
|
|
2
|
+
// Registry ownership and semantic publication remain package-private.
|
|
3
|
+
export { registerStateFlowModule } from "../lib/module-registry.ts";
|
|
4
|
+
export type {
|
|
5
|
+
ModuleContextContribution,
|
|
6
|
+
StateFlowModule,
|
|
7
|
+
StateFlowModuleFrame,
|
|
8
|
+
StateFlowModuleInput,
|
|
9
|
+
StateFlowModuleSource,
|
|
10
|
+
} from "../lib/semantic-module.ts";
|
|
11
|
+
export type { JsonObject, JsonValue } from "../lib/json.ts";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { registerStateFlowModule } from "../lib/module-registry.ts";
|
|
2
|
+
export type { ModuleContextContribution, StateFlowModule, StateFlowModuleFrame, StateFlowModuleInput, StateFlowModuleSource, } from "../lib/semantic-module.ts";
|
|
3
|
+
export type { JsonObject, JsonValue } from "../lib/json.ts";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ArtifactInvalidationReason, type ArtifactInvalidationRequest, type ArtifactSourceIdentity, type ArtifactProvenanceRegistry } from "./artifact.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type AtomicScopePatches, type ScopeStateValues, type StateScope } from "./state.ts";
|
|
3
|
+
import { type ScopeSelection } from "./selection.ts";
|
|
3
4
|
/** Why the caller is considering source-body acquisition. */
|
|
4
5
|
export type ArtifactAcquisitionIntent = "routine" | "new-session" | "relevant-gap" | "exact-source" | "exact-edit" | "contradiction-or-failure" | "explicit-request" | "maintenance";
|
|
5
6
|
export type ArtifactAcquisitionReason = ArtifactInvalidationReason | "materialized-gap" | "exact-source" | "exact-edit" | "contradiction-or-failure" | "explicit-request" | "maintenance";
|
|
@@ -25,7 +26,7 @@ export interface SuccessfulArtifactRead extends ArtifactInvalidationRequest {
|
|
|
25
26
|
export declare class ArtifactReadTracker {
|
|
26
27
|
#private;
|
|
27
28
|
readonly successful: Map<string, SuccessfulArtifactRead>;
|
|
28
|
-
setCandidates(candidates: Iterable<ArtifactInvalidationRequest
|
|
29
|
+
setCandidates(candidates: Iterable<ArtifactInvalidationRequest>, selection?: ScopeSelection): void;
|
|
29
30
|
clear(): void;
|
|
30
31
|
recordStart(toolCallId: string, toolName: string, args: unknown): void;
|
|
31
32
|
recordCall(toolCallId: string, toolName: string, input: unknown): void;
|
|
@@ -54,9 +55,11 @@ export declare class ArtifactAcquisitionState {
|
|
|
54
55
|
/** Forget plan and hints when no memory view is selected. */
|
|
55
56
|
reset(): void;
|
|
56
57
|
/** Re-observe exact registered sources for the selected scope artifacts. */
|
|
57
|
-
refresh(states:
|
|
58
|
+
refresh(states: ScopeStateValues, provenance: (scope: StateScope) => ArtifactProvenanceRegistry, selection?: ScopeSelection): void;
|
|
58
59
|
/** Accepted compilations leave the plan; correlated read evidence is single-use. */
|
|
59
60
|
acceptAcquired(paths?: ReadonlySet<string>): void;
|
|
60
61
|
}
|
|
62
|
+
/** Exact-path metadata revalidation only; callers acquire source bodies outside canonical exclusion. */
|
|
63
|
+
export declare function validateArtifactReadEvidence(reads: Iterable<SuccessfulArtifactRead>, selection?: ScopeSelection): void;
|
|
61
64
|
/** Deletion patches for registered artifacts whose exact source is observed missing, in every owning scope. */
|
|
62
|
-
export declare function missingArtifactRemovals(states:
|
|
65
|
+
export declare function missingArtifactRemovals(states: ScopeStateValues, selection?: ScopeSelection): AtomicScopePatches;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { classifyArtifactCompilationNeed, inspectRegisteredArtifactPaths, sameArtifactSourceFingerprint, ORDINARY_ARTIFACT_COMPILER, } from "./artifact.js";
|
|
2
|
-
import { isObject } from "./json.js";
|
|
2
|
+
import { isObject, sameJson } from "./json.js";
|
|
3
|
+
import { assertScopeStateValues } from "./state.js";
|
|
4
|
+
import { captureScopeSelection, DEFAULT_SCOPE_SELECTION } from "./selection.js";
|
|
3
5
|
function readPath(toolName, args) {
|
|
4
6
|
return toolName === "read" && isObject(args) && typeof args.path === "string"
|
|
5
7
|
? args.path
|
|
@@ -10,10 +12,35 @@ export class ArtifactReadTracker {
|
|
|
10
12
|
successful = new Map();
|
|
11
13
|
#pending = new Map();
|
|
12
14
|
#candidates = new Map();
|
|
13
|
-
|
|
15
|
+
#selection = DEFAULT_SCOPE_SELECTION;
|
|
16
|
+
setCandidates(candidates, selection = DEFAULT_SCOPE_SELECTION) {
|
|
17
|
+
const wanted = captureScopeSelection(selection);
|
|
18
|
+
const next = new Map();
|
|
19
|
+
for (const candidate of candidates) {
|
|
20
|
+
if (!wanted.includes(candidate.scope ?? "global"))
|
|
21
|
+
continue;
|
|
22
|
+
const captured = structuredClone(candidate);
|
|
23
|
+
for (const key of ["scope", "hash", "sourceFingerprint"])
|
|
24
|
+
if (captured[key] === undefined)
|
|
25
|
+
delete captured[key];
|
|
26
|
+
next.set(candidate.path, captured);
|
|
27
|
+
}
|
|
28
|
+
if (!sameJson(wanted, this.#selection))
|
|
29
|
+
this.clear();
|
|
30
|
+
this.#selection = wanted;
|
|
31
|
+
for (const [path] of this.successful) {
|
|
32
|
+
const previous = this.#candidates.get(path), current = next.get(path);
|
|
33
|
+
if (previous === undefined || current === undefined || !sameJson(previous, current))
|
|
34
|
+
this.successful.delete(path);
|
|
35
|
+
}
|
|
14
36
|
this.#candidates.clear();
|
|
15
|
-
for (const candidate of
|
|
16
|
-
this.#candidates.set(
|
|
37
|
+
for (const [path, candidate] of next)
|
|
38
|
+
this.#candidates.set(path, candidate);
|
|
39
|
+
for (const [id, pending] of this.#pending) {
|
|
40
|
+
const current = pending.path === undefined ? undefined : next.get(pending.path);
|
|
41
|
+
if (current === undefined || pending.candidate === undefined || !sameJson(current, pending.candidate))
|
|
42
|
+
this.#pending.delete(id);
|
|
43
|
+
}
|
|
17
44
|
}
|
|
18
45
|
clear() {
|
|
19
46
|
this.successful.clear();
|
|
@@ -31,10 +58,10 @@ export class ArtifactReadTracker {
|
|
|
31
58
|
if (isError || !pending || toolName !== pending.toolName)
|
|
32
59
|
return;
|
|
33
60
|
const path = readPath(pending.toolName, pending.args);
|
|
34
|
-
if (path === undefined)
|
|
61
|
+
if (path === undefined || path !== pending.path)
|
|
35
62
|
return;
|
|
36
63
|
const candidate = this.#candidates.get(path);
|
|
37
|
-
if (candidate === undefined || pending.fingerprint === undefined)
|
|
64
|
+
if (candidate === undefined || pending.candidate === undefined || pending.fingerprint === undefined || !sameJson(candidate, pending.candidate))
|
|
38
65
|
return;
|
|
39
66
|
const observation = inspectRegisteredArtifactPaths([path])[0];
|
|
40
67
|
if (observation?.kind !== "present" || !sameArtifactSourceFingerprint(pending.fingerprint, observation.fingerprint))
|
|
@@ -53,7 +80,7 @@ export class ArtifactReadTracker {
|
|
|
53
80
|
const observation = path !== undefined && this.#candidates.has(path) ? inspectRegisteredArtifactPaths([path])[0] : undefined;
|
|
54
81
|
this.#pending.set(toolCallId, {
|
|
55
82
|
toolName,
|
|
56
|
-
args,
|
|
83
|
+
args, path, candidate: path === undefined ? undefined : this.#candidates.get(path),
|
|
57
84
|
...(observation?.kind === "present" ? { fingerprint: observation.fingerprint } : {}),
|
|
58
85
|
});
|
|
59
86
|
}
|
|
@@ -90,7 +117,6 @@ export function decideArtifactAcquisition(source, metadata, compiler, options) {
|
|
|
90
117
|
}
|
|
91
118
|
}
|
|
92
119
|
export const SOURCE_CHANGED_HINT = "Source changed since this artifact was compiled. Read and recompile it before relying on it.";
|
|
93
|
-
const SCOPES = ["global", "cwd", "session"];
|
|
94
120
|
/**
|
|
95
121
|
* One selected branch's ordinary-artifact acquisition plan: runtime-observed
|
|
96
122
|
* invalidations, their model hints and the read tracker correlated to them.
|
|
@@ -100,6 +126,7 @@ export class ArtifactAcquisitionState {
|
|
|
100
126
|
reads = new ArtifactReadTracker();
|
|
101
127
|
#invalidations = [];
|
|
102
128
|
#hints = {};
|
|
129
|
+
#selection = DEFAULT_SCOPE_SELECTION;
|
|
103
130
|
get invalidations() { return this.#invalidations; }
|
|
104
131
|
get hints() { return this.#hints; }
|
|
105
132
|
/** Drop the invalidation plan; hints remain until the next refresh. */
|
|
@@ -110,35 +137,35 @@ export class ArtifactAcquisitionState {
|
|
|
110
137
|
this.#setInvalidations([]);
|
|
111
138
|
}
|
|
112
139
|
/** Re-observe exact registered sources for the selected scope artifacts. */
|
|
113
|
-
refresh(states, provenance) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
140
|
+
refresh(states, provenance, selection = DEFAULT_SCOPE_SELECTION) {
|
|
141
|
+
const wanted = captureScopeSelection(selection);
|
|
142
|
+
assertScopeStateValues(states, wanted);
|
|
143
|
+
const effective = new Map();
|
|
144
|
+
for (const scope of wanted)
|
|
145
|
+
for (const [path, metadata] of Object.entries(states[scope].artifacts ?? {}))
|
|
146
|
+
effective.set(path, { scope, metadata });
|
|
147
|
+
const paths = [...effective].filter(([, { metadata }]) => metadata.kind !== "skill").map(([path]) => path);
|
|
118
148
|
const observations = new Map(inspectRegisteredArtifactPaths(paths).map((observation) => [observation.path, observation]));
|
|
119
149
|
const hints = {};
|
|
120
|
-
const invalidations =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (need.reason === "source-changed")
|
|
136
|
-
hints[path] = SOURCE_CHANGED_HINT;
|
|
137
|
-
invalidations.set(path, { path, scope, reason: need.reason });
|
|
138
|
-
}
|
|
150
|
+
const invalidations = [];
|
|
151
|
+
const registries = new Map();
|
|
152
|
+
for (const path of paths) {
|
|
153
|
+
const { scope, metadata } = effective.get(path);
|
|
154
|
+
const observed = observations.get(path);
|
|
155
|
+
if (observed?.kind !== "present")
|
|
156
|
+
continue;
|
|
157
|
+
if (!registries.has(scope))
|
|
158
|
+
registries.set(scope, provenance(scope));
|
|
159
|
+
const need = classifyArtifactCompilationNeed({ path, scope, sourceFingerprint: observed.fingerprint }, metadata, ORDINARY_ARTIFACT_COMPILER, false, registries.get(scope)[path]);
|
|
160
|
+
if (need.kind !== "requires-compilation")
|
|
161
|
+
continue;
|
|
162
|
+
if (need.reason === "source-changed")
|
|
163
|
+
hints[path] = SOURCE_CHANGED_HINT;
|
|
164
|
+
invalidations.push({ path, scope, reason: need.reason });
|
|
139
165
|
}
|
|
166
|
+
this.#selection = wanted;
|
|
140
167
|
this.#hints = hints;
|
|
141
|
-
this.#setInvalidations(
|
|
168
|
+
this.#setInvalidations(invalidations.sort((left, right) => left.path.localeCompare(right.path)));
|
|
142
169
|
}
|
|
143
170
|
/** Accepted compilations leave the plan; correlated read evidence is single-use. */
|
|
144
171
|
acceptAcquired(paths = new Set(this.reads.successful.keys())) {
|
|
@@ -147,13 +174,27 @@ export class ArtifactAcquisitionState {
|
|
|
147
174
|
}
|
|
148
175
|
#setInvalidations(invalidations) {
|
|
149
176
|
this.#invalidations = invalidations;
|
|
150
|
-
this.reads.setCandidates(invalidations);
|
|
177
|
+
this.reads.setCandidates(invalidations, this.#selection);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** Exact-path metadata revalidation only; callers acquire source bodies outside canonical exclusion. */
|
|
181
|
+
export function validateArtifactReadEvidence(reads, selection = DEFAULT_SCOPE_SELECTION) {
|
|
182
|
+
const wanted = captureScopeSelection(selection);
|
|
183
|
+
for (const read of reads) {
|
|
184
|
+
if (!wanted.includes(read.scope ?? "global"))
|
|
185
|
+
continue;
|
|
186
|
+
const observed = inspectRegisteredArtifactPaths([read.path])[0];
|
|
187
|
+
if (read.sourceFingerprint === undefined || observed?.kind !== "present" || !sameArtifactSourceFingerprint(read.sourceFingerprint, observed.fingerprint)) {
|
|
188
|
+
throw new Error(`Artifact source changed after acquisition: ${read.path}`);
|
|
189
|
+
}
|
|
151
190
|
}
|
|
152
191
|
}
|
|
153
192
|
/** Deletion patches for registered artifacts whose exact source is observed missing, in every owning scope. */
|
|
154
|
-
export function missingArtifactRemovals(states) {
|
|
193
|
+
export function missingArtifactRemovals(states, selection = DEFAULT_SCOPE_SELECTION) {
|
|
194
|
+
const wanted = captureScopeSelection(selection);
|
|
195
|
+
assertScopeStateValues(states, wanted);
|
|
155
196
|
const owners = new Map();
|
|
156
|
-
for (const scope of
|
|
197
|
+
for (const scope of wanted)
|
|
157
198
|
for (const path of Object.keys(states[scope].artifacts ?? {})) {
|
|
158
199
|
owners.set(path, [...owners.get(path) ?? [], scope]);
|
|
159
200
|
}
|
|
@@ -107,6 +107,8 @@ export declare function isArtifactRegistry(value: unknown): value is ArtifactReg
|
|
|
107
107
|
export declare function parseArtifactProvenanceRegistry(value: unknown, context?: string): ArtifactProvenanceRegistry;
|
|
108
108
|
/** Canonical retained form; uninterpretable entries cannot round-trip and are omitted. */
|
|
109
109
|
export declare function serializeArtifactProvenanceRegistry(registry: Readonly<ArtifactProvenanceRegistry>): JsonObject;
|
|
110
|
+
/** Eligibility to revoke known evidence, not retained-data admission or a sanitizer. */
|
|
111
|
+
export declare function isInterpretableArtifactProvenance(entry: unknown): entry is ArtifactProvenance;
|
|
110
112
|
/** Decide whether retained compilation evidence requires source reacquisition. */
|
|
111
113
|
export declare function classifyArtifactCompilationNeed(source: ArtifactSourceIdentity, metadata: unknown, compiler: string, explicitRefresh?: boolean, provenance?: unknown): ArtifactCompilationNeed;
|
|
112
114
|
/** Split one compiler output into model-visible semantics and runtime-owned provenance. */
|
|
@@ -160,34 +160,25 @@ export function parseArtifactProvenanceRegistry(value, context = "Artifact prove
|
|
|
160
160
|
return {};
|
|
161
161
|
if (!isObject(value))
|
|
162
162
|
throw new Error(`${context} must be a path-keyed JSON object`);
|
|
163
|
-
const
|
|
164
|
-
|
|
163
|
+
const known = new Set(["sourceHash", "sourceFingerprint", "compilerRevision", "compiledAt"]);
|
|
164
|
+
return Object.fromEntries(Object.entries(value).map(([path, entry]) => {
|
|
165
165
|
if (path.trim().length === 0)
|
|
166
166
|
throw new Error(`${context} path keys must be non-empty`);
|
|
167
|
-
if (!isObject(entry) || !isJsonValue(entry))
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
registry[path] = {
|
|
177
|
-
...(Object.hasOwn(entry, "sourceHash") ? { sourceHash: entry.sourceHash } : {}),
|
|
178
|
-
...(Object.hasOwn(entry, "sourceFingerprint") ? { sourceFingerprint: entry.sourceFingerprint } : {}),
|
|
179
|
-
...(Object.hasOwn(entry, "compilerRevision") ? { compilerRevision: entry.compilerRevision } : {}),
|
|
180
|
-
...(Object.hasOwn(entry, "compiledAt") ? { compiledAt: entry.compiledAt } : {}),
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
return registry;
|
|
167
|
+
if (!isObject(entry) || !isJsonValue(entry) || Object.keys(entry).some(key => !known.has(key)))
|
|
168
|
+
return [path, { malformed: true }];
|
|
169
|
+
return [path, {
|
|
170
|
+
...(Object.hasOwn(entry, "sourceHash") ? { sourceHash: entry.sourceHash } : {}),
|
|
171
|
+
...(Object.hasOwn(entry, "sourceFingerprint") ? { sourceFingerprint: entry.sourceFingerprint } : {}),
|
|
172
|
+
...(Object.hasOwn(entry, "compilerRevision") ? { compilerRevision: entry.compilerRevision } : {}),
|
|
173
|
+
...(Object.hasOwn(entry, "compiledAt") ? { compiledAt: entry.compiledAt } : {}),
|
|
174
|
+
}];
|
|
175
|
+
}));
|
|
184
176
|
}
|
|
185
177
|
/** Canonical retained form; uninterpretable entries cannot round-trip and are omitted. */
|
|
186
178
|
export function serializeArtifactProvenanceRegistry(registry) {
|
|
187
|
-
|
|
188
|
-
for (const [path, entry] of Object.entries(registry)) {
|
|
179
|
+
return Object.fromEntries(Object.entries(registry).flatMap(([path, entry]) => {
|
|
189
180
|
if (entry.malformed === true)
|
|
190
|
-
|
|
181
|
+
return [];
|
|
191
182
|
const fields = {};
|
|
192
183
|
if (entry.sourceHash !== undefined)
|
|
193
184
|
fields.sourceHash = entry.sourceHash;
|
|
@@ -197,11 +188,19 @@ export function serializeArtifactProvenanceRegistry(registry) {
|
|
|
197
188
|
fields.compilerRevision = entry.compilerRevision;
|
|
198
189
|
if (entry.compiledAt !== undefined)
|
|
199
190
|
fields.compiledAt = entry.compiledAt;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
191
|
+
return Object.keys(fields).length === 0 ? [] : [[path, fields]];
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
/** Eligibility to revoke known evidence, not retained-data admission or a sanitizer. */
|
|
195
|
+
export function isInterpretableArtifactProvenance(entry) {
|
|
196
|
+
if (!isObject(entry))
|
|
197
|
+
return false;
|
|
198
|
+
const keys = Object.keys(entry);
|
|
199
|
+
return keys.length > 0 && keys.every(key => ["sourceHash", "sourceFingerprint", "compilerRevision", "compiledAt"].includes(key))
|
|
200
|
+
&& (!Object.hasOwn(entry, "sourceHash") || isArtifactHash(entry.sourceHash))
|
|
201
|
+
&& (!Object.hasOwn(entry, "sourceFingerprint") || parseArtifactSourceFingerprint(entry.sourceFingerprint) !== undefined)
|
|
202
|
+
&& (!Object.hasOwn(entry, "compilerRevision") || (typeof entry.compilerRevision === "string" && entry.compilerRevision.trim().length > 0))
|
|
203
|
+
&& (!Object.hasOwn(entry, "compiledAt") || typeof entry.compiledAt === "string");
|
|
205
204
|
}
|
|
206
205
|
function invalidField(value, validate) {
|
|
207
206
|
return value !== undefined && !validate(value);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { type ScopeSelection } from "./selection.ts";
|
|
1
2
|
import { type InactiveMode, type StateFlowMode } from "./snapshot.ts";
|
|
2
3
|
export interface StateFlowConfig {
|
|
3
4
|
/** Canonical State Flow repository. SDK callers may still override it explicitly. */
|
|
4
5
|
directory: string;
|
|
5
6
|
/** Default mode for genuinely new sessions only; each session owns its selected mode. */
|
|
6
7
|
mode: StateFlowMode;
|
|
8
|
+
/** Concrete default selection for genuinely new or not-yet-captured legacy sessions. */
|
|
9
|
+
scopes: ScopeSelection;
|
|
7
10
|
/** Non-active fallback for legacy `enabled:false` evidence and unavailable selections; never serialized. */
|
|
8
11
|
inactiveMode: InactiveMode;
|
|
9
12
|
/** Opt-in local capture of rejected patch attempts and unresolved terminal drafts. */
|
|
@@ -5,6 +5,7 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
|
5
5
|
import { getDurableRepositoryRoot } from "./durable.js";
|
|
6
6
|
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
|
|
7
7
|
import { isObject } from "./json.js";
|
|
8
|
+
import { captureScopeSelection, DEFAULT_SCOPE_SELECTION } from "./selection.js";
|
|
8
9
|
import { isStateFlowMode } from "./snapshot.js";
|
|
9
10
|
/** Read the repository-global config once at extension load/reload. Missing config uses defaults; invalid config never falls back. */
|
|
10
11
|
export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = getDurableRepositoryRoot(agentDir)) {
|
|
@@ -13,6 +14,7 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
|
|
|
13
14
|
const defaults = {
|
|
14
15
|
directory,
|
|
15
16
|
mode: "off",
|
|
17
|
+
scopes: DEFAULT_SCOPE_SELECTION,
|
|
16
18
|
inactiveMode: "off",
|
|
17
19
|
logging: false,
|
|
18
20
|
showSuccessfulPatches: true,
|
|
@@ -28,7 +30,7 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
|
|
|
28
30
|
throw new Error(`Cannot read State Flow configuration: ${path}`, { cause: error });
|
|
29
31
|
}
|
|
30
32
|
// autoStart/passiveBootstrap/passiveTools are read-only compatibility inputs; explicit mode is authoritative.
|
|
31
|
-
const allowed = new Set(["mode", "autoStart", "passiveBootstrap", "passiveTools", "logging", "showSuccessfulPatches", "historyLimit"]);
|
|
33
|
+
const allowed = new Set(["scopes", "mode", "autoStart", "passiveBootstrap", "passiveTools", "logging", "showSuccessfulPatches", "historyLimit"]);
|
|
32
34
|
if (!isObject(value) || Object.keys(value).some((key) => !allowed.has(key))) {
|
|
33
35
|
throw new Error(`State Flow configuration contains unknown settings: ${path}`);
|
|
34
36
|
}
|
|
@@ -46,12 +48,22 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
|
|
|
46
48
|
throw new Error(`State Flow showSuccessfulPatches must be a boolean: ${path}`);
|
|
47
49
|
if (Object.hasOwn(value, "historyLimit") && (!Number.isSafeInteger(value.historyLimit) || value.historyLimit < 0 || value.historyLimit > MAX_HISTORY_LIMIT))
|
|
48
50
|
throw new Error(`State Flow historyLimit must be an integer from 0 to ${MAX_HISTORY_LIMIT}: ${path}`);
|
|
51
|
+
let scopes = DEFAULT_SCOPE_SELECTION;
|
|
52
|
+
if (Object.hasOwn(value, "scopes")) {
|
|
53
|
+
try {
|
|
54
|
+
scopes = captureScopeSelection(value.scopes);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new Error(`Invalid State Flow scopes configuration: ${path}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
49
60
|
const legacyInactive = value.passiveBootstrap !== false || value.passiveTools !== false ? "passive" : "off";
|
|
50
61
|
const hasLegacyMode = Object.hasOwn(value, "autoStart") || Object.hasOwn(value, "passiveBootstrap") || Object.hasOwn(value, "passiveTools");
|
|
51
62
|
const mode = isStateFlowMode(value.mode) ? value.mode : value.autoStart === true ? "active" : hasLegacyMode ? legacyInactive : "off";
|
|
52
63
|
return {
|
|
53
64
|
directory,
|
|
54
65
|
mode,
|
|
66
|
+
scopes,
|
|
55
67
|
inactiveMode: isStateFlowMode(value.mode) ? inactiveModeFor(value.mode) : hasLegacyMode ? legacyInactive : "off",
|
|
56
68
|
logging: value.logging === true,
|
|
57
69
|
showSuccessfulPatches: value.showSuccessfulPatches !== false,
|