@llblab/pi-kit 0.29.1 → 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.
- package/BACKLOG.md +2 -1
- package/CHANGELOG.md +5 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +12 -0
- package/node_modules/@llblab/pi-state-flow/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +17 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +7 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -3
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +17 -8
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +6 -3
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +4 -2
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +6 -5
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +18 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +12 -0
- package/node_modules/@llblab/pi-telegram/README.md +5 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +7 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +72 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +29 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +12 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +25 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +118 -44
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +48 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +22 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +6 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +45 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +11 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +34 -18
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +14 -8
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +1 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +4 -2
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +5 -0
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +80 -11
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +28 -11
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +21 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +26 -3
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +91 -36
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +47 -5
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +26 -1
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +46 -7
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +43 -21
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
package/BACKLOG.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
The 0.29.
|
|
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.
|
|
7
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.
|
|
8
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.
|
|
9
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.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
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
|
+
|
|
5
10
|
## 0.29.1: State Flow Temporary Paths and Mode Colors
|
|
6
11
|
|
|
7
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.
|
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.
|
|
21
|
-
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.52.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -2,6 +2,18 @@
|
|
|
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
|
+
|
|
5
17
|
## 0.26.1: Consistent Temporary Paths and Mode Colors
|
|
6
18
|
|
|
7
19
|
- `Telegram mode colors`: The selected mode uses 🟣 Off, 🟡 Passive or 🟢 Active; unselected options remain ⚫️. Mode behavior and callbacks are unchanged.
|
|
@@ -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
|
-
|
|
133
|
-
|
|
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 =
|
|
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 ??
|
|
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:
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
|
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
|
|
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:
|
|
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.
|
|
@@ -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
|
|
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.
|
|
@@ -121,8 +121,14 @@ export interface ContextView {
|
|
|
121
121
|
knowledge_rehydration: { phase: RehydrationPhase } | null;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
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 =
|
|
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 ??
|
|
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:
|
|
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
|
-
|
|
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 };
|
|
@@ -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
|
|
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:
|
|
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.
|
|
@@ -84,21 +84,22 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
84
84
|
|
|
85
85
|
- Admission is journal-first: validate and persist the complete `getUpdates` response before one monotonic offset commit, then signal an independent worker without awaiting semantic execution. Missing cursor with a non-empty journal, malformed/foreign authority, or capacity exhaustion fails closed. “Durable” means process-crash recovery after atomic rename, not unflushed host/kernel/filesystem/device/power-loss survival.
|
|
86
86
|
- Storage cutovers must reconcile actual consumer locations before correcting path adapters. Never normalize a relative historical reference into new authority or treat equal reference strings / empty canonical storage as source completeness. Exact-path preflight is lexical only; physical identity, historical coverage, writer closure and migration remain separate proofs.
|
|
87
|
-
- Workspace mutations acquire cross-process admission before their shared process-local gate and hold it through asynchronous API work and durable settlement. Topic lifecycle, complete unbound/reroute target handling, manual disconnect, and session-restart cleanup use profile-wide scope; either retained retirement-fence phase rejects them before state access. Cleanup
|
|
87
|
+
- Workspace mutations acquire cross-process admission before their shared process-local gate and hold it through asynchronous API work and durable settlement. Topic lifecycle, complete unbound/reroute target handling, manual disconnect, and session-restart cleanup use profile-wide scope; either retained retirement-fence phase rejects them before state access. Cleanup admission spans intent publication, target mutation, persistence, and successful transport release. Explicit manual disconnect may stop only its captured local transport after cleanup admission/effects fail; it must not bypass Thread mutation fences, release a replacement connection, claim unconfirmed deletion, or retry an uncertain stop. Session-restart cleanup retains its admission-required behavior. Detached reconciliation that mutates Thread state must reacquire fresh profile admission through the same gate; it cannot inherit a caller lease that ended before its timer runs. A live operation ID has one process-local caller: concurrent reuse is rejected before lease acquisition, while retry after the caller exits may resume exact durable authority.
|
|
88
88
|
- Workspace retirement is capacity-pressure-only. Elapsed time and heartbeat silence never trigger deletion; only complete `A`–`Z` exhaustion may propose the oldest continuously proven inactive, fully unprotected binding. Exact deletion, durable retirement, and fence completion precede slot reuse. Authorized demand-driven rotation retries one failed fresh allocation after retirement; restore-only follower startup never evicts. Release ordinary registration/provisioning leases before acquiring the destructive fence, retain the shared mutation gate across retirement, and validate the exact permit immediately before one non-retried deletion. Persist an exact method/target-matched rejection before withdrawing its intent; release that fence only after durable withdrawal, retaining the binding. A later attempt requires fresh operation authority. Protection reads must never repair, quarantine, or reset journals. Non-destructive owner detachment must atomically retain one exact Workspace binding and its letter while removing only its uniquely matched owner record and stamping first inactivity; it never manufactures Thread-deletion evidence or clears accepted work. During authenticated follower registration, the exact Workspace claim owns the letter: a mismatched retained target record is repaired to that claim before binding commit, while any unrelated binding that owns the stale letter remains untouched. Retained prune observations are bounded, non-routing and registration/profile/epoch/runtime-fenced. One unfinished preservation operation retains its admission identity across fresh-PID-proof retries; it can never become deletion authority. Leader quit requires completed delivery/polling/worker teardown under the captured session generation, profile and epoch; reload/new/resume/fork never establish inactivity. Unknown deletion outcomes retain their fence; only confirmed durable completion permits reuse.
|
|
89
|
-
- Foreign forwarding settles as `accepted`, `retryable`, or `terminal-rejected`. Only an authenticated acknowledgement carrying the expected `deliveryId` and `sourceUpdateId` releases leader journal authority. Negative, missing, stale, mismatched, or capacity-failed settlement remains durable; callback error answers are side effects only.
|
|
89
|
+
- Foreign forwarding settles as `accepted`, `retryable`, or `terminal-rejected`. Only an authenticated acknowledgement carrying the expected `deliveryId` and `sourceUpdateId` releases leader journal authority. Negative, missing, stale, mismatched, or capacity-failed settlement remains durable; callback error answers are side effects only. Exception: the operator-approved fixed chooser deadline may discard its exact unclaimed donor pending source without a body archive or delivery-success claim. Accepted recipient queue/work and bound Threads are untouched; old donor controls and late reports cannot replay it. The lifetime and temporary cleanup contract lives in `docs/architecture.md`.
|
|
90
90
|
- A forwarding delivery id is stable across registration replacement and derives from envelope kind, source `update_id`, and stable recipient binding. Runtime instance and registration generation remain separate attempt fences. Persisted message ownership carries the stable binding so replay can rebind only to its current authenticated registration.
|
|
91
91
|
- A queued receipt persists its acquiring runtime instance, OS pid/process-birth identity, session generation, acquisition id, and acquisition time. Only exact authority may settle or discard it. Cached presence is not current execution proof: prepared custody must revalidate the exact queued owner/group without recovery and refuse offers or uncertain reads. Completion requires an exact removal acknowledgement, never merely `!ready`; a retained acknowledgement permits local cleanup only, not replay. Same-process session replacement may reconstruct the claim and the original process may settle after transport ownership moves; a foreign process may neither replay nor settle it through generic removal or a copied acquisition id.
|
|
92
92
|
- Startup and elapsed time are not owner-death proof; queued authority has no time lease. Dead-owner cleanup groups the complete receipt and transactionally rechecks pid liveness plus process-birth identity: only an absent PID or mismatched stable Linux/macOS birth proof discards all session-owned sources without replay; a matching proof is `alive`, while Windows or inaccessible birth metadata is `unverifiable`, and both non-dead outcomes keep authority queued. Under actual `A`–`Z` allocation pressure, retirement may invoke that journal-owned CAS only for a current inactive binding after complete strict source inspection, no local work/live owner/delivery authority, whole unoffered receipt groups, and a preflight proving every grouped owner dead. It must then recapture all protection before preparing deletion; partial progress never grants deletion authority. The live-transfer contract is authenticated offer → exact-generation bounded payload staging → recipient CAS acceptance → exact receipt-and-owner ACK → donor removal → recipient readiness. The offer freezes donor settlement/recovery; controls rebuild local closures; negative/mismatched pre-acceptance ACK cancels only an unaccepted offer and retains donor work; a lost post-acceptance ACK cannot cancel recipient authority and leaves donor memory frozen for explicit reconciliation.
|
|
93
93
|
- Execution failures persist bounded diagnostics and attempt state as `retry-wait`, except that an exact Telegram HTTP 400 stale/deleted-thread API failure with a proven `{chatId, threadId}` terminally settles the currently executing source after best-effort shared binding invalidation. Automatic retry continues indefinitely with exponential `1s → 2s → 4s → 8s → 16s → 32s → 60s` delay capped at 60 seconds; later independent updates continue draining, durable authority is never silently discarded, and legacy `failed` entries resume automatically at startup. Snapshot-plus-segment journals compact only after 256 unapplied revisions or 4 MiB; snapshot-first cleanup tolerates redundant segments, and empty authority may atomically rebind bot/profile identity. Missing snapshots left by the retired broad temp cleanup rebuild only from a complete provably empty segment chain, while revisionless snapshots may recover from a validated later segment predecessor; otherwise the transaction-locked reset deletes damaged segments before publishing a fresh journal, with accepted input loss and informational evidence, not a quarantine copy. Unsupported versions remain untouched; strict protection reads never reset. Startup best-effort removes obsolete current-runtime root/session `recovery/` folders without touching pre-0.52.0 storage.
|
|
94
94
|
- Business connection chats are a separate namespace even when their chat/message IDs match bot-chat IDs. Default DM routing must never infer private-queue deletion intent from `deleted_business_messages`; raw companion handlers remain separate owners.
|
|
95
|
+
- Clock-bearing chooser sources survive cold spending until their fixed deadline; new-world forgetting preserves their positively unbound temporary frame without restoring routing intent. Expiry is a donor discard, not recipient cancellation or source execution completion. Confirmed expiry releases worker custody before UI/cleanup awaits; metadata retries retain no prompt bodies and cannot repeat an issued delete.
|
|
95
96
|
- An unresolved reaction delays only the exact governed queue item identified by chat/message sources, not unrelated queue work. An explicit immutable `preApprovalExcluded: true` cannot govern accepted work, even after re-pairing; missing or false exclusion evidence must not bypass the dependency guard. Prepared v3 drain may dispose of excluded pending input only through journal-owned `removeExcluded`, never generic raw completion; mixed requests containing non-excluded input must fail atomically. Queue receipt publication follows in-memory append and precedes dispatch request; receipt-bearing turns remain queued until every exact source commits.
|
|
96
97
|
- The detailed implementation and release gates live in [`docs/architecture.md`](./docs/architecture.md), [`docs/multi-instance-bus.md`](./docs/multi-instance-bus.md), and [`BACKLOG.md`](./BACKLOG.md).
|
|
97
98
|
|
|
98
99
|
### 4.4 Queue, Delivery, And User Surfaces
|
|
99
100
|
|
|
100
|
-
- Queue lane/kind admission is explicit. Dispatch waits for active-turn, pending-dispatch, control, compaction, `ctx.isIdle()`, and Pi pending-message guards; a dispatched prompt stays queued until `agent_start` consumes it. The terminal `+N` suffix is a yellow count of executable prompts still waiting, excludes the dispatched head immediately, and never counts current agent work from any source. Each prompt is one object with one active lane and no reserved return slot. Normal and Priority are separate FIFO lanes: crossing lanes removes it from the source and appends it at the destination tail, while Keep/Skip and same-category emoji changes preserve lane position.
|
|
101
|
-
- `/stop`, `/abort`, `/next`, and `/continue` respectively reset+abort, abort while preserving queue, force the next turn, and enqueue a control-lane continuation. Abort-history folding applies only to Telegram-owned active turns. A busy `/next` marks only its exact active Telegram turn: terminal abort settlement attempts one explicit abort notice before the exact selected queued prompt receives its dispatch notice. Notice failure is diagnostic and cannot block dispatch; a later `/abort` or `/stop` cancels both pending transition notices before taking ownership. Target command adapters must forward these transition ports. A successful dispatch notice keeps the queued prompt's one reply-header claim across agent start, so later messages in that turn never repeat it.
|
|
101
|
+
- Queue lane/kind admission is explicit. Dispatch waits for active-turn, pending-dispatch, control, compaction, `ctx.isIdle()`, and Pi pending-message guards; a dispatched prompt stays queued until `agent_start` consumes it. The terminal `+N` suffix is a yellow count of executable prompts still waiting, excludes the dispatched head immediately, and never counts current agent work from any source. Each prompt is one object with one active lane and no reserved return slot. Normal and Priority are separate FIFO lanes: crossing lanes removes it from the source and appends it at the destination tail, while Keep/Skip and same-category emoji changes preserve lane position. For ordinary Normal/Priority prompts, complete reaction sets independently derive Priority from recognized positive emoji and Skip from recognized negative emoji; both may coexist, suppressed turns retain durable receipts while waiting, and Skip settles them only when the prompt reaches dispatch before dropping it without inference. Suppressed turns remain visible at a struck-through physical ordinal without contributing to executable queue counters, while graceful session shutdown discards all remaining queue authority before clearing memory.
|
|
102
|
+
- `/stop`, `/abort`, `/next`, and `/continue` respectively reset+abort, abort while preserving queue, force the next turn, and enqueue a control-lane continuation. A negative reaction to an original standalone `/continue` settles and removes only its exact waiting source-addressed continuation; positive reactions do not change its control lane. Protect the Pi-owned pending head and already-started work, preserve other queue positions, and never address synthetic model continuations by their reply anchor. Settlement failure retains the continuation. Abort-history folding applies only to Telegram-owned active turns. A busy `/next` marks only its exact active Telegram turn: terminal abort settlement attempts one explicit abort notice before the exact selected queued prompt receives its dispatch notice. Notice failure is diagnostic and cannot block dispatch; a later `/abort` or `/stop` cancels both pending transition notices before taking ownership. Target command adapters must forward these transition ports. A successful dispatch notice keeps the queued prompt's one reply-header claim across agent start, so later messages in that turn never repeat it.
|
|
102
103
|
- A model selection from an authorized Telegram target may stop and continue any interruptible active agent run in that same Pi session, including local/TUI work. Queue the synthetic control-lane continuation before aborting; when no Telegram prompt owns the run, bind continuation and reply ownership to the exact model-menu chat/Thread/message. Delay abort until every active tool execution settles, and clear both selection and fallback target on cancellation, agent start, settlement, or session replacement.
|
|
103
104
|
- Telegram extension side effects must not hold Pi's core lifecycle hostage after semantic completion. Preserve ordering in extension-owned background work, record failures, and fence target/profile/transport/session authority.
|
|
104
105
|
- Complete assistant/guest model answers use Telegram-native Rich Markdown. Harness-owned menus, status, diagnostics, thinking, and tool evidence remain explicit HTML/plain or their documented native surface. Before Telegram preview or final delivery, strip every assistant-authored HTML comment regardless of Markdown position while keeping action activation top-level-only; a comment-only result sends no text message. Preserve literal code outside comments and structurally safe chunking; never split invalid markup.
|
|
@@ -136,7 +137,7 @@ The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md
|
|
|
136
137
|
- Keep interfaces consistent with their owning exported contract. Use local structural `*Like`/view types only for deliberate narrow projections, not duplicate source-of-truth models.
|
|
137
138
|
- Remove dead code immediately. Reachability from composition roots, public exports, tests, registered surfaces, and documented APIs—not recent usefulness—determines whether code is live. `node scripts/audit-exports.mjs` provides a read-only TypeScript-symbol inventory including aliases, JS consumers and public `api/` reexports (including namespaces); `namespaceEscapes` flags whole-module alias usage. No-external-reference candidates are not deletion proof. Review generated text, dynamic namespace access and public declaration reachability before removing or hiding a symbol.
|
|
138
139
|
- Treat every meaningful `lib/extension.ts` edit as a composition-pressure check, but keep one-off live adapter wiring there when extraction would only hide cross-domain state.
|
|
139
|
-
- Follow [`docs/ui-style.md`](./docs/ui-style.md) for interface copy, emoji, buttons, menus, and dialogs. Update the registry before assigning a new UI emoji meaning. Standalone notices use one fully bold emoji-led sentence with a terminal period; menu or chooser headings use the same hierarchy with a terminal colon. Material names may add nested italic emphasis without breaking the outer bold span.
|
|
140
|
+
- Follow [`docs/ui-style.md`](./docs/ui-style.md) for interface copy, emoji, buttons, menus, and dialogs. Update the registry before assigning a new UI emoji meaning. Standalone notices use one fully bold emoji-led sentence with a terminal period; menu or chooser headings use the same hierarchy with a terminal colon. Material names may add nested italic emphasis without breaking the outer bold span. Typed callback toasts preserve equivalent emoji-led plain text without a terminal sentence period; question/exclamation marks and ellipses remain. In-chat notices and explicit raw API payloads keep their punctuation.
|
|
140
141
|
- Markdown lists never contain blank lines between adjacent items; list items are not paragraphs. Use blank lines only between paragraphs or independently separated blocks. Markdown tables use compact source formatting with `---` separator cells and one surrounding space per cell. Preserve vendored references unchanged.
|
|
141
142
|
- Treat Windows filesystem, named-pipe, lock, heartbeat, and atomic-rename reports as high-signal evidence; reduce them to regressions or explicit platform caveats.
|
|
142
143
|
- Route significant runtime failures through the redacted recent-event recorder. Keep the compact TUI status at generic `error`; details belong in diagnostics.
|
|
@@ -2,4 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
_This file owns unresolved project work only. Completed behavior belongs in `CHANGELOG.md`; durable contracts belong in `AGENTS.md` and `/docs`._
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Post-Release Transport Recovery Acceptance
|
|
6
|
+
|
|
7
|
+
- Release boundary: The operator authorized the hotfix on native regression and CI evidence; real Telegram smoke/fault acceptance remains open below, not a claimed completed check. Installing/reloading Pi or injecting live faults needs separate authorization.
|
|
8
|
+
- CI boundary: Native Linux/macOS/Windows checks exercise capped poll/admission backoff, preserved persistent-conflict stand-down and generation-fenced manual disconnect. Existing skipped strict journal witnesses remain a separate Windows boundary; green CI does not replace operator acceptance.
|
|
9
|
+
- [ ] Obtain operator acceptance for ordinary disconnect, unconfirmed-cleanup reporting and outage recovery without manual reconnect on a disposable setup. Local fake-transport/storage regressions do not certify real Telegram or platform fault behavior; installation/reload and live fault injection require separate authorization.
|
|
10
|
+
- Acceptance: Captured manual stop never affects a replacement connection or bypasses Thread deletion fences, uncertain stop is not retried, and accepted local work is preserved. Session-restart cleanup remains admission-required.
|
|
11
|
+
|
|
12
|
+
## Windows Reliability
|
|
13
|
+
|
|
14
|
+
- [ ] Strengthen native Windows reliability beyond the 0.52.0 ordinary-queue fix. Audit filesystem paths, named pipes, locking, owner fencing, heartbeat and atomic publication; cover interrupted writes, damaged state, ownership changes and lost-ACK retries with focused regressions.
|
|
15
|
+
- [ ] Investigate Windows-compatible strict journal evidence for Restore and cleanup without weakening source identity, scoped ACK or fail-closed protections. Inventory witnesses currently skipped on Windows and enable them only when equivalent native evidence exists.
|
|
16
|
+
- Acceptance: Local synthetic tests distinguish ordinary delivery from strict proofs; native Windows CI exercises supported paths and refusal boundaries. Linux emulation or a green matrix with skipped Restore witnesses does not establish Windows Restore acceptance.
|
|
17
|
+
|
|
18
|
+
## Consistent Callback Notification Copy Across Other Extensions
|
|
19
|
+
|
|
20
|
+
- [ ] Inventory Telegram button callback acknowledgements across all local extensions, including companion callbacks. Standardize the transient non-interactive notification bubbles (callback answers/toasts) to concise, consistent text with no trailing period.
|
|
21
|
+
- [ ] Distinguish transient callback bubbles from in-chat notices, chooser headings and blocking alerts; reconcile the owning UI-copy contracts before implementation rather than stripping punctuation from every message.
|
|
22
|
+
- Acceptance: All participating extensions follow the same callback-notification format, with focused copy checks for success, no-op and rejection outcomes; in-chat notice formatting and callback behavior remain unchanged.
|
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.52.2: Polling recovery, safe disconnect and Windows downloads
|
|
8
|
+
|
|
9
|
+
- `Polling recovery`: Non-conflict poll/admission failures back off from 1 to 30 seconds and keep retrying, allowing recovery after prolonged outages without manual reconnect. Backoff resets only after durable admission succeeds; persistent competing-client conflicts retain their existing terminal stand-down.
|
|
10
|
+
- `Disconnect safety`: Failed or unavailable Thread cleanup still attempts one captured local transport stop without bypassing Workspace fences. Reconnect revokes stale cleanup/stop continuations; delayed teardown cannot release replacement ownership. Unconfirmed cleanup is reported without claiming it was skipped, while uncertain stop/release outcomes remain incomplete and are never blindly retried ([#310](https://github.com/llblab/pi-telegram/pull/310)).
|
|
11
|
+
- `Windows downloads`: Publishing a complete attachment retries transient Windows `EPERM`/`EACCES` sharing failures for at most six rename attempts, without replaying HTTP. Cancellation stops the bounded wait; permanent/non-sharing failures still propagate and remove the private partial file. Concurrent downloads retain deterministic names and complete-file publication.
|
|
12
|
+
|
|
13
|
+
## 0.52.1: Finite chooser lifetime, continue cancellation and callback copy
|
|
14
|
+
|
|
15
|
+
- `Chooser expiry`: Fixed 60-minute clocks end unselected/unacknowledged donor routing without archives or replay. Fully resolved disposable tabs use fenced one-shot cleanup; accepted queues, independent work and restored Threads survive. Confirmed expiry clears worker custody even if cleanup fails; metadata retries use body-free proof and never repeat an issued delete. Cold startup preserves deadlines, and queue admission retains selected lifetime metadata.
|
|
16
|
+
- `Continue cancellation`: Negative reactions to an original standalone `/continue` now settle and remove its exact waiting continuation, even while other work runs. Other prompts, the Pi-owned dispatched head, active work and synthetic model continuations remain untouched. Positive reactions preserve the control lane; failed receipt settlement keeps the item waiting, and duplicate reactions cannot cancel twice.
|
|
17
|
+
- `Callback copy`: Shared typed callback toasts omit a terminal sentence period on direct and follower delivery. Questions, exclamations, ellipses, in-chat notices and explicit raw API payloads keep their punctuation.
|
|
18
|
+
|
|
7
19
|
## 0.52.0: Workspace Restore, routing lifetime and resilient runtime storage
|
|
8
20
|
|
|
9
21
|
- `Restore foundation`: One snapshot owns binding, slot, targets and one-shot grants. Publication and late creation receipts share a transaction; unnamed owners round-trip. Unknown or contradictory creation evidence blocks new Restore; absence and expiry never prove availability. Loading validates receipts before replacing working state, preserving disk, memory and source protection on conflict. Recovered targets survive expiry; conflicting creation stays blocked.
|