@llblab/pi-kit 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -7
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +8 -9
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +26 -0
- package/node_modules/@llblab/pi-state-flow/README.md +1 -3
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/index.js +20 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +78 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +110 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +334 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +49 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +67 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +53 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +23 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +109 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.d.ts +111 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +189 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +125 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +102 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +507 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +8 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +27 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +22 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1263 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +72 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +565 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +22 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +12 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +109 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +25 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +24 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +36 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +98 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +15 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +42 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +13 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +133 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +7 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +59 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +69 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +335 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +27 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +35 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +8 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +27 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +36 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +38 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +142 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +529 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +44 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +25 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +131 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +255 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +55 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +31 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +38 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +46 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +217 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +98 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +231 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +203 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +25 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +204 -0
- package/node_modules/@llblab/pi-state-flow/dist/package.json +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/pi-state-flow/index.js +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +138 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +11 -5
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +5 -1
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +3 -3
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +6 -4
- package/node_modules/@llblab/pi-state-flow/index.ts +1 -0
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +17 -1
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -1
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +88 -96
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +64 -12
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +32 -188
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +84 -48
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +40 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +7 -30
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +48 -50
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +41 -97
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +17 -12
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/package.json +23 -6
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +3 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +5 -3
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +0 -6
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +22 -0
- package/node_modules/@llblab/pi-telegram/README.md +6 -3
- package/node_modules/@llblab/pi-telegram/dist/api/activity.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/activity.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/commands.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/commands.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/delivery.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/delivery.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/inbound.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/inbound.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/keyboard.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/keyboard.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/outbound.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/outbound.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/sections.d.ts +7 -0
- package/node_modules/@llblab/pi-telegram/dist/api/sections.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/status.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/status.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/updates.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/updates.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/voice.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/api/voice.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/index.d.ts +6 -0
- package/node_modules/@llblab/pi-telegram/dist/index.js +6 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +52 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +596 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +220 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +574 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/agent-messages.d.ts +28 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/agent-messages.js +86 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +250 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +936 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +18 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +254 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +464 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +1689 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +292 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +2242 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +64 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +140 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +518 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +2055 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +170 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +611 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +70 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +744 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +541 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +1155 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +252 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/config.js +889 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +163 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +542 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.d.ts +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +1604 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-app-worker.mjs +104 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +212 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +1006 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +91 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +502 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +665 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +3701 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +23 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +37 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +157 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +395 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +164 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +1208 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +50 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +274 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +181 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/media.js +602 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +217 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +663 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +37 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +408 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +115 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +595 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +32 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +112 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +29 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +82 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +171 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +323 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +127 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/model.js +409 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +241 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +639 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.d.ts +69 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +248 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +42 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +678 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-voice.d.ts +55 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-voice.js +152 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +185 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +516 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/ownership.d.ts +77 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/ownership.js +174 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +40 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +94 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +72 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +121 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +351 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +1196 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +192 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +614 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +24 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +118 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.d.ts +57 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +167 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +766 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +1965 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +86 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +285 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/rendering.d.ts +20 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/rendering.js +983 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +214 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +737 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +207 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2282 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +172 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +402 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +165 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +327 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/setup.d.ts +82 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/setup.js +150 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.d.ts +8 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +442 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/status.js +1000 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +179 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +782 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/target.d.ts +20 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/target.js +27 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +541 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +1159 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.d.ts +89 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.js +317 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +288 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +560 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +46 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +257 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +46 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +78 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +239 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +644 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +621 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +3679 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/time-injection.d.ts +15 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/time-injection.js +56 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +109 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +500 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +1290 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +3634 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/voice.d.ts +117 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/voice.js +174 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +264 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +1136 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +219 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +587 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.d.ts +30 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +54 -0
- package/node_modules/@llblab/pi-telegram/dist/package.json +126 -0
- package/node_modules/@llblab/pi-telegram/dist/pi-telegram/index.js +1 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +107 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/references/capability-adapters.md +27 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/references/layout-and-state.md +37 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/generative-apps/SKILL.md +115 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/show-me/SKILL.md +166 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/show-me/references/telegram-surfaces.md +43 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/SKILL.md +129 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/configuration.md +15 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/delivery-and-threads.md +27 -0
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +18 -0
- package/node_modules/@llblab/pi-telegram/docs/README.md +2 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +4 -2
- package/node_modules/@llblab/pi-telegram/docs/compact-matrix-literal.md +10 -3
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +15 -13
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +6 -4
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -3
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +89 -6
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +12 -5
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +44 -19
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +4 -1
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/config.ts +14 -7
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +38 -6
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +379 -13
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +20 -6
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +16 -7
- package/node_modules/@llblab/pi-telegram/lib/status.ts +9 -15
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +12 -1
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +96 -18
- package/node_modules/@llblab/pi-telegram/package.json +56 -13
- package/node_modules/@llblab/pi-telegram/scripts/build-dist.mjs +44 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +8 -1
- package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +8 -1
- package/node_modules/@llblab/pi-telegram/skills/generative-apps/SKILL.md +1 -1
- package/node_modules/@llblab/pi-telegram/skills/show-me/SKILL.md +1 -1
- package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +1 -1
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,1263 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
3
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
5
|
+
import { assistantToolCallCount, finalizedAssistantResponse, stateFlowProtocol } from "./protocol.js";
|
|
6
|
+
import { createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, runtimeContextMessage, VALIDATION_MESSAGE_TYPE, withoutPrivateValidation } from "./context.js";
|
|
7
|
+
import { ArtifactReadTracker } from "./acquisition.js";
|
|
8
|
+
import { loadStateFlowConfig } from "./config.js";
|
|
9
|
+
import { createStateFlowTelegramAdapter } from "./telegram.js";
|
|
10
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
11
|
+
import { SkillReadTracker } from "./skills.js";
|
|
12
|
+
import { emptySnapshot, migrationFailure, persistableSnapshot, RevisionUnavailableError } from "./snapshot.js";
|
|
13
|
+
import { readNativeSessionHeader } from "./continuation.js";
|
|
14
|
+
import { MissingSessionRuntimeError, SharedScopeRemovalConflictError, TemporalRuntime } from "./runtime.js";
|
|
15
|
+
import { emptyState, overlayStates, projectStateForModel } from "./state.js";
|
|
16
|
+
import { commitScopedTransition, stageAtomicScopePatches, stageScopedTransition, validateFinalEligibility } from "./transition.js";
|
|
17
|
+
import { discoverSnapshotData, hasPriorConversation, isNewSession, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
18
|
+
import { compactStatus, detailedStatus, STATUS_KEY } from "./status.js";
|
|
19
|
+
import { prepareRun, resumeEpisode, startEpisode, stopEpisode } from "./episode.js";
|
|
20
|
+
import { recoverSnapshot } from "./recovery.js";
|
|
21
|
+
import { resolveRemotePublicationPolicy, serializeRemotePublicationPolicyDocument } from "./publication.js";
|
|
22
|
+
import { coalescePublicationTarget, createPublicationQueue } from "./publication.js";
|
|
23
|
+
import { acquirePublicationWorkerLease, loadPublicationQueue, publicationQueuePath, removePublicationQueue, savePublicationQueue } from "./publication.js";
|
|
24
|
+
import { runPublicationWorker } from "./publication.js";
|
|
25
|
+
import { getKnowledgeRoot, GlobalMarkdownDiscovery } from "./discovery.js";
|
|
26
|
+
import { isObject, sameJson } from "./json.js";
|
|
27
|
+
import { readStatePath } from "./query.js";
|
|
28
|
+
import { cwdScopeKey, resolveSessionAddress, sessionScopeKey, } from "./durable.js";
|
|
29
|
+
import { projectRecentTransitionsWithLimit, RECENT_TRANSITION_LIMIT } from "./history.js";
|
|
30
|
+
import { appendStateFlowDiagnostic, projectDiagnosticContent, stateFlowLogPath } from "./logging.js";
|
|
31
|
+
import { isGitCommitAncestor, pushGitCommit, pushGitTarget, resolveGitPushDestination } from "./git.js";
|
|
32
|
+
import { ORDINARY_ARTIFACT_COMPILER, planArtifactInvalidation, } from "./artifact.js";
|
|
33
|
+
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, stateFlowCompactionResult } from "./compaction.js";
|
|
34
|
+
export const PATCH_STATE_TOOL_NAME = "patch_state";
|
|
35
|
+
export const READ_STATE_TOOL_NAME = "read_state";
|
|
36
|
+
export const MAX_FALLBACK_ATTEMPTS = 2;
|
|
37
|
+
const PASSIVE_STOP_ENTRY_TYPE = "state-flow-passive-stop";
|
|
38
|
+
const PUBLICATION_SHUTDOWN_WAIT_MS = 2_000;
|
|
39
|
+
const PATCH_DISPLAY_SECTION_KEYS = new Set([
|
|
40
|
+
"global",
|
|
41
|
+
"cwd",
|
|
42
|
+
"session",
|
|
43
|
+
"artifacts",
|
|
44
|
+
"contract",
|
|
45
|
+
"working",
|
|
46
|
+
"response",
|
|
47
|
+
"final",
|
|
48
|
+
]);
|
|
49
|
+
/** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
|
|
50
|
+
export function formatPatchStateArguments(args) {
|
|
51
|
+
const seenAtIndent = new Set();
|
|
52
|
+
return JSON.stringify(args, null, 2).split("\n").flatMap((line) => {
|
|
53
|
+
const indent = line.length - line.trimStart().length;
|
|
54
|
+
const match = /^(\s+)"([^"]+)":/.exec(line);
|
|
55
|
+
if (match === null || !PATCH_DISPLAY_SECTION_KEYS.has(match[2])) {
|
|
56
|
+
for (const seenIndent of seenAtIndent) {
|
|
57
|
+
if (seenIndent > indent)
|
|
58
|
+
seenAtIndent.delete(seenIndent);
|
|
59
|
+
}
|
|
60
|
+
return [line];
|
|
61
|
+
}
|
|
62
|
+
const separator = seenAtIndent.has(indent) ? [""] : [];
|
|
63
|
+
seenAtIndent.add(indent);
|
|
64
|
+
return [...separator, line];
|
|
65
|
+
}).join("\n");
|
|
66
|
+
}
|
|
67
|
+
/** Normalize a bounded compatibility superset without advertising aliases in the model-facing contract. */
|
|
68
|
+
export function normalizePatchStateArguments(args) {
|
|
69
|
+
if (!isObject(args) || !Object.hasOwn(args, "final"))
|
|
70
|
+
return args;
|
|
71
|
+
const value = args.final;
|
|
72
|
+
let final;
|
|
73
|
+
if (typeof value === "boolean")
|
|
74
|
+
final = value;
|
|
75
|
+
else if (value === 1)
|
|
76
|
+
final = true;
|
|
77
|
+
else if (value === 0)
|
|
78
|
+
final = false;
|
|
79
|
+
else if (typeof value === "string" && value.trim().toLowerCase() === "true")
|
|
80
|
+
final = true;
|
|
81
|
+
else if (typeof value === "string" && value.trim().toLowerCase() === "false")
|
|
82
|
+
final = false;
|
|
83
|
+
else
|
|
84
|
+
return args;
|
|
85
|
+
return { ...args, final };
|
|
86
|
+
}
|
|
87
|
+
/** Keep tool output visually separated from its heading with exactly one leading newline. */
|
|
88
|
+
function separatedOutput(text) {
|
|
89
|
+
return `\n${text.replace(/^\n+/, "")}`;
|
|
90
|
+
}
|
|
91
|
+
/** Keep a failed tool invocation visually separated from its rendered error without changing error semantics. */
|
|
92
|
+
function separatedFailure(error) {
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
return new Error(separatedOutput(message), error instanceof Error ? { cause: error } : undefined);
|
|
95
|
+
}
|
|
96
|
+
export default function stateFlowExtension(pi, options = {}) {
|
|
97
|
+
const agentDir = options.agentDir ?? getAgentDir();
|
|
98
|
+
const config = loadStateFlowConfig(agentDir);
|
|
99
|
+
let snapshot = emptySnapshot();
|
|
100
|
+
let scopeStates = { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
101
|
+
let branchHasSnapshot = false;
|
|
102
|
+
let branchStartsWithoutRuntime = false;
|
|
103
|
+
let forkInitialization = false;
|
|
104
|
+
let terminalEligible = false;
|
|
105
|
+
let resolutionPending = false;
|
|
106
|
+
let fallbackAttempts = 0;
|
|
107
|
+
let fallbackFailureReported = false;
|
|
108
|
+
let responseAwaitingReconciliation = false;
|
|
109
|
+
let completedRunAccepted = false;
|
|
110
|
+
let compactionPlan;
|
|
111
|
+
let compactionInFlight = false;
|
|
112
|
+
let compactionStopped = false;
|
|
113
|
+
const compactionMarker = `state-flow-boundary:${randomUUID()}`;
|
|
114
|
+
let passiveContinuation;
|
|
115
|
+
let bootstrapContinuation;
|
|
116
|
+
let artifactRefreshPending = false;
|
|
117
|
+
let runAnchorTimestamp;
|
|
118
|
+
let runtime;
|
|
119
|
+
let activeContext;
|
|
120
|
+
let pendingPublication;
|
|
121
|
+
let rehydrationPhase;
|
|
122
|
+
let turnPublicationTarget;
|
|
123
|
+
const activePublicationWorkers = new Map();
|
|
124
|
+
let publicationStopped = false;
|
|
125
|
+
let publicationShutdown;
|
|
126
|
+
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
127
|
+
const skillReads = new SkillReadTracker();
|
|
128
|
+
const artifactReads = new ArtifactReadTracker();
|
|
129
|
+
const globalMarkdown = new GlobalMarkdownDiscovery(options.knowledgeRoot ?? getKnowledgeRoot(agentDir));
|
|
130
|
+
let artifactInvalidations = [];
|
|
131
|
+
let loggingWarningReported = false;
|
|
132
|
+
let telegramStartPending = false;
|
|
133
|
+
function sessionAddress(ctx) {
|
|
134
|
+
return resolveSessionAddress(ctx.sessionManager.getSessionFile(), ctx.sessionManager.getSessionId(), ctx.sessionManager.getHeader()?.timestamp);
|
|
135
|
+
}
|
|
136
|
+
function createRuntime(ctx) {
|
|
137
|
+
return new TemporalRuntime(ctx.cwd, sessionAddress(ctx), repositoryRoot);
|
|
138
|
+
}
|
|
139
|
+
options.onRuntime?.({ read: (offset, scope) => {
|
|
140
|
+
if (!runtime)
|
|
141
|
+
throw new Error("State Flow temporal runtime is unavailable");
|
|
142
|
+
return projectStateForModel(runtime.read(offset, scope));
|
|
143
|
+
} });
|
|
144
|
+
function persist() {
|
|
145
|
+
const mode = snapshot.meta.remotePublication?.mode ?? "transition";
|
|
146
|
+
const publication = runtime?.view ? runtime.publish(snapshot, false, undefined, { pushRemote: mode === "transition" }) : undefined;
|
|
147
|
+
if (publication?.commit && mode === "turn-end")
|
|
148
|
+
turnPublicationTarget = publication.commit;
|
|
149
|
+
if (publication && activeContext)
|
|
150
|
+
recordPublication(publication, activeContext);
|
|
151
|
+
const checkpoint = persistableSnapshot(snapshot);
|
|
152
|
+
if ("disabled" in checkpoint && !branchStartsWithoutRuntime) {
|
|
153
|
+
throw new Error("State Flow cannot checkpoint an unproven branch as ordinary disabled; restore a valid checkpoint first");
|
|
154
|
+
}
|
|
155
|
+
pi.appendEntry(SNAPSHOT_ENTRY_TYPE, checkpoint);
|
|
156
|
+
if ("revision" in checkpoint)
|
|
157
|
+
branchStartsWithoutRuntime = false;
|
|
158
|
+
}
|
|
159
|
+
function updateUi(ctx) {
|
|
160
|
+
ctx.ui.setStatus(STATUS_KEY, compactStatus(snapshot, (color, text) => ctx.ui.theme.fg(color, text)));
|
|
161
|
+
}
|
|
162
|
+
function setPendingPublication(value) {
|
|
163
|
+
pendingPublication = value === undefined ? undefined : structuredClone(value);
|
|
164
|
+
if (value === undefined)
|
|
165
|
+
delete snapshot.meta.pendingPublication;
|
|
166
|
+
else
|
|
167
|
+
snapshot.meta.pendingPublication = structuredClone(value);
|
|
168
|
+
}
|
|
169
|
+
function clearRunTransient() {
|
|
170
|
+
terminalEligible = false;
|
|
171
|
+
resolutionPending = false;
|
|
172
|
+
fallbackAttempts = 0;
|
|
173
|
+
fallbackFailureReported = false;
|
|
174
|
+
responseAwaitingReconciliation = false;
|
|
175
|
+
completedRunAccepted = false;
|
|
176
|
+
compactionPlan = undefined;
|
|
177
|
+
compactionInFlight = false;
|
|
178
|
+
runAnchorTimestamp = undefined;
|
|
179
|
+
skillReads.clear();
|
|
180
|
+
artifactReads.clear();
|
|
181
|
+
}
|
|
182
|
+
function passiveStopBoundary(ctx) {
|
|
183
|
+
for (const entry of [...ctx.sessionManager.getBranch()].reverse()) {
|
|
184
|
+
try {
|
|
185
|
+
if (entry?.type !== "custom" || entry.customType !== PASSIVE_STOP_ENTRY_TYPE)
|
|
186
|
+
continue;
|
|
187
|
+
const { at, from, reset, owner } = entry.data ?? {};
|
|
188
|
+
if (reset === true && owner === ctx.sessionManager.getSessionId())
|
|
189
|
+
return undefined;
|
|
190
|
+
if (typeof at === "number" && Number.isSafeInteger(at) && at >= 0)
|
|
191
|
+
return {
|
|
192
|
+
at,
|
|
193
|
+
...(typeof from === "number" && Number.isSafeInteger(from) && from >= 0 ? { from } : {}),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// A hostile unrelated branch entry cannot manufacture or suppress a valid marker.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
function retainsPhysicalSessionProjection(reason) {
|
|
203
|
+
return reason === undefined || reason === "startup" || reason === "reload" || reason === "resume";
|
|
204
|
+
}
|
|
205
|
+
function deferArtifactRefresh() {
|
|
206
|
+
artifactInvalidations = [];
|
|
207
|
+
artifactReads.setCandidates([]);
|
|
208
|
+
artifactRefreshPending = true;
|
|
209
|
+
}
|
|
210
|
+
function refreshArtifactInvalidations(ctx) {
|
|
211
|
+
artifactRefreshPending = false;
|
|
212
|
+
if (!snapshot.config.enabled) {
|
|
213
|
+
artifactInvalidations = [];
|
|
214
|
+
artifactReads.setCandidates([]);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const discovery = globalMarkdown.refresh(Object.keys(scopeStates.global.artifacts));
|
|
219
|
+
const plan = planArtifactInvalidation(discovery.sources, scopeStates.global.artifacts, ORDINARY_ARTIFACT_COMPILER, { removed: discovery.removed }, runtime?.artifactProvenance("global") ?? {});
|
|
220
|
+
artifactInvalidations = structuredClone(plan.requiresCompilation);
|
|
221
|
+
if (plan.removed.length > 0) {
|
|
222
|
+
const removals = Object.fromEntries(plan.removed.map((path) => [path, null]));
|
|
223
|
+
const stage = stageAtomicScopePatches(scopeStates, { global: { artifacts: removals } }, [], runtime.causalBasis());
|
|
224
|
+
commitStage(stage, ctx, false);
|
|
225
|
+
}
|
|
226
|
+
artifactReads.setCandidates(artifactInvalidations);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
artifactInvalidations = [];
|
|
230
|
+
artifactReads.setCandidates([]);
|
|
231
|
+
ctx.ui.notify(`State Flow could not discover global Markdown sources: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function installScopeStates() {
|
|
235
|
+
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
236
|
+
}
|
|
237
|
+
function syncStateFlowTools() {
|
|
238
|
+
const active = pi.getActiveTools();
|
|
239
|
+
const owned = [PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME];
|
|
240
|
+
if (owned.every((name) => active.includes(name) === snapshot.config.enabled))
|
|
241
|
+
return;
|
|
242
|
+
pi.setActiveTools(snapshot.config.enabled
|
|
243
|
+
? [...new Set([...active, ...owned])]
|
|
244
|
+
: active.filter((name) => !owned.includes(name)));
|
|
245
|
+
}
|
|
246
|
+
function assistantToolBatch(ctx, toolCallId) {
|
|
247
|
+
for (let cursor = ctx.sessionManager.getLeafEntry(); cursor; cursor = cursor.parentId ? ctx.sessionManager.getEntry(cursor.parentId) : undefined) {
|
|
248
|
+
const entry = cursor;
|
|
249
|
+
if (entry.type !== "message" || entry.message?.role !== "assistant" || !Array.isArray(entry.message.content))
|
|
250
|
+
continue;
|
|
251
|
+
const calls = entry.message.content.filter((block) => {
|
|
252
|
+
return typeof block === "object" && block !== null
|
|
253
|
+
&& block.type === "toolCall"
|
|
254
|
+
&& typeof block.id === "string"
|
|
255
|
+
&& typeof block.name === "string";
|
|
256
|
+
});
|
|
257
|
+
if (calls.some(({ id }) => id === toolCallId))
|
|
258
|
+
return calls.map(({ name }) => name);
|
|
259
|
+
}
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
function recordPublication(publication, ctx) {
|
|
263
|
+
const revision = publication.revision ?? publication.commit;
|
|
264
|
+
if (revision !== undefined)
|
|
265
|
+
snapshot.meta.durableBase = revision;
|
|
266
|
+
if (publication.push?.status === "pending") {
|
|
267
|
+
setPendingPublication({
|
|
268
|
+
commit: publication.push.commit,
|
|
269
|
+
error: publication.push.error ?? "unknown push failure",
|
|
270
|
+
});
|
|
271
|
+
ctx.ui.notify(`State Flow accepted durable commit ${publication.push.commit.slice(0, 12)}, but push is pending: ${publication.push.error}`, "warning");
|
|
272
|
+
}
|
|
273
|
+
else if (revision !== undefined) {
|
|
274
|
+
setPendingPublication(undefined);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** Accept an activation or lifecycle commit locally; turn-end policy queues it for the asynchronous worker. */
|
|
278
|
+
function recordPolicyPublication(publication, ctx) {
|
|
279
|
+
if (!publication)
|
|
280
|
+
return;
|
|
281
|
+
recordPublication(publication, ctx);
|
|
282
|
+
const mode = snapshot.meta.remotePublication?.mode ?? "transition";
|
|
283
|
+
const target = publication.commit ?? publication.revision;
|
|
284
|
+
if (mode !== "turn-end" || target === undefined || !/^[0-9a-f]{40,64}$/.test(target))
|
|
285
|
+
return;
|
|
286
|
+
turnPublicationTarget = target;
|
|
287
|
+
try {
|
|
288
|
+
enqueueTurnPublication(ctx);
|
|
289
|
+
launchPublicationWorker();
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
ctx.ui.notify(`State Flow accepted the local commit; remote publication is deferred: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function commitStage(stage, ctx, finalizeRun) {
|
|
296
|
+
const acquiredArtifactPaths = new Set(artifactReads.successful.keys());
|
|
297
|
+
let committed;
|
|
298
|
+
try {
|
|
299
|
+
committed = commitScopedTransition(snapshot, scopeStates, stage, (accepted, nextSnapshot) => {
|
|
300
|
+
if (!runtime?.view)
|
|
301
|
+
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
302
|
+
const mode = nextSnapshot.meta.remotePublication?.mode ?? "transition";
|
|
303
|
+
const publication = runtime.publish(nextSnapshot, accepted !== undefined, accepted, {
|
|
304
|
+
pushRemote: mode === "transition",
|
|
305
|
+
provenance: stage.provenanceUpdates,
|
|
306
|
+
});
|
|
307
|
+
if (publication?.commit && mode === "turn-end")
|
|
308
|
+
turnPublicationTarget = publication.commit;
|
|
309
|
+
if (publication)
|
|
310
|
+
recordPublication(publication, ctx);
|
|
311
|
+
}, runtime.causalBasis(), { finalizeRun });
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (error instanceof SharedScopeRemovalConflictError)
|
|
315
|
+
installScopeStates();
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
if (!committed)
|
|
319
|
+
return false;
|
|
320
|
+
installScopeStates();
|
|
321
|
+
// A preserved primary response commits mid-run; pending acquisition obligations must
|
|
322
|
+
// still block final eligibility until the fallback turns resolve or expire.
|
|
323
|
+
if (!(finalizeRun && resolutionPending)) {
|
|
324
|
+
artifactInvalidations = artifactInvalidations.filter(({ path }) => !acquiredArtifactPaths.has(path));
|
|
325
|
+
artifactReads.setCandidates(artifactInvalidations);
|
|
326
|
+
skillReads.clear();
|
|
327
|
+
artifactReads.clear();
|
|
328
|
+
}
|
|
329
|
+
persist();
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
function enqueueTurnPublication(ctx) {
|
|
333
|
+
const target = turnPublicationTarget;
|
|
334
|
+
turnPublicationTarget = undefined;
|
|
335
|
+
if (!target)
|
|
336
|
+
return;
|
|
337
|
+
const destination = resolveGitPushDestination(repositoryRoot);
|
|
338
|
+
if (!destination)
|
|
339
|
+
return;
|
|
340
|
+
const path = publicationQueuePath(destination);
|
|
341
|
+
const previous = loadPublicationQueue(path);
|
|
342
|
+
const next = previous
|
|
343
|
+
? coalescePublicationTarget(previous, destination, target, (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant), {
|
|
344
|
+
onDivergedLineage: (dropped) => recordDiagnostic(`Retired publication queue target ${dropped.target} after a journal lineage rewrite; retargeting to ${target}`, "publication-conflict", ctx),
|
|
345
|
+
})
|
|
346
|
+
: createPublicationQueue(destination, target);
|
|
347
|
+
savePublicationQueue(path, next, previous);
|
|
348
|
+
}
|
|
349
|
+
function launchPublicationWorker() {
|
|
350
|
+
if (publicationStopped)
|
|
351
|
+
return;
|
|
352
|
+
let destination;
|
|
353
|
+
try {
|
|
354
|
+
destination = resolveGitPushDestination(repositoryRoot);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
if (error instanceof Error && /ENOENT/.test(error.message))
|
|
358
|
+
return;
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
if (!destination)
|
|
362
|
+
return;
|
|
363
|
+
const path = publicationQueuePath(destination);
|
|
364
|
+
if (activePublicationWorkers.has(path))
|
|
365
|
+
return;
|
|
366
|
+
let queued;
|
|
367
|
+
let lease;
|
|
368
|
+
try {
|
|
369
|
+
queued = loadPublicationQueue(path);
|
|
370
|
+
if (!queued)
|
|
371
|
+
return;
|
|
372
|
+
lease = acquirePublicationWorkerLease(path);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (!lease)
|
|
378
|
+
return;
|
|
379
|
+
const controller = new AbortController();
|
|
380
|
+
const done = runPublicationWorker(queued, ({ target }) => pushGitTarget(repositoryRoot, destination, target, controller.signal), () => loadPublicationQueue(path) ?? queued, (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant)).then((result) => {
|
|
381
|
+
if (publicationStopped)
|
|
382
|
+
return; // Late outcomes belong to an unconfirmed queue, not the replacement generation.
|
|
383
|
+
const current = loadPublicationQueue(path);
|
|
384
|
+
if (!current)
|
|
385
|
+
return;
|
|
386
|
+
if (result.next === undefined) {
|
|
387
|
+
if (current.target === result.attempted.target)
|
|
388
|
+
removePublicationQueue(path, current);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (current.target === result.attempted.target || result.next.target === current.target) {
|
|
392
|
+
savePublicationQueue(path, result.next, current);
|
|
393
|
+
}
|
|
394
|
+
}).catch(() => {
|
|
395
|
+
// Queue/CAS truth remains durable; status and a later activation expose retry.
|
|
396
|
+
}).finally(() => {
|
|
397
|
+
activePublicationWorkers.delete(path);
|
|
398
|
+
try {
|
|
399
|
+
lease.release();
|
|
400
|
+
if (!publicationStopped && loadPublicationQueue(path)?.status === "pending")
|
|
401
|
+
launchPublicationWorker();
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
// Failed lease cleanup or malformed persistence stays inert until retry or repair.
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
activePublicationWorkers.set(path, { controller, done });
|
|
408
|
+
}
|
|
409
|
+
function shutdownPublicationWorkers(ctx) {
|
|
410
|
+
publicationStopped = true;
|
|
411
|
+
return publicationShutdown ??= (async () => {
|
|
412
|
+
const workers = [...activePublicationWorkers.values()];
|
|
413
|
+
for (const { controller } of workers)
|
|
414
|
+
controller.abort();
|
|
415
|
+
if (workers.length === 0)
|
|
416
|
+
return;
|
|
417
|
+
let timeout;
|
|
418
|
+
try {
|
|
419
|
+
const completed = await Promise.race([
|
|
420
|
+
Promise.all(workers.map(({ done }) => done)).then(() => true),
|
|
421
|
+
new Promise((resolve) => { timeout = setTimeout(() => resolve(false), PUBLICATION_SHUTDOWN_WAIT_MS); }),
|
|
422
|
+
]);
|
|
423
|
+
if (!completed)
|
|
424
|
+
ctx.ui.notify(`State Flow push cleanup is unconfirmed after ${PUBLICATION_SHUTDOWN_WAIT_MS}ms; worker leases remain held until child exit.`, "warning");
|
|
425
|
+
}
|
|
426
|
+
finally {
|
|
427
|
+
clearTimeout(timeout);
|
|
428
|
+
}
|
|
429
|
+
})();
|
|
430
|
+
}
|
|
431
|
+
function retryPendingPush(ctx) {
|
|
432
|
+
if (pendingPublication === undefined || !runtime?.view)
|
|
433
|
+
return;
|
|
434
|
+
const mode = snapshot.meta.remotePublication?.mode ?? "transition";
|
|
435
|
+
if (mode !== "transition") {
|
|
436
|
+
const target = pendingPublication.commit;
|
|
437
|
+
setPendingPublication(undefined);
|
|
438
|
+
if (mode === "turn-end") {
|
|
439
|
+
turnPublicationTarget = target;
|
|
440
|
+
try {
|
|
441
|
+
enqueueTurnPublication(ctx);
|
|
442
|
+
launchPublicationWorker();
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
ctx.ui.notify(`State Flow retained local state; asynchronous publication recovery is deferred: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const result = pushGitCommit(repositoryRoot, pendingPublication.commit);
|
|
451
|
+
if (result.status !== "pending") {
|
|
452
|
+
setPendingPublication(undefined);
|
|
453
|
+
persist();
|
|
454
|
+
ctx.ui.notify(result.status === "local"
|
|
455
|
+
? `State Flow retained local-only durable commit ${result.commit.slice(0, 12)}; no remote configured.`
|
|
456
|
+
: `State Flow pushed pending durable commit ${result.commit.slice(0, 12)}.`, "info");
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
setPendingPublication({ commit: result.commit, error: result.error ?? "unknown push failure" });
|
|
460
|
+
persist();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function currentRehydrationPhase() {
|
|
464
|
+
return rehydrationPhase;
|
|
465
|
+
}
|
|
466
|
+
function statusDiagnostics(ctx) {
|
|
467
|
+
const cwd = ctx.cwd;
|
|
468
|
+
const diagnosticStates = scopeStates;
|
|
469
|
+
const view = runtime?.view;
|
|
470
|
+
const diagnosticRecent = runtime?.recent() ?? [];
|
|
471
|
+
const durableStateError = view ? undefined
|
|
472
|
+
: snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : "no temporal runtime is selected on this branch";
|
|
473
|
+
let staleArtifacts = [];
|
|
474
|
+
let artifactFreshnessError;
|
|
475
|
+
if (durableStateError !== undefined) {
|
|
476
|
+
artifactFreshnessError = "durable global artifact registry is unavailable";
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
try {
|
|
480
|
+
const discovery = globalMarkdown.refresh(Object.keys(diagnosticStates.global.artifacts));
|
|
481
|
+
artifactFreshnessError = discovery.unavailable;
|
|
482
|
+
const plan = planArtifactInvalidation(discovery.sources, diagnosticStates.global.artifacts, ORDINARY_ARTIFACT_COMPILER, { removed: discovery.removed }, runtime?.artifactProvenance("global") ?? {});
|
|
483
|
+
staleArtifacts = [
|
|
484
|
+
...plan.requiresCompilation.map(({ path, reason }) => ({ scope: "global", path, reason })),
|
|
485
|
+
...plan.removed.map((path) => ({ scope: "global", path, reason: "source-removed" })),
|
|
486
|
+
];
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
artifactFreshnessError = error instanceof Error ? error.message : String(error);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const session = sessionAddress(ctx);
|
|
493
|
+
let publicationQueue;
|
|
494
|
+
let publicationQueueError;
|
|
495
|
+
try {
|
|
496
|
+
const destination = resolveGitPushDestination(repositoryRoot);
|
|
497
|
+
publicationQueue = destination ? loadPublicationQueue(publicationQueuePath(destination)) : undefined;
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
publicationQueue = undefined;
|
|
501
|
+
publicationQueueError = error instanceof Error ? error.message : String(error);
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
repositoryRoot,
|
|
505
|
+
cwdScopeKey: cwdScopeKey(cwd),
|
|
506
|
+
sessionScopeKey: sessionScopeKey(session.key),
|
|
507
|
+
scopeStates: diagnosticStates,
|
|
508
|
+
recent: diagnosticRecent,
|
|
509
|
+
...(view === undefined ? {} : { temporal: {
|
|
510
|
+
head: structuredClone(view.lineage.at(-1)),
|
|
511
|
+
historyDepth: view.lineage.length - 1,
|
|
512
|
+
tailCounts: { global: view.scopes.global.patches.length, cwd: view.scopes.cwd.patches.length, session: view.scopes.session.patches.length },
|
|
513
|
+
} }),
|
|
514
|
+
staleArtifacts,
|
|
515
|
+
...(artifactFreshnessError === undefined ? {} : { artifactFreshnessError }),
|
|
516
|
+
...(durableStateError === undefined ? {} : { durableStateError }),
|
|
517
|
+
...(pendingPublication === undefined ? {} : { pendingPublication }),
|
|
518
|
+
...(publicationQueue === undefined ? {} : { publicationQueue }),
|
|
519
|
+
...(publicationQueueError === undefined ? {} : { publicationQueueError }),
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function prepareBranchRestore(ctx, revision, legacy) {
|
|
523
|
+
if (!forkInitialization) {
|
|
524
|
+
try {
|
|
525
|
+
return runtime.prepareRestore(revision, legacy);
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
if (error instanceof MissingSessionRuntimeError && typeof ctx.sessionManager.getHeader()?.parentSession === "string") {
|
|
529
|
+
throw new RevisionUnavailableError("State Flow checkpoint has no child-owned runtime; select a child checkpoint or resume the parent");
|
|
530
|
+
}
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const file = ctx.sessionManager.getHeader()?.parentSession;
|
|
535
|
+
if (typeof file !== "string" || !isAbsolute(file))
|
|
536
|
+
throw new Error("State Flow fork requires a persisted native parent session");
|
|
537
|
+
const parent = readNativeSessionHeader(file);
|
|
538
|
+
if (parent.cwd !== resolve(ctx.cwd))
|
|
539
|
+
throw new Error("State Flow fork parent CWD identity mismatch");
|
|
540
|
+
const source = resolveSessionAddress(parent.file, parent.id, parent.timestamp);
|
|
541
|
+
const prepared = runtime.prepareFork(source, revision);
|
|
542
|
+
return { snapshot: prepared.snapshot, restore: () => {
|
|
543
|
+
const accepted = prepared.fork();
|
|
544
|
+
snapshot = accepted.snapshot;
|
|
545
|
+
recordPolicyPublication(accepted.publication, ctx);
|
|
546
|
+
// Copied Stop markers belong to the parent, including after a child reload/resume.
|
|
547
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner: ctx.sessionManager.getSessionId() });
|
|
548
|
+
forkInitialization = false;
|
|
549
|
+
return snapshot;
|
|
550
|
+
} };
|
|
551
|
+
}
|
|
552
|
+
function restoreActiveBranch(ctx, sessionStartReason) {
|
|
553
|
+
clearRunTransient();
|
|
554
|
+
passiveContinuation = undefined;
|
|
555
|
+
bootstrapContinuation = undefined;
|
|
556
|
+
artifactInvalidations = [];
|
|
557
|
+
artifactReads.setCandidates([]);
|
|
558
|
+
artifactRefreshPending = false;
|
|
559
|
+
telegramStartPending = false;
|
|
560
|
+
activeContext = ctx;
|
|
561
|
+
const session = sessionAddress(ctx);
|
|
562
|
+
runtime = new TemporalRuntime(ctx.cwd, session, repositoryRoot);
|
|
563
|
+
installScopeStates();
|
|
564
|
+
pendingPublication = undefined;
|
|
565
|
+
branchHasSnapshot = false;
|
|
566
|
+
branchStartsWithoutRuntime = false;
|
|
567
|
+
forkInitialization = sessionStartReason === "fork";
|
|
568
|
+
try {
|
|
569
|
+
const branch = ctx.sessionManager.getBranch();
|
|
570
|
+
const discovery = discoverSnapshotData(branch);
|
|
571
|
+
let restoreSelected;
|
|
572
|
+
const recovery = recoverSnapshot(discovery.candidates, (revision) => {
|
|
573
|
+
try {
|
|
574
|
+
const prepared = prepareBranchRestore(ctx, revision);
|
|
575
|
+
restoreSelected = prepared.restore;
|
|
576
|
+
return prepared.snapshot;
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
// A failed source proof never licenses an older/empty private copy.
|
|
580
|
+
if (forkInitialization)
|
|
581
|
+
throw new RevisionUnavailableError(`Cannot copy State Flow fork source: ${error instanceof Error ? error.message : String(error)}`);
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
branchStartsWithoutRuntime = recovery.disabledMarker === true
|
|
586
|
+
|| (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
587
|
+
const skipped = discovery.errors.length + recovery.skipped.length;
|
|
588
|
+
branchHasSnapshot = recovery.skipped.length < discovery.candidates.length;
|
|
589
|
+
if (discovery.candidates.length === 0 && discovery.errors.length > 0) {
|
|
590
|
+
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}`);
|
|
591
|
+
}
|
|
592
|
+
else if (discovery.candidates.length > 0) {
|
|
593
|
+
snapshot = recovery.snapshot;
|
|
594
|
+
if (branchHasSnapshot && snapshot.meta.durableBase) {
|
|
595
|
+
const selectedRevision = snapshot.meta.durableBase;
|
|
596
|
+
snapshot = restoreSelected ? restoreSelected() : prepareBranchRestore(ctx, selectedRevision, snapshot).restore();
|
|
597
|
+
if (snapshot.meta.durableBase !== selectedRevision)
|
|
598
|
+
persist();
|
|
599
|
+
}
|
|
600
|
+
else if (branchHasSnapshot && snapshot.config.enabled) {
|
|
601
|
+
const publication = runtime.initialize(snapshot, true);
|
|
602
|
+
recordPolicyPublication(publication, ctx);
|
|
603
|
+
}
|
|
604
|
+
installScopeStates();
|
|
605
|
+
}
|
|
606
|
+
else if (config.autoStart && isNewSession(sessionStartReason, branch)) {
|
|
607
|
+
snapshot = startEpisode(hasPriorConversation(branch));
|
|
608
|
+
snapshot.meta.remotePublication = serializeRemotePublicationPolicyDocument(resolveRemotePublicationPolicy(config.remotePublication, { legacyRuntime: false }));
|
|
609
|
+
runtime.prepare();
|
|
610
|
+
const initialization = runtime.initialize(snapshot, true);
|
|
611
|
+
recordPolicyPublication(initialization, ctx);
|
|
612
|
+
if (!runtime.view)
|
|
613
|
+
snapshot = emptySnapshot();
|
|
614
|
+
installScopeStates();
|
|
615
|
+
if (snapshot.config.enabled) {
|
|
616
|
+
branchHasSnapshot = true;
|
|
617
|
+
persist();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
snapshot = emptySnapshot();
|
|
622
|
+
installScopeStates();
|
|
623
|
+
}
|
|
624
|
+
if (snapshot.config.enabled && skipped > 0) {
|
|
625
|
+
ctx.ui.notify(`State Flow recovered the previous valid snapshot after skipping ${skipped} malformed newer snapshot(s).`, "warning");
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
catch (error) {
|
|
629
|
+
const failure = migrationFailure({}, `Snapshot restoration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
630
|
+
snapshot = {
|
|
631
|
+
...snapshot,
|
|
632
|
+
config: { ...snapshot.config, enabled: false },
|
|
633
|
+
meta: { ...snapshot.meta, validation: failure.meta.validation },
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
if (branchHasSnapshot && snapshot.meta.remotePublication === undefined) {
|
|
637
|
+
snapshot.meta.remotePublication = serializeRemotePublicationPolicyDocument(resolveRemotePublicationPolicy(undefined, { legacyRuntime: true }));
|
|
638
|
+
}
|
|
639
|
+
if (snapshot.config.enabled && snapshot.meta.remotePublication === undefined) {
|
|
640
|
+
snapshot.meta.remotePublication = serializeRemotePublicationPolicyDocument(resolveRemotePublicationPolicy(undefined, { legacyRuntime: true }));
|
|
641
|
+
}
|
|
642
|
+
pendingPublication = snapshot.meta.pendingPublication === undefined
|
|
643
|
+
? undefined
|
|
644
|
+
: structuredClone(snapshot.meta.pendingPublication);
|
|
645
|
+
if (!snapshot.config.enabled && snapshot.meta.validation?.attempt === 0) {
|
|
646
|
+
ctx.ui.notify(`State Flow restored disabled: ${snapshot.meta.validation.error}`, "error");
|
|
647
|
+
}
|
|
648
|
+
if (retainsPhysicalSessionProjection(sessionStartReason) && runtime?.view) {
|
|
649
|
+
const boundary = passiveStopBoundary(ctx);
|
|
650
|
+
if (boundary !== undefined) {
|
|
651
|
+
const continuation = createPassiveContinuation(projectStateForModel(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), boundary.at, boundary.from);
|
|
652
|
+
if (!snapshot.config.enabled)
|
|
653
|
+
passiveContinuation = continuation;
|
|
654
|
+
else if (snapshot.meta.bootstrap)
|
|
655
|
+
bootstrapContinuation = continuation;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (snapshot.config.enabled)
|
|
659
|
+
deferArtifactRefresh();
|
|
660
|
+
syncStateFlowTools();
|
|
661
|
+
updateUi(ctx);
|
|
662
|
+
}
|
|
663
|
+
function recordDiagnostic(error, category, ctx, extras = {}) {
|
|
664
|
+
if (!config.logging)
|
|
665
|
+
return;
|
|
666
|
+
try {
|
|
667
|
+
const path = stateFlowLogPath(agentDir);
|
|
668
|
+
const fromRepository = relative(repositoryRoot, path);
|
|
669
|
+
if (fromRepository === "" || (!isAbsolute(fromRepository) && fromRepository !== ".." && !fromRepository.startsWith(`..${sep}`))) {
|
|
670
|
+
throw new Error("diagnostic path overlaps the State Flow repository");
|
|
671
|
+
}
|
|
672
|
+
appendStateFlowDiagnostic(path, {
|
|
673
|
+
at: new Date().toISOString(),
|
|
674
|
+
sessionId: sessionAddress(ctx).id,
|
|
675
|
+
cwd: resolve(ctx.cwd),
|
|
676
|
+
category,
|
|
677
|
+
error,
|
|
678
|
+
...(extras.content === undefined ? {} : { content: projectDiagnosticContent(extras.content) }),
|
|
679
|
+
...(extras.input === undefined ? {} : { input: extras.input }),
|
|
680
|
+
...(extras.tool === undefined ? {} : { tool: extras.tool }),
|
|
681
|
+
...(extras.toolCallId === undefined ? {} : { toolCallId: extras.toolCallId }),
|
|
682
|
+
...(extras.resolutionAttempt === undefined ? {} : { resolutionAttempt: extras.resolutionAttempt }),
|
|
683
|
+
...(extras.terminalEligible === undefined ? {} : { terminalEligible: extras.terminalEligible }),
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
catch (failure) {
|
|
687
|
+
if (loggingWarningReported)
|
|
688
|
+
return;
|
|
689
|
+
loggingWarningReported = true;
|
|
690
|
+
ctx.ui.notify(`State Flow could not write diagnostics: ${failure instanceof Error ? failure.message : String(failure)}`, "warning");
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function continueFallbackResolution(lastWarning) {
|
|
694
|
+
resolutionPending = true;
|
|
695
|
+
pi.sendMessage({
|
|
696
|
+
customType: VALIDATION_MESSAGE_TYPE,
|
|
697
|
+
content: lastWarning
|
|
698
|
+
? "This is the last State Flow fallback turn. The iteration's answer is preserved and will not change. Apply the final:true patch now: call patch_state with any remaining durable scope changes and final:true, or with {\"final\":true} when nothing remains. Do not restate the answer."
|
|
699
|
+
: "The iteration's answer is preserved as the final response; later turns cannot replace it. Apply the final:true patch: call patch_state with any durable scope changes from this iteration (including required artifact or Skill compilation) and final:true, or with {\"final\":true} when nothing remains to persist. Do not restate the answer.",
|
|
700
|
+
display: false,
|
|
701
|
+
}, { deliverAs: "steer", triggerTurn: true });
|
|
702
|
+
}
|
|
703
|
+
/** Preserve the primary answer as this iteration's response and steer bounded fallback turns whose only purpose is the final:true patch. */
|
|
704
|
+
function beginFallbackResolution(ctx, message, reason) {
|
|
705
|
+
responseAwaitingReconciliation = true;
|
|
706
|
+
fallbackAttempts = 0;
|
|
707
|
+
fallbackFailureReported = false;
|
|
708
|
+
recordDiagnostic(`Preserved the terminal draft as the iteration response; fallback resolution started: ${reason}`, "terminal-pending", ctx, {
|
|
709
|
+
content: message.content,
|
|
710
|
+
resolutionAttempt: 0,
|
|
711
|
+
terminalEligible,
|
|
712
|
+
});
|
|
713
|
+
continueFallbackResolution(MAX_FALLBACK_ATTEMPTS === 1);
|
|
714
|
+
}
|
|
715
|
+
/** Fallback turns never become the response; they exist only to supply the final:true patch. */
|
|
716
|
+
function resolveFallbackTurn(ctx, message) {
|
|
717
|
+
let resolved = terminalEligible;
|
|
718
|
+
if (resolved) {
|
|
719
|
+
try {
|
|
720
|
+
validateFinalEligibility(scopeStates, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
721
|
+
}
|
|
722
|
+
catch (error) {
|
|
723
|
+
resolved = false;
|
|
724
|
+
recordDiagnostic(error instanceof Error ? error.message : String(error), "terminal-pending", ctx, {
|
|
725
|
+
content: message.content,
|
|
726
|
+
resolutionAttempt: fallbackAttempts,
|
|
727
|
+
terminalEligible,
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (resolved) {
|
|
732
|
+
resolutionPending = false;
|
|
733
|
+
recordDiagnostic(`Fallback resolution obtained final:true after ${fallbackAttempts} fallback turn${fallbackAttempts === 1 ? "" : "s"}; the preserved answer stands`, "finalization", ctx, {
|
|
734
|
+
content: message.content,
|
|
735
|
+
resolutionAttempt: fallbackAttempts,
|
|
736
|
+
terminalEligible,
|
|
737
|
+
});
|
|
738
|
+
return { message: { ...message, role: "assistant", content: [] } };
|
|
739
|
+
}
|
|
740
|
+
fallbackAttempts = Math.min(MAX_FALLBACK_ATTEMPTS, fallbackAttempts + 1);
|
|
741
|
+
if (fallbackAttempts < MAX_FALLBACK_ATTEMPTS) {
|
|
742
|
+
recordDiagnostic(`Fallback turn ended without final:true (${fallbackAttempts}/${MAX_FALLBACK_ATTEMPTS})`, "terminal-pending", ctx, {
|
|
743
|
+
content: message.content,
|
|
744
|
+
resolutionAttempt: fallbackAttempts,
|
|
745
|
+
terminalEligible,
|
|
746
|
+
});
|
|
747
|
+
continueFallbackResolution(fallbackAttempts + 1 >= MAX_FALLBACK_ATTEMPTS);
|
|
748
|
+
return { message: { ...message, role: "assistant", content: [] } };
|
|
749
|
+
}
|
|
750
|
+
resolutionPending = false;
|
|
751
|
+
recordDiagnostic(`Fallback resolution exhausted without final:true (${fallbackAttempts}/${MAX_FALLBACK_ATTEMPTS}); the preserved response and current state remain`, "finalization", ctx, {
|
|
752
|
+
content: message.content,
|
|
753
|
+
resolutionAttempt: fallbackAttempts,
|
|
754
|
+
terminalEligible,
|
|
755
|
+
});
|
|
756
|
+
if (!fallbackFailureReported) {
|
|
757
|
+
fallbackFailureReported = true;
|
|
758
|
+
ctx.ui.notify(`State Flow kept the preserved answer; no final:true patch arrived after ${MAX_FALLBACK_ATTEMPTS} fallback turns, so the iteration closed with its current state.`, "warning");
|
|
759
|
+
}
|
|
760
|
+
return { message: { ...message, role: "assistant", content: [] } };
|
|
761
|
+
}
|
|
762
|
+
pi.registerTool({
|
|
763
|
+
name: READ_STATE_TOOL_NAME,
|
|
764
|
+
label: "Read State",
|
|
765
|
+
description: "Read State Flow through a unified path such as state, state[1], state.cwd[2], or state.global.patches[0]. The legacy offset/scope form remains accepted. Read-only and lazy; unavailable hot history is an error.",
|
|
766
|
+
promptSnippet: "Read cached state or accepted scope patches with a unified path",
|
|
767
|
+
parameters: Type.Object({
|
|
768
|
+
path: Type.Optional(Type.String({ description: "Unified query path; current aliases use index 0" })),
|
|
769
|
+
offset: Type.Optional(Type.Integer({ minimum: 0, maximum: 7, description: "Legacy accepted-transition offset; defaults to 0" })),
|
|
770
|
+
scope: Type.Optional(StringEnum(["effective", "global", "cwd", "session"], { description: "Legacy projection at the same boundary; defaults to effective" })),
|
|
771
|
+
}, { additionalProperties: false }),
|
|
772
|
+
async execute(_toolCallId, params, signal) {
|
|
773
|
+
try {
|
|
774
|
+
if (!snapshot.config.enabled)
|
|
775
|
+
throw new Error("State Flow is disabled on this session branch");
|
|
776
|
+
if (signal?.aborted)
|
|
777
|
+
throw new Error("State Flow read was aborted");
|
|
778
|
+
if (!runtime?.view)
|
|
779
|
+
throw new Error("State Flow temporal runtime is unavailable");
|
|
780
|
+
if (params.path !== undefined) {
|
|
781
|
+
if (params.offset !== undefined || params.scope !== undefined)
|
|
782
|
+
throw new Error("read_state path cannot be combined with legacy offset or scope");
|
|
783
|
+
const result = readStatePath(runtime.view, params.path);
|
|
784
|
+
return {
|
|
785
|
+
content: [{ type: "text", text: `\n${JSON.stringify(result)}` }],
|
|
786
|
+
details: { path: params.path, transitionId: result.boundary.id },
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
const { offset = 0, scope = "effective" } = params;
|
|
790
|
+
const state = runtime.read(offset, scope === "effective" ? undefined : scope);
|
|
791
|
+
const boundary = runtime.view.lineage.at(-1 - offset);
|
|
792
|
+
return {
|
|
793
|
+
content: [{ type: "text", text: `\n${JSON.stringify({ offset, scope, boundary, state: projectStateForModel(state) })}` }],
|
|
794
|
+
details: { offset, scope, transitionId: boundary.id },
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
throw separatedFailure(error);
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
pi.registerTool({
|
|
803
|
+
name: PATCH_STATE_TOOL_NAME,
|
|
804
|
+
label: "Patch State",
|
|
805
|
+
description: "The sole State Flow semantic mutation protocol. Supply any combination of global, cwd, and session patches; all supplied scopes commit atomically. Set final:true when the current iteration may finish at a later turn_end. final:true does not stop reasoning, tools, or later patch_state calls. Use {final:true} when no semantic update is needed. This call must be the only State Flow barrier in its assistant response.",
|
|
806
|
+
promptSnippet: "Atomically patch global/cwd/session; final:true permits a later turn_end",
|
|
807
|
+
promptGuidelines: [
|
|
808
|
+
"Use patch_state for durable semantic changes. Before a final answer, make the iteration terminal-eligible with final:true, optionally alongside atomic global/cwd/session patches.",
|
|
809
|
+
"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.",
|
|
810
|
+
"Call patch_state alone in an assistant response; after its acknowledgement, further reasoning, tools, and later patch_state calls remain allowed.",
|
|
811
|
+
],
|
|
812
|
+
executionMode: "sequential",
|
|
813
|
+
parameters: Type.Object({
|
|
814
|
+
global: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional global semantic patch" })),
|
|
815
|
+
cwd: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional project semantic patch" })),
|
|
816
|
+
session: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional session semantic patch" })),
|
|
817
|
+
final: Type.Optional(Type.Boolean({ description: "Set exactly true to permit this iteration to finish at a later turn_end" })),
|
|
818
|
+
}, { additionalProperties: false }),
|
|
819
|
+
prepareArguments: normalizePatchStateArguments,
|
|
820
|
+
renderResult(result, { isPartial }, theme, context) {
|
|
821
|
+
const text = result.content.find((block) => block.type === "text")?.text ?? "";
|
|
822
|
+
if (context.isError)
|
|
823
|
+
return new Text(separatedOutput(text), 0, 0);
|
|
824
|
+
if (isPartial || !config.showSuccessfulPatches)
|
|
825
|
+
return new Text(text, 0, 0);
|
|
826
|
+
return new Text(separatedOutput(theme.fg("dim", formatPatchStateArguments(context.args))), 0, 0);
|
|
827
|
+
},
|
|
828
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
829
|
+
try {
|
|
830
|
+
if (!snapshot.config.enabled)
|
|
831
|
+
throw new Error("State Flow is disabled on this session branch");
|
|
832
|
+
if (signal?.aborted)
|
|
833
|
+
throw new Error("State Flow patch was aborted before materialization");
|
|
834
|
+
if (!isObject(params))
|
|
835
|
+
throw new Error("patch_state requires an object");
|
|
836
|
+
const allowed = new Set(["global", "cwd", "session", "final"]);
|
|
837
|
+
for (const key of Object.keys(params)) {
|
|
838
|
+
if (!allowed.has(key))
|
|
839
|
+
throw new Error(`patch_state does not accept field ${key}`);
|
|
840
|
+
}
|
|
841
|
+
if (Object.hasOwn(params, "final") && typeof params.final !== "boolean")
|
|
842
|
+
throw new Error("patch_state final must be a Boolean when supplied");
|
|
843
|
+
const patches = {};
|
|
844
|
+
for (const scope of ["global", "cwd", "session"]) {
|
|
845
|
+
if (!Object.hasOwn(params, scope))
|
|
846
|
+
continue;
|
|
847
|
+
const patch = params[scope];
|
|
848
|
+
if (!isObject(patch))
|
|
849
|
+
throw new Error(`patch_state ${scope} must be a semantic patch object`);
|
|
850
|
+
if (Object.keys(patch).length === 0)
|
|
851
|
+
throw new Error(`patch_state ${scope} cannot be empty; omit it when unchanged`);
|
|
852
|
+
patches[scope] = patch;
|
|
853
|
+
}
|
|
854
|
+
const scopes = Object.keys(patches);
|
|
855
|
+
if (scopes.length === 0) {
|
|
856
|
+
if (params.final === false) {
|
|
857
|
+
return { content: [{ type: "text", text: "\nState unchanged; iteration remains non-terminal." }], details: { final: false } };
|
|
858
|
+
}
|
|
859
|
+
if (params.final !== true)
|
|
860
|
+
throw new Error('patch_state requires at least one scope patch or {"final":true}');
|
|
861
|
+
validateFinalEligibility(scopeStates, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
862
|
+
terminalEligible = true;
|
|
863
|
+
return { content: [{ type: "text", text: "\nState iteration is terminal-eligible." }], details: { final: true } };
|
|
864
|
+
}
|
|
865
|
+
const stage = stageAtomicScopePatches(scopeStates, patches, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
866
|
+
const semanticChange = ["global", "cwd", "session"].some((scope) => !sameJson(scopeStates[scope], stage.nextStates[scope]));
|
|
867
|
+
const provenanceChange = Object.values(stage.provenanceUpdates).some((updates) => Object.keys(updates).length > 0);
|
|
868
|
+
if (!semanticChange && !provenanceChange)
|
|
869
|
+
throw new Error('patch_state scope patches must materially update state or required provenance; omit them and use {"final":true} when unchanged');
|
|
870
|
+
commitStage(stage, ctx, false);
|
|
871
|
+
if (params.final === true) {
|
|
872
|
+
terminalEligible = true;
|
|
873
|
+
}
|
|
874
|
+
updateUi(ctx);
|
|
875
|
+
const publication = pendingPublication === undefined ? "" : "; durable publication pending";
|
|
876
|
+
return { content: [{ type: "text", text: `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}${publication}.` }], details: { scopes, final: params.final === true, step: snapshot.meta.step } };
|
|
877
|
+
}
|
|
878
|
+
catch (error) {
|
|
879
|
+
let attempted;
|
|
880
|
+
try {
|
|
881
|
+
attempted = structuredClone(params);
|
|
882
|
+
}
|
|
883
|
+
catch {
|
|
884
|
+
attempted = undefined;
|
|
885
|
+
}
|
|
886
|
+
recordDiagnostic(error instanceof Error ? error.message : String(error), /concurrently|advanced/.test(String(error)) ? "publication-conflict" : "invalid-patch", ctx, {
|
|
887
|
+
input: attempted,
|
|
888
|
+
tool: PATCH_STATE_TOOL_NAME,
|
|
889
|
+
toolCallId,
|
|
890
|
+
resolutionAttempt: fallbackAttempts,
|
|
891
|
+
terminalEligible,
|
|
892
|
+
});
|
|
893
|
+
throw separatedFailure(error);
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
});
|
|
897
|
+
function startStateFlow(ctx) {
|
|
898
|
+
if (!activeContext)
|
|
899
|
+
restoreActiveBranch(ctx);
|
|
900
|
+
telegramStartPending = false;
|
|
901
|
+
const previousSnapshot = structuredClone(snapshot);
|
|
902
|
+
const previousPassiveContinuation = passiveContinuation;
|
|
903
|
+
const previousBootstrapContinuation = bootstrapContinuation;
|
|
904
|
+
const previousArtifactRefreshPending = artifactRefreshPending;
|
|
905
|
+
const previousArtifactInvalidations = structuredClone(artifactInvalidations);
|
|
906
|
+
try {
|
|
907
|
+
if (!runtime?.view && !snapshot.meta.durableBase && !branchStartsWithoutRuntime) {
|
|
908
|
+
throw new Error("Selected branch revision is unavailable; restore its original Git history before starting State Flow");
|
|
909
|
+
}
|
|
910
|
+
const branch = ctx.sessionManager.getBranch();
|
|
911
|
+
activeContext = ctx;
|
|
912
|
+
runtime ??= createRuntime(ctx);
|
|
913
|
+
if (branchStartsWithoutRuntime)
|
|
914
|
+
runtime.prepare();
|
|
915
|
+
runtime.migrateLegacyStorage();
|
|
916
|
+
const bootstrap = (!branchHasSnapshot || !snapshot.config.enabled)
|
|
917
|
+
&& (hasPriorConversation(branch) || previousPassiveContinuation !== undefined);
|
|
918
|
+
if (!runtime.view && snapshot.meta.durableBase) {
|
|
919
|
+
snapshot = prepareBranchRestore(ctx, snapshot.meta.durableBase, snapshot).restore();
|
|
920
|
+
setPendingPublication(snapshot.meta.pendingPublication);
|
|
921
|
+
branchHasSnapshot = true;
|
|
922
|
+
}
|
|
923
|
+
const existingBranch = branchHasSnapshot;
|
|
924
|
+
snapshot = existingBranch
|
|
925
|
+
? resumeEpisode(snapshot, bootstrap)
|
|
926
|
+
: startEpisode(bootstrap);
|
|
927
|
+
if (snapshot.meta.remotePublication === undefined) {
|
|
928
|
+
snapshot.meta.remotePublication = serializeRemotePublicationPolicyDocument(resolveRemotePublicationPolicy(existingBranch ? undefined : config.remotePublication, { legacyRuntime: existingBranch }));
|
|
929
|
+
}
|
|
930
|
+
branchHasSnapshot = true;
|
|
931
|
+
const publication = runtime.view
|
|
932
|
+
? runtime.promote(snapshot) ?? runtime.publish(snapshot)
|
|
933
|
+
: runtime.initialize(snapshot, true, undefined, branchStartsWithoutRuntime);
|
|
934
|
+
recordPolicyPublication(publication, ctx);
|
|
935
|
+
installScopeStates();
|
|
936
|
+
clearRunTransient();
|
|
937
|
+
passiveContinuation = undefined;
|
|
938
|
+
bootstrapContinuation = snapshot.meta.bootstrap
|
|
939
|
+
? previousPassiveContinuation ?? previousBootstrapContinuation
|
|
940
|
+
: undefined;
|
|
941
|
+
deferArtifactRefresh();
|
|
942
|
+
syncStateFlowTools();
|
|
943
|
+
persist();
|
|
944
|
+
updateUi(ctx);
|
|
945
|
+
ctx.ui.notify(snapshot.meta.bootstrap
|
|
946
|
+
? "State Flow enabled. The next complete agent run will migrate active context into state."
|
|
947
|
+
: "State Flow enabled. The next prompt starts a stateful agent run.", "info");
|
|
948
|
+
return { ok: true, message: "State Flow enabled" };
|
|
949
|
+
}
|
|
950
|
+
catch (error) {
|
|
951
|
+
snapshot = previousSnapshot;
|
|
952
|
+
passiveContinuation = previousPassiveContinuation;
|
|
953
|
+
bootstrapContinuation = previousBootstrapContinuation;
|
|
954
|
+
artifactRefreshPending = previousArtifactRefreshPending;
|
|
955
|
+
artifactInvalidations = previousArtifactInvalidations;
|
|
956
|
+
artifactReads.setCandidates(artifactInvalidations);
|
|
957
|
+
syncStateFlowTools();
|
|
958
|
+
const message = `State Flow could not initialize CWD state: ${error instanceof Error ? error.message : String(error)}`;
|
|
959
|
+
ctx.ui.notify(message, "error");
|
|
960
|
+
return { ok: false, message };
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
pi.registerCommand("state-flow-start", {
|
|
964
|
+
description: "Start State Flow mode",
|
|
965
|
+
handler: async (_args, ctx) => {
|
|
966
|
+
startStateFlow(ctx);
|
|
967
|
+
},
|
|
968
|
+
});
|
|
969
|
+
pi.registerCommand("state-flow-status", {
|
|
970
|
+
description: "Show State Flow runtime status",
|
|
971
|
+
handler: async (_args, ctx) => {
|
|
972
|
+
ctx.ui.notify(detailedStatus(snapshot, statusDiagnostics(ctx)), "info");
|
|
973
|
+
},
|
|
974
|
+
});
|
|
975
|
+
function stopStateFlow(ctx) {
|
|
976
|
+
if (!activeContext)
|
|
977
|
+
restoreActiveBranch(ctx);
|
|
978
|
+
telegramStartPending = false;
|
|
979
|
+
let selected = runtime;
|
|
980
|
+
let current = snapshot;
|
|
981
|
+
if (!selected?.view && snapshot.meta.durableBase) {
|
|
982
|
+
selected = createRuntime(ctx);
|
|
983
|
+
current = selected.restore(snapshot.meta.durableBase, snapshot);
|
|
984
|
+
}
|
|
985
|
+
const stoppedAt = Date.now();
|
|
986
|
+
const exitStates = selected?.view ? selected.states() : undefined;
|
|
987
|
+
const exitHandoff = current.config.enabled && exitStates
|
|
988
|
+
? createPassiveContinuation(projectStateForModel(overlayStates(exitStates.global, exitStates.cwd, exitStates.session)), stoppedAt, ctx.isIdle() ? undefined : runAnchorTimestamp)
|
|
989
|
+
: undefined;
|
|
990
|
+
const retainedHandoff = exitHandoff ?? (!current.config.enabled ? passiveContinuation : undefined);
|
|
991
|
+
const stopped = stopEpisode(current);
|
|
992
|
+
const publication = selected?.view ? selected.publish(stopped) : undefined;
|
|
993
|
+
if (selected !== runtime) {
|
|
994
|
+
runtime = selected;
|
|
995
|
+
installScopeStates();
|
|
996
|
+
}
|
|
997
|
+
snapshot = stopped;
|
|
998
|
+
recordPolicyPublication(publication, ctx);
|
|
999
|
+
branchHasSnapshot = true;
|
|
1000
|
+
clearRunTransient();
|
|
1001
|
+
passiveContinuation = retainedHandoff;
|
|
1002
|
+
bootstrapContinuation = undefined;
|
|
1003
|
+
artifactInvalidations = [];
|
|
1004
|
+
artifactReads.setCandidates([]);
|
|
1005
|
+
artifactRefreshPending = false;
|
|
1006
|
+
if (exitHandoff)
|
|
1007
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1008
|
+
at: stoppedAt,
|
|
1009
|
+
...(exitHandoff.activeRunStartedAt === undefined ? {} : { from: exitHandoff.activeRunStartedAt }),
|
|
1010
|
+
});
|
|
1011
|
+
syncStateFlowTools();
|
|
1012
|
+
persist();
|
|
1013
|
+
updateUi(ctx);
|
|
1014
|
+
return { ok: true, message: "State Flow disabled" };
|
|
1015
|
+
}
|
|
1016
|
+
pi.registerCommand("state-flow-stop", {
|
|
1017
|
+
description: "Stop State Flow on the current session branch",
|
|
1018
|
+
handler: async (_args, ctx) => {
|
|
1019
|
+
stopStateFlow(ctx);
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
const telegram = createStateFlowTelegramAdapter({
|
|
1023
|
+
...(options.telegram?.load === undefined ? {} : { load: options.telegram.load }),
|
|
1024
|
+
port: {
|
|
1025
|
+
snapshot: () => ({
|
|
1026
|
+
enabled: snapshot.config.enabled,
|
|
1027
|
+
step: snapshot.meta.step,
|
|
1028
|
+
bootstrap: snapshot.meta.bootstrap === true,
|
|
1029
|
+
startPending: telegramStartPending,
|
|
1030
|
+
}),
|
|
1031
|
+
state: (scope) => projectStateForModel(scope === "effective"
|
|
1032
|
+
? overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)
|
|
1033
|
+
: scopeStates[scope]),
|
|
1034
|
+
canStartNow: () => activeContext === undefined || activeContext.isIdle(),
|
|
1035
|
+
start: () => {
|
|
1036
|
+
if (!activeContext)
|
|
1037
|
+
throw new Error("State Flow is not attached to an active session yet");
|
|
1038
|
+
return startStateFlow(activeContext);
|
|
1039
|
+
},
|
|
1040
|
+
stop: () => {
|
|
1041
|
+
if (!activeContext)
|
|
1042
|
+
throw new Error("State Flow is not attached to an active session yet");
|
|
1043
|
+
try {
|
|
1044
|
+
return stopStateFlow(activeContext);
|
|
1045
|
+
}
|
|
1046
|
+
catch (error) {
|
|
1047
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
deferStart: () => {
|
|
1051
|
+
telegramStartPending = true;
|
|
1052
|
+
},
|
|
1053
|
+
cancelStart: () => {
|
|
1054
|
+
telegramStartPending = false;
|
|
1055
|
+
},
|
|
1056
|
+
},
|
|
1057
|
+
});
|
|
1058
|
+
void telegram.ensure();
|
|
1059
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
1060
|
+
if (!snapshot.config.enabled)
|
|
1061
|
+
return;
|
|
1062
|
+
skillReads.clear();
|
|
1063
|
+
artifactReads.clear();
|
|
1064
|
+
if (artifactRefreshPending)
|
|
1065
|
+
refreshArtifactInvalidations(ctx);
|
|
1066
|
+
terminalEligible = false;
|
|
1067
|
+
resolutionPending = false;
|
|
1068
|
+
fallbackAttempts = 0;
|
|
1069
|
+
fallbackFailureReported = false;
|
|
1070
|
+
responseAwaitingReconciliation = false;
|
|
1071
|
+
completedRunAccepted = false;
|
|
1072
|
+
const rotatesRun = snapshot.meta.specification !== undefined;
|
|
1073
|
+
if (rotatesRun && rehydrationPhase !== "new-bootstrap" && rehydrationPhase !== "resume-bootstrap")
|
|
1074
|
+
rehydrationPhase = "step";
|
|
1075
|
+
if (prepareRun(snapshot, event.prompt)) {
|
|
1076
|
+
runAnchorTimestamp = undefined;
|
|
1077
|
+
persist();
|
|
1078
|
+
}
|
|
1079
|
+
return {
|
|
1080
|
+
systemPrompt: `${event.systemPrompt}\n\n${stateFlowProtocol(snapshot.meta.bootstrap === true)}`,
|
|
1081
|
+
};
|
|
1082
|
+
});
|
|
1083
|
+
pi.on("context", (event) => {
|
|
1084
|
+
if (passiveContinuation) {
|
|
1085
|
+
return { messages: passiveContinuationMessages(event.messages, passiveContinuation) };
|
|
1086
|
+
}
|
|
1087
|
+
if (!snapshot.config.enabled || snapshot.meta.specification === undefined)
|
|
1088
|
+
return;
|
|
1089
|
+
const effectiveState = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
1090
|
+
const invalidations = artifactInvalidations.map(({ path, reason }) => ({ path, reason }));
|
|
1091
|
+
const recentTransitions = projectRecentTransitionsWithLimit(RECENT_TRANSITION_LIMIT, runtime?.recent() ?? []);
|
|
1092
|
+
const activeRehydrationPhase = currentRehydrationPhase();
|
|
1093
|
+
if (snapshot.meta.bootstrap) {
|
|
1094
|
+
const sourceMessages = bootstrapContinuation
|
|
1095
|
+
? passiveContinuationMessages(event.messages, bootstrapContinuation)
|
|
1096
|
+
: event.messages;
|
|
1097
|
+
const messages = withoutPrivateValidation(sourceMessages);
|
|
1098
|
+
return { messages: [runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, resolutionPending), ...messages] };
|
|
1099
|
+
}
|
|
1100
|
+
const trajectory = currentRunTrajectory(event.messages, snapshot.meta.specification, runAnchorTimestamp);
|
|
1101
|
+
runAnchorTimestamp = trajectory.anchorTimestamp;
|
|
1102
|
+
return {
|
|
1103
|
+
messages: [
|
|
1104
|
+
runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, resolutionPending),
|
|
1105
|
+
...trajectory.messages,
|
|
1106
|
+
],
|
|
1107
|
+
};
|
|
1108
|
+
});
|
|
1109
|
+
pi.on("tool_execution_start", (event) => {
|
|
1110
|
+
if (!snapshot.config.enabled)
|
|
1111
|
+
return;
|
|
1112
|
+
// Pi emits this before tool_call. Keep the argument object as a fallback;
|
|
1113
|
+
// tool_call replaces it with the mutable, post-preflight input reference.
|
|
1114
|
+
skillReads.recordStart(event.toolCallId, event.toolName, event.args);
|
|
1115
|
+
artifactReads.recordStart(event.toolCallId, event.toolName, event.args);
|
|
1116
|
+
});
|
|
1117
|
+
pi.on("tool_call", (event, ctx) => {
|
|
1118
|
+
if (!snapshot.config.enabled)
|
|
1119
|
+
return;
|
|
1120
|
+
const batch = assistantToolBatch(ctx, event.toolCallId);
|
|
1121
|
+
const patchCalls = batch?.filter((name) => name === PATCH_STATE_TOOL_NAME).length ?? 0;
|
|
1122
|
+
if (patchCalls > 0) {
|
|
1123
|
+
if (patchCalls !== 1) {
|
|
1124
|
+
return {
|
|
1125
|
+
block: true,
|
|
1126
|
+
reason: "A State Flow barrier response must contain exactly one patch_state call",
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
if (event.toolName !== PATCH_STATE_TOOL_NAME) {
|
|
1130
|
+
return {
|
|
1131
|
+
block: true,
|
|
1132
|
+
reason: "Blocked by the patch_state barrier; reconsider this action after State Flow rematerializes context",
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
skillReads.recordCall(event.toolCallId, event.toolName, event.input);
|
|
1137
|
+
artifactReads.recordCall(event.toolCallId, event.toolName, event.input);
|
|
1138
|
+
});
|
|
1139
|
+
pi.on("tool_execution_end", (event) => {
|
|
1140
|
+
if (!snapshot.config.enabled)
|
|
1141
|
+
return;
|
|
1142
|
+
skillReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
1143
|
+
artifactReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
1144
|
+
});
|
|
1145
|
+
pi.on("message_end", (event, ctx) => {
|
|
1146
|
+
if (!snapshot.config.enabled)
|
|
1147
|
+
return;
|
|
1148
|
+
// Capture the first native user boundary even during bootstrap; steering keeps that anchor.
|
|
1149
|
+
if (event.message.role === "user" && runAnchorTimestamp === undefined)
|
|
1150
|
+
runAnchorTimestamp = event.message.timestamp;
|
|
1151
|
+
if (event.message.role !== "assistant")
|
|
1152
|
+
return;
|
|
1153
|
+
const message = event.message;
|
|
1154
|
+
if (message.stopReason === "aborted" || assistantToolCallCount(message.content) > 0 || message.stopReason === "toolUse") {
|
|
1155
|
+
responseAwaitingReconciliation = false;
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (message.stopReason === "length" || message.stopReason === "error") {
|
|
1159
|
+
responseAwaitingReconciliation = false;
|
|
1160
|
+
recordDiagnostic(`Assistant response ended with ${message.stopReason}`, "finalization", ctx, { content: message.content, terminalEligible });
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (resolutionPending)
|
|
1164
|
+
return resolveFallbackTurn(ctx, message);
|
|
1165
|
+
if (!terminalEligible) {
|
|
1166
|
+
beginFallbackResolution(ctx, message, "the terminal draft ended without State Flow eligibility");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
validateFinalEligibility(scopeStates, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
1171
|
+
}
|
|
1172
|
+
catch (error) {
|
|
1173
|
+
beginFallbackResolution(ctx, message, error instanceof Error ? error.message : String(error));
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
responseAwaitingReconciliation = true;
|
|
1177
|
+
});
|
|
1178
|
+
pi.on("turn_end", (event, ctx) => {
|
|
1179
|
+
const wasBootstrap = snapshot.meta.bootstrap === true;
|
|
1180
|
+
if (!snapshot.config.enabled || !responseAwaitingReconciliation) {
|
|
1181
|
+
updateUi(ctx);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
let responseCommitted = false;
|
|
1185
|
+
try {
|
|
1186
|
+
const response = finalizedAssistantResponse(event.message);
|
|
1187
|
+
const stage = stageScopedTransition(scopeStates, { transitions: [], response }, [], runtime.causalBasis());
|
|
1188
|
+
commitStage(stage, ctx, true);
|
|
1189
|
+
responseCommitted = true;
|
|
1190
|
+
bootstrapContinuation = undefined;
|
|
1191
|
+
rehydrationPhase = "step";
|
|
1192
|
+
enqueueTurnPublication(ctx);
|
|
1193
|
+
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1194
|
+
launchPublicationWorker();
|
|
1195
|
+
completedRunAccepted = !wasBootstrap;
|
|
1196
|
+
}
|
|
1197
|
+
catch (error) {
|
|
1198
|
+
recordDiagnostic(error instanceof Error ? error.message : String(error), "finalization", ctx);
|
|
1199
|
+
ctx.ui.notify(responseCommitted
|
|
1200
|
+
? `State Flow committed the final response; remote publication is deferred: ${error instanceof Error ? error.message : String(error)}`
|
|
1201
|
+
: `State Flow could not reconcile the final response: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1202
|
+
}
|
|
1203
|
+
finally {
|
|
1204
|
+
responseAwaitingReconciliation = false;
|
|
1205
|
+
}
|
|
1206
|
+
updateUi(ctx);
|
|
1207
|
+
});
|
|
1208
|
+
pi.on("session_before_compact", (event) => {
|
|
1209
|
+
if (!compactionPlan)
|
|
1210
|
+
return;
|
|
1211
|
+
if (compactionStopped && event.reason === "manual" && event.customInstructions === compactionMarker)
|
|
1212
|
+
return { cancel: true };
|
|
1213
|
+
const result = stateFlowCompactionResult(compactionPlan, compactionMarker, event);
|
|
1214
|
+
if (result === undefined || "cancel" in result)
|
|
1215
|
+
return result;
|
|
1216
|
+
return { compaction: result };
|
|
1217
|
+
});
|
|
1218
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
1219
|
+
if (telegramStartPending && !snapshot.config.enabled) {
|
|
1220
|
+
telegramStartPending = false;
|
|
1221
|
+
startStateFlow(ctx);
|
|
1222
|
+
}
|
|
1223
|
+
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1224
|
+
launchPublicationWorker();
|
|
1225
|
+
if (!completedRunAccepted || compactionStopped || !snapshot.config.enabled || snapshot.meta.bootstrap || resolutionPending
|
|
1226
|
+
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !snapshot.meta.durableBase
|
|
1227
|
+
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage()))
|
|
1228
|
+
return;
|
|
1229
|
+
completedRunAccepted = false;
|
|
1230
|
+
const entries = ctx.sessionManager.buildContextEntries();
|
|
1231
|
+
if (!hasCompactionSizedTranscript(entries))
|
|
1232
|
+
return;
|
|
1233
|
+
const plan = planStateFlowCompaction(entries, snapshot.meta.durableBase, snapshot.meta.step);
|
|
1234
|
+
if (!plan)
|
|
1235
|
+
return;
|
|
1236
|
+
compactionPlan = plan;
|
|
1237
|
+
compactionInFlight = true;
|
|
1238
|
+
ctx.compact({
|
|
1239
|
+
customInstructions: compactionMarker,
|
|
1240
|
+
onComplete: () => { compactionPlan = undefined; compactionInFlight = false; },
|
|
1241
|
+
onError: () => { compactionPlan = undefined; compactionInFlight = false; },
|
|
1242
|
+
});
|
|
1243
|
+
});
|
|
1244
|
+
pi.on("session_start", (event, ctx) => {
|
|
1245
|
+
rehydrationPhase = event.reason === "resume" ? "resume-bootstrap" : "new-bootstrap";
|
|
1246
|
+
restoreActiveBranch(ctx, event.reason);
|
|
1247
|
+
retryPendingPush(ctx);
|
|
1248
|
+
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1249
|
+
launchPublicationWorker();
|
|
1250
|
+
updateUi(ctx);
|
|
1251
|
+
void telegram.ensure();
|
|
1252
|
+
});
|
|
1253
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
1254
|
+
restoreActiveBranch(ctx);
|
|
1255
|
+
});
|
|
1256
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
1257
|
+
telegramStartPending = false;
|
|
1258
|
+
compactionStopped = true;
|
|
1259
|
+
completedRunAccepted = false;
|
|
1260
|
+
telegram.dispose();
|
|
1261
|
+
return shutdownPublicationWorkers(ctx);
|
|
1262
|
+
});
|
|
1263
|
+
}
|