@moretea-labs/forge 1.5.1 → 1.7.0
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 +2 -0
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md +2 -0
- package/DESIGN.md +7 -2
- package/README.md +4 -4
- package/README.zh-CN.md +2 -2
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/pre-edit-guard.sh +8 -39
- package/assets/hooks/prompt-guard.sh +7 -83
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +54 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/hook-operations.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/sprint-backlog.sh +6 -6
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +170 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +38 -22
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +3 -3
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Installation.md +1 -1
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Quick-Start.md +1 -1
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +29 -10
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/benchmark-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -0
- package/scripts/capture-plan.sh +3 -3
- package/scripts/check-agent-tooling.sh +8 -35
- package/scripts/check-architecture-sync.sh +17 -86
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-public-docs.sh +14 -1
- package/scripts/check-runtime-architecture.mjs +107 -64
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-workflow.sh +4 -4
- package/scripts/contract-worktree.sh +1 -21
- package/scripts/ensure-task-workflow.sh +22 -55
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +15 -44
- package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
- package/scripts/migrate-project-template.sh +23 -1179
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +11 -36
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/ship-worktrees.sh +2 -29
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- package/scripts/sprint-backlog.sh +1 -1
- package/scripts/verify-contract.sh +1 -111
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +3 -180
- package/scripts/workflow-contract.ts +1 -5
- package/src/cli/commands/chatgpt.ts +31 -2
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +37 -10
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +106 -3
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +898 -430
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/github.ts +133 -0
- package/src/cli/hook/prompt-guard-decision.ts +17 -18
- package/src/cli/hook/prompt-intents.ts +1 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +225 -67
- package/src/cli/local-bridge/console-view-models.ts +3 -35
- package/src/cli/local-bridge/facade-api.ts +2 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/styles.css +27 -11
- package/src/cli/local-bridge/frontend/src/types.ts +8 -7
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +29 -532
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -189
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/paths.ts +8 -2
- package/src/cli/mcp/repository-tools.ts +356 -101
- package/src/cli/mcp/server.ts +625 -54
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +42 -5
- package/src/cli/mcp/transports/http.ts +125 -15
- package/src/cli/mcp/transports/session-registry.ts +67 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +209 -14
- package/src/cli/repositories/command-executor.ts +55 -521
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +121 -0
- package/src/cli/repositories/safe-patch.ts +6 -4
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +110 -10
- package/src/cli/repository/inspector.ts +565 -62
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation.ts +275 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/validation.ts +15 -2
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
- package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
- package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
- package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
- package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
- package/src/runtime/control-plane/facade/types.ts +55 -11
- package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/routing/route-policy.ts +7 -16
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +19 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
- package/src/runtime/execution/process-runtime/check-result.ts +63 -2
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
- package/src/runtime/execution/process-runtime/gc.ts +98 -12
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
- package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
- package/src/runtime/execution/process-runtime/runtime.ts +59 -4
- package/src/runtime/execution/process-runtime/store.ts +7 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -0
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/workers/executor.ts +0 -48
- package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +185 -83
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
- package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2925 -318
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +6 -31
- package/src/runtime/plugins/browser-handoff.ts +92 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
- package/src/runtime/plugins/browser-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
- package/src/runtime/plugins/browser-runtime.ts +183 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-registration.ts +84 -5
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +4 -2
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/gmail-adapter.ts +28 -2
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +125 -8
- package/src/runtime/plugins/macos-capability-broker.ts +55 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +366 -24
- package/src/runtime/plugins/types.ts +39 -2
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/actions.ts +0 -10
- package/src/runtime/recovery/classifier.ts +0 -9
- package/src/runtime/recovery/maintenance-executor.ts +415 -18
- package/src/runtime/recovery/probe.ts +0 -1
- package/src/runtime/recovery/types.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +186 -8
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +430 -22
- package/src/runtime/root/release-manifest.ts +7 -2
- package/src/runtime/root/release-materialize.ts +162 -19
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -27
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/index.ts +0 -1
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/safe-tooling/types.ts +0 -30
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +702 -94
- package/src/runtime/standalone-recovery/entry.ts +99 -23
- package/src/runtime/standalone-recovery/installer.ts +19 -3
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
- package/src/runtime/workflow/schedules/engine.ts +509 -80
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +48 -4
- package/src/runtime/workflow/schedules/store.ts +131 -10
- package/src/runtime/workflow/schedules/types.ts +6 -3
- package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/bin/forge-desktop-helper.mjs +0 -204
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
- package/docs/architecture/current/codegraph-context-provider.md +0 -226
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
- package/docs/architecture/current/entity-model.md +0 -592
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -144
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -311
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
- package/docs/operations/controller-desktop-plugin.md +0 -112
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/plugins/desktop-adapter.ts +0 -374
- package/src/runtime/safe-tooling/browser-safe.ts +0 -133
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -2,13 +2,19 @@ import { createHash } from 'crypto';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import type { RepositoryRecord } from '../../cli/repositories/types';
|
|
4
4
|
import { CONTROLLER_SCOPE_REPO_ID, controllerSystemRoot, ensureControllerHome, repositoryControllerRoot } from '../../cli/repositories/controller-home';
|
|
5
|
-
import { existsSync, mkdirSync, readdirSync} from 'fs';
|
|
5
|
+
import { existsSync, 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
10
|
import { getExternalPluginAdapter, listExternalPluginAdapters } from './external-adapter';
|
|
11
11
|
import { AssistantPluginError, toAssistantPluginError } from './errors';
|
|
12
|
+
import {
|
|
13
|
+
findActivePluginCapabilityAuthorization,
|
|
14
|
+
pluginCapabilityAuthorizationOwnerScope,
|
|
15
|
+
recordPluginCapabilityAuthorization,
|
|
16
|
+
type PluginCapabilityAuthorizationGrant,
|
|
17
|
+
} from './capability-authorization-grants';
|
|
12
18
|
import { markControllerContextProjectionDirty } from '../projections/controller-context';
|
|
13
19
|
import { classifyRepositoryCommand } from '../../cli/repositories/command-classifier';
|
|
14
20
|
import {
|
|
@@ -18,12 +24,13 @@ import {
|
|
|
18
24
|
recordWorkCompletionReceipt,
|
|
19
25
|
updateWorkContract,
|
|
20
26
|
} from '../control-plane/facade/work-contract-store';
|
|
21
|
-
import type
|
|
27
|
+
import { isTerminalWorkContractStatus, type LocalEffectCompletionReceipt, type RemoteEffectCompletionReceipt, type WorkContract, type WorkRisk } from '../control-plane/facade/types';
|
|
22
28
|
import type {
|
|
23
29
|
AssistantPluginAdapter,
|
|
24
30
|
AssistantPluginActionDescriptor,
|
|
25
31
|
AssistantPluginActionExecutionInput,
|
|
26
32
|
AssistantPluginActionRequest,
|
|
33
|
+
AssistantPluginAuthorizationContext,
|
|
27
34
|
AssistantPluginManifest,
|
|
28
35
|
AssistantPluginRegistryIndex,
|
|
29
36
|
AssistantPluginRegistryIndexEntry,
|
|
@@ -44,7 +51,8 @@ export function assistantPluginScope(pluginId: string, controllerHome?: string):
|
|
|
44
51
|
return PLUGIN_ADAPTERS.get(pluginId)?.scope ?? (controllerHome ? getExternalPluginAdapter(controllerHome, pluginId)?.scope : undefined);
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
const
|
|
54
|
+
const PLUGIN_MANIFEST_LIVE_CACHE_TTL_MS = 5_000;
|
|
55
|
+
const PLUGIN_MANIFEST_STORED_CACHE_TTL_MS = Number.POSITIVE_INFINITY;
|
|
48
56
|
|
|
49
57
|
|
|
50
58
|
interface PluginManifestCacheEntry<T> {
|
|
@@ -83,6 +91,7 @@ export function controllerPluginRepository(controllerHome: string): RepositoryRe
|
|
|
83
91
|
|
|
84
92
|
function adapterMatchesRepository(adapter: AssistantPluginAdapter, repository: RepositoryRecord): boolean {
|
|
85
93
|
const scope = adapter.scope ?? 'repository';
|
|
94
|
+
if (scope === 'controller_with_repository_overlay') return true;
|
|
86
95
|
return repository.repoId === CONTROLLER_SCOPE_REPO_ID ? scope === 'controller' : scope === 'repository';
|
|
87
96
|
}
|
|
88
97
|
|
|
@@ -101,10 +110,11 @@ function itemCacheKey(controllerHome: string, repoId: string, pluginId: string,
|
|
|
101
110
|
function readPluginManifestCache<T>(
|
|
102
111
|
cache: Map<string, PluginManifestCacheEntry<T>>,
|
|
103
112
|
key: string,
|
|
113
|
+
maxAgeMs = PLUGIN_MANIFEST_LIVE_CACHE_TTL_MS,
|
|
104
114
|
): T | undefined {
|
|
105
115
|
const cached = cache.get(key);
|
|
106
116
|
if (!cached) return undefined;
|
|
107
|
-
if (Date.now() - cached.createdAt >
|
|
117
|
+
if (Number.isFinite(maxAgeMs) && Date.now() - cached.createdAt > maxAgeMs) {
|
|
108
118
|
cache.delete(key);
|
|
109
119
|
return undefined;
|
|
110
120
|
}
|
|
@@ -231,8 +241,11 @@ function cachedManifestForRepository(
|
|
|
231
241
|
repoId: string,
|
|
232
242
|
pluginId: string,
|
|
233
243
|
): AssistantPluginManifest | undefined {
|
|
234
|
-
return readPluginManifestCache(
|
|
235
|
-
|
|
244
|
+
return readPluginManifestCache(
|
|
245
|
+
pluginManifestItemCache,
|
|
246
|
+
itemCacheKey(controllerHome, repoId, pluginId, true),
|
|
247
|
+
PLUGIN_MANIFEST_STORED_CACHE_TTL_MS,
|
|
248
|
+
) ?? readPluginManifestCache(pluginManifestItemCache, itemCacheKey(controllerHome, repoId, pluginId, false));
|
|
236
249
|
}
|
|
237
250
|
|
|
238
251
|
function computeManifest(
|
|
@@ -244,7 +257,12 @@ function computeManifest(
|
|
|
244
257
|
const adapter = resolvedAdapter ?? resolvePluginAdapter(controllerHome, pluginId);
|
|
245
258
|
if (!adapter || !adapterMatchesRepository(adapter, repository)) throw new Error(`PLUGIN_NOT_FOUND: ${pluginId}`);
|
|
246
259
|
const previous = readStoredManifest(controllerHome, repository.repoId, pluginId);
|
|
247
|
-
const built = adapter.buildManifest(previous?.revision ?? 0, previous?.updatedAt, repository.canonicalRoot
|
|
260
|
+
const built = adapter.buildManifest(previous?.revision ?? 0, previous?.updatedAt, repository.canonicalRoot, {
|
|
261
|
+
controllerHome,
|
|
262
|
+
repoId: repository.repoId,
|
|
263
|
+
repoRoot: repository.canonicalRoot,
|
|
264
|
+
controllerScoped: repository.repoId === CONTROLLER_SCOPE_REPO_ID,
|
|
265
|
+
});
|
|
248
266
|
const changed = !previous || fingerprintManifest(previous) !== fingerprintManifest(built);
|
|
249
267
|
return {
|
|
250
268
|
...built,
|
|
@@ -356,27 +374,63 @@ function enforceConfirmation(action: AssistantPluginActionDescriptor, request: A
|
|
|
356
374
|
}
|
|
357
375
|
}
|
|
358
376
|
|
|
377
|
+
export interface PluginActionAuthorizationEvidence {
|
|
378
|
+
source: 'none' | 'host_permission_model' | 'capability_grant' | 'strong_confirmation';
|
|
379
|
+
reusable: boolean;
|
|
380
|
+
capabilityId?: string;
|
|
381
|
+
target?: { kind: string; id: string };
|
|
382
|
+
grantId?: string;
|
|
383
|
+
established?: boolean;
|
|
384
|
+
lookupError?: string;
|
|
385
|
+
persistenceError?: string;
|
|
386
|
+
}
|
|
387
|
+
|
|
359
388
|
function actionForManifest(manifest: AssistantPluginManifest, actionId: string): AssistantPluginActionDescriptor {
|
|
360
389
|
const action = manifest.actions.find((entry) => entry.actionId === actionId);
|
|
361
390
|
if (!action) throw new Error(`PLUGIN_ACTION_NOT_FOUND: ${manifest.pluginId}/${actionId}`);
|
|
362
391
|
return action;
|
|
363
392
|
}
|
|
364
393
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
394
|
+
function reusableCapabilityId(manifest: AssistantPluginManifest, action: AssistantPluginActionDescriptor): string {
|
|
395
|
+
const matches = manifest.capabilities
|
|
396
|
+
.filter((capability) => capability.actions.includes(action.actionId))
|
|
397
|
+
.map((capability) => capability.capabilityId)
|
|
398
|
+
.filter(Boolean)
|
|
399
|
+
.sort();
|
|
400
|
+
// Overlapping capability groups are ambiguous privilege boundaries. Fall back
|
|
401
|
+
// to action-local reuse instead of silently choosing a broader capability.
|
|
402
|
+
return matches.length === 1 ? matches[0]! : `action:${action.actionId}`;
|
|
403
|
+
}
|
|
372
404
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
405
|
+
const INTERACTIVE_PLUGIN_AUTHORIZATION_SURFACES = new Set(['mcp', 'local-ui', 'chatgpt-action']);
|
|
406
|
+
|
|
407
|
+
function originMayEstablishCapabilityAuthorization(origin: AssistantPluginActionExecutionInput['origin']): boolean {
|
|
408
|
+
return INTERACTIVE_PLUGIN_AUTHORIZATION_SURFACES.has(origin.surface);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function authorizationTargetSummary(context: AssistantPluginAuthorizationContext): { kind: string; id: string } {
|
|
412
|
+
return { kind: context.target.kind, id: context.target.id };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function authorizationError(error: unknown): string {
|
|
416
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
417
|
+
return message.slice(0, 500);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function initialAuthorizationEvidence(
|
|
421
|
+
manifest: AssistantPluginManifest,
|
|
422
|
+
action: AssistantPluginActionDescriptor,
|
|
423
|
+
): PluginActionAuthorizationEvidence {
|
|
424
|
+
if (action.confirmation === 'none') return { source: 'none', reusable: false };
|
|
425
|
+
const capabilityId = reusableCapabilityId(manifest, action);
|
|
426
|
+
if (action.confirmation === 'strong_confirmation') {
|
|
427
|
+
return { source: 'strong_confirmation', reusable: false, capabilityId };
|
|
378
428
|
}
|
|
379
|
-
|
|
429
|
+
return { source: 'host_permission_model', reusable: false, capabilityId };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function denyAutomatedWrite(manifest: AssistantPluginManifest, action: AssistantPluginActionDescriptor, origin: AssistantPluginActionExecutionInput['origin']): void {
|
|
433
|
+
if (!['schedule', 'reconciliation', 'system'].includes(origin.surface)) return;
|
|
380
434
|
if (action.readOnly) return;
|
|
381
435
|
throw new Error(`EXTERNAL_EFFECT_AUTHORIZATION_REQUIRED: ${manifest.pluginId}/${action.actionId} cannot run from ${origin.surface}`);
|
|
382
436
|
}
|
|
@@ -406,7 +460,14 @@ export function listAssistantPluginManifests(
|
|
|
406
460
|
const preferStored = options.preferStored === true && options.forceRefresh !== true;
|
|
407
461
|
const cacheKey = listCacheKey(controllerHome, repository.repoId, preferStored);
|
|
408
462
|
if (options.forceRefresh !== true) {
|
|
409
|
-
|
|
463
|
+
// Stored manifests are persisted snapshots. Their freshness is governed by
|
|
464
|
+
// explicit registry/config mutation invalidation, not a wall-clock trust
|
|
465
|
+
// window. Live execution manifests retain the short identity-validation TTL.
|
|
466
|
+
const cached = readPluginManifestCache(
|
|
467
|
+
pluginManifestListCache,
|
|
468
|
+
cacheKey,
|
|
469
|
+
preferStored ? PLUGIN_MANIFEST_STORED_CACHE_TTL_MS : PLUGIN_MANIFEST_LIVE_CACHE_TTL_MS,
|
|
470
|
+
);
|
|
410
471
|
if (cached) return cached;
|
|
411
472
|
}
|
|
412
473
|
const manifests = listAssistantPluginIds(controllerHome, repository)
|
|
@@ -544,6 +605,7 @@ export interface PluginActionReceipt {
|
|
|
544
605
|
createdAt: string;
|
|
545
606
|
workId?: string;
|
|
546
607
|
origin?: { surface: string; actor?: string; correlationId?: string };
|
|
608
|
+
authorization?: PluginActionAuthorizationEvidence;
|
|
547
609
|
result?: Record<string, unknown>;
|
|
548
610
|
error?: { code: string; message: string };
|
|
549
611
|
}
|
|
@@ -671,6 +733,137 @@ function localEffectTarget(
|
|
|
671
733
|
};
|
|
672
734
|
}
|
|
673
735
|
|
|
736
|
+
function remoteEffectWorkForPluginAction(
|
|
737
|
+
controllerHome: string,
|
|
738
|
+
repository: RepositoryRecord,
|
|
739
|
+
action: AssistantPluginActionDescriptor,
|
|
740
|
+
request: AssistantPluginActionRequest,
|
|
741
|
+
receiptId?: string,
|
|
742
|
+
): WorkContract | undefined {
|
|
743
|
+
const workId = request.workId?.trim();
|
|
744
|
+
if (!workId) return undefined;
|
|
745
|
+
if (action.risk !== 'remote_write') {
|
|
746
|
+
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_REQUIRES_REMOTE_WRITE: ${action.actionId}`);
|
|
747
|
+
}
|
|
748
|
+
const work = getWorkContract({ controllerHome, repoId: repository.repoId }, workId);
|
|
749
|
+
if (!work) throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_NOT_FOUND: ${workId}`);
|
|
750
|
+
if (work.workKind !== 'remote_effect') {
|
|
751
|
+
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_KIND_MISMATCH: ${workId} is ${work.workKind}, expected remote_effect`);
|
|
752
|
+
}
|
|
753
|
+
if (work.checks.length > 0) {
|
|
754
|
+
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_CHECKS_PRESENT: ${workId} declares repository checks and cannot be completed by a plugin receipt alone`);
|
|
755
|
+
}
|
|
756
|
+
if (isTerminalWorkContractStatus(work.status)) {
|
|
757
|
+
const receiptAlreadyBound = Boolean(receiptId) && (
|
|
758
|
+
work.completionReceipt?.receiptId === receiptId
|
|
759
|
+
|| work.evidenceRefs.some((evidence) => evidence.evidenceId === receiptId)
|
|
760
|
+
);
|
|
761
|
+
if (work.status === 'completed' && receiptAlreadyBound) return work;
|
|
762
|
+
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_TERMINAL: ${workId} is ${work.status}`);
|
|
763
|
+
}
|
|
764
|
+
return work;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function attributedWorkForPluginAction(
|
|
768
|
+
controllerHome: string,
|
|
769
|
+
repository: RepositoryRecord,
|
|
770
|
+
action: AssistantPluginActionDescriptor,
|
|
771
|
+
request: AssistantPluginActionRequest,
|
|
772
|
+
): WorkContract | undefined {
|
|
773
|
+
const workId = request.workId?.trim();
|
|
774
|
+
if (!workId || action.risk === 'remote_write') return undefined;
|
|
775
|
+
const work = getWorkContract({ controllerHome, repoId: repository.repoId }, workId);
|
|
776
|
+
if (!work) throw new Error(`WORK_PLUGIN_ATTRIBUTION_NOT_FOUND: ${workId}`);
|
|
777
|
+
if (isTerminalWorkContractStatus(work.status)) {
|
|
778
|
+
throw new Error(`WORK_PLUGIN_ATTRIBUTION_TERMINAL: ${workId} is ${work.status}`);
|
|
779
|
+
}
|
|
780
|
+
return work;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export function finalizeRemoteEffectWorkFromActionReceipt(
|
|
784
|
+
controllerHome: string,
|
|
785
|
+
repoId: string,
|
|
786
|
+
workId: string,
|
|
787
|
+
): WorkContract {
|
|
788
|
+
const work = getWorkContract({ controllerHome, repoId }, workId);
|
|
789
|
+
if (!work) throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_NOT_FOUND: ${workId}`);
|
|
790
|
+
if (work.workKind !== 'remote_effect') {
|
|
791
|
+
throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_KIND_MISMATCH: ${workId} is ${work.workKind}, expected remote_effect`);
|
|
792
|
+
}
|
|
793
|
+
if (work.status === 'completed' && work.completionReceipt?.source === 'remote_effect') return work;
|
|
794
|
+
if (isTerminalWorkContractStatus(work.status)) {
|
|
795
|
+
throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_TERMINAL: ${workId} is ${work.status}`);
|
|
796
|
+
}
|
|
797
|
+
const receipt = work.evidenceRefs
|
|
798
|
+
.map((evidence) => evidence.evidenceId ? readPluginActionReceipt(controllerHome, repoId, evidence.evidenceId) : undefined)
|
|
799
|
+
.find((candidate) => candidate?.workId === workId && candidate.status === 'succeeded');
|
|
800
|
+
if (!receipt) {
|
|
801
|
+
throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_RECEIPT_REQUIRED: ${workId} has no successful durable plugin action receipt`);
|
|
802
|
+
}
|
|
803
|
+
const remoteReceipt: RemoteEffectCompletionReceipt = {
|
|
804
|
+
schemaVersion: 1,
|
|
805
|
+
receiptId: receipt.receiptId,
|
|
806
|
+
source: 'remote_effect',
|
|
807
|
+
workId,
|
|
808
|
+
pluginId: receipt.pluginId,
|
|
809
|
+
actionId: receipt.actionId,
|
|
810
|
+
requestId: receipt.requestId,
|
|
811
|
+
semanticKey: receipt.semanticKey,
|
|
812
|
+
resultDigest: createHash('sha256').update(JSON.stringify(receipt.result ?? null)).digest('hex'),
|
|
813
|
+
recordedAt: new Date().toISOString(),
|
|
814
|
+
};
|
|
815
|
+
return recordWorkCompletionReceipt(
|
|
816
|
+
{ controllerHome, repoId },
|
|
817
|
+
workId,
|
|
818
|
+
remoteReceipt,
|
|
819
|
+
'completed_remote',
|
|
820
|
+
'remote_effect',
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function bindRemoteEffectReceiptToWork(
|
|
825
|
+
controllerHome: string,
|
|
826
|
+
repository: RepositoryRecord,
|
|
827
|
+
action: AssistantPluginActionDescriptor,
|
|
828
|
+
request: AssistantPluginActionRequest,
|
|
829
|
+
receipt: PluginActionReceipt,
|
|
830
|
+
): WorkContract | undefined {
|
|
831
|
+
if (!request.workId) return undefined;
|
|
832
|
+
if (receipt.status !== 'succeeded') return remoteEffectWorkForPluginAction(controllerHome, repository, action, request);
|
|
833
|
+
const work = remoteEffectWorkForPluginAction(controllerHome, repository, action, request, receipt.receiptId);
|
|
834
|
+
if (!work) return undefined;
|
|
835
|
+
if (work.status === 'completed') return work;
|
|
836
|
+
let updated = work;
|
|
837
|
+
if (!work.evidenceRefs.some((evidence) => evidence.evidenceId === receipt.receiptId)) {
|
|
838
|
+
updated = appendWorkEvidence({ controllerHome, repoId: repository.repoId }, work.workId, {
|
|
839
|
+
evidenceId: receipt.receiptId,
|
|
840
|
+
title: 'typed remote plugin effect completed',
|
|
841
|
+
summary: `${receipt.pluginId}/${receipt.actionId} completed with durable receipt ${receipt.receiptId}.`,
|
|
842
|
+
detailLevel: 'summary',
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
if (action.remoteEffectWorkCompletion !== 'terminal') return updated;
|
|
846
|
+
const remoteReceipt: RemoteEffectCompletionReceipt = {
|
|
847
|
+
schemaVersion: 1,
|
|
848
|
+
receiptId: receipt.receiptId,
|
|
849
|
+
source: 'remote_effect',
|
|
850
|
+
workId: work.workId,
|
|
851
|
+
pluginId: receipt.pluginId,
|
|
852
|
+
actionId: receipt.actionId,
|
|
853
|
+
requestId: receipt.requestId,
|
|
854
|
+
semanticKey: receipt.semanticKey,
|
|
855
|
+
resultDigest: createHash('sha256').update(JSON.stringify(receipt.result ?? null)).digest('hex'),
|
|
856
|
+
recordedAt: new Date().toISOString(),
|
|
857
|
+
};
|
|
858
|
+
return recordWorkCompletionReceipt(
|
|
859
|
+
{ controllerHome, repoId: repository.repoId },
|
|
860
|
+
work.workId,
|
|
861
|
+
remoteReceipt,
|
|
862
|
+
'completed_remote',
|
|
863
|
+
'remote_effect',
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
674
867
|
function compatibilityJobFromReceipt(receipt: PluginActionReceipt, checkoutId: string): ExecutionJob {
|
|
675
868
|
return {
|
|
676
869
|
schemaVersion: 1,
|
|
@@ -717,8 +910,11 @@ export async function submitAssistantPluginAction(
|
|
|
717
910
|
deduplicated: boolean;
|
|
718
911
|
result?: Record<string, unknown>;
|
|
719
912
|
receipt: PluginActionReceipt;
|
|
913
|
+
authorization?: PluginActionAuthorizationEvidence;
|
|
720
914
|
workId?: string;
|
|
721
915
|
}> {
|
|
916
|
+
const adapter = resolvePluginAdapter(controllerHome, request.pluginId);
|
|
917
|
+
if (!adapter) throw new Error(`PLUGIN_NOT_FOUND: ${request.pluginId}`);
|
|
722
918
|
const manifest = getAssistantPluginManifest(controllerHome, repository, request.pluginId);
|
|
723
919
|
const action = actionForManifest(manifest, request.actionId);
|
|
724
920
|
if (!manifest.enabled && action.actionId !== 'configure') {
|
|
@@ -741,6 +937,10 @@ export async function submitAssistantPluginAction(
|
|
|
741
937
|
if (!existing) {
|
|
742
938
|
throw new Error(`PLUGIN_RECEIPT_LOST: ${request.requestId}`);
|
|
743
939
|
}
|
|
940
|
+
if (request.workId && existing.workId !== request.workId) {
|
|
941
|
+
throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_CONFLICT: ${request.requestId} already belongs to Work ${existing.workId ?? 'none'}`);
|
|
942
|
+
}
|
|
943
|
+
if (request.workId && action.risk === 'remote_write') bindRemoteEffectReceiptToWork(controllerHome, repository, action, request, existing);
|
|
744
944
|
return {
|
|
745
945
|
manifest,
|
|
746
946
|
action,
|
|
@@ -748,18 +948,29 @@ export async function submitAssistantPluginAction(
|
|
|
748
948
|
deduplicated: true,
|
|
749
949
|
result: existing.result,
|
|
750
950
|
receipt: existing,
|
|
951
|
+
authorization: existing.authorization,
|
|
751
952
|
workId: existing.workId,
|
|
752
953
|
};
|
|
753
954
|
}
|
|
754
955
|
|
|
755
956
|
const createdAt = new Date().toISOString();
|
|
756
957
|
const receiptId = `PLG-${Date.now()}-${createHash('sha256').update(request.requestId).digest('hex').slice(0, 8)}`;
|
|
958
|
+
const capabilityId = reusableCapabilityId(manifest, action);
|
|
959
|
+
let authorization = initialAuthorizationEvidence(manifest, action);
|
|
960
|
+
let authorizationContext: AssistantPluginAuthorizationContext | undefined;
|
|
961
|
+
let activeAuthorizationGrant: PluginCapabilityAuthorizationGrant | undefined;
|
|
757
962
|
const requiresLocalEffectWork = localSystemActionRequiresWork(
|
|
758
963
|
repository,
|
|
759
964
|
request.pluginId,
|
|
760
965
|
request.actionId,
|
|
761
966
|
normalizedArgs,
|
|
762
967
|
);
|
|
968
|
+
const boundRemoteWork = action.risk === 'remote_write'
|
|
969
|
+
? remoteEffectWorkForPluginAction(controllerHome, repository, action, request)
|
|
970
|
+
: undefined;
|
|
971
|
+
const attributedWork = !requiresLocalEffectWork
|
|
972
|
+
? attributedWorkForPluginAction(controllerHome, repository, action, request)
|
|
973
|
+
: undefined;
|
|
763
974
|
const acceptedWork = requiresLocalEffectWork
|
|
764
975
|
? acceptSubmittedWorkContract(controllerHome, {
|
|
765
976
|
requestId: request.requestId,
|
|
@@ -812,10 +1023,63 @@ export async function submitAssistantPluginAction(
|
|
|
812
1023
|
receiptId,
|
|
813
1024
|
risk: action.risk,
|
|
814
1025
|
confirmation: action.confirmation,
|
|
1026
|
+
capabilityId,
|
|
1027
|
+
authorizationReuseSupported: action.confirmation === 'authorization' && Boolean(adapter.resolveAuthorizationContext),
|
|
815
1028
|
},
|
|
816
1029
|
});
|
|
817
1030
|
|
|
818
1031
|
try {
|
|
1032
|
+
if (action.confirmation === 'authorization'
|
|
1033
|
+
&& adapter.resolveAuthorizationContext
|
|
1034
|
+
&& originMayEstablishCapabilityAuthorization(request.origin)) {
|
|
1035
|
+
authorizationContext = await adapter.resolveAuthorizationContext({
|
|
1036
|
+
controllerHome,
|
|
1037
|
+
repoId: repository.repoId,
|
|
1038
|
+
repoRoot: repository.canonicalRoot,
|
|
1039
|
+
pluginId: request.pluginId,
|
|
1040
|
+
actionId: request.actionId,
|
|
1041
|
+
requestId: request.requestId,
|
|
1042
|
+
args: normalizedArgs,
|
|
1043
|
+
origin: request.origin,
|
|
1044
|
+
jobId: receiptId,
|
|
1045
|
+
timeoutMs: request.timeoutMs,
|
|
1046
|
+
signal: request.signal,
|
|
1047
|
+
});
|
|
1048
|
+
if (authorizationContext) {
|
|
1049
|
+
authorization = {
|
|
1050
|
+
source: 'host_permission_model',
|
|
1051
|
+
reusable: false,
|
|
1052
|
+
capabilityId,
|
|
1053
|
+
target: authorizationTargetSummary(authorizationContext),
|
|
1054
|
+
};
|
|
1055
|
+
try {
|
|
1056
|
+
activeAuthorizationGrant = findActivePluginCapabilityAuthorization(controllerHome, {
|
|
1057
|
+
ownerScope: pluginCapabilityAuthorizationOwnerScope(request.origin),
|
|
1058
|
+
repoId: repository.repoId,
|
|
1059
|
+
pluginId: request.pluginId,
|
|
1060
|
+
capabilityId,
|
|
1061
|
+
target: authorizationContext.target,
|
|
1062
|
+
scopes: action.scopes,
|
|
1063
|
+
risk: action.risk,
|
|
1064
|
+
});
|
|
1065
|
+
if (activeAuthorizationGrant) {
|
|
1066
|
+
authorization = {
|
|
1067
|
+
source: 'capability_grant',
|
|
1068
|
+
reusable: true,
|
|
1069
|
+
capabilityId,
|
|
1070
|
+
target: authorizationTargetSummary(authorizationContext),
|
|
1071
|
+
grantId: activeAuthorizationGrant.grantId,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
authorization = {
|
|
1076
|
+
...authorization,
|
|
1077
|
+
lookupError: authorizationError(error),
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
819
1083
|
const result = await executeAssistantPluginAction({
|
|
820
1084
|
controllerHome,
|
|
821
1085
|
repoId: repository.repoId,
|
|
@@ -832,7 +1096,52 @@ export async function submitAssistantPluginAction(
|
|
|
832
1096
|
? Date.now() + Math.max(1, Math.trunc(request.timeoutMs))
|
|
833
1097
|
: undefined,
|
|
834
1098
|
});
|
|
1099
|
+
if (action.confirmation === 'authorization'
|
|
1100
|
+
&& authorizationContext
|
|
1101
|
+
&& !activeAuthorizationGrant
|
|
1102
|
+
&& originMayEstablishCapabilityAuthorization(request.origin)) {
|
|
1103
|
+
try {
|
|
1104
|
+
const grant = recordPluginCapabilityAuthorization(controllerHome, {
|
|
1105
|
+
ownerScope: pluginCapabilityAuthorizationOwnerScope(request.origin),
|
|
1106
|
+
repoId: repository.repoId,
|
|
1107
|
+
pluginId: request.pluginId,
|
|
1108
|
+
capabilityId,
|
|
1109
|
+
target: authorizationContext.target,
|
|
1110
|
+
scopes: action.scopes,
|
|
1111
|
+
riskCeiling: action.risk,
|
|
1112
|
+
expiresInMinutes: authorizationContext.expiresInMinutes,
|
|
1113
|
+
});
|
|
1114
|
+
authorization = {
|
|
1115
|
+
source: 'host_permission_model',
|
|
1116
|
+
reusable: true,
|
|
1117
|
+
capabilityId,
|
|
1118
|
+
target: authorizationTargetSummary(authorizationContext),
|
|
1119
|
+
grantId: grant.grantId,
|
|
1120
|
+
established: true,
|
|
1121
|
+
...(authorization.lookupError ? { lookupError: authorization.lookupError } : {}),
|
|
1122
|
+
};
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
authorization = {
|
|
1125
|
+
...authorization,
|
|
1126
|
+
reusable: false,
|
|
1127
|
+
persistenceError: authorizationError(error),
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
835
1132
|
let resultWithLineage = result;
|
|
1133
|
+
if (boundRemoteWork) {
|
|
1134
|
+
const terminalRemoteEffect = action.remoteEffectWorkCompletion === 'terminal';
|
|
1135
|
+
resultWithLineage = {
|
|
1136
|
+
...result,
|
|
1137
|
+
work: {
|
|
1138
|
+
workId: boundRemoteWork.workId,
|
|
1139
|
+
workKind: 'remote_effect',
|
|
1140
|
+
...(terminalRemoteEffect ? { completionOutcome: 'completed_remote' as const } : {}),
|
|
1141
|
+
status: terminalRemoteEffect ? 'completed' : 'running',
|
|
1142
|
+
},
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
836
1145
|
if (acceptedWork) {
|
|
837
1146
|
const recordedAt = new Date().toISOString();
|
|
838
1147
|
const target = localEffectTarget(normalizedArgs, result);
|
|
@@ -877,8 +1186,15 @@ export async function submitAssistantPluginAction(
|
|
|
877
1186
|
semanticKey: key,
|
|
878
1187
|
status: 'succeeded',
|
|
879
1188
|
createdAt,
|
|
880
|
-
...(acceptedWork
|
|
1189
|
+
...(acceptedWork
|
|
1190
|
+
? { workId: acceptedWork.workId }
|
|
1191
|
+
: boundRemoteWork
|
|
1192
|
+
? { workId: boundRemoteWork.workId }
|
|
1193
|
+
: attributedWork
|
|
1194
|
+
? { workId: attributedWork.workId }
|
|
1195
|
+
: {}),
|
|
881
1196
|
origin: request.origin,
|
|
1197
|
+
authorization,
|
|
882
1198
|
result: resultWithLineage,
|
|
883
1199
|
};
|
|
884
1200
|
writeJsonAtomic(pluginActionReceiptPath(controllerHome, repository.repoId, receiptId), receipt);
|
|
@@ -889,6 +1205,7 @@ export async function submitAssistantPluginAction(
|
|
|
889
1205
|
semanticKey: key,
|
|
890
1206
|
createdAt,
|
|
891
1207
|
} satisfies PluginActionRequestIndex);
|
|
1208
|
+
if (boundRemoteWork) bindRemoteEffectReceiptToWork(controllerHome, repository, action, request, receipt);
|
|
892
1209
|
const nextManifest = getAssistantPluginManifest(controllerHome, repository, request.pluginId);
|
|
893
1210
|
return {
|
|
894
1211
|
manifest: nextManifest,
|
|
@@ -897,11 +1214,19 @@ export async function submitAssistantPluginAction(
|
|
|
897
1214
|
deduplicated: false,
|
|
898
1215
|
result: resultWithLineage,
|
|
899
1216
|
receipt,
|
|
900
|
-
|
|
1217
|
+
authorization,
|
|
1218
|
+
workId: acceptedWork?.workId ?? boundRemoteWork?.workId ?? attributedWork?.workId,
|
|
901
1219
|
};
|
|
902
1220
|
} catch (error) {
|
|
903
1221
|
const message = error instanceof Error ? error.message : String(error);
|
|
904
1222
|
const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'PLUGIN_ACTION_FAILED';
|
|
1223
|
+
if (boundRemoteWork) {
|
|
1224
|
+
appendWorkEvidence({ controllerHome, repoId: repository.repoId }, boundRemoteWork.workId, {
|
|
1225
|
+
title: 'typed remote plugin effect failed',
|
|
1226
|
+
summary: `${request.pluginId}/${request.actionId}: ${message}`.slice(0, 1_000),
|
|
1227
|
+
detailLevel: 'summary',
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
905
1230
|
if (acceptedWork) {
|
|
906
1231
|
const current = getWorkContract({ controllerHome, repoId: repository.repoId }, acceptedWork.workId);
|
|
907
1232
|
updateWorkContract({ controllerHome, repoId: repository.repoId }, acceptedWork.workId, {
|
|
@@ -926,8 +1251,15 @@ export async function submitAssistantPluginAction(
|
|
|
926
1251
|
semanticKey: key,
|
|
927
1252
|
status: 'failed',
|
|
928
1253
|
createdAt,
|
|
929
|
-
...(acceptedWork
|
|
1254
|
+
...(acceptedWork
|
|
1255
|
+
? { workId: acceptedWork.workId }
|
|
1256
|
+
: boundRemoteWork
|
|
1257
|
+
? { workId: boundRemoteWork.workId }
|
|
1258
|
+
: attributedWork
|
|
1259
|
+
? { workId: attributedWork.workId }
|
|
1260
|
+
: {}),
|
|
930
1261
|
origin: request.origin,
|
|
1262
|
+
authorization,
|
|
931
1263
|
error: { code, message },
|
|
932
1264
|
};
|
|
933
1265
|
writeJsonAtomic(pluginActionReceiptPath(controllerHome, repository.repoId, receiptId), receipt);
|
|
@@ -963,6 +1295,16 @@ export async function executeAssistantPluginAction(
|
|
|
963
1295
|
args: normalizedArgs,
|
|
964
1296
|
providerIdentityPrevalidated: manifestLookup.providerIdentityPrevalidated,
|
|
965
1297
|
});
|
|
1298
|
+
for (const affectedPluginId of adapter.affectedPluginIdsAfterAction?.(input.actionId, result) ?? []) {
|
|
1299
|
+
if (!affectedPluginId || affectedPluginId === input.pluginId) continue;
|
|
1300
|
+
invalidateAssistantPluginManifestCache(input.controllerHome, repository.repoId, affectedPluginId);
|
|
1301
|
+
const affectedAdapter = resolvePluginAdapter(input.controllerHome, affectedPluginId);
|
|
1302
|
+
if (affectedAdapter && adapterMatchesRepository(affectedAdapter, repository)) {
|
|
1303
|
+
syncAssistantPluginManifest(input.controllerHome, repository, affectedPluginId);
|
|
1304
|
+
} else {
|
|
1305
|
+
rmSync(manifestPath(input.controllerHome, repository.repoId, affectedPluginId), { force: true });
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
966
1308
|
const refreshManifest = adapter.shouldRefreshManifestAfterAction?.(input.actionId) ?? true;
|
|
967
1309
|
const nextManifest = refreshManifest
|
|
968
1310
|
? syncAssistantPluginManifest(input.controllerHome, repository, input.pluginId).manifest
|
|
@@ -4,8 +4,24 @@ export type AssistantPluginLifecycleState = 'enabled' | 'disabled' | 'degraded'
|
|
|
4
4
|
export type AssistantPluginHealthState = 'ready' | 'disabled' | 'degraded' | 'error';
|
|
5
5
|
export type AssistantPluginActionRisk = 'readonly' | 'workspace_write' | 'remote_write' | 'destructive';
|
|
6
6
|
export type AssistantPluginActionConfirmation = 'none' | 'authorization' | 'strong_confirmation';
|
|
7
|
+
export type AssistantPluginForegroundEffect = 'none' | 'possible' | 'required';
|
|
7
8
|
export type AssistantPluginResource = 'repo-state' | 'workspace' | 'remote' | 'git-refs' | 'provider-state';
|
|
8
9
|
|
|
10
|
+
export interface AssistantPluginAuthorizationTarget {
|
|
11
|
+
/** Stable provider resource class, for example ios-physical-device. */
|
|
12
|
+
kind: string;
|
|
13
|
+
/** Stable exact provider identity. Ephemeral request/session ids must never be used here. */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Optional provider identity fence. A changed fingerprint invalidates reuse. */
|
|
16
|
+
identityFingerprint?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AssistantPluginAuthorizationContext {
|
|
20
|
+
target: AssistantPluginAuthorizationTarget;
|
|
21
|
+
/** Optional grant lifetime. The grant authority applies its own upper bound. */
|
|
22
|
+
expiresInMinutes?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
export interface AssistantPluginPermissionScope {
|
|
10
26
|
scope: string;
|
|
11
27
|
mode: 'read' | 'write';
|
|
@@ -38,6 +54,14 @@ export interface AssistantPluginActionDescriptor {
|
|
|
38
54
|
defaultTimeoutMs: number;
|
|
39
55
|
cancellable: boolean;
|
|
40
56
|
idempotent: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Successful remote-write actions normally append a durable Work receipt only.
|
|
59
|
+
* Set terminal only when this action itself is authoritative semantic completion
|
|
60
|
+
* for a bound remote_effect Work.
|
|
61
|
+
*/
|
|
62
|
+
remoteEffectWorkCompletion?: 'intermediate' | 'terminal';
|
|
63
|
+
executionMode?: 'runtime' | 'lightweight_process';
|
|
64
|
+
foregroundEffect?: AssistantPluginForegroundEffect;
|
|
41
65
|
scopes: string[];
|
|
42
66
|
resourceClaims: AssistantPluginActionResourceClaim[];
|
|
43
67
|
argumentsSchema: Record<string, unknown>;
|
|
@@ -100,6 +124,8 @@ export interface AssistantPluginActionRequest {
|
|
|
100
124
|
pluginId: string;
|
|
101
125
|
actionId: string;
|
|
102
126
|
requestId: string;
|
|
127
|
+
/** Optional existing external-effect Work that owns this typed action receipt. */
|
|
128
|
+
workId?: string;
|
|
103
129
|
args: Record<string, unknown>;
|
|
104
130
|
timeoutMs?: number;
|
|
105
131
|
signal?: AbortSignal;
|
|
@@ -126,13 +152,24 @@ export interface AssistantPluginActionExecutionInput {
|
|
|
126
152
|
providerIdentityPrevalidated?: boolean;
|
|
127
153
|
}
|
|
128
154
|
|
|
129
|
-
export type AssistantPluginScope = 'repository' | 'controller';
|
|
155
|
+
export type AssistantPluginScope = 'repository' | 'controller' | 'controller_with_repository_overlay';
|
|
156
|
+
|
|
157
|
+
export interface AssistantPluginBuildContext {
|
|
158
|
+
controllerHome: string;
|
|
159
|
+
repoId: string;
|
|
160
|
+
repoRoot: string;
|
|
161
|
+
controllerScoped: boolean;
|
|
162
|
+
}
|
|
130
163
|
|
|
131
164
|
export interface AssistantPluginAdapter {
|
|
132
165
|
pluginId: string;
|
|
133
166
|
scope?: AssistantPluginScope;
|
|
134
|
-
buildManifest(previousRevision?: number, previousUpdatedAt?: string, repoRoot?: string): AssistantPluginManifest;
|
|
167
|
+
buildManifest(previousRevision?: number, previousUpdatedAt?: string, repoRoot?: string, context?: AssistantPluginBuildContext): AssistantPluginManifest;
|
|
168
|
+
/** Resolve an exact stable resource identity for reusable authorization-class actions. */
|
|
169
|
+
resolveAuthorizationContext?(input: AssistantPluginActionExecutionInput): Promise<AssistantPluginAuthorizationContext | undefined>;
|
|
135
170
|
executeAction(input: AssistantPluginActionExecutionInput): Promise<Record<string, unknown>>;
|
|
136
171
|
/** Defaults to true. External providers can defer routine health refresh until the normal manifest cache expires. */
|
|
137
172
|
shouldRefreshManifestAfterAction?(actionId: string): boolean;
|
|
173
|
+
/** Other plugin manifests whose canonical authority changed as a side effect of this action. */
|
|
174
|
+
affectedPluginIdsAfterAction?(actionId: string, result: Record<string, unknown>): string[];
|
|
138
175
|
}
|