@llblab/pi-kit 0.29.0 → 0.29.2

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.
Files changed (65) hide show
  1. package/BACKLOG.md +3 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +4 -4
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +1 -32
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +2 -2
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +2 -2
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +17 -8
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +7 -3
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +1 -1
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +2 -1
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +3 -2
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
  14. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  15. package/node_modules/@llblab/pi-state-flow/docs/agent-contract-relocation.md +1 -1
  16. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -3
  17. package/node_modules/@llblab/pi-state-flow/docs/usage.md +5 -4
  18. package/node_modules/@llblab/pi-state-flow/lib/context.ts +17 -8
  19. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +6 -3
  20. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -1
  21. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +4 -2
  22. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
  23. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  24. package/node_modules/@llblab/pi-telegram/AGENTS.md +6 -5
  25. package/node_modules/@llblab/pi-telegram/BACKLOG.md +18 -1
  26. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +12 -0
  27. package/node_modules/@llblab/pi-telegram/README.md +5 -2
  28. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +1 -0
  29. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -3
  30. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +4 -0
  31. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +7 -1
  32. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +72 -11
  33. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +5 -0
  34. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +29 -11
  35. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +2 -0
  36. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +12 -2
  37. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +2 -0
  38. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +25 -3
  39. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
  40. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +118 -44
  41. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +5 -0
  42. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +48 -5
  43. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +2 -0
  44. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +22 -1
  45. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +6 -4
  46. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +45 -3
  47. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +11 -4
  48. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +34 -18
  49. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  50. package/node_modules/@llblab/pi-telegram/docs/architecture.md +14 -8
  51. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +1 -1
  52. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +1 -0
  53. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +4 -2
  54. package/node_modules/@llblab/pi-telegram/lib/extension.ts +5 -0
  55. package/node_modules/@llblab/pi-telegram/lib/journal.ts +80 -11
  56. package/node_modules/@llblab/pi-telegram/lib/locks.ts +28 -11
  57. package/node_modules/@llblab/pi-telegram/lib/polling.ts +21 -2
  58. package/node_modules/@llblab/pi-telegram/lib/queue.ts +26 -3
  59. package/node_modules/@llblab/pi-telegram/lib/routing.ts +91 -36
  60. package/node_modules/@llblab/pi-telegram/lib/sync.ts +47 -5
  61. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +26 -1
  62. package/node_modules/@llblab/pi-telegram/lib/threads.ts +46 -7
  63. package/node_modules/@llblab/pi-telegram/lib/updates.ts +43 -21
  64. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  65. package/package.json +3 -3
package/BACKLOG.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # Backlog
2
2
 
3
- The 0.29.0 composition advances State Flow to 0.26.0; package pins, resource order and bundled runtime ownership remain authoritative in `package.json`. Release outcomes belong in [CHANGELOG.md](./CHANGELOG.md).
3
+ The 0.29.2 composition advances State Flow to 0.26.4 and Telegram to 0.52.2; package pins, resource order and bundled runtime ownership remain authoritative in `package.json`. Release outcomes belong in [CHANGELOG.md](./CHANGELOG.md).
4
4
 
5
5
  ## Carried checks
6
6
 
7
+ - **Installed 0.29.2 continuity/recovery smoke (operator-owned):** After separately authorized installation/reload, use disposable sessions/storage to confirm idle Stop followed by native compaction preserves the summary and tool results through Passive reload/resume, without replaying effects. Confirm Telegram outage recovery and unconfirmed-disconnect reporting on a disposable setup. Packed validation does not certify running clients or authorize live fault injection.
8
+ - **Installed 0.29.1 State Flow smoke (operator-owned):** After separately authorized installation/reload, use disposable storage to confirm new diagnostics use `tmp/pi-state-flow`, old `tmp/state-flow` logs and canonical memory stay untouched, and selected Telegram modes show 🟣 Off, 🟡 Passive and 🟢 Active. Packed validation does not certify installed clients.
7
9
  - **Installed 0.29.0 State Flow smoke (operator-owned):** After separately authorized installation/reload, use disposable storage to confirm `/tree` to an old step and reload keep current memory writable in Active and Passive, a fork copies the parent's current memory, and unreadable current files show `active (blocked)` with Passive retry and Off releasing inference only. Packed validation does not certify installed clients.
8
10
  - **Installed 0.28.0 Telegram smoke (operator-owned):** After separately authorized installation/reload and queue drain or accepted waiting-work loss, confirm Restore/routing controls and follower delivery with disposable runtime storage. Do not test damaged-state reset against live shared state. Windows Restore remains fail-closed without strict evidence; packed validation does not certify installed clients.
9
11
  - **Installed 0.27.6 restoration/compaction smoke (operator-owned):** After separately authorized installation/reload, use disposable storage to confirm failed Active restoration blocks provider inference across tree/reload, explicit mode recovery remains available, and native Codemode values/deletions survive completed-history compaction. Packed validation does not certify installed clients.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to `@llblab/pi-kit` are documented here.
4
4
 
5
+ ## 0.29.2: State Flow Continuity and Telegram Recovery
6
+
7
+ - `State Flow Continuity`: Advances the exact State Flow pin to `0.26.4`. Passive retains native summaries and tool calls/results after idle Stop and split-turn compaction, including through reload/resume. Automatic Passive memory omits stored answers; Active response guidance keeps answer ownership with the runtime. No memory migration or configuration change is required.
8
+ - `Telegram Recovery`: Advances the exact Telegram pin to `0.52.2`, adding finite chooser lifetimes, waiting-continue cancellation, polling recovery, replacement-safe disconnect and bounded Windows attachment publication retries. Unconfirmed cleanup remains explicit. Other pins, package membership, resources and load order are unchanged.
9
+
10
+ ## 0.29.1: State Flow Temporary Paths and Mode Colors
11
+
12
+ - `State Flow Consistency`: Advances the exact State Flow pin to `0.26.1`. New diagnostics and Git push failure logs use `tmp/pi-state-flow` without moving old logs or canonical memory. Selected Telegram modes use 🟣 Off, 🟡 Passive and 🟢 Active; unselected options remain ⚫️. Other pins, resources and load order are unchanged.
13
+
5
14
  ## 0.29.0: State Flow Stable Modes
6
15
 
7
16
  - `Step-independent memory`: Advances the exact State Flow pin to `0.26.0`. Memory no longer depends on the Pi step: startup, reload, resume and `/tree` attach current same-session memory, old branch points never block or rewind it, and forks copy the parent's current memory. Passive stays writable across reload; unreadable memory under Active shows `active (blocked)`. Other pins, resources and load order are unchanged.
package/README.md CHANGED
@@ -17,15 +17,15 @@ Package links lead to the owning repositories for usage, documentation, issues,
17
17
  | [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.3.0` | Isolated nested Pi TUI with named npm extensions and compatible model selection |
18
18
  | [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.12.1` | Shared Codex quota/Business credit status and persistent priority Fast toggle, mirrored in Telegram |
19
19
  | [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.9.0` | Visible continuation scheduling and bounded worker Skills through compiled, manifest-owned resources |
20
- | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.26.0` | Scoped context/memory compiler with step-independent current memory, stable Passive/Active modes and visible blocked-state status |
21
- | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.52.0` | Telegram companion with Workspace Restore, routing lifetime, resilient runtime storage, follower Threads, files, voice, and controls |
20
+ | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.26.4` | Scoped context/memory compiler with step-independent current memory, stable Passive/Active modes and visible blocked-state status |
21
+ | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.52.2` | Telegram companion with Workspace Restore, routing lifetime, resilient runtime storage, follower Threads, files, voice, and controls |
22
22
  | [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
23
23
 
24
24
  Versions are exact by design. An upstream release does not change an installed kit until this repository explicitly advances the dependency and publishes a new kit version. Runtime defects and package-specific feature requests belong in the linked repository; package selection and kit installation issues belong here.
25
25
 
26
26
  ## Install
27
27
 
28
- Requires **Pi 1.0.0+** and **Node.js 22.19.0+**. State Flow requires its canonical checkpoint/tail storage format and does not convert unsupported stores in place. Preserve existing stores and consult the [owning package's storage guidance](https://github.com/llblab/pi-state-flow/blob/v0.26.0/docs/usage.md#moving-a-store-and-supported-formats) before changing installations.
28
+ Requires **Pi 1.0.0+** and **Node.js 22.19.0+**. State Flow requires its canonical checkpoint/tail storage format and does not convert unsupported stores in place. Preserve existing stores and consult the [owning package's storage guidance](https://github.com/llblab/pi-state-flow/blob/v0.26.4/docs/usage.md#moving-a-store-and-supported-formats) before changing installations.
29
29
 
30
30
  From npm:
31
31
 
@@ -45,7 +45,7 @@ Prefer the kit instead of separately loading the same packages. If you already u
45
45
 
46
46
  ## Development
47
47
 
48
- The `0.29.0` composition includes State Flow `0.26.0` with step-independent current memory and stable modes; all other pins, resources and load order remain unchanged. Telegram runtime storage now uses `tmp/pi-telegram` without migrating the old directory. Drain the prompt queue or accept losing waiting work before reload/restart; damaged-state reset discards all profiles' runtime continuity. Windows Restore remains fail-closed without strict journal evidence. `npm run validate` checks exact installed pins, declared resources, dependency audit and bundled inventory. It does not certify installed-client rendering; carried checks remain in [Backlog](./BACKLOG.md).
48
+ The `0.29.2` composition includes State Flow `0.26.4`, retaining native summaries and tool continuity after Passive compaction while omitting stored answers from automatic Passive memory. Telegram `0.52.2` adds polling recovery, fenced disconnect and bounded Windows attachment publication retries. Current memory, stable modes, all other pins, resources and load order remain unchanged. Telegram runtime storage now uses `tmp/pi-telegram` without migrating the old directory. Drain the prompt queue or accept losing waiting work before reload/restart; damaged-state reset discards all profiles' runtime continuity. Windows Restore remains fail-closed without strict journal evidence. `npm run validate` checks exact installed pins, declared resources, dependency audit and bundled inventory. It does not certify installed-client rendering; carried checks remain in [Backlog](./BACKLOG.md).
49
49
 
50
50
  ```bash
51
51
  npm install
@@ -1,34 +1,3 @@
1
1
  # Backlog
2
2
 
3
- The **0.25.5: Active Restoration Fence and Codemode Compaction** hotfix is published (exact-tag workflow, GitHub Release and npm commit verified) and bundled in published Pi Kit 0.27.6. **0.26.0: Stable Modes** is prepared and validated locally but not committed, tagged or published; Pi Kit sync follows its release. Outcomes belong in [CHANGELOG.md](CHANGELOG.md). This backlog retains installed-client gates and deferred decisions.
4
-
5
- ## Out of scope
6
-
7
- - Changing what a cascade deletes, including writes into an owned target made by the closing patch. That stays deleted by design and already appears in the receipt.
8
- - Nested `lazy_navigation` and intent-ownership warnings, validation or rejection. Authored missing-deletion hints do not change cascade behavior.
9
- - Any reduction of lifecycle state; the tagged-union question stays deferred.
10
-
11
- ## 0.26.0 acceptance
12
-
13
- - **Installed stable-mode smoke (operator-owned).** After separately authorized reload, use disposable storage to confirm `/tree` to an old step and reload keep current memory writable in Active and Passive; a fork copies the parent's current memory; unreadable current files show `active (blocked)` in terminal/Telegram, Passive retries and Off releases inference only. Local/native validation does not certify the installed client.
14
-
15
- ## 0.25.5 acceptance
16
-
17
- - **Installed restoration/compaction smoke (operator-owned).** After separately authorized reload, use disposable storage to confirm failed Active restoration blocks provider inference across tree/reload, explicit mode recovery remains available, and native Codemode values/deletions survive completed-history compaction. Local/native tests do not certify the installed client.
18
-
19
- ## Carried gates
20
-
21
- - **Installed 0.25.4 cleanup smoke (operator-owned).** After separately authorized installation/reload, use disposable storage to confirm missing-deletion hints, recursive empty-object cleanup, inherited fallback and preserved array slots. Local validation and publication do not certify installed clients.
22
- - **Installed 0.25.3 inspection smoke (operator-owned).** After separately authorized installation/reload, inspect a large nested field: real layout newlines/quotes, separate omitted-character notice and intact genuine JSON string escapes. Use disposable storage; local adapter tests do not certify installed Telegram clients.
23
- - **Installed 0.25.2 smoke (operator-owned).** Disposable store: confirm one patch argument block followed by changed/no-op acknowledgements, compact rows with `showSuccessfulPatches: false`, and visible rejected arguments/errors.
24
- - **Installed 0.25.1 smoke (operator-owned).** Disposable store: close an intent that owns a nested lazy key and confirm the receipt lists it under `cascaded`. May be combined with the open 0.25.0 smoke.
25
- - **Installed 0.22.0, 0.23.0, 0.24.0 and 0.25.0 smokes** remain open as recorded.
26
-
27
- ## Deferred (decision inputs, not commitments)
28
-
29
- - **Closing-patch losses.** In real sessions, look for closing patches that write into a target the same patch cascades. If frequent, revisit the closing sentence in the protocol, not the mechanism.
30
- - Convention uptake, behavioural evaluation, lifecycle real-use measurement, lifecycle tagged union, Skill compilation fidelity and compaction threshold carry over unchanged from the 0.25.0 backlog.
31
-
32
- ## Release boundary
33
-
34
- Installation/reload requires separate authorization and disposable storage. Local validation and publication do not certify installed clients.
3
+ No open work.
@@ -2,6 +2,23 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
5
+ ## 0.26.4: Passive Compaction Continuity
6
+
7
+ - `Passive continuation`: After idle Stop, native compaction can replace the later user anchor with a summary. Passive now retains that summary, the available tool calls/results and later refinements instead of reducing requests to a frozen handoff. Reload/resume preserve the same continuation; discarded raw history is not reconstructed. Native compaction, memory scopes and stored state are unchanged; no migration or configuration change is required.
8
+
9
+ ## 0.26.3: Passive Response Isolation
10
+
11
+ - `Passive context`: Automatic Passive memory heads, Stop handoffs and state-update receipts omit the `response` plane, including after reload and mode changes. Active still sees the previous accepted answer. Canonical response storage, explicit reads and native/user/tool history are unchanged; no migration or configuration change is required.
12
+
13
+ ## 0.26.2: Automatic Response Guidance
14
+
15
+ - `Response ownership`: Active/bootstrap and Passive prompts explicitly forbid patching `response`; the `patch_state` guidelines forbid it in Global, CWD and Session, including clearing/deleting it, and explain that Active mode captures the final answer automatically. Runtime validation and answer capture are unchanged. No migration or configuration change is required.
16
+
17
+ ## 0.26.1: Consistent Temporary Paths and Mode Colors
18
+
19
+ - `Telegram mode colors`: The selected mode uses 🟣 Off, 🟡 Passive or 🟢 Active; unselected options remain ⚫️. Mode behavior and callbacks are unchanged.
20
+ - `Diagnostic directory`: New diagnostics and Git push failure logs use `<agentDir>/tmp/pi-state-flow/logs.jsonl`, matching the Pi extension temporary-directory naming convention. Older `tmp/state-flow` logs are left untouched; canonical `state-flow/` memory storage is unchanged.
21
+
5
22
  ## 0.26.0: Stable Modes
6
23
 
7
24
  - `Step-independent memory`: Memory no longer depends on the Pi step. Startup, reload, resume and `/tree` attach the current same-session JSON state; native checkpoints supply only mode and run lifecycle. Old or expired branch points never block or rewind memory. Forks copy the parent's current memory. Past values remain readable through `read_state` offsets within retained history.
@@ -81,7 +81,7 @@ New sessions default to **Off**. Choose Passive for ordinary conversation with m
81
81
  ### Active, passive, off
82
82
 
83
83
  - `Active`: Memory tools are available, and the agent consolidates necessary final state changes before completing an iteration. Later iterations use accepted state and new input rather than completed prior reasoning.
84
- - `Passive`: Both memory tools are available, and existing state is projected once a validated memory view is available. The agent patches on demand, and ordinary conversation context continues without State Flow's active iteration reset.
84
+ - `Passive`: Both memory tools are available, and existing state except `response` is projected once a validated memory view is available. The agent patches on demand, and ordinary conversation context continues without State Flow's active iteration reset.
85
85
  - `Off`: The model sees neither memory tool nor any State Flow context, including a frozen passive handoff.
86
86
  - Startup, reload and tree attachment do not read or restore memory.
87
87
  - Selecting Off cancels pending memory waits and records only native policy/bookmarks.
@@ -115,7 +115,7 @@ Runtime views provide these documented planes:
115
115
  - `contract`: Requirements, decisions, rejected approaches, constraints and interface commitments.
116
116
  - `working`: Temporary context of those actions: observations, results and uncertainties.
117
117
  - `artifacts`: Source-addressed descriptions and compiled knowledge.
118
- - `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.
118
+ - `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
119
  - `lazy`: Supporting memory available through explicit reads; its body is omitted from baseline model context.
120
120
 
121
121
  Every plane is optional on disk:
@@ -39,7 +39,7 @@ export interface ContextView {
39
39
  phase: RehydrationPhase;
40
40
  } | null;
41
41
  }
42
- export declare function contextView(state: SemanticState, hints: ArtifactModelHints, invalidations: readonly ArtifactInvalidationNotice[], phase?: RehydrationPhase): ContextView;
42
+ export declare function contextView(state: SemanticState, hints: ArtifactModelHints, invalidations: readonly ArtifactInvalidationNotice[], phase?: RehydrationPhase, includeResponse?: boolean): ContextView;
43
43
  /** Volatile model projection only. Native messages own trajectory; this cache owns no persistence or lifecycle. */
44
44
  export declare class ContextProjection {
45
45
  private identity;
@@ -49,7 +49,7 @@ export declare class ContextProjection {
49
49
  private notices;
50
50
  reset(): void;
51
51
  /** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
52
- acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints, cascades?: Partial<Record<StateScope, readonly OwnedPath[]>>): {
52
+ acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints, cascades?: Partial<Record<StateScope, readonly OwnedPath[]>>, includeResponse?: boolean): {
53
53
  effective: ModelStateUpdate[];
54
54
  lazy_navigation?: {
55
55
  available: boolean;
@@ -129,8 +129,14 @@ function projectedStateUpdates(previous, current, patches, beforeNavigation, nav
129
129
  }
130
130
  return { effective, ...(navigation !== undefined && (beforeNavigation === undefined || !sameJson(beforeNavigation, navigation)) ? { lazy_navigation: navigation } : {}) };
131
131
  }
132
- export function contextView(state, hints, invalidations, phase) {
133
- return { state: projectStateForModel(state, hints), lazy_navigation: lazyNavigationHint(state),
132
+ function projectContextState(state, hints, includeResponse) {
133
+ const projected = projectStateForModel(state, hints);
134
+ if (!includeResponse)
135
+ delete projected.response;
136
+ return projected;
137
+ }
138
+ export function contextView(state, hints, invalidations, phase, includeResponse = true) {
139
+ return { state: projectContextState(state, hints, includeResponse), lazy_navigation: lazyNavigationHint(state),
134
140
  artifact_invalidations: structuredClone(invalidations), knowledge_rehydration: phase === undefined ? null : { phase } };
135
141
  }
136
142
  /** Volatile model projection only. Native messages own trajectory; this cache owns no persistence or lifecycle. */
@@ -148,11 +154,11 @@ export class ContextProjection {
148
154
  this.notices = [];
149
155
  }
150
156
  /** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
151
- acceptPatch(before, after, patches, hints, cascades = {}) {
152
- const state = projectStateForModel(after, hints);
157
+ acceptPatch(before, after, patches, hints, cascades = {}, includeResponse = true) {
158
+ const state = projectContextState(after, hints, includeResponse);
153
159
  const navigation = lazyNavigationHint(after);
154
160
  const beforeNavigation = this.view?.lazy_navigation ?? lazyNavigationHint(before);
155
- const updates = projectedStateUpdates(this.view?.state ?? projectStateForModel(before, hints), state, patches, beforeNavigation, navigation);
161
+ const updates = projectedStateUpdates(this.view?.state ?? projectContextState(before, hints, includeResponse), state, patches, beforeNavigation, navigation);
156
162
  // Suppress direct writes only when the accepted effective value matches.
157
163
  // Overlap stays conservative except for explicit top-scope replacements:
158
164
  // Session scalars/arrays mask every lower-scope value at that path.
@@ -319,12 +325,14 @@ function messageText(message) {
319
325
  return contentText(message.content);
320
326
  }
321
327
  export function createPassiveContinuation(state, startedAt = Date.now(), activeRunStartedAt, preserveContext = false) {
328
+ const visibleState = structuredClone(state);
329
+ delete visibleState.response;
322
330
  return {
323
331
  startedAt,
324
- state: structuredClone(state),
332
+ state: visibleState,
325
333
  ...(activeRunStartedAt === undefined ? {} : { activeRunStartedAt }),
326
334
  ...(preserveContext ? { preserveContext: true } : {}),
327
- handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state, continuation: preserveContext
335
+ handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state: visibleState, continuation: preserveContext
328
336
  ? "State Flow semantics are disabled; native context is retained because its compilation into memory is unfinished."
329
337
  : "State Flow semantics are disabled; this handoff replaces completed history while retaining the active and post-stop trajectory." })}`),
330
338
  };
@@ -337,7 +345,8 @@ export function passiveContinuationMessages(messages, continuation) {
337
345
  const trajectory = currentRunTrajectory(messages, "", continuation.activeRunStartedAt);
338
346
  return [continuation.handoff, ...trajectory.messages];
339
347
  }
340
- const start = messages.findIndex((message) => message.role === "user"
348
+ // A post-stop native summary replaces its compacted user anchor, including the retained tool suffix.
349
+ const start = messages.findIndex((message) => (message.role === "user" || message.role === "compactionSummary")
341
350
  && typeof message.timestamp === "number"
342
351
  && message.timestamp >= continuation.startedAt);
343
352
  return [continuation.handoff, ...messages.filter((message, index) => message.role === "custom" || (start >= 0 && index >= start))];
@@ -15,7 +15,7 @@ import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, SettledTurnBack
15
15
  import { projectRecentTransitionsWithLimit } from "./history.js";
16
16
  import { isObject, presentationJson, sameJson } from "./json.js";
17
17
  import { StateFlowDiagnosticWriter, stateFlowLogPath } from "./logging.js";
18
- import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.js";
18
+ import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, RESPONSE_PATCH_GUIDELINE, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.js";
19
19
  import { OwnedOperationSlot, RenewableLifetime } from "./operation.js";
20
20
  import { readProjectedState, readStatePath } from "./query.js";
21
21
  import { selectedBoundaryFailure, selectRetainedCheckpoint, waitForRecovery } from "./recovery.js";
@@ -728,6 +728,7 @@ export default function stateFlowExtension(pi, options = {}) {
728
728
  description: "The sole State Flow semantic mutation protocol. Supply one or more global, cwd, or session patches; all supplied scopes commit atomically. This call must be the only State Flow barrier in its assistant response. Deleting an intent also deletes same-scope working/lazy keys its {\"$ref\"} values own unless another intent refs them. Name keys in ASCII ([A-Za-z_$][A-Za-z0-9_$-]*) so read paths and refs resolve; values may use any language.",
729
729
  promptSnippet: "Atomically patch one or more global/cwd/session scopes",
730
730
  promptGuidelines: [
731
+ RESPONSE_PATCH_GUIDELINE,
731
732
  "Use patch_state only for material durable semantic changes; ordinary answers need no finalization call.",
732
733
  "Use patch_state as reconciliation, not append-only notes: place new knowledge at the narrowest valid scope and remove superseded or completed state from touched branches.",
733
734
  "Call patch_state alone in an assistant response; after its acknowledgement, further reasoning, tools, and later patch_state calls remain allowed.",
@@ -811,7 +812,7 @@ export default function stateFlowExtension(pi, options = {}) {
811
812
  }
812
813
  clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
813
814
  updateUi(ctx);
814
- const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints, stage.cascades);
815
+ const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints, stage.cascades, isActive());
815
816
  const acknowledgement = changed
816
817
  ? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
817
818
  : "\nState already current.";
@@ -1011,6 +1012,9 @@ export default function stateFlowExtension(pi, options = {}) {
1011
1012
  }
1012
1013
  /** Local policy, tools and UI change before any canonical wait. */
1013
1014
  function applyInactiveMode(ctx, mode) {
1015
+ // Visibility changes before optional recovery waits; a frozen Active head must not survive Passive.
1016
+ if (snapshot.config.mode !== mode)
1017
+ contextProjection.reset();
1014
1018
  snapshot = deactivateEpisode(snapshot, mode);
1015
1019
  clearRunTransient();
1016
1020
  syncStateFlowTools();
@@ -1344,7 +1348,7 @@ export default function stateFlowExtension(pi, options = {}) {
1344
1348
  const effective = effectiveState;
1345
1349
  const invalidations = acquisition.invalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
1346
1350
  const phase = isActive() ? rehydrationPhase : undefined;
1347
- const view = contextView(effective, acquisition.hints, invalidations, phase);
1351
+ const view = contextView(effective, acquisition.hints, invalidations, phase, isActive());
1348
1352
  if (passiveContinuation) {
1349
1353
  const retained = passiveContinuationMessages(messages, passiveContinuation);
1350
1354
  if (!runtime?.view)
@@ -16,7 +16,7 @@ export function projectDiagnosticContent(content) {
16
16
  }
17
17
  /** Diagnostic JSONL lives beneath the active Pi agent directory, never inside the state repository. */
18
18
  export function stateFlowLogPath(agentDir) {
19
- return join(agentDir, "tmp", "state-flow", "logs.jsonl");
19
+ return join(agentDir, "tmp", "pi-state-flow", "logs.jsonl");
20
20
  }
21
21
  function ensureDiagnosticDirectory(path) {
22
22
  if (dirname(path) !== path)
@@ -1,6 +1,7 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
2
  export type { StateDocument } from "./state.ts";
3
- export declare const PASSIVE_MEMORY_PROTOCOL = "State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory. Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; other IDs are history. Results/context notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); cascaded lists owner paths an intent deletion removed. Direct writes may elide; shared drift stays visible. Latest entries win over earlier state; lazy bodies stay omitted. Notices replace invalidations/rehydration, including []/null.";
3
+ export declare const RESPONSE_PATCH_GUIDELINE = "Never patch response in global, cwd or session, including clearing/deleting it; State Flow captures your final answer automatically in Active mode.";
4
+ export declare const PASSIVE_MEMORY_PROTOCOL = "State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Never patch response in global, cwd or session, including clearing/deleting it; State Flow captures your final answer automatically in Active mode. Passive turns never trigger State Flow continuation or compaction. Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory. Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; other IDs are history. Results/context notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); cascaded lists owner paths an intent deletion removed. Direct writes may elide; shared drift stays visible. Latest entries win over earlier state; lazy bodies stay omitted. Notices replace invalidations/rehydration, including []/null.";
4
5
  /** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
5
6
  export declare function formatPatchStateArguments(args: unknown): string;
6
7
  /** Flatten causes before transport; native tool results need not retain Error.cause or AggregateError.errors. */
@@ -1,6 +1,7 @@
1
1
  const TASK_DRIVEN_HISTORY = "Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory.";
2
2
  const PATCH_RESULT_PROTOCOL = "Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; other IDs are history. Results/context notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); cascaded lists owner paths an intent deletion removed. Direct writes may elide; shared drift stays visible. Latest entries win over earlier state; lazy bodies stay omitted. Notices replace invalidations/rehydration, including []/null.";
3
- export const PASSIVE_MEMORY_PROTOCOL = `State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. ${TASK_DRIVEN_HISTORY} ${PATCH_RESULT_PROTOCOL}`;
3
+ export const RESPONSE_PATCH_GUIDELINE = "Never patch response in global, cwd or session, including clearing/deleting it; State Flow captures your final answer automatically in Active mode.";
4
+ export const PASSIVE_MEMORY_PROTOCOL = `State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. ${RESPONSE_PATCH_GUIDELINE} Passive turns never trigger State Flow continuation or compaction. ${TASK_DRIVEN_HISTORY} ${PATCH_RESULT_PROTOCOL}`;
4
5
  const PATCH_DISPLAY_SECTION_KEYS = new Set(["global", "cwd", "session", "intents", "contract", "working", "artifacts", "response", "lazy"]);
5
6
  /** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
6
7
  export function formatPatchStateArguments(args) {
@@ -88,7 +89,7 @@ ${bootstrapProtocol}STATE:
88
89
  - contract: durable requirements, decisions, rejections, interfaces.
89
90
  - working: temporary context of intents: facts, validation, failures, domain state.
90
91
  - artifacts: source-path routing metadata; descriptions do not imply body acquisition.
91
- - response: previous answer; runtime stores the exact accepted answer at turn_end (empty="").
92
+ - response: read-only prior answer; never patch it in any scope. Auto-stored at turn_end (empty="").
92
93
  - lazy: retrieve explicitly.
93
94
 
94
95
  SCOPES: Use the narrowest scope: session=branch/run continuation by default; cwd=reusable project truth; global=established cross-project/user/environment knowledge.
@@ -26,7 +26,7 @@ export function formatStateFlowSectionLabel(snapshot) {
26
26
  }
27
27
  export const STATE_FLOW_MODES = ["off", "passive", "active"];
28
28
  const STATE_FLOW_MODE_LABELS = { off: "Off", passive: "Passive", active: "Active" };
29
- const STATE_FLOW_SELECTED_MARKERS = { off: "🟡", passive: "🟣", active: "🟢" };
29
+ const STATE_FLOW_SELECTED_MARKERS = { off: "🟣", passive: "🟡", active: "🟢" };
30
30
  function isStateFlowModeAction(value) {
31
31
  return STATE_FLOW_MODES.includes(value);
32
32
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-state-flow",
3
- "version": "0.26.0",
3
+ "version": "0.26.4",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "description": "Incremental scoped state/context/memory compiler for Pi, inspired by SKILL.state",
@@ -59,7 +59,7 @@ This is the source-bound **paragraph-to-owner parity map** for the 8,141-word pr
59
59
  - L43 terminal status and read-only scope inspection → [status and controls](usage.md#status-and-controls), [observability](architecture.md#observability).
60
60
  - L48 system section, context refresh and foreign prompt precedence → [Pi lifecycle](architecture.md#pi-lifecycle), [host context compatibility](compatibility.md#context-tools-and-provider-input).
61
61
  - L50 extension-agnostic core and Telegram controls/receipts → [composition](architecture.md#composition), [observability](architecture.md#observability), [Telegram compatibility](compatibility.md#telegram-adapter).
62
- - L52 tag release authority and version alignment → [release workflow](../.github/workflows/release.yml), [release boundary](../BACKLOG.md#release-boundary). Retain the durable no-token constraint in compact `AGENTS.md` until a permanent developer contract owns it.
62
+ - L52 tag release authority and version alignment → [release workflow](../.github/workflows/release.yml), [release boundary](../AGENTS.md#delivery-discipline). Retain the durable no-token constraint in compact `AGENTS.md` until a permanent developer contract owns it.
63
63
  - L53 opt-in benchmark location and source identity → [benchmark guide](../benchmarks/README.md), [performance validation](performance.md#validation-and-reporting).
64
64
  - L54 provider callback completion witness → [temporal acceptance](temporal-acceptance.md#required-properties-and-witnesses), [integration tests](../tests/integration.test.ts).
65
65
  - L55 awaited fixture handlers and withdrawal witness → [temporal acceptance](temporal-acceptance.md#required-properties-and-witnesses), [extension tests](../tests/extension.test.ts).
@@ -42,7 +42,7 @@ Runtime views select these documented semantic planes when present. Stored scope
42
42
  - `contract` retains durable requirements, decisions, interfaces and rejected approaches.
43
43
  - `working` is the temporary working context of those actions: verified current facts, results and uncertainties.
44
44
  - `artifacts` maps exact source paths to compiled routing metadata.
45
- - `response` is owned only by the session scope and stores the exact latest accepted assistant answer, including the empty string. Global/CWD do not receive newly accepted answers, and stored scopes may omit response entirely. An explicit nonempty Session answer overrides inherited values; an empty or absent response contributes nothing to projection.
45
+ - `response` is owned only by the session scope and stores the exact latest accepted assistant answer, including the empty string. Global/CWD do not receive newly accepted answers, and stored scopes may omit response entirely. An explicit nonempty Session answer overrides inherited values; an empty or absent response contributes nothing to Active projection. Automatic Passive context omits the response plane, including frozen Stop handoffs and state-update receipts. Canonical storage, explicit reads and native answer text are unchanged.
46
46
  - `lazy` is an optional stored object root for ordinary JSON detail. It defaults to `{}` only in internal compatibility views, is omitted from baseline model state and is read explicitly.
47
47
  - Automatic recent-transition projection also removes each whole `patch.lazy`, including deletions. Empty scoped patches and transitions disappear, and an empty projected window is omitted.
48
48
  - Visible hot patches keep their original identities, order and positions. Canonical history and explicit current/historical reads are unchanged.
@@ -61,7 +61,7 @@ Later scopes win. A scope-local `null` deletion removes only that scope's key an
61
61
  - Checkpoint/tail codecs select only known top-level fields on read and write; unknown fields are neither kept in runtime streams nor carried into later writes. Nested data within known planes stays intact.
62
62
  - Retained record identities and positions survive even when filtering leaves an empty patch.
63
63
  - `readTemporalView` selects present known fields at the requested boundary, then overlays scopes without inventing fields.
64
- - Empty and absent responses mean the same for projection; clearing a nonempty response projects as a deletion.
64
+ - Empty and absent responses mean the same for Active projection; clearing a nonempty response projects as a deletion. Passive omits the plane regardless of its value.
65
65
  - Explicit reads of absent documented top-level fields return `null`.
66
66
  - `readTemporalState` and the embedding callback keep default-bearing compatibility views for internal registry consumers. Those defaults never become stored overrides or model context.
67
67
  - Passive reads and Start do not fill checkpoint fields or create revisions for normalization; missing shared pairs initialize as empty objects.
@@ -290,7 +290,7 @@ Which boundary Stop keeps:
290
290
 
291
291
  - A recorded active anchor uses the same conservative selector as active inference. If native compaction removed it, or matching is ambiguous or nonfinite, the available native summary and tool trajectory are kept, without guessing a post-stop boundary or rereading discarded raw entries.
292
292
  - An interrupted run keeps its captured anchor even after Pi becomes idle. If capture is unavailable, Stop preserves all available context.
293
- - Completed idle runs, and markers without an active anchor or preservation flag, keep only post-stop conversation plus foreign custom context.
293
+ - Completed idle runs, and markers without an active anchor or preservation flag, keep only post-stop conversation plus foreign custom context. A native `compactionSummary` timestamped at or after Stop is also a continuation boundary: its entire available suffix remains visible through later tool turns and user refinements. Pre-stop summaries do not reopen completed history; no discarded raw request is reconstructed.
294
294
 
295
295
  The initial system prompt is composed at `before_agent_start`. Stop does not rewrite an already-issued request; the next provider request receives the current owned protocol section, as described below.
296
296
 
@@ -13,7 +13,8 @@ State Flow has three distinct model-facing states. **Active versus passive chang
13
13
  - The final meaningful semantic patch preserves the decisions, outcomes and continuation needed once the completed conversation leaves model projection.
14
14
  - The next iteration starts from accepted state and its new input, not from the previous iteration's completed reasoning.
15
15
  - The native trace stays inspectable: a clean model context does not mean deleting Pi history.
16
- - **Passive:** Both memory tools are available, existing state is projected into context, and the agent may read or patch it as useful.
16
+ - **Passive:** Both memory tools are available, existing state except the `response` plane is projected into context, and the agent may read or patch memory as useful.
17
+ - The last Active answer is not repeated in automatic memory context, Stop handoffs or state updates. Its stored value and explicit `read_state` access remain intact; native/user/tool history is not redacted.
17
18
  - Ordinary conversation continuity remains: there is no active iteration-ending context reset and no mandatory consolidation pressure.
18
19
  - Accepted patches still reach the same canonical store, with the same validation and ownership guarantees.
19
20
  - **Off:** Neither memory tool is exposed to the model, and State Flow injects no protocol, bootstrap/state context or frozen passive handoff. Stored memory and the native conversation trace are not deleted.
@@ -84,7 +85,7 @@ How each lifecycle event behaves:
84
85
  - If native split-turn compaction removed the original request anchor, Stop keeps the available summary and tools instead, without reconstructing discarded raw input.
85
86
  - An unfinished bootstrap keeps all available native context, or the earlier passive boundary it received. Repeated Start/Stop cannot move that boundary past uncompiled conversation.
86
87
  - An interrupted run keeps its captured anchor even after Pi becomes idle, and falls back to all available context when that anchor is unknown.
87
- - Only Stop after a completed idle run keeps just the later conversation plus foreign custom context. Other extensions' custom context survives in every case.
88
+ - Only Stop after a completed idle run keeps just the later conversation plus foreign custom context. A native compaction summary created after Stop keeps its complete available tool suffix and later refinements, even when the original user anchor is no longer present. Earlier summaries do not reopen completed history. Other extensions' custom context survives in every case.
88
89
  - Mid-tool Start and repeated Start/Stop keep the first user event already observed while disabled, so a later Stop does not mistake that busy run for idle.
89
90
  - Reload/resume/tree preserve this projection; new or forked physical sessions do not inherit it.
90
91
  - Active restart uses the retained projection for one bootstrap run. Off keeps this boundary for a later Passive/Active selection but never projects it.
@@ -144,7 +145,7 @@ The canonical store is `state-flow/` beneath the agent directory. Keeping config
144
145
  - Active initializes missing storage when safe.
145
146
  - Passive reads existing memory without publishing; its first explicit patch may initialize absent storage without starting an episode or compaction.
146
147
  - Off exposes neither memory tools nor State Flow context.
147
- - `logging`: Defaults to `false`. When enabled, rejected patch executions, active barrier blocks, preparation failures and accepted-answer reconciliation failures are recorded locally at `tmp/state-flow/logs.jsonl` beneath the agent directory. Asynchronous Git push failures are recorded there even when this setting is off.
148
+ - `logging`: Defaults to `false`. When enabled, rejected patch executions, active barrier blocks, preparation failures and accepted-answer reconciliation failures are recorded locally at `tmp/pi-state-flow/logs.jsonl` beneath the agent directory. Older `tmp/state-flow` logs are left untouched and are not migrated. Asynchronous Git push failures are recorded there even when this setting is off.
148
149
  - `showSuccessfulPatches`: Defaults to `true`. In interactive Pi, `patch_state` rows show pretty-printed JSON arguments once in the call, with blank lines between adjacent memory sections; the result shows the acceptance or no-op acknowledgement, never a second patch. Set it to `false` to hide call arguments and keep the compact acknowledgement. Rejected calls retain their arguments and error acknowledgement; State Flow adds no private validation turn.
149
150
  - `historyLimit`: Defaults to `7` and accepts integers from `0` through `100`. It counts accepted semantic transitions, **not elapsed time or conversation length**, and bounds materialized-history and scope patch-history offsets.
150
151
  - Lowering it on reload/restore/fork folds excess tails forward without losing current state; selected boundaries outside the new window become unavailable.
@@ -208,7 +209,7 @@ When `pi-telegram` is available, its main-menu section shows `State Flow: active
208
209
 
209
210
  **Mode controls:**
210
211
 
211
- - A horizontal radio row presents `Off | Passive | Active`. The selected option uses 🟡, 🟣 or 🟢 respectively; each inactive option uses ⚫️.
212
+ - A horizontal radio row presents `Off | Passive | Active`. The selected option uses 🟣, 🟡 or 🟢 respectively; each inactive option uses ⚫️.
212
213
  - Telegram Active requested during a run waits for settlement; Passive and Off apply immediately.
213
214
  - Ordinary successful controls add no redundant mode receipt; diagnostic outcomes stay visible.
214
215
  - Off exposes neither tool nor State Flow model context.
@@ -121,8 +121,14 @@ export interface ContextView {
121
121
  knowledge_rehydration: { phase: RehydrationPhase } | null;
122
122
  }
123
123
 
124
- export function contextView(state: SemanticState, hints: ArtifactModelHints, invalidations: readonly ArtifactInvalidationNotice[], phase?: RehydrationPhase): ContextView {
125
- return { state: projectStateForModel(state, hints), lazy_navigation: lazyNavigationHint(state),
124
+ function projectContextState(state: SemanticState, hints: ArtifactModelHints, includeResponse: boolean): SemanticState {
125
+ const projected = projectStateForModel(state, hints);
126
+ if (!includeResponse) delete projected.response;
127
+ return projected;
128
+ }
129
+
130
+ export function contextView(state: SemanticState, hints: ArtifactModelHints, invalidations: readonly ArtifactInvalidationNotice[], phase?: RehydrationPhase, includeResponse = true): ContextView {
131
+ return { state: projectContextState(state, hints, includeResponse), lazy_navigation: lazyNavigationHint(state),
126
132
  artifact_invalidations: structuredClone(invalidations), knowledge_rehydration: phase === undefined ? null : { phase } };
127
133
  }
128
134
 
@@ -144,11 +150,11 @@ export class ContextProjection {
144
150
 
145
151
  /** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
146
152
  acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints,
147
- cascades: Partial<Record<StateScope, readonly OwnedPath[]>> = {}) {
148
- const state = projectStateForModel(after, hints);
153
+ cascades: Partial<Record<StateScope, readonly OwnedPath[]>> = {}, includeResponse = true) {
154
+ const state = projectContextState(after, hints, includeResponse);
149
155
  const navigation = lazyNavigationHint(after);
150
156
  const beforeNavigation = this.view?.lazy_navigation ?? lazyNavigationHint(before);
151
- const updates = projectedStateUpdates(this.view?.state ?? projectStateForModel(before, hints), state, patches,
157
+ const updates = projectedStateUpdates(this.view?.state ?? projectContextState(before, hints, includeResponse), state, patches,
152
158
  beforeNavigation, navigation);
153
159
  // Suppress direct writes only when the accepted effective value matches.
154
160
  // Overlap stays conservative except for explicit top-scope replacements:
@@ -303,12 +309,14 @@ function messageText(message: AgentMessage): string {
303
309
  }
304
310
 
305
311
  export function createPassiveContinuation(state: SemanticState, startedAt = Date.now(), activeRunStartedAt?: number, preserveContext = false): PassiveContinuation {
312
+ const visibleState = structuredClone(state);
313
+ delete visibleState.response;
306
314
  return {
307
315
  startedAt,
308
- state: structuredClone(state),
316
+ state: visibleState,
309
317
  ...(activeRunStartedAt === undefined ? {} : { activeRunStartedAt }),
310
318
  ...(preserveContext ? { preserveContext: true as const } : {}),
311
- handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state, continuation: preserveContext
319
+ handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state: visibleState, continuation: preserveContext
312
320
  ? "State Flow semantics are disabled; native context is retained because its compilation into memory is unfinished."
313
321
  : "State Flow semantics are disabled; this handoff replaces completed history while retaining the active and post-stop trajectory." })}`),
314
322
  };
@@ -321,7 +329,8 @@ export function passiveContinuationMessages(messages: AgentMessage[], continuati
321
329
  const trajectory = currentRunTrajectory(messages, "", continuation.activeRunStartedAt);
322
330
  return [continuation.handoff, ...trajectory.messages];
323
331
  }
324
- const start = messages.findIndex((message) => message.role === "user"
332
+ // A post-stop native summary replaces its compacted user anchor, including the retained tool suffix.
333
+ const start = messages.findIndex((message) => (message.role === "user" || message.role === "compactionSummary")
325
334
  && typeof message.timestamp === "number"
326
335
  && message.timestamp >= continuation.startedAt);
327
336
  return [continuation.handoff, ...messages.filter((message, index) =>
@@ -22,7 +22,7 @@ import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, SettledTurnBack
22
22
  import { projectRecentTransitionsWithLimit } from "./history.ts";
23
23
  import { isObject, presentationJson, sameJson } from "./json.ts";
24
24
  import { StateFlowDiagnosticWriter, stateFlowLogPath, type DiagnosticExtras, type StateFlowDiagnosticCategory } from "./logging.ts";
25
- import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.ts";
25
+ import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, RESPONSE_PATCH_GUIDELINE, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.ts";
26
26
  import { OwnedOperationSlot, RenewableLifetime, type OwnedOperation } from "./operation.ts";
27
27
  import { readProjectedState, readStatePath } from "./query.ts";
28
28
  import { selectedBoundaryFailure, selectRetainedCheckpoint, waitForRecovery } from "./recovery.ts";
@@ -746,6 +746,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
746
746
  description: "The sole State Flow semantic mutation protocol. Supply one or more global, cwd, or session patches; all supplied scopes commit atomically. This call must be the only State Flow barrier in its assistant response. Deleting an intent also deletes same-scope working/lazy keys its {\"$ref\"} values own unless another intent refs them. Name keys in ASCII ([A-Za-z_$][A-Za-z0-9_$-]*) so read paths and refs resolve; values may use any language.",
747
747
  promptSnippet: "Atomically patch one or more global/cwd/session scopes",
748
748
  promptGuidelines: [
749
+ RESPONSE_PATCH_GUIDELINE,
749
750
  "Use patch_state only for material durable semantic changes; ordinary answers need no finalization call.",
750
751
  "Use patch_state as reconciliation, not append-only notes: place new knowledge at the narrowest valid scope and remove superseded or completed state from touched branches.",
751
752
  "Call patch_state alone in an assistant response; after its acknowledgement, further reasoning, tools, and later patch_state calls remain allowed.",
@@ -820,7 +821,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
820
821
  }
821
822
  clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
822
823
  updateUi(ctx);
823
- const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints, stage.cascades);
824
+ const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints, stage.cascades, isActive());
824
825
  const acknowledgement = changed
825
826
  ? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
826
827
  : "\nState already current.";
@@ -1006,6 +1007,8 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
1006
1007
 
1007
1008
  /** Local policy, tools and UI change before any canonical wait. */
1008
1009
  function applyInactiveMode(ctx: ExtensionContext, mode: InactiveMode): void {
1010
+ // Visibility changes before optional recovery waits; a frozen Active head must not survive Passive.
1011
+ if (snapshot.config.mode !== mode) contextProjection.reset();
1009
1012
  snapshot = deactivateEpisode(snapshot, mode);
1010
1013
  clearRunTransient();
1011
1014
  syncStateFlowTools();
@@ -1324,7 +1327,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
1324
1327
  const effective = effectiveState;
1325
1328
  const invalidations = acquisition.invalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
1326
1329
  const phase = isActive() ? rehydrationPhase : undefined;
1327
- const view = contextView(effective, acquisition.hints, invalidations, phase);
1330
+ const view = contextView(effective, acquisition.hints, invalidations, phase, isActive());
1328
1331
  if (passiveContinuation) {
1329
1332
  const retained = passiveContinuationMessages(messages, passiveContinuation);
1330
1333
  if (!runtime?.view) return { messages: retained };
@@ -38,7 +38,7 @@ export function projectDiagnosticContent(content: unknown): StateFlowDiagnosticB
38
38
 
39
39
  /** Diagnostic JSONL lives beneath the active Pi agent directory, never inside the state repository. */
40
40
  export function stateFlowLogPath(agentDir: string): string {
41
- return join(agentDir, "tmp", "state-flow", "logs.jsonl");
41
+ return join(agentDir, "tmp", "pi-state-flow", "logs.jsonl");
42
42
  }
43
43
 
44
44
  function ensureDiagnosticDirectory(path: string): void {
@@ -6,7 +6,9 @@ const TASK_DRIVEN_HISTORY = "Missing paths/hints do not require history search.
6
6
 
7
7
  const PATCH_RESULT_PROTOCOL = "Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; other IDs are history. Results/context notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); cascaded lists owner paths an intent deletion removed. Direct writes may elide; shared drift stays visible. Latest entries win over earlier state; lazy bodies stay omitted. Notices replace invalidations/rehydration, including []/null.";
8
8
 
9
- export const PASSIVE_MEMORY_PROTOCOL = `State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. ${TASK_DRIVEN_HISTORY} ${PATCH_RESULT_PROTOCOL}`;
9
+ export const RESPONSE_PATCH_GUIDELINE = "Never patch response in global, cwd or session, including clearing/deleting it; State Flow captures your final answer automatically in Active mode.";
10
+
11
+ export const PASSIVE_MEMORY_PROTOCOL = `State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. ${RESPONSE_PATCH_GUIDELINE} Passive turns never trigger State Flow continuation or compaction. ${TASK_DRIVEN_HISTORY} ${PATCH_RESULT_PROTOCOL}`;
10
12
 
11
13
  const PATCH_DISPLAY_SECTION_KEYS = new Set(["global", "cwd", "session", "intents", "contract", "working", "artifacts", "response", "lazy"]);
12
14
 
@@ -92,7 +94,7 @@ ${bootstrapProtocol}STATE:
92
94
  - contract: durable requirements, decisions, rejections, interfaces.
93
95
  - working: temporary context of intents: facts, validation, failures, domain state.
94
96
  - artifacts: source-path routing metadata; descriptions do not imply body acquisition.
95
- - response: previous answer; runtime stores the exact accepted answer at turn_end (empty="").
97
+ - response: read-only prior answer; never patch it in any scope. Auto-stored at turn_end (empty="").
96
98
  - lazy: retrieve explicitly.
97
99
 
98
100
  SCOPES: Use the narrowest scope: session=branch/run continuation by default; cwd=reusable project truth; global=established cross-project/user/environment knowledge.
@@ -139,7 +139,7 @@ export function formatStateFlowSectionLabel(snapshot: StateFlowTelegramSnapshot)
139
139
 
140
140
  export const STATE_FLOW_MODES = ["off", "passive", "active"] as const satisfies readonly StateFlowMode[];
141
141
  const STATE_FLOW_MODE_LABELS: Record<StateFlowMode, string> = { off: "Off", passive: "Passive", active: "Active" };
142
- const STATE_FLOW_SELECTED_MARKERS: Record<StateFlowMode, string> = { off: "🟡", passive: "🟣", active: "🟢" };
142
+ const STATE_FLOW_SELECTED_MARKERS: Record<StateFlowMode, string> = { off: "🟣", passive: "🟡", active: "🟢" };
143
143
 
144
144
  function isStateFlowModeAction(value: string): value is StateFlowMode {
145
145
  return (STATE_FLOW_MODES as readonly string[]).includes(value);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-state-flow",
3
- "version": "0.26.0",
3
+ "version": "0.26.4",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "description": "Incremental scoped state/context/memory compiler for Pi, inspired by SKILL.state",