p-spore 0.1.4 → 0.1.43
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/LICENSE +21 -0
- package/README.md +242 -67
- package/dist/agents/agent-manager.d.ts +3 -3
- package/dist/agents/agent-manager.d.ts.map +1 -1
- package/dist/agents/agent-manager.js +3 -2
- package/dist/agents/agent-manager.js.map +1 -1
- package/dist/application/agent-application.d.ts +29 -5
- package/dist/application/agent-application.d.ts.map +1 -1
- package/dist/application/agent-application.js +444 -14
- package/dist/application/agent-application.js.map +1 -1
- package/dist/articles/diff.d.ts +70 -0
- package/dist/articles/diff.d.ts.map +1 -0
- package/dist/articles/diff.js +330 -0
- package/dist/articles/diff.js.map +1 -0
- package/dist/articles/docx.d.ts +10 -0
- package/dist/articles/docx.d.ts.map +1 -0
- package/dist/articles/docx.js +219 -0
- package/dist/articles/docx.js.map +1 -0
- package/dist/articles/draft.d.ts +18 -0
- package/dist/articles/draft.d.ts.map +1 -0
- package/dist/articles/draft.js +88 -0
- package/dist/articles/draft.js.map +1 -0
- package/dist/articles/edits.d.ts +24 -0
- package/dist/articles/edits.d.ts.map +1 -0
- package/dist/articles/edits.js +54 -0
- package/dist/articles/edits.js.map +1 -0
- package/dist/articles/html.d.ts +10 -0
- package/dist/articles/html.d.ts.map +1 -0
- package/dist/articles/html.js +122 -0
- package/dist/articles/html.js.map +1 -0
- package/dist/articles/plain.d.ts +8 -0
- package/dist/articles/plain.d.ts.map +1 -0
- package/dist/articles/plain.js +69 -0
- package/dist/articles/plain.js.map +1 -0
- package/dist/articles/preview.d.ts +43 -0
- package/dist/articles/preview.d.ts.map +1 -0
- package/dist/articles/preview.js +90 -0
- package/dist/articles/preview.js.map +1 -0
- package/dist/articles/publish.d.ts +10 -0
- package/dist/articles/publish.d.ts.map +1 -0
- package/dist/articles/publish.js +54 -0
- package/dist/articles/publish.js.map +1 -0
- package/dist/articles/receipt.d.ts +16 -0
- package/dist/articles/receipt.d.ts.map +1 -0
- package/dist/articles/receipt.js +27 -0
- package/dist/articles/receipt.js.map +1 -0
- package/dist/articles/store.d.ts +78 -0
- package/dist/articles/store.d.ts.map +1 -0
- package/dist/articles/store.js +308 -0
- package/dist/articles/store.js.map +1 -0
- package/dist/articles/tool.d.ts +14 -0
- package/dist/articles/tool.d.ts.map +1 -0
- package/dist/articles/tool.js +175 -0
- package/dist/articles/tool.js.map +1 -0
- package/dist/articles/writer-context.d.ts +5 -0
- package/dist/articles/writer-context.d.ts.map +1 -0
- package/dist/articles/writer-context.js +56 -0
- package/dist/articles/writer-context.js.map +1 -0
- package/dist/articles/writer.d.ts +18 -0
- package/dist/articles/writer.d.ts.map +1 -0
- package/dist/articles/writer.js +79 -0
- package/dist/articles/writer.js.map +1 -0
- package/dist/articles/zip-write.d.ts +11 -0
- package/dist/articles/zip-write.d.ts.map +1 -0
- package/dist/articles/zip-write.js +63 -0
- package/dist/articles/zip-write.js.map +1 -0
- package/dist/bundled-skills/README.md +17 -0
- package/dist/bundled-skills/aegis-search/SKILL.md +59 -0
- package/dist/bundled-skills/aegis-search/bin/.gitkeep +0 -0
- package/dist/bundled-skills/article-canvas/SKILL.md +28 -0
- package/dist/bundled-skills/binaries.json +23 -0
- package/dist/bundled-skills/doc-extract/SKILL.md +56 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +407 -54
- package/dist/cli/main.js.map +1 -1
- package/dist/config/config.d.ts +43 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +150 -6
- package/dist/config/config.js.map +1 -1
- package/dist/daemon/daemon.d.ts.map +1 -1
- package/dist/daemon/daemon.js +80 -11
- package/dist/daemon/daemon.js.map +1 -1
- package/dist/desktop/application.d.ts +4 -0
- package/dist/desktop/application.d.ts.map +1 -0
- package/dist/desktop/application.js +1688 -0
- package/dist/desktop/application.js.map +1 -0
- package/dist/desktop/article-export.d.ts +12 -0
- package/dist/desktop/article-export.d.ts.map +1 -0
- package/dist/desktop/article-export.js +87 -0
- package/dist/desktop/article-export.js.map +1 -0
- package/dist/desktop/assets/pspore.png +0 -0
- package/dist/desktop/assets/pspore.svg +24 -0
- package/dist/desktop/conversation-smoke.d.ts +5 -0
- package/dist/desktop/conversation-smoke.d.ts.map +1 -0
- package/dist/desktop/conversation-smoke.js +107 -0
- package/dist/desktop/conversation-smoke.js.map +1 -0
- package/dist/desktop/daemon-bridge.d.ts.map +1 -1
- package/dist/desktop/daemon-bridge.js +62 -17
- package/dist/desktop/daemon-bridge.js.map +1 -1
- package/dist/desktop/ipc-contract.d.ts +124 -0
- package/dist/desktop/ipc-contract.d.ts.map +1 -1
- package/dist/desktop/ipc-contract.js +191 -12
- package/dist/desktop/ipc-contract.js.map +1 -1
- package/dist/desktop/local-path.d.ts +15 -0
- package/dist/desktop/local-path.d.ts.map +1 -0
- package/dist/desktop/local-path.js +59 -0
- package/dist/desktop/local-path.js.map +1 -0
- package/dist/desktop/main.d.ts +10 -1
- package/dist/desktop/main.d.ts.map +1 -1
- package/dist/desktop/main.js +122 -530
- package/dist/desktop/main.js.map +1 -1
- package/dist/desktop/native-startup.d.ts +4 -0
- package/dist/desktop/native-startup.d.ts.map +1 -0
- package/dist/desktop/native-startup.js +57 -0
- package/dist/desktop/native-startup.js.map +1 -0
- package/dist/desktop/preload.cjs +28 -1
- package/dist/desktop/renderer/article-panel.d.ts +45 -0
- package/dist/desktop/renderer/article-panel.d.ts.map +1 -0
- package/dist/desktop/renderer/article-panel.js +1126 -0
- package/dist/desktop/renderer/article-panel.js.map +1 -0
- package/dist/desktop/renderer/article-split.d.ts +32 -0
- package/dist/desktop/renderer/article-split.d.ts.map +1 -0
- package/dist/desktop/renderer/article-split.js +48 -0
- package/dist/desktop/renderer/article-split.js.map +1 -0
- package/dist/desktop/renderer/composer-file-references.d.ts +23 -0
- package/dist/desktop/renderer/composer-file-references.d.ts.map +1 -0
- package/dist/desktop/renderer/composer-file-references.js +51 -0
- package/dist/desktop/renderer/composer-file-references.js.map +1 -0
- package/dist/desktop/renderer/conversation.d.ts +23 -0
- package/dist/desktop/renderer/conversation.d.ts.map +1 -0
- package/dist/desktop/renderer/conversation.js +66 -0
- package/dist/desktop/renderer/conversation.js.map +1 -0
- package/dist/desktop/renderer/dom-reconcile.d.ts +15 -0
- package/dist/desktop/renderer/dom-reconcile.d.ts.map +1 -0
- package/dist/desktop/renderer/dom-reconcile.js +33 -0
- package/dist/desktop/renderer/dom-reconcile.js.map +1 -0
- package/dist/desktop/renderer/index.html +211 -34
- package/dist/desktop/renderer/indexing-view.d.ts +22 -0
- package/dist/desktop/renderer/indexing-view.d.ts.map +1 -0
- package/dist/desktop/renderer/indexing-view.js +115 -0
- package/dist/desktop/renderer/indexing-view.js.map +1 -0
- package/dist/desktop/renderer/markdown-view.d.ts +23 -0
- package/dist/desktop/renderer/markdown-view.d.ts.map +1 -0
- package/dist/desktop/renderer/markdown-view.js +436 -0
- package/dist/desktop/renderer/markdown-view.js.map +1 -0
- package/dist/desktop/renderer/motion.d.ts +67 -0
- package/dist/desktop/renderer/motion.d.ts.map +1 -0
- package/dist/desktop/renderer/motion.js +296 -0
- package/dist/desktop/renderer/motion.js.map +1 -0
- package/dist/desktop/renderer/renderer.js +1799 -152
- package/dist/desktop/renderer/renderer.js.map +1 -1
- package/dist/desktop/renderer/session-find.d.ts +18 -0
- package/dist/desktop/renderer/session-find.d.ts.map +1 -0
- package/dist/desktop/renderer/session-find.js +29 -0
- package/dist/desktop/renderer/session-find.js.map +1 -0
- package/dist/desktop/renderer/session-search.d.ts +30 -0
- package/dist/desktop/renderer/session-search.d.ts.map +1 -0
- package/dist/desktop/renderer/session-search.js +296 -0
- package/dist/desktop/renderer/session-search.js.map +1 -0
- package/dist/desktop/renderer/store.d.ts +33 -0
- package/dist/desktop/renderer/store.d.ts.map +1 -1
- package/dist/desktop/renderer/store.js +92 -19
- package/dist/desktop/renderer/store.js.map +1 -1
- package/dist/desktop/renderer/styles.css +928 -268
- package/dist/desktop/renderer/vendor/gsap.min.js +11 -0
- package/dist/desktop/renderer-diagnostics.d.ts +5 -0
- package/dist/desktop/renderer-diagnostics.d.ts.map +1 -0
- package/dist/desktop/renderer-diagnostics.js +47 -0
- package/dist/desktop/renderer-diagnostics.js.map +1 -0
- package/dist/desktop/security.d.ts +26 -1
- package/dist/desktop/security.d.ts.map +1 -1
- package/dist/desktop/security.js +26 -6
- package/dist/desktop/security.js.map +1 -1
- package/dist/desktop/smoke-fixture.d.ts.map +1 -1
- package/dist/desktop/smoke-fixture.js +77 -3
- package/dist/desktop/smoke-fixture.js.map +1 -1
- package/dist/desktop/splash/index.html +25 -0
- package/dist/desktop/splash/splash.d.ts +2 -0
- package/dist/desktop/splash/splash.d.ts.map +1 -0
- package/dist/desktop/splash/splash.js +8 -0
- package/dist/desktop/splash/splash.js.map +1 -0
- package/dist/desktop/splash/styles.css +24 -0
- package/dist/desktop/system-open.d.ts +16 -0
- package/dist/desktop/system-open.d.ts.map +1 -0
- package/dist/desktop/system-open.js +90 -0
- package/dist/desktop/system-open.js.map +1 -0
- package/dist/desktop/validation.d.ts.map +1 -1
- package/dist/desktop/validation.js +99 -21
- package/dist/desktop/validation.js.map +1 -1
- package/dist/desktop/window-security.d.ts +4 -0
- package/dist/desktop/window-security.d.ts.map +1 -0
- package/dist/desktop/window-security.js +26 -0
- package/dist/desktop/window-security.js.map +1 -0
- package/dist/documents/errors.d.ts +12 -0
- package/dist/documents/errors.d.ts.map +1 -0
- package/dist/documents/errors.js +12 -0
- package/dist/documents/errors.js.map +1 -0
- package/dist/documents/extract.d.ts +37 -0
- package/dist/documents/extract.d.ts.map +1 -0
- package/dist/documents/extract.js +90 -0
- package/dist/documents/extract.js.map +1 -0
- package/dist/documents/legacy-office.d.ts +23 -0
- package/dist/documents/legacy-office.d.ts.map +1 -0
- package/dist/documents/legacy-office.js +439 -0
- package/dist/documents/legacy-office.js.map +1 -0
- package/dist/documents/ole.d.ts +20 -0
- package/dist/documents/ole.d.ts.map +1 -0
- package/dist/documents/ole.js +189 -0
- package/dist/documents/ole.js.map +1 -0
- package/dist/documents/ooxml.d.ts +23 -0
- package/dist/documents/ooxml.d.ts.map +1 -0
- package/dist/documents/ooxml.js +197 -0
- package/dist/documents/ooxml.js.map +1 -0
- package/dist/documents/pdf.d.ts +20 -0
- package/dist/documents/pdf.d.ts.map +1 -0
- package/dist/documents/pdf.js +95 -0
- package/dist/documents/pdf.js.map +1 -0
- package/dist/documents/plain-text.d.ts +22 -0
- package/dist/documents/plain-text.d.ts.map +1 -0
- package/dist/documents/plain-text.js +57 -0
- package/dist/documents/plain-text.js.map +1 -0
- package/dist/documents/zip.d.ts +15 -0
- package/dist/documents/zip.d.ts.map +1 -0
- package/dist/documents/zip.js +137 -0
- package/dist/documents/zip.js.map +1 -0
- package/dist/domain/indexing.d.ts +60 -0
- package/dist/domain/indexing.d.ts.map +1 -0
- package/dist/domain/indexing.js +13 -0
- package/dist/domain/indexing.js.map +1 -0
- package/dist/domain/runtime.d.ts +133 -5
- package/dist/domain/runtime.d.ts.map +1 -1
- package/dist/gateway/local-gateway.d.ts +8 -0
- package/dist/gateway/local-gateway.d.ts.map +1 -1
- package/dist/gateway/local-gateway.js +105 -21
- package/dist/gateway/local-gateway.js.map +1 -1
- package/dist/indexing/index-service.d.ts +56 -0
- package/dist/indexing/index-service.d.ts.map +1 -0
- package/dist/indexing/index-service.js +262 -0
- package/dist/indexing/index-service.js.map +1 -0
- package/dist/kernel/loop.d.ts +10 -0
- package/dist/kernel/loop.d.ts.map +1 -1
- package/dist/kernel/loop.js +6 -2
- package/dist/kernel/loop.js.map +1 -1
- package/dist/kernel/reducer.d.ts.map +1 -1
- package/dist/kernel/reducer.js +17 -1
- package/dist/kernel/reducer.js.map +1 -1
- package/dist/kernel/state.d.ts +17 -4
- package/dist/kernel/state.d.ts.map +1 -1
- package/dist/kernel/state.js +2 -0
- package/dist/kernel/state.js.map +1 -1
- package/dist/pi/context-budget.d.ts +8 -0
- package/dist/pi/context-budget.d.ts.map +1 -0
- package/dist/pi/context-budget.js +62 -0
- package/dist/pi/context-budget.js.map +1 -0
- package/dist/pi/pi-runtime-adapter.d.ts +38 -3
- package/dist/pi/pi-runtime-adapter.d.ts.map +1 -1
- package/dist/pi/pi-runtime-adapter.js +551 -56
- package/dist/pi/pi-runtime-adapter.js.map +1 -1
- package/dist/plugins/bundled-skills.d.ts +56 -0
- package/dist/plugins/bundled-skills.d.ts.map +1 -0
- package/dist/plugins/bundled-skills.js +243 -0
- package/dist/plugins/bundled-skills.js.map +1 -0
- package/dist/plugins/extension-manager.d.ts +23 -1
- package/dist/plugins/extension-manager.d.ts.map +1 -1
- package/dist/plugins/extension-manager.js +117 -0
- package/dist/plugins/extension-manager.js.map +1 -1
- package/dist/plugins/types.d.ts +29 -0
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/plugins/workspace-skills.d.ts +36 -0
- package/dist/plugins/workspace-skills.d.ts.map +1 -0
- package/dist/plugins/workspace-skills.js +309 -0
- package/dist/plugins/workspace-skills.js.map +1 -0
- package/dist/policy/policy.d.ts.map +1 -1
- package/dist/policy/policy.js +22 -3
- package/dist/policy/policy.js.map +1 -1
- package/dist/protocol/client.d.ts +1 -0
- package/dist/protocol/client.d.ts.map +1 -1
- package/dist/protocol/client.js +49 -24
- package/dist/protocol/client.js.map +1 -1
- package/dist/protocol/limits.d.ts +69 -0
- package/dist/protocol/limits.d.ts.map +1 -0
- package/dist/protocol/limits.js +166 -0
- package/dist/protocol/limits.js.map +1 -0
- package/dist/protocol/schema.d.ts +6 -0
- package/dist/protocol/schema.d.ts.map +1 -1
- package/dist/protocol/schema.js.map +1 -1
- package/dist/protocol/server-frame-codec.d.ts +18 -0
- package/dist/protocol/server-frame-codec.d.ts.map +1 -0
- package/dist/protocol/server-frame-codec.js +211 -0
- package/dist/protocol/server-frame-codec.js.map +1 -0
- package/dist/service/legacy-daemon.d.ts +6 -0
- package/dist/service/legacy-daemon.d.ts.map +1 -0
- package/dist/service/legacy-daemon.js +42 -0
- package/dist/service/legacy-daemon.js.map +1 -0
- package/dist/service/service-launch.d.ts +10 -0
- package/dist/service/service-launch.d.ts.map +1 -0
- package/dist/service/service-launch.js +56 -0
- package/dist/service/service-launch.js.map +1 -0
- package/dist/service/system-service.d.ts +57 -0
- package/dist/service/system-service.d.ts.map +1 -0
- package/dist/service/system-service.js +263 -0
- package/dist/service/system-service.js.map +1 -0
- package/dist/sessions/session-catalog.d.ts +45 -0
- package/dist/sessions/session-catalog.d.ts.map +1 -0
- package/dist/sessions/session-catalog.js +270 -0
- package/dist/sessions/session-catalog.js.map +1 -0
- package/dist/sessions/session-file.d.ts +12 -0
- package/dist/sessions/session-file.d.ts.map +1 -0
- package/dist/sessions/session-file.js +47 -0
- package/dist/sessions/session-file.js.map +1 -0
- package/dist/settings/compaction-settings.d.ts +10 -0
- package/dist/settings/compaction-settings.d.ts.map +1 -0
- package/dist/settings/compaction-settings.js +8 -0
- package/dist/settings/compaction-settings.js.map +1 -0
- package/dist/settings/indexing-settings-service.d.ts +16 -0
- package/dist/settings/indexing-settings-service.d.ts.map +1 -0
- package/dist/settings/indexing-settings-service.js +57 -0
- package/dist/settings/indexing-settings-service.js.map +1 -0
- package/dist/settings/indexing-settings.d.ts +30 -0
- package/dist/settings/indexing-settings.d.ts.map +1 -0
- package/dist/settings/indexing-settings.js +58 -0
- package/dist/settings/indexing-settings.js.map +1 -0
- package/dist/settings/llm-settings-service.d.ts +6 -0
- package/dist/settings/llm-settings-service.d.ts.map +1 -1
- package/dist/settings/llm-settings-service.js +137 -12
- package/dist/settings/llm-settings-service.js.map +1 -1
- package/dist/settings/llm-settings.d.ts +68 -2
- package/dist/settings/llm-settings.d.ts.map +1 -1
- package/dist/settings/llm-settings.js +119 -17
- package/dist/settings/llm-settings.js.map +1 -1
- package/dist/settings/model-api.d.ts +10 -0
- package/dist/settings/model-api.d.ts.map +1 -0
- package/dist/settings/model-api.js +10 -0
- package/dist/settings/model-api.js.map +1 -0
- package/dist/storage/home.d.ts +1 -0
- package/dist/storage/home.d.ts.map +1 -1
- package/dist/storage/home.js +37 -2
- package/dist/storage/home.js.map +1 -1
- package/dist/tools/core-tools.d.ts +2 -0
- package/dist/tools/core-tools.d.ts.map +1 -1
- package/dist/tools/core-tools.js +91 -1
- package/dist/tools/core-tools.js.map +1 -1
- package/dist/ui/shared/activity.d.ts +2 -0
- package/dist/ui/shared/activity.d.ts.map +1 -1
- package/dist/ui/shared/activity.js +18 -4
- package/dist/ui/shared/activity.js.map +1 -1
- package/dist/ui/shared/full-message.d.ts +22 -0
- package/dist/ui/shared/full-message.d.ts.map +1 -0
- package/dist/ui/shared/full-message.js +40 -0
- package/dist/ui/shared/full-message.js.map +1 -0
- package/dist/ui/shared/local-links.d.ts +7 -0
- package/dist/ui/shared/local-links.d.ts.map +1 -0
- package/dist/ui/shared/local-links.js +24 -0
- package/dist/ui/shared/local-links.js.map +1 -0
- package/dist/ui/shared/markdown-stream.d.ts +25 -0
- package/dist/ui/shared/markdown-stream.d.ts.map +1 -0
- package/dist/ui/shared/markdown-stream.js +35 -0
- package/dist/ui/shared/markdown-stream.js.map +1 -0
- package/dist/ui/shared/markdown.d.ts +70 -0
- package/dist/ui/shared/markdown.d.ts.map +1 -0
- package/dist/ui/shared/markdown.js +434 -0
- package/dist/ui/shared/markdown.js.map +1 -0
- package/dist/ui/shared/presentation.d.ts +10 -2
- package/dist/ui/shared/presentation.d.ts.map +1 -1
- package/dist/ui/shared/presentation.js +83 -22
- package/dist/ui/shared/presentation.js.map +1 -1
- package/dist/ui/shared/protocol-state.d.ts +19 -0
- package/dist/ui/shared/protocol-state.d.ts.map +1 -1
- package/dist/ui/shared/protocol-state.js +188 -26
- package/dist/ui/shared/protocol-state.js.map +1 -1
- package/dist/ui/shared/virtual-list.d.ts +34 -0
- package/dist/ui/shared/virtual-list.d.ts.map +1 -0
- package/dist/ui/shared/virtual-list.js +143 -0
- package/dist/ui/shared/virtual-list.js.map +1 -0
- package/dist/ui/tui/commands.d.ts +1 -1
- package/dist/ui/tui/commands.d.ts.map +1 -1
- package/dist/ui/tui/commands.js +8 -3
- package/dist/ui/tui/commands.js.map +1 -1
- package/dist/ui/tui/store.d.ts +22 -1
- package/dist/ui/tui/store.d.ts.map +1 -1
- package/dist/ui/tui/store.js +50 -3
- package/dist/ui/tui/store.js.map +1 -1
- package/dist/ui/tui/tui-app.d.ts.map +1 -1
- package/dist/ui/tui/tui-app.js +231 -31
- package/dist/ui/tui/tui-app.js.map +1 -1
- package/dist/ui/tui/view.d.ts +7 -0
- package/dist/ui/tui/view.d.ts.map +1 -1
- package/dist/ui/tui/view.js +161 -57
- package/dist/ui/tui/view.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +13 -2
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { mkdir, realpath } from "node:fs/promises";
|
|
1
|
+
import { mkdir, realpath, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createSyntheticSourceInfo, ModelRuntime, SessionManager, SettingsManager, estimateTokens, findCutPoint, getLatestCompactionEntry, sessionEntryToContextMessages, } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { articleDraftFromToolUpdate, articleDraftFromUpdate } from "../articles/draft.js";
|
|
6
|
+
import { ArticlePreviewBus } from "../articles/preview.js";
|
|
7
|
+
import { publishedArticleValue } from "../articles/publish.js";
|
|
8
|
+
import { resolveSessionFile } from "../sessions/session-file.js";
|
|
9
|
+
import { toolInputText } from "../ui/shared/activity.js";
|
|
10
|
+
import { boundToolResult, estimateTextTokens } from "./context-budget.js";
|
|
11
|
+
const CONTEXT_RESUME_MESSAGE = "pspore-context-resume";
|
|
5
12
|
export class PiRuntimeAdapter {
|
|
6
13
|
#agentDir;
|
|
7
14
|
#sessionRoot;
|
|
@@ -9,9 +16,12 @@ export class PiRuntimeAdapter {
|
|
|
9
16
|
#resolveModel;
|
|
10
17
|
#createTools;
|
|
11
18
|
#createSkillPaths;
|
|
19
|
+
#createSkills;
|
|
12
20
|
#toolGate;
|
|
13
21
|
#clock;
|
|
14
22
|
#idFactory;
|
|
23
|
+
#autoCompaction;
|
|
24
|
+
#articlePreviews = new ArticlePreviewBus((draft) => this.#publishArticleDraft(draft), () => this.#clock().getTime());
|
|
15
25
|
#sessions = new Map();
|
|
16
26
|
constructor(options, modelRuntime) {
|
|
17
27
|
this.#agentDir = options.agentDir;
|
|
@@ -22,9 +32,11 @@ export class PiRuntimeAdapter {
|
|
|
22
32
|
((ref, runtime) => (ref ? runtime.getModel(ref.provider, ref.model) : undefined));
|
|
23
33
|
this.#createTools = options.createTools ?? (() => []);
|
|
24
34
|
this.#createSkillPaths = options.createSkillPaths ?? (() => []);
|
|
35
|
+
this.#createSkills = options.createSkills ?? (() => []);
|
|
25
36
|
this.#toolGate = options.toolGate;
|
|
26
37
|
this.#clock = options.clock ?? (() => new Date());
|
|
27
38
|
this.#idFactory = options.idFactory ?? randomUUID;
|
|
39
|
+
this.#autoCompaction = options.autoCompaction ? { ...options.autoCompaction } : undefined;
|
|
28
40
|
}
|
|
29
41
|
static async create(options) {
|
|
30
42
|
await mkdir(options.agentDir, { recursive: true, mode: 0o700 });
|
|
@@ -37,6 +49,9 @@ export class PiRuntimeAdapter {
|
|
|
37
49
|
}));
|
|
38
50
|
return new PiRuntimeAdapter(options, modelRuntime);
|
|
39
51
|
}
|
|
52
|
+
articlePreview(toolCallId) {
|
|
53
|
+
return this.#articlePreviews.get(toolCallId);
|
|
54
|
+
}
|
|
40
55
|
async create(spec) {
|
|
41
56
|
this.#assertNewSession(spec.sessionId);
|
|
42
57
|
const normalized = await this.#normalizeSpec(spec);
|
|
@@ -48,7 +63,7 @@ export class PiRuntimeAdapter {
|
|
|
48
63
|
async resume(spec) {
|
|
49
64
|
this.#assertNewSession(spec.sessionId);
|
|
50
65
|
const normalized = await this.#normalizeSpec(spec);
|
|
51
|
-
const sessionFile = await
|
|
66
|
+
const sessionFile = await resolveSessionFile(spec.sessionFile, { allowMissing: true });
|
|
52
67
|
const sessionDir = join(this.#sessionRoot, normalized.workspaceId);
|
|
53
68
|
await mkdir(sessionDir, { recursive: true, mode: 0o700 });
|
|
54
69
|
const sessionManager = SessionManager.open(sessionFile, sessionDir, normalized.cwd);
|
|
@@ -56,12 +71,14 @@ export class PiRuntimeAdapter {
|
|
|
56
71
|
}
|
|
57
72
|
async prompt(sessionId, input) {
|
|
58
73
|
const record = this.#record(sessionId);
|
|
59
|
-
if (record.runtime.session.isStreaming) {
|
|
74
|
+
if (record.runtime.session.isStreaming || record.activeTask) {
|
|
60
75
|
throw new Error(`Session ${sessionId} is busy; use steer or followUp`);
|
|
61
76
|
}
|
|
62
77
|
if (!input.trim()) {
|
|
63
78
|
throw new Error("Prompt cannot be empty");
|
|
64
79
|
}
|
|
80
|
+
record.abortRequested = false;
|
|
81
|
+
record.lastError = undefined;
|
|
65
82
|
const commandId = this.#idFactory();
|
|
66
83
|
let resolveAccepted;
|
|
67
84
|
let rejectAccepted;
|
|
@@ -69,19 +86,74 @@ export class PiRuntimeAdapter {
|
|
|
69
86
|
resolveAccepted = resolve;
|
|
70
87
|
rejectAccepted = reject;
|
|
71
88
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
// Own the logical task before any asynchronous compaction. Otherwise a
|
|
90
|
+
// restored session blocks prompt acceptance (and the serialized abort
|
|
91
|
+
// command behind it) for the entire summarization request.
|
|
92
|
+
const task = Promise.resolve()
|
|
93
|
+
.then(async () => {
|
|
94
|
+
if (this.#needsThresholdCompaction(record)) {
|
|
95
|
+
this.#startRun(record);
|
|
96
|
+
resolveAccepted({ accepted: true, commandId, acceptedAt: this.#clock().toISOString() });
|
|
97
|
+
}
|
|
98
|
+
await this.#compactAtConfiguredThreshold(record);
|
|
99
|
+
if (record.abortRequested) {
|
|
100
|
+
rejectAccepted(new Error("Prompt cancelled during context compaction"));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await record.runtime.session.prompt(input, {
|
|
104
|
+
source: "rpc",
|
|
105
|
+
preflightResult: (success) => {
|
|
106
|
+
if (success)
|
|
107
|
+
resolveAccepted({ accepted: true, commandId, acceptedAt: this.#clock().toISOString() });
|
|
108
|
+
else
|
|
109
|
+
rejectAccepted(new Error("Pi rejected the prompt during preflight"));
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
while (!record.abortRequested) {
|
|
113
|
+
const resumeTask = record.compactionPending;
|
|
114
|
+
await this.#compactAtConfiguredThreshold(record);
|
|
115
|
+
if (record.abortRequested)
|
|
116
|
+
break;
|
|
117
|
+
await this.#restoreHeldQueue(record);
|
|
118
|
+
record.compactionPending = false;
|
|
119
|
+
if (resumeTask) {
|
|
120
|
+
await record.runtime.session.sendCustomMessage({
|
|
121
|
+
customType: CONTEXT_RESUME_MESSAGE,
|
|
122
|
+
content: "Continue the original user task after context compaction. Use the summary and retained tool results; do not repeat completed actions.",
|
|
123
|
+
display: false,
|
|
124
|
+
}, { triggerTurn: true });
|
|
78
125
|
}
|
|
79
126
|
else {
|
|
80
|
-
|
|
127
|
+
// A message can arrive while the *final answer* is compacting too.
|
|
128
|
+
// Start with that queued user input, rather than replying to the
|
|
129
|
+
// already-completed original request again.
|
|
130
|
+
const session = record.runtime.session;
|
|
131
|
+
if (!session.pendingMessageCount)
|
|
132
|
+
break;
|
|
133
|
+
const queue = session.clearQueue();
|
|
134
|
+
const nextInput = queue.steering.shift() ?? queue.followUp.shift();
|
|
135
|
+
if (nextInput === undefined)
|
|
136
|
+
break;
|
|
137
|
+
for (const message of queue.steering)
|
|
138
|
+
await session.steer(message);
|
|
139
|
+
for (const message of queue.followUp)
|
|
140
|
+
await session.followUp(message);
|
|
141
|
+
await session.prompt(nextInput, { source: "rpc", expandPromptTemplates: false });
|
|
81
142
|
}
|
|
82
|
-
}
|
|
143
|
+
}
|
|
144
|
+
if (record.compactionPending) {
|
|
145
|
+
await this.#restoreHeldQueue(record);
|
|
146
|
+
record.compactionPending = false;
|
|
147
|
+
}
|
|
83
148
|
})
|
|
84
|
-
.catch((error) => {
|
|
149
|
+
.catch(async (error) => {
|
|
150
|
+
await this.#restoreHeldQueue(record);
|
|
151
|
+
if (record.abortRequested) {
|
|
152
|
+
record.compactionPending = false;
|
|
153
|
+
rejectAccepted(new Error("Prompt cancelled during context compaction"));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
record.compactionPending = false;
|
|
85
157
|
const normalizedError = toError(error);
|
|
86
158
|
record.lastError = normalizedError.message;
|
|
87
159
|
this.#emit(record, "runtime.error", { message: normalizedError.message }, false);
|
|
@@ -91,6 +163,9 @@ export class PiRuntimeAdapter {
|
|
|
91
163
|
.finally(() => {
|
|
92
164
|
if (record.activeTask === task) {
|
|
93
165
|
record.activeTask = undefined;
|
|
166
|
+
record.runStarted = false;
|
|
167
|
+
if (!record.lastError)
|
|
168
|
+
this.#emit(record, "run.settled", {}, false);
|
|
94
169
|
}
|
|
95
170
|
});
|
|
96
171
|
record.activeTask = task;
|
|
@@ -99,18 +174,57 @@ export class PiRuntimeAdapter {
|
|
|
99
174
|
}
|
|
100
175
|
async steer(sessionId, input) {
|
|
101
176
|
const record = this.#record(sessionId);
|
|
177
|
+
if (record.compactionPending) {
|
|
178
|
+
record.heldQueue.steering.push(input);
|
|
179
|
+
this.#publishQueue(record);
|
|
180
|
+
return this.#accepted();
|
|
181
|
+
}
|
|
102
182
|
await record.runtime.session.steer(input);
|
|
103
183
|
return this.#accepted();
|
|
104
184
|
}
|
|
105
185
|
async followUp(sessionId, input) {
|
|
106
186
|
const record = this.#record(sessionId);
|
|
187
|
+
if (record.compactionPending) {
|
|
188
|
+
record.heldQueue.followUp.push(input);
|
|
189
|
+
this.#publishQueue(record);
|
|
190
|
+
return this.#accepted();
|
|
191
|
+
}
|
|
107
192
|
await record.runtime.session.followUp(input);
|
|
108
193
|
return this.#accepted();
|
|
109
194
|
}
|
|
195
|
+
async promoteFollowUp(sessionId, index) {
|
|
196
|
+
const record = this.#record(sessionId);
|
|
197
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= record.queue.followUp.length) {
|
|
198
|
+
throw new Error(`Follow-up queue index is out of range: ${index}`);
|
|
199
|
+
}
|
|
200
|
+
const selected = record.queue.followUp[index];
|
|
201
|
+
if (selected === undefined)
|
|
202
|
+
throw new Error(`Follow-up queue entry is unavailable: ${index}`);
|
|
203
|
+
if (record.compactionPending) {
|
|
204
|
+
record.heldQueue.followUp.splice(index, 1);
|
|
205
|
+
record.heldQueue.steering.push(selected);
|
|
206
|
+
this.#publishQueue(record);
|
|
207
|
+
return this.#accepted();
|
|
208
|
+
}
|
|
209
|
+
const queued = record.runtime.session.clearQueue();
|
|
210
|
+
for (const message of queued.steering)
|
|
211
|
+
await record.runtime.session.steer(message);
|
|
212
|
+
await record.runtime.session.steer(selected);
|
|
213
|
+
for (const [queuedIndex, message] of queued.followUp.entries()) {
|
|
214
|
+
if (queuedIndex !== index)
|
|
215
|
+
await record.runtime.session.followUp(message);
|
|
216
|
+
}
|
|
217
|
+
return this.#accepted();
|
|
218
|
+
}
|
|
110
219
|
async abort(sessionId, _reason) {
|
|
111
|
-
|
|
220
|
+
const record = this.#record(sessionId);
|
|
221
|
+
record.abortRequested = true;
|
|
222
|
+
record.runtime.session.abortCompaction();
|
|
223
|
+
await record.runtime.session.abort();
|
|
224
|
+
await record.activeTask?.catch(() => undefined);
|
|
112
225
|
}
|
|
113
226
|
async compact(sessionId, instructions) {
|
|
227
|
+
await this.abort(sessionId, "manual compaction");
|
|
114
228
|
const session = this.#record(sessionId).runtime.session;
|
|
115
229
|
if (instructions === undefined) {
|
|
116
230
|
await session.compact();
|
|
@@ -126,23 +240,21 @@ export class PiRuntimeAdapter {
|
|
|
126
240
|
const model = session.model;
|
|
127
241
|
const sessionFile = session.sessionFile;
|
|
128
242
|
const lastError = record.lastError;
|
|
243
|
+
// Counting entries walks the branch but renders no text, so session state
|
|
244
|
+
// stays cheap no matter how long the conversation has grown.
|
|
245
|
+
const entries = this.#branchEntries(record);
|
|
246
|
+
const firstUserText = this.#firstUserText(record, entries);
|
|
129
247
|
return {
|
|
130
248
|
sessionId,
|
|
131
249
|
upstreamSessionId: session.sessionId,
|
|
132
250
|
...(sessionFile ? { sessionFile } : {}),
|
|
133
251
|
cwd: record.runtime.cwd,
|
|
134
|
-
status: lastError ? "failed" : session.isStreaming ? "running" : "idle",
|
|
252
|
+
status: lastError ? "failed" : session.isStreaming || record.activeTask || record.thresholdCompaction ? "running" : "idle",
|
|
135
253
|
...(model ? { model: { provider: model.provider, model: model.id } } : {}),
|
|
136
254
|
thinking: session.thinkingLevel,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.filter((entry) => entry.type === "message")
|
|
141
|
-
.map((entry) => ({
|
|
142
|
-
id: entry.id,
|
|
143
|
-
role: entry.message.role,
|
|
144
|
-
text: messageText(entry.message),
|
|
145
|
-
})),
|
|
255
|
+
...(session.model ? { context: this.#contextUsage(record) } : {}),
|
|
256
|
+
messageCount: entries.length,
|
|
257
|
+
...(firstUserText === undefined ? {} : { firstUserText }),
|
|
146
258
|
queue: {
|
|
147
259
|
steering: [...record.queue.steering],
|
|
148
260
|
followUp: [...record.queue.followUp],
|
|
@@ -187,6 +299,72 @@ export class PiRuntimeAdapter {
|
|
|
187
299
|
}
|
|
188
300
|
return this.#handle(record);
|
|
189
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* A slice of the persisted branch, which keeps entries a compaction has
|
|
304
|
+
* already dropped from the model's context. Only the requested slice is
|
|
305
|
+
* rendered to text: doing that for the whole branch is what made every
|
|
306
|
+
* streamed token cost as much as the conversation was long.
|
|
307
|
+
*/
|
|
308
|
+
transcript(sessionId, options) {
|
|
309
|
+
const record = this.#record(sessionId);
|
|
310
|
+
const entries = this.#branchEntries(record);
|
|
311
|
+
const limit = Math.max(0, options.limit);
|
|
312
|
+
const from = Math.min(Math.max(0, options.from ?? Math.max(0, entries.length - limit)), entries.length);
|
|
313
|
+
return {
|
|
314
|
+
from,
|
|
315
|
+
count: entries.length,
|
|
316
|
+
messages: entries.slice(from, from + limit).map(toBranchMessage),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* One message's full text, sliced. The transcript on the wire is bounded per
|
|
321
|
+
* string, so a message longer than that ceiling arrives cut; this is how a
|
|
322
|
+
* client asks for the rest of it without widening the ceiling for everything.
|
|
323
|
+
*/
|
|
324
|
+
transcriptMessage(sessionId, options) {
|
|
325
|
+
const record = this.#record(sessionId);
|
|
326
|
+
const entries = this.#branchEntries(record);
|
|
327
|
+
const index = options.id === undefined
|
|
328
|
+
? options.index ?? -1
|
|
329
|
+
: entries.findIndex((entry) => entry.id === options.id);
|
|
330
|
+
const entry = index >= 0 ? entries[index] : undefined;
|
|
331
|
+
if (!entry) {
|
|
332
|
+
throw new Error(`Transcript message is unavailable: ${options.id ?? String(options.index)}`);
|
|
333
|
+
}
|
|
334
|
+
const message = toBranchMessage(entry);
|
|
335
|
+
const offset = Math.min(Math.max(0, options.offset ?? 0), message.text.length);
|
|
336
|
+
return {
|
|
337
|
+
...(message.id === undefined ? {} : { id: message.id }),
|
|
338
|
+
index,
|
|
339
|
+
role: message.role,
|
|
340
|
+
offset,
|
|
341
|
+
length: message.text.length,
|
|
342
|
+
text: message.text.slice(offset, offset + Math.max(0, options.limit)),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/** The persisted branch's message entries, unrendered. */
|
|
346
|
+
branch(sessionId) {
|
|
347
|
+
return this.#branchEntries(this.#record(sessionId)).map(toBranchMessage);
|
|
348
|
+
}
|
|
349
|
+
#branchEntries(record) {
|
|
350
|
+
return record.runtime.session.sessionManager
|
|
351
|
+
.getBranch()
|
|
352
|
+
.filter((entry) => entry.type === "message");
|
|
353
|
+
}
|
|
354
|
+
#firstUserText(record, entries) {
|
|
355
|
+
if (record.firstUserText !== undefined)
|
|
356
|
+
return record.firstUserText || undefined;
|
|
357
|
+
const first = entries.find((entry) => asRecord(entry.message)["role"] === "user");
|
|
358
|
+
// Nothing is cached until a user message exists: a session snapshotted at
|
|
359
|
+
// creation has none yet, and caching that absence would leave it untitled
|
|
360
|
+
// for the rest of its life.
|
|
361
|
+
if (!first)
|
|
362
|
+
return undefined;
|
|
363
|
+
// Only ever used as a session title, so it never needs the whole message.
|
|
364
|
+
const text = messageText(first.message).trim().slice(0, 200);
|
|
365
|
+
record.firstUserText = text;
|
|
366
|
+
return text || undefined;
|
|
367
|
+
}
|
|
190
368
|
async fork(sessionId, entryId, position = "at") {
|
|
191
369
|
const record = this.#record(sessionId);
|
|
192
370
|
const result = await record.runtime.fork(entryId, { position });
|
|
@@ -197,6 +375,8 @@ export class PiRuntimeAdapter {
|
|
|
197
375
|
}
|
|
198
376
|
async dispose(sessionId) {
|
|
199
377
|
const record = this.#record(sessionId);
|
|
378
|
+
await this.abort(sessionId, "session disposed");
|
|
379
|
+
this.#articlePreviews.clearSession(sessionId);
|
|
200
380
|
record.unsubscribe?.();
|
|
201
381
|
record.listeners.clear();
|
|
202
382
|
await record.runtime.dispose();
|
|
@@ -212,6 +392,7 @@ export class PiRuntimeAdapter {
|
|
|
212
392
|
agentDir: this.#agentDir,
|
|
213
393
|
sessionManager,
|
|
214
394
|
});
|
|
395
|
+
await this.#materializeSessionManager(runtime.session.sessionManager);
|
|
215
396
|
const record = {
|
|
216
397
|
spec,
|
|
217
398
|
runtime,
|
|
@@ -219,22 +400,61 @@ export class PiRuntimeAdapter {
|
|
|
219
400
|
listeners: new Set(),
|
|
220
401
|
activeTask: undefined,
|
|
221
402
|
queue: { steering: [], followUp: [] },
|
|
403
|
+
thresholdCompaction: false,
|
|
404
|
+
compactionPending: false,
|
|
405
|
+
runStarted: false,
|
|
406
|
+
abortRequested: false,
|
|
407
|
+
heldQueue: { steering: [], followUp: [] },
|
|
222
408
|
lastError: undefined,
|
|
409
|
+
streaming: undefined,
|
|
410
|
+
itemSequence: 0,
|
|
411
|
+
firstUserText: undefined,
|
|
223
412
|
};
|
|
224
413
|
this.#sessions.set(spec.sessionId, record);
|
|
225
414
|
this.#bind(record, runtime.session, false);
|
|
226
|
-
runtime.setRebindSession(async (session) =>
|
|
415
|
+
runtime.setRebindSession(async (session) => {
|
|
416
|
+
await this.#materializeSessionManager(session.sessionManager);
|
|
417
|
+
this.#bind(record, session, true);
|
|
418
|
+
});
|
|
227
419
|
return this.#handle(record);
|
|
228
420
|
}
|
|
421
|
+
async #materializeSessionManager(sessionManager) {
|
|
422
|
+
const sessionFile = sessionManager.getSessionFile();
|
|
423
|
+
if (!sessionFile)
|
|
424
|
+
return;
|
|
425
|
+
const contents = [sessionManager.getHeader(), ...sessionManager.getEntries()]
|
|
426
|
+
.map((entry) => JSON.stringify(entry))
|
|
427
|
+
.join("\n") + "\n";
|
|
428
|
+
try {
|
|
429
|
+
await writeFile(sessionFile, contents, { flag: "wx", mode: 0o600 });
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (!isNodeError(error) || error.code !== "EEXIST")
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
sessionManager.setSessionFile(await resolveSessionFile(sessionFile));
|
|
436
|
+
}
|
|
229
437
|
#runtimeFactory(spec) {
|
|
230
438
|
return async ({ cwd, agentDir, sessionManager, sessionStartEvent }) => {
|
|
231
439
|
const policyExtension = this.#policyExtension(spec);
|
|
232
440
|
const contextFiles = spec.contextFiles ? [...spec.contextFiles] : [];
|
|
233
441
|
const skillPaths = [...await this.#createSkillPaths(spec.sessionId)].sort();
|
|
442
|
+
const resolvedSkills = [...await this.#createSkills(spec.sessionId)];
|
|
234
443
|
const extensionFactories = policyExtension ? [policyExtension] : [];
|
|
235
444
|
const services = await createAgentSessionServices({
|
|
236
445
|
cwd,
|
|
237
446
|
agentDir,
|
|
447
|
+
...(this.#autoCompaction
|
|
448
|
+
? {
|
|
449
|
+
settingsManager: SettingsManager.inMemory({
|
|
450
|
+
compaction: {
|
|
451
|
+
enabled: this.#autoCompaction.enabled,
|
|
452
|
+
reserveTokens: this.#autoCompaction.reserveTokens,
|
|
453
|
+
keepRecentTokens: this.#autoCompaction.keepRecentTokens,
|
|
454
|
+
},
|
|
455
|
+
}),
|
|
456
|
+
}
|
|
457
|
+
: {}),
|
|
238
458
|
modelRuntime: this.#modelRuntime,
|
|
239
459
|
resourceLoaderOptions: {
|
|
240
460
|
noExtensions: true,
|
|
@@ -243,6 +463,9 @@ export class PiRuntimeAdapter {
|
|
|
243
463
|
noThemes: true,
|
|
244
464
|
noContextFiles: true,
|
|
245
465
|
...(skillPaths.length > 0 ? { additionalSkillPaths: skillPaths } : {}),
|
|
466
|
+
...(resolvedSkills.length > 0
|
|
467
|
+
? { skillsOverride: (base) => withResolvedSkills(base, resolvedSkills) }
|
|
468
|
+
: {}),
|
|
246
469
|
...(spec.systemPrompt ? { systemPrompt: spec.systemPrompt } : {}),
|
|
247
470
|
...(extensionFactories.length > 0 ? { extensionFactories } : {}),
|
|
248
471
|
...(contextFiles.length > 0
|
|
@@ -258,7 +481,13 @@ export class PiRuntimeAdapter {
|
|
|
258
481
|
if (spec.model && !model) {
|
|
259
482
|
throw new Error(`Configured model is unavailable: ${spec.model.provider}/${spec.model.model}`);
|
|
260
483
|
}
|
|
261
|
-
const customTools =
|
|
484
|
+
const customTools = this.#createTools(spec.sessionId, this.#modelRuntime).map((tool) => ({
|
|
485
|
+
...tool,
|
|
486
|
+
execute: async (...args) => {
|
|
487
|
+
const result = await tool.execute(...args);
|
|
488
|
+
return { ...result, content: await boundToolResult(result.content, join(this.#sessionRoot, "artifacts", "tool-results")) };
|
|
489
|
+
},
|
|
490
|
+
}));
|
|
262
491
|
const enabledTools = spec.enabledTools ? [...spec.enabledTools] : customTools.map((tool) => tool.name);
|
|
263
492
|
const result = await createAgentSessionFromServices({
|
|
264
493
|
services,
|
|
@@ -321,6 +550,25 @@ export class PiRuntimeAdapter {
|
|
|
321
550
|
record.upstreamSessionId = session.sessionId;
|
|
322
551
|
record.lastError = undefined;
|
|
323
552
|
record.queue = { steering: [], followUp: [] };
|
|
553
|
+
record.streaming = undefined;
|
|
554
|
+
record.firstUserText = undefined;
|
|
555
|
+
const previousStop = session.agent.shouldStopAfterTurn;
|
|
556
|
+
session.agent.shouldStopAfterTurn = async (turn, signal) => {
|
|
557
|
+
if (await previousStop?.(turn, signal))
|
|
558
|
+
return true;
|
|
559
|
+
if (signal?.aborted || record.abortRequested || !this.#autoCompaction?.enabled)
|
|
560
|
+
return false;
|
|
561
|
+
if (turn.toolResults.length === 0 && session.pendingMessageCount === 0)
|
|
562
|
+
return false;
|
|
563
|
+
if (this.#contextUsage(record).tokens < this.#compactionThreshold(record))
|
|
564
|
+
return false;
|
|
565
|
+
record.compactionPending = true;
|
|
566
|
+
// Pi drains queues at agent_end, so hold them outside the SDK until the
|
|
567
|
+
// context has been compacted. New user messages join this same queue.
|
|
568
|
+
record.heldQueue = { steering: [...session.getSteeringMessages()], followUp: [...session.getFollowUpMessages()] };
|
|
569
|
+
session.clearQueue();
|
|
570
|
+
return true;
|
|
571
|
+
};
|
|
324
572
|
record.unsubscribe = session.subscribe((event) => this.#onPiEvent(record, event));
|
|
325
573
|
if (replacement) {
|
|
326
574
|
this.#emit(record, "session.replaced", { previousUpstreamSessionId, upstreamSessionId: session.sessionId }, false);
|
|
@@ -329,50 +577,88 @@ export class PiRuntimeAdapter {
|
|
|
329
577
|
#onPiEvent(record, event) {
|
|
330
578
|
switch (event.type) {
|
|
331
579
|
case "agent_start":
|
|
332
|
-
this.#
|
|
580
|
+
this.#startRun(record);
|
|
333
581
|
break;
|
|
334
582
|
case "agent_settled":
|
|
335
|
-
this.#
|
|
583
|
+
this.#articlePreviews.clearSession(record.spec.sessionId);
|
|
584
|
+
// The adapter's logical task also includes automatic compaction and
|
|
585
|
+
// continuation. Publish settled only when that entire task finishes.
|
|
586
|
+
if (!record.activeTask)
|
|
587
|
+
this.#emit(record, "run.settled", {}, false);
|
|
336
588
|
break;
|
|
337
|
-
case "message_start":
|
|
338
|
-
|
|
589
|
+
case "message_start": {
|
|
590
|
+
if (asRecord(event.message)["customType"] === CONTEXT_RESUME_MESSAGE)
|
|
591
|
+
break;
|
|
592
|
+
const streaming = this.#beginItem(record, event.message);
|
|
593
|
+
this.#emit(record, "transcript.item.started", { item: this.#startedItem(streaming) }, false);
|
|
339
594
|
break;
|
|
595
|
+
}
|
|
340
596
|
case "message_update": {
|
|
341
597
|
const update = event.assistantMessageEvent;
|
|
342
|
-
if (update.type === "
|
|
343
|
-
this.#
|
|
344
|
-
}
|
|
345
|
-
else if (update.type === "thinking_delta") {
|
|
346
|
-
this.#emit(record, "message.thinking.delta", { delta: update.delta }, true);
|
|
598
|
+
if (update.type === "toolcall_delta" || update.type === "toolcall_end") {
|
|
599
|
+
this.#noteArticleDraft(record, event.message, update);
|
|
347
600
|
}
|
|
601
|
+
if (update.type !== "text_delta" && update.type !== "thinking_delta")
|
|
602
|
+
break;
|
|
603
|
+
const kind = update.type === "text_delta" ? "text" : "thinking";
|
|
604
|
+
// A delta with no started item would have nowhere to land; opening one
|
|
605
|
+
// keeps the stream addressable even if Pi skips the start event.
|
|
606
|
+
const streaming = record.streaming ?? this.#beginItem(record, event.message);
|
|
607
|
+
if (kind === "text")
|
|
608
|
+
streaming.text += update.delta;
|
|
609
|
+
else
|
|
610
|
+
streaming.thinking += update.delta;
|
|
611
|
+
this.#emit(record, "transcript.delta", { itemId: streaming.id, kind, delta: update.delta }, true);
|
|
348
612
|
break;
|
|
349
613
|
}
|
|
350
|
-
case "message_end":
|
|
351
|
-
|
|
614
|
+
case "message_end": {
|
|
615
|
+
if (asRecord(event.message)["customType"] === CONTEXT_RESUME_MESSAGE)
|
|
616
|
+
break;
|
|
617
|
+
const item = this.#finishItem(record, event.message);
|
|
618
|
+
this.#emit(record, "transcript.item.finished", { item }, false);
|
|
352
619
|
break;
|
|
620
|
+
}
|
|
353
621
|
case "tool_execution_start":
|
|
354
|
-
this.#emit(record, "tool.started", {
|
|
622
|
+
this.#emit(record, "tool.started", {
|
|
623
|
+
toolCallId: event.toolCallId,
|
|
624
|
+
toolName: event.toolName,
|
|
625
|
+
input: publishedArticleValue(event.toolName, event.args),
|
|
626
|
+
}, false);
|
|
355
627
|
break;
|
|
356
|
-
case "tool_execution_update":
|
|
357
|
-
|
|
628
|
+
case "tool_execution_update": {
|
|
629
|
+
if (event.toolName === "article") {
|
|
630
|
+
const draft = articleDraftFromToolUpdate(record.spec.sessionId, event.toolCallId, event.partialResult);
|
|
631
|
+
if (draft) {
|
|
632
|
+
this.#articlePreviews.note(draft, this.#clock().getTime());
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
this.#emit(record, "tool.updated", { toolCallId: event.toolCallId, toolName: event.toolName, partialResult: publishedArticleValue(event.toolName, event.partialResult) }, true);
|
|
358
637
|
break;
|
|
638
|
+
}
|
|
359
639
|
case "tool_execution_end":
|
|
640
|
+
this.#articlePreviews.clear(event.toolCallId);
|
|
360
641
|
this.#emit(record, "tool.completed", {
|
|
361
642
|
toolCallId: event.toolCallId,
|
|
362
643
|
toolName: event.toolName,
|
|
363
644
|
isError: event.isError,
|
|
364
|
-
result: event.result,
|
|
645
|
+
result: publishedArticleValue(event.toolName, event.result),
|
|
365
646
|
}, false);
|
|
366
647
|
break;
|
|
367
648
|
case "queue_update":
|
|
368
|
-
record
|
|
369
|
-
this.#emit(record, "queue.updated", record.queue, false);
|
|
649
|
+
this.#publishQueue(record);
|
|
370
650
|
break;
|
|
371
651
|
case "compaction_start":
|
|
372
|
-
this.#emit(record, "compaction.started", { reason: event.reason }, false);
|
|
652
|
+
this.#emit(record, "compaction.started", { reason: record.thresholdCompaction && event.reason === "manual" ? "token_threshold" : event.reason }, false);
|
|
373
653
|
break;
|
|
374
654
|
case "compaction_end":
|
|
375
|
-
this.#emit(record, "compaction.completed", {
|
|
655
|
+
this.#emit(record, "compaction.completed", {
|
|
656
|
+
reason: record.thresholdCompaction && event.reason === "manual" ? "token_threshold" : event.reason,
|
|
657
|
+
aborted: event.aborted,
|
|
658
|
+
willRetry: event.willRetry,
|
|
659
|
+
...(event.errorMessage ? { errorMessage: event.errorMessage } : {}),
|
|
660
|
+
...(event.result ? { tokensBefore: event.result.tokensBefore, tokensAfter: event.result.estimatedTokensAfter } : {}),
|
|
661
|
+
}, false);
|
|
376
662
|
break;
|
|
377
663
|
case "auto_retry_start":
|
|
378
664
|
this.#emit(record, "retry.started", { attempt: event.attempt, maxAttempts: event.maxAttempts, delayMs: event.delayMs }, false);
|
|
@@ -384,6 +670,60 @@ export class PiRuntimeAdapter {
|
|
|
384
670
|
break;
|
|
385
671
|
}
|
|
386
672
|
}
|
|
673
|
+
#beginItem(record, message) {
|
|
674
|
+
const candidate = asRecord(message);
|
|
675
|
+
const streaming = {
|
|
676
|
+
id: `${record.upstreamSessionId}:${++record.itemSequence}`,
|
|
677
|
+
role: typeof candidate["role"] === "string" ? candidate["role"] : "assistant",
|
|
678
|
+
text: messageText(message),
|
|
679
|
+
thinking: "",
|
|
680
|
+
startedAt: this.#clock().toISOString(),
|
|
681
|
+
};
|
|
682
|
+
record.streaming = streaming;
|
|
683
|
+
return streaming;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* The item a `transcript.item.started` event announces. It is the same shape
|
|
687
|
+
* a finished item has — clients and the desktop's frame validator both read a
|
|
688
|
+
* `TranscriptItem` — only still open, so the status is `streaming`.
|
|
689
|
+
*/
|
|
690
|
+
#startedItem(streaming) {
|
|
691
|
+
return {
|
|
692
|
+
id: streaming.id,
|
|
693
|
+
role: streaming.role,
|
|
694
|
+
text: streaming.text,
|
|
695
|
+
...(streaming.thinking ? { thinking: streaming.thinking } : {}),
|
|
696
|
+
status: "streaming",
|
|
697
|
+
ts: streaming.startedAt,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
#finishItem(record, message) {
|
|
701
|
+
const streaming = record.streaming ?? this.#beginItem(record, message);
|
|
702
|
+
record.streaming = undefined;
|
|
703
|
+
return {
|
|
704
|
+
...toRuntimeMessage(message),
|
|
705
|
+
id: streaming.id,
|
|
706
|
+
status: "complete",
|
|
707
|
+
ts: streaming.startedAt,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
#noteArticleDraft(record, message, update) {
|
|
711
|
+
const draft = articleDraftFromUpdate(record.spec.sessionId, update, message);
|
|
712
|
+
if (!draft)
|
|
713
|
+
return;
|
|
714
|
+
this.#articlePreviews.note(draft, this.#clock().getTime());
|
|
715
|
+
}
|
|
716
|
+
#publishArticleDraft(draft) {
|
|
717
|
+
const record = this.#sessions.get(draft.sessionId);
|
|
718
|
+
if (!record)
|
|
719
|
+
return;
|
|
720
|
+
this.#emit(record, "article.preview", {
|
|
721
|
+
toolCallId: draft.toolCallId,
|
|
722
|
+
...(draft.title === undefined ? {} : { title: draft.title }),
|
|
723
|
+
...(draft.articleId === undefined ? {} : { articleId: draft.articleId }),
|
|
724
|
+
chars: draft.content?.length ?? 0,
|
|
725
|
+
}, true);
|
|
726
|
+
}
|
|
387
727
|
#emit(record, type, payload, transient) {
|
|
388
728
|
const event = {
|
|
389
729
|
id: this.#idFactory(),
|
|
@@ -425,6 +765,107 @@ export class PiRuntimeAdapter {
|
|
|
425
765
|
#accepted() {
|
|
426
766
|
return { accepted: true, commandId: this.#idFactory(), acceptedAt: this.#clock().toISOString() };
|
|
427
767
|
}
|
|
768
|
+
#startRun(record) {
|
|
769
|
+
if (record.runStarted)
|
|
770
|
+
return;
|
|
771
|
+
record.runStarted = true;
|
|
772
|
+
this.#emit(record, "run.started", {}, false);
|
|
773
|
+
}
|
|
774
|
+
#needsThresholdCompaction(record) {
|
|
775
|
+
const settings = this.#autoCompaction;
|
|
776
|
+
if (!settings?.enabled)
|
|
777
|
+
return false;
|
|
778
|
+
const session = record.runtime.session;
|
|
779
|
+
if (record.abortRequested || session.messages.length === 0)
|
|
780
|
+
return false;
|
|
781
|
+
// A just-compacted leaf cannot be compacted again without new work.
|
|
782
|
+
if (session.sessionManager.getBranch().at(-1)?.type === "compaction")
|
|
783
|
+
return false;
|
|
784
|
+
return this.#contextUsage(record).tokens >= this.#compactionThreshold(record);
|
|
785
|
+
}
|
|
786
|
+
async #compactAtConfiguredThreshold(record) {
|
|
787
|
+
if (!this.#needsThresholdCompaction(record))
|
|
788
|
+
return;
|
|
789
|
+
const session = record.runtime.session;
|
|
790
|
+
if (session.messages.at(-1)?.role === "toolResult") {
|
|
791
|
+
// Pi never cuts at a tool result. Add an invisible, user-like boundary
|
|
792
|
+
// after the completed batch so even the first long task can be compacted.
|
|
793
|
+
await session.sendCustomMessage({
|
|
794
|
+
customType: CONTEXT_RESUME_MESSAGE,
|
|
795
|
+
content: "The tool batch above has finished. Preserve the original user request, completed actions, evidence and remaining work in the context summary.",
|
|
796
|
+
display: false,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
// Pi deliberately keeps whole recent messages. A single large retained
|
|
800
|
+
// answer can still exceed our threshold, yet leave nothing new to summarize.
|
|
801
|
+
// Use its public cut-point rules to avoid reporting that normal no-op as a
|
|
802
|
+
// failed task ("Nothing to compact").
|
|
803
|
+
const entries = session.sessionManager.getBranch();
|
|
804
|
+
const previous = getLatestCompactionEntry(entries);
|
|
805
|
+
const start = previous ? Math.max(0, entries.findIndex((entry) => entry.id === previous.firstKeptEntryId)) : 0;
|
|
806
|
+
const cut = findCutPoint(entries, start, entries.length, session.settingsManager.getCompactionSettings().keepRecentTokens);
|
|
807
|
+
if (!entries.slice(start, cut.firstKeptEntryIndex).some((entry) => entry.type !== "compaction" && sessionEntryToContextMessages(entry).length > 0))
|
|
808
|
+
return;
|
|
809
|
+
record.thresholdCompaction = true;
|
|
810
|
+
try {
|
|
811
|
+
await record.runtime.session.compact();
|
|
812
|
+
}
|
|
813
|
+
finally {
|
|
814
|
+
record.thresholdCompaction = false;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
#compactionThreshold(record) {
|
|
818
|
+
const settings = this.#autoCompaction;
|
|
819
|
+
if (!settings)
|
|
820
|
+
return Number.POSITIVE_INFINITY;
|
|
821
|
+
const limit = record.runtime.session.model?.contextWindow ?? Number.POSITIVE_INFINITY;
|
|
822
|
+
return Math.max(1, Math.min(settings.triggerTokens, limit - settings.reserveTokens));
|
|
823
|
+
}
|
|
824
|
+
#publishQueue(record) {
|
|
825
|
+
record.queue = {
|
|
826
|
+
steering: [...record.runtime.session.getSteeringMessages(), ...record.heldQueue.steering],
|
|
827
|
+
followUp: [...record.runtime.session.getFollowUpMessages(), ...record.heldQueue.followUp],
|
|
828
|
+
};
|
|
829
|
+
this.#emit(record, "queue.updated", record.queue, false);
|
|
830
|
+
}
|
|
831
|
+
async #restoreHeldQueue(record) {
|
|
832
|
+
while (record.heldQueue.steering.length || record.heldQueue.followUp.length) {
|
|
833
|
+
const queue = record.heldQueue;
|
|
834
|
+
record.heldQueue = { steering: [], followUp: [] };
|
|
835
|
+
for (const message of queue.steering)
|
|
836
|
+
await record.runtime.session.steer(message);
|
|
837
|
+
for (const message of queue.followUp)
|
|
838
|
+
await record.runtime.session.followUp(message);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
#contextUsage(record) {
|
|
842
|
+
const session = record.runtime.session;
|
|
843
|
+
const usage = session.getContextUsage();
|
|
844
|
+
let tokens = usage?.tokens;
|
|
845
|
+
const lastUsageIndex = session.messages.findLastIndex((message) => message.role === "assistant"
|
|
846
|
+
&& message.stopReason !== "error" && message.stopReason !== "aborted"
|
|
847
|
+
&& (message.usage.input + message.usage.output + message.usage.cacheRead + message.usage.cacheWrite) > 0);
|
|
848
|
+
if (tokens === undefined || tokens === null || lastUsageIndex < 0) {
|
|
849
|
+
tokens = estimateTextTokens(session.systemPrompt)
|
|
850
|
+
+ estimateTextTokens(JSON.stringify(session.agent.state.tools.map((tool) => ({ name: tool.name, description: tool.description, parameters: tool.parameters }))));
|
|
851
|
+
for (const message of session.messages) {
|
|
852
|
+
tokens += estimateMessageTokens(message);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
// Pi estimates messages after the latest measured usage as chars / 4.
|
|
857
|
+
// Correct only that unmeasured tail; never add the entire history again.
|
|
858
|
+
for (const message of session.messages.slice(lastUsageIndex + 1)) {
|
|
859
|
+
tokens += estimateMessageTokens(message) - estimateTokens(message);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return {
|
|
863
|
+
tokens,
|
|
864
|
+
window: usage?.contextWindow ?? session.model?.contextWindow ?? 0,
|
|
865
|
+
estimated: true,
|
|
866
|
+
...(this.#autoCompaction?.enabled ? { triggerTokens: this.#compactionThreshold(record) } : {}),
|
|
867
|
+
};
|
|
868
|
+
}
|
|
428
869
|
#handle(record) {
|
|
429
870
|
const sessionFile = record.runtime.session.sessionFile;
|
|
430
871
|
return {
|
|
@@ -434,19 +875,73 @@ export class PiRuntimeAdapter {
|
|
|
434
875
|
};
|
|
435
876
|
}
|
|
436
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Adds skills resolved outside Pi to what Pi loaded from paths. A name Pi
|
|
880
|
+
* already has keeps its skill — the same first-wins rule Pi applies among its
|
|
881
|
+
* own paths — and the loser is reported the way Pi reports a collision.
|
|
882
|
+
*/
|
|
883
|
+
function withResolvedSkills(base, resolved) {
|
|
884
|
+
const skills = [...base.skills];
|
|
885
|
+
const diagnostics = [...base.diagnostics];
|
|
886
|
+
for (const skill of resolved) {
|
|
887
|
+
const winner = skills.find((candidate) => candidate.name === skill.name);
|
|
888
|
+
if (winner) {
|
|
889
|
+
diagnostics.push({
|
|
890
|
+
type: "collision",
|
|
891
|
+
message: `name "${skill.name}" collision`,
|
|
892
|
+
path: skill.filePath,
|
|
893
|
+
collision: { resourceType: "skill", name: skill.name, winnerPath: winner.filePath, loserPath: skill.filePath },
|
|
894
|
+
});
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
skills.push({
|
|
898
|
+
...skill,
|
|
899
|
+
sourceInfo: createSyntheticSourceInfo(skill.filePath, { source: "local", scope: "user", baseDir: skill.baseDir }),
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
return { skills, diagnostics };
|
|
903
|
+
}
|
|
904
|
+
function toBranchMessage(entry) {
|
|
905
|
+
return { id: entry.id, ...toRuntimeMessage(entry.message) };
|
|
906
|
+
}
|
|
437
907
|
function toRuntimeMessage(message) {
|
|
438
908
|
const candidate = asRecord(message);
|
|
909
|
+
const content = Array.isArray(candidate["content"]) ? candidate["content"].map(asRecord) : [];
|
|
910
|
+
const thinking = content.filter((block) => block["type"] === "thinking" && typeof block["thinking"] === "string")
|
|
911
|
+
.map((block) => block["thinking"]).join("\n");
|
|
912
|
+
const toolCalls = content.flatMap((block) => {
|
|
913
|
+
if (block["type"] !== "toolCall" || typeof block["id"] !== "string" || typeof block["name"] !== "string")
|
|
914
|
+
return [];
|
|
915
|
+
return [{ id: block["id"], name: block["name"], input: toolInputText(publishedArticleValue(block["name"], block["arguments"] ?? {})) }];
|
|
916
|
+
});
|
|
439
917
|
return {
|
|
440
918
|
role: typeof candidate["role"] === "string" ? candidate["role"] : "unknown",
|
|
441
919
|
text: messageText(message),
|
|
920
|
+
...(thinking ? { thinking } : {}),
|
|
921
|
+
...(toolCalls.length ? { toolCalls } : {}),
|
|
922
|
+
...(typeof candidate["toolCallId"] === "string" ? { toolCallId: candidate["toolCallId"] } : {}),
|
|
923
|
+
...(typeof candidate["toolName"] === "string" ? { toolName: candidate["toolName"] } : {}),
|
|
924
|
+
...(typeof candidate["isError"] === "boolean" ? { isError: candidate["isError"] } : {}),
|
|
442
925
|
};
|
|
443
926
|
}
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
927
|
+
function isNodeError(error) {
|
|
928
|
+
return error instanceof Error && "code" in error;
|
|
929
|
+
}
|
|
930
|
+
function estimateMessageTokens(message) {
|
|
931
|
+
let prose = messageText(message);
|
|
932
|
+
if (message.role === "compactionSummary" || message.role === "branchSummary")
|
|
933
|
+
prose += message.summary;
|
|
934
|
+
if (message.role === "bashExecution")
|
|
935
|
+
prose += message.output;
|
|
936
|
+
if (message.role === "assistant") {
|
|
937
|
+
for (const block of message.content) {
|
|
938
|
+
if (block.type === "thinking")
|
|
939
|
+
prose += block.thinking;
|
|
940
|
+
if (block.type === "toolCall")
|
|
941
|
+
prose += block.name + JSON.stringify(block.arguments);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return Math.max(estimateTokens(message), estimateTextTokens(prose));
|
|
450
945
|
}
|
|
451
946
|
function messageText(message) {
|
|
452
947
|
const candidate = asRecord(message);
|
|
@@ -463,12 +958,12 @@ function messageText(message) {
|
|
|
463
958
|
return content
|
|
464
959
|
.map((block) => {
|
|
465
960
|
const value = asRecord(block);
|
|
466
|
-
|
|
961
|
+
// Tool calls and hidden reasoning have dedicated runtime events. Mixing
|
|
962
|
+
// synthetic markers into assistant prose leaks strings such as
|
|
963
|
+
// `[tool:shell]` into Markdown and persisted history.
|
|
964
|
+
if ((value["type"] === undefined || value["type"] === "text") && typeof value["text"] === "string") {
|
|
467
965
|
return value["text"];
|
|
468
|
-
|
|
469
|
-
return value["thinking"];
|
|
470
|
-
if (value["type"] === "toolCall" && typeof value["name"] === "string")
|
|
471
|
-
return `[tool:${value["name"]}]`;
|
|
966
|
+
}
|
|
472
967
|
return "";
|
|
473
968
|
})
|
|
474
969
|
.filter(Boolean)
|