@moretea-labs/forge 1.7.2 → 1.8.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/AGENTS.md +121 -62
- package/CHANGELOG.md +40 -0
- package/CLAUDE.md +57 -62
- package/CONTRIBUTING.md +3 -2
- package/README.md +2 -2
- package/SKILL.md +0 -1
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/adapters/browser/index.ts +2 -0
- package/adapters/chatgpt/browser-delivery-host.ts +77 -0
- package/adapters/chatgpt/browser-delivery-runtime.ts +1256 -0
- package/adapters/chatgpt/controller-binding-store.ts +134 -0
- package/adapters/chatgpt/controller-round-host.ts +68 -0
- package/adapters/chatgpt/controller-round-settlement-store.ts +99 -0
- package/adapters/chatgpt/provider-delivery.ts +340 -0
- package/adapters/chatgpt/work-conversation-binding-store.ts +218 -0
- package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
- package/adapters/computer/chatgpt-extension-target.ts +463 -0
- package/adapters/computer/chatgpt-target-router.ts +103 -0
- package/adapters/computer/chrome-extension/background.js +385 -0
- package/adapters/computer/chrome-extension/content.js +378 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +30 -0
- package/adapters/computer/desktop-operator-contract.ts +7 -0
- package/adapters/computer/desktop-operator-discovery.ts +181 -0
- package/adapters/computer/desktop-operator-negotiation.ts +174 -0
- package/adapters/computer/desktop-operator-provider.ts +312 -0
- package/adapters/computer/index.ts +1 -0
- package/adapters/computer/interaction-target-authority.ts +1068 -0
- package/adapters/controller-process/binding-store.ts +34 -0
- package/adapters/controller-process/controller-host.ts +26 -0
- package/adapters/mcp/access-mode.ts +132 -0
- package/adapters/mcp/audit.ts +32 -0
- package/adapters/mcp/auth.ts +406 -0
- package/adapters/mcp/instructions.ts +37 -0
- package/adapters/mcp/legacy-context.ts +18 -0
- package/adapters/mcp/multi-repository.ts +421 -0
- package/adapters/mcp/oauth.ts +464 -0
- package/adapters/mcp/paths.ts +143 -0
- package/adapters/mcp/policy.ts +232 -0
- package/adapters/mcp/proxy-env.ts +62 -0
- package/adapters/mcp/readiness-projection.ts +24 -0
- package/adapters/mcp/redaction.ts +21 -0
- package/adapters/mcp/repo.ts +24 -0
- package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
- package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
- package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +1167 -0
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +206 -0
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +130 -0
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +173 -0
- package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +412 -0
- package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
- package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
- package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
- package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +380 -0
- package/adapters/mcp/runtime-gateway/process-tools.ts +457 -0
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +88 -0
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +285 -0
- package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
- package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +79 -0
- package/adapters/mcp/runtime-gateway/result-adapter.ts +114 -0
- package/adapters/mcp/runtime-gateway/router.ts +242 -0
- package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1200 -0
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +574 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +78 -0
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +187 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +45 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +962 -0
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +325 -0
- package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
- package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
- package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
- package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
- package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
- package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
- package/adapters/mcp/server.ts +1092 -0
- package/adapters/mcp/setup.ts +789 -0
- package/adapters/mcp/tool-capability-cache.ts +87 -0
- package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5139 -0
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +145 -0
- package/adapters/mcp/tool-mapping/repository-tools.ts +1682 -0
- package/adapters/mcp/tool-mapping/tools.ts +9 -0
- package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
- package/adapters/mcp/toolset-names.ts +79 -0
- package/adapters/mcp/toolset-selection.ts +41 -0
- package/adapters/mcp/toolset.ts +337 -0
- package/adapters/mcp/transports/http-observation.ts +365 -0
- package/adapters/mcp/transports/http.ts +933 -0
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +519 -0
- package/adapters/mcp/transports/stdio.ts +8 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +223 -0
- package/adapters/mcp/types.ts +41 -0
- package/assets/AGENTS.md +2 -16
- package/assets/CLAUDE.md +2 -16
- package/assets/hooks/AGENTS.md +3 -14
- package/assets/hooks/CLAUDE.md +3 -14
- package/assets/hooks/hook-input.sh +9 -2
- package/assets/hooks/lib/workflow-state.sh +28 -10
- package/assets/hooks/prompt-guard.sh +63 -37
- package/assets/hooks/security-sentinel.sh +2 -1
- package/assets/hooks/session-start-context.sh +7 -5
- package/assets/hooks/stop-orchestrator.sh +13 -3
- package/assets/initializer-question-pack.v4.json +11 -7
- package/assets/partials/05-workflow.partial.md +18 -73
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -2
- package/assets/partials-agents/04-task-protocol.partial.md +14 -59
- package/assets/partials-agents/06-quality-safety.partial.md +11 -16
- package/assets/partials-agents/08-deep-docs.partial.md +1 -2
- package/assets/plan-map.json +11 -11
- package/assets/plugin-registry.v1.json +11 -3
- package/assets/plugin-trust-policy.v1.json +6 -0
- package/assets/recovery/ForgeRecovery.ps1 +100 -0
- package/assets/recovery/forge-wsl-rescue +271 -0
- package/assets/reference-configs/agentic-development-flow.md +25 -0
- package/assets/reference-configs/development-protocol.md +11 -15
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/global-working-rules.md +28 -1
- package/assets/reference-configs/harness-overview.md +22 -112
- package/assets/reference-configs/hook-operations.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +2 -2
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/architecture-event.ts +3 -34
- package/assets/templates/helpers/architecture-queue.sh +61 -10
- package/assets/templates/helpers/archive-workflow.sh +90 -180
- package/assets/templates/helpers/capability-config.ts +8 -11
- package/assets/templates/helpers/capability-resolver.ts +37 -19
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +1 -9
- package/assets/templates/helpers/check-task-sync.sh +82 -11
- package/assets/templates/helpers/check-task-workflow.sh +1 -3
- package/assets/templates/helpers/context-contract-sync.sh +1 -12
- package/assets/templates/helpers/contract-worktree.sh +26 -24
- package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
- package/assets/templates/helpers/inspect-project-state.ts +1 -1
- package/assets/templates/helpers/migrate-project-template.sh +1 -1
- package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
- package/assets/templates/helpers/new-plan.sh +2 -2
- package/assets/templates/helpers/plan-to-todo.sh +3 -31
- package/assets/templates/helpers/sprint-backlog.sh +1 -1
- package/assets/templates/helpers/switch-plan.sh +1 -1
- package/assets/templates/helpers/workflow-contract.ts +1 -0
- package/assets/workflow-contract.v1.json +63 -171
- package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
- package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
- package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
- package/docs/architecture/CURRENT.md +233 -28
- package/docs/architecture/EVOLUTION.md +13 -0
- package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
- package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
- package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +37 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +311 -0
- package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- package/docs/architecture/domains/verification.md +8 -0
- package/docs/architecture/domains/workflow-engine.md +7 -1
- package/docs/architecture/index.md +3 -3
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
- package/docs/forge-chatgpt-mcp-setup.md +5 -3
- package/docs/forge-plugin-management.md +20 -4
- package/docs/forge-runtime-self-healing-loop.md +2 -0
- package/docs/operations/benchmark-cross-version-freeze.md +38 -0
- package/docs/operations/controller-browser-plugin.md +3 -1
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- package/docs/operations/features.md +1 -1
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +2 -0
- package/docs/operations/platform-support.md +16 -8
- package/docs/operations/platform-support.zh-CN.md +2 -2
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/standalone-disaster-recovery.md +38 -5
- package/docs/operations/uu-remote-rescue.md +41 -0
- package/docs/operations/windows-wsl-host-rescue.md +133 -0
- package/docs/public-usage-guide.md +1 -1
- package/docs/public-usage-guide.zh-CN.md +1 -1
- package/docs/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/02-connect-chatgpt.md +4 -4
- package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +46 -26
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +3 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +41 -0
- package/packages/kernel/controller/application/controller-service.ts +30 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +529 -0
- package/packages/kernel/controller/domain/controller-round.ts +110 -0
- package/packages/kernel/controller/domain/execution-quality.ts +135 -0
- package/packages/kernel/controller/domain/types.ts +56 -0
- package/packages/kernel/controller/index.ts +1 -0
- package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +1970 -0
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +804 -0
- package/packages/kernel/controller/ports/controller-host.ts +18 -0
- package/packages/kernel/goal/api/index.ts +1 -0
- package/packages/kernel/goal/domain/types.ts +40 -0
- package/packages/kernel/goal/index.ts +1 -0
- package/packages/kernel/identity/api/index.ts +15 -0
- package/packages/kernel/identity/application/identity-service.ts +67 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/scope.ts +63 -0
- package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
- package/packages/kernel/identity/domain/types.ts +44 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/domain/workspace.ts +56 -0
- package/packages/kernel/identity/index.ts +1 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
- package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
- package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
- package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
- package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/memory/api/index.ts +5 -0
- package/packages/kernel/memory/application/experience-service.ts +118 -0
- package/packages/kernel/memory/application/outcome-service.ts +77 -0
- package/packages/kernel/memory/domain/experience.ts +103 -0
- package/packages/kernel/memory/domain/operational-prior.ts +92 -0
- package/packages/kernel/memory/index.ts +1 -0
- package/packages/kernel/scheduler/api/index.ts +4 -0
- package/packages/kernel/scheduler/application/eligibility.ts +252 -0
- package/packages/kernel/scheduler/application/schedule-service.ts +27 -0
- package/packages/kernel/scheduler/application/settlement.ts +182 -0
- package/packages/kernel/scheduler/domain/schedule.ts +236 -0
- package/packages/kernel/scheduler/index.ts +1 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +805 -0
- package/packages/kernel/work/api/index.ts +16 -0
- package/packages/kernel/work/application/work-service.ts +123 -0
- package/packages/kernel/work/domain/check-receipt.ts +43 -0
- package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
- package/packages/kernel/work/domain/engineering-design.ts +286 -0
- package/packages/kernel/work/domain/engineering-profile.ts +238 -0
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +247 -0
- package/packages/kernel/work/domain/execution-snapshot.ts +2 -0
- package/packages/kernel/work/domain/implementation-review.ts +193 -0
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +224 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
- package/{src/runtime/control-plane/facade/work-state-machine.ts → packages/kernel/work/domain/state-machine.ts} +37 -66
- package/packages/kernel/work/domain/types.ts +534 -0
- package/packages/kernel/work/index.ts +1 -0
- package/packages/kernel/work/infrastructure/work-contract-store.ts +1967 -0
- package/packages/kernel/work/ports/work-contract-store.ts +21 -0
- package/packages/plugin-runtime/browser/index.ts +3 -0
- package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
- package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
- package/packages/plugin-runtime/browser/session-authority.ts +62 -0
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
- package/packages/plugin-runtime/computer/index.ts +8 -0
- package/packages/plugin-runtime/computer/provider-error.ts +26 -0
- package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
- package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
- package/packages/plugin-runtime/computer/provider.ts +19 -0
- package/packages/plugin-runtime/computer/target-authority.ts +215 -0
- package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
- package/packages/plugin-runtime/external/index.ts +1 -0
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +581 -0
- package/packages/plugin-runtime/index.ts +3 -0
- package/packages/protocols/browser/index.ts +1 -0
- package/packages/protocols/browser/session.ts +98 -0
- package/packages/protocols/computer/contract.ts +225 -0
- package/packages/protocols/computer/index.ts +1 -0
- package/packages/protocols/failure/contract.ts +158 -0
- package/packages/protocols/failure/index.ts +1 -0
- package/packages/protocols/handoff/index.ts +1 -0
- package/packages/protocols/handoff/status.ts +22 -0
- package/packages/protocols/mcp/execution-context.ts +11 -0
- package/packages/protocols/mcp/index.ts +2 -0
- package/packages/protocols/mcp/tool-contract.ts +42 -0
- package/packages/workflow-runtime/api/index.ts +2 -0
- package/packages/workflow-runtime/application/interpreter.ts +342 -0
- package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
- package/packages/workflow-runtime/index.ts +1 -0
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/assemble-template.ts +1 -1
- package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
- package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
- package/scripts/canary-controller-semantic-wait.ts +183 -0
- package/scripts/canary-global-hook.sh +2 -2
- package/scripts/check-mcp-compatibility.ts +85 -7
- package/scripts/check-platform-support.mjs +28 -2
- package/scripts/check-public-docs.sh +1 -1
- package/scripts/check-repository-hygiene.mjs +92 -0
- package/scripts/check-runtime-architecture.mjs +1643 -127
- package/scripts/check-source-duplication.mjs +9 -1
- package/scripts/controller-ngrok-rotation.sh +5 -4
- package/scripts/create-project-dirs.sh +10 -74
- package/scripts/forge-local-recovery-helper.mjs +282 -0
- package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
- package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
- package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
- package/scripts/hook-shim.sh +2 -2
- package/scripts/init-project.sh +12 -70
- package/scripts/install-desktop-operator-registration.ts +4 -2
- package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
- package/scripts/install-standalone-recovery.ts +122 -10
- package/scripts/install-uu-remote-rescue-registration.ts +74 -0
- package/scripts/install-windows-host-recovery-registration.ts +22 -0
- package/scripts/lib/controller-home-env.sh +6 -3
- package/scripts/lib/project-init-lib.sh +42 -582
- package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
- package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
- package/scripts/open-source-audit-allowlist.txt +11 -1
- package/scripts/portability-bindings.mjs +70 -0
- package/scripts/run-bounded-command.mjs +60 -0
- package/scripts/run-bun-test-file.ts +22 -6
- package/scripts/run-governed-gate.ts +4 -2
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-mcp-http-runtime.ts +6 -1
- package/scripts/smoke-runtime-recovery.ts +13 -11
- package/scripts/smoke-schedule-engine.ts +24 -16
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
- package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
- package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
- package/scripts/smoke-workflow-supervisor.ts +103 -0
- package/scripts/test-governance.ts +51 -2
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +11 -34
- package/src/cli/agent-jobs/integration.ts +18 -49
- package/src/cli/agent-jobs/job-manager.ts +17 -4
- package/src/cli/chatgpt-browser/bind-server.ts +2 -1
- package/src/cli/chatgpt-browser/binding.ts +20 -10
- package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
- package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
- package/src/cli/chatgpt-browser/engine.ts +10 -11
- package/src/cli/chatgpt-browser/native-provider.ts +24 -83
- package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
- package/src/cli/chatgpt-browser/types.ts +1 -0
- package/src/cli/commands/bootstrap-control.ts +334 -0
- package/src/cli/commands/brain-assistant.ts +52 -0
- package/src/cli/commands/brain.ts +191 -40
- package/src/cli/commands/chatgpt.ts +32 -67
- package/src/cli/commands/computer.ts +437 -0
- package/src/cli/commands/doctor.ts +18 -1
- package/src/cli/commands/init-hook.ts +71 -61
- package/src/cli/commands/init.ts +3 -1
- package/src/cli/commands/mcp.ts +16 -10
- package/src/cli/commands/plugin.ts +38 -12
- package/src/cli/commands/recovery.ts +507 -80
- package/src/cli/commands/runtime.ts +12 -7
- package/src/cli/commands/setup-profile.ts +97 -39
- package/src/cli/commands/status.ts +2 -1
- package/src/cli/controller/check-runner.ts +500 -100
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +4 -43
- package/src/cli/controller/completion-target.ts +3 -72
- package/src/cli/controller/context/source-materializer.ts +81 -100
- package/src/cli/controller/context/types.ts +7 -1
- package/src/cli/controller/context-pack.ts +28 -6
- package/src/cli/controller/execution-completion.ts +4 -7
- package/src/cli/controller/execution-policy.ts +36 -153
- package/src/cli/controller/governance.ts +2 -3
- package/src/cli/controller/issue-store.ts +4 -99
- package/src/cli/controller/launch-agents.ts +59 -18
- package/src/cli/controller/operational-plan.ts +24 -51
- package/src/cli/controller/runtime-config.ts +5 -1
- package/src/cli/controller/stuck-state-migration.ts +2 -3
- package/src/cli/controller/systemd-user.ts +148 -0
- package/src/cli/controller/types.ts +10 -70
- package/src/cli/controller/worklog.ts +67 -13
- package/src/cli/editing/edit-operation-contract.ts +92 -0
- package/src/cli/editing/edit-session.ts +290 -20
- package/src/cli/github/plugin.ts +17 -12
- package/src/cli/hook/route-registry.ts +3 -5
- package/src/cli/hook/runtime.ts +49 -3
- package/src/cli/index.ts +3 -0
- package/src/cli/local-bridge/connector-freshness.ts +2 -2
- package/src/cli/local-bridge/console-automations.ts +20 -15
- package/src/cli/local-bridge/console-view-models.ts +3 -12
- package/src/cli/local-bridge/facade-api.ts +105 -205
- package/src/cli/local-bridge/job-store.ts +1 -1
- package/src/cli/local-bridge/server.ts +48 -174
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/access-mode.ts +2 -132
- package/src/cli/mcp/access-tools.ts +2 -387
- package/src/cli/mcp/audit.ts +2 -32
- package/src/cli/mcp/auth.ts +2 -404
- package/src/cli/mcp/instructions.ts +2 -34
- package/src/cli/mcp/legacy-context.ts +2 -16
- package/src/cli/mcp/legacy-tool-service.ts +2 -5308
- package/src/cli/mcp/multi-repository.ts +2 -436
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/mcp/oauth.ts +2 -375
- package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
- package/src/cli/mcp/paths.ts +2 -140
- package/src/cli/mcp/policy.ts +2 -232
- package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
- package/src/cli/mcp/proxy-env.ts +2 -62
- package/src/cli/mcp/readiness-projection.ts +2 -24
- package/src/cli/mcp/redaction.ts +2 -21
- package/src/cli/mcp/repo.ts +2 -24
- package/src/cli/mcp/repository-tools.ts +2 -1613
- package/src/cli/mcp/server.ts +2 -951
- package/src/cli/mcp/setup.ts +2 -737
- package/src/cli/mcp/tool-capability-cache.ts +2 -87
- package/src/cli/mcp/tool-contract.ts +2 -14
- package/src/cli/mcp/tools.ts +2 -9
- package/src/cli/mcp/toolset-names.ts +2 -70
- package/src/cli/mcp/toolset-selection.ts +2 -41
- package/src/cli/mcp/toolset.ts +2 -336
- package/src/cli/mcp/transports/AGENTS.md +1 -10
- package/src/cli/mcp/transports/CLAUDE.md +1 -10
- package/src/cli/mcp/transports/http.ts +2 -1414
- package/src/cli/mcp/transports/session-registry.ts +2 -490
- package/src/cli/mcp/transports/stdio.ts +2 -8
- package/src/cli/mcp/types.ts +2 -41
- package/src/cli/mcp/umbrella-tools.ts +2 -117
- package/src/cli/repositories/command-executor.ts +91 -39
- package/src/cli/repositories/command-scope.ts +139 -4
- package/src/cli/repositories/controller-home.ts +167 -1
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/identity.ts +13 -0
- package/src/cli/repositories/registry.ts +145 -60
- package/src/cli/repositories/repository-snapshot.ts +53 -7
- package/src/cli/repositories/runtime-storage.ts +27 -2
- package/src/cli/repositories/safe-patch.ts +34 -38
- package/src/cli/repositories/selected-path-actions.ts +22 -2
- package/src/cli/repositories/structured-git.ts +167 -27
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/cli/runtime/helper-runner.ts +7 -0
- package/src/cli/runtime-invocation.ts +8 -1
- package/src/cli/tools/codegraph.ts +44 -25
- package/src/core/adoption/manifest-templates.ts +1 -1
- package/src/core/adoption/plan.ts +3 -26
- package/src/core/adoption/workflow-contract-plan.ts +17 -9
- package/src/effects/process-runner.ts +8 -14
- package/src/runtime/context/assistant-context.ts +167 -0
- package/src/runtime/context/assistant-work-context.ts +209 -0
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
- package/src/runtime/context/codegraph-read-provider.ts +55 -22
- package/src/runtime/context/codegraph-sidecar.cjs +8 -1
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +305 -0
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +203 -0
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/project-engineering-contract.ts +60 -0
- package/src/runtime/context/semantic-navigation-contract.ts +9 -0
- package/src/runtime/context/semantic-navigation.ts +6 -34
- package/src/runtime/context/typescript-navigation-process.ts +145 -0
- package/src/runtime/context/typescript-navigation.ts +89 -3
- package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
- package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
- package/src/runtime/control-plane/bootstrap/index.ts +5 -0
- package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
- package/src/runtime/control-plane/bootstrap/store.ts +21 -0
- package/src/runtime/control-plane/bootstrap/types.ts +87 -0
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +531 -0
- package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +282 -0
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +380 -0
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +113 -16
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +439 -102
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +34 -11
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +87 -0
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +31 -44
- package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +46 -53
- package/src/runtime/control-plane/execution/work-execution-support.ts +85 -164
- package/src/runtime/control-plane/execution/work-handle-authority.ts +349 -73
- package/src/runtime/control-plane/execution/work-handle-store.ts +55 -2
- package/src/runtime/control-plane/execution/work-head-settlement.ts +108 -0
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +173 -28
- package/src/runtime/control-plane/execution/work-operation-service.ts +92 -40
- package/src/runtime/control-plane/execution/work-preparation-service.ts +145 -269
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
- package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +543 -225
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +59 -45
- package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
- package/src/runtime/control-plane/execution/work-verification-service.ts +662 -52
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +205 -16
- package/src/runtime/control-plane/facade/capability-registry.ts +380 -44
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -22
- package/src/runtime/control-plane/facade/goal-workloop.ts +565 -1202
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +145 -0
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +266 -52
- package/src/runtime/control-plane/facade/index.ts +4 -3
- package/src/runtime/control-plane/facade/operation-digest.ts +34 -73
- package/src/runtime/control-plane/facade/plan-contract-store.ts +671 -245
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +31 -23
- package/src/runtime/control-plane/facade/requirement-authority.ts +121 -0
- package/src/runtime/control-plane/facade/requirement-board.ts +9 -4
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +85 -0
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -15
- package/src/runtime/control-plane/facade/types.ts +218 -620
- package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
- package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
- package/src/runtime/control-plane/generated-cache-retention.ts +336 -19
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +13 -2
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +274 -84
- package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
- package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -2
- package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +227 -109
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +160 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +139 -670
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +25 -6
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -194
- package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +302 -0
- package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +230 -80
- package/src/runtime/control-plane/persistence/sqlite-store.ts +654 -78
- package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
- package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
- package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
- package/src/runtime/control-plane/release-retention.ts +154 -26
- package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +844 -38
- package/src/runtime/control-plane/runtime-generation.ts +6 -0
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +479 -0
- package/src/runtime/control-plane/workspace/workspace-store.ts +249 -0
- package/src/runtime/diagnostics/incident-repair.ts +573 -0
- package/src/runtime/diagnostics/mcp-timing.ts +239 -6
- package/src/runtime/diagnostics/performance.ts +6 -14
- package/src/runtime/diagnostics/read-only-tool.ts +1 -1
- package/src/runtime/evidence/artifact-store.ts +216 -35
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/operational-shadow.ts +38 -0
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/process-check-receipt.ts +5 -43
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/evidence/sensitive-output.ts +4 -4
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +124 -6
- package/src/runtime/execution/managed-workspace.ts +112 -5
- package/src/runtime/execution/process-runtime/canary.ts +19 -5
- package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +24 -10
- package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
- package/src/runtime/execution/process-runtime/check-result.ts +177 -2
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +58 -9
- package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +117 -114
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +4 -0
- package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +283 -73
- package/src/runtime/execution/process-runtime/persisted-check.ts +130 -23
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +125 -64
- package/src/runtime/execution/process-runtime/runtime.ts +167 -17
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +27 -0
- package/src/runtime/execution/workers/executor.ts +18 -19
- package/src/runtime/execution/workers/worker-entry.ts +1 -1
- package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
- package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
- package/src/runtime/gateway/mcp/process-tools.ts +2 -289
- package/src/runtime/gateway/mcp/router.ts +2 -1125
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
- package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
- package/src/runtime/maintenance/cleanup.ts +38 -24
- package/src/runtime/platform/browser-product-discovery.ts +174 -0
- package/src/runtime/platform/computer-platform.ts +14 -0
- package/src/runtime/platform/executable-discovery.ts +54 -0
- package/src/runtime/platform/fetch-http-server.ts +90 -0
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +146 -0
- package/src/runtime/platform/windows-host-discovery.ts +118 -0
- package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
- package/src/runtime/plugins/action-application.ts +192 -0
- package/src/runtime/plugins/app-store-connect-adapter.ts +47 -35
- package/src/runtime/plugins/browser-adapter.ts +898 -135
- package/src/runtime/plugins/browser-automation-service.ts +33 -34
- package/src/runtime/plugins/browser-macos-bridge.ts +386 -46
- package/src/runtime/plugins/browser-manifest-surface.ts +75 -17
- package/src/runtime/plugins/browser-native-session-service.ts +64 -14
- package/src/runtime/plugins/browser-provider-registry.ts +2 -109
- package/src/runtime/plugins/browser-registration.ts +32 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
- package/src/runtime/plugins/browser-runtime.ts +55 -10
- package/src/runtime/plugins/browser-session-authority.ts +101 -291
- package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
- package/src/runtime/plugins/browser-session-store.ts +243 -80
- package/src/runtime/plugins/browser-session-types.ts +2 -98
- package/src/runtime/plugins/capability-authorization-grants.ts +272 -59
- package/src/runtime/plugins/catalog-trust.ts +82 -0
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
- package/src/runtime/plugins/computer-registration.ts +1050 -0
- package/src/runtime/plugins/config-store.ts +119 -0
- package/src/runtime/plugins/desktop-operator-external-policy.ts +964 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +123 -12
- package/src/runtime/plugins/errors.ts +8 -1
- package/src/runtime/plugins/external-adapter.ts +82 -323
- package/src/runtime/plugins/external-provider-policy.ts +48 -0
- package/src/runtime/plugins/external-registration.ts +30 -2
- package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
- package/src/runtime/plugins/external-unix-socket.ts +64 -193
- package/src/runtime/plugins/first-party-external-registration.ts +163 -0
- package/src/runtime/plugins/first-party-registry.ts +3 -1
- package/src/runtime/plugins/github-adapter.ts +1 -2
- package/src/runtime/plugins/gmail-adapter.ts +29 -8
- package/src/runtime/plugins/google-calendar-adapter.ts +8 -8
- package/src/runtime/plugins/google-shared.ts +45 -53
- package/src/runtime/plugins/google-tasks-adapter.ts +8 -8
- package/src/runtime/plugins/ios-adapter.ts +2 -3
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +192 -2
- package/src/runtime/plugins/macos-capability-broker.ts +15 -105
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/resend-adapter.ts +15 -24
- package/src/runtime/plugins/store.ts +1003 -387
- package/src/runtime/plugins/types.ts +28 -1
- package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
- package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
- package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +102 -430
- package/src/runtime/projections/controller-context.ts +12 -6
- package/src/runtime/projections/git-status-sampler.ts +73 -22
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +93 -43
- package/src/runtime/recovery/actions.ts +3 -3
- package/src/runtime/recovery/capability-action-application.ts +229 -0
- package/src/runtime/recovery/index.ts +3 -0
- package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
- package/src/runtime/recovery/local-jobs-repair.ts +14 -14
- package/src/runtime/recovery/maintenance-executor.ts +701 -168
- package/src/runtime/recovery/quarantine-retention.ts +256 -0
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/identity.ts +6 -0
- package/src/runtime/release/release-coordinator.ts +296 -0
- package/src/runtime/release/release-session.ts +716 -0
- package/src/runtime/release/release-state-migration.ts +25 -0
- package/src/runtime/resources/leases/store.ts +56 -25
- package/src/runtime/root/active-execution-power-assertion.ts +139 -0
- package/src/runtime/root/assistant-learning-loop.ts +45 -0
- package/src/runtime/root/browser-session-composition.ts +59 -0
- package/src/runtime/root/computer-composition.ts +376 -0
- package/src/runtime/root/computer-target-composition.ts +16 -0
- package/src/runtime/root/computer-target-persistence.ts +39 -0
- package/src/runtime/root/controller-progression-composition.ts +192 -0
- package/src/runtime/root/controller-round-composition.ts +126 -0
- package/src/runtime/root/controller-services.ts +2 -0
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +8 -6
- package/src/runtime/root/gateway-adapter.ts +3 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +5 -3
- package/src/runtime/root/mcp-transport.ts +116 -23
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +212 -74
- package/src/runtime/root/package-runtime-release.ts +199 -33
- package/src/runtime/root/package-runtime-service.ts +77 -108
- package/src/runtime/root/release-execution-canary.ts +103 -0
- package/src/runtime/root/release-loader.ts +150 -0
- package/src/runtime/root/release-manifest.ts +185 -7
- package/src/runtime/root/release-materialize.ts +474 -31
- package/src/runtime/root/release-store.ts +425 -37
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +379 -32
- package/src/runtime/root/scheduled-controller-composition.ts +76 -0
- package/src/runtime/root/scheduler.ts +12 -4
- package/src/runtime/root/service-runner.ts +3 -3
- package/src/runtime/root/service.ts +73 -45
- package/src/runtime/root/status.ts +49 -0
- package/src/runtime/root/types.ts +55 -3
- package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/local-bridge-surface.ts +1 -1
- package/src/runtime/shared/process-environment.ts +36 -10
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/shared/storage-capacity.ts +68 -0
- package/src/runtime/standalone-recovery/controller-home-migration.ts +1108 -0
- package/src/runtime/standalone-recovery/core.ts +4609 -495
- package/src/runtime/standalone-recovery/entry.ts +642 -81
- package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
- package/src/runtime/standalone-recovery/installer.ts +399 -177
- package/src/runtime/standalone-recovery/mcp-server.ts +79 -0
- package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
- package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
- package/src/runtime/standalone-recovery/performance.ts +173 -0
- package/src/runtime/standalone-recovery/release.ts +58 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -4
- package/src/runtime/workflow/schedules/engine.ts +298 -484
- package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
- package/src/runtime/workflow/schedules/settlement.ts +2 -191
- package/src/runtime/workflow/schedules/store.ts +2 -607
- package/src/runtime/workflow/schedules/types.ts +2 -127
- package/src/runtime/workflow/schedules/work-continuation.ts +220 -16
- package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
- package/src/runtime/workflows/runtime.ts +482 -0
- package/src/testing/test-governance.ts +95 -30
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +201 -0
- package/supervisor/control-plane.ts +868 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +80 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +786 -0
- package/supervisor/native-messaging/host.ts +39 -0
- package/supervisor/native-messaging/manifest.ts +26 -0
- package/supervisor/native-messaging/protocol.ts +27 -0
- package/supervisor/paths.ts +18 -0
- package/supervisor/protocol.ts +202 -0
- package/supervisor/server.ts +404 -0
- package/supervisor/store.ts +1451 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +255 -0
- package/assets/templates/helpers/archive-architecture-request.sh +0 -244
- package/docs/architecture/history/README.md +0 -7
- package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
- package/docs/architecture/history/global-hook-runtime.md +0 -166
- package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
- package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
- package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
- package/docs/architecture/requests/archive/2026/root.md +0 -68
- package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
- package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
- package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
- package/scripts/archive-architecture-request.sh +0 -4
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -139
- package/scripts/benchmark-ios-agent-device.ts +0 -514
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -170
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-tarball-install-smoke.sh +0 -67
- package/scripts/package-source-archive.sh +0 -147
- package/scripts/public-release-files.txt +0 -39
- package/scripts/public-release.ts +0 -174
- package/scripts/publish-release-tarball.sh +0 -27
- package/scripts/route-nl-vs-ts-eval.ts +0 -679
- package/scripts/run-skill-evals.ts +0 -1022
- package/scripts/stage-runtime-release.ts +0 -39
- package/src/cli/controller/progress.ts +0 -656
- package/src/cli/controller/work-mode.ts +0 -181
- package/src/cli/local-bridge/console-assets.ts +0 -17
- package/src/cli/local-bridge/dashboard.ts +0 -17
- package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
- package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
- package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
- package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/styles.css +0 -28
- package/src/cli/local-bridge/frontend/src/types.ts +0 -15
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
- package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
- package/src/cli/local-bridge/ui-dist/app.css +0 -1
- package/src/cli/local-bridge/ui-dist/app.js +0 -11228
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -385
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -1913
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -449
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -1118
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -93
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -382
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
1
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import type { RepositoryRecord } from '../../cli/repositories/types';
|
|
4
|
-
import { CONTROLLER_SCOPE_REPO_ID, controllerSystemRoot, ensureControllerHome, repositoryControllerRoot } from '../../cli/repositories/controller-home';
|
|
5
|
-
import { existsSync, mkdirSync, readdirSync, rmSync } from 'fs';
|
|
4
|
+
import { CONTROLLER_SCOPE_REPO_ID, FORGE_INSTANCE_SCOPE_KEY, controllerSystemRoot, ensureControllerHome, repositoryControllerRoot } from '../../cli/repositories/controller-home';
|
|
5
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, rmSync } from 'fs';
|
|
6
6
|
import type { ExecutionJob, ResourceClaimSpec } from '../execution/jobs/types';
|
|
7
7
|
import { appendRuntimeEvent } from '../evidence/event-ledger';
|
|
8
8
|
import { readJsonFile, sanitizeFileComponent, writeJsonAtomic } from '../shared/json-files';
|
|
9
9
|
import { createFirstPartyPluginAdapterMap } from './first-party-registry';
|
|
10
|
+
import { reconcileFirstPartyExternalPluginRegistration, reconcileFirstPartyExternalPluginRegistrations } from './first-party-external-registration';
|
|
11
|
+
import { REPOSITORY_PLUGIN_CONFIG_IDS, repositoryPluginConfigFileName, repositoryPluginConfigPath } from './config-store';
|
|
10
12
|
import { getExternalPluginAdapter, listExternalPluginAdapters } from './external-adapter';
|
|
11
|
-
import { AssistantPluginError, toAssistantPluginError } from './errors';
|
|
13
|
+
import { AssistantPluginError, isAssistantPluginError, toAssistantPluginError, type AssistantPluginEffectOutcome } from './errors';
|
|
12
14
|
import {
|
|
15
|
+
findActivePluginCapabilityAuthorizationById,
|
|
13
16
|
findActivePluginCapabilityAuthorization,
|
|
14
17
|
pluginCapabilityAuthorizationOwnerScope,
|
|
15
18
|
recordPluginCapabilityAuthorization,
|
|
16
19
|
type PluginCapabilityAuthorizationGrant,
|
|
17
20
|
} from './capability-authorization-grants';
|
|
18
21
|
import { markControllerContextProjectionDirty } from '../projections/controller-context';
|
|
19
|
-
import {
|
|
22
|
+
import { acquireExecutionLeases, releaseExactExecutionLeases } from '../resources/leases/store';
|
|
23
|
+
import { isRuntimeWriteFenceError } from '../root/write-fence';
|
|
20
24
|
import {
|
|
21
|
-
acceptSubmittedWorkContract,
|
|
22
25
|
appendWorkEvidence,
|
|
23
26
|
getWorkContract,
|
|
24
27
|
recordWorkCompletionReceipt,
|
|
25
28
|
updateWorkContract,
|
|
26
|
-
} from '
|
|
27
|
-
import {
|
|
29
|
+
} from '../../../packages/kernel/work/api/index';
|
|
30
|
+
import { isTerminalSemanticWorkState, type RemoteEffectCompletionReceipt, type WorkContract } from '../control-plane/facade/types';
|
|
28
31
|
import type {
|
|
29
32
|
AssistantPluginAdapter,
|
|
30
33
|
AssistantPluginActionDescriptor,
|
|
@@ -43,8 +46,10 @@ function resolvePluginAdapter(controllerHome: string, pluginId: string): Assista
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
function listPluginAdapters(controllerHome: string): AssistantPluginAdapter[] {
|
|
46
|
-
const
|
|
47
|
-
|
|
49
|
+
const firstParty = [...PLUGIN_ADAPTERS.values()].filter((adapter) => adapter.exposure !== 'internal');
|
|
50
|
+
const external = listExternalPluginAdapters(controllerHome)
|
|
51
|
+
.filter((adapter) => !PLUGIN_ADAPTERS.has(adapter.pluginId) && adapter.exposure !== 'internal');
|
|
52
|
+
return [...firstParty, ...external];
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
export function assistantPluginScope(pluginId: string, controllerHome?: string): AssistantPluginAdapter['scope'] | undefined {
|
|
@@ -67,40 +72,200 @@ function now(): string {
|
|
|
67
72
|
return new Date().toISOString();
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
const CONTROLLER_PLUGIN_CACHE_SCOPE = 'controller';
|
|
76
|
+
const CONTROLLER_PLUGIN_GRANT_SCOPE = 'controller:global';
|
|
77
|
+
const CONTROLLER_PLUGIN_SCOPE_KEY = FORGE_INSTANCE_SCOPE_KEY;
|
|
78
|
+
|
|
79
|
+
interface PluginActionScopeContext {
|
|
80
|
+
kind: 'controller' | 'repository';
|
|
81
|
+
scopeKey: string;
|
|
82
|
+
grantRepoId: string;
|
|
83
|
+
root: string;
|
|
84
|
+
checkoutId?: string;
|
|
85
|
+
repository?: RepositoryRecord;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function repositoryPluginActionScope(repository: RepositoryRecord): PluginActionScopeContext {
|
|
89
|
+
return {
|
|
90
|
+
kind: 'repository',
|
|
91
|
+
scopeKey: repository.repoId,
|
|
92
|
+
grantRepoId: repository.repoId,
|
|
93
|
+
root: repository.canonicalRoot,
|
|
94
|
+
checkoutId: repository.activeCheckoutId,
|
|
95
|
+
repository,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function controllerPluginActionScope(controllerHome: string): PluginActionScopeContext {
|
|
100
|
+
return {
|
|
101
|
+
kind: 'controller',
|
|
102
|
+
scopeKey: CONTROLLER_PLUGIN_SCOPE_KEY,
|
|
103
|
+
grantRepoId: CONTROLLER_PLUGIN_GRANT_SCOPE,
|
|
104
|
+
root: controllerSystemRoot(controllerHome),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function pluginActionExecutionFields(scope: PluginActionScopeContext): { repoId: string; repoRoot: string } {
|
|
109
|
+
// Adapter fields are a compatibility projection. controller:global identifies
|
|
110
|
+
// the Grant/config domain; it is not a Repository Registry identity.
|
|
111
|
+
return { repoId: scope.grantRepoId, repoRoot: scope.root };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function controllerPluginsRoot(controllerHome: string): string {
|
|
115
|
+
return join(controllerSystemRoot(controllerHome), 'plugins');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function controllerManifestPath(controllerHome: string, pluginId: string): string {
|
|
119
|
+
return join(controllerPluginsRoot(controllerHome), 'manifests', `${sanitizeFileComponent(pluginId)}.json`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function controllerIndexPath(controllerHome: string): string {
|
|
123
|
+
return join(controllerPluginsRoot(controllerHome), 'index.json');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function adapterMatchesController(adapter: AssistantPluginAdapter): boolean {
|
|
127
|
+
const scope = adapter.scope ?? 'repository';
|
|
128
|
+
return scope === 'controller' || scope === 'controller_with_repository_overlay';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function adapterMatchesRepository(adapter: AssistantPluginAdapter, _repository: RepositoryRecord): boolean {
|
|
132
|
+
const scope = adapter.scope ?? 'repository';
|
|
133
|
+
return scope === 'repository' || scope === 'controller_with_repository_overlay';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function computeControllerManifest(
|
|
137
|
+
controllerHome: string,
|
|
138
|
+
pluginId: string,
|
|
139
|
+
resolvedAdapter?: AssistantPluginAdapter,
|
|
140
|
+
): AssistantPluginManifest {
|
|
141
|
+
const adapter = resolvedAdapter ?? resolvePluginAdapter(controllerHome, pluginId);
|
|
142
|
+
if (!adapter || !adapterMatchesController(adapter)) throw new Error(`PLUGIN_NOT_FOUND: ${pluginId}`);
|
|
143
|
+
const previous = readControllerStoredPluginManifest(controllerHome, pluginId);
|
|
71
144
|
const root = controllerSystemRoot(controllerHome);
|
|
72
|
-
const
|
|
145
|
+
const built = adapter.buildManifest(previous?.revision ?? 0, previous?.updatedAt, root, {
|
|
146
|
+
controllerHome,
|
|
147
|
+
repoId: CONTROLLER_PLUGIN_GRANT_SCOPE,
|
|
148
|
+
repoRoot: root,
|
|
149
|
+
controllerScoped: true,
|
|
150
|
+
});
|
|
151
|
+
const changed = !previous || fingerprintManifest(previous) !== fingerprintManifest(built);
|
|
73
152
|
return {
|
|
153
|
+
...built,
|
|
154
|
+
revision: previous ? (changed ? previous.revision + 1 : previous.revision) : 1,
|
|
155
|
+
updatedAt: changed ? now() : previous?.updatedAt ?? built.updatedAt,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function readControllerStoredPluginManifest(controllerHome: string, pluginId: string): AssistantPluginManifest | undefined {
|
|
160
|
+
try {
|
|
161
|
+
const canonicalPath = controllerManifestPath(controllerHome, pluginId);
|
|
162
|
+
if (existsSync(canonicalPath)) return readJsonFile<AssistantPluginManifest>(canonicalPath);
|
|
163
|
+
const legacyPath = join(legacyControllerPluginsRoot(controllerHome), 'manifests', `${sanitizeFileComponent(pluginId)}.json`);
|
|
164
|
+
return existsSync(legacyPath) ? readJsonFile<AssistantPluginManifest>(legacyPath) : undefined;
|
|
165
|
+
} catch {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function listControllerPluginIds(controllerHome: string): string[] {
|
|
171
|
+
return listPluginAdapters(controllerHome).filter(adapterMatchesController).map((adapter) => adapter.pluginId).sort();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function listControllerPluginManifests(
|
|
175
|
+
controllerHome: string,
|
|
176
|
+
options: ListAssistantPluginManifestsOptions = {},
|
|
177
|
+
): AssistantPluginManifest[] {
|
|
178
|
+
const preferStored = options.preferStored === true && options.forceRefresh !== true;
|
|
179
|
+
const fallbackToLive = options.fallbackToLive !== false || !preferStored;
|
|
180
|
+
const cacheKey = listCacheKey(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, preferStored, fallbackToLive);
|
|
181
|
+
if (options.forceRefresh !== true) {
|
|
182
|
+
const cached = readPluginManifestCache(pluginManifestListCache, cacheKey, preferStored ? PLUGIN_MANIFEST_STORED_CACHE_TTL_MS : PLUGIN_MANIFEST_LIVE_CACHE_TTL_MS);
|
|
183
|
+
if (cached) return cached;
|
|
184
|
+
}
|
|
185
|
+
const manifests = listControllerPluginIds(controllerHome).map((pluginId) => {
|
|
186
|
+
if (preferStored) {
|
|
187
|
+
const stored = readControllerStoredPluginManifest(controllerHome, pluginId);
|
|
188
|
+
if (stored) return stored;
|
|
189
|
+
if (!fallbackToLive) return undefined;
|
|
190
|
+
}
|
|
191
|
+
return computeControllerManifest(controllerHome, pluginId);
|
|
192
|
+
}).filter((manifest): manifest is AssistantPluginManifest => Boolean(manifest)).sort((left, right) => left.pluginId.localeCompare(right.pluginId));
|
|
193
|
+
primePluginManifestItemCache(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, manifests, preferStored);
|
|
194
|
+
return writePluginManifestCache(pluginManifestListCache, cacheKey, manifests);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getControllerPluginManifestForExecution(
|
|
198
|
+
controllerHome: string,
|
|
199
|
+
pluginId: string,
|
|
200
|
+
adapter: AssistantPluginAdapter,
|
|
201
|
+
): { manifest: AssistantPluginManifest; providerIdentityPrevalidated: boolean } {
|
|
202
|
+
const cacheKey = itemCacheKey(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, pluginId, false);
|
|
203
|
+
const cached = readPluginManifestCache(pluginManifestItemCache, cacheKey);
|
|
204
|
+
const wrap = (manifest: AssistantPluginManifest) => ({
|
|
205
|
+
manifest,
|
|
206
|
+
providerIdentityPrevalidated: manifest.enabled && manifest.health.probed && manifest.health.ready,
|
|
207
|
+
});
|
|
208
|
+
if (cached) return wrap(cached);
|
|
209
|
+
return wrap(writePluginManifestCache(pluginManifestItemCache, cacheKey, computeControllerManifest(controllerHome, pluginId, adapter)));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getControllerPluginManifest(controllerHome: string, pluginId: string): AssistantPluginManifest {
|
|
213
|
+
const adapter = resolvePluginAdapter(controllerHome, pluginId);
|
|
214
|
+
if (!adapter || !adapterMatchesController(adapter)) throw new Error(`PLUGIN_NOT_FOUND: ${pluginId}`);
|
|
215
|
+
return getControllerPluginManifestForExecution(controllerHome, pluginId, adapter).manifest;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function writeControllerRegistry(controllerHome: string, manifests: AssistantPluginManifest[]): AssistantPluginRegistryIndex {
|
|
219
|
+
const index: AssistantPluginRegistryIndex = {
|
|
74
220
|
schemaVersion: 1,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
checkouts: [],
|
|
81
|
-
defaultBranch: 'none',
|
|
82
|
-
repositoryType: 'local-git',
|
|
83
|
-
enabled: true,
|
|
84
|
-
createdAt: timestamp,
|
|
85
|
-
updatedAt: timestamp,
|
|
86
|
-
lastSeenAt: timestamp,
|
|
87
|
-
configurationPath: join(root, 'local-system', 'targets.json'),
|
|
88
|
-
stateStorageStrategy: 'controller-home',
|
|
221
|
+
updatedAt: now(),
|
|
222
|
+
plugins: manifests.map((manifest) => ({
|
|
223
|
+
...pluginIndexEntry(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, manifest),
|
|
224
|
+
manifestPath: controllerManifestPath(controllerHome, manifest.pluginId),
|
|
225
|
+
})).sort((left, right) => left.pluginId.localeCompare(right.pluginId)),
|
|
89
226
|
};
|
|
227
|
+
writeJsonAtomic(controllerIndexPath(controllerHome), index);
|
|
228
|
+
return index;
|
|
90
229
|
}
|
|
91
230
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
231
|
+
export function syncControllerPluginRegistry(controllerHome: string) {
|
|
232
|
+
reconcileFirstPartyExternalPluginRegistrations(controllerHome);
|
|
233
|
+
invalidateAssistantPluginManifestCache(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE);
|
|
234
|
+
const manifests = listControllerPluginManifests(controllerHome, { forceRefresh: true });
|
|
235
|
+
for (const manifest of manifests) writeJsonAtomic(controllerManifestPath(controllerHome, manifest.pluginId), manifest);
|
|
236
|
+
return { manifests, index: writeControllerRegistry(controllerHome, manifests) };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function syncControllerPluginManifest(controllerHome: string, pluginId: string) {
|
|
240
|
+
reconcileFirstPartyExternalPluginRegistration(controllerHome, pluginId);
|
|
241
|
+
invalidateAssistantPluginManifestCache(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, pluginId);
|
|
242
|
+
const manifest = computeControllerManifest(controllerHome, pluginId);
|
|
243
|
+
writeJsonAtomic(controllerManifestPath(controllerHome, pluginId), manifest);
|
|
244
|
+
cacheAssistantPluginManifest(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, manifest, false);
|
|
245
|
+
cacheAssistantPluginManifest(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, manifest, true);
|
|
246
|
+
const manifests = listControllerPluginIds(controllerHome).map((candidate) => candidate === pluginId
|
|
247
|
+
? manifest
|
|
248
|
+
: readControllerStoredPluginManifest(controllerHome, candidate) ?? cachedManifestForRepository(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, candidate))
|
|
249
|
+
.filter((entry): entry is AssistantPluginManifest => Boolean(entry)).sort((left, right) => left.pluginId.localeCompare(right.pluginId));
|
|
250
|
+
return { manifest, index: writeControllerRegistry(controllerHome, manifests) };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function removeControllerPluginManifestProjection(controllerHome: string, pluginId: string) {
|
|
254
|
+
invalidateAssistantPluginManifestCache(controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, pluginId);
|
|
255
|
+
rmSync(controllerManifestPath(controllerHome, pluginId), { force: true });
|
|
256
|
+
const current = readJsonFile<AssistantPluginRegistryIndex>(controllerIndexPath(controllerHome), { schemaVersion: 1, updatedAt: now(), plugins: [] });
|
|
257
|
+
const next = { ...current, updatedAt: now(), plugins: current.plugins.filter((entry) => entry.pluginId !== pluginId) };
|
|
258
|
+
writeJsonAtomic(controllerIndexPath(controllerHome), next);
|
|
259
|
+
return next;
|
|
96
260
|
}
|
|
97
261
|
|
|
98
262
|
function cloneCacheValue<T>(value: T): T {
|
|
99
263
|
return structuredClone(value);
|
|
100
264
|
}
|
|
101
265
|
|
|
102
|
-
function listCacheKey(controllerHome: string, repoId: string, preferStored: boolean): string {
|
|
103
|
-
|
|
266
|
+
function listCacheKey(controllerHome: string, repoId: string, preferStored: boolean, fallbackToLive: boolean): string {
|
|
267
|
+
const mode = preferStored ? (fallbackToLive ? 'stored-with-live-fallback' : 'stored-only') : 'live';
|
|
268
|
+
return `${controllerHome}::${repoId}::list::${mode}`;
|
|
104
269
|
}
|
|
105
270
|
|
|
106
271
|
function itemCacheKey(controllerHome: string, repoId: string, pluginId: string, preferStored: boolean): string {
|
|
@@ -184,7 +349,13 @@ export function clearAssistantPluginManifestCacheForTest(): void {
|
|
|
184
349
|
}
|
|
185
350
|
|
|
186
351
|
function pluginsRoot(controllerHome: string, repoId: string): string {
|
|
187
|
-
return
|
|
352
|
+
return repoId === CONTROLLER_SCOPE_REPO_ID
|
|
353
|
+
? join(controllerSystemRoot(controllerHome), 'plugins')
|
|
354
|
+
: join(repositoryControllerRoot(controllerHome, repoId), 'plugins');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function legacyControllerPluginsRoot(controllerHome: string): string {
|
|
358
|
+
return join(repositoryControllerRoot(controllerHome, CONTROLLER_SCOPE_REPO_ID), 'plugins');
|
|
188
359
|
}
|
|
189
360
|
|
|
190
361
|
function manifestPath(controllerHome: string, repoId: string, pluginId: string): string {
|
|
@@ -229,13 +400,28 @@ function pluginIndexEntry(controllerHome: string, repoId: string, manifest: Assi
|
|
|
229
400
|
}
|
|
230
401
|
|
|
231
402
|
function readStoredManifest(controllerHome: string, repoId: string, pluginId: string): AssistantPluginManifest | undefined {
|
|
403
|
+
const canonicalPath = manifestPath(controllerHome, repoId, pluginId);
|
|
232
404
|
try {
|
|
233
|
-
return readJsonFile<AssistantPluginManifest>(
|
|
405
|
+
if (existsSync(canonicalPath)) return readJsonFile<AssistantPluginManifest>(canonicalPath);
|
|
406
|
+
if (repoId === CONTROLLER_SCOPE_REPO_ID) {
|
|
407
|
+
const legacyPath = join(legacyControllerPluginsRoot(controllerHome), 'manifests', `${sanitizeFileComponent(pluginId)}.json`);
|
|
408
|
+
if (existsSync(legacyPath)) return readJsonFile<AssistantPluginManifest>(legacyPath);
|
|
409
|
+
}
|
|
410
|
+
return undefined;
|
|
234
411
|
} catch {
|
|
235
412
|
return undefined;
|
|
236
413
|
}
|
|
237
414
|
}
|
|
238
415
|
|
|
416
|
+
/** Read-only persisted plugin projection. This never probes or rebuilds a provider manifest. */
|
|
417
|
+
export function readStoredAssistantPluginManifest(
|
|
418
|
+
controllerHome: string,
|
|
419
|
+
repository: RepositoryRecord,
|
|
420
|
+
pluginId: string,
|
|
421
|
+
): AssistantPluginManifest | undefined {
|
|
422
|
+
return readStoredManifest(controllerHome, repository.repoId, pluginId);
|
|
423
|
+
}
|
|
424
|
+
|
|
239
425
|
function cachedManifestForRepository(
|
|
240
426
|
controllerHome: string,
|
|
241
427
|
repoId: string,
|
|
@@ -248,12 +434,48 @@ function cachedManifestForRepository(
|
|
|
248
434
|
) ?? readPluginManifestCache(pluginManifestItemCache, itemCacheKey(controllerHome, repoId, pluginId, false));
|
|
249
435
|
}
|
|
250
436
|
|
|
437
|
+
export function migrateRepositoryPluginConfigLegacyFiles(
|
|
438
|
+
controllerHome: string,
|
|
439
|
+
repository: Pick<RepositoryRecord, 'repoId' | 'canonicalRoot'>,
|
|
440
|
+
): string {
|
|
441
|
+
const targetRoot = join(repositoryControllerRoot(controllerHome, repository.repoId), 'plugins', 'config');
|
|
442
|
+
const legacyRoots = [
|
|
443
|
+
join(repository.canonicalRoot, '.forge', 'plugins'),
|
|
444
|
+
join(repository.canonicalRoot, '.repo-harness', 'plugins'),
|
|
445
|
+
];
|
|
446
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
447
|
+
for (const legacyRoot of legacyRoots) {
|
|
448
|
+
if (!existsSync(legacyRoot)) continue;
|
|
449
|
+
const stat = lstatSync(legacyRoot);
|
|
450
|
+
// Historical compatibility symlinks may point at another Controller Home.
|
|
451
|
+
// Never traverse them; unlink only the retired repository-local compatibility entry.
|
|
452
|
+
if (stat.isSymbolicLink()) {
|
|
453
|
+
rmSync(legacyRoot, { force: true });
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (!stat.isDirectory()) throw new Error(`PLUGIN_CONFIG_LEGACY_PATH_INVALID: ${legacyRoot}`);
|
|
457
|
+
for (const pluginId of REPOSITORY_PLUGIN_CONFIG_IDS) {
|
|
458
|
+
const fileName = repositoryPluginConfigFileName(pluginId);
|
|
459
|
+
const source = join(legacyRoot, fileName);
|
|
460
|
+
if (!existsSync(source)) continue;
|
|
461
|
+
const sourceStat = lstatSync(source);
|
|
462
|
+
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) continue;
|
|
463
|
+
const target = repositoryPluginConfigPath({ controllerHome, repoId: repository.repoId }, pluginId);
|
|
464
|
+
if (!existsSync(target)) cpSync(source, target, { force: false, errorOnExist: false });
|
|
465
|
+
rmSync(source, { force: true });
|
|
466
|
+
}
|
|
467
|
+
if (readdirSync(legacyRoot).length === 0) rmSync(legacyRoot, { recursive: true, force: true });
|
|
468
|
+
}
|
|
469
|
+
return targetRoot;
|
|
470
|
+
}
|
|
471
|
+
|
|
251
472
|
function computeManifest(
|
|
252
473
|
controllerHome: string,
|
|
253
474
|
repository: RepositoryRecord,
|
|
254
475
|
pluginId: string,
|
|
255
476
|
resolvedAdapter?: AssistantPluginAdapter,
|
|
256
477
|
): AssistantPluginManifest {
|
|
478
|
+
migrateRepositoryPluginConfigLegacyFiles(controllerHome, repository);
|
|
257
479
|
const adapter = resolvedAdapter ?? resolvePluginAdapter(controllerHome, pluginId);
|
|
258
480
|
if (!adapter || !adapterMatchesRepository(adapter, repository)) throw new Error(`PLUGIN_NOT_FOUND: ${pluginId}`);
|
|
259
481
|
const previous = readStoredManifest(controllerHome, repository.repoId, pluginId);
|
|
@@ -261,7 +483,7 @@ function computeManifest(
|
|
|
261
483
|
controllerHome,
|
|
262
484
|
repoId: repository.repoId,
|
|
263
485
|
repoRoot: repository.canonicalRoot,
|
|
264
|
-
controllerScoped:
|
|
486
|
+
controllerScoped: false,
|
|
265
487
|
});
|
|
266
488
|
const changed = !previous || fingerprintManifest(previous) !== fingerprintManifest(built);
|
|
267
489
|
return {
|
|
@@ -283,33 +505,126 @@ function writeRegistry(controllerHome: string, repoId: string, manifests: Assist
|
|
|
283
505
|
return index;
|
|
284
506
|
}
|
|
285
507
|
|
|
286
|
-
|
|
508
|
+
function claimsForPluginActionScope(
|
|
287
509
|
action: AssistantPluginActionDescriptor,
|
|
288
|
-
|
|
510
|
+
scope: PluginActionScopeContext,
|
|
289
511
|
trustedPluginId: string,
|
|
290
512
|
): ResourceClaimSpec[] {
|
|
291
|
-
const controllerScoped = repository.repoId === CONTROLLER_SCOPE_REPO_ID;
|
|
292
513
|
const pluginId = trustedPluginId.trim().replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
293
514
|
if (!pluginId) throw new Error('PLUGIN_ID_REQUIRED_FOR_RESOURCE_CLAIMS');
|
|
294
515
|
return action.resourceClaims.map((claim) => ({
|
|
295
516
|
resourceKey: claim.resource === 'remote' || claim.resource === 'provider-state'
|
|
296
|
-
// The manifest/plugin id was resolved from the trusted registration; a
|
|
297
|
-
// provider never supplies arbitrary resource-key text.
|
|
298
517
|
? `provider-state:${pluginId}`
|
|
299
|
-
:
|
|
518
|
+
: scope.kind === 'controller'
|
|
300
519
|
? `controller-system:${claim.resource}`
|
|
301
520
|
: claim.resource === 'workspace'
|
|
302
|
-
? `workspace:${
|
|
521
|
+
? `workspace:${scope.checkoutId}`
|
|
303
522
|
: claim.resource === 'git-refs'
|
|
304
|
-
? `git-refs:${
|
|
305
|
-
: `repo-state:${
|
|
523
|
+
? `git-refs:${scope.scopeKey}`
|
|
524
|
+
: `repo-state:${scope.scopeKey}`,
|
|
306
525
|
mode: claim.mode,
|
|
307
526
|
}));
|
|
308
527
|
}
|
|
309
528
|
|
|
310
|
-
function
|
|
529
|
+
export function claimsForAssistantPluginAction(
|
|
530
|
+
action: AssistantPluginActionDescriptor,
|
|
531
|
+
repository: RepositoryRecord,
|
|
532
|
+
trustedPluginId: string,
|
|
533
|
+
): ResourceClaimSpec[] {
|
|
534
|
+
return claimsForPluginActionScope(action, repositoryPluginActionScope(repository), trustedPluginId);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function withAssistantPluginResourceLeases<T>(
|
|
538
|
+
controllerHome: string,
|
|
539
|
+
scope: PluginActionScopeContext,
|
|
540
|
+
action: AssistantPluginActionDescriptor,
|
|
541
|
+
request: AssistantPluginActionRequest,
|
|
542
|
+
operation: () => Promise<T>,
|
|
543
|
+
): Promise<T> {
|
|
544
|
+
const claims = claimsForPluginActionScope(action, scope, request.pluginId).map((claim) => ({
|
|
545
|
+
...claim,
|
|
546
|
+
repoId: scope.scopeKey,
|
|
547
|
+
...(scope.checkoutId ? { checkoutId: scope.checkoutId } : {}),
|
|
548
|
+
}));
|
|
549
|
+
if (claims.length === 0) return operation();
|
|
550
|
+
|
|
551
|
+
// requestId is the semantic idempotency key, but before its receipt/index is
|
|
552
|
+
// persisted two concurrent replays can enter this function. Give each live
|
|
553
|
+
// invocation a distinct lease owner so the later replay contends on the
|
|
554
|
+
// first invocation's declared resource instead of silently accumulating a
|
|
555
|
+
// second same-owner lease and then failing exact release with LEASE_SET_MISMATCH.
|
|
556
|
+
const ownerJobId = `plugin:${request.requestId}:${randomUUID()}`;
|
|
557
|
+
const timeoutMs = Math.max(5_000, Math.min(10 * 60_000, request.timeoutMs ?? action.defaultTimeoutMs));
|
|
558
|
+
const acquisition = acquireExecutionLeases(controllerHome, scope.scopeKey, ownerJobId, claims, {
|
|
559
|
+
ttlMs: timeoutMs + 60_000,
|
|
560
|
+
// This lease exists only to fence concurrent invocations of one external
|
|
561
|
+
// effect. It is not Scheduler/recovery authority, so derived projection,
|
|
562
|
+
// event-ledger, and wake side effects must never participate in the effect
|
|
563
|
+
// outcome. The lease file + fencing token remain cross-process durable for
|
|
564
|
+
// the bounded invocation and expire if its process dies.
|
|
565
|
+
visibility: 'ephemeral',
|
|
566
|
+
ownerIdentity: {
|
|
567
|
+
repositoryId: scope.scopeKey,
|
|
568
|
+
checkoutId: scope.checkoutId ?? CONTROLLER_PLUGIN_SCOPE_KEY,
|
|
569
|
+
worktreeId: scope.checkoutId ?? CONTROLLER_PLUGIN_SCOPE_KEY,
|
|
570
|
+
branch: 'workflow-plugin-action',
|
|
571
|
+
principalId: request.origin.actor?.trim() || 'workflow-plugin-action',
|
|
572
|
+
controllerInstanceId: process.env.FORGE_RUNTIME_INSTANCE_ID?.trim()
|
|
573
|
+
|| process.env.FORGE_WRITER_INSTANCE_ID?.trim()
|
|
574
|
+
|| process.env.FORGE_DAEMON_INSTANCE_ID?.trim()
|
|
575
|
+
|| `process:${process.pid}`,
|
|
576
|
+
controllerGeneration: process.env.FORGE_RUNTIME_INCARNATION_GENERATION?.trim()
|
|
577
|
+
|| process.env.FORGE_WRITER_GENERATION?.trim()
|
|
578
|
+
|| process.env.FORGE_ACTIVE_RUNTIME_REVISION?.trim()
|
|
579
|
+
|| 'unbound',
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
if (!acquisition.acquired) {
|
|
583
|
+
throw new AssistantPluginError(
|
|
584
|
+
'PLUGIN_RESOURCE_CONTENTION',
|
|
585
|
+
`${request.pluginId}/${request.actionId} could not acquire its declared resource claims before dispatch.`,
|
|
586
|
+
{ retryable: true, details: { blockers: acquisition.blockers } },
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
const expected = acquisition.leases.map((lease) => ({
|
|
590
|
+
leaseId: lease.leaseId,
|
|
591
|
+
fencingToken: lease.fencingToken,
|
|
592
|
+
repoId: lease.repoId,
|
|
593
|
+
checkoutId: lease.checkoutId,
|
|
594
|
+
resourceKey: lease.resourceKey,
|
|
595
|
+
ownerIdentityDigest: lease.ownerIdentityDigest,
|
|
596
|
+
}));
|
|
597
|
+
let retainForReconciliation = false;
|
|
598
|
+
try {
|
|
599
|
+
const result = await operation();
|
|
600
|
+
const nestedResult = result && typeof result === 'object' && !Array.isArray(result)
|
|
601
|
+
? (result as Record<string, unknown>).result
|
|
602
|
+
: undefined;
|
|
603
|
+
retainForReconciliation = Boolean(nestedResult && typeof nestedResult === 'object' && !Array.isArray(nestedResult)
|
|
604
|
+
&& (nestedResult as Record<string, unknown>).outcome === 'outcome_unknown');
|
|
605
|
+
return result;
|
|
606
|
+
} catch (error) {
|
|
607
|
+
retainForReconciliation = isAssistantPluginError(error) && error.effectOutcome === 'outcome_unknown';
|
|
608
|
+
throw error;
|
|
609
|
+
} finally {
|
|
610
|
+
if (!retainForReconciliation) {
|
|
611
|
+
try {
|
|
612
|
+
releaseExactExecutionLeases(controllerHome, scope.scopeKey, ownerJobId, expected, { visibility: 'ephemeral' });
|
|
613
|
+
} catch (error) {
|
|
614
|
+
// A successful provider effect can rotate Runtime authority (for example
|
|
615
|
+
// a fenced Runtime cutover). The old sidecar must not release leases
|
|
616
|
+
// after it is fenced, but that cleanup denial must not overwrite the
|
|
617
|
+
// already-known provider result. The canonical successor Runtime can
|
|
618
|
+
// reclaim this ephemeral lease from its fenced owner immediately.
|
|
619
|
+
if (!isRuntimeWriteFenceError(error)) throw error;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function semanticKey(scope: PluginActionScopeContext, pluginId: string, actionId: string, args: Record<string, unknown>): string {
|
|
311
626
|
const digest = createHash('sha256').update(JSON.stringify(canonical(args))).digest('hex').slice(0, 20);
|
|
312
|
-
return `plugin-action:${
|
|
627
|
+
return `plugin-action:${scope.scopeKey}:${pluginId}:${actionId}:${digest}`;
|
|
313
628
|
}
|
|
314
629
|
|
|
315
630
|
function validatePrimitive(type: string, value: unknown): boolean {
|
|
@@ -408,6 +723,92 @@ function originMayEstablishCapabilityAuthorization(origin: AssistantPluginAction
|
|
|
408
723
|
return INTERACTIVE_PLUGIN_AUTHORIZATION_SURFACES.has(origin.surface);
|
|
409
724
|
}
|
|
410
725
|
|
|
726
|
+
function requiresAutomatedWriteAuthorization(origin: AssistantPluginActionExecutionInput['origin']): boolean {
|
|
727
|
+
return ['schedule', 'reconciliation', 'system'].includes(origin.surface);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const AUTHORIZATION_RISK_RANK: Record<AssistantPluginActionDescriptor['risk'], number> = {
|
|
731
|
+
readonly: 0,
|
|
732
|
+
workspace_write: 1,
|
|
733
|
+
remote_write: 2,
|
|
734
|
+
destructive: 3,
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
function authorizationTargetMatches(
|
|
738
|
+
left: PluginCapabilityAuthorizationGrant['target'],
|
|
739
|
+
right: PluginCapabilityAuthorizationGrant['target'],
|
|
740
|
+
): boolean {
|
|
741
|
+
return left.kind === right.kind
|
|
742
|
+
&& left.id === right.id
|
|
743
|
+
&& (left.identityFingerprint ?? '') === (right.identityFingerprint ?? '');
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async function resolveAutomatedWriteAuthorization(input: {
|
|
747
|
+
controllerHome: string;
|
|
748
|
+
scope: PluginActionScopeContext;
|
|
749
|
+
adapter: AssistantPluginAdapter;
|
|
750
|
+
manifest: AssistantPluginManifest;
|
|
751
|
+
action: AssistantPluginActionDescriptor;
|
|
752
|
+
args: Record<string, unknown>;
|
|
753
|
+
origin: AssistantPluginActionExecutionInput['origin'];
|
|
754
|
+
requestId: string;
|
|
755
|
+
jobId?: string;
|
|
756
|
+
authorizationGrantRefs?: readonly string[];
|
|
757
|
+
}): Promise<PluginCapabilityAuthorizationGrant | undefined> {
|
|
758
|
+
if (input.action.readOnly) return undefined;
|
|
759
|
+
if (input.action.confirmation === 'strong_confirmation') {
|
|
760
|
+
throw new AssistantPluginError(
|
|
761
|
+
'EXTERNAL_EFFECT_AUTHORIZATION_REQUIRED',
|
|
762
|
+
`${input.manifest.pluginId}/${input.action.actionId} requires an interactive strong confirmation.`,
|
|
763
|
+
{ retryable: false },
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const refs = [...new Set((input.authorizationGrantRefs ?? []).map((ref) => ref.trim()).filter(Boolean))];
|
|
767
|
+
if (refs.length === 0 || !input.adapter.resolveAuthorizationContext) {
|
|
768
|
+
throw new AssistantPluginError(
|
|
769
|
+
'EXTERNAL_EFFECT_AUTHORIZATION_REQUIRED',
|
|
770
|
+
`${input.manifest.pluginId}/${input.action.actionId} requires an active Workflow capability grant.`,
|
|
771
|
+
{ retryable: false },
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
const targetContext = await input.adapter.resolveAuthorizationContext({
|
|
775
|
+
controllerHome: input.controllerHome,
|
|
776
|
+
...pluginActionExecutionFields(input.scope),
|
|
777
|
+
pluginId: input.manifest.pluginId,
|
|
778
|
+
actionId: input.action.actionId,
|
|
779
|
+
requestId: input.requestId,
|
|
780
|
+
args: input.args,
|
|
781
|
+
origin: input.origin,
|
|
782
|
+
jobId: input.jobId,
|
|
783
|
+
authorizationGrantRefs: refs,
|
|
784
|
+
});
|
|
785
|
+
if (!targetContext) {
|
|
786
|
+
throw new AssistantPluginError(
|
|
787
|
+
'PLUGIN_CAPABILITY_GRANT_TARGET_UNAVAILABLE',
|
|
788
|
+
`${input.manifest.pluginId}/${input.action.actionId} did not resolve an exact authorization target.`,
|
|
789
|
+
{ retryable: false },
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
const capabilityId = reusableCapabilityId(input.manifest, input.action);
|
|
793
|
+
const grant = refs
|
|
794
|
+
.map((ref) => findActivePluginCapabilityAuthorizationById(input.controllerHome, ref))
|
|
795
|
+
.find((candidate) => candidate
|
|
796
|
+
&& candidate.repoId === input.scope.grantRepoId
|
|
797
|
+
&& candidate.pluginId === input.manifest.pluginId
|
|
798
|
+
&& candidate.capabilityId === capabilityId
|
|
799
|
+
&& authorizationTargetMatches(candidate.target, targetContext.target)
|
|
800
|
+
&& input.action.scopes.every((scope) => candidate.scopes.includes(scope))
|
|
801
|
+
&& AUTHORIZATION_RISK_RANK[candidate.riskCeiling] >= AUTHORIZATION_RISK_RANK[input.action.risk]);
|
|
802
|
+
if (!grant) {
|
|
803
|
+
throw new AssistantPluginError(
|
|
804
|
+
'PLUGIN_CAPABILITY_GRANT_NOT_FOUND',
|
|
805
|
+
`${input.manifest.pluginId}/${input.action.actionId} has no active grant for this exact target.`,
|
|
806
|
+
{ retryable: false },
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return grant;
|
|
810
|
+
}
|
|
811
|
+
|
|
411
812
|
function authorizationTargetSummary(context: AssistantPluginAuthorizationContext): { kind: string; id: string } {
|
|
412
813
|
return { kind: context.target.kind, id: context.target.id };
|
|
413
814
|
}
|
|
@@ -429,19 +830,15 @@ function initialAuthorizationEvidence(
|
|
|
429
830
|
return { source: 'host_permission_model', reusable: false, capabilityId };
|
|
430
831
|
}
|
|
431
832
|
|
|
432
|
-
function denyAutomatedWrite(manifest: AssistantPluginManifest, action: AssistantPluginActionDescriptor, origin: AssistantPluginActionExecutionInput['origin']): void {
|
|
433
|
-
if (!['schedule', 'reconciliation', 'system'].includes(origin.surface)) return;
|
|
434
|
-
if (action.readOnly) return;
|
|
435
|
-
throw new Error(`EXTERNAL_EFFECT_AUTHORIZATION_REQUIRED: ${manifest.pluginId}/${action.actionId} cannot run from ${origin.surface}`);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
833
|
export type ListAssistantPluginManifestsOptions = {
|
|
439
834
|
/**
|
|
440
835
|
* Prefer previously persisted manifests for connector/status hot paths.
|
|
441
836
|
* Live adapter rebuild (including host probes such as Xcode) runs only when
|
|
442
|
-
* no stored manifest exists, or when forceRefresh is true.
|
|
837
|
+
* no stored manifest exists, unless fallbackToLive=false, or when forceRefresh is true.
|
|
443
838
|
*/
|
|
444
839
|
preferStored?: boolean;
|
|
840
|
+
/** Materialized projections set this false so a cold summary never synchronously probes providers. */
|
|
841
|
+
fallbackToLive?: boolean;
|
|
445
842
|
forceRefresh?: boolean;
|
|
446
843
|
};
|
|
447
844
|
|
|
@@ -458,7 +855,8 @@ export function listAssistantPluginManifests(
|
|
|
458
855
|
options: ListAssistantPluginManifestsOptions = {},
|
|
459
856
|
): AssistantPluginManifest[] {
|
|
460
857
|
const preferStored = options.preferStored === true && options.forceRefresh !== true;
|
|
461
|
-
const
|
|
858
|
+
const fallbackToLive = options.fallbackToLive !== false || !preferStored;
|
|
859
|
+
const cacheKey = listCacheKey(controllerHome, repository.repoId, preferStored, fallbackToLive);
|
|
462
860
|
if (options.forceRefresh !== true) {
|
|
463
861
|
// Stored manifests are persisted snapshots. Their freshness is governed by
|
|
464
862
|
// explicit registry/config mutation invalidation, not a wall-clock trust
|
|
@@ -475,9 +873,11 @@ export function listAssistantPluginManifests(
|
|
|
475
873
|
if (preferStored) {
|
|
476
874
|
const stored = readStoredManifest(controllerHome, repository.repoId, pluginId);
|
|
477
875
|
if (stored) return stored;
|
|
876
|
+
if (!fallbackToLive) return undefined;
|
|
478
877
|
}
|
|
479
878
|
return computeManifest(controllerHome, repository, pluginId);
|
|
480
879
|
})
|
|
880
|
+
.filter((manifest): manifest is AssistantPluginManifest => Boolean(manifest))
|
|
481
881
|
.sort((left, right) => left.pluginId.localeCompare(right.pluginId));
|
|
482
882
|
primePluginManifestItemCache(controllerHome, repository.repoId, manifests, preferStored);
|
|
483
883
|
return writePluginManifestCache(pluginManifestListCache, cacheKey, manifests);
|
|
@@ -533,7 +933,7 @@ export function syncAssistantPluginRegistry(
|
|
|
533
933
|
};
|
|
534
934
|
}
|
|
535
935
|
|
|
536
|
-
function syncAssistantPluginManifest(
|
|
936
|
+
export function syncAssistantPluginManifest(
|
|
537
937
|
controllerHome: string,
|
|
538
938
|
repository: RepositoryRecord,
|
|
539
939
|
pluginId: string,
|
|
@@ -558,19 +958,144 @@ function syncAssistantPluginManifest(
|
|
|
558
958
|
};
|
|
559
959
|
}
|
|
560
960
|
|
|
961
|
+
/** Remove one persisted plugin projection without probing or rebuilding unrelated providers. */
|
|
962
|
+
export function removeAssistantPluginManifestProjection(
|
|
963
|
+
controllerHome: string,
|
|
964
|
+
repository: RepositoryRecord,
|
|
965
|
+
pluginId: string,
|
|
966
|
+
): AssistantPluginRegistryIndex {
|
|
967
|
+
invalidateAssistantPluginManifestCache(controllerHome, repository.repoId, pluginId);
|
|
968
|
+
rmSync(manifestPath(controllerHome, repository.repoId, pluginId), { force: true });
|
|
969
|
+
const current = readJsonFile<AssistantPluginRegistryIndex>(indexPath(controllerHome, repository.repoId), {
|
|
970
|
+
schemaVersion: 1,
|
|
971
|
+
updatedAt: now(),
|
|
972
|
+
plugins: [],
|
|
973
|
+
});
|
|
974
|
+
const next: AssistantPluginRegistryIndex = {
|
|
975
|
+
...current,
|
|
976
|
+
updatedAt: now(),
|
|
977
|
+
plugins: current.plugins.filter((entry) => entry.pluginId !== pluginId),
|
|
978
|
+
};
|
|
979
|
+
writeJsonAtomic(indexPath(controllerHome, repository.repoId), next);
|
|
980
|
+
markControllerContextProjectionDirty(repository.canonicalRoot, `plugin:${pluginId}:removed`);
|
|
981
|
+
return next;
|
|
982
|
+
}
|
|
983
|
+
|
|
561
984
|
export function isDirectPluginReadAction(action: AssistantPluginActionDescriptor): boolean {
|
|
562
|
-
return action.
|
|
985
|
+
return action.executionMode !== 'direct_non_persistent'
|
|
986
|
+
&& action.readOnly === true
|
|
563
987
|
&& action.risk === 'readonly'
|
|
564
988
|
&& action.confirmation === 'none'
|
|
565
989
|
&& action.idempotent === true;
|
|
566
990
|
}
|
|
567
991
|
|
|
568
|
-
export
|
|
992
|
+
export function isDirectNonPersistentPluginAction(action: AssistantPluginActionDescriptor): boolean {
|
|
993
|
+
if (
|
|
994
|
+
action.executionMode !== 'direct_non_persistent'
|
|
995
|
+
|| action.remoteEffectWorkCompletion !== undefined
|
|
996
|
+
|| action.resourceClaims.length !== 0
|
|
997
|
+
) {
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// Direct non-persistent actions are provider-local operations that must never
|
|
1002
|
+
// enter the durable effect/receipt path. They are intentionally limited to
|
|
1003
|
+
// either a pure provider read or a provider-local workspace effect. Remote
|
|
1004
|
+
// writes/destructive effects still require the normal durable application
|
|
1005
|
+
// path and its reconciliation authority.
|
|
1006
|
+
if (action.readOnly) {
|
|
1007
|
+
return action.risk === 'readonly'
|
|
1008
|
+
&& (action.confirmation === 'none' || action.confirmation === 'authorization')
|
|
1009
|
+
&& action.idempotent === true;
|
|
1010
|
+
}
|
|
1011
|
+
return action.risk === 'workspace_write'
|
|
1012
|
+
&& (action.confirmation === 'authorization' || action.confirmation === 'none');
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async function executePluginDirectNonPersistentInScope(
|
|
569
1016
|
controllerHome: string,
|
|
570
|
-
|
|
1017
|
+
scope: PluginActionScopeContext,
|
|
571
1018
|
request: AssistantPluginActionRequest,
|
|
572
1019
|
): Promise<{ manifest: AssistantPluginManifest; action: AssistantPluginActionDescriptor; result: Record<string, unknown> }> {
|
|
573
|
-
const
|
|
1020
|
+
const adapter = resolvePluginAdapter(controllerHome, request.pluginId);
|
|
1021
|
+
if (!adapter) throw new Error(`PLUGIN_NOT_FOUND: ${request.pluginId}`);
|
|
1022
|
+
if (PLUGIN_ADAPTERS.get(request.pluginId) !== adapter) {
|
|
1023
|
+
throw new Error(`PLUGIN_DIRECT_NON_PERSISTENT_FIRST_PARTY_REQUIRED: ${request.pluginId}/${request.actionId}`);
|
|
1024
|
+
}
|
|
1025
|
+
const manifestLookup = scope.kind === 'controller'
|
|
1026
|
+
? getControllerPluginManifestForExecution(controllerHome, request.pluginId, adapter)
|
|
1027
|
+
: getAssistantPluginManifestForExecution(controllerHome, scope.repository!, request.pluginId, adapter);
|
|
1028
|
+
const manifest = manifestLookup.manifest;
|
|
1029
|
+
const action = actionForManifest(manifest, request.actionId);
|
|
1030
|
+
if (!manifest.enabled && action.actionId !== 'configure') {
|
|
1031
|
+
throw new Error(`PLUGIN_DISABLED: ${request.pluginId} is disabled`);
|
|
1032
|
+
}
|
|
1033
|
+
if (action.executionMode !== 'direct_non_persistent') {
|
|
1034
|
+
throw new Error(`PLUGIN_DIRECT_NON_PERSISTENT_NOT_ALLOWED: ${request.pluginId}/${request.actionId}`);
|
|
1035
|
+
}
|
|
1036
|
+
if (!isDirectNonPersistentPluginAction(action)) {
|
|
1037
|
+
throw new Error(`PLUGIN_DIRECT_NON_PERSISTENT_CONTRACT_INVALID: ${request.pluginId}/${request.actionId}`);
|
|
1038
|
+
}
|
|
1039
|
+
if (request.workId || request.workRepoId) {
|
|
1040
|
+
throw new Error(`PLUGIN_DIRECT_NON_PERSISTENT_WORK_ATTRIBUTION_FORBIDDEN: ${request.pluginId}/${request.actionId}`);
|
|
1041
|
+
}
|
|
1042
|
+
if (action.confirmation === 'authorization' && request.confirmAuthorization !== true) {
|
|
1043
|
+
throw new Error(`PLUGIN_DIRECT_NON_PERSISTENT_AUTHORIZATION_REQUIRED: ${request.pluginId}/${request.actionId} requires confirm_authorization=true for this invocation`);
|
|
1044
|
+
}
|
|
1045
|
+
const normalizedArgs = validateActionArguments(action, request.args ?? {});
|
|
1046
|
+
enforceConfirmation(action, { ...request, args: normalizedArgs });
|
|
1047
|
+
if (requiresAutomatedWriteAuthorization(request.origin)) {
|
|
1048
|
+
await resolveAutomatedWriteAuthorization({
|
|
1049
|
+
controllerHome,
|
|
1050
|
+
scope,
|
|
1051
|
+
adapter,
|
|
1052
|
+
manifest,
|
|
1053
|
+
action,
|
|
1054
|
+
args: normalizedArgs,
|
|
1055
|
+
origin: request.origin,
|
|
1056
|
+
requestId: request.requestId,
|
|
1057
|
+
authorizationGrantRefs: request.authorizationGrantRefs,
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
const result = await adapter.executeAction({
|
|
1061
|
+
controllerHome,
|
|
1062
|
+
...pluginActionExecutionFields(scope),
|
|
1063
|
+
pluginId: request.pluginId,
|
|
1064
|
+
actionId: request.actionId,
|
|
1065
|
+
requestId: request.requestId,
|
|
1066
|
+
args: normalizedArgs,
|
|
1067
|
+
origin: request.origin,
|
|
1068
|
+
timeoutMs: request.timeoutMs,
|
|
1069
|
+
signal: request.signal,
|
|
1070
|
+
authorizationGrantRefs: request.authorizationGrantRefs,
|
|
1071
|
+
providerIdentityPrevalidated: manifestLookup.providerIdentityPrevalidated,
|
|
1072
|
+
});
|
|
1073
|
+
return { manifest, action, result };
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
export function executeAssistantPluginDirectNonPersistent(
|
|
1077
|
+
controllerHome: string,
|
|
1078
|
+
repository: RepositoryRecord,
|
|
1079
|
+
request: AssistantPluginActionRequest,
|
|
1080
|
+
) {
|
|
1081
|
+
return executePluginDirectNonPersistentInScope(controllerHome, repositoryPluginActionScope(repository), request);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
export function executeControllerPluginDirectNonPersistent(
|
|
1085
|
+
controllerHome: string,
|
|
1086
|
+
request: AssistantPluginActionRequest,
|
|
1087
|
+
) {
|
|
1088
|
+
return executePluginDirectNonPersistentInScope(controllerHome, controllerPluginActionScope(controllerHome), request);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
async function executePluginReadDirectInScope(
|
|
1092
|
+
controllerHome: string,
|
|
1093
|
+
scope: PluginActionScopeContext,
|
|
1094
|
+
request: AssistantPluginActionRequest,
|
|
1095
|
+
): Promise<{ manifest: AssistantPluginManifest; action: AssistantPluginActionDescriptor; result: Record<string, unknown>; receipt: PluginActionReceipt }> {
|
|
1096
|
+
const manifest = scope.kind === 'controller'
|
|
1097
|
+
? getControllerPluginManifest(controllerHome, request.pluginId)
|
|
1098
|
+
: getAssistantPluginManifest(controllerHome, scope.repository!, request.pluginId);
|
|
574
1099
|
const action = actionForManifest(manifest, request.actionId);
|
|
575
1100
|
if (!manifest.enabled && action.actionId !== 'configure') {
|
|
576
1101
|
throw new Error(`PLUGIN_DISABLED: ${request.pluginId} is disabled`);
|
|
@@ -580,53 +1105,110 @@ export async function executeAssistantPluginReadDirect(
|
|
|
580
1105
|
}
|
|
581
1106
|
const normalizedArgs = validateActionArguments(action, request.args ?? {});
|
|
582
1107
|
enforceConfirmation(action, { ...request, args: normalizedArgs });
|
|
583
|
-
const result = await
|
|
1108
|
+
const result = await executePluginActionInScope(scope, {
|
|
584
1109
|
controllerHome,
|
|
585
|
-
|
|
586
|
-
repoRoot: repository.canonicalRoot,
|
|
1110
|
+
...pluginActionExecutionFields(scope),
|
|
587
1111
|
pluginId: request.pluginId,
|
|
588
1112
|
actionId: request.actionId,
|
|
589
1113
|
requestId: request.requestId,
|
|
590
1114
|
args: normalizedArgs,
|
|
591
1115
|
origin: request.origin,
|
|
592
1116
|
});
|
|
593
|
-
|
|
1117
|
+
const createdAt = new Date().toISOString();
|
|
1118
|
+
const resultDigest = createHash('sha256').update(JSON.stringify(result)).digest('hex');
|
|
1119
|
+
const observationSemanticKey = semanticKey(scope, request.pluginId, request.actionId, normalizedArgs);
|
|
1120
|
+
const observationActor = request.origin?.actor?.trim() ?? '';
|
|
1121
|
+
// Observation evidence is content-addressed so repeated identical reads do not create
|
|
1122
|
+
// an unbounded receipt trail. This remains provenance only, never replay/effect state.
|
|
1123
|
+
const receiptId = `PLG-OBS-${createHash('sha256')
|
|
1124
|
+
.update(`${observationSemanticKey}:${resultDigest}:${observationActor}`)
|
|
1125
|
+
.digest('hex').slice(0, 24)}`;
|
|
1126
|
+
const receipt: PluginActionReceipt = {
|
|
1127
|
+
schemaVersion: 1,
|
|
1128
|
+
receiptId,
|
|
1129
|
+
requestId: request.requestId,
|
|
1130
|
+
scopeKey: scope.scopeKey,
|
|
1131
|
+
...(scope.repository ? { repoId: scope.repository.repoId } : {}),
|
|
1132
|
+
pluginId: request.pluginId,
|
|
1133
|
+
actionId: request.actionId,
|
|
1134
|
+
semanticKey: observationSemanticKey,
|
|
1135
|
+
status: 'succeeded',
|
|
1136
|
+
createdAt,
|
|
1137
|
+
...(request.origin ? { origin: request.origin } : {}),
|
|
1138
|
+
observationOnly: true,
|
|
1139
|
+
resultDigest,
|
|
1140
|
+
};
|
|
1141
|
+
// Direct reads stay on the inline fast path. Persist only a compact observation
|
|
1142
|
+
// receipt, never provider result content and never request replay state.
|
|
1143
|
+
writeJsonAtomic(pluginActionReceiptPath(controllerHome, scope.scopeKey, receiptId), receipt);
|
|
1144
|
+
return { manifest, action, result, receipt };
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
export function executeAssistantPluginReadDirect(
|
|
1148
|
+
controllerHome: string,
|
|
1149
|
+
repository: RepositoryRecord,
|
|
1150
|
+
request: AssistantPluginActionRequest,
|
|
1151
|
+
) {
|
|
1152
|
+
return executePluginReadDirectInScope(controllerHome, repositoryPluginActionScope(repository), request);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
export function executeControllerPluginReadDirect(
|
|
1156
|
+
controllerHome: string,
|
|
1157
|
+
request: AssistantPluginActionRequest,
|
|
1158
|
+
) {
|
|
1159
|
+
return executePluginReadDirectInScope(controllerHome, controllerPluginActionScope(controllerHome), request);
|
|
594
1160
|
}
|
|
595
1161
|
|
|
596
1162
|
export interface PluginActionReceipt {
|
|
597
1163
|
schemaVersion: 1;
|
|
598
1164
|
receiptId: string;
|
|
599
1165
|
requestId: string;
|
|
600
|
-
|
|
1166
|
+
/** Canonical plugin-action receipt scope: `instance` for controller plugins, repository id otherwise. */
|
|
1167
|
+
scopeKey: string;
|
|
1168
|
+
/** Repository provenance only; absent for controller-scoped plugin actions. */
|
|
1169
|
+
repoId?: string;
|
|
601
1170
|
/** Work authority repository when it differs from controller/provider receipt scope. */
|
|
602
1171
|
workRepoId?: string;
|
|
603
1172
|
pluginId: string;
|
|
604
1173
|
actionId: string;
|
|
605
1174
|
semanticKey: string;
|
|
606
1175
|
status: 'succeeded' | 'failed';
|
|
1176
|
+
/** Durable semantic disposition for an attempted external/local effect. */
|
|
1177
|
+
effectOutcome?: AssistantPluginEffectOutcome;
|
|
607
1178
|
createdAt: string;
|
|
608
1179
|
workId?: string;
|
|
609
1180
|
origin?: { surface: string; actor?: string; correlationId?: string };
|
|
610
1181
|
authorization?: PluginActionAuthorizationEvidence;
|
|
611
1182
|
result?: Record<string, unknown>;
|
|
612
1183
|
error?: { code: string; message: string };
|
|
1184
|
+
/** True for a compact direct-read observation; it is evidence, never an effect-completion authority. */
|
|
1185
|
+
observationOnly?: boolean;
|
|
1186
|
+
/** SHA-256 of the direct-read result. Result content is intentionally not persisted for observation-only receipts. */
|
|
1187
|
+
resultDigest?: string;
|
|
613
1188
|
}
|
|
614
1189
|
|
|
615
1190
|
interface PluginActionRequestIndex {
|
|
616
1191
|
requestId: string;
|
|
617
|
-
|
|
1192
|
+
scopeKey: string;
|
|
1193
|
+
repoId?: string;
|
|
618
1194
|
workRepoId?: string;
|
|
619
1195
|
receiptId: string;
|
|
620
1196
|
semanticKey: string;
|
|
621
1197
|
createdAt: string;
|
|
622
1198
|
}
|
|
623
1199
|
|
|
624
|
-
function pluginActionReceiptRoot(controllerHome: string,
|
|
625
|
-
const root =
|
|
1200
|
+
function pluginActionReceiptRoot(controllerHome: string, scopeKey: string): string {
|
|
1201
|
+
const root = scopeKey === CONTROLLER_PLUGIN_SCOPE_KEY
|
|
1202
|
+
? join(controllerSystemRoot(controllerHome), 'plugin-action-receipts')
|
|
1203
|
+
: join(repositoryControllerRoot(controllerHome, scopeKey), 'plugin-action-receipts');
|
|
626
1204
|
mkdirSync(root, { recursive: true });
|
|
627
1205
|
return root;
|
|
628
1206
|
}
|
|
629
1207
|
|
|
1208
|
+
function legacyControllerPluginActionReceiptPath(controllerHome: string, receiptId: string): string {
|
|
1209
|
+
return join(repositoryControllerRoot(controllerHome, CONTROLLER_SCOPE_REPO_ID), 'plugin-action-receipts', `${sanitizeFileComponent(receiptId)}.json`);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
630
1212
|
function pluginActionRequestPath(controllerHome: string, requestId: string): string {
|
|
631
1213
|
const hash = createHash('sha256').update(requestId).digest('hex');
|
|
632
1214
|
const root = join(ensureControllerHome(controllerHome), 'indexes', 'plugin-actions', 'requests');
|
|
@@ -634,14 +1216,30 @@ function pluginActionRequestPath(controllerHome: string, requestId: string): str
|
|
|
634
1216
|
return join(root, `${hash}.json`);
|
|
635
1217
|
}
|
|
636
1218
|
|
|
637
|
-
function pluginActionReceiptPath(controllerHome: string,
|
|
638
|
-
return join(pluginActionReceiptRoot(controllerHome,
|
|
1219
|
+
function pluginActionReceiptPath(controllerHome: string, scopeKey: string, receiptId: string): string {
|
|
1220
|
+
return join(pluginActionReceiptRoot(controllerHome, scopeKey), `${sanitizeFileComponent(receiptId)}.json`);
|
|
639
1221
|
}
|
|
640
1222
|
|
|
641
|
-
function
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
1223
|
+
function normalizePluginActionReceipt(receipt: PluginActionReceipt): PluginActionReceipt {
|
|
1224
|
+
const scopeKey = receipt.scopeKey?.trim()
|
|
1225
|
+
|| (receipt.repoId === CONTROLLER_SCOPE_REPO_ID ? CONTROLLER_PLUGIN_SCOPE_KEY : receipt.repoId?.trim())
|
|
1226
|
+
|| '';
|
|
1227
|
+
if (!scopeKey) throw new Error('PLUGIN_RECEIPT_SCOPE_CORRUPT');
|
|
1228
|
+
return {
|
|
1229
|
+
...receipt,
|
|
1230
|
+
scopeKey,
|
|
1231
|
+
...(scopeKey === CONTROLLER_PLUGIN_SCOPE_KEY ? { repoId: undefined } : { repoId: receipt.repoId ?? scopeKey }),
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function readPluginActionReceipt(controllerHome: string, scopeKey: string, receiptId: string): PluginActionReceipt | undefined {
|
|
1236
|
+
const path = pluginActionReceiptPath(controllerHome, scopeKey, receiptId);
|
|
1237
|
+
if (existsSync(path)) return normalizePluginActionReceipt(readJsonFile<PluginActionReceipt>(path));
|
|
1238
|
+
if (scopeKey === CONTROLLER_PLUGIN_SCOPE_KEY || scopeKey === CONTROLLER_SCOPE_REPO_ID) {
|
|
1239
|
+
const legacyPath = legacyControllerPluginActionReceiptPath(controllerHome, receiptId);
|
|
1240
|
+
if (existsSync(legacyPath)) return normalizePluginActionReceipt(readJsonFile<PluginActionReceipt>(legacyPath));
|
|
1241
|
+
}
|
|
1242
|
+
return undefined;
|
|
645
1243
|
}
|
|
646
1244
|
|
|
647
1245
|
export function findPluginActionReceipt(
|
|
@@ -649,6 +1247,12 @@ export function findPluginActionReceipt(
|
|
|
649
1247
|
receiptId: string,
|
|
650
1248
|
): PluginActionReceipt | undefined {
|
|
651
1249
|
const home = ensureControllerHome(controllerHome);
|
|
1250
|
+
// Controller-scoped plugins (for example Local System) live under system/, not
|
|
1251
|
+
// repositories/. They are first-class evidence producers and must be searched too.
|
|
1252
|
+
const controllerReceipt = readPluginActionReceipt(home, CONTROLLER_PLUGIN_SCOPE_KEY, receiptId)
|
|
1253
|
+
// One-way read compatibility for pre-cutover controller receipts only.
|
|
1254
|
+
?? readPluginActionReceipt(home, CONTROLLER_SCOPE_REPO_ID, receiptId);
|
|
1255
|
+
if (controllerReceipt) return controllerReceipt;
|
|
652
1256
|
const repositoriesRoot = join(home, 'repositories');
|
|
653
1257
|
try {
|
|
654
1258
|
for (const repoId of readdirSync(repositoriesRoot)) {
|
|
@@ -661,6 +1265,19 @@ export function findPluginActionReceipt(
|
|
|
661
1265
|
return undefined;
|
|
662
1266
|
}
|
|
663
1267
|
|
|
1268
|
+
/** Exact request lookup for subordinate Workflow recovery; never dispatches an action. */
|
|
1269
|
+
export function readPluginActionReceiptForRequest(controllerHome: string, requestId: string): PluginActionReceipt | undefined {
|
|
1270
|
+
const path = pluginActionRequestPath(controllerHome, requestId);
|
|
1271
|
+
if (!existsSync(path)) return undefined;
|
|
1272
|
+
const index = readJsonFile<PluginActionRequestIndex>(path);
|
|
1273
|
+
if (index.requestId !== requestId) throw new Error('PLUGIN_RECEIPT_REQUEST_MISMATCH');
|
|
1274
|
+
const scopeKey = index.scopeKey?.trim() || index.repoId?.trim();
|
|
1275
|
+
if (!scopeKey) throw new Error('PLUGIN_RECEIPT_REQUEST_SCOPE_CORRUPT');
|
|
1276
|
+
const receipt = readPluginActionReceipt(controllerHome, scopeKey, index.receiptId);
|
|
1277
|
+
if (!receipt || receipt.requestId !== requestId || receipt.semanticKey !== index.semanticKey) throw new Error('PLUGIN_RECEIPT_LOST');
|
|
1278
|
+
return receipt;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
664
1281
|
export function compatibilityPluginJobFromReceipt(
|
|
665
1282
|
receipt: PluginActionReceipt,
|
|
666
1283
|
checkoutId = 'unknown',
|
|
@@ -668,86 +1285,20 @@ export function compatibilityPluginJobFromReceipt(
|
|
|
668
1285
|
return compatibilityJobFromReceipt(receipt, checkoutId);
|
|
669
1286
|
}
|
|
670
1287
|
|
|
671
|
-
|
|
672
|
-
'authorize_target',
|
|
673
|
-
'revoke_target',
|
|
674
|
-
'create_directory',
|
|
675
|
-
'write_text',
|
|
676
|
-
'delete_file',
|
|
677
|
-
'delete_empty_directory',
|
|
678
|
-
'initialize_git',
|
|
679
|
-
'execute_project_script',
|
|
680
|
-
'copy_file',
|
|
681
|
-
'move_file',
|
|
682
|
-
'rename_file',
|
|
683
|
-
'open_application',
|
|
684
|
-
'reveal_in_finder',
|
|
685
|
-
'open_file',
|
|
686
|
-
]);
|
|
687
|
-
|
|
688
|
-
function localSystemActionRequiresWork(
|
|
689
|
-
repository: RepositoryRecord,
|
|
690
|
-
pluginId: string,
|
|
691
|
-
actionId: string,
|
|
692
|
-
args: Record<string, unknown>,
|
|
693
|
-
): boolean {
|
|
694
|
-
if (repository.repoId !== CONTROLLER_SCOPE_REPO_ID || pluginId !== 'local_system') return false;
|
|
695
|
-
if (LOCAL_SYSTEM_MUTATION_ACTIONS.has(actionId)) return true;
|
|
696
|
-
if (actionId !== 'execute_command' || !Array.isArray(args.command)) return false;
|
|
697
|
-
return classifyRepositoryCommand(args.command.map(String)).risk !== 'readonly';
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
function workRiskForPluginAction(action: AssistantPluginActionDescriptor): WorkRisk {
|
|
701
|
-
if (action.risk === 'readonly') return 'readonly';
|
|
702
|
-
if (action.risk === 'workspace_write') return 'medium';
|
|
703
|
-
if (action.risk === 'remote_write') return 'high';
|
|
704
|
-
return 'destructive';
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
function localEffectTarget(
|
|
708
|
-
args: Record<string, unknown>,
|
|
709
|
-
result: Record<string, unknown>,
|
|
710
|
-
): LocalEffectCompletionReceipt['target'] {
|
|
711
|
-
const inner = result.result && typeof result.result === 'object' && !Array.isArray(result.result)
|
|
712
|
-
? result.result as Record<string, unknown>
|
|
713
|
-
: result;
|
|
714
|
-
const target = inner.target && typeof inner.target === 'object' && !Array.isArray(inner.target)
|
|
715
|
-
? inner.target as Record<string, unknown>
|
|
716
|
-
: undefined;
|
|
717
|
-
const id = String(
|
|
718
|
-
target?.workspaceId
|
|
719
|
-
?? inner.workspaceId
|
|
720
|
-
?? args.target_key
|
|
721
|
-
?? args.destination_target_key
|
|
722
|
-
?? args.source_target_key
|
|
723
|
-
?? 'controller-local',
|
|
724
|
-
);
|
|
725
|
-
const identityFingerprint = typeof target?.identityFingerprint === 'string'
|
|
726
|
-
? target.identityFingerprint
|
|
727
|
-
: typeof inner.identityFingerprint === 'string'
|
|
728
|
-
? inner.identityFingerprint
|
|
729
|
-
: undefined;
|
|
730
|
-
return {
|
|
731
|
-
kind: id.startsWith('workspace_') || args.target_key || args.destination_target_key || args.source_target_key
|
|
732
|
-
? 'workspace_target'
|
|
733
|
-
: 'controller_local',
|
|
734
|
-
id,
|
|
735
|
-
...(identityFingerprint ? { identityFingerprint } : {}),
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function workAttributionRepoId(repository: RepositoryRecord, request: AssistantPluginActionRequest): string {
|
|
1288
|
+
function workAttributionRepoId(scope: PluginActionScopeContext, request: AssistantPluginActionRequest): string {
|
|
740
1289
|
const explicit = request.workRepoId?.trim();
|
|
741
1290
|
if (!request.workId?.trim()) {
|
|
742
1291
|
if (explicit) throw new Error(`WORK_PLUGIN_ATTRIBUTION_REPO_WITHOUT_WORK: ${explicit}`);
|
|
743
|
-
return repository
|
|
1292
|
+
return scope.repository?.repoId ?? '';
|
|
744
1293
|
}
|
|
745
|
-
|
|
1294
|
+
if (explicit) return explicit;
|
|
1295
|
+
if (scope.repository) return scope.repository.repoId;
|
|
1296
|
+
throw new Error(`WORK_PLUGIN_ATTRIBUTION_REPO_REQUIRED: controller-scoped action ${request.pluginId}/${request.actionId} must name the Work repository explicitly`);
|
|
746
1297
|
}
|
|
747
1298
|
|
|
748
1299
|
function remoteEffectWorkForPluginAction(
|
|
749
1300
|
controllerHome: string,
|
|
750
|
-
|
|
1301
|
+
scope: PluginActionScopeContext,
|
|
751
1302
|
action: AssistantPluginActionDescriptor,
|
|
752
1303
|
request: AssistantPluginActionRequest,
|
|
753
1304
|
receiptId?: string,
|
|
@@ -757,7 +1308,7 @@ function remoteEffectWorkForPluginAction(
|
|
|
757
1308
|
if (action.risk !== 'remote_write') {
|
|
758
1309
|
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_REQUIRES_REMOTE_WRITE: ${action.actionId}`);
|
|
759
1310
|
}
|
|
760
|
-
const workRepoId = workAttributionRepoId(
|
|
1311
|
+
const workRepoId = workAttributionRepoId(scope, request);
|
|
761
1312
|
const work = getWorkContract({ controllerHome, repoId: workRepoId }, workId);
|
|
762
1313
|
if (!work) throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_NOT_FOUND: ${workId}`);
|
|
763
1314
|
if (work.workKind !== 'remote_effect') {
|
|
@@ -766,49 +1317,49 @@ function remoteEffectWorkForPluginAction(
|
|
|
766
1317
|
if (work.checks.length > 0) {
|
|
767
1318
|
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_CHECKS_PRESENT: ${workId} declares repository checks and cannot be completed by a plugin receipt alone`);
|
|
768
1319
|
}
|
|
769
|
-
if (
|
|
1320
|
+
if (isTerminalSemanticWorkState(work.semanticState)) {
|
|
770
1321
|
const receiptAlreadyBound = Boolean(receiptId) && (
|
|
771
1322
|
work.completionReceipt?.receiptId === receiptId
|
|
772
1323
|
|| work.evidenceRefs.some((evidence) => evidence.evidenceId === receiptId)
|
|
773
1324
|
);
|
|
774
|
-
if (work.
|
|
775
|
-
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_TERMINAL: ${workId} is ${work.
|
|
1325
|
+
if (work.semanticState === 'completed' && receiptAlreadyBound) return work;
|
|
1326
|
+
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_TERMINAL: ${workId} is ${work.semanticState}`);
|
|
776
1327
|
}
|
|
777
1328
|
return work;
|
|
778
1329
|
}
|
|
779
1330
|
|
|
780
1331
|
function attributedWorkForPluginAction(
|
|
781
1332
|
controllerHome: string,
|
|
782
|
-
|
|
1333
|
+
scope: PluginActionScopeContext,
|
|
783
1334
|
action: AssistantPluginActionDescriptor,
|
|
784
1335
|
request: AssistantPluginActionRequest,
|
|
785
1336
|
): WorkContract | undefined {
|
|
786
1337
|
const workId = request.workId?.trim();
|
|
787
1338
|
if (!workId || action.risk === 'remote_write') return undefined;
|
|
788
|
-
const workRepoId = workAttributionRepoId(
|
|
1339
|
+
const workRepoId = workAttributionRepoId(scope, request);
|
|
789
1340
|
const work = getWorkContract({ controllerHome, repoId: workRepoId }, workId);
|
|
790
1341
|
if (!work) throw new Error(`WORK_PLUGIN_ATTRIBUTION_NOT_FOUND: ${workRepoId}:${workId}`);
|
|
791
|
-
if (
|
|
792
|
-
throw new Error(`WORK_PLUGIN_ATTRIBUTION_TERMINAL: ${workId} is ${work.
|
|
1342
|
+
if (isTerminalSemanticWorkState(work.semanticState)) {
|
|
1343
|
+
throw new Error(`WORK_PLUGIN_ATTRIBUTION_TERMINAL: ${workId} is ${work.semanticState}`);
|
|
793
1344
|
}
|
|
794
1345
|
return work;
|
|
795
1346
|
}
|
|
796
1347
|
|
|
797
1348
|
function bindLocalEffectReceiptToAttributedWork(
|
|
798
1349
|
controllerHome: string,
|
|
799
|
-
|
|
1350
|
+
scope: PluginActionScopeContext,
|
|
800
1351
|
action: AssistantPluginActionDescriptor,
|
|
801
1352
|
request: AssistantPluginActionRequest,
|
|
802
1353
|
receipt: PluginActionReceipt,
|
|
803
1354
|
work?: WorkContract,
|
|
804
1355
|
): WorkContract | undefined {
|
|
805
1356
|
if (!request.workId || receipt.status !== 'succeeded') return work;
|
|
806
|
-
const workRepoId = workAttributionRepoId(
|
|
1357
|
+
const workRepoId = workAttributionRepoId(scope, request);
|
|
807
1358
|
const attributed = work ?? getWorkContract({ controllerHome, repoId: workRepoId }, request.workId.trim());
|
|
808
1359
|
if (!attributed) throw new Error(`WORK_PLUGIN_ATTRIBUTION_NOT_FOUND: ${workRepoId}:${request.workId.trim()}`);
|
|
809
1360
|
if (attributed.workKind !== 'local_effect' || action.readOnly || action.risk === 'readonly') return attributed;
|
|
810
1361
|
if (attributed.evidenceRefs.some((evidence) => evidence.evidenceId === receipt.receiptId)) return attributed;
|
|
811
|
-
if (
|
|
1362
|
+
if (isTerminalSemanticWorkState(attributed.semanticState)) return attributed;
|
|
812
1363
|
return appendWorkEvidence({ controllerHome, repoId: workRepoId }, attributed.workId, {
|
|
813
1364
|
evidenceId: receipt.receiptId,
|
|
814
1365
|
title: 'typed local plugin effect completed',
|
|
@@ -817,7 +1368,7 @@ function bindLocalEffectReceiptToAttributedWork(
|
|
|
817
1368
|
});
|
|
818
1369
|
}
|
|
819
1370
|
|
|
820
|
-
export function
|
|
1371
|
+
export function recordRemoteEffectWorkActionReceipt(
|
|
821
1372
|
controllerHome: string,
|
|
822
1373
|
repoId: string,
|
|
823
1374
|
workId: string,
|
|
@@ -827,14 +1378,12 @@ export function finalizeRemoteEffectWorkFromActionReceipt(
|
|
|
827
1378
|
if (work.workKind !== 'remote_effect') {
|
|
828
1379
|
throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_KIND_MISMATCH: ${workId} is ${work.workKind}, expected remote_effect`);
|
|
829
1380
|
}
|
|
830
|
-
if (work.
|
|
831
|
-
if (isTerminalWorkContractStatus(work.status)) {
|
|
832
|
-
throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_TERMINAL: ${workId} is ${work.status}`);
|
|
833
|
-
}
|
|
1381
|
+
if (work.completionReceipt?.source === 'remote_effect') return work;
|
|
834
1382
|
const receipt = work.evidenceRefs
|
|
835
1383
|
.map((evidence) => {
|
|
836
1384
|
if (!evidence.evidenceId) return undefined;
|
|
837
1385
|
return readPluginActionReceipt(controllerHome, repoId, evidence.evidenceId)
|
|
1386
|
+
?? readPluginActionReceipt(controllerHome, CONTROLLER_PLUGIN_SCOPE_KEY, evidence.evidenceId)
|
|
838
1387
|
?? readPluginActionReceipt(controllerHome, CONTROLLER_SCOPE_REPO_ID, evidence.evidenceId);
|
|
839
1388
|
})
|
|
840
1389
|
.find((candidate) => candidate?.workId === workId && candidate.status === 'succeeded');
|
|
@@ -864,17 +1413,17 @@ export function finalizeRemoteEffectWorkFromActionReceipt(
|
|
|
864
1413
|
|
|
865
1414
|
function bindRemoteEffectReceiptToWork(
|
|
866
1415
|
controllerHome: string,
|
|
867
|
-
|
|
1416
|
+
scope: PluginActionScopeContext,
|
|
868
1417
|
action: AssistantPluginActionDescriptor,
|
|
869
1418
|
request: AssistantPluginActionRequest,
|
|
870
1419
|
receipt: PluginActionReceipt,
|
|
871
1420
|
): WorkContract | undefined {
|
|
872
1421
|
if (!request.workId) return undefined;
|
|
873
|
-
if (receipt.status !== 'succeeded') return remoteEffectWorkForPluginAction(controllerHome,
|
|
874
|
-
const work = remoteEffectWorkForPluginAction(controllerHome,
|
|
1422
|
+
if (receipt.status !== 'succeeded') return remoteEffectWorkForPluginAction(controllerHome, scope, action, request);
|
|
1423
|
+
const work = remoteEffectWorkForPluginAction(controllerHome, scope, action, request, receipt.receiptId);
|
|
875
1424
|
if (!work) return undefined;
|
|
876
|
-
if (work.
|
|
877
|
-
const workRepoId = workAttributionRepoId(
|
|
1425
|
+
if (work.completionReceipt?.source === 'remote_effect') return work;
|
|
1426
|
+
const workRepoId = workAttributionRepoId(scope, request);
|
|
878
1427
|
let updated = work;
|
|
879
1428
|
if (!work.evidenceRefs.some((evidence) => evidence.evidenceId === receipt.receiptId)) {
|
|
880
1429
|
updated = appendWorkEvidence({ controllerHome, repoId: workRepoId }, work.workId, {
|
|
@@ -911,10 +1460,12 @@ function compatibilityJobFromReceipt(receipt: PluginActionReceipt, checkoutId: s
|
|
|
911
1460
|
schemaVersion: 1,
|
|
912
1461
|
revision: 1,
|
|
913
1462
|
jobId: receipt.receiptId,
|
|
914
|
-
repoId: receipt.repoId,
|
|
1463
|
+
repoId: receipt.repoId ?? receipt.scopeKey,
|
|
915
1464
|
checkoutId,
|
|
916
1465
|
type: 'plugin-action',
|
|
917
|
-
status: receipt.
|
|
1466
|
+
status: receipt.effectOutcome === 'outcome_unknown'
|
|
1467
|
+
? 'human_attention_required'
|
|
1468
|
+
: receipt.status === 'succeeded' ? 'succeeded' : 'failed',
|
|
918
1469
|
priority: 'P2',
|
|
919
1470
|
requestId: receipt.requestId,
|
|
920
1471
|
semanticKey: receipt.semanticKey,
|
|
@@ -941,9 +1492,9 @@ function compatibilityJobFromReceipt(receipt: PluginActionReceipt, checkoutId: s
|
|
|
941
1492
|
* Deterministic plugin action path: validate → authorize/confirm → invoke adapter → receipt.
|
|
942
1493
|
* Does not create ExecutionJobs or call a model.
|
|
943
1494
|
*/
|
|
944
|
-
|
|
1495
|
+
async function submitPluginAction(
|
|
945
1496
|
controllerHome: string,
|
|
946
|
-
|
|
1497
|
+
scope: PluginActionScopeContext,
|
|
947
1498
|
request: AssistantPluginActionRequest,
|
|
948
1499
|
): Promise<{
|
|
949
1500
|
manifest: AssistantPluginManifest;
|
|
@@ -957,44 +1508,51 @@ export async function submitAssistantPluginAction(
|
|
|
957
1508
|
}> {
|
|
958
1509
|
const adapter = resolvePluginAdapter(controllerHome, request.pluginId);
|
|
959
1510
|
if (!adapter) throw new Error(`PLUGIN_NOT_FOUND: ${request.pluginId}`);
|
|
960
|
-
const manifest =
|
|
1511
|
+
const manifest = scope.kind === 'controller'
|
|
1512
|
+
? getControllerPluginManifest(controllerHome, request.pluginId)
|
|
1513
|
+
: getAssistantPluginManifest(controllerHome, scope.repository!, request.pluginId);
|
|
961
1514
|
const action = actionForManifest(manifest, request.actionId);
|
|
962
1515
|
if (!manifest.enabled && action.actionId !== 'configure') {
|
|
963
1516
|
throw new Error(`PLUGIN_DISABLED: ${request.pluginId} is disabled`);
|
|
964
1517
|
}
|
|
1518
|
+
if (action.executionMode === 'direct_non_persistent') {
|
|
1519
|
+
throw new Error(`PLUGIN_DIRECT_NON_PERSISTENT_RECEIPT_PATH_FORBIDDEN: ${request.pluginId}/${request.actionId}`);
|
|
1520
|
+
}
|
|
965
1521
|
const normalizedArgs = validateActionArguments(action, request.args ?? {});
|
|
966
1522
|
enforceConfirmation(action, { ...request, args: normalizedArgs });
|
|
967
1523
|
|
|
968
|
-
const key = semanticKey(
|
|
1524
|
+
const key = semanticKey(scope, request.pluginId, request.actionId, normalizedArgs);
|
|
969
1525
|
const requestPath = pluginActionRequestPath(controllerHome, request.requestId);
|
|
970
1526
|
if (existsSync(requestPath)) {
|
|
971
1527
|
const index = readJsonFile<PluginActionRequestIndex>(requestPath);
|
|
972
1528
|
if (index.semanticKey !== key) {
|
|
973
1529
|
throw new Error(`REQUEST_ID_CONFLICT: ${request.requestId} already belongs to ${index.semanticKey}`);
|
|
974
1530
|
}
|
|
975
|
-
|
|
976
|
-
|
|
1531
|
+
const existingScopeKey = index.scopeKey?.trim() || index.repoId?.trim();
|
|
1532
|
+
if (!existingScopeKey) throw new Error(`PLUGIN_RECEIPT_REQUEST_SCOPE_CORRUPT: ${request.requestId}`);
|
|
1533
|
+
if (existingScopeKey !== scope.scopeKey) {
|
|
1534
|
+
throw new Error(`REQUEST_ID_SCOPE_CONFLICT: ${request.requestId} already belongs to ${existingScopeKey}`);
|
|
977
1535
|
}
|
|
978
|
-
const existing = readPluginActionReceipt(controllerHome,
|
|
1536
|
+
const existing = readPluginActionReceipt(controllerHome, existingScopeKey, index.receiptId);
|
|
979
1537
|
if (!existing) {
|
|
980
1538
|
throw new Error(`PLUGIN_RECEIPT_LOST: ${request.requestId}`);
|
|
981
1539
|
}
|
|
982
1540
|
if (request.workId && existing.workId !== request.workId) {
|
|
983
1541
|
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_CONFLICT: ${request.requestId} already belongs to Work ${existing.workId ?? 'none'}`);
|
|
984
1542
|
}
|
|
985
|
-
const requestedWorkRepoId = request.workId ? workAttributionRepoId(
|
|
986
|
-
if (requestedWorkRepoId &&
|
|
987
|
-
throw new Error(`WORK_PLUGIN_ATTRIBUTION_REPO_CONFLICT: ${request.requestId} already belongs to Work repository ${index.workRepoId ??
|
|
1543
|
+
const requestedWorkRepoId = request.workId ? workAttributionRepoId(scope, request) : undefined;
|
|
1544
|
+
if (requestedWorkRepoId && index.workRepoId !== requestedWorkRepoId) {
|
|
1545
|
+
throw new Error(`WORK_PLUGIN_ATTRIBUTION_REPO_CONFLICT: ${request.requestId} already belongs to Work repository ${index.workRepoId ?? 'none'}`);
|
|
988
1546
|
}
|
|
989
1547
|
if (request.workId && action.risk === 'remote_write') {
|
|
990
|
-
bindRemoteEffectReceiptToWork(controllerHome,
|
|
1548
|
+
bindRemoteEffectReceiptToWork(controllerHome, scope, action, request, existing);
|
|
991
1549
|
} else if (request.workId) {
|
|
992
|
-
bindLocalEffectReceiptToAttributedWork(controllerHome,
|
|
1550
|
+
bindLocalEffectReceiptToAttributedWork(controllerHome, scope, action, request, existing);
|
|
993
1551
|
}
|
|
994
1552
|
return {
|
|
995
1553
|
manifest,
|
|
996
1554
|
action,
|
|
997
|
-
job: compatibilityJobFromReceipt(existing,
|
|
1555
|
+
job: compatibilityJobFromReceipt(existing, scope.checkoutId ?? CONTROLLER_PLUGIN_SCOPE_KEY),
|
|
998
1556
|
deduplicated: true,
|
|
999
1557
|
result: existing.result,
|
|
1000
1558
|
receipt: existing,
|
|
@@ -1009,74 +1567,56 @@ export async function submitAssistantPluginAction(
|
|
|
1009
1567
|
let authorization = initialAuthorizationEvidence(manifest, action);
|
|
1010
1568
|
let authorizationContext: AssistantPluginAuthorizationContext | undefined;
|
|
1011
1569
|
let activeAuthorizationGrant: PluginCapabilityAuthorizationGrant | undefined;
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1570
|
+
if (requiresAutomatedWriteAuthorization(request.origin)) {
|
|
1571
|
+
activeAuthorizationGrant = await resolveAutomatedWriteAuthorization({
|
|
1572
|
+
controllerHome,
|
|
1573
|
+
scope,
|
|
1574
|
+
adapter,
|
|
1575
|
+
manifest,
|
|
1576
|
+
action,
|
|
1577
|
+
args: normalizedArgs,
|
|
1578
|
+
origin: request.origin,
|
|
1579
|
+
requestId: request.requestId,
|
|
1580
|
+
jobId: receiptId,
|
|
1581
|
+
authorizationGrantRefs: request.authorizationGrantRefs,
|
|
1582
|
+
});
|
|
1583
|
+
if (activeAuthorizationGrant) {
|
|
1584
|
+
authorization = {
|
|
1585
|
+
source: 'capability_grant',
|
|
1586
|
+
reusable: true,
|
|
1587
|
+
capabilityId,
|
|
1588
|
+
target: activeAuthorizationGrant.target,
|
|
1589
|
+
grantId: activeAuthorizationGrant.grantId,
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1018
1593
|
const boundRemoteWork = action.risk === 'remote_write'
|
|
1019
|
-
? remoteEffectWorkForPluginAction(controllerHome,
|
|
1020
|
-
: undefined;
|
|
1021
|
-
const attributedWork = !requiresLocalEffectWork
|
|
1022
|
-
? attributedWorkForPluginAction(controllerHome, repository, action, request)
|
|
1594
|
+
? remoteEffectWorkForPluginAction(controllerHome, scope, action, request)
|
|
1023
1595
|
: undefined;
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
operation: {
|
|
1030
|
-
name: `plugin:${request.pluginId}/${request.actionId}`,
|
|
1031
|
-
semanticKey: key,
|
|
1032
|
-
argumentHash: createHash('sha256').update(JSON.stringify(normalizedArgs)).digest('hex'),
|
|
1033
|
-
mode: 'mutating',
|
|
1034
|
-
idempotent: action.idempotent,
|
|
1035
|
-
replayable: action.idempotent,
|
|
1036
|
-
resourceClaims: claimsForAssistantPluginAction(action, repository, manifest.pluginId),
|
|
1037
|
-
},
|
|
1038
|
-
objective: `Execute bounded controller-local effect ${request.pluginId}/${request.actionId}`,
|
|
1039
|
-
mode: 'direct_control',
|
|
1040
|
-
requestedBy: 'chatgpt',
|
|
1041
|
-
principalId: request.origin.actor,
|
|
1042
|
-
controllerInstanceId: process.env.FORGE_RUNTIME_INSTANCE_ID?.trim()
|
|
1043
|
-
|| process.env.FORGE_WRITER_INSTANCE_ID?.trim()
|
|
1044
|
-
|| process.env.FORGE_DAEMON_INSTANCE_ID?.trim(),
|
|
1045
|
-
workKind: 'local_effect',
|
|
1046
|
-
risk: workRiskForPluginAction(action),
|
|
1047
|
-
acceptanceCriteria: ['The requested local effect completes within its authorized Target Grant boundary.'],
|
|
1048
|
-
constraints: {
|
|
1049
|
-
requireHandoffOnAmbiguity: true,
|
|
1050
|
-
allowDestructive: action.risk === 'destructive'
|
|
1051
|
-
&& request.confirmAuthorization === true
|
|
1052
|
-
&& request.confirmationText === action.requiredConfirmationText,
|
|
1053
|
-
},
|
|
1054
|
-
}).contract
|
|
1596
|
+
// Capability execution never creates semantic Work implicitly. Existing Work
|
|
1597
|
+
// attribution is opt-in through work_id; otherwise the plugin receipt is the
|
|
1598
|
+
// durable effect record.
|
|
1599
|
+
const attributedWork = action.risk !== 'remote_write'
|
|
1600
|
+
? attributedWorkForPluginAction(controllerHome, scope, action, request)
|
|
1055
1601
|
: undefined;
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1602
|
+
if (scope.repository) {
|
|
1603
|
+
appendRuntimeEvent(controllerHome, {
|
|
1604
|
+
repoId: scope.repository.repoId,
|
|
1605
|
+
entityType: 'plugin',
|
|
1606
|
+
entityId: manifest.pluginId,
|
|
1607
|
+
eventType: 'plugin_action_requested',
|
|
1608
|
+
requestId: request.requestId,
|
|
1609
|
+
revision: manifest.revision,
|
|
1610
|
+
data: {
|
|
1611
|
+
actionId: action.actionId,
|
|
1612
|
+
receiptId,
|
|
1613
|
+
risk: action.risk,
|
|
1614
|
+
confirmation: action.confirmation,
|
|
1615
|
+
capabilityId,
|
|
1616
|
+
authorizationReuseSupported: action.confirmation === 'authorization' && Boolean(adapter.resolveAuthorizationContext),
|
|
1617
|
+
},
|
|
1062
1618
|
});
|
|
1063
1619
|
}
|
|
1064
|
-
appendRuntimeEvent(controllerHome, {
|
|
1065
|
-
repoId: repository.repoId,
|
|
1066
|
-
entityType: 'plugin',
|
|
1067
|
-
entityId: manifest.pluginId,
|
|
1068
|
-
eventType: 'plugin_action_requested',
|
|
1069
|
-
requestId: request.requestId,
|
|
1070
|
-
revision: manifest.revision,
|
|
1071
|
-
data: {
|
|
1072
|
-
actionId: action.actionId,
|
|
1073
|
-
receiptId,
|
|
1074
|
-
risk: action.risk,
|
|
1075
|
-
confirmation: action.confirmation,
|
|
1076
|
-
capabilityId,
|
|
1077
|
-
authorizationReuseSupported: action.confirmation === 'authorization' && Boolean(adapter.resolveAuthorizationContext),
|
|
1078
|
-
},
|
|
1079
|
-
});
|
|
1080
1620
|
|
|
1081
1621
|
try {
|
|
1082
1622
|
if (action.confirmation === 'authorization'
|
|
@@ -1084,8 +1624,7 @@ export async function submitAssistantPluginAction(
|
|
|
1084
1624
|
&& originMayEstablishCapabilityAuthorization(request.origin)) {
|
|
1085
1625
|
authorizationContext = await adapter.resolveAuthorizationContext({
|
|
1086
1626
|
controllerHome,
|
|
1087
|
-
|
|
1088
|
-
repoRoot: repository.canonicalRoot,
|
|
1627
|
+
...pluginActionExecutionFields(scope),
|
|
1089
1628
|
pluginId: request.pluginId,
|
|
1090
1629
|
actionId: request.actionId,
|
|
1091
1630
|
requestId: request.requestId,
|
|
@@ -1105,7 +1644,7 @@ export async function submitAssistantPluginAction(
|
|
|
1105
1644
|
try {
|
|
1106
1645
|
activeAuthorizationGrant = findActivePluginCapabilityAuthorization(controllerHome, {
|
|
1107
1646
|
ownerScope: pluginCapabilityAuthorizationOwnerScope(request.origin),
|
|
1108
|
-
repoId:
|
|
1647
|
+
repoId: scope.grantRepoId,
|
|
1109
1648
|
pluginId: request.pluginId,
|
|
1110
1649
|
capabilityId,
|
|
1111
1650
|
target: authorizationContext.target,
|
|
@@ -1130,22 +1669,53 @@ export async function submitAssistantPluginAction(
|
|
|
1130
1669
|
}
|
|
1131
1670
|
}
|
|
1132
1671
|
|
|
1133
|
-
const result = await
|
|
1672
|
+
const result = await withAssistantPluginResourceLeases(
|
|
1134
1673
|
controllerHome,
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
:
|
|
1148
|
-
|
|
1674
|
+
scope,
|
|
1675
|
+
action,
|
|
1676
|
+
request,
|
|
1677
|
+
() => executePluginActionInScope(scope, {
|
|
1678
|
+
controllerHome,
|
|
1679
|
+
...pluginActionExecutionFields(scope),
|
|
1680
|
+
pluginId: request.pluginId,
|
|
1681
|
+
actionId: request.actionId,
|
|
1682
|
+
requestId: request.requestId,
|
|
1683
|
+
args: normalizedArgs,
|
|
1684
|
+
origin: request.origin,
|
|
1685
|
+
jobId: receiptId,
|
|
1686
|
+
timeoutMs: request.timeoutMs,
|
|
1687
|
+
signal: request.signal,
|
|
1688
|
+
authorizationGrantRefs: request.authorizationGrantRefs,
|
|
1689
|
+
deadlineAtMs: typeof request.timeoutMs === 'number'
|
|
1690
|
+
? Date.now() + Math.max(1, Math.trunc(request.timeoutMs))
|
|
1691
|
+
: undefined,
|
|
1692
|
+
}),
|
|
1693
|
+
);
|
|
1694
|
+
// create_session materializes the exact provider/session identity during
|
|
1695
|
+
// execution, so its authorization target cannot always be resolved before
|
|
1696
|
+
// the action runs. Resolve that postcondition once after a successful
|
|
1697
|
+
// interactive bootstrap, then persist the same exact-target grant path as
|
|
1698
|
+
// every other authorization-class Browser action. Scheduled execution
|
|
1699
|
+
// still remains grant-only because this branch is interactive-origin only.
|
|
1700
|
+
if (action.actionId === 'create_session'
|
|
1701
|
+
&& action.confirmation === 'authorization'
|
|
1702
|
+
&& !authorizationContext
|
|
1703
|
+
&& originMayEstablishCapabilityAuthorization(request.origin)
|
|
1704
|
+
&& adapter.resolveAuthorizationContext) {
|
|
1705
|
+
authorizationContext = await adapter.resolveAuthorizationContext({
|
|
1706
|
+
controllerHome,
|
|
1707
|
+
...pluginActionExecutionFields(scope),
|
|
1708
|
+
pluginId: request.pluginId,
|
|
1709
|
+
actionId: request.actionId,
|
|
1710
|
+
requestId: request.requestId,
|
|
1711
|
+
args: normalizedArgs,
|
|
1712
|
+
origin: request.origin,
|
|
1713
|
+
jobId: receiptId,
|
|
1714
|
+
timeoutMs: request.timeoutMs,
|
|
1715
|
+
signal: request.signal,
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1149
1719
|
if (action.confirmation === 'authorization'
|
|
1150
1720
|
&& authorizationContext
|
|
1151
1721
|
&& !activeAuthorizationGrant
|
|
@@ -1153,7 +1723,7 @@ export async function submitAssistantPluginAction(
|
|
|
1153
1723
|
try {
|
|
1154
1724
|
const grant = recordPluginCapabilityAuthorization(controllerHome, {
|
|
1155
1725
|
ownerScope: pluginCapabilityAuthorizationOwnerScope(request.origin),
|
|
1156
|
-
repoId:
|
|
1726
|
+
repoId: scope.grantRepoId,
|
|
1157
1727
|
pluginId: request.pluginId,
|
|
1158
1728
|
capabilityId,
|
|
1159
1729
|
target: authorizationContext.target,
|
|
@@ -1187,42 +1757,8 @@ export async function submitAssistantPluginAction(
|
|
|
1187
1757
|
work: {
|
|
1188
1758
|
workId: boundRemoteWork.workId,
|
|
1189
1759
|
workKind: 'remote_effect',
|
|
1760
|
+
semanticState: 'open' as const,
|
|
1190
1761
|
...(terminalRemoteEffect ? { completionOutcome: 'completed_remote' as const } : {}),
|
|
1191
|
-
status: terminalRemoteEffect ? 'completed' : 'running',
|
|
1192
|
-
},
|
|
1193
|
-
};
|
|
1194
|
-
}
|
|
1195
|
-
if (acceptedWork) {
|
|
1196
|
-
const recordedAt = new Date().toISOString();
|
|
1197
|
-
const target = localEffectTarget(normalizedArgs, result);
|
|
1198
|
-
appendWorkEvidence({ controllerHome, repoId: repository.repoId }, acceptedWork.workId, {
|
|
1199
|
-
title: 'controller-local effect completed',
|
|
1200
|
-
summary: `${request.pluginId}/${request.actionId} completed for ${target.id}.`,
|
|
1201
|
-
detailLevel: 'summary',
|
|
1202
|
-
});
|
|
1203
|
-
recordWorkCompletionReceipt(
|
|
1204
|
-
{ controllerHome, repoId: repository.repoId },
|
|
1205
|
-
acceptedWork.workId,
|
|
1206
|
-
{
|
|
1207
|
-
schemaVersion: 1,
|
|
1208
|
-
receiptId: `LFX-${Date.now()}-${createHash('sha256').update(`${request.requestId}:${acceptedWork.workId}`).digest('hex').slice(0, 8)}`,
|
|
1209
|
-
source: 'local_effect',
|
|
1210
|
-
workId: acceptedWork.workId,
|
|
1211
|
-
operation: `${request.pluginId}/${request.actionId}`,
|
|
1212
|
-
target,
|
|
1213
|
-
changed: true,
|
|
1214
|
-
recordedAt,
|
|
1215
|
-
},
|
|
1216
|
-
'completed_local',
|
|
1217
|
-
'local_effect',
|
|
1218
|
-
);
|
|
1219
|
-
resultWithLineage = {
|
|
1220
|
-
...result,
|
|
1221
|
-
work: {
|
|
1222
|
-
workId: acceptedWork.workId,
|
|
1223
|
-
workKind: 'local_effect',
|
|
1224
|
-
completionOutcome: 'completed_local',
|
|
1225
|
-
status: 'completed',
|
|
1226
1762
|
},
|
|
1227
1763
|
};
|
|
1228
1764
|
}
|
|
@@ -1230,157 +1766,213 @@ export async function submitAssistantPluginAction(
|
|
|
1230
1766
|
schemaVersion: 1,
|
|
1231
1767
|
receiptId,
|
|
1232
1768
|
requestId: request.requestId,
|
|
1233
|
-
|
|
1234
|
-
...(
|
|
1769
|
+
scopeKey: scope.scopeKey,
|
|
1770
|
+
...(scope.repository ? { repoId: scope.repository.repoId } : {}),
|
|
1771
|
+
...(request.workId ? { workRepoId: workAttributionRepoId(scope, request) } : {}),
|
|
1235
1772
|
pluginId: request.pluginId,
|
|
1236
1773
|
actionId: request.actionId,
|
|
1237
1774
|
semanticKey: key,
|
|
1238
1775
|
status: 'succeeded',
|
|
1239
1776
|
createdAt,
|
|
1240
|
-
...(
|
|
1241
|
-
? { workId:
|
|
1242
|
-
:
|
|
1243
|
-
? { workId:
|
|
1244
|
-
:
|
|
1245
|
-
? { workId: attributedWork.workId }
|
|
1246
|
-
: {}),
|
|
1777
|
+
...(boundRemoteWork
|
|
1778
|
+
? { workId: boundRemoteWork.workId }
|
|
1779
|
+
: attributedWork
|
|
1780
|
+
? { workId: attributedWork.workId }
|
|
1781
|
+
: {}),
|
|
1247
1782
|
origin: request.origin,
|
|
1248
1783
|
authorization,
|
|
1249
1784
|
result: resultWithLineage,
|
|
1250
1785
|
};
|
|
1251
|
-
writeJsonAtomic(pluginActionReceiptPath(controllerHome,
|
|
1786
|
+
writeJsonAtomic(pluginActionReceiptPath(controllerHome, scope.scopeKey, receiptId), receipt);
|
|
1252
1787
|
writeJsonAtomic(requestPath, {
|
|
1253
1788
|
requestId: request.requestId,
|
|
1254
|
-
|
|
1255
|
-
...(
|
|
1789
|
+
scopeKey: scope.scopeKey,
|
|
1790
|
+
...(scope.repository ? { repoId: scope.repository.repoId } : {}),
|
|
1791
|
+
...(request.workId ? { workRepoId: workAttributionRepoId(scope, request) } : {}),
|
|
1256
1792
|
receiptId,
|
|
1257
1793
|
semanticKey: key,
|
|
1258
1794
|
createdAt,
|
|
1259
1795
|
} satisfies PluginActionRequestIndex);
|
|
1260
1796
|
if (boundRemoteWork) {
|
|
1261
|
-
bindRemoteEffectReceiptToWork(controllerHome,
|
|
1797
|
+
bindRemoteEffectReceiptToWork(controllerHome, scope, action, request, receipt);
|
|
1262
1798
|
} else if (attributedWork) {
|
|
1263
|
-
bindLocalEffectReceiptToAttributedWork(controllerHome,
|
|
1799
|
+
bindLocalEffectReceiptToAttributedWork(controllerHome, scope, action, request, receipt, attributedWork);
|
|
1264
1800
|
}
|
|
1265
|
-
const nextManifest =
|
|
1801
|
+
const nextManifest = scope.kind === 'controller'
|
|
1802
|
+
? getControllerPluginManifest(controllerHome, request.pluginId)
|
|
1803
|
+
: getAssistantPluginManifest(controllerHome, scope.repository!, request.pluginId);
|
|
1266
1804
|
return {
|
|
1267
1805
|
manifest: nextManifest,
|
|
1268
1806
|
action,
|
|
1269
|
-
job: compatibilityJobFromReceipt(receipt,
|
|
1807
|
+
job: compatibilityJobFromReceipt(receipt, scope.checkoutId ?? CONTROLLER_PLUGIN_SCOPE_KEY),
|
|
1270
1808
|
deduplicated: false,
|
|
1271
1809
|
result: resultWithLineage,
|
|
1272
1810
|
receipt,
|
|
1273
1811
|
authorization,
|
|
1274
|
-
workId:
|
|
1812
|
+
workId: boundRemoteWork?.workId ?? attributedWork?.workId,
|
|
1275
1813
|
};
|
|
1276
1814
|
} catch (error) {
|
|
1277
1815
|
const message = error instanceof Error ? error.message : String(error);
|
|
1278
|
-
const code =
|
|
1816
|
+
const code = isAssistantPluginError(error)
|
|
1817
|
+
? error.code
|
|
1818
|
+
: /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'PLUGIN_ACTION_FAILED';
|
|
1819
|
+
const effectOutcome: AssistantPluginEffectOutcome = isAssistantPluginError(error)
|
|
1820
|
+
? error.effectOutcome
|
|
1821
|
+
: 'failed';
|
|
1822
|
+
const outcomeUnknown = effectOutcome === 'outcome_unknown';
|
|
1823
|
+
const outcomeResult = outcomeUnknown
|
|
1824
|
+
? { outcome: 'outcome_unknown', error: { code, message } }
|
|
1825
|
+
: undefined;
|
|
1279
1826
|
if (boundRemoteWork) {
|
|
1280
|
-
appendWorkEvidence({ controllerHome, repoId: workAttributionRepoId(
|
|
1281
|
-
title: 'typed remote plugin effect failed',
|
|
1827
|
+
appendWorkEvidence({ controllerHome, repoId: workAttributionRepoId(scope, request) }, boundRemoteWork.workId, {
|
|
1828
|
+
title: outcomeUnknown ? 'typed remote plugin effect outcome unknown' : 'typed remote plugin effect failed',
|
|
1282
1829
|
summary: `${request.pluginId}/${request.actionId}: ${message}`.slice(0, 1_000),
|
|
1283
1830
|
detailLevel: 'summary',
|
|
1284
1831
|
});
|
|
1285
1832
|
}
|
|
1286
|
-
if (acceptedWork) {
|
|
1287
|
-
const current = getWorkContract({ controllerHome, repoId: repository.repoId }, acceptedWork.workId);
|
|
1288
|
-
updateWorkContract({ controllerHome, repoId: repository.repoId }, acceptedWork.workId, {
|
|
1289
|
-
status: 'failed',
|
|
1290
|
-
workKind: 'local_effect',
|
|
1291
|
-
dispatchState: 'terminal',
|
|
1292
|
-
evidenceState: 'failed',
|
|
1293
|
-
evidenceRefs: [{
|
|
1294
|
-
title: 'controller-local effect failed',
|
|
1295
|
-
summary: `${request.pluginId}/${request.actionId}: ${message}`.slice(0, 1_000),
|
|
1296
|
-
detailLevel: 'summary',
|
|
1297
|
-
}, ...(current?.evidenceRefs ?? [])],
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
1833
|
const receipt: PluginActionReceipt = {
|
|
1301
1834
|
schemaVersion: 1,
|
|
1302
1835
|
receiptId,
|
|
1303
1836
|
requestId: request.requestId,
|
|
1304
|
-
|
|
1305
|
-
...(
|
|
1837
|
+
scopeKey: scope.scopeKey,
|
|
1838
|
+
...(scope.repository ? { repoId: scope.repository.repoId } : {}),
|
|
1839
|
+
...(request.workId ? { workRepoId: workAttributionRepoId(scope, request) } : {}),
|
|
1306
1840
|
pluginId: request.pluginId,
|
|
1307
1841
|
actionId: request.actionId,
|
|
1308
1842
|
semanticKey: key,
|
|
1309
1843
|
status: 'failed',
|
|
1844
|
+
effectOutcome,
|
|
1310
1845
|
createdAt,
|
|
1311
|
-
...(
|
|
1312
|
-
? { workId:
|
|
1313
|
-
:
|
|
1314
|
-
? { workId:
|
|
1315
|
-
:
|
|
1316
|
-
? { workId: attributedWork.workId }
|
|
1317
|
-
: {}),
|
|
1846
|
+
...(boundRemoteWork
|
|
1847
|
+
? { workId: boundRemoteWork.workId }
|
|
1848
|
+
: attributedWork
|
|
1849
|
+
? { workId: attributedWork.workId }
|
|
1850
|
+
: {}),
|
|
1318
1851
|
origin: request.origin,
|
|
1319
1852
|
authorization,
|
|
1853
|
+
...(outcomeResult ? { result: outcomeResult } : {}),
|
|
1320
1854
|
error: { code, message },
|
|
1321
1855
|
};
|
|
1322
|
-
writeJsonAtomic(pluginActionReceiptPath(controllerHome,
|
|
1856
|
+
writeJsonAtomic(pluginActionReceiptPath(controllerHome, scope.scopeKey, receiptId), receipt);
|
|
1323
1857
|
writeJsonAtomic(requestPath, {
|
|
1324
1858
|
requestId: request.requestId,
|
|
1325
|
-
|
|
1859
|
+
scopeKey: scope.scopeKey,
|
|
1860
|
+
...(scope.repository ? { repoId: scope.repository.repoId } : {}),
|
|
1861
|
+
...(request.workId ? { workRepoId: workAttributionRepoId(scope, request) } : {}),
|
|
1326
1862
|
receiptId,
|
|
1327
1863
|
semanticKey: key,
|
|
1328
1864
|
createdAt,
|
|
1329
1865
|
} satisfies PluginActionRequestIndex);
|
|
1866
|
+
if (outcomeUnknown) {
|
|
1867
|
+
return {
|
|
1868
|
+
manifest,
|
|
1869
|
+
action,
|
|
1870
|
+
job: compatibilityJobFromReceipt(receipt, scope.checkoutId ?? CONTROLLER_PLUGIN_SCOPE_KEY),
|
|
1871
|
+
deduplicated: false,
|
|
1872
|
+
result: outcomeResult,
|
|
1873
|
+
receipt,
|
|
1874
|
+
authorization,
|
|
1875
|
+
workId: boundRemoteWork?.workId ?? attributedWork?.workId,
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1330
1878
|
throw error;
|
|
1331
1879
|
}
|
|
1332
1880
|
}
|
|
1333
1881
|
|
|
1334
|
-
export
|
|
1882
|
+
export function submitAssistantPluginAction(
|
|
1883
|
+
controllerHome: string,
|
|
1884
|
+
repository: RepositoryRecord,
|
|
1885
|
+
request: AssistantPluginActionRequest,
|
|
1886
|
+
) {
|
|
1887
|
+
return submitPluginAction(controllerHome, repositoryPluginActionScope(repository), request);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
export function submitControllerPluginAction(
|
|
1891
|
+
controllerHome: string,
|
|
1892
|
+
request: AssistantPluginActionRequest,
|
|
1893
|
+
) {
|
|
1894
|
+
return submitPluginAction(controllerHome, controllerPluginActionScope(controllerHome), request);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
async function executePluginActionInScope(
|
|
1898
|
+
scope: PluginActionScopeContext,
|
|
1335
1899
|
input: AssistantPluginActionExecutionInput,
|
|
1336
1900
|
): Promise<Record<string, unknown>> {
|
|
1337
1901
|
const adapter = resolvePluginAdapter(input.controllerHome, input.pluginId);
|
|
1338
1902
|
if (!adapter) throw new Error(`PLUGIN_NOT_FOUND: ${input.pluginId}`);
|
|
1339
|
-
const
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
activeCheckoutId: 'active',
|
|
1343
|
-
} as RepositoryRecord;
|
|
1344
|
-
const manifestLookup = getAssistantPluginManifestForExecution(input.controllerHome, repository, input.pluginId, adapter);
|
|
1903
|
+
const manifestLookup = scope.kind === 'controller'
|
|
1904
|
+
? getControllerPluginManifestForExecution(input.controllerHome, input.pluginId, adapter)
|
|
1905
|
+
: getAssistantPluginManifestForExecution(input.controllerHome, scope.repository!, input.pluginId, adapter);
|
|
1345
1906
|
const manifest = manifestLookup.manifest;
|
|
1346
1907
|
const action = actionForManifest(manifest, input.actionId);
|
|
1347
|
-
|
|
1908
|
+
if (action.executionMode === 'direct_non_persistent') {
|
|
1909
|
+
throw new Error(`PLUGIN_DIRECT_NON_PERSISTENT_APPLICATION_PATH_REQUIRED: ${input.pluginId}/${input.actionId}`);
|
|
1910
|
+
}
|
|
1348
1911
|
const normalizedArgs = validateActionArguments(action, input.args);
|
|
1912
|
+
if (requiresAutomatedWriteAuthorization(input.origin)) {
|
|
1913
|
+
await resolveAutomatedWriteAuthorization({
|
|
1914
|
+
controllerHome: input.controllerHome,
|
|
1915
|
+
scope,
|
|
1916
|
+
adapter,
|
|
1917
|
+
manifest,
|
|
1918
|
+
action,
|
|
1919
|
+
args: normalizedArgs,
|
|
1920
|
+
origin: input.origin,
|
|
1921
|
+
requestId: input.requestId,
|
|
1922
|
+
jobId: input.jobId,
|
|
1923
|
+
authorizationGrantRefs: input.authorizationGrantRefs,
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1349
1926
|
try {
|
|
1350
1927
|
const result = await adapter.executeAction({
|
|
1351
1928
|
...input,
|
|
1929
|
+
...pluginActionExecutionFields(scope),
|
|
1352
1930
|
args: normalizedArgs,
|
|
1353
1931
|
providerIdentityPrevalidated: manifestLookup.providerIdentityPrevalidated,
|
|
1354
1932
|
});
|
|
1355
1933
|
for (const affectedPluginId of adapter.affectedPluginIdsAfterAction?.(input.actionId, result) ?? []) {
|
|
1356
1934
|
if (!affectedPluginId || affectedPluginId === input.pluginId) continue;
|
|
1357
|
-
invalidateAssistantPluginManifestCache(input.controllerHome, repository.repoId, affectedPluginId);
|
|
1358
1935
|
const affectedAdapter = resolvePluginAdapter(input.controllerHome, affectedPluginId);
|
|
1359
|
-
if (
|
|
1360
|
-
|
|
1936
|
+
if (scope.kind === 'controller') {
|
|
1937
|
+
invalidateAssistantPluginManifestCache(input.controllerHome, CONTROLLER_PLUGIN_CACHE_SCOPE, affectedPluginId);
|
|
1938
|
+
if (affectedAdapter && adapterMatchesController(affectedAdapter)) {
|
|
1939
|
+
syncControllerPluginManifest(input.controllerHome, affectedPluginId);
|
|
1940
|
+
} else {
|
|
1941
|
+
removeControllerPluginManifestProjection(input.controllerHome, affectedPluginId);
|
|
1942
|
+
}
|
|
1361
1943
|
} else {
|
|
1362
|
-
|
|
1944
|
+
const repository = scope.repository!;
|
|
1945
|
+
invalidateAssistantPluginManifestCache(input.controllerHome, repository.repoId, affectedPluginId);
|
|
1946
|
+
if (affectedAdapter && adapterMatchesRepository(affectedAdapter, repository)) {
|
|
1947
|
+
syncAssistantPluginManifest(input.controllerHome, repository, affectedPluginId);
|
|
1948
|
+
} else {
|
|
1949
|
+
rmSync(manifestPath(input.controllerHome, repository.repoId, affectedPluginId), { force: true });
|
|
1950
|
+
}
|
|
1363
1951
|
}
|
|
1364
1952
|
}
|
|
1365
1953
|
const refreshManifest = adapter.shouldRefreshManifestAfterAction?.(input.actionId) ?? true;
|
|
1366
1954
|
const nextManifest = refreshManifest
|
|
1367
|
-
?
|
|
1955
|
+
? scope.kind === 'controller'
|
|
1956
|
+
? syncControllerPluginManifest(input.controllerHome, input.pluginId).manifest
|
|
1957
|
+
: syncAssistantPluginManifest(input.controllerHome, scope.repository!, input.pluginId).manifest
|
|
1368
1958
|
: manifest;
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1959
|
+
if (scope.repository) {
|
|
1960
|
+
appendRuntimeEvent(input.controllerHome, {
|
|
1961
|
+
repoId: scope.repository.repoId,
|
|
1962
|
+
entityType: 'plugin',
|
|
1963
|
+
entityId: input.pluginId,
|
|
1964
|
+
eventType: 'plugin_action_succeeded',
|
|
1965
|
+
requestId: input.requestId,
|
|
1966
|
+
revision: nextManifest.revision,
|
|
1967
|
+
data: {
|
|
1968
|
+
actionId: input.actionId,
|
|
1969
|
+
jobId: input.jobId,
|
|
1970
|
+
resultKeys: Object.keys(result).slice(0, 20),
|
|
1971
|
+
lifecycleState: nextManifest.lifecycle.state,
|
|
1972
|
+
healthState: nextManifest.health.state,
|
|
1973
|
+
},
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1384
1976
|
return {
|
|
1385
1977
|
schemaVersion: 1,
|
|
1386
1978
|
plugin: {
|
|
@@ -1408,25 +2000,49 @@ export async function executeAssistantPluginAction(
|
|
|
1408
2000
|
actionId: input.actionId,
|
|
1409
2001
|
},
|
|
1410
2002
|
});
|
|
1411
|
-
const
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
2003
|
+
const nextManifest = scope.kind === 'controller'
|
|
2004
|
+
? syncControllerPluginManifest(input.controllerHome, input.pluginId).manifest
|
|
2005
|
+
: syncAssistantPluginManifest(input.controllerHome, scope.repository!, input.pluginId).manifest;
|
|
2006
|
+
if (scope.repository) {
|
|
2007
|
+
appendRuntimeEvent(input.controllerHome, {
|
|
2008
|
+
repoId: scope.repository.repoId,
|
|
2009
|
+
entityType: 'plugin',
|
|
2010
|
+
entityId: input.pluginId,
|
|
2011
|
+
eventType: 'plugin_action_failed',
|
|
2012
|
+
requestId: input.requestId,
|
|
2013
|
+
revision: nextManifest.revision,
|
|
2014
|
+
data: {
|
|
2015
|
+
actionId: input.actionId,
|
|
2016
|
+
jobId: input.jobId,
|
|
2017
|
+
code: pluginError.code,
|
|
2018
|
+
retryable: pluginError.retryable,
|
|
2019
|
+
},
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
1427
2022
|
throw new AssistantPluginError(pluginError.code, pluginError.message.replace(/^[^:]+:\s*/, ''), {
|
|
1428
2023
|
retryable: pluginError.retryable,
|
|
1429
2024
|
details: pluginError.details,
|
|
1430
2025
|
});
|
|
1431
2026
|
}
|
|
1432
2027
|
}
|
|
2028
|
+
|
|
2029
|
+
export function executeControllerScopedPluginAction(
|
|
2030
|
+
input: Omit<AssistantPluginActionExecutionInput, 'repoId' | 'repoRoot'>,
|
|
2031
|
+
): Promise<Record<string, unknown>> {
|
|
2032
|
+
const scope = controllerPluginActionScope(input.controllerHome);
|
|
2033
|
+
return executePluginActionInScope(scope, {
|
|
2034
|
+
...input,
|
|
2035
|
+
...pluginActionExecutionFields(scope),
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
export function executeAssistantPluginAction(
|
|
2040
|
+
input: AssistantPluginActionExecutionInput,
|
|
2041
|
+
): Promise<Record<string, unknown>> {
|
|
2042
|
+
const repository = {
|
|
2043
|
+
repoId: input.repoId,
|
|
2044
|
+
canonicalRoot: input.repoRoot,
|
|
2045
|
+
activeCheckoutId: 'active',
|
|
2046
|
+
} as RepositoryRecord;
|
|
2047
|
+
return executePluginActionInScope(repositoryPluginActionScope(repository), input);
|
|
2048
|
+
}
|