dsh-agent-plugins-market 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -154
- package/README.zh.md +74 -164
- package/assets/dsh-agent-plugins.png +0 -0
- package/client/client.js +5371 -1085
- package/client/client.js.map +1 -1
- package/lib/index.js +2 -257
- package/lib/{application → packages/market-bundle/src/application}/catalog.js +207 -41
- package/lib/{application → packages/market-bundle/src/application}/details.js +73 -29
- package/lib/packages/market-bundle/src/application/extension-inventory.js +408 -0
- package/lib/packages/market-bundle/src/application/extension-project.js +64 -0
- package/lib/packages/market-bundle/src/application/resource-inventory.js +96 -0
- package/lib/packages/market-bundle/src/application/server-config.js +9 -0
- package/lib/packages/market-bundle/src/application/suite-queries.js +86 -0
- package/lib/packages/market-bundle/src/index.js +372 -0
- package/lib/{runtime/host → packages/market-bundle/src/platform}/feedback-tool.js +2 -2
- package/lib/{runtime/host → packages/market-bundle/src/platform}/settings-namespace.js +55 -7
- package/lib/packages/market-bundle/src/platform/settings-schema.js +25 -0
- package/lib/packages/market-bundle/src/routes-extension-presets.js +161 -0
- package/lib/packages/market-bundle/src/routes-resources.js +127 -0
- package/lib/{routes.js → packages/market-bundle/src/routes.js} +225 -21
- package/lib/packages/market-bundle/src/runtime-adapters.js +57 -0
- package/lib/packages/market-bundle/src/session-extension.js +337 -0
- package/lib/{application → packages/market-catalog/src/application}/catalog-context.js +7 -4
- package/lib/{application → packages/market-catalog/src/application}/install-store.js +3 -3
- package/lib/{application → packages/market-catalog/src/application}/snapshot-cache.js +100 -11
- package/lib/{application → packages/market-catalog/src/application}/source-store.js +24 -11
- package/lib/{application → packages/market-catalog/src/application}/state/state-store.js +1 -1
- package/lib/{application → packages/market-catalog/src/application}/state/storage-migration.js +1 -1
- package/lib/packages/market-catalog/src/index.js +19 -0
- package/lib/{catalog → packages/market-catalog/src/scanning}/archive.js +1 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/codex-mcp.js +1 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/component-files.js +2 -2
- package/lib/{catalog → packages/market-catalog/src/scanning}/manifests.js +2 -2
- package/lib/{catalog → packages/market-catalog/src/scanning}/native-project.js +1 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/paths.js +1 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/plugin-variables.js +21 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/scan-resolvers.js +35 -2
- package/lib/{catalog → packages/market-catalog/src/scanning}/skills-parse.js +40 -10
- package/lib/{catalog → packages/market-catalog/src/scanning}/validate.js +15 -2
- package/lib/packages/market-contracts/src/contracts/extension-presets.js +125 -0
- package/lib/{contracts → packages/market-contracts/src/contracts}/market.js +33 -5
- package/lib/packages/market-contracts/src/contracts/mcp-overrides.js +2 -0
- package/lib/packages/market-contracts/src/contracts/resource-window.js +29 -0
- package/lib/packages/market-contracts/src/contracts/settings.js +130 -0
- package/lib/packages/market-contracts/src/contracts/surface-toggles.js +29 -0
- package/lib/packages/market-contracts/src/contracts/translation.js +28 -0
- package/lib/packages/market-contracts/src/index.js +4 -0
- package/lib/{model → packages/market-contracts/src/model}/layouts.js +11 -0
- package/lib/{application → packages/market-contracts/src/ports}/ports.js +3 -0
- package/lib/{application/mcp/mcp-redaction.js → packages/market-contracts/src/redaction.js} +17 -1
- package/lib/{application → packages/market-lsp/src/application}/lsp/lsp-direct-config.js +2 -2
- package/lib/{application → packages/market-lsp/src/application}/lsp/lsp-server-state.js +1 -1
- package/lib/{application → packages/market-lsp/src/application}/lsp/lsp-status.js +20 -3
- package/lib/{application → packages/market-lsp/src/application}/lsp/profile-seam.js +1 -1
- package/lib/packages/market-lsp/src/application/lsp/validation.js +46 -0
- package/lib/{application → packages/market-lsp/src/application}/lsp-service.js +61 -3
- package/lib/packages/market-lsp/src/index.js +8 -0
- package/lib/{runtime → packages/market-lsp/src/runtime}/lsp/lsp-mounts.js +79 -40
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-backend.js +0 -16
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-config.js +104 -21
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-direct-config.js +12 -4
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-overrides.js +4 -4
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-status.js +49 -10
- package/lib/packages/market-mcp/src/application/mcp/validation.js +63 -0
- package/lib/{application → packages/market-mcp/src/application}/mcp-service.js +111 -91
- package/lib/packages/market-mcp/src/index.js +10 -0
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/bridge.js +12 -9
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/connection.js +1 -1
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/oauth.js +1 -1
- package/lib/packages/market-mcp/src/runtime/mcp/bridge/tool-ownership.js +26 -0
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/tools.js +1 -1
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-auth-record.js +35 -4
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-mounts.js +35 -4
- package/lib/packages/market-runtime/src/adapter-contracts.js +1 -0
- package/lib/packages/market-runtime/src/application/command-resources.js +74 -0
- package/lib/packages/market-runtime/src/application/extension-authorization.js +27 -0
- package/lib/packages/market-runtime/src/application/extension-hook-selection.js +56 -0
- package/lib/packages/market-runtime/src/application/extension-suite-declarations.js +57 -0
- package/lib/packages/market-runtime/src/application/extension-suite-selection.js +84 -0
- package/lib/packages/market-runtime/src/application/fs-probe.js +43 -0
- package/lib/packages/market-runtime/src/application/panel-resources.js +370 -0
- package/lib/{application → packages/market-runtime/src/application}/panels/user-hooks.js +2 -2
- package/lib/{application → packages/market-runtime/src/application}/panels/user-store.js +53 -13
- package/lib/{application → packages/market-runtime/src/application}/project-agent-roles.js +20 -11
- package/lib/packages/market-runtime/src/application/state/extension-presets.js +144 -0
- package/lib/packages/market-runtime/src/application/state/resource-favorites.js +90 -0
- package/lib/packages/market-runtime/src/application/state/resource-filters.js +2 -0
- package/lib/packages/market-runtime/src/application/state/surface-toggles.js +10 -0
- package/lib/packages/market-runtime/src/application/state/workspace-policy.js +56 -0
- package/lib/packages/market-runtime/src/catalog-port.js +1 -0
- package/lib/packages/market-runtime/src/extension-service.js +1 -0
- package/lib/packages/market-runtime/src/index.js +31 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/agents/agent-role-router.js +74 -79
- package/lib/packages/market-runtime/src/runtime/agents/agent-teams-seat.js +58 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/agents/subagent-catalog.js +89 -21
- package/lib/packages/market-runtime/src/runtime/agents/team-coordination.js +51 -0
- package/lib/packages/market-runtime/src/runtime/agents/teammate-role-runtime.js +241 -0
- package/lib/packages/market-runtime/src/runtime/agents/teammate-role-tool.js +130 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/core/reconcile-scheduler.js +2 -2
- package/lib/packages/market-runtime/src/runtime/core/reconciler.js +457 -0
- package/lib/packages/market-runtime/src/runtime/host/command-name-allocator.js +168 -0
- package/lib/packages/market-runtime/src/runtime/host/extension-progress.js +120 -0
- package/lib/packages/market-runtime/src/runtime/host/extension-runtime.js +405 -0
- package/lib/packages/market-runtime/src/runtime/host/extension-session-state.js +839 -0
- package/lib/packages/market-runtime/src/runtime/host/extension-tool-gates.js +96 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/host/failure-detail.js +1 -1
- package/lib/{runtime → packages/market-runtime/src/runtime}/host/host-locale.js +2 -0
- package/lib/packages/market-runtime/src/runtime/host/menu-row-identities.js +38 -0
- package/lib/packages/market-runtime/src/runtime/host/resource-filter-service.js +81 -0
- package/lib/packages/market-runtime/src/runtime/host/scoped-contributors.js +304 -0
- package/lib/packages/market-runtime/src/runtime/host/surface-toggle-service.js +15 -0
- package/lib/packages/market-runtime/src/runtime/panels/user-commands.js +211 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/panels/user-panels.js +34 -14
- package/lib/packages/market-runtime/src/runtime/surfaces/commands-mounts.js +256 -0
- package/lib/packages/market-runtime/src/runtime/surfaces/extension-hooks.js +294 -0
- package/lib/packages/market-runtime/src/runtime/surfaces/hook-dry-run.js +122 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/surfaces/hooks-mounts.js +1 -1
- package/lib/{runtime → packages/market-runtime/src/runtime}/surfaces/project-runtime.js +23 -13
- package/lib/packages/market-runtime/src/runtime/surfaces/skills-provider.js +241 -0
- package/lib/packages/market-translation/src/application/state/translation-cache.js +122 -0
- package/lib/packages/market-translation/src/application/translation/chain.js +99 -0
- package/lib/packages/market-translation/src/application/translation/document.js +384 -0
- package/lib/packages/market-translation/src/application/translation/localizer.js +642 -0
- package/lib/packages/market-translation/src/application/translation/service.js +113 -0
- package/lib/packages/market-translation/src/application/translation/unit.js +19 -0
- package/lib/packages/market-translation/src/index.js +4 -0
- package/lib/packages/market-translation/src/runtime/host/llm-translator.js +278 -0
- package/lib/packages/market-translation/src/runtime/host/machine-translator.js +152 -0
- package/lib/packages/market-translation/src/runtime/host/text-masking.js +96 -0
- package/lib/packages/market-translation/src/runtime/host/translation-providers.js +116 -0
- package/lib/types/index.d.ts +2 -130
- package/lib/types/{application → packages/market-bundle/src/application}/catalog.d.ts +117 -22
- package/lib/types/packages/market-bundle/src/application/details.d.ts +24 -0
- package/lib/types/packages/market-bundle/src/application/extension-inventory.d.ts +45 -0
- package/lib/types/packages/market-bundle/src/application/extension-project.d.ts +26 -0
- package/lib/types/packages/market-bundle/src/application/resource-inventory.d.ts +42 -0
- package/lib/types/packages/market-bundle/src/application/server-config.d.ts +9 -0
- package/lib/types/packages/market-bundle/src/application/suite-queries.d.ts +84 -0
- package/lib/types/packages/market-bundle/src/index.d.ts +125 -0
- package/lib/types/{runtime/host → packages/market-bundle/src/platform}/settings-namespace.d.ts +44 -9
- package/lib/types/{application/mcp/mcp-backend.d.ts → packages/market-bundle/src/platform/settings-schema.d.ts} +13 -28
- package/lib/types/packages/market-bundle/src/routes-extension-presets.d.ts +15 -0
- package/lib/types/packages/market-bundle/src/routes-resources.d.ts +17 -0
- package/lib/types/packages/market-bundle/src/routes.d.ts +32 -0
- package/lib/types/packages/market-bundle/src/runtime-adapters.d.ts +42 -0
- package/lib/types/packages/market-bundle/src/session-extension.d.ts +62 -0
- package/lib/types/{application → packages/market-catalog/src/application}/catalog-context.d.ts +11 -3
- package/lib/types/{application → packages/market-catalog/src/application}/install-store.d.ts +1 -1
- package/lib/types/{application → packages/market-catalog/src/application}/regions.d.ts +1 -1
- package/lib/types/{application → packages/market-catalog/src/application}/snapshot-cache.d.ts +44 -2
- package/lib/types/{application → packages/market-catalog/src/application}/source-store.d.ts +12 -3
- package/lib/types/{application → packages/market-catalog/src/application}/state/state-store.d.ts +1 -1
- package/lib/types/packages/market-catalog/src/index.d.ts +22 -0
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/archive.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/codex-mcp.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/component-files.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/lsp-spec.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/manifests.d.ts +3 -3
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/native-project.d.ts +2 -2
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/plugin-variables.d.ts +12 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/project-config.d.ts +2 -2
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/project-hooks.d.ts +2 -2
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/scan-pipeline.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/scan-resolvers.d.ts +3 -3
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/source-catalog.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/suite-components.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/suite-scanner.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/surfaces.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/validate.d.ts +1 -1
- package/lib/types/packages/market-contracts/src/contracts/extension-presets.d.ts +237 -0
- package/lib/types/{contracts → packages/market-contracts/src/contracts}/market.d.ts +87 -16
- package/lib/types/packages/market-contracts/src/contracts/mcp-overrides.d.ts +39 -0
- package/lib/types/{contracts → packages/market-contracts/src/contracts}/mcp-status.d.ts +9 -0
- package/lib/types/packages/market-contracts/src/contracts/resource-window.d.ts +70 -0
- package/lib/types/packages/market-contracts/src/contracts/settings.d.ts +121 -0
- package/lib/types/packages/market-contracts/src/contracts/surface-toggles.d.ts +28 -0
- package/lib/types/packages/market-contracts/src/contracts/translation.d.ts +81 -0
- package/lib/types/{runtime → packages/market-contracts/src}/host/plugin-message-source.d.ts +1 -1
- package/lib/types/packages/market-contracts/src/index.d.ts +5 -0
- package/lib/types/{model → packages/market-contracts/src/model}/layouts.d.ts +7 -0
- package/lib/types/{model → packages/market-contracts/src/model}/types.d.ts +2 -2
- package/lib/types/packages/market-contracts/src/ports/ports.d.ts +183 -0
- package/lib/types/{application → packages/market-contracts/src/ports}/queries.d.ts +27 -6
- package/lib/types/{application/mcp/mcp-redaction.d.ts → packages/market-contracts/src/redaction.d.ts} +2 -0
- package/lib/types/{application → packages/market-lsp/src/application}/lsp/lsp-direct-config.d.ts +1 -1
- package/lib/types/{application → packages/market-lsp/src/application}/lsp/lsp-status.d.ts +21 -5
- package/lib/types/packages/market-lsp/src/application/lsp/validation.d.ts +8 -0
- package/lib/types/{application → packages/market-lsp/src/application}/lsp-service.d.ts +19 -6
- package/lib/types/packages/market-lsp/src/index.d.ts +8 -0
- package/lib/types/{runtime → packages/market-lsp/src/runtime}/lsp/lsp-mounts.d.ts +26 -6
- package/lib/types/packages/market-mcp/src/application/mcp/mcp-backend.d.ts +28 -0
- package/lib/types/{application → packages/market-mcp/src/application}/mcp/mcp-bridge-config.d.ts +1 -1
- package/lib/types/{application → packages/market-mcp/src/application}/mcp/mcp-config.d.ts +2 -2
- package/lib/types/{application → packages/market-mcp/src/application}/mcp/mcp-direct-config.d.ts +6 -1
- package/lib/types/{application → packages/market-mcp/src/application}/mcp/mcp-overrides.d.ts +4 -38
- package/lib/types/packages/market-mcp/src/application/mcp/mcp-status.d.ts +40 -0
- package/lib/types/{application/server-config.d.ts → packages/market-mcp/src/application/mcp/validation.d.ts} +2 -10
- package/lib/types/{application → packages/market-mcp/src/application}/mcp-service.d.ts +22 -16
- package/lib/types/packages/market-mcp/src/index.d.ts +12 -0
- package/lib/types/packages/market-mcp/src/runtime/mcp/bridge/tool-ownership.d.ts +14 -0
- package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-auth-record.d.ts +4 -0
- package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-mounts.d.ts +19 -3
- package/lib/types/packages/market-runtime/src/adapter-contracts.d.ts +89 -0
- package/lib/types/{application → packages/market-runtime/src/application}/agent-roles.d.ts +2 -0
- package/lib/types/packages/market-runtime/src/application/command-resources.d.ts +26 -0
- package/lib/types/packages/market-runtime/src/application/extension-authorization.d.ts +12 -0
- package/lib/types/packages/market-runtime/src/application/extension-hook-selection.d.ts +23 -0
- package/lib/types/packages/market-runtime/src/application/extension-suite-declarations.d.ts +10 -0
- package/lib/types/packages/market-runtime/src/application/extension-suite-selection.d.ts +30 -0
- package/lib/types/packages/market-runtime/src/application/fs-probe.d.ts +12 -0
- package/lib/types/packages/market-runtime/src/application/panel-resources.d.ts +100 -0
- package/lib/types/{application → packages/market-runtime/src/application}/panels/user-hooks.d.ts +1 -1
- package/lib/types/packages/market-runtime/src/application/project-agent-roles.d.ts +10 -0
- package/lib/types/packages/market-runtime/src/application/state/extension-presets.d.ts +24 -0
- package/lib/types/packages/market-runtime/src/application/state/resource-favorites.d.ts +9 -0
- package/lib/types/packages/market-runtime/src/application/state/resource-filters.d.ts +2 -0
- package/lib/types/packages/market-runtime/src/application/state/surface-toggles.d.ts +6 -0
- package/lib/types/packages/market-runtime/src/application/state/workspace-policy.d.ts +19 -0
- package/lib/types/packages/market-runtime/src/catalog-port.d.ts +30 -0
- package/lib/types/packages/market-runtime/src/extension-service.d.ts +14 -0
- package/lib/types/packages/market-runtime/src/index.d.ts +41 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/agents/agent-role-router.d.ts +66 -31
- package/lib/types/packages/market-runtime/src/runtime/agents/agent-teams-seat.d.ts +41 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/agents/subagent-catalog.d.ts +34 -2
- package/lib/types/packages/market-runtime/src/runtime/agents/team-coordination.d.ts +8 -0
- package/lib/types/packages/market-runtime/src/runtime/agents/teammate-role-runtime.d.ts +74 -0
- package/lib/types/packages/market-runtime/src/runtime/agents/teammate-role-tool.d.ts +6 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/reconcile-scheduler.d.ts +5 -2
- package/lib/types/packages/market-runtime/src/runtime/core/reconciler.d.ts +141 -0
- package/lib/types/packages/market-runtime/src/runtime/host/command-name-allocator.d.ts +132 -0
- package/lib/types/packages/market-runtime/src/runtime/host/extension-progress.d.ts +91 -0
- package/lib/types/packages/market-runtime/src/runtime/host/extension-runtime.d.ts +77 -0
- package/lib/types/packages/market-runtime/src/runtime/host/extension-session-state.d.ts +242 -0
- package/lib/types/packages/market-runtime/src/runtime/host/extension-tool-gates.d.ts +38 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/host-locale.d.ts +1 -1
- package/lib/types/packages/market-runtime/src/runtime/host/menu-row-identities.d.ts +50 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/model-catalog.d.ts +1 -1
- package/lib/types/packages/market-runtime/src/runtime/host/resource-filter-service.d.ts +34 -0
- package/lib/types/packages/market-runtime/src/runtime/host/scoped-contributors.d.ts +58 -0
- package/lib/types/packages/market-runtime/src/runtime/host/surface-toggle-service.d.ts +10 -0
- package/lib/types/packages/market-runtime/src/runtime/panels/user-commands.d.ts +74 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/panels/user-panels.d.ts +7 -1
- package/lib/types/packages/market-runtime/src/runtime/surfaces/commands-mounts.d.ts +81 -0
- package/lib/types/packages/market-runtime/src/runtime/surfaces/extension-hooks.d.ts +59 -0
- package/lib/types/packages/market-runtime/src/runtime/surfaces/hook-dry-run.d.ts +91 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/surfaces/hooks-mounts.d.ts +1 -1
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/surfaces/project-runtime.d.ts +8 -5
- package/lib/types/packages/market-runtime/src/runtime/surfaces/skills-provider.d.ts +71 -0
- package/lib/types/packages/market-translation/src/application/state/translation-cache.d.ts +56 -0
- package/lib/types/packages/market-translation/src/application/translation/chain.d.ts +47 -0
- package/lib/types/packages/market-translation/src/application/translation/document.d.ts +54 -0
- package/lib/types/packages/market-translation/src/application/translation/localizer.d.ts +325 -0
- package/lib/types/packages/market-translation/src/application/translation/service.d.ts +89 -0
- package/lib/types/packages/market-translation/src/application/translation/unit.d.ts +35 -0
- package/lib/types/packages/market-translation/src/index.d.ts +4 -0
- package/lib/types/packages/market-translation/src/runtime/host/llm-translator.d.ts +30 -0
- package/lib/types/packages/market-translation/src/runtime/host/machine-translator.d.ts +52 -0
- package/lib/types/packages/market-translation/src/runtime/host/text-masking.d.ts +42 -0
- package/lib/types/packages/market-translation/src/runtime/host/translation-providers.d.ts +42 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +47 -10
- package/schemas/README.md +4 -4
- package/schemas/agent-plugins/spec.md +2 -2
- package/schemas/com.deepseek.harness/spec.md +1 -1
- package/schemas/skill-collection/spec.md +10 -2
- package/lib/application/panel-resources.js +0 -161
- package/lib/application/server-config.js +0 -109
- package/lib/contracts/settings.js +0 -63
- package/lib/runtime/core/reconciler.js +0 -100
- package/lib/runtime/panels/user-commands.js +0 -105
- package/lib/runtime/surfaces/commands-mounts.js +0 -186
- package/lib/runtime/surfaces/skills-provider.js +0 -160
- package/lib/types/application/details.d.ts +0 -7
- package/lib/types/application/mcp/mcp-status.d.ts +0 -24
- package/lib/types/application/panel-resources.d.ts +0 -28
- package/lib/types/application/ports.d.ts +0 -86
- package/lib/types/application/project-agent-roles.d.ts +0 -3
- package/lib/types/contracts/settings.d.ts +0 -59
- package/lib/types/routes.d.ts +0 -16
- package/lib/types/runtime/core/reconciler.d.ts +0 -58
- package/lib/types/runtime/panels/user-commands.d.ts +0 -27
- package/lib/types/runtime/surfaces/commands-mounts.d.ts +0 -48
- package/lib/types/runtime/surfaces/skills-provider.d.ts +0 -23
- /package/lib/{application → packages/market-catalog/src/application}/deadline.js +0 -0
- /package/lib/{application → packages/market-catalog/src/application}/json-file.js +0 -0
- /package/lib/{application → packages/market-catalog/src/application}/regions.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/fs-probes.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/git.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/lsp-spec.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/project-config.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/project-hooks.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/scan-pipeline.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/source-catalog.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/suite-components.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/suite-scanner.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/surfaces.js +0 -0
- /package/lib/{contracts → packages/market-contracts/src/contracts}/lsp-status.js +0 -0
- /package/lib/{contracts → packages/market-contracts/src/contracts}/lsp.js +0 -0
- /package/lib/{contracts → packages/market-contracts/src/contracts}/mcp-status.js +0 -0
- /package/lib/{contracts → packages/market-contracts/src/contracts}/mcp.js +0 -0
- /package/lib/{runtime → packages/market-contracts/src}/host/plugin-message-source.js +0 -0
- /package/lib/{model → packages/market-contracts/src/model}/command-names.js +0 -0
- /package/lib/{model → packages/market-contracts/src/model}/preset-source.js +0 -0
- /package/lib/{model → packages/market-contracts/src/model}/types.js +0 -0
- /package/lib/{application → packages/market-contracts/src/ports}/queries.js +0 -0
- /package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-bridge-config.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/host-contract.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/host-seams.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/plugin-identity.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/projection.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/transport.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-credentials.js +0 -0
- /package/lib/{application → packages/market-runtime/src/application}/agent-roles.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/agents/agent-role-names.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/core/context.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/core/mount-lifecycle.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/core/source-auto-update.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/core/timer-seat.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/host/model-catalog.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/host/shell-path.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/host/tool-registry-observer.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/surfaces/dynamic-context.js +0 -0
- /package/lib/types/{runtime/host → packages/market-bundle/src/platform}/feedback-tool.d.ts +0 -0
- /package/lib/types/{application → packages/market-catalog/src/application}/deadline.d.ts +0 -0
- /package/lib/types/{application → packages/market-catalog/src/application}/json-file.d.ts +0 -0
- /package/lib/types/{application → packages/market-catalog/src/application}/state/storage-migration.d.ts +0 -0
- /package/lib/types/{catalog → packages/market-catalog/src/scanning}/fs-probes.d.ts +0 -0
- /package/lib/types/{catalog → packages/market-catalog/src/scanning}/git.d.ts +0 -0
- /package/lib/types/{catalog → packages/market-catalog/src/scanning}/paths.d.ts +0 -0
- /package/lib/types/{catalog → packages/market-catalog/src/scanning}/skills-parse.d.ts +0 -0
- /package/lib/types/{contracts → packages/market-contracts/src/contracts}/lsp-status.d.ts +0 -0
- /package/lib/types/{contracts → packages/market-contracts/src/contracts}/lsp.d.ts +0 -0
- /package/lib/types/{contracts → packages/market-contracts/src/contracts}/mcp.d.ts +0 -0
- /package/lib/types/{model → packages/market-contracts/src/model}/command-names.d.ts +0 -0
- /package/lib/types/{model → packages/market-contracts/src/model}/preset-source.d.ts +0 -0
- /package/lib/types/{application → packages/market-lsp/src/application}/lsp/lsp-server-state.d.ts +0 -0
- /package/lib/types/{application → packages/market-lsp/src/application}/lsp/profile-seam.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/bridge.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/connection.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/host-contract.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/host-seams.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/oauth.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/plugin-identity.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/projection.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/tools.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/transport.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-credentials.d.ts +0 -0
- /package/lib/types/{application → packages/market-runtime/src/application}/panels/user-store.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/agents/agent-role-names.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/context.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/mount-lifecycle.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/source-auto-update.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/timer-seat.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/failure-detail.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/shell-path.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/tool-registry-observer.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/surfaces/dynamic-context.d.ts +0 -0
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import '@deepseek-ai/dsh-session-query';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { parseExtensionIds } from '../../../../market-contracts/src/contracts/extension-presets.js';
|
|
5
|
+
export const EXTENSION_SESSION_SOURCE = 'market-extension-selection';
|
|
6
|
+
const selectionSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
presetId: z.string().min(1).nullable(),
|
|
9
|
+
presetName: z.string().min(1).max(80).nullable(),
|
|
10
|
+
presetRevision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable(),
|
|
11
|
+
modified: z.boolean(),
|
|
12
|
+
enabledIds: z.array(z.string()).transform(ids => parseExtensionIds(ids))
|
|
13
|
+
})
|
|
14
|
+
.strict()
|
|
15
|
+
.refine(value => (value.presetId === null) === (value.presetName === null) && (value.presetId === null) === (value.presetRevision === null));
|
|
16
|
+
const bindingSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
version: z.literal(1),
|
|
19
|
+
ownerId: z.string().min(1),
|
|
20
|
+
revision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
21
|
+
phase: z.enum(['pending', 'committed']),
|
|
22
|
+
requestRevision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
23
|
+
selection: selectionSchema,
|
|
24
|
+
recoveryOf: z
|
|
25
|
+
.object({ ownerId: z.string().min(1), revision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER) })
|
|
26
|
+
.strict()
|
|
27
|
+
.optional()
|
|
28
|
+
})
|
|
29
|
+
.strict();
|
|
30
|
+
/** The metadata marker that wakes a driver after a recovery; it never enters a request. */
|
|
31
|
+
export const EXTENSION_SESSION_WAKE_SOURCE = 'market-extension-wake';
|
|
32
|
+
/** One durable request that becomes effective only at a safe boundary. */
|
|
33
|
+
const intentSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
ownerId: z.string().min(1),
|
|
36
|
+
/** The public selection CAS token, independent of the binding journal. */
|
|
37
|
+
revision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
38
|
+
selection: selectionSchema
|
|
39
|
+
})
|
|
40
|
+
.strict();
|
|
41
|
+
export const EXTENSION_INTENT_SOURCE = 'market-extension-intent';
|
|
42
|
+
/** Whether one message kind is this service's own metadata rather than user input. */
|
|
43
|
+
function isPluginMetadata(kind) {
|
|
44
|
+
return kind === EXTENSION_SESSION_SOURCE || kind === EXTENSION_SESSION_WAKE_SOURCE || kind === EXTENSION_INTENT_SOURCE;
|
|
45
|
+
}
|
|
46
|
+
function failure(code) {
|
|
47
|
+
return Object.assign(new Error(code), { code });
|
|
48
|
+
}
|
|
49
|
+
/** Durable selections use known message envelopes; pending transactions reject replay after an uncertain failure. */
|
|
50
|
+
export class ExtensionSessionState {
|
|
51
|
+
ctx;
|
|
52
|
+
ports;
|
|
53
|
+
states = new Map();
|
|
54
|
+
initializing = new Map();
|
|
55
|
+
checkpoints = new Map();
|
|
56
|
+
errors = new Map();
|
|
57
|
+
lifetime = new AbortController();
|
|
58
|
+
unavailable = new Set();
|
|
59
|
+
/** The capture classification each attempt used, so an explicit recovery replays it exactly. */
|
|
60
|
+
captureSources = new Map();
|
|
61
|
+
/** Durable requests waiting for a safe boundary; the last one received wins. */
|
|
62
|
+
intents = new Map();
|
|
63
|
+
/** The turn each pending request was made in: a boundary only exists in a later turn. */
|
|
64
|
+
intentTurns = new Map();
|
|
65
|
+
/** Per-agent order for requests and promotions, so no two of them interleave. */
|
|
66
|
+
requests = new Map();
|
|
67
|
+
/** The turn each agent last entered, so a request can record the turn it came from. */
|
|
68
|
+
currentTurns = new Map();
|
|
69
|
+
/** Single-flight promotion per agent: one turn boundary promotes once. */
|
|
70
|
+
promoting = new Map();
|
|
71
|
+
operations = new Set();
|
|
72
|
+
off = [];
|
|
73
|
+
disposed = false;
|
|
74
|
+
disposal;
|
|
75
|
+
constructor(ctx, ports) {
|
|
76
|
+
this.ctx = ctx;
|
|
77
|
+
this.ports = ports;
|
|
78
|
+
this.off.push(ctx.on('agent/created', async ({ agent, source }) => {
|
|
79
|
+
// An ungoverned agent is never tracked, so it can neither be cancelled
|
|
80
|
+
// nor inherit a selection this service had no mandate to capture.
|
|
81
|
+
if (!this.governs(agent))
|
|
82
|
+
return;
|
|
83
|
+
await this.initialize(agent, source).catch(() => { });
|
|
84
|
+
}));
|
|
85
|
+
this.off.push(ctx.on('agent/pre-step', async (event, next) => {
|
|
86
|
+
if (!this.governs(event.agent))
|
|
87
|
+
return next();
|
|
88
|
+
this.currentTurns.set(event.agent, event.turn);
|
|
89
|
+
if (this.intents.get(event.agent) !== undefined && this.intentTurns.get(event.agent) === undefined) {
|
|
90
|
+
// The request arrived before any turn was observed for it, so this turn is
|
|
91
|
+
// the one it came from: a boundary exists only in a later turn. Seeding here
|
|
92
|
+
// keeps that from costing one blocked turn at this turn's own first step.
|
|
93
|
+
this.intentTurns.set(event.agent, event.turn);
|
|
94
|
+
}
|
|
95
|
+
// A request waiting for its boundary must not run under the superseded
|
|
96
|
+
// authorization. Only the first step of a turn that started after the
|
|
97
|
+
// request is that request's step: park it whole, promote from idle, and let
|
|
98
|
+
// the filtered wake hand it back, so no model step is spent on the old
|
|
99
|
+
// selection and no stale tool assembly is ever used.
|
|
100
|
+
//
|
|
101
|
+
// A later step of the same turn is mid-turn work. The host claims every
|
|
102
|
+
// next-step message at every step, so a steer belongs here, and rejecting it
|
|
103
|
+
// would block the running turn. Such a step keeps the committed selection.
|
|
104
|
+
const intentTurn = this.intentTurns.get(event.agent);
|
|
105
|
+
const newTurnStep = event.step === 1 && (intentTurn === undefined || event.turn > intentTurn);
|
|
106
|
+
if (this.intents.get(event.agent) !== undefined && newTurnStep && event.messages.some(message => message.source.kind === 'user')) {
|
|
107
|
+
const { agent } = event;
|
|
108
|
+
const strandedIds = new Set([...agent.inbox.nextStep, ...agent.inbox.nextTurn].map(message => message.id));
|
|
109
|
+
let identityHeld = false;
|
|
110
|
+
for (const message of [...event.messages].reverse()) {
|
|
111
|
+
if (strandedIds.has(message.id)) {
|
|
112
|
+
identityHeld = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
agent.inbox.prepend('next-step', message);
|
|
116
|
+
strandedIds.add(message.id);
|
|
117
|
+
}
|
|
118
|
+
this.promoteAfterIdle(agent);
|
|
119
|
+
// An empty enter at the turn's first step completes that turn with no step
|
|
120
|
+
// and no model call, while reject would label it blocked in the durable log.
|
|
121
|
+
// The claim already removed these messages, so the prepend above is what
|
|
122
|
+
// keeps the request intact for the wake to re-run. A claimed identity that is
|
|
123
|
+
// somehow still pending is a corruption signal, and then reject fails closed.
|
|
124
|
+
if (identityHeld)
|
|
125
|
+
return Promise.resolve({ kind: 'reject' });
|
|
126
|
+
return Promise.resolve({ kind: 'enter', messages: [] });
|
|
127
|
+
}
|
|
128
|
+
if (this.status(event.agent).ready) {
|
|
129
|
+
// The envelope's durable record is its inbox splice; the claimed copy is
|
|
130
|
+
// filtered out before it can enter a request as placeholder user text.
|
|
131
|
+
const decision = await next();
|
|
132
|
+
if (decision.kind !== 'enter')
|
|
133
|
+
return decision;
|
|
134
|
+
// The durable envelope and the recovery wake marker are plugin metadata:
|
|
135
|
+
// neither may enter a request as placeholder user text.
|
|
136
|
+
return { ...decision, messages: decision.messages.filter(message => !isPluginMetadata(message.source.kind)) };
|
|
137
|
+
}
|
|
138
|
+
// The host claims input before this hook; reject alone would discard it.
|
|
139
|
+
const { agent } = event;
|
|
140
|
+
const pendingIds = new Set([...agent.inbox.nextStep, ...agent.inbox.nextTurn].map(message => message.id));
|
|
141
|
+
for (const message of [...event.messages].reverse()) {
|
|
142
|
+
if (pendingIds.has(message.id))
|
|
143
|
+
continue;
|
|
144
|
+
agent.inbox.prepend('next-step', message);
|
|
145
|
+
pendingIds.add(message.id);
|
|
146
|
+
}
|
|
147
|
+
return Promise.resolve({ kind: 'reject' });
|
|
148
|
+
}));
|
|
149
|
+
this.off.push(
|
|
150
|
+
// A clean stop promotes the pending request after the last step of the turn
|
|
151
|
+
// and before the next turn claims input, which is the only boundary where the
|
|
152
|
+
// next request's tool assembly and the gates agree.
|
|
153
|
+
ctx.on('agent/turn-stopping', async ({ agent, signal }) => {
|
|
154
|
+
if (this.disposed || !this.governs(agent) || this.intents.get(agent) === undefined)
|
|
155
|
+
return;
|
|
156
|
+
signal.throwIfAborted();
|
|
157
|
+
await this.promote(agent).catch(() => { });
|
|
158
|
+
}));
|
|
159
|
+
this.off.push(
|
|
160
|
+
// A turn that ended without stopping (cancelled, aborted, failed) never
|
|
161
|
+
// dispatches turn-stopping, so the idle transition promotes instead.
|
|
162
|
+
ctx.on('agent/status', ({ agent, status }) => {
|
|
163
|
+
if (status !== 'idle' || this.disposed || !this.governs(agent))
|
|
164
|
+
return;
|
|
165
|
+
if (this.intents.get(agent) === undefined)
|
|
166
|
+
return;
|
|
167
|
+
this.promoteAfterIdle(agent);
|
|
168
|
+
}));
|
|
169
|
+
this.off.push(ctx.on('agent/disposed', ({ agent }) => {
|
|
170
|
+
this.states.delete(agent);
|
|
171
|
+
this.checkpoints.delete(agent);
|
|
172
|
+
this.errors.delete(agent);
|
|
173
|
+
this.unavailable.delete(agent);
|
|
174
|
+
this.initializing.delete(agent);
|
|
175
|
+
this.captureSources.delete(agent);
|
|
176
|
+
this.intents.delete(agent);
|
|
177
|
+
this.promoting.delete(agent);
|
|
178
|
+
this.intentTurns.delete(agent);
|
|
179
|
+
this.currentTurns.delete(agent);
|
|
180
|
+
this.requests.delete(agent);
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
/** Whether this service governs one agent; an undecidable answer stays governed. */
|
|
184
|
+
governs(agent) {
|
|
185
|
+
if (this.ports.eligible === undefined)
|
|
186
|
+
return true;
|
|
187
|
+
try {
|
|
188
|
+
return this.ports.eligible(agent);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** Attach idle agents independently; busy agents remain denied until their current activity ends. */
|
|
195
|
+
async start() {
|
|
196
|
+
await Promise.all(this.ctx.agents
|
|
197
|
+
.list()
|
|
198
|
+
.filter(agent => this.governs(agent))
|
|
199
|
+
.map(agent => this.attach(agent)));
|
|
200
|
+
}
|
|
201
|
+
async attach(agent) {
|
|
202
|
+
if (!this.governs(agent))
|
|
203
|
+
return;
|
|
204
|
+
this.unavailable.add(agent);
|
|
205
|
+
try {
|
|
206
|
+
await this.maintenance(agent, signal => this.initialize(agent, 'legacy', signal));
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (!(error instanceof Error) || !('code' in error) || error.code !== 'extension-session-busy')
|
|
210
|
+
return;
|
|
211
|
+
this.errors.set(agent, 'extension-session-busy');
|
|
212
|
+
const signal = this.lifetime.signal;
|
|
213
|
+
let stop = () => { };
|
|
214
|
+
const disposed = new Promise(resolve => {
|
|
215
|
+
const onAbort = () => resolve();
|
|
216
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
217
|
+
stop = () => signal.removeEventListener('abort', onAbort);
|
|
218
|
+
});
|
|
219
|
+
const retry = Promise.race([agent.whenIdle(), disposed]).then(async () => {
|
|
220
|
+
stop();
|
|
221
|
+
if (this.disposed || this.ctx.agents.get(agent.id) !== agent)
|
|
222
|
+
return;
|
|
223
|
+
this.errors.delete(agent);
|
|
224
|
+
await this.attach(agent);
|
|
225
|
+
// A failed retry leaves the session unavailable, and wake() refuses that
|
|
226
|
+
// state itself; this call only offers the retry's outcome to that guard.
|
|
227
|
+
this.wake(agent);
|
|
228
|
+
});
|
|
229
|
+
// initialize() already recorded this failure; the handler only detaches the rejection.
|
|
230
|
+
void this.track(retry).catch(() => { });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** Return diagnostics without exposing an unavailable snapshot as effective state. */
|
|
234
|
+
status(agent) {
|
|
235
|
+
// An ungoverned agent reports why it has no session state at all, so a route
|
|
236
|
+
// or panel can name the reason instead of reading a permanent "not ready".
|
|
237
|
+
if (!this.governs(agent))
|
|
238
|
+
return { ready: false, revision: 0, recoverable: false, error: 'extension-session-ineligible' };
|
|
239
|
+
const ready = !this.disposed && !this.unavailable.has(agent) && this.states.has(agent);
|
|
240
|
+
const checkpoint = this.checkpoints.get(agent);
|
|
241
|
+
const error = this.errors.get(agent);
|
|
242
|
+
const selection = ready ? this.states.get(agent)?.selection : (checkpoint?.committed?.selection ?? checkpoint?.initial);
|
|
243
|
+
// A capture that failed before recording anything leaves no recovery target.
|
|
244
|
+
// That is the one case where an explicit recovery re-runs the capture itself;
|
|
245
|
+
// every other failure keeps the durable-record rule.
|
|
246
|
+
// Only an attempt that actually ran can be retried: a busy attach records an
|
|
247
|
+
// error without ever reaching the capture, and its own retry owns that state.
|
|
248
|
+
const captureOnly = selection === undefined && error !== undefined && this.captureSources.has(agent);
|
|
249
|
+
return {
|
|
250
|
+
ready,
|
|
251
|
+
revision: checkpoint?.latest?.revision ?? 0,
|
|
252
|
+
recoverable: !ready && !this.disposed && !this.initializing.has(agent) && (selection !== undefined || captureOnly) && error !== 'extension-session-corrupt',
|
|
253
|
+
...(error ? { error } : {}),
|
|
254
|
+
...(selection ? { selection: structuredClone(selection) } : {})
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
read(agent) {
|
|
258
|
+
if (this.disposed || this.unavailable.has(agent))
|
|
259
|
+
throw failure('extension-session-not-ready');
|
|
260
|
+
// An ungoverned agent never receives a selection, so every gate reading it
|
|
261
|
+
// denies managed resources instead of falling back to an implicit allow.
|
|
262
|
+
if (!this.governs(agent))
|
|
263
|
+
return undefined;
|
|
264
|
+
const state = this.states.get(agent);
|
|
265
|
+
return state && structuredClone(state);
|
|
266
|
+
}
|
|
267
|
+
initialize(agent, source, signal) {
|
|
268
|
+
if (this.disposed)
|
|
269
|
+
return Promise.reject(failure('extension-session-not-ready'));
|
|
270
|
+
if (!this.governs(agent))
|
|
271
|
+
return Promise.reject(failure('extension-session-ineligible'));
|
|
272
|
+
const pending = this.initializing.get(agent);
|
|
273
|
+
if (pending)
|
|
274
|
+
return pending;
|
|
275
|
+
if (this.states.has(agent) && !this.unavailable.has(agent))
|
|
276
|
+
return Promise.resolve();
|
|
277
|
+
if (this.errors.has(agent))
|
|
278
|
+
return Promise.reject(failure(this.errors.get(agent)));
|
|
279
|
+
this.unavailable.add(agent);
|
|
280
|
+
const operation = this.initializeOnce(agent, source, signal ?? this.lifetime.signal)
|
|
281
|
+
.catch(error => {
|
|
282
|
+
this.fail(agent, error);
|
|
283
|
+
throw error;
|
|
284
|
+
})
|
|
285
|
+
.finally(() => this.initializing.delete(agent));
|
|
286
|
+
this.initializing.set(agent, operation);
|
|
287
|
+
return operation;
|
|
288
|
+
}
|
|
289
|
+
async replay(agent) {
|
|
290
|
+
const observation = await this.ctx.sessionQuery.observeSession(agent.id, { projectionMode: 'none' });
|
|
291
|
+
const checkpoint = {};
|
|
292
|
+
const seen = new Map();
|
|
293
|
+
try {
|
|
294
|
+
for (const event of observation.events) {
|
|
295
|
+
const messages = event.type === 'user/message' ? [event.data] : event.type === 'agent/inbox/spliced' ? event.data.inserted : [];
|
|
296
|
+
for (const message of messages) {
|
|
297
|
+
if (message.source.kind === EXTENSION_INTENT_SOURCE) {
|
|
298
|
+
let intent;
|
|
299
|
+
try {
|
|
300
|
+
intent = intentSchema.parse(message.source.intent);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
throw failure('extension-session-corrupt');
|
|
304
|
+
}
|
|
305
|
+
const serializedIntent = JSON.stringify(intent);
|
|
306
|
+
const previousIntent = seen.get(message.id);
|
|
307
|
+
if (previousIntent !== undefined) {
|
|
308
|
+
if (previousIntent !== serializedIntent)
|
|
309
|
+
throw failure('extension-session-corrupt');
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
seen.set(message.id, serializedIntent);
|
|
313
|
+
if (intent.ownerId !== agent.id) {
|
|
314
|
+
// An inherited request belongs to the session that made it: a fork keeps
|
|
315
|
+
// the committed selection and drops the request.
|
|
316
|
+
if (event.seq >= observation.inheritedEventCount)
|
|
317
|
+
throw failure('extension-session-corrupt');
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
// The log is read in order, so the last request written is the one that wins.
|
|
321
|
+
checkpoint.intent = intent;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (message.source.kind !== EXTENSION_SESSION_SOURCE)
|
|
325
|
+
continue;
|
|
326
|
+
let binding;
|
|
327
|
+
try {
|
|
328
|
+
binding = bindingSchema.parse(message.source.binding);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
throw failure('extension-session-corrupt');
|
|
332
|
+
}
|
|
333
|
+
const serialized = JSON.stringify(binding);
|
|
334
|
+
const previous = seen.get(message.id);
|
|
335
|
+
if (previous !== undefined) {
|
|
336
|
+
// A repeated identity is one envelope seen twice: the durable splice that
|
|
337
|
+
// carried it, then the step that claimed it, and after a fork the copy
|
|
338
|
+
// carries the owner that wrote it rather than this agent. Only the first
|
|
339
|
+
// occurrence can testify about live ownership.
|
|
340
|
+
if (previous !== serialized)
|
|
341
|
+
throw failure('extension-session-corrupt');
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (event.seq >= observation.inheritedEventCount && binding.ownerId !== agent.id)
|
|
345
|
+
throw failure('extension-session-corrupt');
|
|
346
|
+
seen.set(message.id, serialized);
|
|
347
|
+
const { latest, committed, initial } = checkpoint;
|
|
348
|
+
if (binding.phase === 'pending') {
|
|
349
|
+
const nextRevision = latest?.ownerId === binding.ownerId ? latest.revision + 1 : 1;
|
|
350
|
+
if (binding.revision !== nextRevision)
|
|
351
|
+
throw failure('extension-session-corrupt');
|
|
352
|
+
if (binding.recoveryOf) {
|
|
353
|
+
if (!latest ||
|
|
354
|
+
binding.recoveryOf.ownerId !== latest.ownerId ||
|
|
355
|
+
binding.recoveryOf.revision !== latest.revision ||
|
|
356
|
+
JSON.stringify(binding.selection) !== JSON.stringify(committed?.selection ?? initial))
|
|
357
|
+
throw failure('extension-session-corrupt');
|
|
358
|
+
}
|
|
359
|
+
else if (latest?.phase === 'pending')
|
|
360
|
+
throw failure('extension-session-corrupt');
|
|
361
|
+
checkpoint.initial ??= binding.selection;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
if (!latest || latest.phase !== 'pending' || JSON.stringify({ ...latest, phase: 'committed' }) !== serialized)
|
|
365
|
+
throw failure('extension-session-corrupt');
|
|
366
|
+
checkpoint.committed = binding;
|
|
367
|
+
// A request written before the newest commit was superseded by it.
|
|
368
|
+
checkpoint.intent = undefined;
|
|
369
|
+
}
|
|
370
|
+
checkpoint.latest = binding;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return { checkpoint, nonempty: observation.events.some(event => event.type === 'user/message' || event.type === 'agent/inbox/spliced') };
|
|
374
|
+
}
|
|
375
|
+
finally {
|
|
376
|
+
observation[Symbol.dispose]();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/** Run one capture through the adapter and commit it as revision 1. */
|
|
380
|
+
async capture(agent, source, signal) {
|
|
381
|
+
const selection = selectionSchema.parse(await this.ports.initialSelection(agent, source));
|
|
382
|
+
const checkpoint = this.checkpoints.get(agent);
|
|
383
|
+
checkpoint.initial ??= selection;
|
|
384
|
+
await this.commit(agent, { revision: 1, selection }, signal);
|
|
385
|
+
}
|
|
386
|
+
async initializeOnce(agent, source, signal) {
|
|
387
|
+
const { checkpoint, nonempty } = await this.replay(agent);
|
|
388
|
+
this.checkpoints.set(agent, checkpoint);
|
|
389
|
+
const { latest } = checkpoint;
|
|
390
|
+
if (latest?.phase === 'pending')
|
|
391
|
+
throw failure('extension-session-incomplete');
|
|
392
|
+
if (checkpoint.intent !== undefined)
|
|
393
|
+
this.intents.set(agent, checkpoint.intent);
|
|
394
|
+
if (latest?.ownerId === agent.id) {
|
|
395
|
+
// Re-publishing a replayed committed selection is a transaction too: the
|
|
396
|
+
// adapter may hand back a receipt, and an interruption before the state is
|
|
397
|
+
// installed must undo whatever it applied.
|
|
398
|
+
let receipt;
|
|
399
|
+
let durable = false;
|
|
400
|
+
try {
|
|
401
|
+
signal?.throwIfAborted();
|
|
402
|
+
receipt = (await this.ports.applySelection(agent, structuredClone(latest.selection))) ?? undefined;
|
|
403
|
+
signal?.throwIfAborted();
|
|
404
|
+
// Same order as the ordinary commit path: durability, then publish the effect,
|
|
405
|
+
// then this service's own state, then the ready callback.
|
|
406
|
+
durable = true;
|
|
407
|
+
receipt?.commit();
|
|
408
|
+
this.states.set(agent, { revision: latest.revision, selection: latest.selection });
|
|
409
|
+
this.unavailable.delete(agent);
|
|
410
|
+
this.errors.delete(agent);
|
|
411
|
+
this.ports.committed?.(agent);
|
|
412
|
+
if (this.intents.has(agent))
|
|
413
|
+
this.promoteAfterIdle(agent);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
// Fail closed before unwinding: the restore path may not have marked the
|
|
417
|
+
// agent unavailable yet, and gates must deny while the undo is in flight.
|
|
418
|
+
this.fail(agent, error);
|
|
419
|
+
await this.rollbackApplied(receipt, durable);
|
|
420
|
+
throw error;
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (latest?.selection !== undefined) {
|
|
425
|
+
const replayed = selectionSchema.parse(latest.selection);
|
|
426
|
+
// Re-applying keeps the published revision, so a client that cached it can
|
|
427
|
+
// still compare against it after an attach.
|
|
428
|
+
checkpoint.initial ??= replayed;
|
|
429
|
+
await this.commit(agent, { revision: 1, selection: replayed }, signal);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
// Remember which classification this attempt used: input parked while the
|
|
433
|
+
// attempt runs makes the log nonempty, and a later explicit recovery must not
|
|
434
|
+
// read that as pre-existing history and capture legacy defaults instead.
|
|
435
|
+
const legacy = nonempty || source === 'resume' || source === 'legacy';
|
|
436
|
+
const captureSource = legacy ? 'legacy' : source;
|
|
437
|
+
this.captureSources.set(agent, captureSource);
|
|
438
|
+
await this.capture(agent, captureSource, signal);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Recover only the validated last committed choice, or the original initial attempt.
|
|
442
|
+
*
|
|
443
|
+
* Never recapture defaults, with one exception: a capture that failed before it
|
|
444
|
+
* recorded anything has no record to protect, so an explicit recovery re-runs that
|
|
445
|
+
* capture with the classification the original attempt used.
|
|
446
|
+
*/
|
|
447
|
+
recover(agent, expectedRevision) {
|
|
448
|
+
return this.maintenance(agent, async (signal) => {
|
|
449
|
+
const status = this.status(agent);
|
|
450
|
+
if (status.ready || status.revision !== expectedRevision)
|
|
451
|
+
throw failure('extension-session-conflict');
|
|
452
|
+
if (!status.recoverable)
|
|
453
|
+
throw failure(status.error ?? 'extension-session-not-ready');
|
|
454
|
+
try {
|
|
455
|
+
const { checkpoint } = await this.replay(agent);
|
|
456
|
+
const latest = checkpoint.latest;
|
|
457
|
+
if ((latest?.revision ?? 0) !== expectedRevision)
|
|
458
|
+
throw failure('extension-session-conflict');
|
|
459
|
+
const selection = checkpoint.committed?.selection ?? checkpoint.initial ?? this.checkpoints.get(agent)?.initial;
|
|
460
|
+
if (!selection) {
|
|
461
|
+
// Nothing durable records the failed attempt, so retrying the capture
|
|
462
|
+
// itself loses nothing. Only an explicit recovery reaches this branch, a
|
|
463
|
+
// repeated failure records again, and the next attempt waits for the next
|
|
464
|
+
// call — this service never schedules a retry of its own.
|
|
465
|
+
if (latest !== undefined || checkpoint.committed !== undefined || checkpoint.initial !== undefined)
|
|
466
|
+
throw failure('extension-session-not-ready');
|
|
467
|
+
// Publish the replayed checkpoint before the capture records into it: the
|
|
468
|
+
// capture path amends the entry this map holds for the agent.
|
|
469
|
+
this.checkpoints.set(agent, checkpoint);
|
|
470
|
+
await this.capture(agent, this.captureSources.get(agent), signal);
|
|
471
|
+
const captured = this.states.get(agent);
|
|
472
|
+
if (captured === undefined)
|
|
473
|
+
throw failure('extension-session-not-ready');
|
|
474
|
+
this.wake(agent);
|
|
475
|
+
return structuredClone(captured);
|
|
476
|
+
}
|
|
477
|
+
this.checkpoints.set(agent, checkpoint);
|
|
478
|
+
const revision = latest?.ownerId === agent.id ? latest.revision + 1 : 1;
|
|
479
|
+
if (!Number.isSafeInteger(revision))
|
|
480
|
+
throw failure('extension-session-conflict');
|
|
481
|
+
const next = { revision, selection: structuredClone(selection) };
|
|
482
|
+
signal.throwIfAborted();
|
|
483
|
+
await this.commit(agent, next, signal, latest && { ownerId: latest.ownerId, revision: latest.revision });
|
|
484
|
+
this.wake(agent);
|
|
485
|
+
return structuredClone(next);
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
this.fail(agent, error);
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
async change(agent, expectedRevision, selection, requestRevision) {
|
|
494
|
+
const captured = selectionSchema.parse(selection);
|
|
495
|
+
return this.maintenance(agent, async (signal) => {
|
|
496
|
+
await this.initialize(agent, undefined, signal);
|
|
497
|
+
const current = this.read(agent);
|
|
498
|
+
if (!current || current.revision !== expectedRevision)
|
|
499
|
+
throw failure('extension-session-conflict');
|
|
500
|
+
const next = { revision: current.revision + 1, selection: captured };
|
|
501
|
+
if (!Number.isSafeInteger(next.revision))
|
|
502
|
+
throw failure('extension-session-conflict');
|
|
503
|
+
signal.throwIfAborted();
|
|
504
|
+
this.unavailable.add(agent);
|
|
505
|
+
await this.commit(agent, next, signal, undefined, requestRevision);
|
|
506
|
+
return structuredClone(next);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Accept one selection request.
|
|
511
|
+
*
|
|
512
|
+
* An idle agent commits immediately through the ordinary transaction. While a
|
|
513
|
+
* turn owns the agent the request is only written durably: the effective
|
|
514
|
+
* selection, every gate and every mount keep reading the committed snapshot
|
|
515
|
+
* until a safe boundary promotes it, so nothing is staged or swapped while the
|
|
516
|
+
* current turn's tools are live.
|
|
517
|
+
* @param agent - the agent the selection belongs to.
|
|
518
|
+
* @param expectedRevision - the revision the caller believes is current: the committed revision or the pending request's revision.
|
|
519
|
+
* @param selection - the requested selection.
|
|
520
|
+
* @returns whether it applied now, and the revision a caller compares against next.
|
|
521
|
+
*/
|
|
522
|
+
async requestSelection(agent, expectedRevision, selection) {
|
|
523
|
+
const captured = selectionSchema.parse(selection);
|
|
524
|
+
// Requests and promotions run one at a time per agent, so two callers cannot
|
|
525
|
+
// both read the same base and lose one another's write. The tracked promise
|
|
526
|
+
// keeps disposal waiting for a request that is still writing.
|
|
527
|
+
return this.track(this.serialize(agent, async () => {
|
|
528
|
+
if (this.disposed)
|
|
529
|
+
throw failure('extension-session-not-ready');
|
|
530
|
+
// A session that is not ready authorizes nothing, even when an older snapshot
|
|
531
|
+
// still sits in memory: accepting a change there would write against a
|
|
532
|
+
// selection the gates refuse to use.
|
|
533
|
+
if (!this.status(agent).ready)
|
|
534
|
+
throw failure('extension-session-not-ready');
|
|
535
|
+
const committed = this.states.get(agent).revision;
|
|
536
|
+
// Latest request wins. Once a request is pending only its own revision
|
|
537
|
+
// compares, so a caller that never saw it cannot overwrite it, and each
|
|
538
|
+
// request carries a revision of its own rather than repeating one.
|
|
539
|
+
if (expectedRevision !== this.selectionRevision(agent))
|
|
540
|
+
throw failure('extension-session-conflict');
|
|
541
|
+
const revision = this.selectionRevision(agent) + 1;
|
|
542
|
+
if (!Number.isSafeInteger(revision))
|
|
543
|
+
throw failure('extension-session-conflict');
|
|
544
|
+
if (this.isIdle(agent)) {
|
|
545
|
+
// Nothing is in flight, so this request is the selection: a pending request
|
|
546
|
+
// it supersedes is dropped instead of being promoted over it later.
|
|
547
|
+
try {
|
|
548
|
+
await this.change(agent, committed, captured, revision);
|
|
549
|
+
this.intents.delete(agent);
|
|
550
|
+
this.intentTurns.delete(agent);
|
|
551
|
+
return { applied: true, revision };
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
if (!(error instanceof Error) || !('code' in error) || error.code !== 'extension-session-busy')
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const intent = { ownerId: agent.id, revision, selection: captured };
|
|
559
|
+
await this.persistIntent(agent, intent);
|
|
560
|
+
this.lifetime.signal.throwIfAborted();
|
|
561
|
+
if (this.ctx.agents.get(agent.id) !== agent)
|
|
562
|
+
throw failure('extension-session-not-ready');
|
|
563
|
+
this.intents.set(agent, intent);
|
|
564
|
+
// The turn is not part of the durable record: turn numbering is per attached
|
|
565
|
+
// lifecycle, so a replayed request must not answer to a number from a past one.
|
|
566
|
+
this.intentTurns.set(agent, this.currentTurns.get(agent));
|
|
567
|
+
// The agent can leave its turn while the write is in flight. An idle session
|
|
568
|
+
// has no later boundary to wait for, so the promote is scheduled here.
|
|
569
|
+
if (this.isIdle(agent))
|
|
570
|
+
this.promoteAfterIdle(agent);
|
|
571
|
+
return { applied: false, revision };
|
|
572
|
+
}));
|
|
573
|
+
}
|
|
574
|
+
/** Join the current selection writer, not the active user turn; failures remain visible through status. */
|
|
575
|
+
async whenSelectionSettled(agent) {
|
|
576
|
+
await this.requests.get(agent);
|
|
577
|
+
}
|
|
578
|
+
/** Public compare-and-swap token, independent of the sequential binding journal. */
|
|
579
|
+
selectionRevision(agent) {
|
|
580
|
+
const committed = this.checkpoints.get(agent)?.committed;
|
|
581
|
+
return this.intents.get(agent)?.revision ?? (committed?.ownerId === agent.id ? (committed.requestRevision ?? committed.revision) : this.states.get(agent)?.revision) ?? 0;
|
|
582
|
+
}
|
|
583
|
+
/** The pending request, for a caller that shows the intended selection. */
|
|
584
|
+
intended(agent) {
|
|
585
|
+
const intent = this.intents.get(agent);
|
|
586
|
+
return intent === undefined ? undefined : { selection: structuredClone(intent.selection), revision: intent.revision };
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Make the pending request effective: durable request first, then the effect,
|
|
590
|
+
* then the committed twin and the published snapshot, exactly like a change.
|
|
591
|
+
* Single-flight per agent, so one boundary promotes once.
|
|
592
|
+
* @param agent - the agent whose pending request becomes effective.
|
|
593
|
+
*/
|
|
594
|
+
async promote(agent) {
|
|
595
|
+
if (this.disposed || !this.governs(agent))
|
|
596
|
+
return;
|
|
597
|
+
const inFlight = this.promoting.get(agent);
|
|
598
|
+
if (inFlight !== undefined)
|
|
599
|
+
return inFlight;
|
|
600
|
+
const operation = this.track(this.serialize(agent, () => this.promoteNow(agent)));
|
|
601
|
+
this.promoting.set(agent, operation);
|
|
602
|
+
try {
|
|
603
|
+
await operation;
|
|
604
|
+
}
|
|
605
|
+
finally {
|
|
606
|
+
if (this.promoting.get(agent) === operation)
|
|
607
|
+
this.promoting.delete(agent);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async promoteNow(agent) {
|
|
611
|
+
const intent = this.intents.get(agent);
|
|
612
|
+
const current = this.states.get(agent);
|
|
613
|
+
if (this.disposed || !this.status(agent).ready || intent === undefined || current === undefined)
|
|
614
|
+
return;
|
|
615
|
+
try {
|
|
616
|
+
this.intents.delete(agent);
|
|
617
|
+
this.intentTurns.delete(agent);
|
|
618
|
+
this.unavailable.add(agent);
|
|
619
|
+
await this.commit(agent, { revision: current.revision + 1, selection: structuredClone(intent.selection) }, this.lifetime.signal, undefined, intent.revision);
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
// The durable request stays in the log; the service denies and lets explicit
|
|
623
|
+
// recovery decide. It never retries on its own.
|
|
624
|
+
this.fail(agent, error);
|
|
625
|
+
throw error;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Promote from the idle phase, for a turn that never reached a clean stop and a
|
|
630
|
+
* request that raced the boundary. Re-wakes parked input so the same request
|
|
631
|
+
* re-runs under the promoted selection.
|
|
632
|
+
*/
|
|
633
|
+
promoteAfterIdle(agent) {
|
|
634
|
+
if (this.disposed || this.intents.get(agent) === undefined)
|
|
635
|
+
return;
|
|
636
|
+
const signal = this.lifetime.signal;
|
|
637
|
+
let stop = () => { };
|
|
638
|
+
const disposed = new Promise(resolve => {
|
|
639
|
+
if (signal.aborted) {
|
|
640
|
+
resolve();
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const onAbort = () => resolve();
|
|
644
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
645
|
+
stop = () => signal.removeEventListener('abort', onAbort);
|
|
646
|
+
});
|
|
647
|
+
const settled = Promise.race([agent.whenIdle(), disposed]).then(async () => {
|
|
648
|
+
stop();
|
|
649
|
+
if (this.disposed || !this.status(agent).ready || !this.intents.has(agent) || this.ctx.agents.get(agent.id) !== agent)
|
|
650
|
+
return;
|
|
651
|
+
try {
|
|
652
|
+
await this.maintenance(agent, () => this.promote(agent));
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
// Busy again: the next clean stop owns the request.
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
this.wake(agent);
|
|
659
|
+
});
|
|
660
|
+
void this.track(settled).catch(() => { });
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Whether the host reports this agent idle. The call keeps the answer opaque to
|
|
664
|
+
* control-flow narrowing, which a direct property comparison cannot.
|
|
665
|
+
*/
|
|
666
|
+
isIdle(agent) {
|
|
667
|
+
return agent.status === 'idle';
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Run one task at a time per agent. Each task starts after the previous settled,
|
|
671
|
+
* whether it succeeded or failed, so a rejected write never blocks the next one.
|
|
672
|
+
*/
|
|
673
|
+
serialize(agent, task) {
|
|
674
|
+
const previous = this.requests.get(agent) ?? Promise.resolve();
|
|
675
|
+
const next = previous.then(task, task);
|
|
676
|
+
this.requests.set(agent, next.then(() => undefined, () => undefined));
|
|
677
|
+
return next;
|
|
678
|
+
}
|
|
679
|
+
async persistIntent(agent, intent) {
|
|
680
|
+
const message = createUserMessage({
|
|
681
|
+
source: { kind: EXTENSION_INTENT_SOURCE, form: 'context', intent },
|
|
682
|
+
content: [{ type: 'text', text: 'Session extension selection request.' }]
|
|
683
|
+
});
|
|
684
|
+
agent.inject(message);
|
|
685
|
+
// The insertion is durable evidence; no pending message must prevent the host's turn-stopping boundary.
|
|
686
|
+
agent.inbox.remove(message.id);
|
|
687
|
+
if (!(await this.ctx.sessions.flush(agent.session)))
|
|
688
|
+
throw failure('extension-session-persistence-unavailable');
|
|
689
|
+
}
|
|
690
|
+
maintenance(agent, task) {
|
|
691
|
+
if (this.disposed)
|
|
692
|
+
return Promise.reject(failure('extension-session-not-ready'));
|
|
693
|
+
let operation;
|
|
694
|
+
try {
|
|
695
|
+
operation = agent.runMaintenance(task);
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
return Promise.reject(failure('extension-session-busy'));
|
|
699
|
+
}
|
|
700
|
+
return this.track(operation);
|
|
701
|
+
}
|
|
702
|
+
track(operation) {
|
|
703
|
+
this.operations.add(operation);
|
|
704
|
+
void operation.then(() => this.operations.delete(operation), () => this.operations.delete(operation));
|
|
705
|
+
return operation;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Start the driver for input stranded while this service denied the session.
|
|
709
|
+
*
|
|
710
|
+
* The stranded messages are already queued, so the wake carries no payload: the
|
|
711
|
+
* marker is plugin metadata the same pre-step guard filters, and `steer` is the
|
|
712
|
+
* published way to start a driver without cancelling live work (published
|
|
713
|
+
* semantics: a wake during maintenance latches and replays at convergence).
|
|
714
|
+
* Callers run it only after a committed recovery or a successful busy retry, and
|
|
715
|
+
* this guard makes that common: an unavailable or disposed session must never
|
|
716
|
+
* open a turn, so the check here is the single place that decides.
|
|
717
|
+
*/
|
|
718
|
+
wake(agent) {
|
|
719
|
+
if (this.disposed || !this.status(agent).ready)
|
|
720
|
+
return;
|
|
721
|
+
// Only an ordinary human request wakes a driver. The inbox decides rather than
|
|
722
|
+
// this instance's memory, because a host reload rebuilds queued input from the
|
|
723
|
+
// log. An absent context form is the opaque default, not evidence of authorship,
|
|
724
|
+
// so injected context from any producer waits for the user's next request
|
|
725
|
+
// instead. This is deliberately narrower than the historical progress
|
|
726
|
+
// classification: it decides whether to start new work, which the progress
|
|
727
|
+
// field never did.
|
|
728
|
+
const stranded = [...agent.inbox.nextStep, ...agent.inbox.nextTurn].some(message => message.source.kind === 'user');
|
|
729
|
+
if (!stranded)
|
|
730
|
+
return;
|
|
731
|
+
agent.steer(createUserMessage({
|
|
732
|
+
source: { kind: EXTENSION_SESSION_WAKE_SOURCE, form: 'context' },
|
|
733
|
+
content: [{ type: 'text', text: 'Session extension readiness restored.' }]
|
|
734
|
+
}));
|
|
735
|
+
}
|
|
736
|
+
fail(agent, error) {
|
|
737
|
+
// Fail-closed covers governed agents only: an ungoverned agent keeps its
|
|
738
|
+
// host lifecycle, so a failed capture must never cancel it.
|
|
739
|
+
if (!this.governs(agent))
|
|
740
|
+
return;
|
|
741
|
+
this.unavailable.add(agent);
|
|
742
|
+
const code = error instanceof Error && 'code' in error && typeof error.code === 'string' ? error.code : 'extension-session-apply-failed';
|
|
743
|
+
this.errors.set(agent, code);
|
|
744
|
+
agent.cancel({ kind: 'disposed' }, { keepInbox: true });
|
|
745
|
+
}
|
|
746
|
+
async persist(agent, binding) {
|
|
747
|
+
agent.inject(createUserMessage({
|
|
748
|
+
source: { kind: EXTENSION_SESSION_SOURCE, form: 'context', binding: structuredClone(binding) },
|
|
749
|
+
content: [{ type: 'text', text: 'Session extension selection metadata.' }]
|
|
750
|
+
}));
|
|
751
|
+
this.checkpoints.get(agent).latest = binding;
|
|
752
|
+
if (!(await this.ctx.sessions.flush(agent.session)))
|
|
753
|
+
throw failure('extension-session-persistence-unavailable');
|
|
754
|
+
}
|
|
755
|
+
async commit(agent, state, signal, recoveryOf, requestRevision) {
|
|
756
|
+
const previous = this.checkpoints.get(agent)?.latest;
|
|
757
|
+
const token = requestRevision ?? Math.max(state.revision, previous?.ownerId === agent.id ? (previous.requestRevision ?? 0) + 1 : 1);
|
|
758
|
+
if (!Number.isSafeInteger(token))
|
|
759
|
+
throw failure('extension-session-conflict');
|
|
760
|
+
const binding = {
|
|
761
|
+
version: 1,
|
|
762
|
+
ownerId: agent.id,
|
|
763
|
+
phase: 'pending',
|
|
764
|
+
...structuredClone(state),
|
|
765
|
+
...(token === state.revision ? {} : { requestRevision: token }),
|
|
766
|
+
...(recoveryOf ? { recoveryOf } : {})
|
|
767
|
+
};
|
|
768
|
+
const checkpoint = this.checkpoints.get(agent);
|
|
769
|
+
checkpoint.initial ??= structuredClone(state.selection);
|
|
770
|
+
let receipt;
|
|
771
|
+
// Durability is the point of no return: past it the receipt must publish, never
|
|
772
|
+
// roll back, so a throwing committed callback cannot undo an acknowledged write.
|
|
773
|
+
let durable = false;
|
|
774
|
+
try {
|
|
775
|
+
await this.persist(agent, binding);
|
|
776
|
+
signal?.throwIfAborted();
|
|
777
|
+
receipt = (await this.ports.applySelection(agent, structuredClone(state.selection))) ?? undefined;
|
|
778
|
+
signal?.throwIfAborted();
|
|
779
|
+
const committed = { ...binding, phase: 'committed' };
|
|
780
|
+
await this.persist(agent, committed);
|
|
781
|
+
durable = true;
|
|
782
|
+
checkpoint.committed = committed;
|
|
783
|
+
// Publish before this service's own state lands, so an effect that must be
|
|
784
|
+
// visible alongside the selection is in place by the time gates read it.
|
|
785
|
+
receipt?.commit();
|
|
786
|
+
this.states.set(agent, structuredClone(state));
|
|
787
|
+
this.unavailable.delete(agent);
|
|
788
|
+
this.errors.delete(agent);
|
|
789
|
+
this.ports.committed?.(agent);
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
// Disk writes and resource effects have no shared transaction. Never acknowledge an
|
|
793
|
+
// uncertain write: deny first, then undo, and let the original error be the outcome.
|
|
794
|
+
this.fail(agent, error);
|
|
795
|
+
await this.rollbackApplied(receipt, durable);
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Undo an applied-but-not-durable effect. A cleanup failure is reported as a
|
|
801
|
+
* diagnostic: the original error is what the caller acts on, so it is never
|
|
802
|
+
* replaced by whatever went wrong while unwinding.
|
|
803
|
+
*/
|
|
804
|
+
async rollbackApplied(receipt, durable) {
|
|
805
|
+
if (receipt === undefined || durable)
|
|
806
|
+
return;
|
|
807
|
+
try {
|
|
808
|
+
await receipt.rollback();
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
this.ctx.logger.warn('extension selection rollback failed: ' + String(error));
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
dispose() {
|
|
815
|
+
return (this.disposal ??= this.finishDisposal());
|
|
816
|
+
}
|
|
817
|
+
async finishDisposal() {
|
|
818
|
+
this.disposed = true;
|
|
819
|
+
this.lifetime.abort();
|
|
820
|
+
await Promise.allSettled([...this.initializing.values(), ...this.operations]);
|
|
821
|
+
for (const off of this.off.splice(0))
|
|
822
|
+
off();
|
|
823
|
+
this.captureSources.clear();
|
|
824
|
+
this.intents.clear();
|
|
825
|
+
this.promoting.clear();
|
|
826
|
+
this.intentTurns.clear();
|
|
827
|
+
this.currentTurns.clear();
|
|
828
|
+
this.requests.clear();
|
|
829
|
+
this.states.clear();
|
|
830
|
+
// Everything still pending was awaited above, so a disposed service keeps no
|
|
831
|
+
// per-agent bookkeeping: not a checkpoint, an error, an unavailable mark, an
|
|
832
|
+
// in-flight initialization, or a tracked operation.
|
|
833
|
+
this.checkpoints.clear();
|
|
834
|
+
this.errors.clear();
|
|
835
|
+
this.unavailable.clear();
|
|
836
|
+
this.initializing.clear();
|
|
837
|
+
this.operations.clear();
|
|
838
|
+
}
|
|
839
|
+
}
|