@roj-ai/sdk 0.1.35 → 0.2.1
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/dist/bootstrap.d.ts +420 -26
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +55 -24
- package/dist/bootstrap.js.map +1 -1
- package/dist/bun-platform/fs.d.ts.map +1 -1
- package/dist/bun-platform/fs.js +1 -0
- package/dist/bun-platform/fs.js.map +1 -1
- package/dist/bun-platform/index.d.ts +4 -1
- package/dist/bun-platform/index.d.ts.map +1 -1
- package/dist/bun-platform/index.js +7 -1
- package/dist/bun-platform/index.js.map +1 -1
- package/dist/bun-platform/server-lifecycle.d.ts +30 -0
- package/dist/bun-platform/server-lifecycle.d.ts.map +1 -0
- package/dist/bun-platform/server-lifecycle.js +182 -0
- package/dist/bun-platform/server-lifecycle.js.map +1 -0
- package/dist/bun-platform/shell.d.ts +33 -0
- package/dist/bun-platform/shell.d.ts.map +1 -0
- package/dist/bun-platform/shell.js +322 -0
- package/dist/bun-platform/shell.js.map +1 -0
- package/dist/config.d.ts +34 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agents/agent.d.ts +42 -7
- package/dist/core/agents/agent.d.ts.map +1 -1
- package/dist/core/agents/agent.js +209 -90
- package/dist/core/agents/agent.js.map +1 -1
- package/dist/core/agents/config.d.ts +8 -1
- package/dist/core/agents/config.d.ts.map +1 -1
- package/dist/core/agents/config.js +5 -1
- package/dist/core/agents/config.js.map +1 -1
- package/dist/core/agents/retry.d.ts +1 -1
- package/dist/core/agents/retry.d.ts.map +1 -1
- package/dist/core/agents/retry.js +14 -16
- package/dist/core/agents/retry.js.map +1 -1
- package/dist/core/agents/state.d.ts +27 -1
- package/dist/core/agents/state.d.ts.map +1 -1
- package/dist/core/agents/state.js +1 -1
- package/dist/core/agents/state.js.map +1 -1
- package/dist/core/context/state.d.ts +3 -3
- package/dist/core/context/state.d.ts.map +1 -1
- package/dist/core/context/state.js +6 -2
- package/dist/core/context/state.js.map +1 -1
- package/dist/core/events/base-event-store.d.ts.map +1 -1
- package/dist/core/events/base-event-store.js +22 -14
- package/dist/core/events/base-event-store.js.map +1 -1
- package/dist/core/events/file.d.ts +39 -1
- package/dist/core/events/file.d.ts.map +1 -1
- package/dist/core/events/file.js +159 -20
- package/dist/core/events/file.js.map +1 -1
- package/dist/core/events/types.d.ts +1 -1
- package/dist/core/file-store/containment.d.ts +19 -0
- package/dist/core/file-store/containment.d.ts.map +1 -0
- package/dist/core/file-store/containment.js +62 -0
- package/dist/core/file-store/containment.js.map +1 -0
- package/dist/core/file-store/file-store.d.ts +22 -2
- package/dist/core/file-store/file-store.d.ts.map +1 -1
- package/dist/core/file-store/file-store.js +88 -20
- package/dist/core/file-store/file-store.js.map +1 -1
- package/dist/core/file-store/types.d.ts +13 -1
- package/dist/core/file-store/types.d.ts.map +1 -1
- package/dist/core/image/image-processor.d.ts.map +1 -1
- package/dist/core/image/image-processor.js +3 -1
- package/dist/core/image/image-processor.js.map +1 -1
- package/dist/core/image/types.d.ts +2 -0
- package/dist/core/image/types.d.ts.map +1 -1
- package/dist/core/image/vips-resizer.d.ts.map +1 -1
- package/dist/core/image/vips-resizer.js +81 -33
- package/dist/core/image/vips-resizer.js.map +1 -1
- package/dist/core/llm/anthropic.d.ts +0 -1
- package/dist/core/llm/anthropic.d.ts.map +1 -1
- package/dist/core/llm/anthropic.js +101 -74
- package/dist/core/llm/anthropic.js.map +1 -1
- package/dist/core/llm/logger.d.ts +14 -2
- package/dist/core/llm/logger.d.ts.map +1 -1
- package/dist/core/llm/logger.js +161 -36
- package/dist/core/llm/logger.js.map +1 -1
- package/dist/core/llm/openrouter.d.ts +0 -1
- package/dist/core/llm/openrouter.d.ts.map +1 -1
- package/dist/core/llm/openrouter.js +70 -74
- package/dist/core/llm/openrouter.js.map +1 -1
- package/dist/core/llm/provider-request.d.ts +10 -0
- package/dist/core/llm/provider-request.d.ts.map +1 -0
- package/dist/core/llm/provider-request.js +41 -0
- package/dist/core/llm/provider-request.js.map +1 -0
- package/dist/core/llm/provider.d.ts +45 -3
- package/dist/core/llm/provider.d.ts.map +1 -1
- package/dist/core/llm/provider.js +26 -0
- package/dist/core/llm/provider.js.map +1 -1
- package/dist/core/llm/state.d.ts +4 -2
- package/dist/core/llm/state.d.ts.map +1 -1
- package/dist/core/llm/state.js +15 -4
- package/dist/core/llm/state.js.map +1 -1
- package/dist/core/plugins/plugin-builder.d.ts +57 -3
- package/dist/core/plugins/plugin-builder.d.ts.map +1 -1
- package/dist/core/plugins/plugin-builder.js +68 -14
- package/dist/core/plugins/plugin-builder.js.map +1 -1
- package/dist/core/preset/preset-builder.d.ts +3 -2
- package/dist/core/preset/preset-builder.d.ts.map +1 -1
- package/dist/core/preset/preset-builder.js +7 -16
- package/dist/core/preset/preset-builder.js.map +1 -1
- package/dist/core/sessions/context.d.ts +23 -5
- package/dist/core/sessions/context.d.ts.map +1 -1
- package/dist/core/sessions/runtime-activity.d.ts +33 -0
- package/dist/core/sessions/runtime-activity.d.ts.map +1 -0
- package/dist/core/sessions/runtime-activity.js +55 -0
- package/dist/core/sessions/runtime-activity.js.map +1 -0
- package/dist/core/sessions/schema.d.ts +23 -2
- package/dist/core/sessions/schema.d.ts.map +1 -1
- package/dist/core/sessions/schema.js +24 -3
- package/dist/core/sessions/schema.js.map +1 -1
- package/dist/core/sessions/session-manager.d.ts +83 -6
- package/dist/core/sessions/session-manager.d.ts.map +1 -1
- package/dist/core/sessions/session-manager.js +517 -118
- package/dist/core/sessions/session-manager.js.map +1 -1
- package/dist/core/sessions/session-store.d.ts +18 -0
- package/dist/core/sessions/session-store.d.ts.map +1 -1
- package/dist/core/sessions/session-store.js +33 -0
- package/dist/core/sessions/session-store.js.map +1 -1
- package/dist/core/sessions/session.d.ts +42 -6
- package/dist/core/sessions/session.d.ts.map +1 -1
- package/dist/core/sessions/session.js +176 -44
- package/dist/core/sessions/session.js.map +1 -1
- package/dist/core/sessions/state.d.ts +10 -1
- package/dist/core/sessions/state.d.ts.map +1 -1
- package/dist/core/sessions/state.js +16 -1
- package/dist/core/sessions/state.js.map +1 -1
- package/dist/core/system.d.ts +9 -0
- package/dist/core/system.d.ts.map +1 -1
- package/dist/core/system.js +4 -1
- package/dist/core/system.js.map +1 -1
- package/dist/core/tools/executor.d.ts +1 -1
- package/dist/core/wake-key.d.ts +34 -0
- package/dist/core/wake-key.d.ts.map +1 -0
- package/dist/core/wake-key.js +51 -0
- package/dist/core/wake-key.js.map +1 -0
- package/dist/index.d.ts +24 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -8
- package/dist/index.js.map +1 -1
- package/dist/lib/archive/archive-inspection.d.ts +48 -0
- package/dist/lib/archive/archive-inspection.d.ts.map +1 -0
- package/dist/lib/archive/archive-inspection.fixtures.d.ts +7 -0
- package/dist/lib/archive/archive-inspection.fixtures.d.ts.map +1 -0
- package/dist/lib/archive/archive-inspection.fixtures.js +134 -0
- package/dist/lib/archive/archive-inspection.fixtures.js.map +1 -0
- package/dist/lib/archive/archive-inspection.js +253 -0
- package/dist/lib/archive/archive-inspection.js.map +1 -0
- package/dist/lib/archive/index.d.ts +3 -0
- package/dist/lib/archive/index.d.ts.map +1 -0
- package/dist/lib/archive/index.js +2 -0
- package/dist/lib/archive/index.js.map +1 -0
- package/dist/lib/logger/console.d.ts +5 -12
- package/dist/lib/logger/console.d.ts.map +1 -1
- package/dist/lib/logger/console.js +5 -47
- package/dist/lib/logger/console.js.map +1 -1
- package/dist/lib/logger/file.d.ts +8 -11
- package/dist/lib/logger/file.d.ts.map +1 -1
- package/dist/lib/logger/file.js +26 -37
- package/dist/lib/logger/file.js.map +1 -1
- package/dist/lib/logger/index.d.ts +3 -1
- package/dist/lib/logger/index.d.ts.map +1 -1
- package/dist/lib/logger/index.js +3 -1
- package/dist/lib/logger/index.js.map +1 -1
- package/dist/lib/logger/jsonl.d.ts +21 -0
- package/dist/lib/logger/jsonl.d.ts.map +1 -0
- package/dist/lib/logger/jsonl.js +29 -0
- package/dist/lib/logger/jsonl.js.map +1 -0
- package/dist/lib/logger/logger.d.ts +22 -0
- package/dist/lib/logger/logger.d.ts.map +1 -1
- package/dist/lib/logger/logger.js +37 -0
- package/dist/lib/logger/logger.js.map +1 -1
- package/dist/lib/logger/session-log.d.ts +21 -0
- package/dist/lib/logger/session-log.d.ts.map +1 -0
- package/dist/lib/logger/session-log.js +37 -0
- package/dist/lib/logger/session-log.js.map +1 -0
- package/dist/lib/utils/concurrency.d.ts +1 -1
- package/dist/lib/utils/concurrency.d.ts.map +1 -1
- package/dist/lib/utils/concurrency.js +30 -7
- package/dist/lib/utils/concurrency.js.map +1 -1
- package/dist/lib/utils/fs-batch.d.ts +17 -0
- package/dist/lib/utils/fs-batch.d.ts.map +1 -0
- package/dist/lib/utils/fs-batch.js +38 -0
- package/dist/lib/utils/fs-batch.js.map +1 -0
- package/dist/lib/utils/result.d.ts +7 -22
- package/dist/lib/utils/result.d.ts.map +1 -1
- package/dist/lib/utils/result.js +6 -17
- package/dist/lib/utils/result.js.map +1 -1
- package/dist/lib/utils/sleep.d.ts +10 -0
- package/dist/lib/utils/sleep.d.ts.map +1 -0
- package/dist/lib/utils/sleep.js +26 -0
- package/dist/lib/utils/sleep.js.map +1 -0
- package/dist/platform/fs-revision.d.ts +21 -0
- package/dist/platform/fs-revision.d.ts.map +1 -0
- package/dist/platform/fs-revision.js +9 -0
- package/dist/platform/fs-revision.js.map +1 -0
- package/dist/platform/fs.d.ts +104 -1
- package/dist/platform/fs.d.ts.map +1 -1
- package/dist/platform/git.d.ts +59 -0
- package/dist/platform/git.d.ts.map +1 -0
- package/dist/platform/git.js +8 -0
- package/dist/platform/git.js.map +1 -0
- package/dist/platform/index.d.ts +40 -1
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/index.js +1 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/llm-call-log.d.ts +80 -0
- package/dist/platform/llm-call-log.d.ts.map +1 -0
- package/dist/platform/llm-call-log.js +12 -0
- package/dist/platform/llm-call-log.js.map +1 -0
- package/dist/platform/scheduler.d.ts +32 -0
- package/dist/platform/scheduler.d.ts.map +1 -0
- package/dist/platform/scheduler.js +46 -0
- package/dist/platform/scheduler.js.map +1 -0
- package/dist/platform/session-log.d.ts +36 -0
- package/dist/platform/session-log.d.ts.map +1 -0
- package/dist/platform/session-log.js +12 -0
- package/dist/platform/session-log.js.map +1 -0
- package/dist/platform/shell.d.ts +83 -0
- package/dist/platform/shell.d.ts.map +1 -0
- package/dist/platform/shell.js +13 -0
- package/dist/platform/shell.js.map +1 -0
- package/dist/plugins/agent-status/plugin.d.ts.map +1 -1
- package/dist/plugins/agent-status/plugin.js +1 -0
- package/dist/plugins/agent-status/plugin.js.map +1 -1
- package/dist/plugins/agents/plugin.d.ts.map +1 -1
- package/dist/plugins/agents/plugin.js +129 -62
- package/dist/plugins/agents/plugin.js.map +1 -1
- package/dist/plugins/filesystem/listing.d.ts +3 -0
- package/dist/plugins/filesystem/listing.d.ts.map +1 -1
- package/dist/plugins/filesystem/listing.js +66 -10
- package/dist/plugins/filesystem/listing.js.map +1 -1
- package/dist/plugins/filesystem/plugin.d.ts.map +1 -1
- package/dist/plugins/filesystem/plugin.js +8 -6
- package/dist/plugins/filesystem/plugin.js.map +1 -1
- package/dist/plugins/git-status/plugin.d.ts +34 -7
- package/dist/plugins/git-status/plugin.d.ts.map +1 -1
- package/dist/plugins/git-status/plugin.js +168 -64
- package/dist/plugins/git-status/plugin.js.map +1 -1
- package/dist/plugins/llm-debug/plugin.d.ts.map +1 -1
- package/dist/plugins/llm-debug/plugin.js +1 -0
- package/dist/plugins/llm-debug/plugin.js.map +1 -1
- package/dist/plugins/logs/plugin.d.ts +1 -4
- package/dist/plugins/logs/plugin.d.ts.map +1 -1
- package/dist/plugins/logs/plugin.js +7 -0
- package/dist/plugins/logs/plugin.js.map +1 -1
- package/dist/plugins/mailbox/helpers.d.ts +2 -2
- package/dist/plugins/mailbox/helpers.js +1 -1
- package/dist/plugins/mailbox/plugin.d.ts.map +1 -1
- package/dist/plugins/mailbox/plugin.js +38 -43
- package/dist/plugins/mailbox/plugin.js.map +1 -1
- package/dist/plugins/mailbox/query.d.ts +7 -2
- package/dist/plugins/mailbox/query.d.ts.map +1 -1
- package/dist/plugins/mailbox/query.js +14 -7
- package/dist/plugins/mailbox/query.js.map +1 -1
- package/dist/plugins/mailbox/state.d.ts +5 -4
- package/dist/plugins/mailbox/state.d.ts.map +1 -1
- package/dist/plugins/mailbox/state.js +6 -5
- package/dist/plugins/mailbox/state.js.map +1 -1
- package/dist/plugins/resources/filename.d.ts +4 -0
- package/dist/plugins/resources/filename.d.ts.map +1 -0
- package/dist/plugins/resources/filename.js +18 -0
- package/dist/plugins/resources/filename.js.map +1 -0
- package/dist/plugins/resources/plugin.d.ts +3 -0
- package/dist/plugins/resources/plugin.d.ts.map +1 -1
- package/dist/plugins/resources/plugin.js +97 -60
- package/dist/plugins/resources/plugin.js.map +1 -1
- package/dist/plugins/resources/state.d.ts +1 -1
- package/dist/plugins/resources/state.js +1 -1
- package/dist/plugins/result-eviction/plugin.d.ts.map +1 -1
- package/dist/plugins/result-eviction/plugin.js +6 -1
- package/dist/plugins/result-eviction/plugin.js.map +1 -1
- package/dist/plugins/services/plugin.d.ts +4 -1
- package/dist/plugins/services/plugin.d.ts.map +1 -1
- package/dist/plugins/services/plugin.js +251 -100
- package/dist/plugins/services/plugin.js.map +1 -1
- package/dist/plugins/services/prompt.d.ts.map +1 -1
- package/dist/plugins/services/prompt.js +1 -4
- package/dist/plugins/services/prompt.js.map +1 -1
- package/dist/plugins/services/schema.d.ts +15 -5
- package/dist/plugins/services/schema.d.ts.map +1 -1
- package/dist/plugins/services/service.d.ts +100 -18
- package/dist/plugins/services/service.d.ts.map +1 -1
- package/dist/plugins/services/service.js +680 -230
- package/dist/plugins/services/service.js.map +1 -1
- package/dist/plugins/session-lifecycle/plugin.d.ts +21 -1
- package/dist/plugins/session-lifecycle/plugin.d.ts.map +1 -1
- package/dist/plugins/session-lifecycle/plugin.js +53 -40
- package/dist/plugins/session-lifecycle/plugin.js.map +1 -1
- package/dist/plugins/session-state/plugin.d.ts +21 -13
- package/dist/plugins/session-state/plugin.d.ts.map +1 -1
- package/dist/plugins/session-state/plugin.js +214 -79
- package/dist/plugins/session-state/plugin.js.map +1 -1
- package/dist/plugins/session-stats/plugin.d.ts.map +1 -1
- package/dist/plugins/session-stats/plugin.js +1 -0
- package/dist/plugins/session-stats/plugin.js.map +1 -1
- package/dist/plugins/shell/executor.d.ts +17 -15
- package/dist/plugins/shell/executor.d.ts.map +1 -1
- package/dist/plugins/shell/executor.js +130 -195
- package/dist/plugins/shell/executor.js.map +1 -1
- package/dist/plugins/shell/index.d.ts +2 -2
- package/dist/plugins/shell/index.d.ts.map +1 -1
- package/dist/plugins/shell/index.js +2 -2
- package/dist/plugins/shell/index.js.map +1 -1
- package/dist/plugins/shell/plugin.d.ts +5 -4
- package/dist/plugins/shell/plugin.d.ts.map +1 -1
- package/dist/plugins/shell/plugin.js +4 -8
- package/dist/plugins/shell/plugin.js.map +1 -1
- package/dist/plugins/skills/discovery.d.ts.map +1 -1
- package/dist/plugins/skills/discovery.js +10 -8
- package/dist/plugins/skills/discovery.js.map +1 -1
- package/dist/plugins/skills/index.d.ts +1 -1
- package/dist/plugins/skills/index.d.ts.map +1 -1
- package/dist/plugins/skills/plugin.d.ts +1 -1
- package/dist/plugins/skills/plugin.d.ts.map +1 -1
- package/dist/plugins/skills/plugin.js +23 -30
- package/dist/plugins/skills/plugin.js.map +1 -1
- package/dist/plugins/skills/prompts.d.ts +3 -3
- package/dist/plugins/skills/prompts.d.ts.map +1 -1
- package/dist/plugins/skills/prompts.js.map +1 -1
- package/dist/plugins/skills/schema.d.ts +4 -2
- package/dist/plugins/skills/schema.d.ts.map +1 -1
- package/dist/plugins/todo/schema.d.ts +1 -1
- package/dist/plugins/uploads/plugin.d.ts +11 -4
- package/dist/plugins/uploads/plugin.d.ts.map +1 -1
- package/dist/plugins/uploads/plugin.js +891 -225
- package/dist/plugins/uploads/plugin.js.map +1 -1
- package/dist/plugins/uploads/preprocessor.d.ts +14 -0
- package/dist/plugins/uploads/preprocessor.d.ts.map +1 -1
- package/dist/plugins/uploads/preprocessor.js +11 -0
- package/dist/plugins/uploads/preprocessor.js.map +1 -1
- package/dist/plugins/uploads/preprocessors/image-classifier.d.ts +1 -1
- package/dist/plugins/uploads/preprocessors/image-classifier.d.ts.map +1 -1
- package/dist/plugins/uploads/preprocessors/image-classifier.js +51 -26
- package/dist/plugins/uploads/preprocessors/image-classifier.js.map +1 -1
- package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.d.ts +2 -2
- package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.d.ts.map +1 -1
- package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js +41 -16
- package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js.map +1 -1
- package/dist/plugins/uploads/preprocessors/pdf-preprocessor.d.ts +1 -1
- package/dist/plugins/uploads/preprocessors/pdf-preprocessor.d.ts.map +1 -1
- package/dist/plugins/uploads/preprocessors/pdf-preprocessor.js +29 -14
- package/dist/plugins/uploads/preprocessors/pdf-preprocessor.js.map +1 -1
- package/dist/plugins/uploads/preprocessors/zip-preprocessor.d.ts +1 -1
- package/dist/plugins/uploads/preprocessors/zip-preprocessor.d.ts.map +1 -1
- package/dist/plugins/uploads/preprocessors/zip-preprocessor.js +62 -34
- package/dist/plugins/uploads/preprocessors/zip-preprocessor.js.map +1 -1
- package/dist/plugins/uploads/schema.d.ts +25 -2
- package/dist/plugins/uploads/schema.d.ts.map +1 -1
- package/dist/plugins/uploads/schema.js +24 -3
- package/dist/plugins/uploads/schema.js.map +1 -1
- package/dist/plugins/uploads/state.d.ts +13 -1
- package/dist/plugins/uploads/state.d.ts.map +1 -1
- package/dist/plugins/uploads/state.js +4 -1
- package/dist/plugins/uploads/state.js.map +1 -1
- package/dist/plugins/user-chat/plugin.d.ts +12 -5
- package/dist/plugins/user-chat/plugin.d.ts.map +1 -1
- package/dist/plugins/user-chat/plugin.js +141 -32
- package/dist/plugins/user-chat/plugin.js.map +1 -1
- package/dist/plugins/workers/context.d.ts +35 -13
- package/dist/plugins/workers/context.d.ts.map +1 -1
- package/dist/plugins/workers/context.js +82 -32
- package/dist/plugins/workers/context.js.map +1 -1
- package/dist/plugins/workers/definition.d.ts +7 -0
- package/dist/plugins/workers/definition.d.ts.map +1 -1
- package/dist/plugins/workers/definition.js.map +1 -1
- package/dist/plugins/workers/index.d.ts +4 -4
- package/dist/plugins/workers/index.d.ts.map +1 -1
- package/dist/plugins/workers/index.js +2 -2
- package/dist/plugins/workers/index.js.map +1 -1
- package/dist/plugins/workers/plugin.d.ts +10 -57
- package/dist/plugins/workers/plugin.d.ts.map +1 -1
- package/dist/plugins/workers/plugin.js +385 -123
- package/dist/plugins/workers/plugin.js.map +1 -1
- package/dist/plugins/workers/state.d.ts +69 -0
- package/dist/plugins/workers/state.d.ts.map +1 -0
- package/dist/plugins/workers/state.js +52 -0
- package/dist/plugins/workers/state.js.map +1 -0
- package/dist/plugins/workers/worker.d.ts +15 -4
- package/dist/plugins/workers/worker.d.ts.map +1 -1
- package/dist/testing/conformance.d.ts +106 -0
- package/dist/testing/conformance.d.ts.map +1 -0
- package/dist/testing/conformance.js +1463 -0
- package/dist/testing/conformance.js.map +1 -0
- package/dist/testing/node-platform.d.ts.map +1 -1
- package/dist/testing/node-platform.js +5 -0
- package/dist/testing/node-platform.js.map +1 -1
- package/dist/testing/test-harness.d.ts +11 -1
- package/dist/testing/test-harness.d.ts.map +1 -1
- package/dist/testing/test-harness.js +17 -37
- package/dist/testing/test-harness.js.map +1 -1
- package/dist/testing/wait-helpers.d.ts.map +1 -1
- package/dist/testing/wait-helpers.js +1 -3
- package/dist/testing/wait-helpers.js.map +1 -1
- package/dist/transport/adapter/client-adapter.d.ts +6 -0
- package/dist/transport/adapter/client-adapter.d.ts.map +1 -1
- package/dist/transport/adapter/client-adapter.js +50 -3
- package/dist/transport/adapter/client-adapter.js.map +1 -1
- package/dist/transport/adapter/heartbeat.d.ts +16 -0
- package/dist/transport/adapter/heartbeat.d.ts.map +1 -0
- package/dist/transport/adapter/heartbeat.js +41 -0
- package/dist/transport/adapter/heartbeat.js.map +1 -0
- package/dist/transport/adapter/server-adapter.d.ts.map +1 -1
- package/dist/transport/adapter/server-adapter.js +6 -0
- package/dist/transport/adapter/server-adapter.js.map +1 -1
- package/dist/transport/http/app.d.ts +3 -30
- package/dist/transport/http/app.d.ts.map +1 -1
- package/dist/transport/http/app.js +3 -7
- package/dist/transport/http/app.js.map +1 -1
- package/dist/transport/http/context.d.ts +40 -0
- package/dist/transport/http/context.d.ts.map +1 -0
- package/dist/transport/http/context.js +17 -0
- package/dist/transport/http/context.js.map +1 -0
- package/dist/transport/http/fetch-guard.d.ts +49 -0
- package/dist/transport/http/fetch-guard.d.ts.map +1 -0
- package/dist/transport/http/fetch-guard.js +312 -0
- package/dist/transport/http/fetch-guard.js.map +1 -0
- package/dist/transport/http/index.d.ts +1 -1
- package/dist/transport/http/index.d.ts.map +1 -1
- package/dist/transport/http/middleware/bearer-auth.d.ts +1 -1
- package/dist/transport/http/middleware/bearer-auth.d.ts.map +1 -1
- package/dist/transport/http/middleware/error-handler.d.ts +2 -2
- package/dist/transport/http/middleware/error-handler.d.ts.map +1 -1
- package/dist/transport/http/path-containment.d.ts +20 -0
- package/dist/transport/http/path-containment.d.ts.map +1 -0
- package/dist/transport/http/path-containment.js +31 -0
- package/dist/transport/http/path-containment.js.map +1 -0
- package/dist/transport/http/responses.d.ts +30 -0
- package/dist/transport/http/responses.d.ts.map +1 -0
- package/dist/transport/http/responses.js +5 -0
- package/dist/transport/http/responses.js.map +1 -0
- package/dist/transport/http/routes/files.d.ts +1 -1
- package/dist/transport/http/routes/files.d.ts.map +1 -1
- package/dist/transport/http/routes/files.js +54 -155
- package/dist/transport/http/routes/files.js.map +1 -1
- package/dist/transport/http/routes/resources.d.ts +1 -1
- package/dist/transport/http/routes/resources.d.ts.map +1 -1
- package/dist/transport/http/routes/resources.js +92 -57
- package/dist/transport/http/routes/resources.js.map +1 -1
- package/dist/transport/http/routes/rpc.d.ts +6 -1
- package/dist/transport/http/routes/rpc.d.ts.map +1 -1
- package/dist/transport/http/routes/rpc.js +51 -21
- package/dist/transport/http/routes/rpc.js.map +1 -1
- package/dist/transport/http/routes/upload.d.ts +1 -1
- package/dist/transport/http/routes/upload.d.ts.map +1 -1
- package/dist/transport/http/routes/upload.js +256 -169
- package/dist/transport/http/routes/upload.js.map +1 -1
- package/dist/user-config.d.ts +34 -7
- package/dist/user-config.d.ts.map +1 -1
- package/dist/user-config.js +71 -0
- package/dist/user-config.js.map +1 -1
- package/package.json +18 -8
- package/src/bootstrap.ts +119 -35
- package/src/bun-platform/fs.ts +1 -0
- package/src/bun-platform/index.ts +18 -1
- package/src/bun-platform/server-lifecycle.ts +218 -0
- package/src/bun-platform/shell.ts +362 -0
- package/src/config.ts +130 -0
- package/src/core/agents/agent.ts +219 -95
- package/src/core/agents/config.ts +8 -0
- package/src/core/agents/retry.ts +16 -17
- package/src/core/agents/state.ts +28 -0
- package/src/core/context/state.ts +4 -0
- package/src/core/events/base-event-store.ts +24 -16
- package/src/core/events/file.ts +198 -22
- package/src/core/file-store/containment.ts +75 -0
- package/src/core/file-store/file-store.ts +106 -25
- package/src/core/file-store/types.ts +14 -1
- package/src/core/image/image-processor.ts +3 -1
- package/src/core/image/types.ts +2 -0
- package/src/core/image/vips-resizer.ts +102 -37
- package/src/core/llm/anthropic.ts +127 -82
- package/src/core/llm/logger.ts +175 -37
- package/src/core/llm/openrouter.ts +82 -81
- package/src/core/llm/provider-request.ts +40 -0
- package/src/core/llm/provider.ts +72 -3
- package/src/core/llm/state.ts +12 -0
- package/src/core/plugins/plugin-builder.ts +106 -14
- package/src/core/preset/preset-builder.ts +12 -22
- package/src/core/sessions/context.ts +18 -0
- package/src/core/sessions/runtime-activity.ts +78 -0
- package/src/core/sessions/schema.ts +29 -3
- package/src/core/sessions/session-manager.ts +560 -140
- package/src/core/sessions/session-store.ts +38 -0
- package/src/core/sessions/session.ts +187 -47
- package/src/core/sessions/state.ts +16 -0
- package/src/core/system.ts +15 -0
- package/src/core/wake-key.ts +65 -0
- package/src/index.ts +31 -12
- package/src/lib/archive/archive-inspection.fixtures.ts +138 -0
- package/src/lib/archive/archive-inspection.ts +359 -0
- package/src/lib/archive/index.ts +17 -0
- package/src/lib/logger/console.ts +7 -57
- package/src/lib/logger/file.ts +28 -50
- package/src/lib/logger/index.ts +3 -1
- package/src/lib/logger/jsonl.ts +36 -0
- package/src/lib/logger/logger.ts +48 -0
- package/src/lib/logger/session-log.ts +42 -0
- package/src/lib/utils/concurrency.ts +37 -8
- package/src/lib/utils/fs-batch.ts +49 -0
- package/src/lib/utils/result.ts +7 -31
- package/src/lib/utils/sleep.ts +23 -0
- package/src/platform/fs-revision.ts +21 -0
- package/src/platform/fs.ts +109 -1
- package/src/platform/git.ts +67 -0
- package/src/platform/index.ts +51 -1
- package/src/platform/llm-call-log.ts +86 -0
- package/src/platform/scheduler.ts +71 -0
- package/src/platform/session-log.ts +39 -0
- package/src/platform/shell.ts +89 -0
- package/src/plugins/agent-status/plugin.ts +1 -0
- package/src/plugins/agents/plugin.ts +134 -81
- package/src/plugins/filesystem/listing.ts +70 -12
- package/src/plugins/filesystem/plugin.ts +8 -6
- package/src/plugins/git-status/plugin.ts +204 -77
- package/src/plugins/llm-debug/plugin.ts +1 -0
- package/src/plugins/logs/plugin.ts +9 -0
- package/src/plugins/mailbox/plugin.ts +46 -64
- package/src/plugins/mailbox/query.ts +15 -7
- package/src/plugins/mailbox/state.ts +1 -0
- package/src/plugins/resources/filename.ts +19 -0
- package/src/plugins/resources/plugin.ts +111 -59
- package/src/plugins/result-eviction/plugin.ts +6 -1
- package/src/plugins/services/plugin.ts +254 -98
- package/src/plugins/services/prompt.ts +1 -6
- package/src/plugins/services/schema.ts +17 -3
- package/src/plugins/services/service.ts +702 -252
- package/src/plugins/session-lifecycle/plugin.ts +56 -41
- package/src/plugins/session-state/plugin.ts +242 -88
- package/src/plugins/session-stats/plugin.ts +1 -0
- package/src/plugins/shell/executor.ts +144 -226
- package/src/plugins/shell/index.ts +1 -4
- package/src/plugins/shell/plugin.ts +7 -9
- package/src/plugins/skills/discovery.ts +10 -8
- package/src/plugins/skills/index.ts +1 -1
- package/src/plugins/skills/plugin.ts +19 -27
- package/src/plugins/skills/prompts.ts +3 -3
- package/src/plugins/skills/schema.ts +5 -2
- package/src/plugins/uploads/plugin.ts +991 -273
- package/src/plugins/uploads/preprocessor.ts +25 -0
- package/src/plugins/uploads/preprocessors/image-classifier.ts +56 -25
- package/src/plugins/uploads/preprocessors/markitdown-preprocessor.ts +41 -15
- package/src/plugins/uploads/preprocessors/pdf-preprocessor.ts +32 -16
- package/src/plugins/uploads/preprocessors/zip-preprocessor.ts +67 -38
- package/src/plugins/uploads/schema.ts +30 -3
- package/src/plugins/uploads/state.ts +12 -0
- package/src/plugins/user-chat/plugin.ts +196 -40
- package/src/plugins/workers/context.ts +102 -39
- package/src/plugins/workers/definition.ts +7 -0
- package/src/plugins/workers/index.ts +4 -4
- package/src/plugins/workers/plugin.ts +461 -151
- package/src/plugins/workers/state.ts +64 -0
- package/src/plugins/workers/worker.ts +15 -4
- package/src/testing/conformance.ts +1751 -0
- package/src/testing/node-platform.ts +5 -0
- package/src/testing/test-harness.ts +21 -38
- package/src/testing/wait-helpers.ts +1 -4
- package/src/transport/adapter/client-adapter.ts +60 -3
- package/src/transport/adapter/heartbeat.ts +50 -0
- package/src/transport/adapter/server-adapter.ts +6 -0
- package/src/transport/http/app.ts +7 -35
- package/src/transport/http/context.ts +46 -0
- package/src/transport/http/fetch-guard.ts +318 -0
- package/src/transport/http/index.ts +1 -1
- package/src/transport/http/middleware/bearer-auth.ts +1 -1
- package/src/transport/http/middleware/error-handler.ts +1 -1
- package/src/transport/http/path-containment.ts +44 -0
- package/src/transport/http/responses.ts +20 -0
- package/src/transport/http/routes/files.ts +69 -159
- package/src/transport/http/routes/resources.ts +124 -93
- package/src/transport/http/routes/rpc.ts +63 -26
- package/src/transport/http/routes/upload.ts +327 -258
- package/src/user-config.ts +93 -7
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -155
- package/dist/config.test.js.map +0 -1
- package/dist/core/agent-loop.integration.test.d.ts +0 -2
- package/dist/core/agent-loop.integration.test.d.ts.map +0 -1
- package/dist/core/agent-loop.integration.test.js +0 -414
- package/dist/core/agent-loop.integration.test.js.map +0 -1
- package/dist/core/agents/agent-config.test.d.ts +0 -2
- package/dist/core/agents/agent-config.test.d.ts.map +0 -1
- package/dist/core/agents/agent-config.test.js +0 -194
- package/dist/core/agents/agent-config.test.js.map +0 -1
- package/dist/core/agents/agent-shutdown.test.d.ts +0 -6
- package/dist/core/agents/agent-shutdown.test.d.ts.map +0 -1
- package/dist/core/agents/agent-shutdown.test.js +0 -180
- package/dist/core/agents/agent-shutdown.test.js.map +0 -1
- package/dist/core/agents/agent.test.d.ts +0 -2
- package/dist/core/agents/agent.test.d.ts.map +0 -1
- package/dist/core/agents/agent.test.js +0 -313
- package/dist/core/agents/agent.test.js.map +0 -1
- package/dist/core/agents/communicator.d.ts +0 -3
- package/dist/core/agents/communicator.d.ts.map +0 -1
- package/dist/core/agents/communicator.js +0 -13
- package/dist/core/agents/communicator.js.map +0 -1
- package/dist/core/agents/handler-events.test.d.ts +0 -5
- package/dist/core/agents/handler-events.test.d.ts.map +0 -1
- package/dist/core/agents/handler-events.test.js +0 -115
- package/dist/core/agents/handler-events.test.js.map +0 -1
- package/dist/core/agents/response-sanitizer.test.d.ts +0 -2
- package/dist/core/agents/response-sanitizer.test.d.ts.map +0 -1
- package/dist/core/agents/response-sanitizer.test.js +0 -101
- package/dist/core/agents/response-sanitizer.test.js.map +0 -1
- package/dist/core/error-recovery.integration.test.d.ts +0 -2
- package/dist/core/error-recovery.integration.test.d.ts.map +0 -1
- package/dist/core/error-recovery.integration.test.js +0 -288
- package/dist/core/error-recovery.integration.test.js.map +0 -1
- package/dist/core/event-sourcing.integration.test.d.ts +0 -2
- package/dist/core/event-sourcing.integration.test.d.ts.map +0 -1
- package/dist/core/event-sourcing.integration.test.js +0 -154
- package/dist/core/event-sourcing.integration.test.js.map +0 -1
- package/dist/core/events/file.test.d.ts +0 -2
- package/dist/core/events/file.test.d.ts.map +0 -1
- package/dist/core/events/file.test.js +0 -284
- package/dist/core/events/file.test.js.map +0 -1
- package/dist/core/events/memory.test.d.ts +0 -2
- package/dist/core/events/memory.test.d.ts.map +0 -1
- package/dist/core/events/memory.test.js +0 -502
- package/dist/core/events/memory.test.js.map +0 -1
- package/dist/core/image/image-processor.test.d.ts +0 -2
- package/dist/core/image/image-processor.test.d.ts.map +0 -1
- package/dist/core/image/image-processor.test.js +0 -171
- package/dist/core/image/image-processor.test.js.map +0 -1
- package/dist/core/image/vips-resizer.test.d.ts +0 -2
- package/dist/core/image/vips-resizer.test.d.ts.map +0 -1
- package/dist/core/image/vips-resizer.test.js +0 -336
- package/dist/core/image/vips-resizer.test.js.map +0 -1
- package/dist/core/llm/anthropic.test.d.ts +0 -2
- package/dist/core/llm/anthropic.test.d.ts.map +0 -1
- package/dist/core/llm/anthropic.test.js +0 -464
- package/dist/core/llm/anthropic.test.js.map +0 -1
- package/dist/core/llm/cache-breakpoints.test.d.ts +0 -2
- package/dist/core/llm/cache-breakpoints.test.d.ts.map +0 -1
- package/dist/core/llm/cache-breakpoints.test.js +0 -69
- package/dist/core/llm/cache-breakpoints.test.js.map +0 -1
- package/dist/core/llm/cache-live.test.d.ts +0 -16
- package/dist/core/llm/cache-live.test.d.ts.map +0 -1
- package/dist/core/llm/cache-live.test.js +0 -137
- package/dist/core/llm/cache-live.test.js.map +0 -1
- package/dist/core/llm/logger.test.d.ts +0 -7
- package/dist/core/llm/logger.test.d.ts.map +0 -1
- package/dist/core/llm/logger.test.js +0 -228
- package/dist/core/llm/logger.test.js.map +0 -1
- package/dist/core/llm/message-sanitization.test.d.ts +0 -2
- package/dist/core/llm/message-sanitization.test.d.ts.map +0 -1
- package/dist/core/llm/message-sanitization.test.js +0 -181
- package/dist/core/llm/message-sanitization.test.js.map +0 -1
- package/dist/core/llm/mock.test.d.ts +0 -2
- package/dist/core/llm/mock.test.d.ts.map +0 -1
- package/dist/core/llm/mock.test.js +0 -318
- package/dist/core/llm/mock.test.js.map +0 -1
- package/dist/core/llm/openrouter-mapping.test.d.ts +0 -2
- package/dist/core/llm/openrouter-mapping.test.d.ts.map +0 -1
- package/dist/core/llm/openrouter-mapping.test.js +0 -172
- package/dist/core/llm/openrouter-mapping.test.js.map +0 -1
- package/dist/core/llm/openrouter.test.d.ts +0 -2
- package/dist/core/llm/openrouter.test.d.ts.map +0 -1
- package/dist/core/llm/openrouter.test.js +0 -377
- package/dist/core/llm/openrouter.test.js.map +0 -1
- package/dist/core/llm/provider-integration.test.d.ts +0 -12
- package/dist/core/llm/provider-integration.test.d.ts.map +0 -1
- package/dist/core/llm/provider-integration.test.js +0 -350
- package/dist/core/llm/provider-integration.test.js.map +0 -1
- package/dist/core/llm/routing-provider.test.d.ts +0 -2
- package/dist/core/llm/routing-provider.test.d.ts.map +0 -1
- package/dist/core/llm/routing-provider.test.js +0 -94
- package/dist/core/llm/routing-provider.test.js.map +0 -1
- package/dist/core/llm/snapshot-middleware.test.d.ts +0 -2
- package/dist/core/llm/snapshot-middleware.test.d.ts.map +0 -1
- package/dist/core/llm/snapshot-middleware.test.js +0 -144
- package/dist/core/llm/snapshot-middleware.test.js.map +0 -1
- package/dist/core/multi-agent.integration.test.d.ts +0 -2
- package/dist/core/multi-agent.integration.test.d.ts.map +0 -1
- package/dist/core/multi-agent.integration.test.js +0 -298
- package/dist/core/multi-agent.integration.test.js.map +0 -1
- package/dist/core/plugin-hooks.integration.test.d.ts +0 -2
- package/dist/core/plugin-hooks.integration.test.d.ts.map +0 -1
- package/dist/core/plugin-hooks.integration.test.js +0 -458
- package/dist/core/plugin-hooks.integration.test.js.map +0 -1
- package/dist/core/session-lifecycle.integration.test.d.ts +0 -2
- package/dist/core/session-lifecycle.integration.test.d.ts.map +0 -1
- package/dist/core/session-lifecycle.integration.test.js +0 -159
- package/dist/core/session-lifecycle.integration.test.js.map +0 -1
- package/dist/core/sessions/fork-utils.test.d.ts +0 -2
- package/dist/core/sessions/fork-utils.test.d.ts.map +0 -1
- package/dist/core/sessions/fork-utils.test.js +0 -129
- package/dist/core/sessions/fork-utils.test.js.map +0 -1
- package/dist/core/sessions/session-overrides.integration.test.d.ts +0 -2
- package/dist/core/sessions/session-overrides.integration.test.d.ts.map +0 -1
- package/dist/core/sessions/session-overrides.integration.test.js +0 -154
- package/dist/core/sessions/session-overrides.integration.test.js.map +0 -1
- package/dist/core/sessions/session.test.d.ts +0 -2
- package/dist/core/sessions/session.test.d.ts.map +0 -1
- package/dist/core/sessions/session.test.js +0 -1100
- package/dist/core/sessions/session.test.js.map +0 -1
- package/dist/core/tools/executor.test.d.ts +0 -2
- package/dist/core/tools/executor.test.d.ts.map +0 -1
- package/dist/core/tools/executor.test.js +0 -144
- package/dist/core/tools/executor.test.js.map +0 -1
- package/dist/lib/logger/console.test.d.ts +0 -2
- package/dist/lib/logger/console.test.d.ts.map +0 -1
- package/dist/lib/logger/console.test.js +0 -258
- package/dist/lib/logger/console.test.js.map +0 -1
- package/dist/lib/never.d.ts +0 -2
- package/dist/lib/never.d.ts.map +0 -1
- package/dist/lib/never.js +0 -4
- package/dist/lib/never.js.map +0 -1
- package/dist/lib/utils/concurrency.test.d.ts +0 -2
- package/dist/lib/utils/concurrency.test.d.ts.map +0 -1
- package/dist/lib/utils/concurrency.test.js +0 -135
- package/dist/lib/utils/concurrency.test.js.map +0 -1
- package/dist/plugins/agent-status/agent-status.test.d.ts +0 -2
- package/dist/plugins/agent-status/agent-status.test.d.ts.map +0 -1
- package/dist/plugins/agent-status/agent-status.test.js +0 -136
- package/dist/plugins/agent-status/agent-status.test.js.map +0 -1
- package/dist/plugins/agents/agents.integration.test.d.ts +0 -2
- package/dist/plugins/agents/agents.integration.test.d.ts.map +0 -1
- package/dist/plugins/agents/agents.integration.test.js +0 -830
- package/dist/plugins/agents/agents.integration.test.js.map +0 -1
- package/dist/plugins/agents/supervision.integration.test.d.ts +0 -2
- package/dist/plugins/agents/supervision.integration.test.d.ts.map +0 -1
- package/dist/plugins/agents/supervision.integration.test.js +0 -215
- package/dist/plugins/agents/supervision.integration.test.js.map +0 -1
- package/dist/plugins/context-compact/compaction-live.test.d.ts +0 -17
- package/dist/plugins/context-compact/compaction-live.test.d.ts.map +0 -1
- package/dist/plugins/context-compact/compaction-live.test.js +0 -177
- package/dist/plugins/context-compact/compaction-live.test.js.map +0 -1
- package/dist/plugins/context-compact/context-compact.integration.test.d.ts +0 -2
- package/dist/plugins/context-compact/context-compact.integration.test.d.ts.map +0 -1
- package/dist/plugins/context-compact/context-compact.integration.test.js +0 -364
- package/dist/plugins/context-compact/context-compact.integration.test.js.map +0 -1
- package/dist/plugins/context-compact/context-compactor.test.d.ts +0 -2
- package/dist/plugins/context-compact/context-compactor.test.d.ts.map +0 -1
- package/dist/plugins/context-compact/context-compactor.test.js +0 -858
- package/dist/plugins/context-compact/context-compactor.test.js.map +0 -1
- package/dist/plugins/context-compact/history-offloader.test.d.ts +0 -2
- package/dist/plugins/context-compact/history-offloader.test.d.ts.map +0 -1
- package/dist/plugins/context-compact/history-offloader.test.js +0 -77
- package/dist/plugins/context-compact/history-offloader.test.js.map +0 -1
- package/dist/plugins/filesystem/filesystem.integration.test.d.ts +0 -2
- package/dist/plugins/filesystem/filesystem.integration.test.d.ts.map +0 -1
- package/dist/plugins/filesystem/filesystem.integration.test.js +0 -447
- package/dist/plugins/filesystem/filesystem.integration.test.js.map +0 -1
- package/dist/plugins/filesystem/schema.d.ts +0 -7
- package/dist/plugins/filesystem/schema.d.ts.map +0 -1
- package/dist/plugins/filesystem/schema.js +0 -2
- package/dist/plugins/filesystem/schema.js.map +0 -1
- package/dist/plugins/limits-guard/limit-guard.test.d.ts +0 -2
- package/dist/plugins/limits-guard/limit-guard.test.d.ts.map +0 -1
- package/dist/plugins/limits-guard/limit-guard.test.js +0 -194
- package/dist/plugins/limits-guard/limit-guard.test.js.map +0 -1
- package/dist/plugins/limits-guard/limits-guard.integration.test.d.ts +0 -2
- package/dist/plugins/limits-guard/limits-guard.integration.test.d.ts.map +0 -1
- package/dist/plugins/limits-guard/limits-guard.integration.test.js +0 -887
- package/dist/plugins/limits-guard/limits-guard.integration.test.js.map +0 -1
- package/dist/plugins/llm-debug/llm-debug.integration.test.d.ts +0 -2
- package/dist/plugins/llm-debug/llm-debug.integration.test.d.ts.map +0 -1
- package/dist/plugins/llm-debug/llm-debug.integration.test.js +0 -157
- package/dist/plugins/llm-debug/llm-debug.integration.test.js.map +0 -1
- package/dist/plugins/mailbox/mailbox.integration.test.d.ts +0 -2
- package/dist/plugins/mailbox/mailbox.integration.test.d.ts.map +0 -1
- package/dist/plugins/mailbox/mailbox.integration.test.js +0 -678
- package/dist/plugins/mailbox/mailbox.integration.test.js.map +0 -1
- package/dist/plugins/resources/resources.integration.test.d.ts +0 -2
- package/dist/plugins/resources/resources.integration.test.d.ts.map +0 -1
- package/dist/plugins/resources/resources.integration.test.js +0 -62
- package/dist/plugins/resources/resources.integration.test.js.map +0 -1
- package/dist/plugins/result-eviction/result-eviction.integration.test.d.ts +0 -2
- package/dist/plugins/result-eviction/result-eviction.integration.test.d.ts.map +0 -1
- package/dist/plugins/result-eviction/result-eviction.integration.test.js +0 -294
- package/dist/plugins/result-eviction/result-eviction.integration.test.js.map +0 -1
- package/dist/plugins/services/pid-registry.test.d.ts +0 -2
- package/dist/plugins/services/pid-registry.test.d.ts.map +0 -1
- package/dist/plugins/services/pid-registry.test.js +0 -161
- package/dist/plugins/services/pid-registry.test.js.map +0 -1
- package/dist/plugins/services/services.integration.test.d.ts +0 -2
- package/dist/plugins/services/services.integration.test.d.ts.map +0 -1
- package/dist/plugins/services/services.integration.test.js +0 -1057
- package/dist/plugins/services/services.integration.test.js.map +0 -1
- package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.d.ts +0 -2
- package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.d.ts.map +0 -1
- package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js +0 -498
- package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js.map +0 -1
- package/dist/plugins/session-stats/plugin.test.d.ts +0 -2
- package/dist/plugins/session-stats/plugin.test.d.ts.map +0 -1
- package/dist/plugins/session-stats/plugin.test.js +0 -145
- package/dist/plugins/session-stats/plugin.test.js.map +0 -1
- package/dist/plugins/shell/shell.integration.test.d.ts +0 -2
- package/dist/plugins/shell/shell.integration.test.d.ts.map +0 -1
- package/dist/plugins/shell/shell.integration.test.js +0 -234
- package/dist/plugins/shell/shell.integration.test.js.map +0 -1
- package/dist/plugins/shell/shell.test.d.ts +0 -2
- package/dist/plugins/shell/shell.test.d.ts.map +0 -1
- package/dist/plugins/shell/shell.test.js +0 -236
- package/dist/plugins/shell/shell.test.js.map +0 -1
- package/dist/plugins/skills/discovery.test.d.ts +0 -2
- package/dist/plugins/skills/discovery.test.d.ts.map +0 -1
- package/dist/plugins/skills/discovery.test.js +0 -312
- package/dist/plugins/skills/discovery.test.js.map +0 -1
- package/dist/plugins/skills/skills.integration.test.d.ts +0 -2
- package/dist/plugins/skills/skills.integration.test.d.ts.map +0 -1
- package/dist/plugins/skills/skills.integration.test.js +0 -475
- package/dist/plugins/skills/skills.integration.test.js.map +0 -1
- package/dist/plugins/todo/todo.integration.test.d.ts +0 -2
- package/dist/plugins/todo/todo.integration.test.d.ts.map +0 -1
- package/dist/plugins/todo/todo.integration.test.js +0 -605
- package/dist/plugins/todo/todo.integration.test.js.map +0 -1
- package/dist/plugins/uploads/preprocessors/image-classifier.test.d.ts +0 -2
- package/dist/plugins/uploads/preprocessors/image-classifier.test.d.ts.map +0 -1
- package/dist/plugins/uploads/preprocessors/image-classifier.test.js +0 -113
- package/dist/plugins/uploads/preprocessors/image-classifier.test.js.map +0 -1
- package/dist/plugins/uploads/uploads.integration.test.d.ts +0 -2
- package/dist/plugins/uploads/uploads.integration.test.d.ts.map +0 -1
- package/dist/plugins/uploads/uploads.integration.test.js +0 -639
- package/dist/plugins/uploads/uploads.integration.test.js.map +0 -1
- package/dist/plugins/user-chat/user-chat.integration.test.d.ts +0 -2
- package/dist/plugins/user-chat/user-chat.integration.test.d.ts.map +0 -1
- package/dist/plugins/user-chat/user-chat.integration.test.js +0 -754
- package/dist/plugins/user-chat/user-chat.integration.test.js.map +0 -1
- package/dist/plugins/workers/workers.integration.test.d.ts +0 -2
- package/dist/plugins/workers/workers.integration.test.d.ts.map +0 -1
- package/dist/plugins/workers/workers.integration.test.js +0 -629
- package/dist/plugins/workers/workers.integration.test.js.map +0 -1
- package/dist/prompts/macros.test.d.ts +0 -2
- package/dist/prompts/macros.test.d.ts.map +0 -1
- package/dist/prompts/macros.test.js +0 -80
- package/dist/prompts/macros.test.js.map +0 -1
- package/dist/testing/test-harness.test.d.ts +0 -2
- package/dist/testing/test-harness.test.d.ts.map +0 -1
- package/dist/testing/test-harness.test.js +0 -92
- package/dist/testing/test-harness.test.js.map +0 -1
- package/dist/transport/http/routes/rpc.integration.test.d.ts +0 -2
- package/dist/transport/http/routes/rpc.integration.test.d.ts.map +0 -1
- package/dist/transport/http/routes/rpc.integration.test.js +0 -189
- package/dist/transport/http/routes/rpc.integration.test.js.map +0 -1
- package/dist/transport/http/routes/rpc.test.d.ts +0 -2
- package/dist/transport/http/routes/rpc.test.d.ts.map +0 -1
- package/dist/transport/http/routes/rpc.test.js +0 -316
- package/dist/transport/http/routes/rpc.test.js.map +0 -1
- package/src/config.test.ts +0 -174
- package/src/core/agent-loop.integration.test.ts +0 -503
- package/src/core/agents/agent-config.test.ts +0 -240
- package/src/core/agents/agent-shutdown.test.ts +0 -236
- package/src/core/agents/agent.test.ts +0 -387
- package/src/core/agents/communicator.ts +0 -16
- package/src/core/agents/handler-events.test.ts +0 -167
- package/src/core/agents/response-sanitizer.test.ts +0 -137
- package/src/core/error-recovery.integration.test.ts +0 -343
- package/src/core/event-sourcing.integration.test.ts +0 -191
- package/src/core/events/file.test.ts +0 -436
- package/src/core/events/memory.test.ts +0 -741
- package/src/core/image/image-processor.test.ts +0 -218
- package/src/core/image/vips-resizer.test.ts +0 -388
- package/src/core/llm/anthropic.test.ts +0 -546
- package/src/core/llm/cache-breakpoints.test.ts +0 -85
- package/src/core/llm/cache-live.test.ts +0 -155
- package/src/core/llm/logger.test.ts +0 -270
- package/src/core/llm/message-sanitization.test.ts +0 -217
- package/src/core/llm/mock.test.ts +0 -402
- package/src/core/llm/openrouter-mapping.test.ts +0 -210
- package/src/core/llm/openrouter.test.ts +0 -499
- package/src/core/llm/provider-integration.test.ts +0 -408
- package/src/core/llm/routing-provider.test.ts +0 -113
- package/src/core/llm/snapshot-middleware.test.ts +0 -168
- package/src/core/multi-agent.integration.test.ts +0 -340
- package/src/core/plugin-hooks.integration.test.ts +0 -568
- package/src/core/session-lifecycle.integration.test.ts +0 -202
- package/src/core/sessions/fork-utils.test.ts +0 -158
- package/src/core/sessions/session-overrides.integration.test.ts +0 -210
- package/src/core/sessions/session.test.ts +0 -1736
- package/src/core/tools/executor.test.ts +0 -161
- package/src/lib/logger/console.test.ts +0 -348
- package/src/lib/never.ts +0 -3
- package/src/lib/utils/concurrency.test.ts +0 -169
- package/src/plugins/agent-status/agent-status.test.ts +0 -164
- package/src/plugins/agents/agents.integration.test.ts +0 -976
- package/src/plugins/agents/supervision.integration.test.ts +0 -249
- package/src/plugins/context-compact/compaction-live.test.ts +0 -221
- package/src/plugins/context-compact/context-compact.integration.test.ts +0 -418
- package/src/plugins/context-compact/context-compactor.test.ts +0 -1036
- package/src/plugins/context-compact/history-offloader.test.ts +0 -100
- package/src/plugins/filesystem/filesystem.integration.test.ts +0 -529
- package/src/plugins/filesystem/schema.ts +0 -6
- package/src/plugins/limits-guard/limit-guard.test.ts +0 -253
- package/src/plugins/limits-guard/limits-guard.integration.test.ts +0 -1012
- package/src/plugins/llm-debug/llm-debug.integration.test.ts +0 -192
- package/src/plugins/mailbox/mailbox.integration.test.ts +0 -794
- package/src/plugins/resources/resources.integration.test.ts +0 -68
- package/src/plugins/result-eviction/result-eviction.integration.test.ts +0 -332
- package/src/plugins/services/pid-registry.test.ts +0 -180
- package/src/plugins/services/services.integration.test.ts +0 -1223
- package/src/plugins/session-lifecycle/session-lifecycle.integration.test.ts +0 -639
- package/src/plugins/session-stats/plugin.test.ts +0 -160
- package/src/plugins/shell/shell.integration.test.ts +0 -273
- package/src/plugins/shell/shell.test.ts +0 -314
- package/src/plugins/skills/discovery.test.ts +0 -395
- package/src/plugins/skills/skills.integration.test.ts +0 -580
- package/src/plugins/todo/todo.integration.test.ts +0 -687
- package/src/plugins/uploads/preprocessors/image-classifier.test.ts +0 -142
- package/src/plugins/uploads/uploads.integration.test.ts +0 -831
- package/src/plugins/user-chat/user-chat.integration.test.ts +0 -893
- package/src/plugins/workers/workers.integration.test.ts +0 -778
- package/src/prompts/macros.test.ts +0 -107
- package/src/testing/test-harness.test.ts +0 -114
- package/src/transport/http/routes/rpc.integration.test.ts +0 -243
- package/src/transport/http/routes/rpc.test.ts +0 -372
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import z from 'zod/v4'
|
|
2
|
-
import {
|
|
2
|
+
import type { AgentId } from '~/core/agents/schema.js'
|
|
3
|
+
import { createDomainError, type DomainError, ValidationErrors } from '~/core/errors.js'
|
|
3
4
|
import type { FileStore } from '~/core/file-store/types.js'
|
|
5
|
+
import type { InferenceContext } from '~/core/llm/provider.js'
|
|
4
6
|
import { definePlugin } from '~/core/plugins/plugin-builder.js'
|
|
7
|
+
import type { SessionContext } from '~/core/sessions/context.js'
|
|
8
|
+
import type { RuntimeLeaseRelease, SessionRuntimeActivity } from '~/core/sessions/runtime-activity.js'
|
|
5
9
|
import { SessionId } from '~/core/sessions/schema.js'
|
|
6
10
|
import { getEntryAgentId } from '~/core/sessions/state.js'
|
|
7
|
-
import {
|
|
8
|
-
import type {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
+
import { ArchiveBudget, type ArchiveLimitOverrides } from '~/lib/archive/index.js'
|
|
12
|
+
import type { Logger } from '~/lib/logger/logger.js'
|
|
13
|
+
import { Err, Ok, type Result } from '~/lib/utils/result.js'
|
|
14
|
+
import type { PreprocessorRegistry, PreprocessorResult } from './preprocessor.js'
|
|
15
|
+
import { generateUploadId, type MessageAttachment, parseUploadId, UploadId, type UploadMetadata, uploadIdSchema } from './schema.js'
|
|
16
|
+
import { type PendingUpload, type UploadsState, uploadEvents } from './state.js'
|
|
11
17
|
|
|
12
18
|
// ============================================================================
|
|
13
19
|
// Notification schemas
|
|
14
20
|
// ============================================================================
|
|
15
21
|
|
|
22
|
+
// No producer sets extractedContent: it can be megabytes and clients read it from the state projection.
|
|
16
23
|
const statusChangedSchema = z.object({
|
|
17
24
|
sessionId: z.string(),
|
|
18
25
|
uploadId: z.string(),
|
|
19
26
|
status: z.enum(['processing', 'ready', 'failed']),
|
|
20
|
-
extractedContent: z.string().optional(),
|
|
21
27
|
error: z.string().optional(),
|
|
22
28
|
})
|
|
23
29
|
|
|
@@ -45,6 +51,13 @@ const ALLOWED_MIME_TYPES = [
|
|
|
45
51
|
]
|
|
46
52
|
|
|
47
53
|
const PROCESSING_TIMEOUT_MS = 120_000 // 120 seconds
|
|
54
|
+
const PROCESSING_ABORT_GRACE_MS = 1_000
|
|
55
|
+
const CLOSE_DRAIN_TIMEOUT_MS = 30_000
|
|
56
|
+
const TERMINAL_APPEND_ATTEMPTS = 3
|
|
57
|
+
const TERMINAL_APPEND_RETRY_DELAY_MS = 25
|
|
58
|
+
|
|
59
|
+
/** Store faults are internal: the client gets this fixed text, never the driver's own message. */
|
|
60
|
+
const uploadStorageError = (): DomainError => createDomainError('upload_storage_error', 'Failed to write file', 500)
|
|
48
61
|
|
|
49
62
|
// ============================================================================
|
|
50
63
|
// Config
|
|
@@ -53,6 +66,66 @@ const PROCESSING_TIMEOUT_MS = 120_000 // 120 seconds
|
|
|
53
66
|
export interface UploadsPluginConfig {
|
|
54
67
|
dataFileStore: FileStore
|
|
55
68
|
preprocessorRegistry?: PreprocessorRegistry
|
|
69
|
+
processingTimeoutMs?: number
|
|
70
|
+
processingAbortGraceMs?: number
|
|
71
|
+
/** Bound in ms on the close-time wait for in-flight uploads and mutations. Must be positive; anything else uses the 30s default. */
|
|
72
|
+
closeDrainTimeoutMs?: number
|
|
73
|
+
archiveLimits?: ArchiveLimitOverrides
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface UploadInput {
|
|
77
|
+
sessionId: string
|
|
78
|
+
filename: string
|
|
79
|
+
mimeType: string
|
|
80
|
+
size: number
|
|
81
|
+
fileBuffer: Buffer
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface UploadDescriptor {
|
|
85
|
+
filename: string
|
|
86
|
+
mimeType: string
|
|
87
|
+
size: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface UploadResult {
|
|
91
|
+
status: 'ready' | 'failed'
|
|
92
|
+
extractedContent?: string
|
|
93
|
+
derivedPaths?: string[]
|
|
94
|
+
error?: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface ActiveUpload {
|
|
98
|
+
controller: AbortController
|
|
99
|
+
processingCompletion: Promise<void>
|
|
100
|
+
completion: Promise<void>
|
|
101
|
+
holdTerminalMutation(): RuntimeLeaseRelease
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface UploadsPluginContext {
|
|
105
|
+
activeUploads: Map<string, ActiveUpload>
|
|
106
|
+
/** Completion promise -> the lease reason, so an expired close drain can name what is still running. */
|
|
107
|
+
activeMutations: Map<Promise<void>, string>
|
|
108
|
+
deletedUploads: Set<string>
|
|
109
|
+
operationQueues: Map<string, Promise<void>>
|
|
110
|
+
closing: boolean
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface PreparedUpload {
|
|
114
|
+
uploadId: ReturnType<typeof generateUploadId>
|
|
115
|
+
uploadIdStr: string
|
|
116
|
+
uploadStore: FileStore
|
|
117
|
+
/** The name as written. Callers report THIS, never their own input — see toStoredFilename. */
|
|
118
|
+
filename: string
|
|
119
|
+
filePath: string
|
|
120
|
+
createdAt: number
|
|
121
|
+
lifecycle: UploadLifecycle
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface UploadLifecycle {
|
|
125
|
+
controller: AbortController
|
|
126
|
+
start<T>(run: (controller: AbortController) => Promise<T>): Promise<T>
|
|
127
|
+
attachRuntimeLease(release: RuntimeLeaseRelease): void
|
|
128
|
+
abandon(): void
|
|
56
129
|
}
|
|
57
130
|
|
|
58
131
|
// ============================================================================
|
|
@@ -60,15 +133,7 @@ export interface UploadsPluginConfig {
|
|
|
60
133
|
// ============================================================================
|
|
61
134
|
|
|
62
135
|
function isAllowedMimeType(mimeType: string): boolean {
|
|
63
|
-
return ALLOWED_MIME_TYPES.some((allowed) =>
|
|
64
|
-
allowed.endsWith('/')
|
|
65
|
-
? mimeType.startsWith(allowed)
|
|
66
|
-
: mimeType === allowed
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function sleep(ms: number): Promise<void> {
|
|
71
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
136
|
+
return ALLOWED_MIME_TYPES.some((allowed) => (allowed.endsWith('/') ? mimeType.startsWith(allowed) : mimeType === allowed))
|
|
72
137
|
}
|
|
73
138
|
|
|
74
139
|
function formatUploadsForLLM(uploads: PendingUpload[], sessionRoot: string): string {
|
|
@@ -80,67 +145,512 @@ function formatUploadsForLLM(uploads: PendingUpload[], sessionRoot: string): str
|
|
|
80
145
|
return blocks.join('\n')
|
|
81
146
|
}
|
|
82
147
|
|
|
148
|
+
function withoutKeys<T>(
|
|
149
|
+
values: Record<string, T>,
|
|
150
|
+
uploadIds: readonly string[],
|
|
151
|
+
): Record<string, T> {
|
|
152
|
+
if (uploadIds.every((uploadId) => values[uploadId] === undefined)) return values
|
|
153
|
+
const next = { ...values }
|
|
154
|
+
for (const uploadId of uploadIds) delete next[uploadId]
|
|
155
|
+
return next
|
|
156
|
+
}
|
|
157
|
+
|
|
83
158
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
159
|
+
* True when the value names one entry inside the upload dir and cannot walk anywhere.
|
|
160
|
+
*
|
|
161
|
+
* Deliberately duplicates the download route's boundary check: this is where the bytes
|
|
162
|
+
* are written, and prepareUpload is reachable from callers that never touch HTTP.
|
|
86
163
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
164
|
+
function isBasename(value: string): boolean {
|
|
165
|
+
if (value === '' || value === '.' || value === '..') return false
|
|
166
|
+
return !/[/\\\0]/.test(value)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The single spelling of a filename this plugin stores and reports.
|
|
171
|
+
*
|
|
172
|
+
* Unicode lets the same name be two different byte strings: macOS hands over the decomposed form
|
|
173
|
+
* (`á` as `a` + U+0301), while a model asked to repeat that name writes the composed one. The two
|
|
174
|
+
* do not compare equal, so every `cp`, `read_file` and shell redirect the agent builds from the
|
|
175
|
+
* name it was given misses the file that is actually on disk. Composing on the way in makes the
|
|
176
|
+
* name the agent produces the name that is there.
|
|
177
|
+
*/
|
|
178
|
+
function toStoredFilename(filename: string): string {
|
|
179
|
+
return filename.normalize('NFC')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function validateUploadInput(input: Pick<UploadInput, 'size' | 'mimeType' | 'filename'>): string | undefined {
|
|
183
|
+
if (!isBasename(input.filename)) return 'Invalid filename'
|
|
184
|
+
if (input.size > MAX_FILE_SIZE) return `File too large: max ${MAX_FILE_SIZE / (1024 * 1024)}MB`
|
|
185
|
+
if (!isAllowedMimeType(input.mimeType)) return `Unsupported file type: ${input.mimeType}`
|
|
186
|
+
return undefined
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function reserveUploadLifecycle(pluginContext: UploadsPluginContext, uploadId: string): UploadLifecycle {
|
|
190
|
+
const controller = new AbortController()
|
|
191
|
+
if (pluginContext.closing) controller.abort(new Error('Session is closing'))
|
|
192
|
+
let resolveProcessingCompletion = () => {}
|
|
193
|
+
const processingCompletion = new Promise<void>((resolve) => {
|
|
194
|
+
resolveProcessingCompletion = resolve
|
|
195
|
+
})
|
|
196
|
+
let resolveCompletion = () => {}
|
|
197
|
+
const completion = new Promise<void>((resolve) => {
|
|
198
|
+
resolveCompletion = resolve
|
|
199
|
+
})
|
|
200
|
+
let processingSettled = false
|
|
201
|
+
let terminalHolds = 0
|
|
202
|
+
let settled = false
|
|
203
|
+
let releaseRuntimeLease: RuntimeLeaseRelease | undefined
|
|
204
|
+
let operation: ActiveUpload
|
|
205
|
+
const settle = () => {
|
|
206
|
+
if (!processingSettled || terminalHolds !== 0 || settled) return
|
|
207
|
+
settled = true
|
|
208
|
+
if (pluginContext.activeUploads.get(uploadId) === operation) pluginContext.activeUploads.delete(uploadId)
|
|
209
|
+
releaseRuntimeLease?.()
|
|
210
|
+
releaseRuntimeLease = undefined
|
|
211
|
+
resolveCompletion()
|
|
212
|
+
}
|
|
213
|
+
const finishProcessing = () => {
|
|
214
|
+
if (processingSettled) return
|
|
215
|
+
processingSettled = true
|
|
216
|
+
resolveProcessingCompletion()
|
|
217
|
+
settle()
|
|
218
|
+
}
|
|
219
|
+
operation = {
|
|
220
|
+
controller,
|
|
221
|
+
processingCompletion,
|
|
222
|
+
completion,
|
|
223
|
+
holdTerminalMutation() {
|
|
224
|
+
if (settled) return () => {}
|
|
225
|
+
terminalHolds++
|
|
226
|
+
let released = false
|
|
227
|
+
return () => {
|
|
228
|
+
if (released) return
|
|
229
|
+
released = true
|
|
230
|
+
terminalHolds--
|
|
231
|
+
settle()
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
pluginContext.activeUploads.set(uploadId, operation)
|
|
236
|
+
return {
|
|
237
|
+
controller,
|
|
238
|
+
start<T>(run: (signalController: AbortController) => Promise<T>): Promise<T> {
|
|
239
|
+
let result: Promise<T>
|
|
240
|
+
try {
|
|
241
|
+
result = run(controller)
|
|
242
|
+
} catch (error) {
|
|
243
|
+
finishProcessing()
|
|
244
|
+
return Promise.reject(error)
|
|
245
|
+
}
|
|
246
|
+
result.then(finishProcessing, finishProcessing)
|
|
247
|
+
return result
|
|
248
|
+
},
|
|
249
|
+
attachRuntimeLease(release) {
|
|
250
|
+
if (settled) {
|
|
251
|
+
release()
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
if (releaseRuntimeLease) {
|
|
255
|
+
release()
|
|
256
|
+
throw new Error(`Upload ${uploadId} already has a runtime lease`)
|
|
257
|
+
}
|
|
258
|
+
releaseRuntimeLease = release
|
|
259
|
+
},
|
|
260
|
+
abandon: finishProcessing,
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function runTrackedMutation<T>(args: {
|
|
265
|
+
pluginContext: UploadsPluginContext
|
|
266
|
+
runtimeActivity: SessionRuntimeActivity
|
|
267
|
+
reason: string
|
|
268
|
+
run: () => Promise<T>
|
|
269
|
+
}): Promise<T> {
|
|
270
|
+
if (args.pluginContext.closing) return Promise.reject(new Error('Session is closing'))
|
|
271
|
+
const releaseRuntimeLease = args.runtimeActivity.acquire(args.reason)
|
|
272
|
+
let resolveCompletion = () => {}
|
|
273
|
+
const completion = new Promise<void>((resolve) => {
|
|
274
|
+
resolveCompletion = resolve
|
|
275
|
+
})
|
|
276
|
+
args.pluginContext.activeMutations.set(completion, args.reason)
|
|
277
|
+
return (async () => {
|
|
278
|
+
try {
|
|
279
|
+
return await args.run()
|
|
280
|
+
} finally {
|
|
281
|
+
args.pluginContext.activeMutations.delete(completion)
|
|
282
|
+
releaseRuntimeLease()
|
|
283
|
+
resolveCompletion()
|
|
284
|
+
}
|
|
285
|
+
})()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function prepareUpload(args: {
|
|
289
|
+
pluginContext: UploadsPluginContext
|
|
290
|
+
dataFileStore: FileStore
|
|
291
|
+
logger: Logger
|
|
292
|
+
input: UploadInput
|
|
293
|
+
}): Promise<Result<PreparedUpload, DomainError>> {
|
|
294
|
+
const filename = toStoredFilename(args.input.filename)
|
|
295
|
+
const validationError = validateUploadInput({ ...args.input, filename })
|
|
296
|
+
if (validationError) return Err(ValidationErrors.invalid(validationError))
|
|
297
|
+
|
|
298
|
+
const uploadId = generateUploadId()
|
|
299
|
+
const uploadIdStr = String(uploadId)
|
|
300
|
+
const lifecycle = reserveUploadLifecycle(args.pluginContext, uploadIdStr)
|
|
301
|
+
let prepared: PreparedUpload | undefined
|
|
302
|
+
try {
|
|
303
|
+
if (lifecycle.controller.signal.aborted) return Err(ValidationErrors.invalid('Session is closing'))
|
|
304
|
+
const uploadStore = args.dataFileStore.scoped(`sessions/${args.input.sessionId}/uploads/${uploadId}`)
|
|
305
|
+
const writeResult = await uploadStore.write(filename, args.input.fileBuffer)
|
|
306
|
+
if (!writeResult.ok) {
|
|
307
|
+
args.logger.error('Could not write upload file', undefined, { uploadId: uploadIdStr, filename, error: writeResult.error })
|
|
308
|
+
return Err(uploadStorageError())
|
|
309
|
+
}
|
|
310
|
+
if (args.pluginContext.closing) {
|
|
311
|
+
await uploadStore.remove(filename)
|
|
312
|
+
return Err(ValidationErrors.invalid('Session is closing'))
|
|
313
|
+
}
|
|
314
|
+
prepared = {
|
|
315
|
+
uploadId,
|
|
316
|
+
uploadIdStr,
|
|
317
|
+
uploadStore,
|
|
318
|
+
filename,
|
|
319
|
+
filePath: writeResult.value.path,
|
|
320
|
+
createdAt: Date.now(),
|
|
321
|
+
lifecycle,
|
|
322
|
+
}
|
|
323
|
+
return Ok(prepared)
|
|
324
|
+
} catch (error) {
|
|
325
|
+
// An embedder-supplied store throws its own driver text (bucket, endpoint, request id) — log it, never echo it.
|
|
326
|
+
args.logger.error('Could not write upload file', error instanceof Error ? error : undefined, { uploadId: uploadIdStr, filename })
|
|
327
|
+
return Err(uploadStorageError())
|
|
328
|
+
} finally {
|
|
329
|
+
// The lifecycle is registered before any fs work, so every failure path must settle it —
|
|
330
|
+
// an orphan never resolves its completion and onSessionClose waits on that.
|
|
331
|
+
if (!prepared) lifecycle.abandon()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
type PreprocessorOutcome = { kind: 'completed'; result: Result<PreprocessorResult, Error> } | { kind: 'threw'; error: unknown }
|
|
336
|
+
|
|
337
|
+
async function runPreprocessor(args: {
|
|
90
338
|
uploadStore: FileStore
|
|
91
339
|
filePath: string
|
|
92
|
-
filename: string
|
|
93
340
|
mimeType: string
|
|
94
|
-
size: number
|
|
95
|
-
createdAt: number
|
|
96
341
|
preprocessorRegistry?: PreprocessorRegistry
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}> {
|
|
342
|
+
processingTimeoutMs?: number
|
|
343
|
+
processingAbortGraceMs?: number
|
|
344
|
+
archiveLimits?: ArchiveLimitOverrides
|
|
345
|
+
controller: AbortController
|
|
346
|
+
inferenceContext?: Omit<InferenceContext, 'signal'>
|
|
347
|
+
}): Promise<UploadResult> {
|
|
103
348
|
const preprocessor = args.preprocessorRegistry?.getForMimeType(args.mimeType)
|
|
349
|
+
if (!preprocessor) return { status: 'ready' }
|
|
104
350
|
|
|
105
|
-
let
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
351
|
+
let timedOut = false
|
|
352
|
+
const processPromise: Promise<PreprocessorOutcome> = (async () => {
|
|
353
|
+
try {
|
|
354
|
+
return {
|
|
355
|
+
kind: 'completed',
|
|
356
|
+
result: await preprocessor.process(args.filePath, args.mimeType, {
|
|
357
|
+
files: args.uploadStore,
|
|
358
|
+
signal: args.controller.signal,
|
|
359
|
+
inferenceContext: args.inferenceContext,
|
|
360
|
+
archiveBudget: new ArchiveBudget(args.archiveLimits),
|
|
361
|
+
}),
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
return { kind: 'threw', error }
|
|
365
|
+
}
|
|
366
|
+
})()
|
|
367
|
+
let resolveAbort: (() => void) | undefined
|
|
368
|
+
const aborted = new Promise<{ kind: 'aborted' }>((resolve) => {
|
|
369
|
+
if (args.controller.signal.aborted) {
|
|
370
|
+
resolve({ kind: 'aborted' })
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
resolveAbort = () => resolve({ kind: 'aborted' })
|
|
374
|
+
args.controller.signal.addEventListener('abort', resolveAbort, {
|
|
375
|
+
once: true,
|
|
113
376
|
})
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
377
|
+
})
|
|
378
|
+
const timeoutId = setTimeout(() => {
|
|
379
|
+
timedOut = true
|
|
380
|
+
args.controller.abort(new Error('Processing timeout'))
|
|
381
|
+
}, args.processingTimeoutMs ?? PROCESSING_TIMEOUT_MS)
|
|
382
|
+
|
|
383
|
+
let outcome: PreprocessorOutcome | undefined
|
|
384
|
+
let graceTimeoutId: ReturnType<typeof setTimeout> | undefined
|
|
385
|
+
try {
|
|
386
|
+
const first = await Promise.race([processPromise, aborted])
|
|
387
|
+
if (first.kind === 'aborted') {
|
|
388
|
+
const grace = new Promise<{ kind: 'grace-expired' }>((resolve) => {
|
|
389
|
+
graceTimeoutId = setTimeout(() => resolve({ kind: 'grace-expired' }), args.processingAbortGraceMs ?? PROCESSING_ABORT_GRACE_MS)
|
|
390
|
+
})
|
|
391
|
+
const afterAbort = await Promise.race([processPromise, grace])
|
|
392
|
+
if (afterAbort.kind !== 'grace-expired') outcome = afterAbort
|
|
122
393
|
} else {
|
|
123
|
-
|
|
124
|
-
errorMessage = result.error.message
|
|
394
|
+
outcome = first
|
|
125
395
|
}
|
|
396
|
+
} finally {
|
|
397
|
+
clearTimeout(timeoutId)
|
|
398
|
+
if (graceTimeoutId) clearTimeout(graceTimeoutId)
|
|
399
|
+
if (resolveAbort) args.controller.signal.removeEventListener('abort', resolveAbort)
|
|
126
400
|
}
|
|
127
401
|
|
|
128
|
-
|
|
129
|
-
|
|
402
|
+
if (args.controller.signal.aborted) {
|
|
403
|
+
return {
|
|
404
|
+
status: 'failed',
|
|
405
|
+
error: timedOut ? 'Processing timeout' : 'Processing cancelled',
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (outcome?.kind === 'threw') {
|
|
409
|
+
return {
|
|
410
|
+
status: 'failed',
|
|
411
|
+
error: outcome.error instanceof Error ? outcome.error.message : String(outcome.error),
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!outcome) return { status: 'failed', error: 'Processing failed' }
|
|
415
|
+
if (!outcome.result.ok) return { status: 'failed', error: outcome.result.error.message }
|
|
416
|
+
return {
|
|
417
|
+
status: 'ready',
|
|
418
|
+
extractedContent: outcome.result.value.extractedContent,
|
|
419
|
+
derivedPaths: outcome.result.value.derivedPaths,
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function writeMetadata(uploadStore: FileStore, metadata: UploadMetadata): Promise<void> {
|
|
424
|
+
const result = await uploadStore.write('meta.json', JSON.stringify(metadata, null, 2))
|
|
425
|
+
if (!result.ok) throw new Error(result.error)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function createFinalMetadata(args: { prepared: PreparedUpload; sessionId: SessionId; upload: UploadDescriptor; result: UploadResult }): UploadMetadata {
|
|
429
|
+
return {
|
|
430
|
+
uploadId: args.prepared.uploadId,
|
|
130
431
|
sessionId: args.sessionId,
|
|
131
|
-
filename: args.filename,
|
|
132
|
-
mimeType: args.mimeType,
|
|
133
|
-
size: args.size,
|
|
134
|
-
path: args.filePath,
|
|
135
|
-
status,
|
|
136
|
-
extractedContent,
|
|
137
|
-
derivedPaths,
|
|
138
|
-
|
|
432
|
+
filename: args.upload.filename,
|
|
433
|
+
mimeType: args.upload.mimeType,
|
|
434
|
+
size: args.upload.size,
|
|
435
|
+
path: args.prepared.filePath,
|
|
436
|
+
status: args.result.status,
|
|
437
|
+
extractedContent: args.result.extractedContent,
|
|
438
|
+
derivedPaths: args.result.derivedPaths,
|
|
439
|
+
error: args.result.error,
|
|
440
|
+
createdAt: args.prepared.createdAt,
|
|
139
441
|
completedAt: Date.now(),
|
|
140
442
|
}
|
|
141
|
-
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function createUploadResultEvent(upload: UploadDescriptor, uploadId: ReturnType<typeof generateUploadId>, result: UploadResult) {
|
|
446
|
+
return uploadEvents.create('attachment_uploaded', {
|
|
447
|
+
uploadId,
|
|
448
|
+
filename: upload.filename,
|
|
449
|
+
mimeType: upload.mimeType,
|
|
450
|
+
size: upload.size,
|
|
451
|
+
status: result.status,
|
|
452
|
+
extractedContent: result.extractedContent,
|
|
453
|
+
derivedPaths: result.derivedPaths,
|
|
454
|
+
error: result.error,
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async function withUploadLock<T>(
|
|
459
|
+
pluginContext: UploadsPluginContext,
|
|
460
|
+
uploadId: string,
|
|
461
|
+
run: () => Promise<T>,
|
|
462
|
+
options?: { allowDuringClose?: boolean },
|
|
463
|
+
): Promise<T> {
|
|
464
|
+
if (pluginContext.closing && !options?.allowDuringClose) throw new Error('Session is closing')
|
|
465
|
+
const previous = pluginContext.operationQueues.get(uploadId) ?? Promise.resolve()
|
|
466
|
+
let release = () => {}
|
|
467
|
+
const current = new Promise<void>((resolve) => {
|
|
468
|
+
release = resolve
|
|
469
|
+
})
|
|
470
|
+
pluginContext.operationQueues.set(uploadId, current)
|
|
471
|
+
await previous
|
|
472
|
+
try {
|
|
473
|
+
if (pluginContext.closing && !options?.allowDuringClose) throw new Error('Session is closing')
|
|
474
|
+
return await run()
|
|
475
|
+
} finally {
|
|
476
|
+
release()
|
|
477
|
+
if (pluginContext.operationQueues.get(uploadId) === current) pluginContext.operationQueues.delete(uploadId)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Resolves true when everything settled, false when the bound expired first. */
|
|
482
|
+
async function settleWithTimeout(pending: readonly Promise<unknown>[], timeoutMs: number): Promise<boolean> {
|
|
483
|
+
if (pending.length === 0) return true
|
|
484
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
485
|
+
const expired = new Promise<boolean>((resolve) => {
|
|
486
|
+
timeoutId = setTimeout(() => resolve(false), timeoutMs)
|
|
487
|
+
})
|
|
488
|
+
try {
|
|
489
|
+
return await Promise.race([Promise.allSettled(pending).then(() => true), expired])
|
|
490
|
+
} finally {
|
|
491
|
+
if (timeoutId !== undefined) clearTimeout(timeoutId)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Appends the terminal event, retrying a transient store failure.
|
|
497
|
+
*
|
|
498
|
+
* Losing the append means the projection never gains the attachment, so it is never
|
|
499
|
+
* auto-dequeued to the agent while the caller was already told the upload is ready.
|
|
500
|
+
* Metadata makes it recoverable, but only when the runtime is next built — with idle
|
|
501
|
+
* eviction off (the default) that is a process restart, so retrying here is the fix.
|
|
502
|
+
*/
|
|
503
|
+
async function appendTerminalEvent(args: {
|
|
504
|
+
pluginContext: UploadsPluginContext
|
|
505
|
+
append: () => Promise<void>
|
|
506
|
+
logger: Logger
|
|
507
|
+
uploadId: string
|
|
508
|
+
filename: string
|
|
509
|
+
}): Promise<boolean> {
|
|
510
|
+
for (let attempt = 1; attempt <= TERMINAL_APPEND_ATTEMPTS; attempt++) {
|
|
511
|
+
try {
|
|
512
|
+
await args.append()
|
|
513
|
+
return true
|
|
514
|
+
} catch (error) {
|
|
515
|
+
const giveUp = attempt === TERMINAL_APPEND_ATTEMPTS || args.pluginContext.closing
|
|
516
|
+
args.logger.error(
|
|
517
|
+
giveUp ? 'Could not persist upload terminal event' : 'Retrying upload terminal event append',
|
|
518
|
+
error instanceof Error ? error : undefined,
|
|
519
|
+
{ uploadId: args.uploadId, filename: args.filename, attempt },
|
|
520
|
+
)
|
|
521
|
+
if (giveUp) return false
|
|
522
|
+
await new Promise<void>((resolve) => setTimeout(resolve, TERMINAL_APPEND_RETRY_DELAY_MS * attempt))
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return false
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async function processUpload(args: {
|
|
529
|
+
pluginContext: UploadsPluginContext
|
|
530
|
+
prepared: PreparedUpload
|
|
531
|
+
sessionId: SessionId
|
|
532
|
+
upload: UploadDescriptor
|
|
533
|
+
config: UploadsPluginConfig
|
|
534
|
+
logger: Logger
|
|
535
|
+
inferenceContext?: Omit<InferenceContext, 'signal'>
|
|
536
|
+
onTerminal: (result: UploadResult) => Promise<void>
|
|
537
|
+
/** Runs once the terminal status is durable in metadata, even if its event append failed. */
|
|
538
|
+
onPersisted: (result: UploadResult) => Promise<void>
|
|
539
|
+
}): Promise<UploadResult> {
|
|
540
|
+
const result = await runPreprocessor({
|
|
541
|
+
uploadStore: args.prepared.uploadStore,
|
|
542
|
+
filePath: args.prepared.filePath,
|
|
543
|
+
mimeType: args.upload.mimeType,
|
|
544
|
+
preprocessorRegistry: args.config.preprocessorRegistry,
|
|
545
|
+
processingTimeoutMs: args.config.processingTimeoutMs,
|
|
546
|
+
processingAbortGraceMs: args.config.processingAbortGraceMs,
|
|
547
|
+
archiveLimits: args.config.archiveLimits,
|
|
548
|
+
controller: args.prepared.lifecycle.controller,
|
|
549
|
+
inferenceContext: args.inferenceContext,
|
|
550
|
+
})
|
|
551
|
+
await withUploadLock(args.pluginContext, args.prepared.uploadIdStr, async () => {
|
|
552
|
+
if (args.pluginContext.deletedUploads.has(args.prepared.uploadIdStr)) return
|
|
553
|
+
const metadata = createFinalMetadata({
|
|
554
|
+
prepared: args.prepared,
|
|
555
|
+
sessionId: args.sessionId,
|
|
556
|
+
upload: args.upload,
|
|
557
|
+
result,
|
|
558
|
+
})
|
|
559
|
+
await writeMetadata(args.prepared.uploadStore, metadata)
|
|
560
|
+
if (args.pluginContext.closing) return
|
|
561
|
+
const appended = await appendTerminalEvent({
|
|
562
|
+
pluginContext: args.pluginContext,
|
|
563
|
+
append: () => args.onTerminal(result),
|
|
564
|
+
logger: args.logger,
|
|
565
|
+
uploadId: args.prepared.uploadIdStr,
|
|
566
|
+
filename: args.upload.filename,
|
|
567
|
+
})
|
|
568
|
+
if (appended) {
|
|
569
|
+
metadata.terminalEventPersisted = true
|
|
570
|
+
try {
|
|
571
|
+
await writeMetadata(args.prepared.uploadStore, metadata)
|
|
572
|
+
} catch (error) {
|
|
573
|
+
// Only the flag write failed; the event is in the log, so onSessionReady reconciles the metadata.
|
|
574
|
+
args.logger.error(
|
|
575
|
+
'Could not record the upload terminal event flag',
|
|
576
|
+
error instanceof Error ? error : undefined,
|
|
577
|
+
{ uploadId: args.prepared.uploadIdStr, filename: args.upload.filename },
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// The client has no polling fallback, so it is told either way — even when the event never landed.
|
|
582
|
+
await args.onPersisted(result)
|
|
583
|
+
}, { allowDuringClose: true })
|
|
584
|
+
return result
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function startAsyncUpload(args: {
|
|
588
|
+
pluginContext: UploadsPluginContext
|
|
589
|
+
prepared: PreparedUpload
|
|
590
|
+
sessionId: SessionId
|
|
591
|
+
upload: UploadDescriptor
|
|
592
|
+
config: UploadsPluginConfig
|
|
593
|
+
inferenceContext?: Omit<InferenceContext, 'signal'>
|
|
594
|
+
emitEvent: SessionContext['emitEvent']
|
|
595
|
+
notify: (type: 'uploadStatusChanged', payload: z.infer<typeof statusChangedSchema>) => void
|
|
596
|
+
logger: Logger
|
|
597
|
+
entryAgentId: AgentId | null
|
|
598
|
+
scheduleAgent: (agentId: AgentId) => void
|
|
599
|
+
}): void {
|
|
600
|
+
// start() rejects synchronously when `run` throws before its first await — that rejection needs a handler.
|
|
601
|
+
void args.prepared.lifecycle.start(async () => {
|
|
602
|
+
try {
|
|
603
|
+
await processUpload({
|
|
604
|
+
pluginContext: args.pluginContext,
|
|
605
|
+
prepared: args.prepared,
|
|
606
|
+
sessionId: args.sessionId,
|
|
607
|
+
upload: args.upload,
|
|
608
|
+
config: args.config,
|
|
609
|
+
logger: args.logger,
|
|
610
|
+
inferenceContext: args.inferenceContext,
|
|
611
|
+
onTerminal: async (result) => {
|
|
612
|
+
await args.emitEvent(createUploadResultEvent(args.upload, args.prepared.uploadId, result))
|
|
613
|
+
},
|
|
614
|
+
onPersisted: async (result) => {
|
|
615
|
+
if (result.status === 'ready' && args.entryAgentId) args.scheduleAgent(args.entryAgentId)
|
|
616
|
+
args.notify('uploadStatusChanged', {
|
|
617
|
+
sessionId: String(args.sessionId),
|
|
618
|
+
uploadId: args.prepared.uploadIdStr,
|
|
619
|
+
status: result.status,
|
|
620
|
+
error: result.error,
|
|
621
|
+
})
|
|
622
|
+
},
|
|
623
|
+
})
|
|
624
|
+
} catch (error) {
|
|
625
|
+
args.logger.error('Async upload processing crashed', error instanceof Error ? error : undefined, {
|
|
626
|
+
uploadId: args.prepared.uploadIdStr,
|
|
627
|
+
filename: args.upload.filename,
|
|
628
|
+
})
|
|
629
|
+
}
|
|
630
|
+
}).catch((error: unknown) => {
|
|
631
|
+
args.logger.error('Async upload never started', error instanceof Error ? error : undefined, {
|
|
632
|
+
uploadId: args.prepared.uploadIdStr,
|
|
633
|
+
filename: args.upload.filename,
|
|
634
|
+
})
|
|
635
|
+
})
|
|
636
|
+
}
|
|
142
637
|
|
|
143
|
-
|
|
638
|
+
async function removeUploadFiles(uploadStore: FileStore, path = ''): Promise<Result<void, string>> {
|
|
639
|
+
const listResult = await uploadStore.list(path, { maxDepth: 1 })
|
|
640
|
+
if (!listResult.ok) return listResult
|
|
641
|
+
for (const entry of listResult.value) {
|
|
642
|
+
const entryPath = path ? `${path}/${entry.name}` : entry.name
|
|
643
|
+
if (entry.type === 'directory') {
|
|
644
|
+
const nested = await removeUploadFiles(uploadStore, entryPath)
|
|
645
|
+
if (!nested.ok) return nested
|
|
646
|
+
continue
|
|
647
|
+
}
|
|
648
|
+
if (entryPath === 'meta.json') continue
|
|
649
|
+
if (entry.type !== 'file' && entry.type !== 'symlink') return Err(`Unsupported upload entry: ${entryPath}`)
|
|
650
|
+
const removed = await uploadStore.remove(entryPath)
|
|
651
|
+
if (!removed.ok) return removed
|
|
652
|
+
}
|
|
653
|
+
return Ok(undefined)
|
|
144
654
|
}
|
|
145
655
|
|
|
146
656
|
// ============================================================================
|
|
@@ -148,16 +658,23 @@ async function runPreprocessAndPersist(args: {
|
|
|
148
658
|
// ============================================================================
|
|
149
659
|
|
|
150
660
|
export const uploadsPlugin = definePlugin('uploads')
|
|
661
|
+
.order(70)
|
|
151
662
|
.pluginConfig<UploadsPluginConfig>()
|
|
152
663
|
.events([uploadEvents])
|
|
153
664
|
.notification('uploadStatusChanged', { schema: statusChangedSchema })
|
|
154
665
|
.state<UploadsState>({
|
|
155
666
|
key: 'uploads',
|
|
156
|
-
initial: (): UploadsState => ({ pending: [] }),
|
|
667
|
+
initial: (): UploadsState => ({ pending: [], terminal: {} }),
|
|
157
668
|
reduce: (state, event) => {
|
|
158
669
|
switch (event.type) {
|
|
159
670
|
case 'attachment_uploaded': {
|
|
160
|
-
|
|
671
|
+
const pending = state.pending.filter((upload) => String(upload.uploadId) !== String(event.uploadId))
|
|
672
|
+
if (event.status === 'processing') return { ...state, pending }
|
|
673
|
+
const terminal = {
|
|
674
|
+
...state.terminal,
|
|
675
|
+
[String(event.uploadId)]: event.status,
|
|
676
|
+
}
|
|
677
|
+
if (event.status !== 'ready') return { ...state, pending, terminal }
|
|
161
678
|
const upload: PendingUpload = {
|
|
162
679
|
uploadId: event.uploadId,
|
|
163
680
|
filename: event.filename,
|
|
@@ -167,13 +684,22 @@ export const uploadsPlugin = definePlugin('uploads')
|
|
|
167
684
|
extractedContent: event.extractedContent,
|
|
168
685
|
derivedPaths: event.derivedPaths,
|
|
169
686
|
}
|
|
170
|
-
return { ...state, pending: [...
|
|
687
|
+
return { ...state, pending: [...pending, upload], terminal }
|
|
171
688
|
}
|
|
172
689
|
case 'attachments_consumed': {
|
|
173
690
|
const consumedIds = new Set(event.uploadIds.map(String))
|
|
174
691
|
return {
|
|
175
692
|
...state,
|
|
176
693
|
pending: state.pending.filter((u) => !consumedIds.has(String(u.uploadId))),
|
|
694
|
+
terminal: withoutKeys(state.terminal, [...consumedIds]),
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
case 'attachment_deletion_completed': {
|
|
698
|
+
const uploadId = String(event.uploadId)
|
|
699
|
+
return {
|
|
700
|
+
...state,
|
|
701
|
+
pending: state.pending.filter((upload) => String(upload.uploadId) !== uploadId),
|
|
702
|
+
terminal: withoutKeys(state.terminal, [uploadId]),
|
|
177
703
|
}
|
|
178
704
|
}
|
|
179
705
|
default:
|
|
@@ -181,6 +707,15 @@ export const uploadsPlugin = definePlugin('uploads')
|
|
|
181
707
|
}
|
|
182
708
|
},
|
|
183
709
|
})
|
|
710
|
+
.context(
|
|
711
|
+
async (): Promise<UploadsPluginContext> => ({
|
|
712
|
+
activeUploads: new Map(),
|
|
713
|
+
activeMutations: new Map(),
|
|
714
|
+
deletedUploads: new Set(),
|
|
715
|
+
operationQueues: new Map(),
|
|
716
|
+
closing: false,
|
|
717
|
+
}),
|
|
718
|
+
)
|
|
184
719
|
.dequeue({
|
|
185
720
|
hasPendingMessages: (ctx) => {
|
|
186
721
|
const uploads = ctx.pluginState
|
|
@@ -191,30 +726,42 @@ export const uploadsPlugin = definePlugin('uploads')
|
|
|
191
726
|
if (uploads.pending.length === 0) return null
|
|
192
727
|
const sessionRoot = ctx.files.getRoots().session
|
|
193
728
|
return {
|
|
194
|
-
messages: [
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
729
|
+
messages: [
|
|
730
|
+
{
|
|
731
|
+
role: 'user',
|
|
732
|
+
content: formatUploadsForLLM(uploads.pending, sessionRoot),
|
|
733
|
+
},
|
|
734
|
+
],
|
|
198
735
|
token: uploads.pending.map((u) => u.uploadId),
|
|
199
736
|
}
|
|
200
737
|
},
|
|
201
738
|
markConsumed: async (ctx, token) => {
|
|
202
|
-
await
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
739
|
+
await runTrackedMutation({
|
|
740
|
+
pluginContext: ctx.pluginContext,
|
|
741
|
+
runtimeActivity: ctx.runtimeActivity,
|
|
742
|
+
reason: 'uploads:consume',
|
|
743
|
+
run: async () => {
|
|
744
|
+
await ctx.emitEvent(
|
|
745
|
+
uploadEvents.create('attachments_consumed', {
|
|
746
|
+
agentId: ctx.agentId,
|
|
747
|
+
uploadIds: token.map(UploadId),
|
|
748
|
+
}),
|
|
749
|
+
)
|
|
206
750
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
751
|
+
const { dataFileStore } = ctx.pluginConfig
|
|
752
|
+
for (const uploadIdStr of token) {
|
|
753
|
+
await withUploadLock(ctx.pluginContext, uploadIdStr, async () => {
|
|
754
|
+
const uploadStore = dataFileStore.scoped(`sessions/${ctx.sessionId}/uploads/${uploadIdStr}`)
|
|
755
|
+
const metaResult = await uploadStore.read('meta.json')
|
|
756
|
+
if (!metaResult.ok) return
|
|
757
|
+
const meta: UploadMetadata = JSON.parse(metaResult.value)
|
|
758
|
+
if (meta.status === 'deleted') return
|
|
759
|
+
meta.usedInMessageId = 'auto-dequeued'
|
|
760
|
+
await writeMetadata(uploadStore, meta)
|
|
761
|
+
}, { allowDuringClose: true })
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
})
|
|
218
765
|
},
|
|
219
766
|
})
|
|
220
767
|
.method('listPending', {
|
|
@@ -222,14 +769,16 @@ export const uploadsPlugin = definePlugin('uploads')
|
|
|
222
769
|
sessionId: z.string(),
|
|
223
770
|
}),
|
|
224
771
|
output: z.object({
|
|
225
|
-
uploads: z.array(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
772
|
+
uploads: z.array(
|
|
773
|
+
z.object({
|
|
774
|
+
uploadId: z.string(),
|
|
775
|
+
filename: z.string(),
|
|
776
|
+
mimeType: z.string(),
|
|
777
|
+
size: z.number(),
|
|
778
|
+
status: z.enum(['processing', 'ready', 'failed']),
|
|
779
|
+
createdAt: z.number(),
|
|
780
|
+
}),
|
|
781
|
+
),
|
|
233
782
|
}),
|
|
234
783
|
handler: async (ctx, input) => {
|
|
235
784
|
const { dataFileStore } = ctx.pluginConfig
|
|
@@ -282,39 +831,69 @@ export const uploadsPlugin = definePlugin('uploads')
|
|
|
282
831
|
.method('delete', {
|
|
283
832
|
input: z.object({
|
|
284
833
|
sessionId: z.string(),
|
|
285
|
-
uploadId:
|
|
834
|
+
uploadId: uploadIdSchema,
|
|
286
835
|
}),
|
|
287
836
|
output: z.object({}),
|
|
288
837
|
handler: async (ctx, input) => {
|
|
289
838
|
const { dataFileStore } = ctx.pluginConfig
|
|
290
839
|
const uploadStore = dataFileStore.scoped(`sessions/${input.sessionId}/uploads/${input.uploadId}`)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
840
|
+
try {
|
|
841
|
+
return await runTrackedMutation({
|
|
842
|
+
pluginContext: ctx.pluginContext,
|
|
843
|
+
runtimeActivity: ctx.runtimeActivity,
|
|
844
|
+
reason: `upload:${input.uploadId}:delete`,
|
|
845
|
+
run: async () => {
|
|
846
|
+
let releaseTerminalHold: RuntimeLeaseRelease | undefined
|
|
847
|
+
try {
|
|
848
|
+
const reservation = await withUploadLock(ctx.pluginContext, input.uploadId, async () => {
|
|
849
|
+
const metaResult = await uploadStore.read('meta.json')
|
|
850
|
+
if (!metaResult.ok) return Err(ValidationErrors.invalid(`Upload not found: ${input.uploadId}`))
|
|
851
|
+
const meta: UploadMetadata = JSON.parse(metaResult.value)
|
|
852
|
+
if (meta.sessionId !== input.sessionId) {
|
|
853
|
+
return Err(ValidationErrors.invalid('Upload does not belong to this session'))
|
|
854
|
+
}
|
|
855
|
+
if (meta.usedInMessageId) {
|
|
856
|
+
return Err(ValidationErrors.invalid('Cannot delete an upload that has been sent in a message'))
|
|
857
|
+
}
|
|
858
|
+
ctx.pluginContext.deletedUploads.add(input.uploadId)
|
|
859
|
+
const active = ctx.pluginContext.activeUploads.get(input.uploadId)
|
|
860
|
+
releaseTerminalHold = active?.holdTerminalMutation()
|
|
861
|
+
active?.controller.abort(new Error('Upload deleted'))
|
|
862
|
+
return Ok(active?.processingCompletion)
|
|
863
|
+
}, { allowDuringClose: true })
|
|
864
|
+
if (!reservation.ok) return reservation
|
|
865
|
+
await reservation.value
|
|
302
866
|
|
|
303
|
-
|
|
304
|
-
|
|
867
|
+
return await withUploadLock(ctx.pluginContext, input.uploadId, async () => {
|
|
868
|
+
const metaResult = await uploadStore.read('meta.json')
|
|
869
|
+
if (!metaResult.ok) return Err(ValidationErrors.invalid(`Upload not found: ${input.uploadId}`))
|
|
870
|
+
const meta: UploadMetadata = JSON.parse(metaResult.value)
|
|
871
|
+
meta.status = 'deleted'
|
|
872
|
+
await writeMetadata(uploadStore, meta)
|
|
873
|
+
const cleanup = await removeUploadFiles(uploadStore)
|
|
874
|
+
if (!cleanup.ok) return Err(ValidationErrors.invalid(`Could not remove upload: ${input.uploadId}`))
|
|
875
|
+
await ctx.emitEvent(
|
|
876
|
+
uploadEvents.create('attachment_deletion_completed', {
|
|
877
|
+
uploadId: input.uploadId,
|
|
878
|
+
}),
|
|
879
|
+
)
|
|
880
|
+
return Ok({})
|
|
881
|
+
}, { allowDuringClose: true })
|
|
882
|
+
} finally {
|
|
883
|
+
ctx.pluginContext.deletedUploads.delete(input.uploadId)
|
|
884
|
+
releaseTerminalHold?.()
|
|
885
|
+
}
|
|
886
|
+
},
|
|
887
|
+
})
|
|
888
|
+
} catch (error) {
|
|
889
|
+
return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not delete upload'))
|
|
305
890
|
}
|
|
306
|
-
|
|
307
|
-
// Mark as deleted
|
|
308
|
-
meta.status = 'deleted'
|
|
309
|
-
await uploadStore.write('meta.json', JSON.stringify(meta, null, 2))
|
|
310
|
-
|
|
311
|
-
return Ok({})
|
|
312
891
|
},
|
|
313
892
|
})
|
|
314
893
|
.method('loadAttachments', {
|
|
315
894
|
input: z.object({
|
|
316
895
|
sessionId: z.string(),
|
|
317
|
-
uploadIds: z.array(
|
|
896
|
+
uploadIds: z.array(uploadIdSchema),
|
|
318
897
|
}),
|
|
319
898
|
output: z.object({
|
|
320
899
|
attachments: z.array(z.unknown()),
|
|
@@ -357,24 +936,62 @@ export const uploadsPlugin = definePlugin('uploads')
|
|
|
357
936
|
.method('markUsed', {
|
|
358
937
|
input: z.object({
|
|
359
938
|
sessionId: z.string(),
|
|
360
|
-
|
|
939
|
+
// The id lands in a path, so it is validated here rather than inside the loop below.
|
|
940
|
+
uploadIds: z.array(uploadIdSchema),
|
|
361
941
|
messageId: z.string(),
|
|
362
942
|
}),
|
|
363
943
|
output: z.object({}),
|
|
364
944
|
handler: async (ctx, input) => {
|
|
365
945
|
const { dataFileStore } = ctx.pluginConfig
|
|
946
|
+
try {
|
|
947
|
+
return await runTrackedMutation({
|
|
948
|
+
pluginContext: ctx.pluginContext,
|
|
949
|
+
runtimeActivity: ctx.runtimeActivity,
|
|
950
|
+
reason: 'uploads:mark-used',
|
|
951
|
+
run: async () => {
|
|
952
|
+
const successfulIds: string[] = []
|
|
953
|
+
const emitSuccessfulConsumption = async () => {
|
|
954
|
+
if (successfulIds.length === 0) return
|
|
955
|
+
await ctx.emitEvent(
|
|
956
|
+
uploadEvents.create('attachments_consumed', {
|
|
957
|
+
agentId: getEntryAgentId(ctx.sessionState) ?? 'uploads.markUsed',
|
|
958
|
+
uploadIds: successfulIds.map(UploadId),
|
|
959
|
+
}),
|
|
960
|
+
)
|
|
961
|
+
}
|
|
366
962
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
963
|
+
for (const uploadIdStr of input.uploadIds) {
|
|
964
|
+
let result: Result<Record<string, never>, ReturnType<typeof ValidationErrors.invalid>>
|
|
965
|
+
try {
|
|
966
|
+
result = await withUploadLock(ctx.pluginContext, uploadIdStr, async () => {
|
|
967
|
+
const uploadStore = dataFileStore.scoped(`sessions/${input.sessionId}/uploads/${uploadIdStr}`)
|
|
968
|
+
const metaResult = await uploadStore.read('meta.json')
|
|
969
|
+
if (!metaResult.ok || ctx.pluginContext.deletedUploads.has(uploadIdStr)) {
|
|
970
|
+
return Err(ValidationErrors.invalid(`Upload not found: ${uploadIdStr}`))
|
|
971
|
+
}
|
|
972
|
+
const meta: UploadMetadata = JSON.parse(metaResult.value)
|
|
973
|
+
if (meta.status === 'deleted') return Err(ValidationErrors.invalid(`Upload not found: ${uploadIdStr}`))
|
|
974
|
+
meta.usedInMessageId = input.messageId
|
|
975
|
+
await writeMetadata(uploadStore, meta)
|
|
976
|
+
return Ok({})
|
|
977
|
+
}, { allowDuringClose: true })
|
|
978
|
+
} catch (error) {
|
|
979
|
+
await emitSuccessfulConsumption()
|
|
980
|
+
throw error
|
|
981
|
+
}
|
|
982
|
+
if (!result.ok) {
|
|
983
|
+
await emitSuccessfulConsumption()
|
|
984
|
+
return result
|
|
985
|
+
}
|
|
986
|
+
successfulIds.push(uploadIdStr)
|
|
987
|
+
}
|
|
988
|
+
await emitSuccessfulConsumption()
|
|
989
|
+
return Ok({})
|
|
990
|
+
},
|
|
991
|
+
})
|
|
992
|
+
} catch (error) {
|
|
993
|
+
return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not mark upload as used'))
|
|
375
994
|
}
|
|
376
|
-
|
|
377
|
-
return Ok({})
|
|
378
995
|
},
|
|
379
996
|
})
|
|
380
997
|
.method('upload', {
|
|
@@ -391,56 +1008,54 @@ export const uploadsPlugin = definePlugin('uploads')
|
|
|
391
1008
|
extractedContent: z.string().optional(),
|
|
392
1009
|
}),
|
|
393
1010
|
handler: async (ctx, input) => {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (!isAllowedMimeType(input.mimeType)) {
|
|
400
|
-
return Err(ValidationErrors.invalid(`Unsupported file type: ${input.mimeType}`))
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const uploadId = generateUploadId()
|
|
404
|
-
const uploadStore = dataFileStore.scoped(`sessions/${input.sessionId}/uploads/${uploadId}`)
|
|
405
|
-
|
|
406
|
-
const writeResult = await uploadStore.write(input.filename, input.fileBuffer)
|
|
407
|
-
if (!writeResult.ok) {
|
|
408
|
-
return Err(ValidationErrors.invalid('Failed to write file'))
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const result = await runPreprocessAndPersist({
|
|
412
|
-
uploadId: String(uploadId),
|
|
413
|
-
sessionId: ctx.sessionId,
|
|
414
|
-
uploadStore,
|
|
415
|
-
filePath: writeResult.value.path,
|
|
416
|
-
filename: input.filename,
|
|
417
|
-
mimeType: input.mimeType,
|
|
418
|
-
size: input.size,
|
|
419
|
-
createdAt: Date.now(),
|
|
420
|
-
preprocessorRegistry,
|
|
1011
|
+
const preparedResult = await prepareUpload({
|
|
1012
|
+
pluginContext: ctx.pluginContext,
|
|
1013
|
+
dataFileStore: ctx.pluginConfig.dataFileStore,
|
|
1014
|
+
logger: ctx.logger,
|
|
1015
|
+
input,
|
|
421
1016
|
})
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
1017
|
+
if (!preparedResult.ok) return Err(preparedResult.error)
|
|
1018
|
+
const prepared = preparedResult.value
|
|
1019
|
+
// Built from the stored name, not from `input`: the metadata and the event tell the agent
|
|
1020
|
+
// where the file is, and a spelling that differs from the directory entry is unusable.
|
|
1021
|
+
const upload: UploadDescriptor = {
|
|
1022
|
+
filename: prepared.filename,
|
|
426
1023
|
mimeType: input.mimeType,
|
|
427
1024
|
size: input.size,
|
|
428
|
-
|
|
429
|
-
extractedContent: result.extractedContent,
|
|
430
|
-
derivedPaths: result.derivedPaths,
|
|
431
|
-
error: result.error,
|
|
432
|
-
}))
|
|
433
|
-
|
|
1025
|
+
}
|
|
434
1026
|
const entryAgentId = getEntryAgentId(ctx.sessionState)
|
|
435
|
-
|
|
436
|
-
|
|
1027
|
+
try {
|
|
1028
|
+
const result = await prepared.lifecycle.start(() =>
|
|
1029
|
+
processUpload({
|
|
1030
|
+
pluginContext: ctx.pluginContext,
|
|
1031
|
+
prepared,
|
|
1032
|
+
sessionId: ctx.sessionId,
|
|
1033
|
+
upload,
|
|
1034
|
+
config: ctx.pluginConfig,
|
|
1035
|
+
logger: ctx.logger,
|
|
1036
|
+
inferenceContext: entryAgentId
|
|
1037
|
+
? {
|
|
1038
|
+
sessionId: String(ctx.sessionId),
|
|
1039
|
+
agentId: String(entryAgentId),
|
|
1040
|
+
fileStore: ctx.files,
|
|
1041
|
+
}
|
|
1042
|
+
: undefined,
|
|
1043
|
+
onTerminal: async (finalResult) => {
|
|
1044
|
+
await ctx.emitEvent(createUploadResultEvent(upload, prepared.uploadId, finalResult))
|
|
1045
|
+
},
|
|
1046
|
+
onPersisted: async (finalResult) => {
|
|
1047
|
+
if (finalResult.status === 'ready' && entryAgentId) ctx.scheduleAgent(entryAgentId)
|
|
1048
|
+
},
|
|
1049
|
+
}),
|
|
1050
|
+
)
|
|
1051
|
+
return Ok({
|
|
1052
|
+
uploadId: prepared.uploadIdStr,
|
|
1053
|
+
status: result.status,
|
|
1054
|
+
extractedContent: result.extractedContent,
|
|
1055
|
+
})
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Upload processing failed'))
|
|
437
1058
|
}
|
|
438
|
-
|
|
439
|
-
return Ok({
|
|
440
|
-
uploadId: String(uploadId),
|
|
441
|
-
status: result.status,
|
|
442
|
-
extractedContent: result.extractedContent,
|
|
443
|
-
})
|
|
444
1059
|
},
|
|
445
1060
|
})
|
|
446
1061
|
.method('uploadAsync', {
|
|
@@ -456,126 +1071,229 @@ export const uploadsPlugin = definePlugin('uploads')
|
|
|
456
1071
|
status: z.enum(['processing']),
|
|
457
1072
|
}),
|
|
458
1073
|
handler: async (ctx, input) => {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (!writeResult.ok) {
|
|
474
|
-
return Err(ValidationErrors.invalid('Failed to write file'))
|
|
1074
|
+
const preparedResult = await prepareUpload({
|
|
1075
|
+
pluginContext: ctx.pluginContext,
|
|
1076
|
+
dataFileStore: ctx.pluginConfig.dataFileStore,
|
|
1077
|
+
logger: ctx.logger,
|
|
1078
|
+
input,
|
|
1079
|
+
})
|
|
1080
|
+
if (!preparedResult.ok) return Err(preparedResult.error)
|
|
1081
|
+
const prepared = preparedResult.value
|
|
1082
|
+
// Built from the stored name, not from `input`: the metadata and the event tell the agent
|
|
1083
|
+
// where the file is, and a spelling that differs from the directory entry is unusable.
|
|
1084
|
+
const upload: UploadDescriptor = {
|
|
1085
|
+
filename: prepared.filename,
|
|
1086
|
+
mimeType: input.mimeType,
|
|
1087
|
+
size: input.size,
|
|
475
1088
|
}
|
|
476
|
-
|
|
477
|
-
const filePath = writeResult.value.path
|
|
478
|
-
const createdAt = Date.now()
|
|
479
|
-
|
|
480
|
-
// Persist initial 'processing' metadata so listPending sees it before preprocessor finishes.
|
|
481
1089
|
const processingMeta: UploadMetadata = {
|
|
482
|
-
uploadId,
|
|
1090
|
+
uploadId: prepared.uploadId,
|
|
483
1091
|
sessionId: ctx.sessionId,
|
|
484
|
-
filename:
|
|
485
|
-
mimeType:
|
|
486
|
-
size:
|
|
487
|
-
path: filePath,
|
|
1092
|
+
filename: upload.filename,
|
|
1093
|
+
mimeType: upload.mimeType,
|
|
1094
|
+
size: upload.size,
|
|
1095
|
+
path: prepared.filePath,
|
|
488
1096
|
status: 'processing',
|
|
489
|
-
createdAt,
|
|
490
|
-
|
|
1097
|
+
createdAt: prepared.createdAt,
|
|
1098
|
+
}
|
|
1099
|
+
try {
|
|
1100
|
+
await writeMetadata(prepared.uploadStore, processingMeta)
|
|
1101
|
+
await ctx.emitEvent(
|
|
1102
|
+
uploadEvents.create('attachment_uploaded', {
|
|
1103
|
+
uploadId: prepared.uploadId,
|
|
1104
|
+
filename: upload.filename,
|
|
1105
|
+
mimeType: upload.mimeType,
|
|
1106
|
+
size: upload.size,
|
|
1107
|
+
status: 'processing',
|
|
1108
|
+
}),
|
|
1109
|
+
)
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
prepared.lifecycle.abandon()
|
|
1112
|
+
return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not start upload'))
|
|
491
1113
|
}
|
|
492
|
-
|
|
1114
|
+
try {
|
|
1115
|
+
const releaseRuntimeLease = ctx.runtimeActivity.acquire(`upload:${prepared.uploadIdStr}:processing`)
|
|
1116
|
+
prepared.lifecycle.attachRuntimeLease(releaseRuntimeLease)
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
prepared.lifecycle.abandon()
|
|
1119
|
+
return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not start upload'))
|
|
1120
|
+
}
|
|
1121
|
+
// Everything from here to startAsyncUpload must be guarded: the lifecycle
|
|
1122
|
+
// only settles once lifecycle.start() runs, so a throw before it leaks the
|
|
1123
|
+
// runtime lease AND hangs onSessionClose, which awaits this completion.
|
|
1124
|
+
// ctx.notify is the realistic trigger — it reaches embedder-supplied
|
|
1125
|
+
// onUserOutput, which in standalone/sandbox writes to sockets.
|
|
1126
|
+
try {
|
|
1127
|
+
ctx.notify('uploadStatusChanged', {
|
|
1128
|
+
sessionId: input.sessionId,
|
|
1129
|
+
uploadId: prepared.uploadIdStr,
|
|
1130
|
+
status: 'processing',
|
|
1131
|
+
})
|
|
493
1132
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
1133
|
+
const sessionId = ctx.sessionId
|
|
1134
|
+
const sessionIdString = String(sessionId)
|
|
1135
|
+
const entryAgentId = getEntryAgentId(ctx.sessionState)
|
|
1136
|
+
const inferenceContext = entryAgentId
|
|
1137
|
+
? {
|
|
1138
|
+
sessionId: sessionIdString,
|
|
1139
|
+
agentId: String(entryAgentId),
|
|
1140
|
+
fileStore: ctx.files,
|
|
1141
|
+
}
|
|
1142
|
+
: undefined
|
|
1143
|
+
startAsyncUpload({
|
|
1144
|
+
pluginContext: ctx.pluginContext,
|
|
1145
|
+
prepared,
|
|
1146
|
+
sessionId,
|
|
1147
|
+
upload,
|
|
1148
|
+
config: ctx.pluginConfig,
|
|
1149
|
+
inferenceContext,
|
|
1150
|
+
emitEvent: ctx.emitEvent,
|
|
1151
|
+
notify: ctx.notify,
|
|
1152
|
+
logger: ctx.logger,
|
|
1153
|
+
entryAgentId,
|
|
1154
|
+
scheduleAgent: ctx.scheduleAgent,
|
|
1155
|
+
})
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
prepared.lifecycle.abandon()
|
|
1158
|
+
return Err(ValidationErrors.invalid(error instanceof Error ? error.message : 'Could not start upload'))
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return Ok({
|
|
1162
|
+
uploadId: prepared.uploadIdStr,
|
|
504
1163
|
status: 'processing',
|
|
505
1164
|
})
|
|
1165
|
+
},
|
|
1166
|
+
})
|
|
1167
|
+
.sessionHook('onSessionReady', async (ctx) => {
|
|
1168
|
+
const uploadsPath = `sessions/${ctx.sessionId}/uploads`
|
|
1169
|
+
const listResult = await ctx.pluginConfig.dataFileStore.list(uploadsPath)
|
|
1170
|
+
if (!listResult.ok) return
|
|
1171
|
+
const seenUploadIds = new Set<string>()
|
|
506
1172
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
filename: input.filename,
|
|
531
|
-
mimeType: input.mimeType,
|
|
532
|
-
size: input.size,
|
|
533
|
-
status: result.status,
|
|
534
|
-
extractedContent: result.extractedContent,
|
|
535
|
-
derivedPaths: result.derivedPaths,
|
|
536
|
-
error: result.error,
|
|
537
|
-
}))
|
|
538
|
-
if (result.status === 'ready' && entryAgentId) {
|
|
539
|
-
scheduleAgent(entryAgentId)
|
|
540
|
-
}
|
|
541
|
-
notify('uploadStatusChanged', {
|
|
542
|
-
sessionId: input.sessionId,
|
|
543
|
-
uploadId: uploadIdStr,
|
|
544
|
-
status: result.status,
|
|
545
|
-
extractedContent: result.extractedContent,
|
|
546
|
-
error: result.error,
|
|
547
|
-
})
|
|
548
|
-
} catch (err) {
|
|
549
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
550
|
-
logger.error('Async upload processing crashed', err instanceof Error ? err : undefined, {
|
|
551
|
-
uploadId: uploadIdStr,
|
|
552
|
-
filename: input.filename,
|
|
553
|
-
})
|
|
554
|
-
try {
|
|
555
|
-
await emitEvent(uploadEvents.create('attachment_uploaded', {
|
|
556
|
-
uploadId,
|
|
557
|
-
filename: input.filename,
|
|
558
|
-
mimeType: input.mimeType,
|
|
559
|
-
size: input.size,
|
|
560
|
-
status: 'failed',
|
|
561
|
-
error: message,
|
|
1173
|
+
for (const entry of listResult.value) {
|
|
1174
|
+
if (entry.type !== 'directory') continue
|
|
1175
|
+
seenUploadIds.add(entry.name)
|
|
1176
|
+
const uploadStore = ctx.pluginConfig.dataFileStore.scoped(`${uploadsPath}/${entry.name}`)
|
|
1177
|
+
try {
|
|
1178
|
+
const metaResult = await uploadStore.read('meta.json')
|
|
1179
|
+
if (!metaResult.ok) continue
|
|
1180
|
+
const metadata: UploadMetadata = JSON.parse(metaResult.value)
|
|
1181
|
+
if (String(metadata.sessionId) !== String(ctx.sessionId)) continue
|
|
1182
|
+
// The agent can write into the upload dir, and this id becomes a path handed back to it.
|
|
1183
|
+
const parsedUploadId = parseUploadId(String(metadata.uploadId))
|
|
1184
|
+
if (!parsedUploadId.ok) {
|
|
1185
|
+
ctx.logger.warn('Skipping upload metadata with an invalid upload id', { entry: entry.name })
|
|
1186
|
+
continue
|
|
1187
|
+
}
|
|
1188
|
+
const uploadId = String(parsedUploadId.value)
|
|
1189
|
+
seenUploadIds.add(uploadId)
|
|
1190
|
+
const projection = ctx.pluginState.terminal[uploadId]
|
|
1191
|
+
if (metadata.usedInMessageId) {
|
|
1192
|
+
if (projection) {
|
|
1193
|
+
await ctx.emitEvent(uploadEvents.create('attachments_consumed', {
|
|
1194
|
+
agentId: 'uploads.recovery',
|
|
1195
|
+
uploadIds: [metadata.uploadId],
|
|
562
1196
|
}))
|
|
563
|
-
} catch {
|
|
564
|
-
// Even event emission failed — best-effort; nothing useful left to do.
|
|
565
1197
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
1198
|
+
continue
|
|
1199
|
+
}
|
|
1200
|
+
if (metadata.status === 'deleted') {
|
|
1201
|
+
if (projection) {
|
|
1202
|
+
await ctx.emitEvent(uploadEvents.create('attachment_deletion_completed', { uploadId: metadata.uploadId }))
|
|
1203
|
+
}
|
|
1204
|
+
continue
|
|
572
1205
|
}
|
|
573
|
-
|
|
1206
|
+
if (projection) {
|
|
1207
|
+
if (metadata.status !== projection || !metadata.terminalEventPersisted) {
|
|
1208
|
+
metadata.status = projection
|
|
1209
|
+
// createFinalMetadata already wrote result.error before the terminal event.
|
|
1210
|
+
metadata.error = projection === 'failed' ? metadata.error ?? 'Processing failed' : undefined
|
|
1211
|
+
if (projection === 'ready') {
|
|
1212
|
+
const pending = ctx.pluginState.pending.find((upload) => String(upload.uploadId) === uploadId)
|
|
1213
|
+
if (!pending) throw new Error('Ready terminal event has no pending projection')
|
|
1214
|
+
metadata.extractedContent = pending.extractedContent
|
|
1215
|
+
metadata.derivedPaths = pending.derivedPaths
|
|
1216
|
+
} else {
|
|
1217
|
+
metadata.extractedContent = undefined
|
|
1218
|
+
metadata.derivedPaths = undefined
|
|
1219
|
+
}
|
|
1220
|
+
metadata.completedAt ??= Date.now()
|
|
1221
|
+
metadata.terminalEventPersisted = true
|
|
1222
|
+
await writeMetadata(uploadStore, metadata)
|
|
1223
|
+
}
|
|
1224
|
+
continue
|
|
1225
|
+
}
|
|
1226
|
+
if (metadata.terminalEventPersisted) continue
|
|
574
1227
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
1228
|
+
if (metadata.status === 'processing') {
|
|
1229
|
+
metadata.status = 'failed'
|
|
1230
|
+
metadata.error = 'Processing interrupted by restart'
|
|
1231
|
+
metadata.completedAt = Date.now()
|
|
1232
|
+
await writeMetadata(uploadStore, metadata)
|
|
1233
|
+
}
|
|
1234
|
+
const result: UploadResult = metadata.status === 'ready'
|
|
1235
|
+
? {
|
|
1236
|
+
status: 'ready',
|
|
1237
|
+
extractedContent: metadata.extractedContent,
|
|
1238
|
+
derivedPaths: metadata.derivedPaths,
|
|
1239
|
+
}
|
|
1240
|
+
: { status: 'failed', error: metadata.error }
|
|
1241
|
+
const recoveredUpload: UploadDescriptor = {
|
|
1242
|
+
filename: metadata.filename,
|
|
1243
|
+
mimeType: metadata.mimeType,
|
|
1244
|
+
size: metadata.size,
|
|
1245
|
+
}
|
|
1246
|
+
await ctx.emitEvent(createUploadResultEvent(recoveredUpload, metadata.uploadId, result))
|
|
1247
|
+
metadata.terminalEventPersisted = true
|
|
1248
|
+
await writeMetadata(uploadStore, metadata)
|
|
1249
|
+
ctx.notify('uploadStatusChanged', {
|
|
1250
|
+
sessionId: String(ctx.sessionId),
|
|
1251
|
+
uploadId,
|
|
1252
|
+
status: result.status,
|
|
1253
|
+
error: result.error,
|
|
1254
|
+
})
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
ctx.logger.warn('Could not recover interrupted upload', {
|
|
1257
|
+
uploadId: entry.name,
|
|
1258
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1259
|
+
})
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
// A fork copies the event log but not the uploads dir, so every inherited upload looks
|
|
1263
|
+
// orphaned here — sweeping them would delete attachments the fork still shows.
|
|
1264
|
+
if (ctx.sessionState.forkedFrom) return
|
|
1265
|
+
for (const uploadId of Object.keys(ctx.pluginState.terminal)) {
|
|
1266
|
+
if (seenUploadIds.has(uploadId)) continue
|
|
1267
|
+
await ctx.emitEvent(uploadEvents.create('attachment_deletion_completed', { uploadId: UploadId(uploadId) }))
|
|
1268
|
+
}
|
|
1269
|
+
})
|
|
1270
|
+
.sessionHook('onSessionClose', async (ctx) => {
|
|
1271
|
+
ctx.pluginContext.closing = true
|
|
1272
|
+
const active = [...ctx.pluginContext.activeUploads.values()]
|
|
1273
|
+
for (const upload of active) upload.controller.abort(new Error('Session is closing'))
|
|
1274
|
+
const configuredDrainTimeoutMs = ctx.pluginConfig.closeDrainTimeoutMs
|
|
1275
|
+
const settled = await settleWithTimeout(
|
|
1276
|
+
[...active.map((upload) => upload.completion), ...ctx.pluginContext.activeMutations.keys()],
|
|
1277
|
+
configuredDrainTimeoutMs !== undefined && configuredDrainTimeoutMs > 0 ? configuredDrainTimeoutMs : CLOSE_DRAIN_TIMEOUT_MS,
|
|
1278
|
+
)
|
|
1279
|
+
if (!settled) {
|
|
1280
|
+
// Releasing the manager's map entry is not the same as freeing the session: a wedged lifecycle
|
|
1281
|
+
// still holds emitEvent/notify/scheduleAgent closures, so the runtime graph outlives disposal
|
|
1282
|
+
// until it settles — bounded here only so disposal itself never hangs on it.
|
|
1283
|
+
ctx.logger.warn('Upload lifecycles did not settle before close', {
|
|
1284
|
+
sessionId: String(ctx.sessionId),
|
|
1285
|
+
reason: ctx.reason,
|
|
1286
|
+
pendingUploadIds: [...ctx.pluginContext.activeUploads.keys()],
|
|
1287
|
+
pendingMutations: [...ctx.pluginContext.activeMutations.values()],
|
|
578
1288
|
})
|
|
579
|
-
}
|
|
1289
|
+
}
|
|
1290
|
+
ctx.pluginContext.activeUploads.clear()
|
|
1291
|
+
ctx.pluginContext.activeMutations.clear()
|
|
1292
|
+
if (settled) {
|
|
1293
|
+
// Work that outlived the drain still reads these: clearing them un-guards a deleted upload
|
|
1294
|
+
// against its own processUpload and drops the lock that keeps the two ordered.
|
|
1295
|
+
ctx.pluginContext.deletedUploads.clear()
|
|
1296
|
+
ctx.pluginContext.operationQueues.clear()
|
|
1297
|
+
}
|
|
580
1298
|
})
|
|
581
1299
|
.build()
|