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,5 +1,36 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { previewArticleEdits } from "../articles/edits.js";
|
|
3
|
+
import { MAX_ARTICLE_CHARS } from "../articles/store.js";
|
|
2
4
|
import { CommandError } from "../protocol/errors.js";
|
|
5
|
+
import { MAX_PUBLISHED_STRING_CHARS } from "../protocol/limits.js";
|
|
6
|
+
/**
|
|
7
|
+
* Runtime events whose broadcast carries the transcript tail. A finished item
|
|
8
|
+
* already carries its own text, so clients append it themselves; the tail rides
|
|
9
|
+
* along only where the transcript needs re-establishing wholesale — the end of a
|
|
10
|
+
* run, a replaced session, a compaction that rewrote the history.
|
|
11
|
+
*/
|
|
12
|
+
const TRANSCRIPT_BEARING_EVENTS = new Set([
|
|
13
|
+
"run.settled",
|
|
14
|
+
"session.replaced",
|
|
15
|
+
"runtime.error",
|
|
16
|
+
"compaction.completed",
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* How much of a transcript rides along with a broadcast session view. Enough to
|
|
20
|
+
* render a conversation on arrival, bounded so the payload never grows with the
|
|
21
|
+
* length of the session; older history comes from `session.transcript`.
|
|
22
|
+
*/
|
|
23
|
+
const SESSION_VIEW_TAIL_MESSAGES = 200;
|
|
24
|
+
/** Largest transcript page one `session.transcript` command may return. */
|
|
25
|
+
const MAX_TRANSCRIPT_PAGE = 500;
|
|
26
|
+
/**
|
|
27
|
+
* Largest slice of one message `session.message` may return. It matches the
|
|
28
|
+
* daemon's per-string publishing ceiling, so a slice is never cut a second time
|
|
29
|
+
* on its way out — a client asking for a long message pages through it instead.
|
|
30
|
+
*/
|
|
31
|
+
const MAX_MESSAGE_SLICE = MAX_PUBLISHED_STRING_CHARS;
|
|
32
|
+
/** Saved versions kept for updates still being streamed. One call is normal; a few cover parallel calls. */
|
|
33
|
+
const DRAFT_BASE_CACHE = 4;
|
|
3
34
|
export class AgentRunBudgetError extends Error {
|
|
4
35
|
constructor(message) {
|
|
5
36
|
super(message);
|
|
@@ -11,15 +42,22 @@ export class AgentApplication {
|
|
|
11
42
|
#kernel;
|
|
12
43
|
#approvals;
|
|
13
44
|
#notifications;
|
|
45
|
+
#sessionCatalog;
|
|
14
46
|
#sessionTemplate;
|
|
15
47
|
#modelConfigured;
|
|
48
|
+
#builtinTools;
|
|
16
49
|
#onShutdownRequested;
|
|
17
50
|
#clock;
|
|
18
51
|
#idFactory;
|
|
52
|
+
#onError;
|
|
19
53
|
#sessions = new Set();
|
|
54
|
+
#rootSessions = new Map();
|
|
20
55
|
#unsubscribers = new Map();
|
|
21
56
|
#sessionTails = new Map();
|
|
22
57
|
#automation;
|
|
58
|
+
#articles;
|
|
59
|
+
/** The saved version each streaming `update` revises, by tool call. */
|
|
60
|
+
#draftBases = new Map();
|
|
23
61
|
#agents;
|
|
24
62
|
#mcp;
|
|
25
63
|
#resources;
|
|
@@ -28,11 +66,19 @@ export class AgentApplication {
|
|
|
28
66
|
this.#kernel = options.kernel;
|
|
29
67
|
this.#approvals = options.approvals;
|
|
30
68
|
this.#notifications = options.notifications;
|
|
69
|
+
this.#sessionCatalog = options.sessionCatalog;
|
|
31
70
|
this.#sessionTemplate = options.sessionTemplate;
|
|
32
71
|
this.#modelConfigured = options.modelConfigured;
|
|
72
|
+
this.#builtinTools = options.builtinTools.map((tool) => ({
|
|
73
|
+
name: tool.name,
|
|
74
|
+
...(tool.label === undefined ? {} : { label: tool.label }),
|
|
75
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
76
|
+
}));
|
|
33
77
|
this.#onShutdownRequested = options.onShutdownRequested;
|
|
78
|
+
this.#articles = options.articles;
|
|
34
79
|
this.#clock = options.clock ?? (() => new Date());
|
|
35
80
|
this.#idFactory = options.idFactory ?? randomUUID;
|
|
81
|
+
this.#onError = options.onError ?? (() => undefined);
|
|
36
82
|
}
|
|
37
83
|
async handleCommand(command) {
|
|
38
84
|
switch (command.type) {
|
|
@@ -42,24 +88,90 @@ export class AgentApplication {
|
|
|
42
88
|
case "session.snapshot": {
|
|
43
89
|
exactPayload(command.payload, [], command.type);
|
|
44
90
|
const sessionId = requiredSessionId(command);
|
|
45
|
-
return this.#sessions.has(sessionId) ? this.#
|
|
91
|
+
return this.#sessions.has(sessionId) ? this.#view(sessionId) : null;
|
|
92
|
+
}
|
|
93
|
+
case "session.transcript": {
|
|
94
|
+
// Broadcasts carry only a bounded tail, so scrolling back through a long
|
|
95
|
+
// conversation asks for the range it needs instead of making every
|
|
96
|
+
// client pay for the whole history on every update.
|
|
97
|
+
const payload = exactPayload(command.payload, ["from", "limit"], command.type);
|
|
98
|
+
const sessionId = requiredSessionId(command);
|
|
99
|
+
if (!this.#sessions.has(sessionId))
|
|
100
|
+
return null;
|
|
101
|
+
const limit = optionalBoundedIndex(payload["limit"], "payload.limit", MAX_TRANSCRIPT_PAGE) ?? MAX_TRANSCRIPT_PAGE;
|
|
102
|
+
const from = optionalBoundedIndex(payload["from"], "payload.from");
|
|
103
|
+
const page = this.#runtime.transcript(sessionId, { limit, ...(from === undefined ? {} : { from }) });
|
|
104
|
+
return { sessionId, ...page };
|
|
105
|
+
}
|
|
106
|
+
case "session.message": {
|
|
107
|
+
// A message longer than the publishing ceiling reaches clients cut, with
|
|
108
|
+
// a marker saying so. This returns the original in slices, so the reader
|
|
109
|
+
// can ask for the whole of one message without every broadcast paying
|
|
110
|
+
// for the possibility.
|
|
111
|
+
const payload = exactPayload(command.payload, ["id", "index", "offset", "limit"], command.type);
|
|
112
|
+
const sessionId = requiredSessionId(command);
|
|
113
|
+
if (!this.#sessions.has(sessionId))
|
|
114
|
+
return null;
|
|
115
|
+
const id = optionalIdentifier(payload["id"], "payload.id");
|
|
116
|
+
const index = optionalBoundedIndex(payload["index"], "payload.index");
|
|
117
|
+
if (id === undefined && index === undefined) {
|
|
118
|
+
throw new CommandError("invalid_payload", "payload.id or payload.index is required");
|
|
119
|
+
}
|
|
120
|
+
const limit = optionalBoundedIndex(payload["limit"], "payload.limit", MAX_MESSAGE_SLICE) ?? MAX_MESSAGE_SLICE;
|
|
121
|
+
const offset = optionalBoundedIndex(payload["offset"], "payload.offset");
|
|
122
|
+
const slice = this.#runtime.transcriptMessage(sessionId, {
|
|
123
|
+
...(id === undefined ? {} : { id }),
|
|
124
|
+
...(index === undefined ? {} : { index }),
|
|
125
|
+
...(offset === undefined ? {} : { offset }),
|
|
126
|
+
limit,
|
|
127
|
+
});
|
|
128
|
+
return { sessionId, ...slice };
|
|
129
|
+
}
|
|
130
|
+
case "session.create": {
|
|
131
|
+
const payload = exactPayload(command.payload, ["title"], command.type);
|
|
132
|
+
const title = optionalSessionTitle(payload["title"], "payload.title");
|
|
133
|
+
const sessionId = generatedRootSessionId(this.#idFactory());
|
|
134
|
+
return this.#serializeSession(sessionId, async () => {
|
|
135
|
+
await this.#createRootSession(sessionId, title);
|
|
136
|
+
return this.#view(sessionId);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
case "session.delete": {
|
|
140
|
+
exactPayload(command.payload, [], command.type);
|
|
141
|
+
const sessionId = requiredSessionId(command);
|
|
142
|
+
return this.#serializeSession(sessionId, async () => this.#deleteRootSession(sessionId));
|
|
46
143
|
}
|
|
47
144
|
case "session.prompt": {
|
|
48
145
|
const payload = exactPayload(command.payload, ["text", "mode"], command.type);
|
|
49
146
|
const text = nonEmptyString(payload["text"], "payload.text");
|
|
50
|
-
const mode = optionalEnum(payload["mode"], ["prompt", "steer", "follow_up"], "payload.mode") ?? "
|
|
147
|
+
const mode = optionalEnum(payload["mode"], ["auto", "prompt", "steer", "follow_up"], "payload.mode") ?? "auto";
|
|
51
148
|
const sessionId = requiredSessionId(command);
|
|
52
149
|
return this.#serializeSession(sessionId, async () => {
|
|
53
|
-
await this.#
|
|
54
|
-
const
|
|
150
|
+
await this.#ensureRootSession(sessionId);
|
|
151
|
+
const record = await this.#sessionCatalog.touch(sessionId, { prompt: text });
|
|
152
|
+
if (record)
|
|
153
|
+
this.#rootSessions.set(sessionId, rootMetadata(record));
|
|
154
|
+
const automaticMode = mode === "auto"
|
|
155
|
+
? this.#runtime.snapshot(sessionId).status === "running" ? "follow_up" : "prompt"
|
|
156
|
+
: mode;
|
|
157
|
+
const accepted = automaticMode === "steer"
|
|
55
158
|
? await this.#runtime.steer(sessionId, text)
|
|
56
|
-
:
|
|
159
|
+
: automaticMode === "follow_up"
|
|
57
160
|
? await this.#runtime.followUp(sessionId, text)
|
|
58
161
|
: await this.#runtime.prompt(sessionId, text);
|
|
59
162
|
setImmediate(() => void this.#agents?.sessionAvailable(sessionId));
|
|
60
163
|
return accepted;
|
|
61
164
|
});
|
|
62
165
|
}
|
|
166
|
+
case "session.queue.send-now": {
|
|
167
|
+
const payload = exactPayload(command.payload, ["index"], command.type);
|
|
168
|
+
const index = nonNegativeIntegerValue(payload["index"], "payload.index", 10_000);
|
|
169
|
+
const sessionId = requiredSessionId(command);
|
|
170
|
+
return this.#serializeSession(sessionId, async () => {
|
|
171
|
+
this.#assertRootSession(sessionId);
|
|
172
|
+
return this.#runtime.promoteFollowUp(sessionId, index);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
63
175
|
case "session.abort": {
|
|
64
176
|
const payload = exactPayload(command.payload, ["reason"], command.type);
|
|
65
177
|
const reason = optionalString(payload["reason"], "payload.reason") ?? "user requested abort";
|
|
@@ -138,6 +250,11 @@ export class AgentApplication {
|
|
|
138
250
|
case "mcp.servers":
|
|
139
251
|
exactPayload(command.payload, [], command.type);
|
|
140
252
|
return this.#requireMcp().listServers();
|
|
253
|
+
case "tools.list":
|
|
254
|
+
exactPayload(command.payload, [], command.type);
|
|
255
|
+
// Fixed tools, not MCP ones: these are what this build ships with, and
|
|
256
|
+
// they exist whether or not any server has been discovered.
|
|
257
|
+
return this.#builtinTools.map((tool) => ({ ...tool }));
|
|
141
258
|
case "mcp.tools":
|
|
142
259
|
exactPayload(command.payload, [], command.type);
|
|
143
260
|
return this.#requireMcp().listTools();
|
|
@@ -155,6 +272,11 @@ export class AgentApplication {
|
|
|
155
272
|
case "skills.list":
|
|
156
273
|
exactPayload(command.payload, [], command.type);
|
|
157
274
|
return this.#requireResources().list().filter((entry) => entry.record.kind === "skill");
|
|
275
|
+
case "skills.catalog":
|
|
276
|
+
exactPayload(command.payload, [], command.type);
|
|
277
|
+
// Every skill a new session is offered, whatever its source, and the
|
|
278
|
+
// ones that were found but not offered with the reason.
|
|
279
|
+
return this.#requireResources().skillCatalog();
|
|
158
280
|
case "plugins.list":
|
|
159
281
|
exactPayload(command.payload, [], command.type);
|
|
160
282
|
return this.#requireResources().list().filter((entry) => entry.record.kind === "host-plugin");
|
|
@@ -186,10 +308,101 @@ export class AgentApplication {
|
|
|
186
308
|
exactPayload(command.payload, [], command.type);
|
|
187
309
|
this.#onShutdownRequested?.();
|
|
188
310
|
return { draining: true };
|
|
311
|
+
case "article.list": {
|
|
312
|
+
exactPayload(command.payload, [], command.type);
|
|
313
|
+
const sessionId = requiredSessionId(command);
|
|
314
|
+
return { articles: await this.#requireArticles().list(sessionId) };
|
|
315
|
+
}
|
|
316
|
+
case "article.get": {
|
|
317
|
+
const payload = exactPayload(command.payload, ["id", "version", "offset", "limit"], command.type);
|
|
318
|
+
const sessionId = requiredSessionId(command);
|
|
319
|
+
const version = optionalBoundedIndex(payload["version"], "payload.version", 100);
|
|
320
|
+
const offset = optionalBoundedIndex(payload["offset"], "payload.offset");
|
|
321
|
+
const limit = optionalBoundedIndex(payload["limit"], "payload.limit", MAX_MESSAGE_SLICE) ?? MAX_MESSAGE_SLICE;
|
|
322
|
+
const document = await this.#requireArticles().read({
|
|
323
|
+
sessionId,
|
|
324
|
+
id: nonEmptyString(payload["id"], "payload.id"),
|
|
325
|
+
...(version === undefined || version < 1 ? {} : { version }),
|
|
326
|
+
...(offset === undefined ? {} : { offset }),
|
|
327
|
+
limit,
|
|
328
|
+
});
|
|
329
|
+
return document ?? null;
|
|
330
|
+
}
|
|
331
|
+
case "article.preview": {
|
|
332
|
+
const payload = exactPayload(command.payload, ["toolCallId", "offset", "limit"], command.type);
|
|
333
|
+
const sessionId = requiredSessionId(command);
|
|
334
|
+
const draft = this.#runtime.articlePreview?.(nonEmptyString(payload["toolCallId"], "payload.toolCallId"));
|
|
335
|
+
if (!draft || draft.sessionId !== sessionId)
|
|
336
|
+
return null;
|
|
337
|
+
const offset = optionalBoundedIndex(payload["offset"], "payload.offset") ?? 0;
|
|
338
|
+
const limit = optionalBoundedIndex(payload["limit"], "payload.limit", MAX_MESSAGE_SLICE) ?? MAX_MESSAGE_SLICE;
|
|
339
|
+
const body = await this.#draftBody(draft);
|
|
340
|
+
if (!body)
|
|
341
|
+
return null;
|
|
342
|
+
const start = Math.min(Math.max(0, offset), body.text.length);
|
|
343
|
+
return {
|
|
344
|
+
toolCallId: draft.toolCallId,
|
|
345
|
+
...(draft.title === undefined ? {} : { title: draft.title }),
|
|
346
|
+
...(draft.articleId === undefined ? {} : { articleId: draft.articleId }),
|
|
347
|
+
whole: body.whole,
|
|
348
|
+
...(body.baseVersion === undefined ? {} : { baseVersion: body.baseVersion }),
|
|
349
|
+
...(body.whole ? { edits: body.edits } : {}),
|
|
350
|
+
offset: start,
|
|
351
|
+
length: body.text.length,
|
|
352
|
+
text: body.text.slice(start, start + limit),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
189
355
|
default:
|
|
190
356
|
throw new CommandError("unknown_command", `Unknown command type: ${command.type}`);
|
|
191
357
|
}
|
|
192
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* What the panel shows for a draft. A full draft is the start of the next
|
|
361
|
+
* version and goes out as written. Edits are applied to the version they
|
|
362
|
+
* revise, so what goes out is that whole document as it will read (`whole`).
|
|
363
|
+
* An update also names the version it starts from, so a panel showing
|
|
364
|
+
* something else can put that version up first and draw the draft over it.
|
|
365
|
+
*/
|
|
366
|
+
async #draftBody(draft) {
|
|
367
|
+
const base = draft.action === "update" && draft.articleId !== undefined
|
|
368
|
+
? await this.#draftBase(draft.toolCallId, draft.sessionId, draft.articleId)
|
|
369
|
+
: undefined;
|
|
370
|
+
const edits = draft.edits ?? [];
|
|
371
|
+
if (draft.action === "create" || (draft.content !== undefined && edits.length === 0)) {
|
|
372
|
+
return { text: draft.content ?? "", whole: false, edits: 0, ...(base ? { baseVersion: base.version } : {}) };
|
|
373
|
+
}
|
|
374
|
+
if (!base)
|
|
375
|
+
return undefined;
|
|
376
|
+
const preview = previewArticleEdits(draft.content ?? base.text, edits, draft.editOpen === true);
|
|
377
|
+
return { text: preview.text, whole: true, baseVersion: base.version, edits: preview.applied };
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* The saved version an update revises. Tools run only after the model stops
|
|
381
|
+
* streaming, so it cannot change under a call still being written and one
|
|
382
|
+
* read serves every preview of that call.
|
|
383
|
+
*/
|
|
384
|
+
async #draftBase(toolCallId, sessionId, id) {
|
|
385
|
+
const cached = this.#draftBases.get(toolCallId);
|
|
386
|
+
if (cached?.id === id)
|
|
387
|
+
return cached;
|
|
388
|
+
const document = await this.#requireArticles().read({ sessionId, id, limit: MAX_ARTICLE_CHARS });
|
|
389
|
+
if (!document)
|
|
390
|
+
return undefined;
|
|
391
|
+
const base = { id, version: document.version, text: document.text };
|
|
392
|
+
this.#draftBases.set(toolCallId, base);
|
|
393
|
+
while (this.#draftBases.size > DRAFT_BASE_CACHE) {
|
|
394
|
+
const oldest = this.#draftBases.keys().next().value;
|
|
395
|
+
if (oldest === undefined)
|
|
396
|
+
break;
|
|
397
|
+
this.#draftBases.delete(oldest);
|
|
398
|
+
}
|
|
399
|
+
return base;
|
|
400
|
+
}
|
|
401
|
+
#requireArticles() {
|
|
402
|
+
if (!this.#articles)
|
|
403
|
+
throw new CommandError("unavailable", "Article store is not configured");
|
|
404
|
+
return this.#articles;
|
|
405
|
+
}
|
|
193
406
|
setAutomation(automation) {
|
|
194
407
|
if (this.#automation)
|
|
195
408
|
throw new Error("Automation control is already configured");
|
|
@@ -199,6 +412,23 @@ export class AgentApplication {
|
|
|
199
412
|
if (this.#agents)
|
|
200
413
|
throw new Error("Agent control is already configured");
|
|
201
414
|
this.#agents = agents;
|
|
415
|
+
for (const sessionId of this.#rootSessions.keys()) {
|
|
416
|
+
agents.registerRoot({ id: sessionId, agentPath: [sessionId] });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async restoreRootSessions() {
|
|
420
|
+
const failures = [];
|
|
421
|
+
for (const record of this.#sessionCatalog.list()) {
|
|
422
|
+
try {
|
|
423
|
+
await this.#serializeSession(record.id, async () => this.#resumeRootSession(record));
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
const failure = toError(error);
|
|
427
|
+
failures.push(failure);
|
|
428
|
+
this.#onError(failure);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return failures;
|
|
202
432
|
}
|
|
203
433
|
setMcp(mcp) {
|
|
204
434
|
if (this.#mcp)
|
|
@@ -275,7 +505,7 @@ export class AgentApplication {
|
|
|
275
505
|
});
|
|
276
506
|
if (finalViolation)
|
|
277
507
|
throw new AgentRunBudgetError(finalViolation);
|
|
278
|
-
return
|
|
508
|
+
return this.#view(sessionId);
|
|
279
509
|
}
|
|
280
510
|
finally {
|
|
281
511
|
signal.removeEventListener("abort", abort);
|
|
@@ -327,7 +557,7 @@ export class AgentApplication {
|
|
|
327
557
|
}
|
|
328
558
|
snapshotAgentSession(sessionId) {
|
|
329
559
|
this.#assertSession(sessionId);
|
|
330
|
-
return this.#
|
|
560
|
+
return this.#view(sessionId);
|
|
331
561
|
}
|
|
332
562
|
subscribeAgentSession(sessionId, sink) {
|
|
333
563
|
this.#assertSession(sessionId);
|
|
@@ -339,33 +569,179 @@ export class AgentApplication {
|
|
|
339
569
|
async dispose() {
|
|
340
570
|
await Promise.allSettled([...this.#sessionTails.values()]);
|
|
341
571
|
await Promise.all([...this.#sessions].map(async (sessionId) => this.#disposeSession(sessionId)));
|
|
572
|
+
await this.#sessionCatalog.idle();
|
|
342
573
|
}
|
|
343
574
|
async #ensureSession(sessionId, cwd, authorization) {
|
|
344
575
|
if (this.#sessions.has(sessionId))
|
|
345
576
|
return;
|
|
346
|
-
|
|
347
|
-
throw new CommandError("model_not_configured", "No model is configured. Set config.model.provider, config.model.model, and credentials before prompting.");
|
|
348
|
-
}
|
|
577
|
+
this.#assertModelConfigured();
|
|
349
578
|
await this.#runtime.create({
|
|
350
579
|
sessionId,
|
|
351
580
|
...this.#sessionTemplate,
|
|
352
581
|
...(cwd === undefined ? {} : { cwd }),
|
|
353
582
|
...(authorization === undefined ? {} : { authorization }),
|
|
354
583
|
});
|
|
584
|
+
await this.#attachSession(sessionId);
|
|
585
|
+
}
|
|
586
|
+
async #ensureRootSession(sessionId) {
|
|
587
|
+
if (this.#sessions.has(sessionId)) {
|
|
588
|
+
this.#assertRootSession(sessionId);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const record = this.#sessionCatalog.get(sessionId);
|
|
592
|
+
if (record) {
|
|
593
|
+
await this.#resumeRootSession(record);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (sessionId !== "main") {
|
|
597
|
+
throw new CommandError("session_not_found", `Root session does not exist: ${sessionId}`);
|
|
598
|
+
}
|
|
599
|
+
await this.#createRootSession(sessionId);
|
|
600
|
+
}
|
|
601
|
+
async #createRootSession(sessionId, title) {
|
|
602
|
+
if (this.#sessions.has(sessionId) || this.#sessionCatalog.get(sessionId)) {
|
|
603
|
+
throw new CommandError("session_exists", `Root session already exists: ${sessionId}`);
|
|
604
|
+
}
|
|
605
|
+
this.#assertModelConfigured();
|
|
606
|
+
const handle = await this.#runtime.create({ sessionId, ...this.#sessionTemplate });
|
|
607
|
+
let rootRegistered = false;
|
|
608
|
+
try {
|
|
609
|
+
const now = this.#clock().toISOString();
|
|
610
|
+
let metadata = {
|
|
611
|
+
title: title ?? (sessionId === "main" ? "Main session" : "New session"),
|
|
612
|
+
createdAt: now,
|
|
613
|
+
updatedAt: now,
|
|
614
|
+
};
|
|
615
|
+
if (handle.sessionFile) {
|
|
616
|
+
metadata = rootMetadata(await this.#sessionCatalog.add({
|
|
617
|
+
id: sessionId,
|
|
618
|
+
sessionFile: handle.sessionFile,
|
|
619
|
+
cwd: this.#sessionTemplate.cwd,
|
|
620
|
+
...(title === undefined ? {} : { title }),
|
|
621
|
+
}));
|
|
622
|
+
}
|
|
623
|
+
this.#rootSessions.set(sessionId, metadata);
|
|
624
|
+
this.#agents?.registerRoot({ id: sessionId, agentPath: [sessionId] });
|
|
625
|
+
rootRegistered = Boolean(this.#agents);
|
|
626
|
+
await this.#attachSession(sessionId);
|
|
627
|
+
}
|
|
628
|
+
catch (error) {
|
|
629
|
+
this.#rootSessions.delete(sessionId);
|
|
630
|
+
if (rootRegistered)
|
|
631
|
+
this.#agents?.unregisterRoot(sessionId);
|
|
632
|
+
await this.#runtime.dispose(sessionId).catch(() => undefined);
|
|
633
|
+
throw error;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Removes a root session for good: any run is stopped, the runtime session is
|
|
638
|
+
* disposed, the catalog entry is dropped and the transcript is deleted.
|
|
639
|
+
*
|
|
640
|
+
* A record that exists only in the catalog is deleted the same way, so a
|
|
641
|
+
* client can clear history the daemon never restored — and so a session whose
|
|
642
|
+
* restore failed can still be cleaned up. Child agents belonging to the
|
|
643
|
+
* session are cancelled first, because their transcripts would otherwise keep
|
|
644
|
+
* writing into a session that no longer exists.
|
|
645
|
+
*/
|
|
646
|
+
async #deleteRootSession(sessionId) {
|
|
647
|
+
const attached = this.#sessions.has(sessionId);
|
|
648
|
+
if (attached)
|
|
649
|
+
this.#assertRootSession(sessionId);
|
|
650
|
+
else if (!this.#sessionCatalog.get(sessionId)) {
|
|
651
|
+
throw new CommandError("session_not_found", `Root session does not exist: ${sessionId}`);
|
|
652
|
+
}
|
|
653
|
+
if (attached) {
|
|
654
|
+
const reason = `root session ${sessionId} deleted`;
|
|
655
|
+
await this.#approvals.cancelSession(sessionId);
|
|
656
|
+
await this.#agents?.cancelChildren(this.#agents.parentIdForSession(sessionId), reason).catch(() => undefined);
|
|
657
|
+
// A running session has to stop before its runtime can be torn down; the
|
|
658
|
+
// abort is best-effort because an already-idle session rejects it.
|
|
659
|
+
await this.#runtime.abort(sessionId, reason).catch(() => undefined);
|
|
660
|
+
await this.#disposeSession(sessionId);
|
|
661
|
+
this.#agents?.unregisterRoot(sessionId);
|
|
662
|
+
}
|
|
663
|
+
const removed = await this.#sessionCatalog.remove(sessionId);
|
|
664
|
+
return {
|
|
665
|
+
deleted: true,
|
|
666
|
+
sessionId,
|
|
667
|
+
...(removed?.record.title === undefined ? {} : { title: removed.record.title }),
|
|
668
|
+
transcriptRemoved: removed?.transcriptRemoved ?? false,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
async #resumeRootSession(record) {
|
|
672
|
+
if (this.#sessions.has(record.id))
|
|
673
|
+
return;
|
|
674
|
+
this.#assertModelConfigured();
|
|
675
|
+
await this.#runtime.resume({
|
|
676
|
+
sessionId: record.id,
|
|
677
|
+
...this.#sessionTemplate,
|
|
678
|
+
cwd: record.cwd,
|
|
679
|
+
sessionFile: record.sessionFile,
|
|
680
|
+
});
|
|
681
|
+
let rootRegistered = false;
|
|
682
|
+
try {
|
|
683
|
+
this.#rootSessions.set(record.id, rootMetadata(record));
|
|
684
|
+
this.#agents?.registerRoot({ id: record.id, agentPath: [record.id] });
|
|
685
|
+
rootRegistered = Boolean(this.#agents);
|
|
686
|
+
await this.#attachSession(record.id);
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
this.#rootSessions.delete(record.id);
|
|
690
|
+
if (rootRegistered)
|
|
691
|
+
this.#agents?.unregisterRoot(record.id);
|
|
692
|
+
await this.#runtime.dispose(record.id).catch(() => undefined);
|
|
693
|
+
throw error;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async #attachSession(sessionId) {
|
|
355
697
|
this.#sessions.add(sessionId);
|
|
356
698
|
this.#unsubscribers.set(sessionId, this.#runtime.subscribe(sessionId, (event) => this.#onRuntimeEvent(event)));
|
|
357
|
-
await this.#publishSnapshot(this.#
|
|
699
|
+
await this.#publishSnapshot(this.#view(sessionId));
|
|
358
700
|
}
|
|
359
701
|
#onRuntimeEvent(event) {
|
|
360
|
-
|
|
361
|
-
|
|
702
|
+
// Transient events arrive per streamed token. Rebuilding and cloning the
|
|
703
|
+
// whole session for each one makes streaming cost grow with the length of
|
|
704
|
+
// the conversation, which stalls the daemon's event loop on long runs until
|
|
705
|
+
// clients are dropped for falling behind. They carry no snapshot anyway.
|
|
706
|
+
const snapshot = event.transient ? undefined : this.#view(event.sessionId);
|
|
707
|
+
void this.#dispatch({
|
|
362
708
|
id: event.id,
|
|
363
709
|
ts: event.ts,
|
|
364
710
|
type: "runtime.received",
|
|
365
711
|
event,
|
|
366
|
-
snapshot,
|
|
712
|
+
...(snapshot === undefined ? {} : { snapshot, publishTranscript: TRANSCRIPT_BEARING_EVENTS.has(event.type) }),
|
|
713
|
+
});
|
|
714
|
+
if (snapshot && this.#rootSessions.has(event.sessionId) && (event.type === "run.settled" || event.type === "session.replaced")) {
|
|
715
|
+
void this.#sessionCatalog.touch(event.sessionId, {
|
|
716
|
+
...(snapshot.sessionFile === undefined ? {} : { sessionFile: snapshot.sessionFile }),
|
|
717
|
+
}).then((record) => {
|
|
718
|
+
if (record)
|
|
719
|
+
this.#rootSessions.set(event.sessionId, rootMetadata(record));
|
|
720
|
+
}).catch((error) => this.#onError(toError(error)));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* A rejected dispatch must never become an unhandled rejection: that ends the
|
|
725
|
+
* daemon process, and repeated restarts trip the service start limit, which
|
|
726
|
+
* leaves clients unable to reconnect at all.
|
|
727
|
+
*/
|
|
728
|
+
#dispatch(event) {
|
|
729
|
+
return this.#kernel.dispatch(event).catch((error) => {
|
|
730
|
+
this.#onError(toError(error));
|
|
731
|
+
return undefined;
|
|
367
732
|
});
|
|
368
733
|
}
|
|
734
|
+
#metadata(sessionId) {
|
|
735
|
+
const snapshot = this.#runtime.snapshot(sessionId);
|
|
736
|
+
const rootSession = this.#rootSessions.get(sessionId);
|
|
737
|
+
return rootSession ? { ...snapshot, rootSession: structuredClone(rootSession) } : snapshot;
|
|
738
|
+
}
|
|
739
|
+
/** The broadcast shape: session metadata plus a bounded transcript tail. */
|
|
740
|
+
#view(sessionId) {
|
|
741
|
+
const metadata = this.#metadata(sessionId);
|
|
742
|
+
const page = this.#runtime.transcript(sessionId, { limit: SESSION_VIEW_TAIL_MESSAGES });
|
|
743
|
+
return { ...metadata, messageOffset: page.from, messages: page.messages };
|
|
744
|
+
}
|
|
369
745
|
async #publishSnapshot(snapshot) {
|
|
370
746
|
await this.#kernel.dispatch({
|
|
371
747
|
id: this.#idFactory(),
|
|
@@ -380,6 +756,7 @@ export class AgentApplication {
|
|
|
380
756
|
this.#unsubscribers.get(sessionId)?.();
|
|
381
757
|
this.#unsubscribers.delete(sessionId);
|
|
382
758
|
this.#sessions.delete(sessionId);
|
|
759
|
+
this.#rootSessions.delete(sessionId);
|
|
383
760
|
let failure;
|
|
384
761
|
try {
|
|
385
762
|
await this.#runtime.dispose(sessionId);
|
|
@@ -400,6 +777,17 @@ export class AgentApplication {
|
|
|
400
777
|
if (!this.#sessions.has(sessionId))
|
|
401
778
|
throw new CommandError("session_not_found", `Session does not exist: ${sessionId}`);
|
|
402
779
|
}
|
|
780
|
+
#assertRootSession(sessionId) {
|
|
781
|
+
this.#assertSession(sessionId);
|
|
782
|
+
if (!this.#rootSessions.has(sessionId)) {
|
|
783
|
+
throw new CommandError("invalid_session", `Session is not an interactive root session: ${sessionId}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
#assertModelConfigured() {
|
|
787
|
+
if (!this.#modelConfigured) {
|
|
788
|
+
throw new CommandError("model_not_configured", "No model is configured. Set config.model.provider, config.model.model, and credentials before prompting.");
|
|
789
|
+
}
|
|
790
|
+
}
|
|
403
791
|
#requireAutomation() {
|
|
404
792
|
if (!this.#automation)
|
|
405
793
|
throw new CommandError("automation_unavailable", "Scheduler is not initialized");
|
|
@@ -459,6 +847,48 @@ function optionalString(value, path) {
|
|
|
459
847
|
return undefined;
|
|
460
848
|
return nonEmptyString(value, path);
|
|
461
849
|
}
|
|
850
|
+
function optionalSessionTitle(value, path) {
|
|
851
|
+
if (value === undefined)
|
|
852
|
+
return undefined;
|
|
853
|
+
const title = nonEmptyString(value, path).replace(/\s+/g, " ").trim();
|
|
854
|
+
if ([...title].length > 120 || /[\u0000-\u001f]/.test(title)) {
|
|
855
|
+
throw new CommandError("invalid_command", `${path} must contain at most 120 characters without controls`);
|
|
856
|
+
}
|
|
857
|
+
return title;
|
|
858
|
+
}
|
|
859
|
+
function generatedRootSessionId(value) {
|
|
860
|
+
const id = `session-${value}`;
|
|
861
|
+
if (id.length > 128 || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(id)) {
|
|
862
|
+
throw new Error("Generated root session id is invalid");
|
|
863
|
+
}
|
|
864
|
+
return id;
|
|
865
|
+
}
|
|
866
|
+
function optionalIdentifier(value, path) {
|
|
867
|
+
if (value === undefined)
|
|
868
|
+
return undefined;
|
|
869
|
+
if (typeof value !== "string" || !value.trim() || value.length > 200) {
|
|
870
|
+
throw new CommandError("invalid_payload", `${path} must be a bounded non-empty string`);
|
|
871
|
+
}
|
|
872
|
+
return value;
|
|
873
|
+
}
|
|
874
|
+
function optionalBoundedIndex(value, path, maximum = Number.MAX_SAFE_INTEGER) {
|
|
875
|
+
if (value === undefined)
|
|
876
|
+
return undefined;
|
|
877
|
+
if (!Number.isSafeInteger(value) || Number(value) < 0 || Number(value) > maximum) {
|
|
878
|
+
throw new CommandError("invalid_payload", `${path} must be an integer from 0 to ${maximum}`);
|
|
879
|
+
}
|
|
880
|
+
return Number(value);
|
|
881
|
+
}
|
|
882
|
+
function rootMetadata(record) {
|
|
883
|
+
return {
|
|
884
|
+
title: record.title,
|
|
885
|
+
createdAt: record.createdAt,
|
|
886
|
+
updatedAt: record.updatedAt,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function toError(error) {
|
|
890
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
891
|
+
}
|
|
462
892
|
function optionalEnum(value, allowed, path) {
|
|
463
893
|
if (value === undefined)
|
|
464
894
|
return undefined;
|